From 370b7994a612d691576007875eb508e755ae322e Mon Sep 17 00:00:00 2001 From: Dmitriy Vasilev Date: Sat, 5 Sep 2026 17:25:15 +0700 Subject: [PATCH 01/15] =?UTF-8?q?feat(website):=20/specs=20=E2=80=94=20the?= =?UTF-8?q?=20.t27=20corpus,=20layer=20by=20layer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A library of all 497 t27 specs. Pick one and see it through every stage the compiler puts it through: tokens, AST, type check, HIR, and five codegen backends (Zig, Verilog, Verilog-from-HIR, C, Rust). The compiler is not reimplemented in TypeScript. bootstrap/src/compiler.rs is built to wasm32-unknown-unknown and run in the browser, so what the page draws is what t27c produces and cannot drift from it. Three things made that practical, none of them obvious up front: compiler.rs carries exactly one external `use`, its only filesystem calls sit inside #[cfg(test)], and the whole module is 173 KB gzipped with every backend linked. Measured across the corpus: 5ms average per spec, 68ms for the largest (1618 lines, 3738 nodes). The page uses parse_ast_full rather than parse_ast. The difference matters: error recovery can discard declarations while still reporting a successful parse, and a tree that quietly omitted half its file would be the wrong thing to draw. 49 of 497 specs (9.9%) lose content this way, so the page shows a banner naming exactly what was dropped. api/c_api_contract.t27 is the clearest case -- 25 discarded declarations, and the generated Verilog says `module unknown` because the module declaration was among them. Also surfaced: the type checker runs in no codegen path (t27c compile gates on it, t27c gen does not), which the Types tab states rather than glossing. Vendored, not fetched at runtime, so the page works offline and deterministically; scripts/sync-t27-specs.mjs regenerates the snapshot and records the exact t27 commit, which the page displays. Route is unlisted, matching the convention for /play and /quantum. gitignore: bare `demos/` and `runtime/` rules matched at any depth and were silently dropping six specs from the snapshot; negated for this path only. Gates: typecheck ratchet unchanged (179/26, new files zero errors), aria refs resolve, vite build clean, route added to the qa audit list. Verified in a real browser against the production build, not just compiled. --- .gitignore | 6 + apps/website/public/t27/manifest.json | 1 + .../website/public/t27/specs/account/auth.t27 | 277 ++ .../website/public/t27/specs/account/repo.t27 | 196 + .../public/t27/specs/account/schema.t27 | 226 ++ .../public/t27/specs/api/c_api_contract.t27 | 242 ++ .../public/t27/specs/api/sdk_contract.t27 | 406 ++ .../public/t27/specs/api/tri_net_api.t27 | 189 + .../public/t27/specs/ar/asp_solver.t27 | 555 +++ .../public/t27/specs/ar/coa_planning.t27 | 637 ++++ .../public/t27/specs/ar/composition.t27 | 570 +++ .../public/t27/specs/ar/datalog_engine.t27 | 349 ++ .../public/t27/specs/ar/explainability.t27 | 555 +++ .../public/t27/specs/ar/proof_trace.t27 | 312 ++ .../website/public/t27/specs/ar/restraint.t27 | 437 +++ .../public/t27/specs/ar/ternary_logic.t27 | 472 +++ apps/website/public/t27/specs/auth/config.t27 | 294 ++ .../t27/specs/automation/wrapup-auto.t27 | 50 + .../public/t27/specs/base/debounce.t27 | 98 + apps/website/public/t27/specs/base/ops.t27 | 1489 ++++++++ .../website/public/t27/specs/base/ring_32.t27 | 24 + apps/website/public/t27/specs/base/seed.t27 | 92 + .../public/t27/specs/base/ternary_add.t27 | 412 ++ .../t27/specs/base/ternary_encoding.t27 | 414 ++ .../public/t27/specs/base/ternary_memory.t27 | 416 ++ apps/website/public/t27/specs/base/types.t27 | 1679 ++++++++ .../t27/specs/benchmarks/bench_main.t27 | 94 + .../public/t27/specs/benchmarks/bench_nn.t27 | 165 + .../specs/benchmarks/gf16_bfloat16_nmse.t27 | 152 + .../specs/benchmarks/ternary_vs_binary.t27 | 91 + .../public/t27/specs/boards/arty_a7.t27 | 287 ++ .../public/t27/specs/boards/xc7a100t_full.t27 | 356 ++ .../t27/specs/boards/xc7a100t_minimal.t27 | 392 ++ apps/website/public/t27/specs/brain/brain.t27 | 57 + apps/website/public/t27/specs/brain/bus.t27 | 16 + .../public/t27/specs/brain/cognitive_loop.t27 | 16 + .../public/t27/specs/brain/neural_gamma.t27 | 88 + .../public/t27/specs/brain/phi_timing.t27 | 79 + .../public/t27/specs/brain/unified_state.t27 | 93 + apps/website/public/t27/specs/bus/pubsub.t27 | 669 ++++ apps/website/public/t27/specs/bus/schema.t27 | 656 ++++ .../public/t27/specs/cloud/railway_deploy.t27 | 362 ++ .../public/t27/specs/compiler/diagnostics.t27 | 147 + .../public/t27/specs/compiler/lexer.t27 | 582 +++ .../public/t27/specs/compiler/linker.t27 | 155 + .../t27/specs/compiler/meta_compile.t27 | 69 + .../t27/specs/compiler/mod_structure.t27 | 123 + .../public/t27/specs/compiler/optimizer.t27 | 285 ++ .../public/t27/specs/compiler/parser.t27 | 1618 ++++++++ .../public/t27/specs/compiler/pipeline.t27 | 169 + .../public/t27/specs/compiler/stdlib.t27 | 662 ++++ .../public/t27/specs/compiler/typechecker.t27 | 693 ++++ apps/website/public/t27/specs/config/load.t27 | 607 +++ .../public/t27/specs/config/migrate.t27 | 662 ++++ .../website/public/t27/specs/config/paths.t27 | 640 ++++ .../public/t27/specs/config/schema.t27 | 702 ++++ .../t27/specs/conformance/e2e_scenarios.t27 | 264 ++ .../demos/jones_topology_decision_gate.t27 | 346 ++ .../t27/specs/demos/jones_topology_filter.t27 | 317 ++ .../public/t27/specs/demos/simple_test.t27 | 14 + apps/website/public/t27/specs/depin/prove.t27 | 284 ++ .../t27/specs/enrichment/audio_overview.t27 | 162 + .../specs/enrichment/youtube_transcript.t27 | 605 +++ .../public/t27/specs/file/operations.t27 | 445 +++ apps/website/public/t27/specs/file/schema.t27 | 333 ++ .../website/public/t27/specs/file/watcher.t27 | 550 +++ .../public/t27/specs/fpga/apb_bridge.t27 | 347 ++ .../public/t27/specs/fpga/assembler.t27 | 346 ++ apps/website/public/t27/specs/fpga/axi4.t27 | 393 ++ .../specs/fpga/boards/arty_a7_integration.t27 | 136 + .../fpga/boards/qmtech_a100t_integration.t27 | 95 + .../website/public/t27/specs/fpga/bootrom.t27 | 139 + apps/website/public/t27/specs/fpga/bridge.t27 | 500 +++ .../public/t27/specs/fpga/clock_domain.t27 | 230 ++ .../public/t27/specs/fpga/crossopt.t27 | 125 + apps/website/public/t27/specs/fpga/cts.t27 | 224 ++ apps/website/public/t27/specs/fpga/dft.t27 | 263 ++ .../public/t27/specs/fpga/e2e_demo.t27 | 252 ++ apps/website/public/t27/specs/fpga/fifo.t27 | 364 ++ apps/website/public/t27/specs/fpga/formal.t27 | 352 ++ .../public/t27/specs/fpga/gf16_accel.t27 | 422 ++ apps/website/public/t27/specs/fpga/hir.t27 | 691 ++++ .../public/t27/specs/fpga/hw_types.t27 | 334 ++ apps/website/public/t27/specs/fpga/linker.t27 | 326 ++ apps/website/public/t27/specs/fpga/mac.t27 | 622 +++ apps/website/public/t27/specs/fpga/memory.t27 | 354 ++ .../public/t27/specs/fpga/partition.t27 | 305 ++ .../public/t27/specs/fpga/placement.t27 | 241 ++ apps/website/public/t27/specs/fpga/power.t27 | 229 ++ .../public/t27/specs/fpga/power_analysis.t27 | 452 +++ apps/website/public/t27/specs/fpga/router.t27 | 256 ++ .../public/t27/specs/fpga/simulator.t27 | 277 ++ apps/website/public/t27/specs/fpga/spi.t27 | 414 ++ apps/website/public/t27/specs/fpga/stdlib.t27 | 371 ++ .../public/t27/specs/fpga/ternary_isa.t27 | 542 +++ .../public/t27/specs/fpga/testbench.t27 | 270 ++ .../specs/fpga/testbench/apb_bridge_tb.t27 | 143 + .../t27/specs/fpga/testbench/assembler_tb.t27 | 110 + .../t27/specs/fpga/testbench/axi4_tb.t27 | 178 + .../t27/specs/fpga/testbench/bootrom_tb.t27 | 86 + .../t27/specs/fpga/testbench/bridge_tb.t27 | 123 + .../specs/fpga/testbench/clock_domain_tb.t27 | 108 + .../t27/specs/fpga/testbench/cts_tb.t27 | 118 + .../t27/specs/fpga/testbench/dft_tb.t27 | 129 + .../t27/specs/fpga/testbench/fifo_tb.t27 | 162 + .../t27/specs/fpga/testbench/formal_tb.t27 | 131 + .../specs/fpga/testbench/gf16_accel_tb.t27 | 135 + .../t27/specs/fpga/testbench/hir_tb.t27 | 104 + .../specs/fpga/testbench/integration_tb.t27 | 125 + .../t27/specs/fpga/testbench/linker_tb.t27 | 88 + .../t27/specs/fpga/testbench/mac_tb.t27 | 553 +++ .../t27/specs/fpga/testbench/memory_tb.t27 | 135 + .../t27/specs/fpga/testbench/partition_tb.t27 | 93 + .../t27/specs/fpga/testbench/placement_tb.t27 | 114 + .../fpga/testbench/power_analysis_tb.t27 | 129 + .../t27/specs/fpga/testbench/power_tb.t27 | 117 + .../t27/specs/fpga/testbench/router_tb.t27 | 111 + .../t27/specs/fpga/testbench/simulator_tb.t27 | 91 + .../t27/specs/fpga/testbench/spi_tb.t27 | 123 + .../t27/specs/fpga/testbench/stdlib_tb.t27 | 111 + .../specs/fpga/testbench/ternary_isa_tb.t27 | 147 + .../t27/specs/fpga/testbench/timing_tb.t27 | 97 + .../t27/specs/fpga/testbench/top_tb.t27 | 218 ++ .../t27/specs/fpga/testbench/uart_tb.t27 | 396 ++ .../testbench/vcd_conformance_compare_tb.t27 | 106 + .../t27/specs/fpga/testbench/vcd_trace_tb.t27 | 85 + apps/website/public/t27/specs/fpga/timing.t27 | 375 ++ .../public/t27/specs/fpga/top_level.t27 | 219 ++ apps/website/public/t27/specs/fpga/uart.t27 | 210 + .../specs/fpga/vcd_conformance_compare.t27 | 435 +++ .../public/t27/specs/fpga/vcd_trace.t27 | 291 ++ .../specs/fpga/verification/build_verify.t27 | 86 + apps/website/public/t27/specs/git/diff.t27 | 229 ++ .../public/t27/specs/git/operations.t27 | 175 + apps/website/public/t27/specs/git/schema.t27 | 228 ++ apps/website/public/t27/specs/git/status.t27 | 188 + apps/website/public/t27/specs/github/auth.t27 | 26 + .../public/t27/specs/github/comments.t27 | 17 + .../public/t27/specs/github/issues.t27 | 21 + apps/website/public/t27/specs/github/prs.t27 | 21 + .../t27/specs/github/tests/e2e_full_flow.t27 | 121 + .../t27/specs/graph/knowledge_graph.t27 | 405 ++ .../public/t27/specs/hslm/forward_pass.t27 | 305 ++ .../t27/specs/interop/gf_cross_language.t27 | 98 + .../public/t27/specs/isa/registers.t27 | 599 +++ .../t27/specs/isa/ternary_arithmetic.t27 | 500 +++ .../public/t27/specs/isa/ternary_bitwise.t27 | 403 ++ .../t27/specs/isa/ternary_control_flow.t27 | 391 ++ .../public/t27/specs/isa/ternary_deque.t27 | 497 +++ .../public/t27/specs/isa/ternary_encoding.t27 | 41 + .../public/t27/specs/isa/ternary_gates.t27 | 409 ++ .../public/t27/specs/isa/ternary_graph.t27 | 130 + .../public/t27/specs/isa/ternary_hash.t27 | 166 + .../public/t27/specs/isa/ternary_memory.t27 | 521 +++ .../specs/isa/ternary_pattern_matching.t27 | 172 + .../public/t27/specs/isa/ternary_search.t27 | 164 + .../public/t27/specs/isa/ternary_set.t27 | 181 + .../public/t27/specs/isa/ternary_shift.t27 | 419 ++ .../public/t27/specs/isa/ternary_sorting.t27 | 172 + .../public/t27/specs/isa/ternary_tree.t27 | 140 + apps/website/public/t27/specs/jit/jit.t27 | 874 +++++ apps/website/public/t27/specs/lsp/client.t27 | 504 +++ .../website/public/t27/specs/lsp/language.t27 | 526 +++ .../website/public/t27/specs/lsp/protocol.t27 | 680 ++++ apps/website/public/t27/specs/lsp/schema.t27 | 588 +++ apps/website/public/t27/specs/lsp/server.t27 | 554 +++ .../public/t27/specs/math/constants.t27 | 458 +++ .../public/t27/specs/math/e8_lie_algebra.t27 | 389 ++ .../public/t27/specs/math/gf_competitive.t27 | 121 + .../specs/math/pellis_precision_verify.t27 | 203 + .../t27/specs/math/phi_split_optimality.t27 | 335 ++ .../specs/math/phi_universal_attractor.t27 | 332 ++ .../t27/specs/math/property_test_template.t27 | 512 +++ .../public/t27/specs/math/radix_economy.t27 | 330 ++ .../public/t27/specs/math/sacred_physics.t27 | 460 +++ .../t27/specs/math/zamolodchikov_e8.t27 | 323 ++ .../public/t27/specs/memory/formula_embed.t27 | 201 + .../t27/specs/memory/memory_primitives.t27 | 179 + .../public/t27/specs/memory/notebooklm.t27 | 485 +++ .../t27/specs/memory/semantic_search.t27 | 140 + .../specs/ml/activation/elu_activation.t27 | 116 + .../specs/ml/activation/gelu_activation.t27 | 67 + .../ml/activation/gelu_approx_activation.t27 | 81 + .../ml/activation/leaky_relu_activation.t27 | 94 + .../specs/ml/activation/relu_activation.t27 | 61 + .../ml/activation/sigmoid_activation.t27 | 62 + .../ml/activation/silu_swish_activation.t27 | 87 + .../activation/silu_swish_vbt_activation.t27 | 15 + .../t27/specs/ml/activation/softmax.t27 | 82 + .../specs/ml/activation/tanh_activation.t27 | 53 + .../t27/specs/ml/layers/avgpool2d_layer.t27 | 122 + .../t27/specs/ml/layers/batchnorm_layer.t27 | 110 + .../t27/specs/ml/layers/conv2d_layer.t27 | 91 + .../t27/specs/ml/layers/dense_layer.t27 | 89 + .../t27/specs/ml/layers/dropout_layer.t27 | 69 + .../t27/specs/ml/layers/embedding_layer.t27 | 120 + .../t27/specs/ml/layers/flatten_layer.t27 | 113 + .../t27/specs/ml/layers/layernorm_layer.t27 | 35 + .../t27/specs/ml/layers/maxpool2d_layer.t27 | 73 + .../specs/ml/layers/residual_connection.t27 | 26 + .../ml/loss/binary_crossentropy_loss.t27 | 119 + .../t27/specs/ml/loss/contrastive_loss.t27 | 34 + .../t27/specs/ml/loss/cross_entropy_loss.t27 | 81 + .../public/t27/specs/ml/loss/huber_loss.t27 | 34 + .../t27/specs/ml/loss/kl_divergence.t27 | 26 + .../public/t27/specs/ml/loss/mse_loss.t27 | 34 + .../public/t27/specs/ml/optimizer/adagrad.t27 | 112 + .../public/t27/specs/ml/optimizer/adam.t27 | 46 + .../public/t27/specs/ml/optimizer/adamw.t27 | 305 ++ .../public/t27/specs/ml/optimizer/lamb.t27 | 133 + .../t27/specs/ml/optimizer/lr_scheduler.t27 | 149 + .../public/t27/specs/ml/optimizer/rmsprop.t27 | 42 + .../public/t27/specs/ml/optimizer/sgd.t27 | 103 + .../t27/specs/ml/optimizer/sgd_momentum.t27 | 279 ++ .../public/t27/specs/ml/pathway/mlp.t27 | 111 + .../ml/recurrent/attention_mechanism.t27 | 94 + .../public/t27/specs/ml/recurrent/bilstm.t27 | 133 + .../t27/specs/ml/recurrent/gru_cell.t27 | 137 + .../t27/specs/ml/recurrent/lstm_cell.t27 | 141 + .../t27/specs/ml/recurrent/lstm_single.t27 | 126 + .../t27/specs/ml/recurrent/rnn_cell.t27 | 119 + .../t27/specs/ml/recurrent/self_attention.t27 | 56 + .../public/t27/specs/ml/recurrent/seq2seq.t27 | 120 + .../t27/specs/ml/rl/advantage_estimator.t27 | 127 + apps/website/public/t27/specs/ml/rl/dqn.t27 | 118 + .../t27/specs/ml/rl/dqn_target_network.t27 | 111 + .../public/t27/specs/ml/rl/ppo_actor.t27 | 154 + .../public/t27/specs/ml/rl/ppo_clip_loss.t27 | 140 + .../public/t27/specs/ml/rl/ppo_critic.t27 | 105 + .../public/t27/specs/ml/rl/sac_actor.t27 | 127 + .../public/t27/specs/ml/rl/sac_critic.t27 | 163 + .../specs/ml/transformer/encoder_block.t27 | 116 + .../t27/specs/ml/transformer/feed_forward.t27 | 436 +++ .../ml/transformer/feed_forward_network.t27 | 41 + .../t27/specs/ml/transformer/mha_block.t27 | 382 ++ .../ml/transformer/multi_head_attention.t27 | 37 + .../specs/ml/transformer/multi_head_attn.t27 | 463 +++ .../public/t27/specs/ml/transformer/norm.t27 | 477 +++ .../specs/ml/transformer/positional_enc.t27 | 378 ++ .../ml/transformer/positional_encoding.t27 | 41 + .../public/t27/specs/neural/forward_pass.t27 | 1094 ++++++ .../website/public/t27/specs/nn/attention.t27 | 633 +++ apps/website/public/t27/specs/nn/hslm.t27 | 631 +++ apps/website/public/t27/specs/nn/phi_rope.t27 | 46 + .../public/t27/specs/nn/sacred_attention.t27 | 49 + .../public/t27/specs/numeric/bigint.t27 | 390 ++ .../public/t27/specs/numeric/formats.t27 | 399 ++ .../website/public/t27/specs/numeric/gf12.t27 | 481 +++ .../website/public/t27/specs/numeric/gf16.t27 | 3391 +++++++++++++++++ .../website/public/t27/specs/numeric/gf20.t27 | 468 +++ .../website/public/t27/specs/numeric/gf24.t27 | 468 +++ .../website/public/t27/specs/numeric/gf32.t27 | 479 +++ apps/website/public/t27/specs/numeric/gf4.t27 | 305 ++ apps/website/public/t27/specs/numeric/gf8.t27 | 521 +++ .../t27/specs/numeric/gf_competitive.t27 | 107 + .../t27/specs/numeric/goldenfloat_family.t27 | 410 ++ .../t27/specs/numeric/pellis_verify.t27 | 64 + .../public/t27/specs/numeric/phi_ratio.t27 | 652 ++++ .../t27/specs/numeric/requant_boundary.t27 | 216 ++ apps/website/public/t27/specs/numeric/tf3.t27 | 1661 ++++++++ .../specs/numeric/trinity_numeric_surface.t27 | 37 + .../t27/specs/physics/chimera_best_gamma.t27 | 19 + .../t27/specs/physics/e8_lqg_bridge.t27 | 112 + .../t27/specs/physics/formula_discovery.t27 | 157 + .../t27/specs/physics/formula_registry.t27 | 212 ++ .../t27/specs/physics/gamma-conflict.t27 | 255 ++ .../t27/specs/physics/gamma_conjecture.t27 | 276 ++ .../public/t27/specs/physics/gi1_analysis.t27 | 237 ++ .../t27/specs/physics/hslm_benchmark.t27 | 89 + .../t27/specs/physics/lqg_cs_bridge.t27 | 140 + .../public/t27/specs/physics/lqg_entropy.t27 | 66 + .../t27/specs/physics/p2_brain_physics.t27 | 164 + .../t27/specs/physics/pellis-formulas.t27 | 66 + .../public/t27/specs/physics/quantum.t27 | 71 + .../t27/specs/physics/sacred_verification.t27 | 604 +++ .../t27/specs/physics/su2_chern_simons.t27 | 348 ++ .../physics/zamolodchikov_4d_conjecture.t27 | 253 ++ .../public/t27/specs/pins/emitter_xdc.t27 | 294 ++ apps/website/public/t27/specs/pins/ir.t27 | 390 ++ apps/website/public/t27/specs/pins/parser.t27 | 583 +++ .../public/t27/specs/pipeline/benchmarks.t27 | 85 + .../public/t27/specs/pipeline/e2e_test.t27 | 150 + .../t27/specs/pipeline/experience_save.t27 | 126 + .../t27/specs/portable/relay_observer.t27 | 650 ++++ .../public/t27/specs/provider/adapters.t27 | 618 +++ .../public/t27/specs/provider/schema.t27 | 604 +++ .../public/t27/specs/provider/stream.t27 | 611 +++ .../public/t27/specs/provider/transform.t27 | 521 +++ .../t27/specs/queen/brain_summaries.t27 | 394 ++ apps/website/public/t27/specs/queen/lotus.t27 | 803 ++++ .../public/t27/specs/queen/task_analysis.t27 | 177 + .../public/t27/specs/runtime/execute.t27 | 734 ++++ .../public/t27/specs/runtime/instance.t27 | 779 ++++ .../public/t27/specs/runtime/process.t27 | 761 ++++ .../public/t27/specs/sacred/cosmology.t27 | 15 + .../public/t27/specs/sacred/dark_matter.t27 | 15 + .../public/t27/specs/sacred/gravity.t27 | 15 + .../public/t27/specs/sacred/monopoles.t27 | 15 + .../public/t27/specs/sacred/quantum.t27 | 15 + .../t27/specs/sacred/quantum_gravity.t27 | 15 + .../t27/specs/sacred/sacred_constants.t27 | 23 + .../t27/specs/sacred/sacred_governance.t27 | 84 + .../t27/specs/sacred/sacred_identity.t27 | 76 + .../t27/specs/sacred/superconductivity.t27 | 15 + .../public/t27/specs/sandbox/health.t27 | 42 + .../t27/specs/sandbox/https_enforce.t27 | 223 ++ .../public/t27/specs/sandbox/modules.t27 | 21 + .../t27/specs/sandbox/orphan_detection.t27 | 256 ++ .../t27/specs/sandbox/session_timeout.t27 | 194 + .../public/t27/specs/server/agent-runner.t27 | 248 ++ apps/website/public/t27/specs/server/api.t27 | 147 + apps/website/public/t27/specs/server/http.t27 | 452 +++ apps/website/public/t27/specs/server/mdns.t27 | 566 +++ .../public/t27/specs/server/project.t27 | 205 + .../public/t27/specs/server/provider.t27 | 256 ++ .../public/t27/specs/server/router.t27 | 574 +++ .../public/t27/specs/server/routes.t27 | 35 + .../public/t27/specs/server/session.t27 | 272 ++ apps/website/public/t27/specs/server/sse.t27 | 562 +++ apps/website/public/t27/specs/server/vm.t27 | 592 +++ .../public/t27/specs/shell/environment.t27 | 303 ++ .../public/t27/specs/shell/process.t27 | 304 ++ .../website/public/t27/specs/shell/schema.t27 | 405 ++ apps/website/public/t27/specs/storage/kv.t27 | 158 + .../website/public/t27/specs/storage/lock.t27 | 178 + .../public/t27/specs/storage/migrate.t27 | 192 + .../public/t27/specs/storage/schema.t27 | 145 + apps/website/public/t27/specs/sync/index.t27 | 778 ++++ apps/website/public/t27/specs/sync/schema.t27 | 721 ++++ .../public/t27/specs/ternary/bigint.t27 | 1440 +++++++ .../t27/specs/ternary/hybrid_arithmetic.t27 | 491 +++ .../t27/specs/ternary/hybrid_bigint.t27 | 1064 ++++++ .../public/t27/specs/ternary/packed_trit.t27 | 429 +++ .../public/t27/specs/test_framework/core.t27 | 317 ++ .../test_framework/graph_drift_detection.t27 | 650 ++++ .../test_framework/property_test_template.t27 | 433 +++ .../t27/specs/test_framework/runner.t27 | 618 +++ .../test_framework/verilog_bench_harness.t27 | 460 +++ .../public/t27/specs/tools/registry.t27 | 522 +++ .../website/public/t27/specs/tools/schema.t27 | 522 +++ .../t27/specs/tools/tri_to_t27_converter.t27 | 405 ++ .../public/t27/specs/tri/agent/agent_run.t27 | 15 + .../public/t27/specs/tri/agent/agents.t27 | 15 + .../specs/tri/agent/autonomous_lifecycle.t27 | 72 + .../specs/tri/agent/autonomous_universe.t27 | 15 + .../t27/specs/tri/agent/eternal_monitor.t27 | 69 + .../t27/specs/tri/agent/experience_hooks.t27 | 15 + .../t27/specs/tri/agent/faculty_board.t27 | 62 + .../t27/specs/tri/agent/governance_agent.t27 | 97 + .../public/t27/specs/tri/agent/handoff.t27 | 66 + .../public/t27/specs/tri/agent/memory.t27 | 15 + .../t27/specs/tri/agent/swarm_agents.t27 | 142 + .../t27/specs/tri/collections/array.t27 | 122 + .../t27/specs/tri/collections/bitmap.t27 | 100 + .../t27/specs/tri/collections/bitset.t27 | 96 + .../t27/specs/tri/collections/bitvector.t27 | 92 + .../t27/specs/tri/collections/btree.t27 | 84 + .../specs/tri/collections/circular_buffer.t27 | 78 + .../t27/specs/tri/collections/context.t27 | 15 + .../t27/specs/tri/collections/deque.t27 | 76 + .../t27/specs/tri/collections/either.t27 | 74 + .../t27/specs/tri/collections/interval.t27 | 85 + .../t27/specs/tri/collections/linked_list.t27 | 83 + .../public/t27/specs/tri/collections/list.t27 | 96 + .../specs/tri/collections/lockfree_stack.t27 | 69 + .../public/t27/specs/tri/collections/lru.t27 | 56 + .../t27/specs/tri/collections/lru_cache.t27 | 67 + .../public/t27/specs/tri/collections/map.t27 | 72 + .../t27/specs/tri/collections/maybe.t27 | 80 + .../t27/specs/tri/collections/namespace.t27 | 57 + .../t27/specs/tri/collections/option.t27 | 78 + .../specs/tri/collections/priority_queue.t27 | 77 + .../t27/specs/tri/collections/queue.t27 | 69 + .../t27/specs/tri/collections/result.t27 | 78 + .../t27/specs/tri/collections/ring_buffer.t27 | 100 + .../public/t27/specs/tri/collections/set.t27 | 64 + .../t27/specs/tri/collections/skip_list.t27 | 68 + .../t27/specs/tri/collections/stack.t27 | 70 + .../t27/specs/tri/collections/state.t27 | 64 + .../t27/specs/tri/collections/tuple.t27 | 71 + .../t27/specs/tri/collections/variant.t27 | 62 + .../public/t27/specs/tri/crypto/base32.t27 | 77 + .../public/t27/specs/tri/crypto/base64.t27 | 96 + .../public/t27/specs/tri/crypto/crypto.t27 | 55 + .../public/t27/specs/tri/crypto/ecc.t27 | 88 + .../public/t27/specs/tri/crypto/hex.t27 | 63 + .../public/t27/specs/tri/crypto/hmac.t27 | 55 + .../t27/specs/tri/crypto/reed_solomon.t27 | 45 + .../public/t27/specs/tri/crypto/rsa.t27 | 82 + .../public/t27/specs/tri/crypto/sha256.t27 | 66 + .../public/t27/specs/tri/encoding/bson.t27 | 69 + .../public/t27/specs/tri/encoding/csv.t27 | 71 + .../public/t27/specs/tri/encoding/html.t27 | 47 + .../public/t27/specs/tri/encoding/json.t27 | 66 + .../public/t27/specs/tri/encoding/markup.t27 | 52 + .../public/t27/specs/tri/encoding/mime.t27 | 56 + .../public/t27/specs/tri/encoding/msgpack.t27 | 92 + .../public/t27/specs/tri/encoding/xml.t27 | 47 + .../t27/specs/tri/graph/bellman_ford.t27 | 36 + .../public/t27/specs/tri/graph/dijkstra.t27 | 37 + .../t27/specs/tri/graph/disjoint_set.t27 | 92 + .../public/t27/specs/tri/graph/graph.t27 | 60 + .../public/t27/specs/tri/graph/graph_bfs.t27 | 112 + .../public/t27/specs/tri/graph/graph_dfs.t27 | 37 + .../public/t27/specs/tri/graph/prims_mst.t27 | 37 + .../t27/specs/tri/graph/topological_sort.t27 | 46 + .../public/t27/specs/tri/io/compress.t27 | 57 + .../public/t27/specs/tri/io/filesystem.t27 | 112 + apps/website/public/t27/specs/tri/io/fs.t27 | 72 + apps/website/public/t27/specs/tri/io/io.t27 | 66 + .../public/t27/specs/tri/io/reader.t27 | 64 + .../public/t27/specs/tri/io/writer.t27 | 65 + apps/website/public/t27/specs/tri/io/zip.t27 | 76 + .../public/t27/specs/tri/math/bezier.t27 | 74 + .../public/t27/specs/tri/math/constants.t27 | 127 + .../public/t27/specs/tri/math/math.t27 | 15 + .../public/t27/specs/tri/math/matrix.t27 | 95 + .../public/t27/specs/tri/math/measurement.t27 | 15 + .../public/t27/specs/tri/math/polynomial.t27 | 79 + .../public/t27/specs/tri/math/probability.t27 | 66 + .../public/t27/specs/tri/math/statistics.t27 | 76 + .../public/t27/specs/tri/net/async.t27 | 85 + .../public/t27/specs/tri/net/async_stream.t27 | 85 + .../public/t27/specs/tri/net/channel.t27 | 66 + .../public/t27/specs/tri/net/cloud.t27 | 15 + .../website/public/t27/specs/tri/net/http.t27 | 132 + apps/website/public/t27/specs/tri/net/net.t27 | 70 + apps/website/public/t27/specs/tri/net/url.t27 | 82 + .../t27/specs/tri/pipeline/batch_runner.t27 | 114 + .../public/t27/specs/tri/pipeline/builder.t27 | 106 + .../specs/tri/pipeline/cloud_orchestrator.t27 | 15 + .../public/t27/specs/tri/pipeline/codegen.t27 | 26 + .../t27/specs/tri/pipeline/pipeline.t27 | 15 + .../specs/tri/pipeline/pipeline_parallel.t27 | 49 + .../t27/specs/tri/pipeline/spec_parser.t27 | 15 + .../t27/specs/tri/pipeline/spec_writer.t27 | 45 + .../t27/specs/tri/pipeline/workflow.t27 | 30 + .../specs/tri/pipeline/workflow_executor.t27 | 15 + .../specs/tri/pipeline/workflow_parser.t27 | 15 + .../t27/specs/tri/search/aho_corasick.t27 | 98 + .../t27/specs/tri/search/bloom_filter.t27 | 57 + .../t27/specs/tri/search/boyer_moore.t27 | 71 + .../specs/tri/search/knuth_morris_pratt.t27 | 84 + .../public/t27/specs/tri/search/match.t27 | 67 + .../public/t27/specs/tri/search/pattern.t27 | 60 + .../t27/specs/tri/search/rabin_karp.t27 | 72 + .../public/t27/specs/tri/search/regex.t27 | 90 + .../t27/specs/tri/search/regex_advanced.t27 | 61 + .../public/t27/specs/tri/search/search.t27 | 62 + .../t27/specs/tri/sort/counting_sort.t27 | 26 + .../public/t27/specs/tri/sort/heap_sort.t27 | 26 + .../t27/specs/tri/sort/insertion_sort.t27 | 26 + .../public/t27/specs/tri/sort/merge_sort.t27 | 36 + .../public/t27/specs/tri/sort/quick_sort.t27 | 40 + .../public/t27/specs/tri/sort/radix_sort.t27 | 55 + .../t27/specs/tri/sort/selection_sort.t27 | 26 + .../public/t27/specs/tri/sort/shell_sort.t27 | 26 + .../public/t27/specs/tri/sort/sort.t27 | 56 + .../public/t27/specs/tri/sort/tim_sort.t27 | 26 + .../public/t27/specs/tri/trees/avl_tree.t27 | 75 + .../public/t27/specs/tri/trees/b_tree.t27 | 95 + .../t27/specs/tri/trees/fenwick_tree.t27 | 100 + .../public/t27/specs/tri/trees/kd_tree.t27 | 88 + .../public/t27/specs/tri/trees/octree.t27 | 102 + .../public/t27/specs/tri/trees/quadtree.t27 | 109 + .../t27/specs/tri/trees/red_black_tree.t27 | 80 + .../public/t27/specs/tri/trees/rtree.t27 | 87 + .../t27/specs/tri/trees/segment_tree.t27 | 66 + .../public/t27/specs/tri/trees/splay_tree.t27 | 75 + .../t27/specs/tri/trees/suffix_array.t27 | 82 + .../public/t27/specs/tri/trees/tree.t27 | 84 + .../public/t27/specs/tri/trees/trie.t27 | 92 + .../public/t27/specs/tri/utils/args.t27 | 85 + .../public/t27/specs/tri/utils/arrow_time.t27 | 15 + .../public/t27/specs/tri/utils/bytes.t27 | 105 + .../public/t27/specs/tri/utils/color.t27 | 94 + .../public/t27/specs/tri/utils/colors.t27 | 15 + .../public/t27/specs/tri/utils/config.t27 | 88 + .../public/t27/specs/tri/utils/error.t27 | 25 + .../public/t27/specs/tri/utils/exit_codes.t27 | 34 + .../public/t27/specs/tri/utils/help.t27 | 31 + .../public/t27/specs/tri/utils/logger.t27 | 65 + .../public/t27/specs/tri/utils/logging.t27 | 101 + .../public/t27/specs/tri/utils/random.t27 | 96 + .../public/t27/specs/tri/utils/string.t27 | 15 + .../public/t27/specs/tri/utils/template.t27 | 69 + .../public/t27/specs/tri/utils/terminal.t27 | 58 + .../public/t27/specs/tri/utils/text.t27 | 66 + .../public/t27/specs/tri/utils/time.t27 | 92 + .../public/t27/specs/tri/utils/utf8.t27 | 81 + .../public/t27/specs/tri/utils/version.t27 | 84 + .../public/t27/specs/vm/jit_semantics.t27 | 318 ++ .../public/t27/specs/vsa/jones_polynomial.t27 | 352 ++ apps/website/public/t27/specs/vsa/ops.t27 | 669 ++++ .../public/t27/specs/vsa/packed_vsa.t27 | 290 ++ apps/website/public/t27/specs/vsa/sdk.t27 | 625 +++ .../public/t27/specs/vsa/sequence_hdc.t27 | 341 ++ .../t27/specs/vsa/similarity_search.t27 | 466 +++ .../website/public/t27/specs/vsa/vsa_core.t27 | 1011 +++++ apps/website/public/t27/t27_compiler.wasm | Bin 0 -> 488709 bytes apps/website/qa/browser-audit.mjs | 2 +- apps/website/scripts/sync-t27-specs.mjs | 100 + apps/website/src/lib/t27Compiler.ts | 127 + apps/website/src/main.tsx | 3 + apps/website/src/pages/SpecExplorer.tsx | 878 +++++ 505 files changed, 117036 insertions(+), 1 deletion(-) create mode 100644 apps/website/public/t27/manifest.json create mode 100644 apps/website/public/t27/specs/account/auth.t27 create mode 100644 apps/website/public/t27/specs/account/repo.t27 create mode 100644 apps/website/public/t27/specs/account/schema.t27 create mode 100644 apps/website/public/t27/specs/api/c_api_contract.t27 create mode 100644 apps/website/public/t27/specs/api/sdk_contract.t27 create mode 100644 apps/website/public/t27/specs/api/tri_net_api.t27 create mode 100644 apps/website/public/t27/specs/ar/asp_solver.t27 create mode 100644 apps/website/public/t27/specs/ar/coa_planning.t27 create mode 100644 apps/website/public/t27/specs/ar/composition.t27 create mode 100644 apps/website/public/t27/specs/ar/datalog_engine.t27 create mode 100644 apps/website/public/t27/specs/ar/explainability.t27 create mode 100644 apps/website/public/t27/specs/ar/proof_trace.t27 create mode 100644 apps/website/public/t27/specs/ar/restraint.t27 create mode 100644 apps/website/public/t27/specs/ar/ternary_logic.t27 create mode 100644 apps/website/public/t27/specs/auth/config.t27 create mode 100644 apps/website/public/t27/specs/automation/wrapup-auto.t27 create mode 100644 apps/website/public/t27/specs/base/debounce.t27 create mode 100644 apps/website/public/t27/specs/base/ops.t27 create mode 100644 apps/website/public/t27/specs/base/ring_32.t27 create mode 100644 apps/website/public/t27/specs/base/seed.t27 create mode 100644 apps/website/public/t27/specs/base/ternary_add.t27 create mode 100644 apps/website/public/t27/specs/base/ternary_encoding.t27 create mode 100644 apps/website/public/t27/specs/base/ternary_memory.t27 create mode 100644 apps/website/public/t27/specs/base/types.t27 create mode 100644 apps/website/public/t27/specs/benchmarks/bench_main.t27 create mode 100644 apps/website/public/t27/specs/benchmarks/bench_nn.t27 create mode 100644 apps/website/public/t27/specs/benchmarks/gf16_bfloat16_nmse.t27 create mode 100644 apps/website/public/t27/specs/benchmarks/ternary_vs_binary.t27 create mode 100644 apps/website/public/t27/specs/boards/arty_a7.t27 create mode 100644 apps/website/public/t27/specs/boards/xc7a100t_full.t27 create mode 100644 apps/website/public/t27/specs/boards/xc7a100t_minimal.t27 create mode 100644 apps/website/public/t27/specs/brain/brain.t27 create mode 100644 apps/website/public/t27/specs/brain/bus.t27 create mode 100644 apps/website/public/t27/specs/brain/cognitive_loop.t27 create mode 100644 apps/website/public/t27/specs/brain/neural_gamma.t27 create mode 100644 apps/website/public/t27/specs/brain/phi_timing.t27 create mode 100644 apps/website/public/t27/specs/brain/unified_state.t27 create mode 100644 apps/website/public/t27/specs/bus/pubsub.t27 create mode 100644 apps/website/public/t27/specs/bus/schema.t27 create mode 100644 apps/website/public/t27/specs/cloud/railway_deploy.t27 create mode 100644 apps/website/public/t27/specs/compiler/diagnostics.t27 create mode 100644 apps/website/public/t27/specs/compiler/lexer.t27 create mode 100644 apps/website/public/t27/specs/compiler/linker.t27 create mode 100644 apps/website/public/t27/specs/compiler/meta_compile.t27 create mode 100644 apps/website/public/t27/specs/compiler/mod_structure.t27 create mode 100644 apps/website/public/t27/specs/compiler/optimizer.t27 create mode 100644 apps/website/public/t27/specs/compiler/parser.t27 create mode 100644 apps/website/public/t27/specs/compiler/pipeline.t27 create mode 100644 apps/website/public/t27/specs/compiler/stdlib.t27 create mode 100644 apps/website/public/t27/specs/compiler/typechecker.t27 create mode 100644 apps/website/public/t27/specs/config/load.t27 create mode 100644 apps/website/public/t27/specs/config/migrate.t27 create mode 100644 apps/website/public/t27/specs/config/paths.t27 create mode 100644 apps/website/public/t27/specs/config/schema.t27 create mode 100644 apps/website/public/t27/specs/conformance/e2e_scenarios.t27 create mode 100644 apps/website/public/t27/specs/demos/jones_topology_decision_gate.t27 create mode 100644 apps/website/public/t27/specs/demos/jones_topology_filter.t27 create mode 100644 apps/website/public/t27/specs/demos/simple_test.t27 create mode 100644 apps/website/public/t27/specs/depin/prove.t27 create mode 100644 apps/website/public/t27/specs/enrichment/audio_overview.t27 create mode 100644 apps/website/public/t27/specs/enrichment/youtube_transcript.t27 create mode 100644 apps/website/public/t27/specs/file/operations.t27 create mode 100644 apps/website/public/t27/specs/file/schema.t27 create mode 100644 apps/website/public/t27/specs/file/watcher.t27 create mode 100644 apps/website/public/t27/specs/fpga/apb_bridge.t27 create mode 100644 apps/website/public/t27/specs/fpga/assembler.t27 create mode 100644 apps/website/public/t27/specs/fpga/axi4.t27 create mode 100644 apps/website/public/t27/specs/fpga/boards/arty_a7_integration.t27 create mode 100644 apps/website/public/t27/specs/fpga/boards/qmtech_a100t_integration.t27 create mode 100644 apps/website/public/t27/specs/fpga/bootrom.t27 create mode 100644 apps/website/public/t27/specs/fpga/bridge.t27 create mode 100644 apps/website/public/t27/specs/fpga/clock_domain.t27 create mode 100644 apps/website/public/t27/specs/fpga/crossopt.t27 create mode 100644 apps/website/public/t27/specs/fpga/cts.t27 create mode 100644 apps/website/public/t27/specs/fpga/dft.t27 create mode 100644 apps/website/public/t27/specs/fpga/e2e_demo.t27 create mode 100644 apps/website/public/t27/specs/fpga/fifo.t27 create mode 100644 apps/website/public/t27/specs/fpga/formal.t27 create mode 100644 apps/website/public/t27/specs/fpga/gf16_accel.t27 create mode 100644 apps/website/public/t27/specs/fpga/hir.t27 create mode 100644 apps/website/public/t27/specs/fpga/hw_types.t27 create mode 100644 apps/website/public/t27/specs/fpga/linker.t27 create mode 100644 apps/website/public/t27/specs/fpga/mac.t27 create mode 100644 apps/website/public/t27/specs/fpga/memory.t27 create mode 100644 apps/website/public/t27/specs/fpga/partition.t27 create mode 100644 apps/website/public/t27/specs/fpga/placement.t27 create mode 100644 apps/website/public/t27/specs/fpga/power.t27 create mode 100644 apps/website/public/t27/specs/fpga/power_analysis.t27 create mode 100644 apps/website/public/t27/specs/fpga/router.t27 create mode 100644 apps/website/public/t27/specs/fpga/simulator.t27 create mode 100644 apps/website/public/t27/specs/fpga/spi.t27 create mode 100644 apps/website/public/t27/specs/fpga/stdlib.t27 create mode 100644 apps/website/public/t27/specs/fpga/ternary_isa.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/apb_bridge_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/assembler_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/axi4_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/bootrom_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/bridge_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/clock_domain_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/cts_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/dft_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/fifo_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/formal_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/gf16_accel_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/hir_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/integration_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/linker_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/mac_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/memory_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/partition_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/placement_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/power_analysis_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/power_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/router_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/simulator_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/spi_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/stdlib_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/ternary_isa_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/timing_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/top_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/uart_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/vcd_conformance_compare_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/testbench/vcd_trace_tb.t27 create mode 100644 apps/website/public/t27/specs/fpga/timing.t27 create mode 100644 apps/website/public/t27/specs/fpga/top_level.t27 create mode 100644 apps/website/public/t27/specs/fpga/uart.t27 create mode 100644 apps/website/public/t27/specs/fpga/vcd_conformance_compare.t27 create mode 100644 apps/website/public/t27/specs/fpga/vcd_trace.t27 create mode 100644 apps/website/public/t27/specs/fpga/verification/build_verify.t27 create mode 100644 apps/website/public/t27/specs/git/diff.t27 create mode 100644 apps/website/public/t27/specs/git/operations.t27 create mode 100644 apps/website/public/t27/specs/git/schema.t27 create mode 100644 apps/website/public/t27/specs/git/status.t27 create mode 100644 apps/website/public/t27/specs/github/auth.t27 create mode 100644 apps/website/public/t27/specs/github/comments.t27 create mode 100644 apps/website/public/t27/specs/github/issues.t27 create mode 100644 apps/website/public/t27/specs/github/prs.t27 create mode 100644 apps/website/public/t27/specs/github/tests/e2e_full_flow.t27 create mode 100644 apps/website/public/t27/specs/graph/knowledge_graph.t27 create mode 100644 apps/website/public/t27/specs/hslm/forward_pass.t27 create mode 100644 apps/website/public/t27/specs/interop/gf_cross_language.t27 create mode 100644 apps/website/public/t27/specs/isa/registers.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_arithmetic.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_bitwise.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_control_flow.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_deque.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_encoding.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_gates.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_graph.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_hash.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_memory.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_pattern_matching.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_search.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_set.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_shift.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_sorting.t27 create mode 100644 apps/website/public/t27/specs/isa/ternary_tree.t27 create mode 100644 apps/website/public/t27/specs/jit/jit.t27 create mode 100644 apps/website/public/t27/specs/lsp/client.t27 create mode 100644 apps/website/public/t27/specs/lsp/language.t27 create mode 100644 apps/website/public/t27/specs/lsp/protocol.t27 create mode 100644 apps/website/public/t27/specs/lsp/schema.t27 create mode 100644 apps/website/public/t27/specs/lsp/server.t27 create mode 100644 apps/website/public/t27/specs/math/constants.t27 create mode 100644 apps/website/public/t27/specs/math/e8_lie_algebra.t27 create mode 100644 apps/website/public/t27/specs/math/gf_competitive.t27 create mode 100644 apps/website/public/t27/specs/math/pellis_precision_verify.t27 create mode 100644 apps/website/public/t27/specs/math/phi_split_optimality.t27 create mode 100644 apps/website/public/t27/specs/math/phi_universal_attractor.t27 create mode 100644 apps/website/public/t27/specs/math/property_test_template.t27 create mode 100644 apps/website/public/t27/specs/math/radix_economy.t27 create mode 100644 apps/website/public/t27/specs/math/sacred_physics.t27 create mode 100644 apps/website/public/t27/specs/math/zamolodchikov_e8.t27 create mode 100644 apps/website/public/t27/specs/memory/formula_embed.t27 create mode 100644 apps/website/public/t27/specs/memory/memory_primitives.t27 create mode 100644 apps/website/public/t27/specs/memory/notebooklm.t27 create mode 100644 apps/website/public/t27/specs/memory/semantic_search.t27 create mode 100644 apps/website/public/t27/specs/ml/activation/elu_activation.t27 create mode 100644 apps/website/public/t27/specs/ml/activation/gelu_activation.t27 create mode 100644 apps/website/public/t27/specs/ml/activation/gelu_approx_activation.t27 create mode 100644 apps/website/public/t27/specs/ml/activation/leaky_relu_activation.t27 create mode 100644 apps/website/public/t27/specs/ml/activation/relu_activation.t27 create mode 100644 apps/website/public/t27/specs/ml/activation/sigmoid_activation.t27 create mode 100644 apps/website/public/t27/specs/ml/activation/silu_swish_activation.t27 create mode 100644 apps/website/public/t27/specs/ml/activation/silu_swish_vbt_activation.t27 create mode 100644 apps/website/public/t27/specs/ml/activation/softmax.t27 create mode 100644 apps/website/public/t27/specs/ml/activation/tanh_activation.t27 create mode 100644 apps/website/public/t27/specs/ml/layers/avgpool2d_layer.t27 create mode 100644 apps/website/public/t27/specs/ml/layers/batchnorm_layer.t27 create mode 100644 apps/website/public/t27/specs/ml/layers/conv2d_layer.t27 create mode 100644 apps/website/public/t27/specs/ml/layers/dense_layer.t27 create mode 100644 apps/website/public/t27/specs/ml/layers/dropout_layer.t27 create mode 100644 apps/website/public/t27/specs/ml/layers/embedding_layer.t27 create mode 100644 apps/website/public/t27/specs/ml/layers/flatten_layer.t27 create mode 100644 apps/website/public/t27/specs/ml/layers/layernorm_layer.t27 create mode 100644 apps/website/public/t27/specs/ml/layers/maxpool2d_layer.t27 create mode 100644 apps/website/public/t27/specs/ml/layers/residual_connection.t27 create mode 100644 apps/website/public/t27/specs/ml/loss/binary_crossentropy_loss.t27 create mode 100644 apps/website/public/t27/specs/ml/loss/contrastive_loss.t27 create mode 100644 apps/website/public/t27/specs/ml/loss/cross_entropy_loss.t27 create mode 100644 apps/website/public/t27/specs/ml/loss/huber_loss.t27 create mode 100644 apps/website/public/t27/specs/ml/loss/kl_divergence.t27 create mode 100644 apps/website/public/t27/specs/ml/loss/mse_loss.t27 create mode 100644 apps/website/public/t27/specs/ml/optimizer/adagrad.t27 create mode 100644 apps/website/public/t27/specs/ml/optimizer/adam.t27 create mode 100644 apps/website/public/t27/specs/ml/optimizer/adamw.t27 create mode 100644 apps/website/public/t27/specs/ml/optimizer/lamb.t27 create mode 100644 apps/website/public/t27/specs/ml/optimizer/lr_scheduler.t27 create mode 100644 apps/website/public/t27/specs/ml/optimizer/rmsprop.t27 create mode 100644 apps/website/public/t27/specs/ml/optimizer/sgd.t27 create mode 100644 apps/website/public/t27/specs/ml/optimizer/sgd_momentum.t27 create mode 100644 apps/website/public/t27/specs/ml/pathway/mlp.t27 create mode 100644 apps/website/public/t27/specs/ml/recurrent/attention_mechanism.t27 create mode 100644 apps/website/public/t27/specs/ml/recurrent/bilstm.t27 create mode 100644 apps/website/public/t27/specs/ml/recurrent/gru_cell.t27 create mode 100644 apps/website/public/t27/specs/ml/recurrent/lstm_cell.t27 create mode 100644 apps/website/public/t27/specs/ml/recurrent/lstm_single.t27 create mode 100644 apps/website/public/t27/specs/ml/recurrent/rnn_cell.t27 create mode 100644 apps/website/public/t27/specs/ml/recurrent/self_attention.t27 create mode 100644 apps/website/public/t27/specs/ml/recurrent/seq2seq.t27 create mode 100644 apps/website/public/t27/specs/ml/rl/advantage_estimator.t27 create mode 100644 apps/website/public/t27/specs/ml/rl/dqn.t27 create mode 100644 apps/website/public/t27/specs/ml/rl/dqn_target_network.t27 create mode 100644 apps/website/public/t27/specs/ml/rl/ppo_actor.t27 create mode 100644 apps/website/public/t27/specs/ml/rl/ppo_clip_loss.t27 create mode 100644 apps/website/public/t27/specs/ml/rl/ppo_critic.t27 create mode 100644 apps/website/public/t27/specs/ml/rl/sac_actor.t27 create mode 100644 apps/website/public/t27/specs/ml/rl/sac_critic.t27 create mode 100644 apps/website/public/t27/specs/ml/transformer/encoder_block.t27 create mode 100644 apps/website/public/t27/specs/ml/transformer/feed_forward.t27 create mode 100644 apps/website/public/t27/specs/ml/transformer/feed_forward_network.t27 create mode 100644 apps/website/public/t27/specs/ml/transformer/mha_block.t27 create mode 100644 apps/website/public/t27/specs/ml/transformer/multi_head_attention.t27 create mode 100644 apps/website/public/t27/specs/ml/transformer/multi_head_attn.t27 create mode 100644 apps/website/public/t27/specs/ml/transformer/norm.t27 create mode 100644 apps/website/public/t27/specs/ml/transformer/positional_enc.t27 create mode 100644 apps/website/public/t27/specs/ml/transformer/positional_encoding.t27 create mode 100644 apps/website/public/t27/specs/neural/forward_pass.t27 create mode 100644 apps/website/public/t27/specs/nn/attention.t27 create mode 100644 apps/website/public/t27/specs/nn/hslm.t27 create mode 100644 apps/website/public/t27/specs/nn/phi_rope.t27 create mode 100644 apps/website/public/t27/specs/nn/sacred_attention.t27 create mode 100644 apps/website/public/t27/specs/numeric/bigint.t27 create mode 100644 apps/website/public/t27/specs/numeric/formats.t27 create mode 100644 apps/website/public/t27/specs/numeric/gf12.t27 create mode 100644 apps/website/public/t27/specs/numeric/gf16.t27 create mode 100644 apps/website/public/t27/specs/numeric/gf20.t27 create mode 100644 apps/website/public/t27/specs/numeric/gf24.t27 create mode 100644 apps/website/public/t27/specs/numeric/gf32.t27 create mode 100644 apps/website/public/t27/specs/numeric/gf4.t27 create mode 100644 apps/website/public/t27/specs/numeric/gf8.t27 create mode 100644 apps/website/public/t27/specs/numeric/gf_competitive.t27 create mode 100644 apps/website/public/t27/specs/numeric/goldenfloat_family.t27 create mode 100644 apps/website/public/t27/specs/numeric/pellis_verify.t27 create mode 100644 apps/website/public/t27/specs/numeric/phi_ratio.t27 create mode 100644 apps/website/public/t27/specs/numeric/requant_boundary.t27 create mode 100644 apps/website/public/t27/specs/numeric/tf3.t27 create mode 100644 apps/website/public/t27/specs/numeric/trinity_numeric_surface.t27 create mode 100644 apps/website/public/t27/specs/physics/chimera_best_gamma.t27 create mode 100644 apps/website/public/t27/specs/physics/e8_lqg_bridge.t27 create mode 100644 apps/website/public/t27/specs/physics/formula_discovery.t27 create mode 100644 apps/website/public/t27/specs/physics/formula_registry.t27 create mode 100644 apps/website/public/t27/specs/physics/gamma-conflict.t27 create mode 100644 apps/website/public/t27/specs/physics/gamma_conjecture.t27 create mode 100644 apps/website/public/t27/specs/physics/gi1_analysis.t27 create mode 100644 apps/website/public/t27/specs/physics/hslm_benchmark.t27 create mode 100644 apps/website/public/t27/specs/physics/lqg_cs_bridge.t27 create mode 100644 apps/website/public/t27/specs/physics/lqg_entropy.t27 create mode 100644 apps/website/public/t27/specs/physics/p2_brain_physics.t27 create mode 100644 apps/website/public/t27/specs/physics/pellis-formulas.t27 create mode 100644 apps/website/public/t27/specs/physics/quantum.t27 create mode 100644 apps/website/public/t27/specs/physics/sacred_verification.t27 create mode 100644 apps/website/public/t27/specs/physics/su2_chern_simons.t27 create mode 100644 apps/website/public/t27/specs/physics/zamolodchikov_4d_conjecture.t27 create mode 100644 apps/website/public/t27/specs/pins/emitter_xdc.t27 create mode 100644 apps/website/public/t27/specs/pins/ir.t27 create mode 100644 apps/website/public/t27/specs/pins/parser.t27 create mode 100644 apps/website/public/t27/specs/pipeline/benchmarks.t27 create mode 100644 apps/website/public/t27/specs/pipeline/e2e_test.t27 create mode 100644 apps/website/public/t27/specs/pipeline/experience_save.t27 create mode 100644 apps/website/public/t27/specs/portable/relay_observer.t27 create mode 100644 apps/website/public/t27/specs/provider/adapters.t27 create mode 100644 apps/website/public/t27/specs/provider/schema.t27 create mode 100644 apps/website/public/t27/specs/provider/stream.t27 create mode 100644 apps/website/public/t27/specs/provider/transform.t27 create mode 100644 apps/website/public/t27/specs/queen/brain_summaries.t27 create mode 100644 apps/website/public/t27/specs/queen/lotus.t27 create mode 100644 apps/website/public/t27/specs/queen/task_analysis.t27 create mode 100644 apps/website/public/t27/specs/runtime/execute.t27 create mode 100644 apps/website/public/t27/specs/runtime/instance.t27 create mode 100644 apps/website/public/t27/specs/runtime/process.t27 create mode 100644 apps/website/public/t27/specs/sacred/cosmology.t27 create mode 100644 apps/website/public/t27/specs/sacred/dark_matter.t27 create mode 100644 apps/website/public/t27/specs/sacred/gravity.t27 create mode 100644 apps/website/public/t27/specs/sacred/monopoles.t27 create mode 100644 apps/website/public/t27/specs/sacred/quantum.t27 create mode 100644 apps/website/public/t27/specs/sacred/quantum_gravity.t27 create mode 100644 apps/website/public/t27/specs/sacred/sacred_constants.t27 create mode 100644 apps/website/public/t27/specs/sacred/sacred_governance.t27 create mode 100644 apps/website/public/t27/specs/sacred/sacred_identity.t27 create mode 100644 apps/website/public/t27/specs/sacred/superconductivity.t27 create mode 100644 apps/website/public/t27/specs/sandbox/health.t27 create mode 100644 apps/website/public/t27/specs/sandbox/https_enforce.t27 create mode 100644 apps/website/public/t27/specs/sandbox/modules.t27 create mode 100644 apps/website/public/t27/specs/sandbox/orphan_detection.t27 create mode 100644 apps/website/public/t27/specs/sandbox/session_timeout.t27 create mode 100644 apps/website/public/t27/specs/server/agent-runner.t27 create mode 100644 apps/website/public/t27/specs/server/api.t27 create mode 100644 apps/website/public/t27/specs/server/http.t27 create mode 100644 apps/website/public/t27/specs/server/mdns.t27 create mode 100644 apps/website/public/t27/specs/server/project.t27 create mode 100644 apps/website/public/t27/specs/server/provider.t27 create mode 100644 apps/website/public/t27/specs/server/router.t27 create mode 100644 apps/website/public/t27/specs/server/routes.t27 create mode 100644 apps/website/public/t27/specs/server/session.t27 create mode 100644 apps/website/public/t27/specs/server/sse.t27 create mode 100644 apps/website/public/t27/specs/server/vm.t27 create mode 100644 apps/website/public/t27/specs/shell/environment.t27 create mode 100644 apps/website/public/t27/specs/shell/process.t27 create mode 100644 apps/website/public/t27/specs/shell/schema.t27 create mode 100644 apps/website/public/t27/specs/storage/kv.t27 create mode 100644 apps/website/public/t27/specs/storage/lock.t27 create mode 100644 apps/website/public/t27/specs/storage/migrate.t27 create mode 100644 apps/website/public/t27/specs/storage/schema.t27 create mode 100644 apps/website/public/t27/specs/sync/index.t27 create mode 100644 apps/website/public/t27/specs/sync/schema.t27 create mode 100644 apps/website/public/t27/specs/ternary/bigint.t27 create mode 100644 apps/website/public/t27/specs/ternary/hybrid_arithmetic.t27 create mode 100644 apps/website/public/t27/specs/ternary/hybrid_bigint.t27 create mode 100644 apps/website/public/t27/specs/ternary/packed_trit.t27 create mode 100644 apps/website/public/t27/specs/test_framework/core.t27 create mode 100644 apps/website/public/t27/specs/test_framework/graph_drift_detection.t27 create mode 100644 apps/website/public/t27/specs/test_framework/property_test_template.t27 create mode 100644 apps/website/public/t27/specs/test_framework/runner.t27 create mode 100644 apps/website/public/t27/specs/test_framework/verilog_bench_harness.t27 create mode 100644 apps/website/public/t27/specs/tools/registry.t27 create mode 100644 apps/website/public/t27/specs/tools/schema.t27 create mode 100644 apps/website/public/t27/specs/tools/tri_to_t27_converter.t27 create mode 100644 apps/website/public/t27/specs/tri/agent/agent_run.t27 create mode 100644 apps/website/public/t27/specs/tri/agent/agents.t27 create mode 100644 apps/website/public/t27/specs/tri/agent/autonomous_lifecycle.t27 create mode 100644 apps/website/public/t27/specs/tri/agent/autonomous_universe.t27 create mode 100644 apps/website/public/t27/specs/tri/agent/eternal_monitor.t27 create mode 100644 apps/website/public/t27/specs/tri/agent/experience_hooks.t27 create mode 100644 apps/website/public/t27/specs/tri/agent/faculty_board.t27 create mode 100644 apps/website/public/t27/specs/tri/agent/governance_agent.t27 create mode 100644 apps/website/public/t27/specs/tri/agent/handoff.t27 create mode 100644 apps/website/public/t27/specs/tri/agent/memory.t27 create mode 100644 apps/website/public/t27/specs/tri/agent/swarm_agents.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/array.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/bitmap.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/bitset.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/bitvector.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/btree.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/circular_buffer.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/context.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/deque.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/either.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/interval.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/linked_list.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/list.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/lockfree_stack.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/lru.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/lru_cache.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/map.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/maybe.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/namespace.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/option.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/priority_queue.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/queue.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/result.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/ring_buffer.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/set.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/skip_list.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/stack.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/state.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/tuple.t27 create mode 100644 apps/website/public/t27/specs/tri/collections/variant.t27 create mode 100644 apps/website/public/t27/specs/tri/crypto/base32.t27 create mode 100644 apps/website/public/t27/specs/tri/crypto/base64.t27 create mode 100644 apps/website/public/t27/specs/tri/crypto/crypto.t27 create mode 100644 apps/website/public/t27/specs/tri/crypto/ecc.t27 create mode 100644 apps/website/public/t27/specs/tri/crypto/hex.t27 create mode 100644 apps/website/public/t27/specs/tri/crypto/hmac.t27 create mode 100644 apps/website/public/t27/specs/tri/crypto/reed_solomon.t27 create mode 100644 apps/website/public/t27/specs/tri/crypto/rsa.t27 create mode 100644 apps/website/public/t27/specs/tri/crypto/sha256.t27 create mode 100644 apps/website/public/t27/specs/tri/encoding/bson.t27 create mode 100644 apps/website/public/t27/specs/tri/encoding/csv.t27 create mode 100644 apps/website/public/t27/specs/tri/encoding/html.t27 create mode 100644 apps/website/public/t27/specs/tri/encoding/json.t27 create mode 100644 apps/website/public/t27/specs/tri/encoding/markup.t27 create mode 100644 apps/website/public/t27/specs/tri/encoding/mime.t27 create mode 100644 apps/website/public/t27/specs/tri/encoding/msgpack.t27 create mode 100644 apps/website/public/t27/specs/tri/encoding/xml.t27 create mode 100644 apps/website/public/t27/specs/tri/graph/bellman_ford.t27 create mode 100644 apps/website/public/t27/specs/tri/graph/dijkstra.t27 create mode 100644 apps/website/public/t27/specs/tri/graph/disjoint_set.t27 create mode 100644 apps/website/public/t27/specs/tri/graph/graph.t27 create mode 100644 apps/website/public/t27/specs/tri/graph/graph_bfs.t27 create mode 100644 apps/website/public/t27/specs/tri/graph/graph_dfs.t27 create mode 100644 apps/website/public/t27/specs/tri/graph/prims_mst.t27 create mode 100644 apps/website/public/t27/specs/tri/graph/topological_sort.t27 create mode 100644 apps/website/public/t27/specs/tri/io/compress.t27 create mode 100644 apps/website/public/t27/specs/tri/io/filesystem.t27 create mode 100644 apps/website/public/t27/specs/tri/io/fs.t27 create mode 100644 apps/website/public/t27/specs/tri/io/io.t27 create mode 100644 apps/website/public/t27/specs/tri/io/reader.t27 create mode 100644 apps/website/public/t27/specs/tri/io/writer.t27 create mode 100644 apps/website/public/t27/specs/tri/io/zip.t27 create mode 100644 apps/website/public/t27/specs/tri/math/bezier.t27 create mode 100644 apps/website/public/t27/specs/tri/math/constants.t27 create mode 100644 apps/website/public/t27/specs/tri/math/math.t27 create mode 100644 apps/website/public/t27/specs/tri/math/matrix.t27 create mode 100644 apps/website/public/t27/specs/tri/math/measurement.t27 create mode 100644 apps/website/public/t27/specs/tri/math/polynomial.t27 create mode 100644 apps/website/public/t27/specs/tri/math/probability.t27 create mode 100644 apps/website/public/t27/specs/tri/math/statistics.t27 create mode 100644 apps/website/public/t27/specs/tri/net/async.t27 create mode 100644 apps/website/public/t27/specs/tri/net/async_stream.t27 create mode 100644 apps/website/public/t27/specs/tri/net/channel.t27 create mode 100644 apps/website/public/t27/specs/tri/net/cloud.t27 create mode 100644 apps/website/public/t27/specs/tri/net/http.t27 create mode 100644 apps/website/public/t27/specs/tri/net/net.t27 create mode 100644 apps/website/public/t27/specs/tri/net/url.t27 create mode 100644 apps/website/public/t27/specs/tri/pipeline/batch_runner.t27 create mode 100644 apps/website/public/t27/specs/tri/pipeline/builder.t27 create mode 100644 apps/website/public/t27/specs/tri/pipeline/cloud_orchestrator.t27 create mode 100644 apps/website/public/t27/specs/tri/pipeline/codegen.t27 create mode 100644 apps/website/public/t27/specs/tri/pipeline/pipeline.t27 create mode 100644 apps/website/public/t27/specs/tri/pipeline/pipeline_parallel.t27 create mode 100644 apps/website/public/t27/specs/tri/pipeline/spec_parser.t27 create mode 100644 apps/website/public/t27/specs/tri/pipeline/spec_writer.t27 create mode 100644 apps/website/public/t27/specs/tri/pipeline/workflow.t27 create mode 100644 apps/website/public/t27/specs/tri/pipeline/workflow_executor.t27 create mode 100644 apps/website/public/t27/specs/tri/pipeline/workflow_parser.t27 create mode 100644 apps/website/public/t27/specs/tri/search/aho_corasick.t27 create mode 100644 apps/website/public/t27/specs/tri/search/bloom_filter.t27 create mode 100644 apps/website/public/t27/specs/tri/search/boyer_moore.t27 create mode 100644 apps/website/public/t27/specs/tri/search/knuth_morris_pratt.t27 create mode 100644 apps/website/public/t27/specs/tri/search/match.t27 create mode 100644 apps/website/public/t27/specs/tri/search/pattern.t27 create mode 100644 apps/website/public/t27/specs/tri/search/rabin_karp.t27 create mode 100644 apps/website/public/t27/specs/tri/search/regex.t27 create mode 100644 apps/website/public/t27/specs/tri/search/regex_advanced.t27 create mode 100644 apps/website/public/t27/specs/tri/search/search.t27 create mode 100644 apps/website/public/t27/specs/tri/sort/counting_sort.t27 create mode 100644 apps/website/public/t27/specs/tri/sort/heap_sort.t27 create mode 100644 apps/website/public/t27/specs/tri/sort/insertion_sort.t27 create mode 100644 apps/website/public/t27/specs/tri/sort/merge_sort.t27 create mode 100644 apps/website/public/t27/specs/tri/sort/quick_sort.t27 create mode 100644 apps/website/public/t27/specs/tri/sort/radix_sort.t27 create mode 100644 apps/website/public/t27/specs/tri/sort/selection_sort.t27 create mode 100644 apps/website/public/t27/specs/tri/sort/shell_sort.t27 create mode 100644 apps/website/public/t27/specs/tri/sort/sort.t27 create mode 100644 apps/website/public/t27/specs/tri/sort/tim_sort.t27 create mode 100644 apps/website/public/t27/specs/tri/trees/avl_tree.t27 create mode 100644 apps/website/public/t27/specs/tri/trees/b_tree.t27 create mode 100644 apps/website/public/t27/specs/tri/trees/fenwick_tree.t27 create mode 100644 apps/website/public/t27/specs/tri/trees/kd_tree.t27 create mode 100644 apps/website/public/t27/specs/tri/trees/octree.t27 create mode 100644 apps/website/public/t27/specs/tri/trees/quadtree.t27 create mode 100644 apps/website/public/t27/specs/tri/trees/red_black_tree.t27 create mode 100644 apps/website/public/t27/specs/tri/trees/rtree.t27 create mode 100644 apps/website/public/t27/specs/tri/trees/segment_tree.t27 create mode 100644 apps/website/public/t27/specs/tri/trees/splay_tree.t27 create mode 100644 apps/website/public/t27/specs/tri/trees/suffix_array.t27 create mode 100644 apps/website/public/t27/specs/tri/trees/tree.t27 create mode 100644 apps/website/public/t27/specs/tri/trees/trie.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/args.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/arrow_time.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/bytes.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/color.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/colors.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/config.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/error.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/exit_codes.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/help.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/logger.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/logging.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/random.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/string.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/template.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/terminal.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/text.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/time.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/utf8.t27 create mode 100644 apps/website/public/t27/specs/tri/utils/version.t27 create mode 100644 apps/website/public/t27/specs/vm/jit_semantics.t27 create mode 100644 apps/website/public/t27/specs/vsa/jones_polynomial.t27 create mode 100644 apps/website/public/t27/specs/vsa/ops.t27 create mode 100644 apps/website/public/t27/specs/vsa/packed_vsa.t27 create mode 100644 apps/website/public/t27/specs/vsa/sdk.t27 create mode 100644 apps/website/public/t27/specs/vsa/sequence_hdc.t27 create mode 100644 apps/website/public/t27/specs/vsa/similarity_search.t27 create mode 100644 apps/website/public/t27/specs/vsa/vsa_core.t27 create mode 100755 apps/website/public/t27/t27_compiler.wasm create mode 100644 apps/website/scripts/sync-t27-specs.mjs create mode 100644 apps/website/src/lib/t27Compiler.ts create mode 100644 apps/website/src/pages/SpecExplorer.tsx diff --git a/.gitignore b/.gitignore index 2b5897caf1..e040aea90c 100644 --- a/.gitignore +++ b/.gitignore @@ -423,3 +423,9 @@ fpga-flash !.trinity/issue_bindings.json !.trinity/souls/ !templates/SOUL.md + +# The vendored t27 spec corpus backs the /specs explorer -- it is data, not +# build output. The bare `demos/` and `runtime/` rules above match at any +# depth, which silently dropped six specs from the snapshot. +!apps/website/public/t27/specs/demos/ +!apps/website/public/t27/specs/runtime/ diff --git a/apps/website/public/t27/manifest.json b/apps/website/public/t27/manifest.json new file mode 100644 index 0000000000..72393cc1ef --- /dev/null +++ b/apps/website/public/t27/manifest.json @@ -0,0 +1 @@ +{"generatedFrom":{"repo":"gHashTag/t27","commit":"8d980d937fcd9a7044a8ffa9e7eb3cbc63e0daf5","shortCommit":"8d980d937","specsOrCompilerDirty":false},"wasmBytes":488709,"specCount":497,"totalLines":116410,"categories":{"tri":150,"fpga":66,"ml":60,"isa":16,"numeric":16,"physics":16,"server":11,"compiler":10,"math":10,"sacred":10,"ar":8,"base":8,"vsa":7,"brain":6,"github":5,"lsp":5,"sandbox":5,"test_framework":5,"benchmarks":4,"config":4,"git":4,"memory":4,"nn":4,"provider":4,"storage":4,"ternary":4,"account":3,"api":3,"boards":3,"demos":3,"file":3,"pins":3,"pipeline":3,"queen":3,"runtime":3,"shell":3,"tools":3,"bus":2,"enrichment":2,"sync":2,"auth":1,"automation":1,"cloud":1,"conformance":1,"depin":1,"graph":1,"hslm":1,"interop":1,"jit":1,"neural":1,"portable":1,"vm":1},"specs":[{"path":"account/auth.t27","category":"account","name":"auth","module":"AccountAuth","lines":278,"bytes":9231},{"path":"account/repo.t27","category":"account","name":"repo","module":"AccountRepo","lines":197,"bytes":5474},{"path":"account/schema.t27","category":"account","name":"schema","module":"Account","lines":227,"bytes":6623},{"path":"api/c_api_contract.t27","category":"api","name":"c_api_contract","module":null,"lines":243,"bytes":7087},{"path":"api/sdk_contract.t27","category":"api","name":"sdk_contract","module":null,"lines":407,"bytes":10914},{"path":"api/tri_net_api.t27","category":"api","name":"tri_net_api","module":null,"lines":190,"bytes":5445},{"path":"ar/asp_solver.t27","category":"ar","name":"asp_solver","module":null,"lines":556,"bytes":16355},{"path":"ar/coa_planning.t27","category":"ar","name":"coa_planning","module":null,"lines":638,"bytes":20107},{"path":"ar/composition.t27","category":"ar","name":"composition","module":null,"lines":571,"bytes":19109},{"path":"ar/datalog_engine.t27","category":"ar","name":"datalog_engine","module":null,"lines":350,"bytes":10935},{"path":"ar/explainability.t27","category":"ar","name":"explainability","module":null,"lines":556,"bytes":16253},{"path":"ar/proof_trace.t27","category":"ar","name":"proof_trace","module":null,"lines":313,"bytes":9671},{"path":"ar/restraint.t27","category":"ar","name":"restraint","module":null,"lines":438,"bytes":14096},{"path":"ar/ternary_logic.t27","category":"ar","name":"ternary_logic","module":null,"lines":473,"bytes":13762},{"path":"auth/config.t27","category":"auth","name":"config","module":"AuthConfig","lines":295,"bytes":9593},{"path":"automation/wrapup-auto.t27","category":"automation","name":"wrapup-auto","module":"automation","lines":51,"bytes":1625},{"path":"base/debounce.t27","category":"base","name":"debounce","module":"base","lines":99,"bytes":2655},{"path":"base/ops.t27","category":"base","name":"ops","module":"tritype","lines":1490,"bytes":47606},{"path":"base/ring_32.t27","category":"base","name":"ring_32","module":"base","lines":25,"bytes":596},{"path":"base/seed.t27","category":"base","name":"seed","module":"seed","lines":93,"bytes":2627},{"path":"base/ternary_add.t27","category":"base","name":"ternary_add","module":"ternary_add","lines":413,"bytes":14371},{"path":"base/ternary_encoding.t27","category":"base","name":"ternary_encoding","module":"TernaryEncoding","lines":415,"bytes":13711},{"path":"base/ternary_memory.t27","category":"base","name":"ternary_memory","module":"TernaryMemory","lines":417,"bytes":13794},{"path":"base/types.t27","category":"base","name":"types","module":"tritype","lines":1680,"bytes":55612},{"path":"benchmarks/bench_main.t27","category":"benchmarks","name":"bench_main","module":null,"lines":95,"bytes":2523},{"path":"benchmarks/bench_nn.t27","category":"benchmarks","name":"bench_nn","module":null,"lines":166,"bytes":3216},{"path":"benchmarks/gf16_bfloat16_nmse.t27","category":"benchmarks","name":"gf16_bfloat16_nmse","module":null,"lines":153,"bytes":4300},{"path":"benchmarks/ternary_vs_binary.t27","category":"benchmarks","name":"ternary_vs_binary","module":null,"lines":92,"bytes":1965},{"path":"boards/arty_a7.t27","category":"boards","name":"arty_a7","module":"BoardArtyA7","lines":288,"bytes":7435},{"path":"boards/xc7a100t_full.t27","category":"boards","name":"xc7a100t_full","module":"BoardFullXC7A100T","lines":357,"bytes":9813},{"path":"boards/xc7a100t_minimal.t27","category":"boards","name":"xc7a100t_minimal","module":"BoardMinimalXC7A100T","lines":393,"bytes":10805},{"path":"brain/brain.t27","category":"brain","name":"brain","module":null,"lines":58,"bytes":2756},{"path":"brain/bus.t27","category":"brain","name":"bus","module":"brain","lines":17,"bytes":419},{"path":"brain/cognitive_loop.t27","category":"brain","name":"cognitive_loop","module":"brain","lines":17,"bytes":515},{"path":"brain/neural_gamma.t27","category":"brain","name":"neural_gamma","module":null,"lines":89,"bytes":2008},{"path":"brain/phi_timing.t27","category":"brain","name":"phi_timing","module":"brain","lines":80,"bytes":3193},{"path":"brain/unified_state.t27","category":"brain","name":"unified_state","module":"brain","lines":94,"bytes":2948},{"path":"bus/pubsub.t27","category":"bus","name":"pubsub","module":"bus","lines":670,"bytes":17611},{"path":"bus/schema.t27","category":"bus","name":"schema","module":"bus","lines":657,"bytes":17052},{"path":"cloud/railway_deploy.t27","category":"cloud","name":"railway_deploy","module":"cloud","lines":363,"bytes":9070},{"path":"compiler/diagnostics.t27","category":"compiler","name":"diagnostics","module":"Diagnostics","lines":148,"bytes":4220},{"path":"compiler/lexer.t27","category":"compiler","name":"lexer","module":"Lexing","lines":583,"bytes":22503},{"path":"compiler/linker.t27","category":"compiler","name":"linker","module":"Linking","lines":156,"bytes":4048},{"path":"compiler/meta_compile.t27","category":"compiler","name":"meta_compile","module":"MetaCompilation","lines":70,"bytes":1886},{"path":"compiler/mod_structure.t27","category":"compiler","name":"mod_structure","module":"compiler","lines":124,"bytes":3540},{"path":"compiler/optimizer.t27","category":"compiler","name":"optimizer","module":"Optimization","lines":286,"bytes":8976},{"path":"compiler/parser.t27","category":"compiler","name":"parser","module":"Parsing","lines":1619,"bytes":55123},{"path":"compiler/pipeline.t27","category":"compiler","name":"pipeline","module":"Pipeline","lines":170,"bytes":4503},{"path":"compiler/stdlib.t27","category":"compiler","name":"stdlib","module":"Stdlib","lines":663,"bytes":16035},{"path":"compiler/typechecker.t27","category":"compiler","name":"typechecker","module":"TypeChecking","lines":694,"bytes":24448},{"path":"config/load.t27","category":"config","name":"load","module":"config","lines":608,"bytes":16362},{"path":"config/migrate.t27","category":"config","name":"migrate","module":"config","lines":663,"bytes":18030},{"path":"config/paths.t27","category":"config","name":"paths","module":"config","lines":641,"bytes":15895},{"path":"config/schema.t27","category":"config","name":"schema","module":"config","lines":703,"bytes":19048},{"path":"conformance/e2e_scenarios.t27","category":"conformance","name":"e2e_scenarios","module":null,"lines":265,"bytes":7796},{"path":"demos/jones_topology_decision_gate.t27","category":"demos","name":"jones_topology_decision_gate","module":"JonesTopologyDecisionGate","lines":347,"bytes":29767},{"path":"demos/jones_topology_filter.t27","category":"demos","name":"jones_topology_filter","module":"JonesTopologyFilter","lines":318,"bytes":10711},{"path":"demos/simple_test.t27","category":"demos","name":"simple_test","module":"SimpleTest","lines":15,"bytes":277},{"path":"depin/prove.t27","category":"depin","name":"prove","module":"depin","lines":285,"bytes":8731},{"path":"enrichment/audio_overview.t27","category":"enrichment","name":"audio_overview","module":"enrichment","lines":163,"bytes":5680},{"path":"enrichment/youtube_transcript.t27","category":"enrichment","name":"youtube_transcript","module":"enrichment","lines":606,"bytes":20285},{"path":"file/operations.t27","category":"file","name":"operations","module":"FileOperations","lines":446,"bytes":13273},{"path":"file/schema.t27","category":"file","name":"schema","module":"File","lines":334,"bytes":11786},{"path":"file/watcher.t27","category":"file","name":"watcher","module":"FileWatcher","lines":551,"bytes":16816},{"path":"fpga/apb_bridge.t27","category":"fpga","name":"apb_bridge","module":"ApbBridge","lines":348,"bytes":9210},{"path":"fpga/assembler.t27","category":"fpga","name":"assembler","module":"Assembler","lines":347,"bytes":8722},{"path":"fpga/axi4.t27","category":"fpga","name":"axi4","module":"Axi4","lines":394,"bytes":10640},{"path":"fpga/boards/arty_a7_integration.t27","category":"fpga","name":"arty_a7_integration","module":"ArtyA7_Integration","lines":137,"bytes":4043},{"path":"fpga/boards/qmtech_a100t_integration.t27","category":"fpga","name":"qmtech_a100t_integration","module":"QMTech_A100T_Integration","lines":96,"bytes":2568},{"path":"fpga/bootrom.t27","category":"fpga","name":"bootrom","module":"BootROM","lines":140,"bytes":4020},{"path":"fpga/bridge.t27","category":"fpga","name":"bridge","module":"FPGA_Bridge","lines":501,"bytes":17323},{"path":"fpga/clock_domain.t27","category":"fpga","name":"clock_domain","module":"ClockDomain","lines":231,"bytes":5913},{"path":"fpga/crossopt.t27","category":"fpga","name":"crossopt","module":"CrossOpt","lines":126,"bytes":3615},{"path":"fpga/cts.t27","category":"fpga","name":"cts","module":"CTS","lines":225,"bytes":5735},{"path":"fpga/dft.t27","category":"fpga","name":"dft","module":"DFT","lines":264,"bytes":6604},{"path":"fpga/e2e_demo.t27","category":"fpga","name":"e2e_demo","module":"E2eDemo","lines":253,"bytes":6432},{"path":"fpga/fifo.t27","category":"fpga","name":"fifo","module":"Fifo","lines":365,"bytes":9822},{"path":"fpga/formal.t27","category":"fpga","name":"formal","module":"Formal","lines":353,"bytes":9441},{"path":"fpga/gf16_accel.t27","category":"fpga","name":"gf16_accel","module":"Gf16Accel","lines":423,"bytes":11072},{"path":"fpga/hir.t27","category":"fpga","name":"hir","module":"Hir","lines":692,"bytes":19053},{"path":"fpga/hw_types.t27","category":"fpga","name":"hw_types","module":"HwTypes","lines":335,"bytes":8002},{"path":"fpga/linker.t27","category":"fpga","name":"linker","module":"Linker","lines":327,"bytes":8187},{"path":"fpga/mac.t27","category":"fpga","name":"mac","module":"ZeroDSP_MAC","lines":623,"bytes":22498},{"path":"fpga/memory.t27","category":"fpga","name":"memory","module":"Memory","lines":355,"bytes":9348},{"path":"fpga/partition.t27","category":"fpga","name":"partition","module":"Partition","lines":306,"bytes":9004},{"path":"fpga/placement.t27","category":"fpga","name":"placement","module":"Placement","lines":242,"bytes":6335},{"path":"fpga/power.t27","category":"fpga","name":"power","module":"Power","lines":230,"bytes":6278},{"path":"fpga/power_analysis.t27","category":"fpga","name":"power_analysis","module":"PowerAnalysis","lines":453,"bytes":12807},{"path":"fpga/router.t27","category":"fpga","name":"router","module":"Router","lines":257,"bytes":6620},{"path":"fpga/simulator.t27","category":"fpga","name":"simulator","module":"Simulator","lines":278,"bytes":7050},{"path":"fpga/spi.t27","category":"fpga","name":"spi","module":"SPI_Master","lines":415,"bytes":13093},{"path":"fpga/stdlib.t27","category":"fpga","name":"stdlib","module":"Stdlib","lines":372,"bytes":17315},{"path":"fpga/ternary_isa.t27","category":"fpga","name":"ternary_isa","module":"TernaryIsa","lines":543,"bytes":14462},{"path":"fpga/testbench.t27","category":"fpga","name":"testbench","module":"Testbench","lines":271,"bytes":6930},{"path":"fpga/testbench/apb_bridge_tb.t27","category":"fpga","name":"apb_bridge_tb","module":"APB_Bridge_Testbench","lines":144,"bytes":3247},{"path":"fpga/testbench/assembler_tb.t27","category":"fpga","name":"assembler_tb","module":"Assembler_Testbench","lines":111,"bytes":2952},{"path":"fpga/testbench/axi4_tb.t27","category":"fpga","name":"axi4_tb","module":"AXI4_Testbench","lines":179,"bytes":4359},{"path":"fpga/testbench/bootrom_tb.t27","category":"fpga","name":"bootrom_tb","module":"BootROM_Testbench","lines":87,"bytes":1856},{"path":"fpga/testbench/bridge_tb.t27","category":"fpga","name":"bridge_tb","module":"Bridge_Testbench","lines":124,"bytes":2796},{"path":"fpga/testbench/clock_domain_tb.t27","category":"fpga","name":"clock_domain_tb","module":"ClockDomain_Testbench","lines":109,"bytes":2439},{"path":"fpga/testbench/cts_tb.t27","category":"fpga","name":"cts_tb","module":"CTS_Testbench","lines":119,"bytes":2939},{"path":"fpga/testbench/dft_tb.t27","category":"fpga","name":"dft_tb","module":"DFT_Testbench","lines":130,"bytes":3050},{"path":"fpga/testbench/fifo_tb.t27","category":"fpga","name":"fifo_tb","module":"FIFO_Testbench","lines":163,"bytes":3606},{"path":"fpga/testbench/formal_tb.t27","category":"fpga","name":"formal_tb","module":"Formal_Testbench","lines":132,"bytes":3125},{"path":"fpga/testbench/gf16_accel_tb.t27","category":"fpga","name":"gf16_accel_tb","module":"GF16_Accel_Testbench","lines":136,"bytes":3024},{"path":"fpga/testbench/hir_tb.t27","category":"fpga","name":"hir_tb","module":"HIR_Testbench","lines":105,"bytes":2327},{"path":"fpga/testbench/integration_tb.t27","category":"fpga","name":"integration_tb","module":"Integration_Testbench","lines":126,"bytes":2971},{"path":"fpga/testbench/linker_tb.t27","category":"fpga","name":"linker_tb","module":"Linker_Testbench","lines":89,"bytes":2000},{"path":"fpga/testbench/mac_tb.t27","category":"fpga","name":"mac_tb","module":"MAC_Testbench","lines":554,"bytes":17571},{"path":"fpga/testbench/memory_tb.t27","category":"fpga","name":"memory_tb","module":"Memory_Testbench","lines":136,"bytes":3129},{"path":"fpga/testbench/partition_tb.t27","category":"fpga","name":"partition_tb","module":"Partition_Testbench","lines":94,"bytes":2266},{"path":"fpga/testbench/placement_tb.t27","category":"fpga","name":"placement_tb","module":"Placement_Testbench","lines":115,"bytes":2993},{"path":"fpga/testbench/power_analysis_tb.t27","category":"fpga","name":"power_analysis_tb","module":"PowerAnalysis_Testbench","lines":130,"bytes":3169},{"path":"fpga/testbench/power_tb.t27","category":"fpga","name":"power_tb","module":"Power_Testbench","lines":118,"bytes":2926},{"path":"fpga/testbench/router_tb.t27","category":"fpga","name":"router_tb","module":"Router_Testbench","lines":112,"bytes":2833},{"path":"fpga/testbench/simulator_tb.t27","category":"fpga","name":"simulator_tb","module":"Simulator_Testbench","lines":92,"bytes":2071},{"path":"fpga/testbench/spi_tb.t27","category":"fpga","name":"spi_tb","module":"SPI_Testbench","lines":124,"bytes":2963},{"path":"fpga/testbench/stdlib_tb.t27","category":"fpga","name":"stdlib_tb","module":"Stdlib_Testbench","lines":112,"bytes":2313},{"path":"fpga/testbench/ternary_isa_tb.t27","category":"fpga","name":"ternary_isa_tb","module":"Ternary_ISA_Testbench","lines":148,"bytes":3310},{"path":"fpga/testbench/timing_tb.t27","category":"fpga","name":"timing_tb","module":"Timing_Testbench","lines":98,"bytes":2410},{"path":"fpga/testbench/top_tb.t27","category":"fpga","name":"top_tb","module":"Top_Level_Testbench","lines":218,"bytes":5626},{"path":"fpga/testbench/uart_tb.t27","category":"fpga","name":"uart_tb","module":"UART_Testbench","lines":397,"bytes":11180},{"path":"fpga/testbench/vcd_conformance_compare_tb.t27","category":"fpga","name":"vcd_conformance_compare_tb","module":"VcdConformanceCompare_Testbench","lines":107,"bytes":2611},{"path":"fpga/testbench/vcd_trace_tb.t27","category":"fpga","name":"vcd_trace_tb","module":"VCD_Trace_Testbench","lines":86,"bytes":1929},{"path":"fpga/timing.t27","category":"fpga","name":"timing","module":"Timing","lines":376,"bytes":9419},{"path":"fpga/top_level.t27","category":"fpga","name":"top_level","module":"ZeroDSP_TopLevel","lines":220,"bytes":5551},{"path":"fpga/uart.t27","category":"fpga","name":"uart","module":"ZeroDSP_UART","lines":211,"bytes":5733},{"path":"fpga/vcd_conformance_compare.t27","category":"fpga","name":"vcd_conformance_compare","module":"VcdConformanceCompare","lines":436,"bytes":11816},{"path":"fpga/vcd_trace.t27","category":"fpga","name":"vcd_trace","module":"VcdTrace","lines":292,"bytes":7555},{"path":"fpga/verification/build_verify.t27","category":"fpga","name":"build_verify","module":"BuildVerify","lines":87,"bytes":2190},{"path":"git/diff.t27","category":"git","name":"diff","module":"GitDiff","lines":230,"bytes":7832},{"path":"git/operations.t27","category":"git","name":"operations","module":"GitOperations","lines":176,"bytes":5799},{"path":"git/schema.t27","category":"git","name":"schema","module":"Git","lines":229,"bytes":6205},{"path":"git/status.t27","category":"git","name":"status","module":"GitStatus","lines":189,"bytes":6535},{"path":"github/auth.t27","category":"github","name":"auth","module":"github","lines":27,"bytes":708},{"path":"github/comments.t27","category":"github","name":"comments","module":"github","lines":18,"bytes":398},{"path":"github/issues.t27","category":"github","name":"issues","module":"github","lines":22,"bytes":484},{"path":"github/prs.t27","category":"github","name":"prs","module":"github","lines":22,"bytes":472},{"path":"github/tests/e2e_full_flow.t27","category":"github","name":"e2e_full_flow","module":"github","lines":122,"bytes":3778},{"path":"graph/knowledge_graph.t27","category":"graph","name":"knowledge_graph","module":"KnowledgeGraph","lines":406,"bytes":17952},{"path":"hslm/forward_pass.t27","category":"hslm","name":"forward_pass","module":"ForwardPass","lines":306,"bytes":14239},{"path":"interop/gf_cross_language.t27","category":"interop","name":"gf_cross_language","module":"GFCrossLanguageConformance","lines":99,"bytes":3587},{"path":"isa/registers.t27","category":"isa","name":"registers","module":"ISARegisters","lines":600,"bytes":20530},{"path":"isa/ternary_arithmetic.t27","category":"isa","name":"ternary_arithmetic","module":"TernaryArithmetic","lines":501,"bytes":16736},{"path":"isa/ternary_bitwise.t27","category":"isa","name":"ternary_bitwise","module":"TernaryBitwise","lines":404,"bytes":14046},{"path":"isa/ternary_control_flow.t27","category":"isa","name":"ternary_control_flow","module":"TernaryControlFlow","lines":392,"bytes":12518},{"path":"isa/ternary_deque.t27","category":"isa","name":"ternary_deque","module":"TernaryDeque","lines":498,"bytes":16013},{"path":"isa/ternary_encoding.t27","category":"isa","name":"ternary_encoding","module":null,"lines":42,"bytes":944},{"path":"isa/ternary_gates.t27","category":"isa","name":"ternary_gates","module":"TernaryGates","lines":410,"bytes":14744},{"path":"isa/ternary_graph.t27","category":"isa","name":"ternary_graph","module":"TernaryGraph","lines":131,"bytes":3644},{"path":"isa/ternary_hash.t27","category":"isa","name":"ternary_hash","module":"TernaryHashTable","lines":167,"bytes":5024},{"path":"isa/ternary_memory.t27","category":"isa","name":"ternary_memory","module":"ISAMemoryOps","lines":522,"bytes":18705},{"path":"isa/ternary_pattern_matching.t27","category":"isa","name":"ternary_pattern_matching","module":"TernaryPatternMatching","lines":173,"bytes":5331},{"path":"isa/ternary_search.t27","category":"isa","name":"ternary_search","module":"TernarySearch","lines":165,"bytes":4613},{"path":"isa/ternary_set.t27","category":"isa","name":"ternary_set","module":"TernarySet","lines":182,"bytes":5215},{"path":"isa/ternary_shift.t27","category":"isa","name":"ternary_shift","module":"TernaryShift","lines":420,"bytes":14016},{"path":"isa/ternary_sorting.t27","category":"isa","name":"ternary_sorting","module":"TernarySorting","lines":173,"bytes":4909},{"path":"isa/ternary_tree.t27","category":"isa","name":"ternary_tree","module":"TernaryTree","lines":141,"bytes":4645},{"path":"jit/jit.t27","category":"jit","name":"jit","module":"jit","lines":875,"bytes":29745},{"path":"lsp/client.t27","category":"lsp","name":"client","module":"lsp","lines":505,"bytes":14486},{"path":"lsp/language.t27","category":"lsp","name":"language","module":"lsp","lines":527,"bytes":14976},{"path":"lsp/protocol.t27","category":"lsp","name":"protocol","module":"lsp","lines":681,"bytes":20652},{"path":"lsp/schema.t27","category":"lsp","name":"schema","module":"lsp","lines":589,"bytes":15702},{"path":"lsp/server.t27","category":"lsp","name":"server","module":"lsp","lines":555,"bytes":15397},{"path":"math/constants.t27","category":"math","name":"constants","module":"Constants","lines":459,"bytes":16818},{"path":"math/e8_lie_algebra.t27","category":"math","name":"e8_lie_algebra","module":"E8LieAlgebra","lines":390,"bytes":14031},{"path":"math/gf_competitive.t27","category":"math","name":"gf_competitive","module":"GFCompetitive","lines":122,"bytes":4213},{"path":"math/pellis_precision_verify.t27","category":"math","name":"pellis_precision_verify","module":"PellisPrecision","lines":204,"bytes":8117},{"path":"math/phi_split_optimality.t27","category":"math","name":"phi_split_optimality","module":"PhiSplitOptimality","lines":336,"bytes":12692},{"path":"math/phi_universal_attractor.t27","category":"math","name":"phi_universal_attractor","module":"PhiUniversalAttractor","lines":333,"bytes":12924},{"path":"math/property_test_template.t27","category":"math","name":"property_test_template","module":"PropertyTestTemplate","lines":513,"bytes":16168},{"path":"math/radix_economy.t27","category":"math","name":"radix_economy","module":"RadixEconomy","lines":331,"bytes":10783},{"path":"math/sacred_physics.t27","category":"math","name":"sacred_physics","module":"SacredPhysics","lines":461,"bytes":18991},{"path":"math/zamolodchikov_e8.t27","category":"math","name":"zamolodchikov_e8","module":"ZamolodchikovE8","lines":324,"bytes":11409},{"path":"memory/formula_embed.t27","category":"memory","name":"formula_embed","module":"FormulaEmbed","lines":202,"bytes":5767},{"path":"memory/memory_primitives.t27","category":"memory","name":"memory_primitives","module":"MemoryPrimitives","lines":180,"bytes":5305},{"path":"memory/notebooklm.t27","category":"memory","name":"notebooklm","module":"NotebookLM","lines":486,"bytes":18329},{"path":"memory/semantic_search.t27","category":"memory","name":"semantic_search","module":"SemanticSearch","lines":141,"bytes":4471},{"path":"ml/activation/elu_activation.t27","category":"ml","name":"elu_activation","module":"Elu","lines":117,"bytes":5464},{"path":"ml/activation/gelu_activation.t27","category":"ml","name":"gelu_activation","module":"Gelu","lines":68,"bytes":3490},{"path":"ml/activation/gelu_approx_activation.t27","category":"ml","name":"gelu_approx_activation","module":"GeluApprox","lines":82,"bytes":4233},{"path":"ml/activation/leaky_relu_activation.t27","category":"ml","name":"leaky_relu_activation","module":"LeakyRelu","lines":95,"bytes":4569},{"path":"ml/activation/relu_activation.t27","category":"ml","name":"relu_activation","module":"Relu","lines":62,"bytes":3341},{"path":"ml/activation/sigmoid_activation.t27","category":"ml","name":"sigmoid_activation","module":"Sigmoid","lines":63,"bytes":3005},{"path":"ml/activation/silu_swish_activation.t27","category":"ml","name":"silu_swish_activation","module":"SiluSwish","lines":88,"bytes":4161},{"path":"ml/activation/silu_swish_vbt_activation.t27","category":"ml","name":"silu_swish_vbt_activation","module":"SiluSwishVbt","lines":16,"bytes":674},{"path":"ml/activation/softmax.t27","category":"ml","name":"softmax","module":"Softmax","lines":83,"bytes":4349},{"path":"ml/activation/tanh_activation.t27","category":"ml","name":"tanh_activation","module":"Tanh","lines":54,"bytes":2372},{"path":"ml/layers/avgpool2d_layer.t27","category":"ml","name":"avgpool2d_layer","module":"Avgpool2d","lines":123,"bytes":6532},{"path":"ml/layers/batchnorm_layer.t27","category":"ml","name":"batchnorm_layer","module":"Batchnorm","lines":111,"bytes":5804},{"path":"ml/layers/conv2d_layer.t27","category":"ml","name":"conv2d_layer","module":"Conv2d","lines":92,"bytes":4236},{"path":"ml/layers/dense_layer.t27","category":"ml","name":"dense_layer","module":"Dense","lines":90,"bytes":4267},{"path":"ml/layers/dropout_layer.t27","category":"ml","name":"dropout_layer","module":"Dropout","lines":70,"bytes":3702},{"path":"ml/layers/embedding_layer.t27","category":"ml","name":"embedding_layer","module":"Embedding","lines":121,"bytes":5785},{"path":"ml/layers/flatten_layer.t27","category":"ml","name":"flatten_layer","module":"Flatten","lines":114,"bytes":5624},{"path":"ml/layers/layernorm_layer.t27","category":"ml","name":"layernorm_layer","module":"Layernorm","lines":36,"bytes":1927},{"path":"ml/layers/maxpool2d_layer.t27","category":"ml","name":"maxpool2d_layer","module":"Maxpool2d","lines":74,"bytes":4252},{"path":"ml/layers/residual_connection.t27","category":"ml","name":"residual_connection","module":"Residual","lines":27,"bytes":1408},{"path":"ml/loss/binary_crossentropy_loss.t27","category":"ml","name":"binary_crossentropy_loss","module":"BinaryCe","lines":120,"bytes":5850},{"path":"ml/loss/contrastive_loss.t27","category":"ml","name":"contrastive_loss","module":"ContrastiveLoss","lines":35,"bytes":1858},{"path":"ml/loss/cross_entropy_loss.t27","category":"ml","name":"cross_entropy_loss","module":"CrossEntropy","lines":82,"bytes":4052},{"path":"ml/loss/huber_loss.t27","category":"ml","name":"huber_loss","module":"HuberLoss","lines":35,"bytes":1823},{"path":"ml/loss/kl_divergence.t27","category":"ml","name":"kl_divergence","module":"KlDivergence","lines":27,"bytes":1319},{"path":"ml/loss/mse_loss.t27","category":"ml","name":"mse_loss","module":"MseLoss","lines":35,"bytes":1788},{"path":"ml/optimizer/adagrad.t27","category":"ml","name":"adagrad","module":"Adagrad","lines":113,"bytes":5650},{"path":"ml/optimizer/adam.t27","category":"ml","name":"adam","module":"Adam","lines":47,"bytes":2422},{"path":"ml/optimizer/adamw.t27","category":"ml","name":"adamw","module":"Adamw","lines":306,"bytes":12635},{"path":"ml/optimizer/lamb.t27","category":"ml","name":"lamb","module":"Lamb","lines":134,"bytes":6076},{"path":"ml/optimizer/lr_scheduler.t27","category":"ml","name":"lr_scheduler","module":"Scheduler","lines":150,"bytes":7274},{"path":"ml/optimizer/rmsprop.t27","category":"ml","name":"rmsprop","module":"Rmsprop","lines":43,"bytes":2269},{"path":"ml/optimizer/sgd.t27","category":"ml","name":"sgd","module":"Sgd","lines":104,"bytes":4735},{"path":"ml/optimizer/sgd_momentum.t27","category":"ml","name":"sgd_momentum","module":"SgdMomentum","lines":280,"bytes":13039},{"path":"ml/pathway/mlp.t27","category":"ml","name":"mlp","module":"Mlp","lines":112,"bytes":5025},{"path":"ml/recurrent/attention_mechanism.t27","category":"ml","name":"attention_mechanism","module":"Attention","lines":95,"bytes":5259},{"path":"ml/recurrent/bilstm.t27","category":"ml","name":"bilstm","module":"Bilstm","lines":134,"bytes":6356},{"path":"ml/recurrent/gru_cell.t27","category":"ml","name":"gru_cell","module":"Gru","lines":138,"bytes":6267},{"path":"ml/recurrent/lstm_cell.t27","category":"ml","name":"lstm_cell","module":"Lstm","lines":142,"bytes":6603},{"path":"ml/recurrent/lstm_single.t27","category":"ml","name":"lstm_single","module":"LstmCell","lines":127,"bytes":6220},{"path":"ml/recurrent/rnn_cell.t27","category":"ml","name":"rnn_cell","module":"RnnCell","lines":120,"bytes":5605},{"path":"ml/recurrent/self_attention.t27","category":"ml","name":"self_attention","module":"SelfAttention","lines":57,"bytes":2662},{"path":"ml/recurrent/seq2seq.t27","category":"ml","name":"seq2seq","module":"Seq2seq","lines":121,"bytes":6457},{"path":"ml/rl/advantage_estimator.t27","category":"ml","name":"advantage_estimator","module":"Advantage","lines":128,"bytes":6229},{"path":"ml/rl/dqn.t27","category":"ml","name":"dqn","module":"Dqn","lines":119,"bytes":5990},{"path":"ml/rl/dqn_target_network.t27","category":"ml","name":"dqn_target_network","module":"DqnTarget","lines":112,"bytes":5334},{"path":"ml/rl/ppo_actor.t27","category":"ml","name":"ppo_actor","module":"PpoActor","lines":155,"bytes":7732},{"path":"ml/rl/ppo_clip_loss.t27","category":"ml","name":"ppo_clip_loss","module":"PpoClipLoss","lines":141,"bytes":7501},{"path":"ml/rl/ppo_critic.t27","category":"ml","name":"ppo_critic","module":"PpoCritic","lines":106,"bytes":5376},{"path":"ml/rl/sac_actor.t27","category":"ml","name":"sac_actor","module":"SacActor","lines":128,"bytes":6327},{"path":"ml/rl/sac_critic.t27","category":"ml","name":"sac_critic","module":"SacCritic","lines":164,"bytes":7664},{"path":"ml/transformer/encoder_block.t27","category":"ml","name":"encoder_block","module":"EncoderBlock","lines":117,"bytes":5609},{"path":"ml/transformer/feed_forward.t27","category":"ml","name":"feed_forward","module":"FeedForward","lines":437,"bytes":20203},{"path":"ml/transformer/feed_forward_network.t27","category":"ml","name":"feed_forward_network","module":"FeedForward","lines":42,"bytes":2390},{"path":"ml/transformer/mha_block.t27","category":"ml","name":"mha_block","module":"MHABlock","lines":383,"bytes":17868},{"path":"ml/transformer/multi_head_attention.t27","category":"ml","name":"multi_head_attention","module":"MultiHeadAttn","lines":38,"bytes":1951},{"path":"ml/transformer/multi_head_attn.t27","category":"ml","name":"multi_head_attn","module":"MultiHeadAttention","lines":464,"bytes":21855},{"path":"ml/transformer/norm.t27","category":"ml","name":"norm","module":"LayerNorm","lines":478,"bytes":23093},{"path":"ml/transformer/positional_enc.t27","category":"ml","name":"positional_enc","module":"PositionalEncoding","lines":379,"bytes":17042},{"path":"ml/transformer/positional_encoding.t27","category":"ml","name":"positional_encoding","module":"PositionalEnc","lines":42,"bytes":2296},{"path":"neural/forward_pass.t27","category":"neural","name":"forward_pass","module":"forward_pass","lines":1095,"bytes":32248},{"path":"nn/attention.t27","category":"nn","name":"attention","module":"SacredAttention","lines":634,"bytes":22599},{"path":"nn/hslm.t27","category":"nn","name":"hslm","module":"HSLM","lines":632,"bytes":21721},{"path":"nn/phi_rope.t27","category":"nn","name":"phi_rope","module":null,"lines":47,"bytes":1243},{"path":"nn/sacred_attention.t27","category":"nn","name":"sacred_attention","module":null,"lines":50,"bytes":1242},{"path":"numeric/bigint.t27","category":"numeric","name":"bigint","module":"BigInt","lines":391,"bytes":14358},{"path":"numeric/formats.t27","category":"numeric","name":"formats","module":"Formats","lines":400,"bytes":13628},{"path":"numeric/gf12.t27","category":"numeric","name":"gf12","module":"GF12","lines":482,"bytes":13953},{"path":"numeric/gf16.t27","category":"numeric","name":"gf16","module":"triformat","lines":3392,"bytes":100829},{"path":"numeric/gf20.t27","category":"numeric","name":"gf20","module":"GF20","lines":469,"bytes":13578},{"path":"numeric/gf24.t27","category":"numeric","name":"gf24","module":"GF24","lines":469,"bytes":13626},{"path":"numeric/gf32.t27","category":"numeric","name":"gf32","module":"GF32","lines":480,"bytes":14168},{"path":"numeric/gf4.t27","category":"numeric","name":"gf4","module":"GF4","lines":306,"bytes":9145},{"path":"numeric/gf8.t27","category":"numeric","name":"gf8","module":"GF8","lines":522,"bytes":15315},{"path":"numeric/gf_competitive.t27","category":"numeric","name":"gf_competitive","module":"GFCompetitive","lines":108,"bytes":3355},{"path":"numeric/goldenfloat_family.t27","category":"numeric","name":"goldenfloat_family","module":"GoldenFloatFamily","lines":411,"bytes":14364},{"path":"numeric/pellis_verify.t27","category":"numeric","name":"pellis_verify","module":"PellisVerify","lines":65,"bytes":2169},{"path":"numeric/phi_ratio.t27","category":"numeric","name":"phi_ratio","module":"PhiRatio","lines":653,"bytes":24250},{"path":"numeric/requant_boundary.t27","category":"numeric","name":"requant_boundary","module":"RequantBoundary","lines":217,"bytes":9731},{"path":"numeric/tf3.t27","category":"numeric","name":"tf3","module":"triformat","lines":1662,"bytes":47300},{"path":"numeric/trinity_numeric_surface.t27","category":"numeric","name":"trinity_numeric_surface","module":"trinity","lines":38,"bytes":1703},{"path":"physics/chimera_best_gamma.t27","category":"physics","name":"chimera_best_gamma","module":"chimera","lines":20,"bytes":468},{"path":"physics/e8_lqg_bridge.t27","category":"physics","name":"e8_lqg_bridge","module":null,"lines":113,"bytes":2853},{"path":"physics/formula_discovery.t27","category":"physics","name":"formula_discovery","module":"FormulaDiscovery","lines":158,"bytes":4536},{"path":"physics/formula_registry.t27","category":"physics","name":"formula_registry","module":null,"lines":213,"bytes":6261},{"path":"physics/gamma-conflict.t27","category":"physics","name":"gamma-conflict","module":null,"lines":256,"bytes":6580},{"path":"physics/gamma_conjecture.t27","category":"physics","name":"gamma_conjecture","module":"GammaConjecture","lines":277,"bytes":11590},{"path":"physics/gi1_analysis.t27","category":"physics","name":"gi1_analysis","module":"GI1Analysis","lines":238,"bytes":9763},{"path":"physics/hslm_benchmark.t27","category":"physics","name":"hslm_benchmark","module":null,"lines":90,"bytes":2287},{"path":"physics/lqg_cs_bridge.t27","category":"physics","name":"lqg_cs_bridge","module":null,"lines":141,"bytes":4725},{"path":"physics/lqg_entropy.t27","category":"physics","name":"lqg_entropy","module":null,"lines":67,"bytes":2841},{"path":"physics/p2_brain_physics.t27","category":"physics","name":"p2_brain_physics","module":"P2Brain","lines":165,"bytes":6932},{"path":"physics/pellis-formulas.t27","category":"physics","name":"pellis-formulas","module":"PellisFormulas","lines":67,"bytes":2116},{"path":"physics/quantum.t27","category":"physics","name":"quantum","module":null,"lines":72,"bytes":1715},{"path":"physics/sacred_verification.t27","category":"physics","name":"sacred_verification","module":"SacredVerification","lines":605,"bytes":24309},{"path":"physics/su2_chern_simons.t27","category":"physics","name":"su2_chern_simons","module":"SU2ChernSimons","lines":349,"bytes":12227},{"path":"physics/zamolodchikov_4d_conjecture.t27","category":"physics","name":"zamolodchikov_4d_conjecture","module":"Zamolodchikov4DConjecture","lines":254,"bytes":10731},{"path":"pins/emitter_xdc.t27","category":"pins","name":"emitter_xdc","module":"EmitterXDC","lines":295,"bytes":9345},{"path":"pins/ir.t27","category":"pins","name":"ir","module":"PinsIR","lines":391,"bytes":11981},{"path":"pins/parser.t27","category":"pins","name":"parser","module":"PinsParser","lines":583,"bytes":17861},{"path":"pipeline/benchmarks.t27","category":"pipeline","name":"benchmarks","module":"PipelineBenchmarks","lines":86,"bytes":2598},{"path":"pipeline/e2e_test.t27","category":"pipeline","name":"e2e_test","module":"PipelineE2E","lines":151,"bytes":5240},{"path":"pipeline/experience_save.t27","category":"pipeline","name":"experience_save","module":"ExperienceSave","lines":127,"bytes":3752},{"path":"portable/relay_observer.t27","category":"portable","name":"relay_observer","module":"portable","lines":651,"bytes":21457},{"path":"provider/adapters.t27","category":"provider","name":"adapters","module":"provider","lines":619,"bytes":16564},{"path":"provider/schema.t27","category":"provider","name":"schema","module":"provider","lines":605,"bytes":15237},{"path":"provider/stream.t27","category":"provider","name":"stream","module":"provider","lines":612,"bytes":16334},{"path":"provider/transform.t27","category":"provider","name":"transform","module":"provider","lines":522,"bytes":15346},{"path":"queen/brain_summaries.t27","category":"queen","name":"brain_summaries","module":"BrainSummaries","lines":395,"bytes":14418},{"path":"queen/lotus.t27","category":"queen","name":"lotus","module":"QueenLotus","lines":804,"bytes":26358},{"path":"queen/task_analysis.t27","category":"queen","name":"task_analysis","module":"queen","lines":178,"bytes":5115},{"path":"runtime/execute.t27","category":"runtime","name":"execute","module":"runtime","lines":735,"bytes":19042},{"path":"runtime/instance.t27","category":"runtime","name":"instance","module":"runtime","lines":780,"bytes":20135},{"path":"runtime/process.t27","category":"runtime","name":"process","module":"runtime","lines":762,"bytes":19101},{"path":"sacred/cosmology.t27","category":"sacred","name":"cosmology","module":"TriCosmology","lines":16,"bytes":629},{"path":"sacred/dark_matter.t27","category":"sacred","name":"dark_matter","module":"dark_matter","lines":16,"bytes":629},{"path":"sacred/gravity.t27","category":"sacred","name":"gravity","module":"TriGravity","lines":16,"bytes":625},{"path":"sacred/monopoles.t27","category":"sacred","name":"monopoles","module":"TriMonopoles","lines":16,"bytes":629},{"path":"sacred/quantum.t27","category":"sacred","name":"quantum","module":"TriQuantum","lines":16,"bytes":625},{"path":"sacred/quantum_gravity.t27","category":"sacred","name":"quantum_gravity","module":"quantum_gravity","lines":16,"bytes":637},{"path":"sacred/sacred_constants.t27","category":"sacred","name":"sacred_constants","module":"SacredConstants","lines":24,"bytes":1160},{"path":"sacred/sacred_governance.t27","category":"sacred","name":"sacred_governance","module":"SacredGovernance","lines":85,"bytes":3036},{"path":"sacred/sacred_identity.t27","category":"sacred","name":"sacred_identity","module":"SacredIdentity","lines":77,"bytes":2757},{"path":"sacred/superconductivity.t27","category":"sacred","name":"superconductivity","module":"TriSuperconductivity","lines":16,"bytes":645},{"path":"sandbox/health.t27","category":"sandbox","name":"health","module":"sandbox","lines":43,"bytes":1234},{"path":"sandbox/https_enforce.t27","category":"sandbox","name":"https_enforce","module":"sandbox","lines":224,"bytes":7449},{"path":"sandbox/modules.t27","category":"sandbox","name":"modules","module":"sandbox","lines":22,"bytes":536},{"path":"sandbox/orphan_detection.t27","category":"sandbox","name":"orphan_detection","module":"sandbox","lines":257,"bytes":9041},{"path":"sandbox/session_timeout.t27","category":"sandbox","name":"session_timeout","module":"sandbox","lines":195,"bytes":6252},{"path":"server/agent-runner.t27","category":"server","name":"agent-runner","module":"AgentRunner","lines":249,"bytes":8126},{"path":"server/api.t27","category":"server","name":"api","module":"Api","lines":148,"bytes":4198},{"path":"server/http.t27","category":"server","name":"http","module":"server","lines":453,"bytes":12359},{"path":"server/mdns.t27","category":"server","name":"mdns","module":"server","lines":567,"bytes":14817},{"path":"server/project.t27","category":"server","name":"project","module":"Project","lines":206,"bytes":6094},{"path":"server/provider.t27","category":"server","name":"provider","module":"Provider","lines":257,"bytes":8105},{"path":"server/router.t27","category":"server","name":"router","module":"server","lines":575,"bytes":15018},{"path":"server/routes.t27","category":"server","name":"routes","module":"Routes","lines":36,"bytes":690},{"path":"server/session.t27","category":"server","name":"session","module":"Session","lines":273,"bytes":8497},{"path":"server/sse.t27","category":"server","name":"sse","module":"server","lines":563,"bytes":15052},{"path":"server/vm.t27","category":"server","name":"vm","module":"VM","lines":593,"bytes":22613},{"path":"shell/environment.t27","category":"shell","name":"environment","module":"ShellEnvironment","lines":304,"bytes":9509},{"path":"shell/process.t27","category":"shell","name":"process","module":"ShellProcess","lines":305,"bytes":8856},{"path":"shell/schema.t27","category":"shell","name":"schema","module":"Shell","lines":406,"bytes":12544},{"path":"storage/kv.t27","category":"storage","name":"kv","module":"StorageKv","lines":159,"bytes":5529},{"path":"storage/lock.t27","category":"storage","name":"lock","module":"StorageLock","lines":179,"bytes":6388},{"path":"storage/migrate.t27","category":"storage","name":"migrate","module":"StorageMigrate","lines":193,"bytes":6892},{"path":"storage/schema.t27","category":"storage","name":"schema","module":"Storage","lines":146,"bytes":4070},{"path":"sync/index.t27","category":"sync","name":"index","module":"sync","lines":779,"bytes":20867},{"path":"sync/schema.t27","category":"sync","name":"schema","module":"sync","lines":722,"bytes":19125},{"path":"ternary/bigint.t27","category":"ternary","name":"bigint","module":"TernaryBigInt","lines":1441,"bytes":43916},{"path":"ternary/hybrid_arithmetic.t27","category":"ternary","name":"hybrid_arithmetic","module":"HybridArithmetic","lines":492,"bytes":18595},{"path":"ternary/hybrid_bigint.t27","category":"ternary","name":"hybrid_bigint","module":"hybrid_bigint","lines":1065,"bytes":30092},{"path":"ternary/packed_trit.t27","category":"ternary","name":"packed_trit","module":"PackedTrit","lines":430,"bytes":16022},{"path":"test_framework/core.t27","category":"test_framework","name":"core","module":"core","lines":317,"bytes":10877},{"path":"test_framework/graph_drift_detection.t27","category":"test_framework","name":"graph_drift_detection","module":"GraphDriftDetection","lines":650,"bytes":23027},{"path":"test_framework/property_test_template.t27","category":"test_framework","name":"property_test_template","module":"PBTTemplate","lines":433,"bytes":16346},{"path":"test_framework/runner.t27","category":"test_framework","name":"runner","module":"runner","lines":618,"bytes":19960},{"path":"test_framework/verilog_bench_harness.t27","category":"test_framework","name":"verilog_bench_harness","module":"VerilogBenchHarness","lines":460,"bytes":17844},{"path":"tools/registry.t27","category":"tools","name":"registry","module":"ToolsRegistry","lines":523,"bytes":16726},{"path":"tools/schema.t27","category":"tools","name":"schema","module":"Tools","lines":523,"bytes":16580},{"path":"tools/tri_to_t27_converter.t27","category":"tools","name":"tri_to_t27_converter","module":"TriToT27Converter","lines":406,"bytes":16958},{"path":"tri/agent/agent_run.t27","category":"tri","name":"agent_run","module":"AgentRun","lines":16,"bytes":624},{"path":"tri/agent/agents.t27","category":"tri","name":"agents","module":"agents","lines":16,"bytes":619},{"path":"tri/agent/autonomous_lifecycle.t27","category":"tri","name":"autonomous_lifecycle","module":"TriAutonomousLifecycle","lines":73,"bytes":2953},{"path":"tri/agent/autonomous_universe.t27","category":"tri","name":"autonomous_universe","module":"AutonomousUniverse","lines":16,"bytes":645},{"path":"tri/agent/eternal_monitor.t27","category":"tri","name":"eternal_monitor","module":"EternalMonitor","lines":70,"bytes":2500},{"path":"tri/agent/experience_hooks.t27","category":"tri","name":"experience_hooks","module":"experience_hooks","lines":16,"bytes":639},{"path":"tri/agent/faculty_board.t27","category":"tri","name":"faculty_board","module":"FacultyBoard","lines":63,"bytes":2412},{"path":"tri/agent/governance_agent.t27","category":"tri","name":"governance_agent","module":"GovernanceAgent","lines":98,"bytes":3447},{"path":"tri/agent/handoff.t27","category":"tri","name":"handoff","module":"Handoff","lines":67,"bytes":2325},{"path":"tri/agent/memory.t27","category":"tri","name":"memory","module":"memory","lines":16,"bytes":619},{"path":"tri/agent/swarm_agents.t27","category":"tri","name":"swarm_agents","module":"SwarmAgents","lines":143,"bytes":5212},{"path":"tri/collections/array.t27","category":"tri","name":"array","module":"TriArray","lines":123,"bytes":5387},{"path":"tri/collections/bitmap.t27","category":"tri","name":"bitmap","module":"TriBitmap","lines":101,"bytes":4201},{"path":"tri/collections/bitset.t27","category":"tri","name":"bitset","module":"TriBitset","lines":97,"bytes":3588},{"path":"tri/collections/bitvector.t27","category":"tri","name":"bitvector","module":"TriBitvector","lines":93,"bytes":4192},{"path":"tri/collections/btree.t27","category":"tri","name":"btree","module":"TriBtree","lines":85,"bytes":4316},{"path":"tri/collections/circular_buffer.t27","category":"tri","name":"circular_buffer","module":"TriCircularBuffer","lines":79,"bytes":3676},{"path":"tri/collections/context.t27","category":"tri","name":"context","module":"TriContext","lines":16,"bytes":625},{"path":"tri/collections/deque.t27","category":"tri","name":"deque","module":"TriDeque","lines":77,"bytes":3243},{"path":"tri/collections/either.t27","category":"tri","name":"either","module":"TriEither","lines":75,"bytes":3222},{"path":"tri/collections/interval.t27","category":"tri","name":"interval","module":"TriInterval","lines":86,"bytes":4186},{"path":"tri/collections/linked_list.t27","category":"tri","name":"linked_list","module":"TriLinkedList","lines":84,"bytes":3032},{"path":"tri/collections/list.t27","category":"tri","name":"list","module":"TriList","lines":97,"bytes":3261},{"path":"tri/collections/lockfree_stack.t27","category":"tri","name":"lockfree_stack","module":"TriLockfreeStack","lines":70,"bytes":3118},{"path":"tri/collections/lru.t27","category":"tri","name":"lru","module":"TriLru","lines":57,"bytes":2516},{"path":"tri/collections/lru_cache.t27","category":"tri","name":"lru_cache","module":"TriLruCache","lines":68,"bytes":2554},{"path":"tri/collections/map.t27","category":"tri","name":"map","module":"TriMap","lines":73,"bytes":3019},{"path":"tri/collections/maybe.t27","category":"tri","name":"maybe","module":"TriMaybe","lines":81,"bytes":3955},{"path":"tri/collections/namespace.t27","category":"tri","name":"namespace","module":"TriNamespace","lines":58,"bytes":2700},{"path":"tri/collections/option.t27","category":"tri","name":"option","module":"TriOption","lines":79,"bytes":3504},{"path":"tri/collections/priority_queue.t27","category":"tri","name":"priority_queue","module":"TriPriorityQueue","lines":78,"bytes":3380},{"path":"tri/collections/queue.t27","category":"tri","name":"queue","module":"TriQueue","lines":70,"bytes":2992},{"path":"tri/collections/result.t27","category":"tri","name":"result","module":"TriResult","lines":79,"bytes":3655},{"path":"tri/collections/ring_buffer.t27","category":"tri","name":"ring_buffer","module":"TriRing","lines":101,"bytes":4905},{"path":"tri/collections/set.t27","category":"tri","name":"set","module":"TriSet","lines":65,"bytes":2723},{"path":"tri/collections/skip_list.t27","category":"tri","name":"skip_list","module":"TriSkipList","lines":69,"bytes":3150},{"path":"tri/collections/stack.t27","category":"tri","name":"stack","module":"TriStack","lines":71,"bytes":3014},{"path":"tri/collections/state.t27","category":"tri","name":"state","module":"TriState","lines":65,"bytes":2474},{"path":"tri/collections/tuple.t27","category":"tri","name":"tuple","module":"TriTuple","lines":72,"bytes":2619},{"path":"tri/collections/variant.t27","category":"tri","name":"variant","module":"TriVariant","lines":63,"bytes":2922},{"path":"tri/crypto/base32.t27","category":"tri","name":"base32","module":"TriBase32","lines":78,"bytes":4129},{"path":"tri/crypto/base64.t27","category":"tri","name":"base64","module":"TriBase64","lines":97,"bytes":4954},{"path":"tri/crypto/crypto.t27","category":"tri","name":"crypto","module":"TriCrypto","lines":56,"bytes":2500},{"path":"tri/crypto/ecc.t27","category":"tri","name":"ecc","module":"TriEcc","lines":89,"bytes":4348},{"path":"tri/crypto/hex.t27","category":"tri","name":"hex","module":"TriHex","lines":64,"bytes":2929},{"path":"tri/crypto/hmac.t27","category":"tri","name":"hmac","module":"TriHmac","lines":56,"bytes":2274},{"path":"tri/crypto/reed_solomon.t27","category":"tri","name":"reed_solomon","module":"TriReedSolomon","lines":46,"bytes":2205},{"path":"tri/crypto/rsa.t27","category":"tri","name":"rsa","module":"TriRsa","lines":83,"bytes":4009},{"path":"tri/crypto/sha256.t27","category":"tri","name":"sha256","module":"TriSha256","lines":67,"bytes":2952},{"path":"tri/encoding/bson.t27","category":"tri","name":"bson","module":"TriBson","lines":70,"bytes":3544},{"path":"tri/encoding/csv.t27","category":"tri","name":"csv","module":"TriCsv","lines":72,"bytes":3155},{"path":"tri/encoding/html.t27","category":"tri","name":"html","module":"TriHtml","lines":48,"bytes":2223},{"path":"tri/encoding/json.t27","category":"tri","name":"json","module":"TriJson","lines":67,"bytes":2655},{"path":"tri/encoding/markup.t27","category":"tri","name":"markup","module":"TriMarkup","lines":53,"bytes":2716},{"path":"tri/encoding/mime.t27","category":"tri","name":"mime","module":"TriMime","lines":57,"bytes":2794},{"path":"tri/encoding/msgpack.t27","category":"tri","name":"msgpack","module":"TriMsgpack","lines":93,"bytes":4485},{"path":"tri/encoding/xml.t27","category":"tri","name":"xml","module":"TriXml","lines":48,"bytes":2195},{"path":"tri/graph/bellman_ford.t27","category":"tri","name":"bellman_ford","module":"TriBellmanFord","lines":37,"bytes":1918},{"path":"tri/graph/dijkstra.t27","category":"tri","name":"dijkstra","module":"TriDijkstra","lines":38,"bytes":1854},{"path":"tri/graph/disjoint_set.t27","category":"tri","name":"disjoint_set","module":"TriDisjointSet","lines":93,"bytes":4626},{"path":"tri/graph/graph.t27","category":"tri","name":"graph","module":"TriGraph","lines":61,"bytes":2379},{"path":"tri/graph/graph_bfs.t27","category":"tri","name":"graph_bfs","module":"TriGraphBfs","lines":113,"bytes":5554},{"path":"tri/graph/graph_dfs.t27","category":"tri","name":"graph_dfs","module":"TriGraphDfs","lines":38,"bytes":1808},{"path":"tri/graph/prims_mst.t27","category":"tri","name":"prims_mst","module":"TriPrimsMst","lines":38,"bytes":1795},{"path":"tri/graph/topological_sort.t27","category":"tri","name":"topological_sort","module":"TriTopological","lines":47,"bytes":2082},{"path":"tri/io/compress.t27","category":"tri","name":"compress","module":"TriCompress","lines":58,"bytes":3115},{"path":"tri/io/filesystem.t27","category":"tri","name":"filesystem","module":"TriFilesystem","lines":113,"bytes":5345},{"path":"tri/io/fs.t27","category":"tri","name":"fs","module":"TriFs","lines":73,"bytes":2757},{"path":"tri/io/io.t27","category":"tri","name":"io","module":"TriIo","lines":67,"bytes":3086},{"path":"tri/io/reader.t27","category":"tri","name":"reader","module":"TriReader","lines":65,"bytes":2550},{"path":"tri/io/writer.t27","category":"tri","name":"writer","module":"TriWriter","lines":66,"bytes":2628},{"path":"tri/io/zip.t27","category":"tri","name":"zip","module":"TriZipper","lines":77,"bytes":2847},{"path":"tri/math/bezier.t27","category":"tri","name":"bezier","module":"TriBezier","lines":75,"bytes":3811},{"path":"tri/math/constants.t27","category":"tri","name":"constants","module":"TriConstants","lines":128,"bytes":5390},{"path":"tri/math/math.t27","category":"tri","name":"math","module":"TriMath","lines":16,"bytes":619},{"path":"tri/math/matrix.t27","category":"tri","name":"matrix","module":"TriMatrix","lines":96,"bytes":4182},{"path":"tri/math/measurement.t27","category":"tri","name":"measurement","module":"TriMeasurement","lines":16,"bytes":633},{"path":"tri/math/polynomial.t27","category":"tri","name":"polynomial","module":"TriPolynomial","lines":80,"bytes":3851},{"path":"tri/math/probability.t27","category":"tri","name":"probability","module":"TriProbability","lines":67,"bytes":2602},{"path":"tri/math/statistics.t27","category":"tri","name":"statistics","module":"TriStatistics","lines":77,"bytes":2735},{"path":"tri/net/async.t27","category":"tri","name":"async","module":"TriAsync","lines":86,"bytes":3896},{"path":"tri/net/async_stream.t27","category":"tri","name":"async_stream","module":"TriAsyncStream","lines":86,"bytes":4087},{"path":"tri/net/channel.t27","category":"tri","name":"channel","module":"TriChannel","lines":67,"bytes":2629},{"path":"tri/net/cloud.t27","category":"tri","name":"cloud","module":"TriCloud","lines":16,"bytes":621},{"path":"tri/net/http.t27","category":"tri","name":"http","module":"TriHttp","lines":133,"bytes":6147},{"path":"tri/net/net.t27","category":"tri","name":"net","module":"TriNet","lines":71,"bytes":3117},{"path":"tri/net/url.t27","category":"tri","name":"url","module":"TriUrl","lines":83,"bytes":4112},{"path":"tri/pipeline/batch_runner.t27","category":"tri","name":"batch_runner","module":"BatchRunner","lines":115,"bytes":4376},{"path":"tri/pipeline/builder.t27","category":"tri","name":"builder","module":"TriBuilder","lines":107,"bytes":3957},{"path":"tri/pipeline/cloud_orchestrator.t27","category":"tri","name":"cloud_orchestrator","module":"CloudOrchestrator","lines":16,"bytes":643},{"path":"tri/pipeline/codegen.t27","category":"tri","name":"codegen","module":"TestSpec","lines":27,"bytes":1147},{"path":"tri/pipeline/pipeline.t27","category":"tri","name":"pipeline","module":"TriPipeline","lines":16,"bytes":627},{"path":"tri/pipeline/pipeline_parallel.t27","category":"tri","name":"pipeline_parallel","module":"TriPipelineParallel","lines":50,"bytes":1900},{"path":"tri/pipeline/spec_parser.t27","category":"tri","name":"spec_parser","module":"TriSpecParser","lines":16,"bytes":632},{"path":"tri/pipeline/spec_writer.t27","category":"tri","name":"spec_writer","module":"SpecWriter","lines":46,"bytes":1622},{"path":"tri/pipeline/workflow.t27","category":"tri","name":"workflow","module":"Workflow","lines":31,"bytes":1263},{"path":"tri/pipeline/workflow_executor.t27","category":"tri","name":"workflow_executor","module":"WorkflowExecutor","lines":16,"bytes":641},{"path":"tri/pipeline/workflow_parser.t27","category":"tri","name":"workflow_parser","module":"WorkflowParser","lines":16,"bytes":637},{"path":"tri/search/aho_corasick.t27","category":"tri","name":"aho_corasick","module":"TriAhoCorasick","lines":99,"bytes":4285},{"path":"tri/search/bloom_filter.t27","category":"tri","name":"bloom_filter","module":"TriBloomFilter","lines":58,"bytes":2868},{"path":"tri/search/boyer_moore.t27","category":"tri","name":"boyer_moore","module":"TriBoyerMoore","lines":72,"bytes":3868},{"path":"tri/search/knuth_morris_pratt.t27","category":"tri","name":"knuth_morris_pratt","module":"TriKmp","lines":85,"bytes":4347},{"path":"tri/search/match.t27","category":"tri","name":"match","module":null,"lines":68,"bytes":3333},{"path":"tri/search/pattern.t27","category":"tri","name":"pattern","module":"TriPattern","lines":61,"bytes":3040},{"path":"tri/search/rabin_karp.t27","category":"tri","name":"rabin_karp","module":"TriRabinKarp","lines":73,"bytes":3867},{"path":"tri/search/regex.t27","category":"tri","name":"regex","module":"TriRegex","lines":91,"bytes":4367},{"path":"tri/search/regex_advanced.t27","category":"tri","name":"regex_advanced","module":"TriRegexAdvanced","lines":62,"bytes":2766},{"path":"tri/search/search.t27","category":"tri","name":"search","module":"TriSearch","lines":63,"bytes":3032},{"path":"tri/sort/counting_sort.t27","category":"tri","name":"counting_sort","module":"TriCountingSort","lines":27,"bytes":1352},{"path":"tri/sort/heap_sort.t27","category":"tri","name":"heap_sort","module":"TriHeapSort","lines":27,"bytes":1258},{"path":"tri/sort/insertion_sort.t27","category":"tri","name":"insertion_sort","module":"TriInsertionSort","lines":27,"bytes":1262},{"path":"tri/sort/merge_sort.t27","category":"tri","name":"merge_sort","module":"TriMergeSort","lines":37,"bytes":1665},{"path":"tri/sort/quick_sort.t27","category":"tri","name":"quick_sort","module":"TriQuickSort","lines":41,"bytes":1923},{"path":"tri/sort/radix_sort.t27","category":"tri","name":"radix_sort","module":"TriRadixSort","lines":56,"bytes":2879},{"path":"tri/sort/selection_sort.t27","category":"tri","name":"selection_sort","module":"TriSelectionSort","lines":27,"bytes":1247},{"path":"tri/sort/shell_sort.t27","category":"tri","name":"shell_sort","module":"TriShellSort","lines":27,"bytes":1257},{"path":"tri/sort/sort.t27","category":"tri","name":"sort","module":"TriSort","lines":57,"bytes":3188},{"path":"tri/sort/tim_sort.t27","category":"tri","name":"tim_sort","module":"TriTimSort","lines":27,"bytes":1311},{"path":"tri/trees/avl_tree.t27","category":"tri","name":"avl_tree","module":"TriAvlTree","lines":76,"bytes":2769},{"path":"tri/trees/b_tree.t27","category":"tri","name":"b_tree","module":"TriBTree","lines":96,"bytes":4616},{"path":"tri/trees/fenwick_tree.t27","category":"tri","name":"fenwick_tree","module":"TriFenwick","lines":101,"bytes":4544},{"path":"tri/trees/kd_tree.t27","category":"tri","name":"kd_tree","module":"TriKdTree","lines":89,"bytes":3998},{"path":"tri/trees/octree.t27","category":"tri","name":"octree","module":"TriOctree","lines":103,"bytes":4639},{"path":"tri/trees/quadtree.t27","category":"tri","name":"quadtree","module":"TriQuadtree","lines":110,"bytes":5036},{"path":"tri/trees/red_black_tree.t27","category":"tri","name":"red_black_tree","module":"TriRbTree","lines":81,"bytes":2892},{"path":"tri/trees/rtree.t27","category":"tri","name":"rtree","module":"TriRtree","lines":88,"bytes":3876},{"path":"tri/trees/segment_tree.t27","category":"tri","name":"segment_tree","module":"TriSegmentTree","lines":67,"bytes":3238},{"path":"tri/trees/splay_tree.t27","category":"tri","name":"splay_tree","module":"TriSplayTree","lines":76,"bytes":2802},{"path":"tri/trees/suffix_array.t27","category":"tri","name":"suffix_array","module":"TriSuffixArray","lines":83,"bytes":4311},{"path":"tri/trees/tree.t27","category":"tri","name":"tree","module":"TriTree","lines":85,"bytes":3768},{"path":"tri/trees/trie.t27","category":"tri","name":"trie","module":"TriTrie","lines":93,"bytes":4263},{"path":"tri/utils/args.t27","category":"tri","name":"args","module":null,"lines":86,"bytes":4133},{"path":"tri/utils/arrow_time.t27","category":"tri","name":"arrow_time","module":"arrow_time","lines":16,"bytes":627},{"path":"tri/utils/bytes.t27","category":"tri","name":"bytes","module":"TriBytes","lines":106,"bytes":3875},{"path":"tri/utils/color.t27","category":"tri","name":"color","module":"TriColor","lines":95,"bytes":4563},{"path":"tri/utils/colors.t27","category":"tri","name":"colors","module":"TriColors","lines":16,"bytes":623},{"path":"tri/utils/config.t27","category":"tri","name":"config","module":"TriConfig","lines":89,"bytes":4082},{"path":"tri/utils/error.t27","category":"tri","name":"error","module":"TriError","lines":26,"bytes":1415},{"path":"tri/utils/exit_codes.t27","category":"tri","name":"exit_codes","module":"TriExitCodes","lines":35,"bytes":1562},{"path":"tri/utils/help.t27","category":"tri","name":"help","module":"TriHelp","lines":32,"bytes":1370},{"path":"tri/utils/logger.t27","category":"tri","name":"logger","module":null,"lines":66,"bytes":2659},{"path":"tri/utils/logging.t27","category":"tri","name":"logging","module":"TriLogging","lines":102,"bytes":5075},{"path":"tri/utils/random.t27","category":"tri","name":"random","module":"TriRandom","lines":97,"bytes":4367},{"path":"tri/utils/string.t27","category":"tri","name":"string","module":"string","lines":16,"bytes":619},{"path":"tri/utils/template.t27","category":"tri","name":"template","module":"TriTemplate","lines":70,"bytes":3884},{"path":"tri/utils/terminal.t27","category":"tri","name":"terminal","module":"TriTerminal","lines":59,"bytes":2447},{"path":"tri/utils/text.t27","category":"tri","name":"text","module":"TriText","lines":67,"bytes":2793},{"path":"tri/utils/time.t27","category":"tri","name":"time","module":"TriTime","lines":93,"bytes":4138},{"path":"tri/utils/utf8.t27","category":"tri","name":"utf8","module":"TriUtf8","lines":82,"bytes":3595},{"path":"tri/utils/version.t27","category":"tri","name":"version","module":"TriVersion","lines":85,"bytes":4113},{"path":"vm/jit_semantics.t27","category":"vm","name":"jit_semantics","module":"JitSemantics","lines":319,"bytes":14011},{"path":"vsa/jones_polynomial.t27","category":"vsa","name":"jones_polynomial","module":"JonesPolynomial","lines":353,"bytes":12139},{"path":"vsa/ops.t27","category":"vsa","name":"ops","module":"VSAOps","lines":670,"bytes":27126},{"path":"vsa/packed_vsa.t27","category":"vsa","name":"packed_vsa","module":"PackedVsa","lines":291,"bytes":11657},{"path":"vsa/sdk.t27","category":"vsa","name":"sdk","module":"SDK","lines":626,"bytes":24071},{"path":"vsa/sequence_hdc.t27","category":"vsa","name":"sequence_hdc","module":"SequenceHdc","lines":342,"bytes":15174},{"path":"vsa/similarity_search.t27","category":"vsa","name":"similarity_search","module":"VSASimilaritySearch","lines":467,"bytes":15246},{"path":"vsa/vsa_core.t27","category":"vsa","name":"vsa_core","module":"vsa_core","lines":1012,"bytes":29297}]} \ No newline at end of file diff --git a/apps/website/public/t27/specs/account/auth.t27 b/apps/website/public/t27/specs/account/auth.t27 new file mode 100644 index 0000000000..5b1822783f --- /dev/null +++ b/apps/website/public/t27/specs/account/auth.t27 @@ -0,0 +1,277 @@ +// specs/account/auth.t27 +// Account Authentication Operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module AccountAuth { + use base::types; + use account::schema; + + // ==================================================================== + // Authentication Operations + // ==================================================================== + + // token retrieves a fresh access token for an account + // Refreshes the token if needed + fn token(account_id: AccountID) -> Result { + // Implementation: Get account row, check freshness, refresh if needed + } + + // refresh refreshes the access token for an account + fn refresh(account_id: AccountID) -> Result { + // Implementation: Use refresh token to get new access token + } + + // login initiates a device code authentication flow + fn login(server: str) -> Result { + // Implementation: Request device code from auth server + } + + // poll checks the status of a pending device auth + fn poll(login: Login) -> Result { + // Implementation: Poll auth server for completion + } + + // logout terminates the current session + fn logout(account_id: AccountID) -> Result { + // Implementation: Clear tokens for the account + } + + // ==================================================================== + // Organization Operations + // ==================================================================== + + // orgs returns organizations for an account + fn orgs(account_id: AccountID) -> Result<[Org], AccountError> { + // Implementation: Fetch orgs from API using access token + } + + // AccountOrgs pairs one account with the organizations it belongs to + struct AccountOrgs { + account: Info, + orgs: [Org], + } + + // orgs_by_account returns all accounts with their orgs + fn orgs_by_account() -> Result<[AccountOrgs], AccountError> { + // Implementation: Fetch all accounts and their orgs + } + + // config retrieves configuration for an account/org + fn config(account_id: AccountID, org_id: OrgID) -> Result<[str: any]?, AccountError> { + // Implementation: Fetch config from API + } + + // ==================================================================== + // User Operations + // ==================================================================== + + // UserInfo represents user information from API + struct UserInfo { + id: AccountID, + email: str, + } + + // get_user retrieves user information for an account + fn get_user(account_id: AccountID) -> Result { + // Implementation: Fetch user info from API + } + + // ==================================================================== + // Token Management + // ==================================================================== + + // TokenRefreshRequest is the request body for token refresh + struct TokenRefreshRequest { + grant_type: str, + refresh_token: RefreshToken, + client_id: str, + } + + // DeviceTokenRequest is the request body for device token + struct DeviceTokenRequest { + grant_type: str, + device_code: DeviceCode, + client_id: str, + } + + // DeviceAuthResponse is the response from device code request + struct DeviceAuthResponse { + device_code: DeviceCode, + user_code: UserCode, + verification_uri_complete: str, + expires_in: u64, + interval: u64, + } + + // TokenRefreshResponse is the response from token refresh + struct TokenRefreshResponse { + access_token: AccessToken, + refresh_token: RefreshToken, + expires_in: u64, + } + + // DeviceTokenSuccessResponse is the successful device token response + struct DeviceTokenSuccessResponse { + access_token: AccessToken, + refresh_token: RefreshToken, + expires_in: u64, + } + + // DeviceTokenErrorResponse is the error device token response + struct DeviceTokenErrorResponse { + error: str, + error_description: str, + } + + // ==================================================================== + // Poll Response Handling + // ==================================================================== + + // parse_poll_result converts a device token response to PollResult + fn parse_poll_result(response: DeviceTokenErrorResponse) -> PollResult { + if (response.error == "authorization_pending") { + return PollResult::Pending; + } + if (response.error == "slow_down") { + return PollResult::Slow; + } + if (response.error == "expired_token") { + return PollResult::Expired; + } + if (response.error == "access_denied") { + return PollResult::Denied; + } + return PollResult::Error; + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "user_info_creation" { + var user = UserInfo { + id = AccountID("user-123"), + email = "user@example.com", + }; + assert(user.id.0 == "user-123"); + assert(user.email == "user@example.com"); + } + + test "token_refresh_request_creation" { + var request = TokenRefreshRequest { + grant_type = "refresh_token", + refresh_token = RefreshToken("refresh-token"), + client_id = "opencode-cli", + }; + assert(request.grant_type == "refresh_token"); + assert(request.client_id == "opencode-cli"); + } + + test "device_token_request_creation" { + var request = DeviceTokenRequest { + grant_type = "urn:ietf:params:oauth:grant-type:device_code", + device_code = DeviceCode("device-code"), + client_id = "opencode-cli", + }; + assert(request.grant_type == "urn:ietf:params:oauth:grant-type:device_code"); + } + + test "device_auth_response_creation" { + var response = DeviceAuthResponse { + device_code = DeviceCode("device-code"), + user_code = UserCode("ABCD-1234"), + verification_uri_complete = "https://example.com/auth", + expires_in = 300, + interval = 5, + }; + assert(response.user_code.0 == "ABCD-1234"); + assert(response.expires_in == 300); + } + + test "token_refresh_response_creation" { + var response = TokenRefreshResponse { + access_token = AccessToken("new-token"), + refresh_token = RefreshToken("new-refresh"), + expires_in = 3600, + }; + assert(response.access_token.0 == "new-token"); + assert(response.expires_in == 3600); + } + + test "device_token_error_response_creation" { + var response = DeviceTokenErrorResponse { + error = "authorization_pending", + error_description = "Waiting for user", + }; + assert(response.error == "authorization_pending"); + } + + test "parse_poll_result_pending" { + var response = DeviceTokenErrorResponse { + error = "authorization_pending", + error_description = "", + }; + var result = parse_poll_result(response); + assert(result == PollResult::Pending); + } + + test "parse_poll_result_slow" { + var response = DeviceTokenErrorResponse { + error = "slow_down", + error_description = "", + }; + var result = parse_poll_result(response); + assert(result == PollResult::Slow); + } + + test "parse_poll_result_expired" { + var response = DeviceTokenErrorResponse { + error = "expired_token", + error_description = "", + }; + var result = parse_poll_result(response); + assert(result == PollResult::Expired); + } + + test "parse_poll_result_denied" { + var response = DeviceTokenErrorResponse { + error = "access_denied", + error_description = "", + }; + var result = parse_poll_result(response); + assert(result == PollResult::Denied); + } + + test "parse_poll_result_error" { + var response = DeviceTokenErrorResponse { + error = "unknown_error", + error_description = "", + }; + var result = parse_poll_result(response); + assert(result == PollResult::Error); + } + + test "org_creation" { + var org = Org { + id = OrgID("org-456"), + name = "Acme Inc", + }; + assert(org.id.0 == "org-456"); + assert(org.name == "Acme Inc"); + } + + test "poll_result_enum_values" { + assert(PollResult::Success as u32 == 0); + assert(PollResult::Pending as u32 == 1); + assert(PollResult::Slow as u32 == 2); + assert(PollResult::Expired as u32 == 3); + assert(PollResult::Denied as u32 == 4); + assert(PollResult::Error as u32 == 5); + } + + test "constants_values" { + assert(CLIENT_ID == "opencode-cli"); + assert(EAGER_REFRESH_THRESHOLD_MINUTES == 5); + assert(DEFAULT_POLL_INTERVAL_MS == 5000); + } +} diff --git a/apps/website/public/t27/specs/account/repo.t27 b/apps/website/public/t27/specs/account/repo.t27 new file mode 100644 index 0000000000..6a85e0da77 --- /dev/null +++ b/apps/website/public/t27/specs/account/repo.t27 @@ -0,0 +1,196 @@ +// specs/account/repo.t27 +// Account Repository Operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module AccountRepo { + use base::types; + + // ==================================================================== + // Type Definitions + // ==================================================================== + + struct AccountID(str); + struct OrgID(str); + struct AccessToken(str); + struct RefreshToken(str); + + struct Info { + id: AccountID, + email: str, + url: str, + active_org_id: OrgID?, + } + + enum AccountError { + Repo = 0, + Service = 1, + } + + // ==================================================================== + // Input Types + // ==================================================================== + + struct PersistTokenInput { + account_id: AccountID, + access_token: AccessToken, + refresh_token: RefreshToken, + expiry: u64?, + } + + struct PersistAccountInput { + id: AccountID, + email: str, + url: str, + access_token: AccessToken, + refresh_token: RefreshToken, + expiry: u64, + org_id: OrgID?, + } + + struct AccountRow { + id: AccountID, + email: str, + url: str, + access_token: AccessToken, + refresh_token: RefreshToken, + token_expiry: u64?, + } + + struct AccountState { + id: u32, + active_account_id: AccountID?, + active_org_id: OrgID?, + } + + // ==================================================================== + // Constants + // ==================================================================== + + const ACCOUNT_STATE_ID: u32 = 1; + + // ==================================================================== + // Repository Operations + // ==================================================================== + + fn active() -> Result { + } + + fn list() -> Result<[Info], AccountError> { + } + + fn remove(account_id: AccountID) -> Result { + } + + fn set_active(account_id: AccountID, org_id: OrgID?) -> Result { + } + + fn get_row(account_id: AccountID) -> Result { + } + + fn persist_token(input: PersistTokenInput) -> Result { + } + + fn persist_account(input: PersistAccountInput) -> Result { + } + + fn get_state() -> Result { + } + + fn set_state(state: AccountState) -> Result { + } + + fn clear_active() -> Result { + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "account_row_creation" { + var row = AccountRow { + id = AccountID("user-123"), + email = "user@example.com", + url = "https://example.com", + access_token = AccessToken("token"), + refresh_token = RefreshToken("refresh"), + token_expiry = null, + }; + assert(row.id.0 == "user-123"); + assert(row.email == "user@example.com"); + } + + test "account_state_creation" { + var state = AccountState { + id = 1, + active_account_id = AccountID("user-123"), + active_org_id = OrgID("org-456"), + }; + assert(state.id == 1); + assert(state.active_account_id?.0 == "user-123"); + } + + test "persist_token_input_creation" { + var input = PersistTokenInput { + account_id = AccountID("user-123"), + access_token = AccessToken("new-token"), + refresh_token = RefreshToken("new-refresh"), + expiry = 3600000, + }; + assert(input.account_id.0 == "user-123"); + assert(input.expiry == 3600000); + } + + test "persist_account_input_creation" { + var input = PersistAccountInput { + id = AccountID("user-123"), + email = "user@example.com", + url = "https://example.com", + access_token = AccessToken("token"), + refresh_token = RefreshToken("refresh"), + expiry = 3600000, + org_id = OrgID("org-456"), + }; + assert(input.id.0 == "user-123"); + assert(input.org_id?.0 == "org-456"); + } + + test "account_state_id_constant" { + assert(ACCOUNT_STATE_ID == 1); + } + + test "clear_active_state_no_active_account" { + var state = AccountState { + id = 1, + active_account_id = null, + active_org_id = null, + }; + assert(state.active_account_id == null); + } + + test "account_error_types" { + var repo_err = AccountError::Repo; + var service_err = AccountError::Service; + assert(repo_err as u32 == 0); + assert(service_err as u32 == 1); + } + + test "info_with_null_org" { + var info = Info { + id = AccountID("user-123"), + email = "user@example.com", + url = "https://example.com", + active_org_id = null, + }; + assert(info.active_org_id == null); + } + + test "info_with_org" { + var info = Info { + id = AccountID("user-123"), + email = "user@example.com", + url = "https://example.com", + active_org_id = OrgID("org-456"), + }; + assert(info.active_org_id?.0 == "org-456"); + } +} diff --git a/apps/website/public/t27/specs/account/schema.t27 b/apps/website/public/t27/specs/account/schema.t27 new file mode 100644 index 0000000000..ac46900dd0 --- /dev/null +++ b/apps/website/public/t27/specs/account/schema.t27 @@ -0,0 +1,226 @@ +// specs/account/schema.t27 +// Account Types Specification +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Account { + use base::types; + + // ==================================================================== + // Account Identifiers + // ==================================================================== + + // AccountID uniquely identifies an account + struct AccountID(str); + + // OrgID uniquely identifies an organization + struct OrgID(str); + + // AccessToken represents an OAuth access token + struct AccessToken(str); + + // RefreshToken represents an OAuth refresh token + struct RefreshToken(str); + + // DeviceCode represents an OAuth device code + struct DeviceCode(str); + + // UserCode represents a user verification code + struct UserCode(str); + + // ==================================================================== + // Account Info Types + // ==================================================================== + + // Info represents account information + struct Info { + id: AccountID, + email: str, + url: str, + active_org_id: OrgID?, + } + + // Org represents an organization + struct Org { + id: OrgID, + name: str, + } + + // Login represents a device code login session + struct Login { + code: DeviceCode, + user: UserCode, + url: str, + server: str, + expiry: u64, // milliseconds + interval: u64, // milliseconds + } + + // ==================================================================== + // Poll Result Types + // ==================================================================== + + // PollResult represents the result of polling for device auth + enum PollResult { + Success = 0, + Pending = 1, + Slow = 2, + Expired = 3, + Denied = 4, + Error = 5, + } + + // PollSuccess indicates successful authentication + struct PollSuccess { + email: str, + } + + // PollPending indicates authentication is still pending + struct PollPending {} + + // PollSlow indicates polling should slow down + struct PollSlow {} + + // PollExpired indicates the device code has expired + struct PollExpired {} + + // PollDenied indicates the user denied the request + struct PollDenied {} + + // PollError indicates an error occurred + struct PollError { + cause: str, + } + + // ==================================================================== + // Error Types + // ==================================================================== + + // AccountRepoError represents a repository error + struct AccountRepoError { + message: str, + cause: str?, + } + + // AccountServiceError represents a service error + struct AccountServiceError { + message: str, + cause: str?, + } + + // AccountError is a union of all account errors + enum AccountError { + Repo = 0, + Service = 1, + } + + // ==================================================================== + // Constants + // ==================================================================== + + const CLIENT_ID: str = "opencode-cli"; + const EAGER_REFRESH_THRESHOLD_MINUTES: u32 = 5; + const DEFAULT_POLL_INTERVAL_MS: u64 = 5000; + + // ==================================================================== + // Helper Functions + // ==================================================================== + + // is_token_fresh checks if a token is fresh (not near expiry) + fn is_token_fresh(token_expiry: u64?, now: u64) -> bool { + // Token is fresh if it exists and expires after eager refresh threshold + if (token_expiry == null) { + return false; + } + var threshold_ms = (EAGER_REFRESH_THRESHOLD_MINUTES as u64) * 60 * 1000; + return token_expiry > (now + threshold_ms); + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "account_id_creation" { + var id = AccountID("user-123"); + assert(id.0 == "user-123"); + } + + test "org_id_creation" { + var id = OrgID("org-456"); + assert(id.0 == "org-456"); + } + + test "access_token_creation" { + var token = AccessToken("ghp_xxx"); + assert(token.0 == "ghp_xxx"); + } + + test "info_creation" { + var info = Info { + id = AccountID("user-123"), + email = "user@example.com", + url = "https://example.com", + active_org_id = OrgID("org-456"), + }; + assert(info.id.0 == "user-123"); + assert(info.email == "user@example.com"); + } + + test "org_creation" { + var org = Org { + id = OrgID("org-456"), + name = "Acme Inc", + }; + assert(org.name == "Acme Inc"); + } + + test "login_creation" { + var login = Login { + code = DeviceCode("device-code"), + user = UserCode("ABCD-1234"), + url = "https://example.com/auth", + server = "https://example.com", + expiry = 300000, // 5 minutes + interval = 5000, + }; + assert(login.user.0 == "ABCD-1234"); + assert(login.expiry == 300000); + } + + test "poll_result_enum_values" { + assert(PollResult::Success as u32 == 0); + assert(PollResult::Pending as u32 == 1); + assert(PollResult::Slow as u32 == 2); + assert(PollResult::Expired as u32 == 3); + assert(PollResult::Denied as u32 == 4); + assert(PollResult::Error as u32 == 5); + } + + test "is_token_fresh_with_valid_expiry" { + var expiry = (60 * 1000) + (10 * 60 * 1000); // 10 minutes from now + var now = 60 * 1000; // 1 minute from now + assert(is_token_fresh(expiry, now)); + } + + test "is_token_fresh_with_near_expiry" { + var expiry = (60 * 1000) + (3 * 60 * 1000); // 3 minutes from now + var now = 60 * 1000; // 1 minute from now + assert(!is_token_fresh(expiry, now)); + } + + test "is_token_fresh_with_null_expiry" { + var now = 60 * 1000; + assert(!is_token_fresh(null, now)); + } + + test "is_token_fresh_with_expired_token" { + var expiry = 30 * 1000; // 30 seconds from now + var now = 60 * 1000; // 1 minute from now (token already expired) + assert(!is_token_fresh(expiry, now)); + } + + test "constants_values" { + assert(CLIENT_ID == "opencode-cli"); + assert(EAGER_REFRESH_THRESHOLD_MINUTES == 5); + assert(DEFAULT_POLL_INTERVAL_MS == 5000); + } +} diff --git a/apps/website/public/t27/specs/api/c_api_contract.t27 b/apps/website/public/t27/specs/api/c_api_contract.t27 new file mode 100644 index 0000000000..5441382b98 --- /dev/null +++ b/apps/website/public/t27/specs/api/c_api_contract.t27 @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: Apache-2.0 +# C API CONTRACT -- Trinity VSA FFI Bridge + +## Specification + +Zig-backed FFI bridge for Trinity VSA core. +Exposes real SIMD-accelerated VSA core to C/C++/Python/Swift/Go via FFI. + +## Mathematical Foundation + +``` +phi^2 + 1/phi^2 = 3 = TRINITY +``` + +## Exposed Functions + +### Version + +``` +export fn trinity_vsa_version() -> [*]const u8 + Returns "0.2.0" +``` + +### Vector Lifecycle + +``` +export fn trinity_vsa_vector_zeros(dim: usize) -> ?*anyopaque + Create a zero vector with given dimension + +export fn trinity_vsa_vector_random(dim: usize, seed: u64) -> ?*anyopaque + Create a random hypervector with given dimension and seed + +export fn trinity_vsa_from_array(data: [*]const i8, dim: usize) -> ?*anyopaque + Create vector from an array of int8 values (-1, 0, +1) + +export fn trinity_vsa_vector_clone(v: ?*anyopaque) -> ?*anyopaque + Clone a vector (deep copy) + +export fn trinity_vsa_vector_free(v: ?*anyopaque) -> void + Free a vector (must be called for every created vector) +``` + +### VSA Operations + +``` +export fn trinity_vsa_bind(a, b) -> ?*anyopaque + Bind two vectors (element-wise multiplication) + bind(a, a) = all +1 (self-inverse) + +export fn trinity_vsa_unbind(a, b) -> ?*anyopaque + Inverse of bind + unbind(bind(a, b), B) = A + +export fn trinity_vsa_bundle2(a, b) -> ?*anyopaque + Bundle 2 vectors (majority voting) + +export fn trinity_vsa_bundle3(a, b, c) -> ?*anyopaque + Bundle 3 vectors (true majority voting) + +export fn trinity_vsa_permute(v, k) -> ?*anyopaque + Permute vector (cyclic shift by k positions) +``` + +### Similarity + +``` +export fn trinity_vsa_cosine_similarity(a, b) -> f64 + Cosine similarity [-1.0, 1.0] + +export fn trinity_vsa_hamming_distance(a, b) -> usize + Hamming distance (number of differing trits) + +export fn trinity_vsa_dot_product(a, b) -> i64 + Dot product (sum of element-wise products) +``` + +### Text Encoding + +``` +export fn trinity_vsa_encode_text(text: [*]const u8, len: usize) -> ?*anyopaque + Encode text string to hypervector (for semantic search) + +export fn trinity_vsa_encode_text_words(text: [*]const u8, len: usize) -> ?*anyopaque + Encode text using word-level bag-of-words + +export fn trinity_vsa_decode_text(v: ?*anyopaque, buf: [*]u8, buf_len: usize) -> usize + Decode hypervector back to text + Returns number of decoded characters written to buf +``` + +### Vector Access + +``` +export fn trinity_vsa_get_dim(v: ?*anyopaque) -> usize + Get vector dimension (number of trits) + +export fn trinity_vsa_get_trit(v: ?*anyopaque, index: usize) -> i8 + Get trit value at index (returns -1, 0, or +1) + +export fn trinity_vsa_set_trit(v: ?*anyopaque, index: usize, value: i8) -> void + Set trit value at index (value clamped to -1, 0, +1) + +export fn trinity_vsa_to_array(v: ?*anyopaque, out: [*]i8, max_len: usize) -> usize + Copy trit data to output array + Returns number of trits written + +export fn trinity_vsa_max_dim() -> usize + Get maximum supported vector dimension +``` + +## Memory Management + +All functions use opaque handles (heap-allocated HybridBigInt). + +**Important:** +- Thread-safe: Each vector is independent. No global state. +- Ownership: Free must be called for every `trinity_vsa_vector_*` created vector. +- Error handling: Null return indicates allocation failure. + +## Tests + +``` +test "C-API: version" { + const ver = trinity_vsa_version(); + try expect(slice.len > 0); +} + +test "C-API: vector zeros create/free" { + const v = trinity_vsa_vector_zeros(1000); + defer trinity_vsa_vector_free(v); + try expectEqual(@as(usize, 1000), trinity_vsa_get_dim(v)); + // All trits should be 0 + try expectEqual(@as(i8, 0), trinity_vsa_get_trit(v, 0)); + try expectEqual(@as(i8, 0), trinity_vsa_get_trit(v, 999)); +} + +test "C-API: vector random" { + const v = trinity_vsa_vector_random(1000, 42); + defer trinity_vsa_vector_free(v); + try expectEqual(@as(usize, 1000), trinity_vsa_get_dim(v)); + // Random vector should have non-zero trits + var non_zero: usize = 0; + for (0..1000) |i| { + if (trinity_vsa_get_trit(v, i) != 0) non_zero += 1; + } + try expect(non_zero > 0); +} + +test "C-API: from_array / to_array roundtrip" { + const data = [_]i8{ 1, -1, 0, 1, -1, 0, 1, -1, 0, 1 }; + const v = trinity_vsa_from_array(&data, data.len); + defer trinity_vsa_vector_free(v); + + var out: [10]i8 = undefined; + const copied = trinity_vsa_to_array(v, &out, out.len); + try expectEqual(@as(usize, 10), copied); + try expectEqualSlices(i8, &data, &out); +} + +test "C-API: clone" { + const v = trinity_vsa_vector_random(500, 123); + defer trinity_vsa_vector_free(v); + + const cloned = trinity_vsa_vector_clone(v); + defer trinity_vsa_vector_free(cloned); + + const sim = trinity_vsa_cosine_similarity(v, cloned); + try expectApproxEqAbs(@as(f64, 1.0), sim, 0.001); +} + +test "C-API: bind self-inverse" { + const a = trinity_vsa_vector_random(1000, 42); + defer trinity_vsa_vector_free(a); + + const bound = trinity_vsa_bind(a, b); + defer trinity_vsa_vector_free(bound); + + // bind(a, a) should give all +1 for non-zero trits + for (0..1000) |i| { + const a_trit = trinity_vsa_get_trit(a, i); + const r_trit = trinity_vsa_get_trit(bound, i); + if (a_trit != 0) { + try expectEqual(@as(i8, 1), r_trit); + } else { + try expectEqual(@as(i8, 0), r_trit); + } + } +} + +test "C-API: bind/unbind roundtrip" { + const a = trinity_vsa_vector_random(1000, 42); + defer trinity_vsa_vector_free(a); + + const b = trinity_vsa_vector_random(1000, 99); + defer trinity_vsa_vector_free(b); + + const bound = trinity_vsa_bind(a, b); + defer trinity_vsa_vector_free(bound); + + const recovered = trinity_vsa_unbind(bound, b); + defer trinity_vsa_vector_free(recovered); + + // Similarity > 0.7 because zero trits lose information in bind + const sim = trinity_vsa_cosine_similarity(a, recovered); + try expect(sim > 0.7); +} + +test "C-API: bundle2 similarity" { + const a = trinity_vsa_vector_random(1000, 42); + defer trinity_vsa_vector_free(a); + + const b = trinity_vsa_vector_random(1000, 99); + defer trinity_vsa_vector_free(b); + + const bundled = trinity_vsa_bundle2(a, b); + defer trinity_vsa_vector_free(bundled); + + // Bundled should be similar to both inputs + const sim_a = trinity_vsa_cosine_similarity(bundled, a); + const sim_b = trinity_vsa_cosine_similarity(bundled, b); + try expect(sim_a > 0.3); + try expect(sim_b > 0.3); +} + +test "C-API: permute" { + const v = trinity_vsa_vector_random(1000, 42); + defer trinity_vsa_vector_free(v); + + const permuted = trinity_vsa_permute(v, 5); + defer trinity_vsa_vector_free(permuted); + + // Permuted should be dissimilar to original (for high dimensions) + const sim = trinity_vsa_cosine_similarity(v, permuted); + try expect(sim < 0.9); +} +``` + +## Notes + +- Thread-safety: All functions are thread-safe (no global state) +- Alignment: Memory allocated via heap allocator (no alignment restrictions) +- FFI boundary: Uses C calling convention (Zig-specific) diff --git a/apps/website/public/t27/specs/api/sdk_contract.t27 b/apps/website/public/t27/specs/api/sdk_contract.t27 new file mode 100644 index 0000000000..c4087a6be8 --- /dev/null +++ b/apps/website/public/t27/specs/api/sdk_contract.t27 @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: Apache-2.0 +# TRINITY SDK 0 High-level API for Developers + +## Specification + +Simplified interface for hyperdimensional computing applications. +Exposes real SIMD-accelerated VSA core to C/C++/Python/Swift/Go via FFI bridge. + +## Mathematical Foundation + +``` +V = n 1 3^k 2 3^m 4 5^p 6 e^q +``` + +## Core Components + +### Hypervector + +``` +pub const Hypervector = struct { + data: HybridBigInt, + label: ?[]const u8 = null, + + fn init(dim: usize) -> Hypervector + Create zero hypervector + + fn random(dim: usize, seed: u64) -> Hypervector + Create random hypervector + + fn randomLabeled(dim: usize, seed: u64, label: []const u8) -> Hypervector + Create random hypervector with label + + fn fromRaw(raw: HybridBigInt) -> Hypervector + Create hypervector from existing HybridBigInt +}; +``` + +### VSA Operations + +``` +bind(self, other) -> Hypervector + Bind two hypervectors (creates association) + Properties: self-inverse, preserves similarity + +unbind(self, key) -> Hypervector + Inverse of bind (bind(A, B), B = A) + +bundle(self, other) -> Hypervector + Bundle two hypervectors (creates superposition) + Similar to both A and B + +bundle3(self, b, c) -> Hypervector + Bundle three hypervectors (true majority voting) + +permute(self, k) -> Hypervector + Cyclic shift by k positions (for sequence encoding) + +inversePermute(self, k) -> Hypervector + Inverse permute +``` + +### Similarity Measures + +``` +similarity(self, other) -> f64 + Cosine similarity [-1, 1] + +hammingDistance(self, other) -> usize + Hamming distance (number of differing trits) + +hammingSimilarity(self, other) -> f64 + Hamming similarity [0, 1] + +dotSimilarity(self, other) -> f64 + Dot product similarity +``` + +### Utility + +``` +countNonZero(self) -> usize + Count non-zero trits (sparsity measure) + +density(self) -> f64 + Ratio of non-zero trits + +clone(self) -> Hypervector + Deep copy hypervector +``` + +### Codebook + +``` +pub const Codebook = struct { + entries: std.StringHashMap(Hypervector), + dimension: usize, + seed_counter: u64, + allocator: std.mem.Allocator, + + fn init(allocator, dimension) -> Codebook + + fn encode(self, symbol: []const u8) -> !*Hypervector + Get or create hypervector for symbol + + fn decode(self, query: Hypervector) -> ?[]const u8 + Decode hypervector to nearest symbol + + fn decodeWithThreshold(self, query: Hypervector, threshold: f64) -> ?[]const u8 + Decode with similarity threshold + + fn count(self) -> usize + Number of symbols in codebook +}; +``` + +### Associative Memory + +``` +pub const AssociativeMemory = struct { + memory: Hypervector, + item_count: usize, + dimension: usize, + + fn init(dimension) -> AssociativeMemory + Initialize associative memory + + fn store(self, key, value) -> void + Store key-value pair (memory = bundle(memory, bind(key, value))) + + fn retrieve(self, key) -> Hypervector + Retrieve value by key (memory.unbind(key)) + + fn contains(self, key, threshold: f64) -> bool + Check if key exists (similarity > threshold) + + fn clear(self) -> void + Clear memory + + fn count(self) -> usize + Number of stored items +}; +``` + +### Sequence Encoder + +``` +pub const SequenceEncoder = struct { + dimension: usize, + + fn init(dimension) -> SequenceEncoder + + fn encode(self, items: []Hypervector) -> Hypervector + Encode sequence: [A, B, C] = A + permute(B, 1) + permute(C, 2) + + fn probe(self, sequence, candidate, position) -> f64 + Probe sequence for element at position + + fn findPosition(self, sequence, candidate, max_length, threshold) -> ?usize + Find position with highest similarity, or null if below threshold +}; +``` + +## Tests + +test sdk_hypervector_init + given hv = Hypervector.init(1024) + and nonzero = hv.countNonZero() + then nonzero == 0 + +test sdk_hypervector_random + given hv = Hypervector.random(1024, 42) + and nonzero = hv.countNonZero() + then nonzero > 0 + +test sdk_hypervector_random_labeled + given hv = Hypervector.randomLabeled(1024, 99, "alpha") + then hv.label != null + +test sdk_hypervector_from_raw + given raw = HybridBigInt.zero() + and hv = Hypervector.fromRaw(raw) + and nonzero = hv.countNonZero() + then nonzero == 0 + +test sdk_vsa_bind_self_inverse + given a = Hypervector.random(1024, 1) + and b = Hypervector.random(1024, 2) + and bound = a.bind(b) + and recovered = bound.unbind(b) + and sim = a.similarity(recovered) + then sim > 0.99 + +test sdk_vsa_unbind_recovers + given a = Hypervector.random(1024, 10) + and b = Hypervector.random(1024, 11) + and bound = a.bind(b) + and original = bound.unbind(b) + and sim = a.similarity(original) + then sim > 0.95 + +test sdk_vsa_bundle_similarity + given a = Hypervector.random(1024, 3) + and b = Hypervector.random(1024, 4) + and bundled = a.bundle(b) + and sim_a = bundled.similarity(a) + and sim_b = bundled.similarity(b) + then sim_a > 0.0 + then sim_b > 0.0 + +test sdk_vsa_bundle3_consensus + given a = Hypervector.random(1024, 5) + and b = Hypervector.random(1024, 6) + and c = Hypervector.random(1024, 7) + and bundled = a.bundle3(b, c) + and sim_a = bundled.similarity(a) + then sim_a > 0.0 + +test sdk_vsa_permute_roundtrip + given v = Hypervector.random(1024, 20) + and shifted = v.permute(3) + and unshifted = shifted.inversePermute(3) + and sim = v.similarity(unshifted) + then sim > 0.99 + +test sdk_vsa_permute_decorrelates + given v = Hypervector.random(1024, 21) + and shifted = v.permute(1) + and sim = v.similarity(shifted) + then abs(sim) < 0.3 + +test sdk_similarity_bounds + given a = Hypervector.random(1024, 30) + and b = Hypervector.random(1024, 31) + and sim = a.similarity(b) + then sim >= -1.0 and sim <= 1.0 + +test sdk_hamming_distance_self + given v = Hypervector.random(1024, 40) + and dist = v.hammingDistance(v) + then dist == 0 + +test sdk_hamming_similarity_self + given v = Hypervector.random(1024, 41) + and sim = v.hammingSimilarity(v) + then abs(sim - 1.0) < 0.01 + +test sdk_dot_similarity_self + given v = Hypervector.random(1024, 42) + and sim = v.dotSimilarity(v) + then sim > 0.0 + +test sdk_count_nonzero + given v = Hypervector.random(1024, 50) + and count = v.countNonZero() + then count > 0 + then count <= 1024 + +test sdk_density_range + given v = Hypervector.random(1024, 51) + and d = v.density() + then d > 0.0 and d <= 1.0 + +test sdk_clone_independence + given v = Hypervector.random(1024, 55) + and c = v.clone() + and sim = v.similarity(c) + then sim > 0.99 + +test sdk_codebook_encode_decode_roundtrip + given cb = Codebook.init(default_allocator, 1024) + and _ = cb.encode("alpha") + and hv = cb.encode("alpha") + and label = cb.decode(hv.?) + then label.? == "alpha" + +test sdk_codebook_count + given cb = Codebook.init(default_allocator, 1024) + and _ = cb.encode("x") + and _ = cb.encode("y") + and _ = cb.encode("z") + then cb.count() == 3 + +test sdk_codebook_threshold_decode + given cb = Codebook.init(default_allocator, 1024) + and _ = cb.encode("hello") + and hv = cb.encode("hello") + and label = cb.decodeWithThreshold(hv.?, 0.5) + then label.? == "hello" + +test sdk_associative_memory_store_retrieve + given am = AssociativeMemory.init(1024) + and key = Hypervector.random(1024, 60) + and val = Hypervector.random(1024, 61) + and _ = am.store(key, val) + and retrieved = am.retrieve(key) + and sim = retrieved.similarity(val) + then sim > 0.5 + +test sdk_associative_memory_contains + given am = AssociativeMemory.init(1024) + and key = Hypervector.random(1024, 62) + and val = Hypervector.random(1024, 63) + and _ = am.store(key, val) + then am.contains(key, 0.1) == true + +test sdk_associative_memory_clear + given am = AssociativeMemory.init(1024) + and key = Hypervector.random(1024, 64) + and val = Hypervector.random(1024, 65) + and _ = am.store(key, val) + and _ = am.clear() + then am.count() == 0 + +test sdk_sequence_encode_probe + given se = SequenceEncoder.init(1024) + and a = Hypervector.random(1024, 70) + and b = Hypervector.random(1024, 71) + and c = Hypervector.random(1024, 72) + and seq = se.encode([a, b, c]) + and probe0 = se.probe(seq, a, 0) + and probe1 = se.probe(seq, b, 1) + and probe2 = se.probe(seq, c, 2) + then probe0 > 0.1 + then probe1 > 0.1 + then probe2 > 0.1 + +test sdk_sequence_find_position + given se = SequenceEncoder.init(1024) + and a = Hypervector.random(1024, 80) + and b = Hypervector.random(1024, 81) + and seq = se.encode([a, b]) + and pos = se.findPosition(seq, a, 4, 0.1) + then pos.? == 0 + +## Invariants + +invariant sdk_similarity_bounded + given a = Hypervector.random(1024, 100) + and b = Hypervector.random(1024, 101) + and sim = a.similarity(b) + assert sim >= -1.0 and sim <= 1.0 + +invariant sdk_hamming_distance_non_negative + given a = Hypervector.random(1024, 102) + and b = Hypervector.random(1024, 103) + and dist = a.hammingDistance(b) + assert dist >= 0 + +invariant sdk_hamming_similarity_bounded + given a = Hypervector.random(1024, 104) + and b = Hypervector.random(1024, 105) + and sim = a.hammingSimilarity(b) + assert sim >= 0.0 and sim <= 1.0 + +invariant sdk_density_bounded + given v = Hypervector.random(1024, 106) + and d = v.density() + assert d >= 0.0 and d <= 1.0 + +invariant sdk_bind_preserves_dimension + given a = Hypervector.random(1024, 110) + and b = Hypervector.random(1024, 111) + and bound = a.bind(b) + assert bound.countNonZero() <= 1024 + +invariant sdk_self_similarity_one + given v = Hypervector.random(1024, 112) + and sim = v.similarity(v) + assert abs(sim - 1.0) < 0.01 + +invariant sdk_codebook_count_monotonic + given cb = Codebook.init(default_allocator, 1024) + assert cb.count() == 0 + +invariant sdk_associative_memory_dimension_positive + assert 1024 > 0 + +## Benchmarks + +bench sdk_hypervector_random_latency + measure: nanoseconds to create random Hypervector(1024, seed) + target: < 5000ns + +bench sdk_bind_latency + measure: nanoseconds to bind two Hypervector(1024) + target: < 10000ns + +bench sdk_bundle_latency + measure: nanoseconds to bundle two Hypervector(1024) + target: < 10000ns + +bench sdk_similarity_latency + measure: nanoseconds to compute cosine similarity + target: < 10000ns + +bench sdk_codebook_lookup_latency + measure: nanoseconds to decode from Codebook(256 entries) + target: < 500000ns + +bench sdk_associative_memory_store_latency + measure: nanoseconds to store key-value pair + target: < 20000ns + +bench sdk_sequence_encode_latency + measure: nanoseconds to encode 3-element sequence + target: < 30000ns diff --git a/apps/website/public/t27/specs/api/tri_net_api.t27 b/apps/website/public/t27/specs/api/tri_net_api.t27 new file mode 100644 index 0000000000..38212dec2a --- /dev/null +++ b/apps/website/public/t27/specs/api/tri_net_api.t27 @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: Apache-2.0 +# TRI-NET API CONTRACT 0 External Integration Surface + +## Specification + +File-based, read-only API surface that downstream tools (chip-repo CI, +third-party auditors, formal-methods reviewers) consume to interact with +TRI-NET artefacts produced by t27 and its sibling chip repos. There is +no hosted network endpoint in scope. + +Human-readable companion: docs/TRI_NET_API.md +Schema: schemas/tri-net-api-v1.json +Numeric SSOT: conformance/FORMAT-SPEC-001.json + schemas/numeric-format-v1.json + +## Mathematical Foundation + +``` +phi^2 + 1/phi^2 = 3 = TRINITY +``` + +The API has no math content of its own; it merely shapes how TRI-NET +artefacts that *do* carry math content are produced and consumed. + +## Versioning + +``` +schema_version: semver + MAJOR change -> breaking; consumer must opt in + MINOR change -> additive; consumer should ignore unknown fields + PATCH change -> editorial; no semantic change +current: 1.0 +``` + +## Artefact Families + +### Format registry +``` +path: conformance/FORMAT-SPEC-001.json +schema: schemas/numeric-format-v1.json +host: t27 only (chip repos consume; do not republish) +law: L6 CEILING +``` + +### NMSE protocol results +``` +path: bench/results/nmse---.json +schema: schemas/nmse-protocol-v1.json +companion_doc: docs/GF16_BFLOAT16_NMSE_PROTOCOL.md +``` + +### Toolchain seal +``` +path: bootstrap/stage0/FROZEN_HASH +format: single SHA-256 hex digest, trailing newline allowed +law: L2 GENERATION +``` + +### Readiness ladder (programmatic mirror, optional) +``` +path: bench/readiness.json // optional +schema: schemas/tri-net-api-v1.json#/$defs/Readiness +levels: [ SPEC, RTL, SIM, SYNTH, GDS_TAPEOUT, SILICON ] +authority: STATUS.md is human-readable SSOT; programmatic mirror is opt-in +``` + +### Conformance vectors +``` +path_glob: conformance/*.json +families: gf*_vectors, ar_*, nn_*, sacred_physics*, FORMAT-SPEC-001 +``` + +### Cross-repo identity (optional) +``` +path: tri-net-identity.json // top level of any TRI-NET-conforming repo +schema: schemas/tri-net-api-v1.json#/$defs/RepoIdentity +purpose: + lets a consumer enumerate the four products of the line without + scraping documentation +``` + +## Consumer Contract + +``` +must pin a schema MAJOR +must validate every artefact before treating fields as authoritative +must fail closed on schema violation +should verify seal_hash matches bootstrap/stage0/FROZEN_HASH +should log schema_version in any output +``` + +## Producer Contract + +``` +must emit only artefacts that validate against the active schema +must include schema_version at artefact root +must place extensions under reserved x_extension object +must cite seal_hash when numeric results are reported +should publish tri-net-identity.json at repo root +``` + +## Tests + +``` +test "API: format registry validates" { + // conformance/FORMAT-SPEC-001.json validates against + // schemas/numeric-format-v1.json +} + +test "API: schema version present" { + // Every conforming artefact carries a schema_version field at the + // top level; reject otherwise. +} + +test "API: seal hash matches frozen seal" { + // A produced artefact whose seal_hash != contents of + // bootstrap/stage0/FROZEN_HASH is non-conforming. +} + +test "API: fail-closed on undocumented top-level field" { + // Producers cannot add new top-level keys; extensions go under + // x_extension or fail validation. +} +``` + +## Invariants + +``` +invariant "no hosted endpoint claim" { + // No artefact in this API surface claims a hosted runtime exists. +} + +invariant "single source for format registry" { + // FORMAT-SPEC-001.json is hosted in t27 only; chip-repo copies are + // verbatim mirrors with no schema drift. +} + +invariant "extension namespace" { + // Unknown keys must reside under x_extension; otherwise validation + // fails. +} + +invariant "L6 CEILING preserved" { + // The numeric registry is read-only at the API; changing it + // requires a constitutional amendment, not an API change. +} +``` + +## Benchmark + +``` +bench "TRI-NET API schema round-trip" { + procedure: + - for each artefact family above + - produce a minimal valid artefact (golden file under + bench/api_goldens/) and validate against its schema + - mutate one required field; confirm validation fails + output: bench/results/api-roundtrip-.json + success_criterion: 100% of golden files validate, 100% of mutated + golden files fail validation +} +``` + +## What This API Is Not + +``` +- not a hosted HTTP service +- not an SDK +- not a control plane for any chip +- not a place where silicon-bring-up endpoints live +``` + +## Cross-links + +- docs/TRI_NET_API.md (human-readable mirror) +- docs/TRI_NET_WHITEPAPER.md (line positioning) +- schemas/tri-net-api-v1.json (this API's schema) +- schemas/numeric-format-v1.json (numeric SSOT schema) +- schemas/nmse-protocol-v1.json (NMSE manifest schema) +- LINEUP.md (the four-product map) +- STATUS.md (readiness ladder) +- specs/api/c_api_contract.t27 (separate FFI bridge contract) +- specs/api/sdk_contract.t27 (separate SDK contract) + +## R5-HONEST Notes + +- API is file-based today; no network endpoint is implied. +- Schemas describe SHAPE only; they do not promise silicon behaviour. +- No throughput / latency / energy field is part of the canonical + surface today. diff --git a/apps/website/public/t27/specs/ar/asp_solver.t27 b/apps/website/public/t27/specs/ar/asp_solver.t27 new file mode 100644 index 0000000000..533172cb2d --- /dev/null +++ b/apps/website/public/t27/specs/ar/asp_solver.t27 @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: Apache-2.0 +// spec: AspSolver +// Answer Set Programming solver for neuro-symbolic reasoning + +spec AspSolver { + // ASP literal structure + struct Literal { + name: string, + is_negated: bool, + args: [string] + } + + // ASP clause structure + struct Clause { + literals: [Literal] + } + + // ASP program (set of clauses) + struct Program { + clauses: [Clause], + constraints: [Clause] + } + + // Answer set (stable model) + struct AnswerSet { + literals: [Literal], + proven: bool, + cost: int + } + + // Maximum ASP derivation steps (bounded for explainability) + const MAX_ASP_STEPS: int = 10 + + // Create empty ASP program + fn new_program() -> Program { + return Program { + clauses: [], + constraints: [] + } + } + + // Add clause to program + fn add_clause(prog: Program, clause: Clause) -> Program { + let new_clauses = prog.clauses; + new_clauses.push(clause); + return Program { + clauses: new_clauses, + constraints: prog.constraints + } + } + + // Add constraint to program + fn add_constraint(prog: Program, constraint: Clause) -> Program { + let new_constraints = prog.constraints; + new_constraints.push(constraint); + return Program { + clauses: prog.clauses, + constraints: new_constraints + } + } + + // Solve ASP program (find stable models) + fn solve(prog: Program) -> [AnswerSet] { + let trace = new_proof_trace(); + let mut answer_sets: [AnswerSet] = []; + + // Bottom-up grounding and solving + let step = 0; + while step < MAX_ASP_STEPS { + step = step + 1; + + // Find candidate model + let candidate = find_stable_model(prog); + if candidate == null { + break + } + + trace = add_step(trace, "asp_grounding", [], K_TRUE); + + // Verify stability + let is_stable = verify_stability(prog, candidate); + if is_stable { + trace = add_step(trace, "asp_stability_verified", [], K_TRUE); + trace = add_step(trace, "asp_answer_found", [], K_TRUE); + answer_sets.push(candidate); + break + } + + trace = add_step(trace, "asp_next_model", [], K_TRUE); + } + + trace = finalize_trace(trace); + return answer_sets + } + + // Find stable model through grounded reasoning + fn find_stable_model(prog: Program) -> AnswerSet { + // Generate all possible literal combinations (bounded) + let all_lits = extract_all_literals(prog); + let candidates = generate_combinations(all_lits); + + for candidate in candidates { + if satisfies_clauses(prog, candidate) && satisfies_constraints(prog, candidate) { + let cost = calculate_cost(candidate); + return AnswerSet { + literals: candidate, + proven: true, + cost: cost + } + } + } + + return AnswerSet { + literals: [], + proven: false, + cost: 0 + } + } + + // Extract all literals from program + fn extract_all_literals(prog: Program) -> [Literal] { + let mut lits: [Literal] = []; + + for clause in prog.clauses { + for lit in clause.literals { + if !literal_in_list(lits, lit) { + lits.push(lit); + } + } + } + + for clause in prog.constraints { + for lit in clause.literals { + if !literal_in_list(lits, lit) { + lits.push(lit); + } + } + } + + return lits + } + + // Generate combinations of literals + fn generate_combinations(literals: [Literal]) -> [[Literal]] { + // Generate all 2^n combinations + let count = len(literals); + let mut combinations: [[Literal]] = []; + let mut i = 0; + + while i < (1 << count) { + let mut combo: [Literal] = []; + + for j in range(0, count) { + if (i >> j) & 1 == 1 { + combo.push(literals[j]); + } + } + + combinations.push(combo); + i = i + 1; + } + + return combinations + } + + // Check if literal is in list + fn literal_in_list(list: [Literal], lit: Literal) -> bool { + for l in list { + if l.name == lit.name && l.args == lit.args { + return true + } + } + return false + } + + // Check if candidate satisfies all clauses + fn satisfies_clauses(prog: Program, candidate: [Literal]) -> bool { + for clause in prog.clauses { + if !satisfies_clause(clause, candidate) { + return false + } + } + return true + } + + // Check if candidate satisfies all constraints + fn satisfies_constraints(prog: Program, candidate: [Literal]) -> bool { + for constraint in prog.constraints { + // Constraints must be false (no violation) + if satisfies_clause(constraint, candidate) { + return false + } + } + return true + } + + // Check if clause is satisfied by candidate model + fn satisfies_clause(clause: Clause, model: [Literal]) -> bool { + for lit in clause.literals { + let satisfied = false; + + for model_lit in model { + if literals_match(lit, model_lit) { + satisfied = true; + break + } + } + + if !satisfied { + return false + } + } + + return true + } + + // Check if two literals match (considering negation) + fn literals_match(a: Literal, b: Literal) -> bool { + if a.name != b.name { + return false + } + if len(a.args) != len(b.args) { + return false + } + + for i in range(0, len(a.args)) { + if a.args[i] != b.args[i] { + return false + } + } + + // Negation must match for negation matching + if a.is_negated == b.is_negated { + return false + } + + return true + } + + // Verify stability of answer set + fn verify_stability(prog: Program, answer: AnswerSet) -> bool { + let model_literals = answer.literals; + + // Check each literal in model + for lit in model_literals { + // If lit is in model, its negation should NOT be derivable + let neg_lit = negate_literal(lit); + + if is_derivable(prog, neg_lit, model_literals) { + return false + } + } + + return true + } + + // Negate a literal + fn negate_literal(lit: Literal) -> Literal { + return Literal { + name: lit.name, + is_negated: !lit.is_negated, + args: lit.args + } + } + + // Check if literal is derivable from program and model + fn is_derivable(prog: Program, lit: Literal, model: [Literal]) -> bool { + // Check if lit is in model + for model_lit in model { + if literals_match(lit, model_lit) { + return true + } + } + + // Check if derivable through grounding + for clause in prog.clauses { + let clause_lits = clause.literals; + let mut body_lits: [Literal] = []; + + for i in range(0, len(clause_lits)) { + let clause_lit = clause_lits[i]; + if !literals_match(clause_lit, lit) { + body_lits.push(clause_lit); + } + } + + // Check if all body literals are in model + let all_body_true = true; + for body_lit in body_lits { + let found = false; + for model_lit in model { + if literals_match(body_lit, model_lit) { + found = true; + break + } + } + if !found { + all_body_true = false; + break + } + } + + if all_body_true { + return true + } + } + + return false + } + + // Calculate cost of answer set + fn calculate_cost(answer: AnswerSet) -> int { + let cost = 0; + for lit in answer.literals { + // Prefer positive literals + if lit.is_negated { + cost = cost + 1 + } + } + return cost + } + + // NAF (Negation as Failure) operator + fn naf(prog: Program, lit: Literal) -> bool { + // NAF returns true if lit is NOT derivable + return !is_derivable(prog, lit, []) + } + + // INVARIANTS + + // Stable model satisfies all clauses + invariant stable_model_satisfies_clauses { + let prog = new_program(); + let clause1 = Clause {literals: [Literal {name: "p", is_negated: false, args: []}]}; + prog = add_clause(prog, clause1); + + let model = AnswerSet { + literals: [Literal {name: "p", is_negated: false, args: []}], + proven: true, + cost: 0 + }; + + assert satisfies_clauses(prog, model.literals) + } + + // NAF is dual of derivability + invariant naf_dual_derivability { + let prog = new_program(); + let clause = Clause {literals: [Literal {name: "p", is_negated: false, args: []}]}; + prog = add_clause(prog, clause); + + let lit = Literal {name: "p", is_negated: false, args: []}; + + let derivable = is_derivable(prog, lit, []); + let not_derivable = naf(prog, lit); + + assert derivable != not_derivable + } + + // Cost of model is minimal for positive literals + invariant minimal_positive_cost { + let model = AnswerSet { + literals: [Literal {name: "p", is_negated: false, args: []}], + proven: true, + cost: 0 + }; + + let alt_model = AnswerSet { + literals: [Literal {name: "p", is_negated: false, args: []}, + proven: true, + cost: 1 + }; + + assert calculate_cost(model) <= calculate_cost(alt_model) + } + + // Max ASP steps bound enforced + invariant max_asp_steps_enforced { + let prog = new_program(); + // Add many clauses to require derivation + for i in range(0, MAX_ASP_STEPS + 5) { + let lit = Literal {name: format("p{}", i), is_negated: false, args: []}; + let clause = Clause {literals: [lit]}; + prog = add_clause(prog, clause); + } + + let answers = solve(prog); + // Should find solution within MAX_ASP_STEPS + assert true // Completion means bounded + } + + // TESTS + + test new_program_creates_empty { + let prog = new_program(); + assert len(prog.clauses) == 0; + assert len(prog.constraints) == 0 + } + + test add_clause_increments_count { + let prog = new_program(); + let clause = Clause {literals: [Literal {name: "p", is_negated: false, args: []}]}; + prog = add_clause(prog, clause); + assert len(prog.clauses) == 1 + } + + test add_constraint_increments_count { + let prog = new_program(); + let constraint = Clause {literals: [Literal {name: "p", is_negated: false, args: []}]}; + prog = add_constraint(prog, constraint); + assert len(prog.constraints) == 1 + } + + test solve_finds_stable_model { + let prog = new_program(); + let clause = Clause {literals: [Literal {name: "p", is_negated: false, args: []}]}; + prog = add_clause(prog, clause); + let answers = solve(prog); + assert len(answers) >= 1 + } + + test verify_stability_accepts_valid { + let prog = new_program(); + let clause = Clause {literals: [Literal {name: "p", is_negated: false, args: []}]}; + prog = add_clause(prog, clause); + + let model = AnswerSet { + literals: [Literal {name: "p", is_negated: false, args: []}], + proven: true, + cost: 0 + }; + + assert verify_stability(prog, model) + } + + test verify_stability_rejects_unstable { + let prog = new_program(); + let clause1 = Clause {literals: [Literal {name: "p", is_negated: false, args: []}]}; + prog = add_clause(prog, clause1); + // Add clause that forces both p and not_p to be true (unstable) + let clause2 = Clause {literals: [ + Literal {name: "p", is_negated: false, args: []}, + Literal {name: "p", is_negated: true, args: []} + ]}; + prog = add_clause(prog, clause2); + + let model = AnswerSet { + literals: [Literal {name: "p", is_negated: false, args: []}], + proven: true, + cost: 0 + }; + + assert !verify_stability(prog, model) + } + + test satisfies_clause_true_for_true_literal { + let clause = Clause {literals: [Literal {name: "p", is_negated: false, args: []}]}; + let model = [Literal {name: "p", is_negated: false, args: []}]; + assert satisfies_clause(clause, model) + } + + test satisfies_clause_false_for_missing_literal { + let clause = Clause {literals: [Literal {name: "p", is_negated: false, args: []}]}; + let model: [Literal] = []; + assert !satisfies_clause(clause, model) + } + + test satisfies_clause_handles_negation { + let clause = Clause {literals: [Literal {name: "p", is_negated: true, args: []}]}; + let model = [Literal {name: "p", is_negated: true, args: []}]; + assert satisfies_clause(clause, model) + } + + test naf_returns_false_for_derivable { + let prog = new_program(); + let clause = Clause {literals: [Literal {name: "p", is_negated: false, args: []}]}; + prog = add_clause(prog, clause); + + let lit = Literal {name: "p", is_negated: false, args: []}; + assert !naf(prog, lit) + } + + test naf_returns_true_for_non_derivable { + let prog = new_program(); + let lit = Literal {name: "p", is_negated: false, args: []}; + assert naf(prog, lit) // p is not derivable + } + + test calculate_cost_prefers_positive { + let model1 = AnswerSet { + literals: [Literal {name: "p", is_negated: false, args: []}], + proven: true, + cost: 0 + }; + + let model2 = AnswerSet { + literals: [Literal {name: "p", is_negated: true, args: []}], + proven: true, + cost: 1 + }; + + assert calculate_cost(model1) < calculate_cost(model2) + } + + test generate_combinations_creates_all_subsets { + let lits = [Literal {name: "a", is_negated: false, args: []}]; + let combinations = generate_combinations(lits); + assert len(combinations) == 2 // {}, {a} + } + + test generate_combinations_two_literals { + let lits = [ + Literal {name: "a", is_negated: false, args: []}, + Literal {name: "b", is_negated: false, args: []} + ]; + let combinations = generate_combinations(lits); + assert len(combinations) == 4 // {}, {a}, {b}, {a,b} + } + + test literals_match_exact { + let a = Literal {name: "p", is_negated: false, args: ["x"]}; + let b = Literal {name: "p", is_negated: false, args: ["x"]}; + assert literals_match(a, b) + } + + test literals_match_different_name { + let a = Literal {name: "p", is_negated: false, args: ["x"]}; + let b = Literal {name: "q", is_negated: false, args: ["x"]}; + assert !literals_match(a, b) + } + + test literals_match_different_negation { + let a = Literal {name: "p", is_negated: false, args: []}; + let b = Literal {name: "p", is_negated: true, args: []}; + assert !literals_match(a, b) + } + + // BENCHMARKS + + bench solve_latency { + // Target: <50μs for simple 2-literal program + } + + bench grounding_latency { + // Target: <10μs per clause + } + + bench stability_check_latency { + // Target: <20μs for verification + } + + bench naf_latency { + // Target: <15μs for NAF operator + } +} diff --git a/apps/website/public/t27/specs/ar/coa_planning.t27 b/apps/website/public/t27/specs/ar/coa_planning.t27 new file mode 100644 index 0000000000..b8971d12b0 --- /dev/null +++ b/apps/website/public/t27/specs/ar/coa_planning.t27 @@ -0,0 +1,637 @@ +// SPDX-License-Identifier: Apache-2.0 +// spec: CoaPlanning +// Course of Action (COA) planning for neuro-symbolic reasoning + +spec CoaPlanning { + // COA action types + enum ActionType { + ASSESS = 0, // Threat/situation assessment + ALLOCATE = 1, // Resource allocation + EXECUTE = 2, // Execute action + VERIFY = 3, // Verify effectiveness + COORDINATE = 4, // Coordinate with allies + EVACUATE = 5, // Evacuation route planning + DEFEND = 6, // Defensive positioning + NEUTRALIZE = 7, // Threat neutralization + DEPLOY = 8, // Deploy assets + WITHDRAW = 9, // Withdrawal operations + MAINTAIN = 10 // Maintain position + } + + // COA step structure + struct COAStep { + step_id: int, + action_type: ActionType, + description: string, + resources: [string], + prerequisites: [int], + proof_trace: [ProofStep], + estimated_duration: float + } + + // Complete COA plan + struct CourseOfAction { + coa_id: string, + objective: string, + threat_assessment: Trit, + steps: [COAStep], + total_steps: int, + total_duration: float, + verification_status: Trit, + proof_trace: ProofTrace, + } + + // COA constraint type + enum ConstraintType { + TEMPORAL = 0, // Time-based constraints + RESOURCE = 1, // Resource limitations + GEOSPATIAL = 2, // Geographic constraints + TACTICAL = 3, // Tactical constraints + LOGICAL = 4, // Logical consistency + ROB = 5 // Rules of Engagement compliance + } + + // COA constraint + struct COAConstraint { + constraint_type: ConstraintType, + description: string, + enforces: [Trit] // K3 constraints + } + + // Maximum COA steps (bounded for explainability) + const MAX_COA_STEPS: int = 10 + + // Maximum planning depth + const MAX_PLANNING_DEPTH: int = 5 + + // Create new COA + fn new_coa(coa_id: string, objective: string) -> CourseOfAction { + return CourseOfAction { + coa_id: coa_id, + objective: objective, + threat_assessment: K_UNKNOWN, + steps: [], + total_steps: 0, + total_duration: 0.0, + verification_status: K_UNKNOWN, + proof_trace: new_proof_trace() + } + } + + // Add step to COA + fn add_step(coa: CourseOfAction, step: COAStep) -> CourseOfAction { + if len(coa.steps) >= MAX_COA_STEPS { + return coa // Reject steps beyond MAX + } + + let new_steps = coa.steps; + new_steps.push(step); + + return CourseOfAction { + coa_id: coa.coa_id, + objective: coa.objective, + threat_assessment: coa.threat_assessment, + steps: new_steps, + total_steps: len(new_steps), + total_duration: coa.total_duration + step.estimated_duration, + verification_status: coa.verification_status, + proof_trace: coa.proof_trace + } + } + + // Generate COA from threat assessment + fn generate_coa(threat_type: string, threat_severity: Trit) -> CourseOfAction { + let coa_id = format("COA-{}-{}", threat_type, now()); + let objective = format("Neutralize {} threat", threat_type); + let coa = new_coa(coa_id, objective); + + // Step 1: Assess threat + let step1 = COAStep { + step_id: 0, + action_type: ActionType::ASSESS, + description: "Assess threat level using K3 reasoning", + resources: ["Sensors", "K3Reasoner"], + prerequisites: [], + proof_trace: [ProofStep {step_id: 0, operation: "k3_assess", inputs: [threat_severity], output: threat_severity}], + estimated_duration: 1.0 + }; + coa = add_step(coa, step1); + + // Step 2: Determine defensive posture + let step2 = COAStep { + step_id: 1, + action_type: ActionType::ALLOCATE, + description: "Determine defensive posture", + resources: ["MLP", "ConstraintSolver"], + prerequisites: [0], + proof_trace: [ProofStep {step_id: 1, operation: "neural_predict", inputs: [K_TRUE], output: K_TRUE}], + estimated_duration: 2.0 + }; + coa = add_step(coa, step2); + + // Step 3: Allocate resources + let step3 = COAStep { + step_id: 2, + action_type: ActionType::ALLOCATE, + description: "Allocate defensive resources", + resources: ["ResourceDB", "ASPSolver"], + prerequisites: [1], + proof_trace: [ProofStep {step_id: 2, operation: "asp_solve", inputs: [K_TRUE], output: K_TRUE}], + estimated_duration: 3.0 + }; + coa = add_step(coa, step3); + + // Step 4: Execute defensive actions + let step4 = COAStep { + step_id: 3, + action_type: ActionType::EXECUTE, + description: "Execute defensive actions", + resources: ["Actuators", "RLPolicy"], + prerequisites: [2], + proof_trace: [ProofStep {step_id: 3, operation: "rl_select", inputs: [K_TRUE], output: K_TRUE}], + estimated_duration: 5.0 + }; + coa = add_step(coa, step4); + + // Step 5: Verify effectiveness + let step5 = COAStep { + step_id: 4, + action_type: ActionType::VERIFY, + description: "Verify defensive effectiveness", + resources: ["CNN", "K3Reasoner"], + prerequisites: [3], + proof_trace: [ProofStep {step_id: 4, operation: "k3_verify", inputs: [K_TRUE], output: K_TRUE}], + estimated_duration: 2.0 + }; + coa = add_step(coa, step5); + + coa.threat_assessment = threat_severity; + + return coa + } + + // Verify COA meets constraints + fn verify_coa(coa: CourseOfAction, constraints: [COAConstraint]) -> (bool, string) { + let errors: [string] = []; + + // Check step count constraint + if coa.total_steps > MAX_COA_STEPS { + errors.push(format("Too many steps: {} > {}", coa.total_steps, MAX_COA_STEPS)); + } + + // Check each constraint + for constraint in constraints { + if !satisfies_constraint(coa, constraint) { + errors.push(format("Constraint failed: {}", constraint.description)); + } + } + + // Verify proof trace + for step in coa.steps { + if len(step.proof_trace) == 0 { + errors.push(format("Step {} missing proof trace", step.step_id)); + } + } + + if len(errors) == 0 { + return (true, "COA verified successfully") + } else { + return (false, join("; ", errors)) + } + } + + // Check if COA satisfies a constraint + fn satisfies_constraint(coa: CourseOfAction, constraint: COAConstraint) -> bool { + match constraint.constraint_type { + ConstraintType::TEMPORAL => { + // Total duration within limits + return coa.total_duration < constraint.description as float + }, + ConstraintType::RESOURCE => { + // Resource availability satisfied + // (Detailed in constraint description) + return true + }, + ConstraintType::GEOSPATIAL => { + // Geographic feasibility + return true + }, + ConstraintType::TACTICAL => { + // Tactical constraints + return true + }, + ConstraintType::LOGICAL => { + // Logical consistency using K3 + let all_consistent = k3_and_all(constraint.enforces); + return all_consistent == K_TRUE + }, + ConstraintType::ROB => { + // ROE compliance + return true + } + } + } + + // Validate COA prerequisites + fn validate_prerequisites(coa: CourseOfAction) -> (bool, [int]) { + let mut errors: [int] = []; + let mut visited: [bool] = []; + + for i in range(0, len(coa.steps)) { + let step = coa.steps[i]; + + // Check prerequisites exist + for prereq in step.prerequisites { + if prereq < 0 || prereq >= i { + errors.push(step.step_id); // Invalid prerequisite + } else if !visited[prereq] { + errors.push(step.step_id); // Prerequisite not completed + } + } + + visited[i] = true + } + + return (len(errors) == 0, errors) + } + + // Get total proof trace from COA + fn extract_proof_trace(coa: CourseOfAction) -> ProofTrace { + let mut all_steps: [ProofStep] = []; + + for step in coa.steps { + for proof_step in step.proof_trace { + all_steps.push(proof_step) + } + } + + return ProofTrace { + steps: all_steps, + start_timestamp: 0, + end_timestamp: 0, + verified: true + } + } + + // Format COA for human review + fn format_coa(coa: CourseOfAction) -> string { + let lines: [string] = []; + + lines.push("=== Course of Action ==="); + lines.push(format("COA ID: {}", coa.coa_id)); + lines.push(format("Objective: {}", coa.objective)); + lines.push(format("Threat Assessment: {}", trit_to_string(coa.threat_assessment))); + lines.push(format("Total Steps: {} (Max: {})", coa.total_steps, MAX_COA_STEPS)); + lines.push(format("Estimated Duration: {:.1f} hours", coa.total_duration)); + lines.push(format("Verification Status: {}", trit_to_string(coa.verification_status))); + lines.push(""); + + for i in range(0, len(coa.steps)) { + let step = coa.steps[i]; + lines.push(format("Step {}: {}", i + 1)); + lines.push(format(" Action: {}", action_type_to_string(step.action_type))); + lines.push(format(" Description: {}", step.description)); + lines.push(format(" Resources: {}", join(", ", step.resources))); + if len(step.prerequisites) > 0 { + lines.push(format(" Prerequisites: {}", join(", ", step.prerequisites))); + } else { + lines.push(" Prerequisites: None"); + } + lines.push(format(" Duration: {:.1f}h", step.estimated_duration)); + lines.push(format(" Proof Steps: {}", len(step.proof_trace))); + } + + return join("\n", lines) + } + + // Convert Trit to string + fn trit_to_string(t: Trit) -> string { + if t == K_TRUE { + return "TRUE" + } else if t == K_FALSE { + return "FALSE" + } else if t == K_UNKNOWN { + return "UNKNOWN" + } else { + return "INVALID" + } + } + + // Convert ActionType to string + fn action_type_to_string(action: ActionType) -> string { + match action { + ActionType::ASSESS => "ASSESS", + ActionType::ALLOCATE => "ALLOCATE", + ActionType::EXECUTE => "EXECUTE", + ActionType::VERIFY => "VERIFY", + ActionType::COORDINATE => "COORDINATE", + ActionType::EVACUATE => "EVACUATE", + ActionType::DEFEND => "DEFEND", + ActionType::NEUTRALIZE => "NEUTRALIZE", + ActionType::DEPLOY => "DEPLOY", + ActionType::WITHDRAW => "WITHDRAW", + ActionType::MAINTAIN => "MAINTAIN" + } + } + + // INVARIANTS + + // COA generation produces valid plan + invariant coa_generation_produces_plan { + let coa = generate_coa("UAV", K_TRUE); + assert coa.total_steps >= 1 + assert coa.total_steps <= MAX_COA_STEPS + } + + // COA steps bounded by MAX_COA_STEPS + invariant coa_steps_bounded { + let coa = new_coa("test", K_TRUE); + + // Try to add MAX_COA_STEPS + 1 steps + for i in range(0, MAX_COA_STEPS + 1) { + let step = COAStep { + step_id: i, + action_type: ActionType::ASSESS, + description: "test", + resources: [], + prerequisites: [], + proof_trace: [], + estimated_duration: 1.0 + }; + coa = add_step(coa, step); + } + + // Should be capped at MAX_COA_STEPS + assert coa.total_steps == MAX_COA_STEPS + } + + // Valid prerequisites produce DAG + invariant valid_prerequisites_dag { + let coa = new_coa("test", K_TRUE); + + // Step 0: no prerequisites + let step0 = COAStep { + step_id: 0, + action_type: ActionType::ASSESS, + description: "test", + resources: [], + prerequisites: [], + proof_trace: [], + estimated_duration: 1.0 + }; + coa = add_step(coa, step0); + + // Step 1: depends on 0 + let step1 = COAStep { + step_id: 1, + action_type: ActionType::EXECUTE, + description: "test", + resources: [], + prerequisites: [0], + proof_trace: [], + estimated_duration: 1.0 + }; + coa = add_step(coa, step1); + + // Step 2: depends on 1 + let step2 = COAStep { + step_id: 2, + action_type: ActionType::VERIFY, + description: "test", + resources: [], + prerequisites: [1], + proof_trace: [], + estimated_duration: 1.0 + }; + coa = add_step(coa, step2); + + let (valid, errors) = validate_prerequisites(coa); + assert valid + } + + // TESTS + + test new_coa_creates_empty { + let coa = new_coa("test", K_UNKNOWN); + assert len(coa.steps) == 0; + assert coa.total_steps == 0; + assert coa.verification_status == K_UNKNOWN + } + + test generate_coa_creates_valid_plan { + let coa = generate_coa("UAV", K_TRUE); + assert coa.total_steps >= 1; + assert coa.total_steps <= MAX_COA_STEPS; + assert coa.threat_assessment == K_TRUE + } + + test add_step_increments_count { + let coa = new_coa("test", K_TRUE); + let coa2 = add_step(coa, COAStep { + step_id: 0, + action_type: ActionType::ASSESS, + description: "test", + resources: [], + prerequisites: [], + proof_trace: [], + estimated_duration: 1.0 + }); + + assert coa2.total_steps == 1 + } + + test add_step_respects_max_steps { + let coa = new_coa("test", K_TRUE); + + for i in range(0, MAX_COA_STEPS) { + let step = COAStep { + step_id: i, + action_type: ActionType::ASSESS, + description: "test", + resources: [], + prerequisites: [], + proof_trace: [], + estimated_duration: 1.0 + }; + coa = add_step(coa, step); + } + + // Try to add one more + let coa2 = add_step(coa, COAStep { + step_id: MAX_COA_STEPS, + action_type: ActionType::ASSESS, + description: "test", + resources: [], + prerequisites: [], + proof_trace: [], + estimated_duration: 1.0 + }); + + // Should remain at MAX_COA_STEPS + assert coa.total_steps == MAX_COA_STEPS + } + + test verify_coa_passes_valid { + let coa = generate_coa("test", K_TRUE); + let constraints: [COAConstraint] = []; + + let (valid, _) = verify_coa(coa, constraints); + assert valid + } + + test verify_coa_fails_excessive_steps { + let coa = new_coa("test", K_TRUE); + + // Add MAX_COA_STEPS + 1 steps + for i in range(0, MAX_COA_STEPS + 1) { + let step = COAStep { + step_id: i, + action_type: ActionType::ASSESS, + description: "test", + resources: [], + prerequisites: [], + proof_trace: [], + estimated_duration: 1.0 + }; + coa = add_step(coa, step); + } + + let constraint = COAConstraint { + constraint_type: ConstraintType::LOGICAL, + description: "step count", + enforces: [] + }; + + let (valid, msg) = verify_coa(coa, [constraint]); + assert !valid; + assert contains(msg, "Too many steps") + } + + test validate_prerequisites_passes_valid_dag { + let coa = new_coa("test", K_TRUE); + + // Chain: 0 -> 1 -> 2 + let step0 = COAStep { + step_id: 0, + action_type: ActionType::ASSESS, + description: "test", + resources: [], + prerequisites: [], + proof_trace: [], + estimated_duration: 1.0 + }; + coa = add_step(coa, step0); + + let step1 = COAStep { + step_id: 1, + action_type: ActionType::EXECUTE, + description: "test", + resources: [], + prerequisites: [0], + proof_trace: [], + estimated_duration: 1.0 + }; + coa = add_step(coa, step1); + + let step2 = COAStep { + step_id: 2, + action_type: ActionType::VERIFY, + description: "test", + resources: [], + prerequisites: [1], + proof_trace: [], + estimated_duration: 1.0 + }; + coa = add_step(coa, step2); + + let (valid, _) = validate_prerequisites(coa); + assert valid + } + + test validate_prerequisites_fails_cycle { + let coa = new_coa("test", K_TRUE); + + // Cycle: 0 -> 1 -> 2 -> 0 + let step0 = COAStep { + step_id: 0, + action_type: ActionType::ASSESS, + description: "test", + resources: [], + prerequisites: [], + proof_trace: [], + estimated_duration: 1.0 + }; + coa = add_step(coa, step0); + + let step1 = COAStep { + step_id: 1, + action_type: ActionType::EXECUTE, + description: "test", + resources: [], + prerequisites: [0], + proof_trace: [], + estimated_duration: 1.0 + }; + coa = add_step(coa, step1); + + let step2 = COAStep { + step_id: 2, + action_type: ActionType::VERIFY, + description: "test", + resources: [], + prerequisites: [1], + proof_trace: [], + estimated_duration: 1.0 + }; + coa = add_step(coa, step2); + + let step3 = COAStep { + step_id: 3, + action_type: ActionType::MAINTAIN, + description: "test", + resources: [], + prerequisites: [2], + proof_trace: [], + estimated_duration: 1.0 + }; + coa = add_step(coa, step3); + + let (valid, _) = validate_prerequisites(coa); + assert !valid + } + + test extract_proof_trace_collects_all_steps { + let coa = generate_coa("test", K_TRUE); + + let trace = extract_proof_trace(coa); + // Each step should have at least one proof step + assert len(trace.steps) >= len(coa.steps) + } + + test format_coa_produces_readable_output { + let coa = generate_coa("UAV", K_TRUE); + let formatted = format_coa(coa); + + assert contains(formatted, "Course of Action"); + assert contains(formatted, "COA ID:"); + assert contains(formatted, "Total Steps:") + } + + // BENCHMARKS + + bench generate_coa_latency { + // Target: <50μs for simple 5-step COA + } + + bench verify_coa_latency { + // Target: <20μs for constraint verification + } + + bench extract_proof_trace_latency { + // Target: <10μs for trace extraction + } + + bench format_coa_latency { + // Target: <30μs for string formatting + } +} diff --git a/apps/website/public/t27/specs/ar/composition.t27 b/apps/website/public/t27/specs/ar/composition.t27 new file mode 100644 index 0000000000..e0db27a012 --- /dev/null +++ b/apps/website/public/t27/specs/ar/composition.t27 @@ -0,0 +1,570 @@ +// SPDX-License-Identifier: Apache-2.0 +// spec: Composition +// ML+AR composition patterns for neuro-symbolic hybrid reasoning + +spec Composition { + // Component types + enum ComponentType { + ML_CNN = 0, // Convolutional Neural Network + ML_MLP = 1, // Multi-Layer Perceptron + ML_RNN = 2, // Recurrent Neural Network + ML_TRANSFORMER = 3, // Transformer architecture + ML_RL = 4, // Reinforcement Learning + ML_BAYESIAN = 5, // Bayesian inference + AR_K3 = 6, // K3 ternary logic + AR_ASP = 7, // Answer Set Programming + AR_DATALOG = 8, // Datalog reasoning + AR_CLASSICAL = 9 // Classical constraints + } + + // Composition patterns (from DARPA CLARA) + enum CompositionPattern { + CNN_RULES = 0, // CNN feature extraction + K3 logic rules + MLP_BAYESIAN = 1, // MLP classification + Bayesian inference + RL_CLASSICAL = 2, // RL policy + classical constraints + NEURO_SYMBOLIC = 3 // Neural embeddings + ASP solver + ATTENTION_LOGIC = 4, // Attention mechanism + logical constraints + HYBRID_VSA = 5, // VSA (Vector Symbolic Architecture) + K3 + ENSEMBLE_K3 = 6 // Multiple ML models with K3 voting + } + + // ML component interface + struct MLComponent { + component_type: ComponentType, + name: string, + parameters: [float], + output_dim: int + } + + // AR component interface + struct ARComponent { + component_type: ComponentType, + name: string, + max_steps: int, + proof_trace_required: bool + } + + // Composed pipeline + struct Pipeline { + ml_components: [MLComponent], + ar_components: [ARComponent], + pattern: CompositionPattern, + fused: bool + } + + // Pipeline execution result + struct PipelineResult { + output: Trit, + proof_trace: ProofTrace, + ml_confidence: float, + ar_steps: int, + fusion_method: string + } + + // Maximum fusion depth (bounded reasoning) + const MAX_FUSION_DEPTH: int = 10 + + // CNN_RULES: CNN feature extraction + K3 rules + fn compose_cnn_rules(cnn: MLComponent, rules: ARComponent) -> Pipeline { + return Pipeline { + ml_components: [cnn], + ar_components: [rules], + pattern: CompositionPattern::CNN_RULES, + fused: true + } + } + + // MLP_BAYESIAN: MLP classification + Bayesian inference + fn compose_mlp_bayesian(mlp: MLComponent, bayesian: ARComponent) -> Pipeline { + return Pipeline { + ml_components: [mlp], + ar_components: [bayesian], + pattern: CompositionPattern::MLP_BAYESIAN, + fused: true + } + } + + // RL_CLASSICAL: RL policy + classical constraints + fn compose_rl_classical(rl: MLComponent, classical: ARComponent) -> Pipeline { + return Pipeline { + ml_components: [rl], + ar_components: [classical], + pattern: CompositionPattern::RL_CLASSICAL, + fused: true + } + } + + // NEURO_SYMBOLIC: Neural embeddings + ASP solver + fn compose_neuro_symbolic(neural: MLComponent, asp: ARComponent) -> Pipeline { + return Pipeline { + ml_components: [neural], + ar_components: [asp], + pattern: CompositionPattern::NEURO_SYMBOLIC, + fused: true + } + } + + // ATTENTION_LOGIC: Attention + logical constraints + fn compose_attention_logic(attention: MLComponent, logic: ARComponent) -> Pipeline { + return Pipeline { + ml_components: [attention], + ar_components: [logic], + pattern: CompositionPattern::ATTENTION_LOGIC, + fused: true + } + } + + // HYBRID_VSA: VSA + K3 ternary logic + fn compose_hybrid_vsa(vsa: MLComponent, k3: ARComponent) -> Pipeline { + return Pipeline { + ml_components: [vsa], + ar_components: [k3], + pattern: CompositionPattern::HYBRID_VSA, + fused: true + } + } + + // ENSEMBLE_K3: Multiple ML models with K3 voting + fn compose_ensemble_k3(ml_models: [MLComponent], k3: ARComponent) -> Pipeline { + return Pipeline { + ml_components: ml_models, + ar_components: [k3], + pattern: CompositionPattern::ENSEMBLE_K3, + fused: true + } + } + + // Execute composed pipeline + fn execute_pipeline(pipeline: Pipeline, input: [float]) -> PipelineResult { + let trace = new_proof_trace(); + let mut ml_outputs: [Trit] = []; + let mut ml_confidences: [float] = []; + + // Execute ML components + for ml_comp in pipeline.ml_components { + let (output, confidence) = execute_ml_component(ml_comp, input); + ml_outputs.push(output); + ml_confidences.push(confidence); + + trace = add_step(trace, + format("ml_{}", ml_comp.name), + map_floats_to_trits(ml_comp.parameters), + output); + } + + // Execute AR components with ML outputs as input + let mut ar_output = K_UNKNOWN; + let mut ar_steps = 0; + + for ar_comp in pipeline.ar_components { + let (result, steps) = execute_ar_component(ar_comp, ml_outputs); + ar_output = result; + ar_steps = ar_steps + steps; + + trace = add_step(trace, + format("ar_{}", ar_comp.name), + ml_outputs, + result); + } + + // Fuse results (K3 voting) + let fused = fuse_results(ml_outputs, ar_output, pipeline.pattern); + trace = add_step(trace, "fusion", [], fused); + + trace = finalize_trace(trace); + + // Calculate overall confidence + let avg_confidence = average(ml_confidences); + + return PipelineResult { + output: fused, + proof_trace: trace, + ml_confidence: avg_confidence, + ar_steps: ar_steps, + fusion_method: get_fusion_method(pipeline.pattern) + } + } + + // Execute ML component (placeholder - actual execution in ML layer) + fn execute_ml_component(ml: MLComponent, input: [float]) -> (Trit, float) { + // Placeholder: ML execution happens in ML pipeline + // This returns K3 values for AR layer + let mut trit_outputs: [Trit] = []; + + for val in input { + let trit = if val > 0.5 { + K_TRUE + } else if val < -0.5 { + K_FALSE + } else { + K_UNKNOWN + }; + trit_outputs.push(trit) + } + + // Mock confidence calculation + let confidence = 0.8; // Placeholder + + // Return aggregated output + let output = if ml.component_type == ComponentType::ML_MLP { + // MLP: aggregate outputs + if count_true(trit_outputs) > len(trit_outputs) / 2 { + K_TRUE + } else if count_false(trit_outputs) > len(trit_outputs) / 2 { + K_FALSE + } else { + K_UNKNOWN + } + } else if ml.component_type == ComponentType::ML_CNN { + // CNN: feature-based aggregation + trit_outputs[0] // Return first feature + } else { + // Default: majority vote + majority_vote(trit_outputs) + }; + + return (output, confidence) + } + + // Execute AR component + fn execute_ar_component(ar: ARComponent, ml_outputs: [Trit]) -> (Trit, int) { + let mut result = K_UNKNOWN; + let mut steps = 0; + let mut trace = new_proof_trace(); + + // K3 reasoning on ML outputs + match ar.component_type { + ComponentType::AR_K3 => { + // Apply K3 logic to ML outputs + result = k3_and_all(ml_outputs); + steps = len(ml_outputs); + }, + ComponentType::AR_ASP => { + // ASP solving on ML outputs + (result, steps) = asp_solve_on_trits(ml_outputs); + }, + ComponentType::AR_DATALOG => { + // Datalog reasoning on ML outputs + (result, steps) = datalog_reason_on_trits(ml_outputs); + }, + ComponentType::AR_CLASSICAL => { + // Classical constraints on ML outputs + (result, steps) = classical_constraints_on_trits(ml_outputs); + } + } + + return (result, steps) + } + + // Fuse ML and AR results based on pattern + fn fuse_results(ml_outputs: [Trit], ar_output: Trit, pattern: CompositionPattern) -> Trit { + match pattern { + CompositionPattern::CNN_RULES => { + // CNN + Rules: Apply rules to CNN output + return ar_output // Rules dominate + }, + CompositionPattern::MLP_BAYESIAN => { + // MLP + Bayesian: Weighted fusion + let ml_aggregate = majority_vote(ml_outputs); + return k3_and(ml_aggregate, ar_output) + }, + CompositionPattern::RL_CLASSICAL => { + // RL + Classical: Constraint satisfaction + return k3_or(majority_vote(ml_outputs), ar_output) + }, + CompositionPattern::NEURO_SYMBOLIC => { + // Neuro + ASP: ASP dominates + return ar_output + }, + CompositionPattern::ATTENTION_LOGIC => { + // Attention + Logic: Logic constrains attention + return k3_and(majority_vote(ml_outputs), ar_output) + }, + CompositionPattern::HYBRID_VSA => { + // VSA + K3: K3 logic on VSA bindings + return ar_output + }, + CompositionPattern::ENSEMBLE_K3 => { + // Ensemble + K3: K3 voting + return k3_and_all(ml_outputs) + } + } + } + + // Get fusion method name for reporting + fn get_fusion_method(pattern: CompositionPattern) -> string { + match pattern { + CompositionPattern::CNN_RULES => "rule_application", + CompositionPattern::MLP_BAYESIAN => "weighted_fusion", + CompositionPattern::RL_CLASSICAL => "constraint_satisfaction", + CompositionPattern::NEURO_SYMBOLIC => "asp_dominant", + CompositionPattern::ATTENTION_LOGIC => "logic_constrained", + CompositionPattern::HYBRID_VSA => "k3_logic", + CompositionPattern::ENSEMBLE_K3 => "k3_voting" + } + } + + // K3 AND across all inputs + fn k3_and_all(inputs: [Trit]) -> Trit { + if len(inputs) == 0 { + return K_TRUE // Empty AND is identity + } + + let mut result = inputs[0]; + for i in range(1, len(inputs)) { + result = k3_and(result, inputs[i]); + } + return result + } + + // Majority vote on trits + fn majority_vote(votes: [Trit]) -> Trit { + let counts = [count_true(votes), count_unknown(votes), count_false(votes)]; + + if counts[0] > counts[1] && counts[0] > counts[2] { + return K_TRUE + } else if counts[2] > counts[0] && counts[2] > counts[1] { + return K_FALSE + } else { + return K_UNKNOWN + } + } + + // Count true values + fn count_true(votes: [Trit]) -> int { + let count = 0; + for v in votes { + if v == K_TRUE { + count = count + 1 + } + } + return count + } + + // Count unknown values + fn count_unknown(votes: [Trit]) -> int { + let count = 0; + for v in votes { + if v == K_UNKNOWN { + count = count + 1 + } + } + return count + } + + // Count false values + fn count_false(votes: [Trit]) -> int { + let count = 0; + for v in votes { + if v == K_FALSE { + count = count + 1 + } + } + return count + } + + // Helper functions for AR components + fn asp_solve_on_trits(inputs: [Trit]) -> (Trit, int) { + // Placeholder ASP solving + let result = majority_vote(inputs); + return (result, 3) // Assume 3 steps + } + + fn datalog_reason_on_trits(inputs: [Trit]) -> (Trit, int) { + // Placeholder Datalog reasoning + let result = k3_and_all(inputs); + return (result, len(inputs)) + } + + fn classical_constraints_on_trits(inputs: [Trit]) -> (Trit, int) { + // Placeholder classical constraints + let result = k3_or_all(inputs); + return (result, 2) + } + + fn k3_or_all(inputs: [Trit]) -> Trit { + if len(inputs) == 0 { + return K_FALSE // Empty OR is identity + } + + let mut result = inputs[0]; + for i in range(1, len(inputs)) { + result = k3_or(result, inputs[i]); + } + return result + } + + // Average of floats + fn average(values: [float]) -> float { + if len(values) == 0 { + return 0.0 + } + + let sum = 0.0; + for v in values { + sum = sum + v + } + return sum / len(values) as float + } + + // Map float values to trits + fn map_floats_to_trits(values: [float]) -> [Trit] { + let mut trits: [Trit] = []; + for v in values { + let trit = if v > 0.5 { + K_TRUE + } else if v < -0.5 { + K_FALSE + } else { + K_UNKNOWN + }; + trits.push(trit) + } + return trits + } + + // INVARIANTS + + // Pipeline execution produces valid result + invariant pipeline_produces_valid_result { + let pipeline = compose_cnn_rules( + MLComponent {component_type: ComponentType::ML_CNN, name: "cnn", parameters: [], output_dim: 3}, + ARComponent {component_type: ComponentType::AR_K3, name: "k3", max_steps: 10, proof_trace_required: true} + ); + + let result = execute_pipeline(pipeline, [0.5, 0.6, 0.7]); + + assert result.output == K_TRUE || result.output == K_FALSE || result.output == K_UNKNOWN + } + + // Proof trace within bounds + invariant proof_trace_within_bounds { + let pipeline = compose_neuro_symbolic( + MLComponent {component_type: ComponentType::ML_MLP, name: "mlp", parameters: [], output_dim: 3}, + ARComponent {component_type: ComponentType::AR_ASP, name: "asp", max_steps: 10, proof_trace_required: true} + ); + + let result = execute_pipeline(pipeline, [0.5, 0.6, 0.7]); + assert len(result.proof_trace.steps) <= MAX_FUSION_DEPTH + } + + // TESTS + + test compose_cnn_rules_creates_pipeline { + let cnn = MLComponent {component_type: ComponentType::ML_CNN, name: "cnn", parameters: [], output_dim: 3}; + let rules = ARComponent {component_type: ComponentType::AR_K3, name: "k3", max_steps: 10, proof_trace_required: true}; + let pipeline = compose_cnn_rules(cnn, rules); + + assert len(pipeline.ml_components) == 1; + assert len(pipeline.ar_components) == 1; + assert pipeline.pattern == CompositionPattern::CNN_RULES + } + + test compose_mlp_bayesian_creates_pipeline { + let mlp = MLComponent {component_type: ComponentType::ML_MLP, name: "mlp", parameters: [], output_dim: 3}; + let bayesian = ARComponent {component_type: ComponentType::AR_BAYESIAN, name: "bayesian", max_steps: 10, proof_trace_required: true}; + let pipeline = compose_mlp_bayesian(mlp, bayesian); + + assert len(pipeline.ml_components) == 1; + assert len(pipeline.ar_components) == 1; + assert pipeline.pattern == CompositionPattern::MLP_BAYESIAN + } + + test compose_rl_classical_creates_pipeline { + let rl = MLComponent {component_type: ComponentType::ML_RL, name: "rl", parameters: [], output_dim: 3}; + let classical = ARComponent {component_type: ComponentType::AR_CLASSICAL, name: "classical", max_steps: 10, proof_trace_required: true}; + let pipeline = compose_rl_classical(rl, classical); + + assert len(pipeline.ml_components) == 1; + assert len(pipeline.ar_components) == 1; + assert pipeline.pattern == CompositionPattern::RL_CLASSICAL + } + + test execute_pipeline_returns_result { + let pipeline = compose_neuro_symbolic( + MLComponent {component_type: ComponentType::ML_TRANSFORMER, name: "transformer", parameters: [], output_dim: 3}, + ARComponent {component_type: ComponentType::AR_ASP, name: "asp", max_steps: 10, proof_trace_required: true} + ); + + let result = execute_pipeline(pipeline, [0.5, 0.6, 0.7]); + + assert result.output != Trit::NULL + assert result.ml_confidence >= 0.0 + } + + test k3_and_all_with_empty_returns_true { + let result = k3_and_all([]); + assert result == K_TRUE + } + + test k3_and_all_single_returns_value { + let result = k3_and_all([K_TRUE]); + assert result == K_TRUE + } + + test k3_and_all_with_multiple { + let result = k3_and_all([K_TRUE, K_UNKNOWN, K_FALSE]); + assert result == K_FALSE // T ∧ U = F, then F ∧ F = F + } + + test majority_vote_takes_true { + let votes = [K_TRUE, K_TRUE, K_FALSE]; + let result = majority_vote(votes); + assert result == K_TRUE + } + + test majority_vote_takes_false { + let votes = [K_FALSE, K_FALSE, K_UNKNOWN]; + let result = majority_vote(votes); + assert result == K_FALSE + } + + test majority_vote_takes_unknown { + let votes = [K_TRUE, K_FALSE, K_UNKNOWN]; + let result = majority_vote(votes); + assert result == K_UNKNOWN // Tie (1T, 1F, 1U) + } + + test fuse_results_respects_pattern { + let ml_outputs = [K_TRUE, K_TRUE]; + + // Test CNN_RULES pattern + let result = fuse_results(ml_outputs, K_FALSE, CompositionPattern::CNN_RULES); + assert result == K_FALSE // Rules dominate + + // Test MLP_BAYESIAN pattern + let ml_aggregate = K_TRUE; + let result = fuse_results(ml_outputs, K_UNKNOWN, CompositionPattern::MLP_BAYESIAN); + assert result == K_UNKNOWN // TRUE ∧ UNKNOWN = UNKNOWN + } + + test execute_ml_component_returns_trit { + let ml = MLComponent {component_type: ComponentType::ML_MLP, name: "mlp", parameters: [], output_dim: 3}; + let (output, confidence) = execute_ml_component(ml, [0.8, 0.2, 0.7]); + + assert output != Trit::NULL + assert confidence > 0.0 + } + + // BENCHMARKS + + bench execute_pipeline_latency { + // Target: <100μs for simple composition + } + + bench fuse_results_latency { + // Target: <10μs for fusion operation + } + + bench majority_vote_latency { + // Target: <5μs for 10-element vote + } + + bench execute_ml_component_latency { + // Target: <50μs (depends on model size) + } + + bench execute_ar_component_latency { + // Target: <20μs for K3 reasoning + } +} diff --git a/apps/website/public/t27/specs/ar/datalog_engine.t27 b/apps/website/public/t27/specs/ar/datalog_engine.t27 new file mode 100644 index 0000000000..f3feceb23a --- /dev/null +++ b/apps/website/public/t27/specs/ar/datalog_engine.t27 @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: Apache-2.0 +// spec: DatalogEngine +// Datalog reasoning engine for neuro-symbolic AI + +spec DatalogEngine { + use tritype-base::Trit; + + // Datalog fact structure + struct Fact { + predicate: string, + args: [string], + truth_value: Trit + } + + // Datalog rule structure (head :- body) + struct Rule { + head: Fact, + body: [Fact] + } + + // Datalog database + struct Database { + facts: [Fact], + rules: [Rule] + } + + // Query result + struct QueryResult { + answers: [Fact], + proof_trace: ProofTrace, + complete: bool + } + + // Create empty database + fn new_database() -> Database { + return Database { + facts: [], + rules: [] + } + } + + // Add fact to database + fn add_fact(db: Database, fact: Fact) -> Database { + let new_facts = db.facts; + new_facts.push(fact); + return Database { + facts: new_facts, + rules: db.rules + } + } + + // Add rule to database + fn add_rule(db: Database, rule: Rule) -> Database { + let new_rules = db.rules; + new_rules.push(rule); + return Database { + facts: db.facts, + rules: new_rules + } + } + + // Query database for matching facts + fn query(db: Database, query: Fact) -> QueryResult { + let trace = new_proof_trace(); + let answers: [Fact] = []; + + // Direct fact lookup + for fact in db.facts { + if facts_match(fact, query) { + answers.push(fact); + trace = add_step(trace, "fact_match", [fact.truth_value], fact.truth_value); + } + } + + // Rule-based reasoning (bounded to MAX_STEPS) + let mut trace = trace; + for rule in db.rules { + if rule_heads_match(rule.head, query) { + let (derived, new_trace) = apply_rule(db, rule, trace); + trace = new_trace; + if derived.truth_value != K_FALSE { + answers.push(derived); + } + } + } + + trace = finalize_trace(trace); + let (valid, _) = verify_trace(trace); + + return QueryResult { + answers: answers, + proof_trace: trace, + complete: valid + } + } + + // Check if two facts match (same predicate, compatible args) + fn facts_match(a: Fact, b: Fact) -> bool { + if a.predicate != b.predicate { + return false + } + if len(a.args) != len(b.args) { + return false + } + return true + } + + // Check if rule head matches query + fn rule_heads_match(head: Fact, query: Fact) -> bool { + return head.predicate == query.predicate && len(head.args) == len(query.args) + } + + // Apply rule to derive new fact + fn apply_rule(db: Database, rule: Rule, trace: ProofTrace) -> (Fact, ProofTrace) { + let mut current_trace = trace; + + // Check if all body facts are true + let mut all_body_true = K_TRUE; + for body_fact in rule.body { + let result = query(db, body_fact); + if len(result.answers) == 0 { + all_body_true = K_FALSE; + current_trace = add_step(current_trace, "rule_body_false", [K_TRUE], K_FALSE); + break + } + for answer in result.answers { + current_trace = add_step(current_trace, "rule_body_check", [answer.truth_value], answer.truth_value); + } + } + + let derived_truth = if all_body_true == K_TRUE { + rule.head.truth_value + } else { + K_FALSE + }; + + let derived = Fact { + predicate: rule.head.predicate, + args: rule.head.args, + truth_value: derived_truth + }; + + current_trace = add_step(current_trace, "rule_derived", [all_body_true], derived_truth); + return (derived, current_trace) + } + + // Bottom-up evaluation (forward chaining) + fn eval_bottom_up(db: Database) -> Database { + let mut result_db = db; + let mut trace = new_proof_trace(); + let mut iteration = 0; + + loop { + iteration = iteration + 1; + if iteration > MAX_STEPS { + break + } + + let mut derived_facts: [Fact] = []; + + for rule in result_db.rules { + let (derived, new_trace) = apply_rule(result_db, rule, trace); + trace = new_trace; + if derived.truth_value != K_FALSE && !fact_in_db(result_db, derived) { + derived_facts.push(derived); + } + } + + if len(derived_facts) == 0 { + break // Fixed point reached + } + + for fact in derived_facts { + result_db = add_fact(result_db, fact); + } + } + + return result_db + } + + // Check if fact exists in database + fn fact_in_db(db: Database, fact: Fact) -> bool { + for existing in db.facts { + if facts_match(existing, fact) && existing.truth_value == fact.truth_value { + return true + } + } + return false + } + + // Top-down evaluation (backward chaining) + fn eval_top_down(db: Database, query: Fact) -> QueryResult { + return query(db, query) + } + + // INVARIANTS + + // Database monotonicity (facts only added) + invariant database_monotonicity { + let db = new_database(); + let db2 = add_fact(db, Fact {predicate: "p", args: [], truth_value: K_TRUE}); + assert len(db2.facts) >= len(db.facts) + } + + // Query returns valid proof trace + invariant query_returns_valid_trace { + let db = new_database(); + let fact = Fact {predicate: "test", args: [], truth_value: K_TRUE}; + db = add_fact(db, fact); + let result = query(db, fact); + assert result.complete + } + + // Bottom-up evaluation terminates + invariant bottom_up_terminates { + let db = new_database(); + let result = eval_bottom_up(db); + assert true // Reaching here means termination + } + + // TESTS + + test new_database_creates_empty { + let db = new_database(); + assert len(db.facts) == 0; + assert len(db.rules) == 0 + } + + test add_fact_increments_count { + let db = new_database(); + let fact = Fact {predicate: "parent", args: ["alice", "bob"], truth_value: K_TRUE}; + db = add_fact(db, fact); + assert len(db.facts) == 1 + } + + test add_rule_increments_count { + let db = new_database(); + let head = Fact {predicate: "grandparent", args: ["a", "b"], truth_value: K_UNKNOWN}; + let body = [Fact {predicate: "parent", args: ["a", "x"], truth_value: K_TRUE}]; + let rule = Rule {head: head, body: body}; + db = add_rule(db, rule); + assert len(db.rules) == 1 + } + + test query_finds_matching_fact { + let db = new_database(); + let fact = Fact {predicate: "parent", args: ["alice", "bob"], truth_value: K_TRUE}; + db = add_fact(db, fact); + let query_fact = Fact {predicate: "parent", args: ["alice", "bob"], truth_value: K_TRUE}; + let result = query(db, query_fact); + assert len(result.answers) == 1; + assert result.answers[0].truth_value == K_TRUE + } + + test query_returns_empty_for_no_match { + let db = new_database(); + let fact = Fact {predicate: "parent", args: ["alice", "bob"], truth_value: K_TRUE}; + db = add_fact(db, fact); + let query_fact = Fact {predicate: "child", args: ["bob", "alice"], truth_value: K_TRUE}; + let result = query(db, query_fact); + assert len(result.answers) == 0 + } + + test facts_match_same_predicate { + let a = Fact {predicate: "p", args: ["x"], truth_value: K_TRUE}; + let b = Fact {predicate: "p", args: ["y"], truth_value: K_TRUE}; + assert facts_match(a, b) + } + + test facts_match_different_predicate { + let a = Fact {predicate: "p", args: ["x"], truth_value: K_TRUE}; + let b = Fact {predicate: "q", args: ["x"], truth_value: K_TRUE}; + assert !facts_match(a, b) + } + + test rule_heads_match_same_predicate { + let head = Fact {predicate: "grandparent", args: ["a", "b"], truth_value: K_TRUE}; + let query = Fact {predicate: "grandparent", args: ["x", "y"], truth_value: K_TRUE}; + assert rule_heads_match(head, query) + } + + test fact_in_db_finds_existing { + let db = new_database(); + let fact = Fact {predicate: "p", args: ["x"], truth_value: K_TRUE}; + db = add_fact(db, fact); + assert fact_in_db(db, fact) + } + + test fact_in_db_returns_false_for_missing { + let db = new_database(); + let fact = Fact {predicate: "p", args: ["x"], truth_value: K_TRUE}; + assert !fact_in_db(db, fact) + } + + test eval_bottom_up_derives_new_facts { + // parent(alice, bob) ^ parent(bob, carol) -> grandparent(alice, carol) + let db = new_database(); + db = add_fact(db, Fact {predicate: "parent", args: ["alice", "bob"], truth_value: K_TRUE}); + db = add_fact(db, Fact {predicate: "parent", args: ["bob", "carol"], truth_value: K_TRUE}); + + let head = Fact {predicate: "grandparent", args: ["alice", "carol"], truth_value: K_TRUE}; + let body = [ + Fact {predicate: "parent", args: ["alice", "x"], truth_value: K_TRUE}, + Fact {predicate: "parent", args: ["x", "carol"], truth_value: K_TRUE} + ]; + db = add_rule(db, Rule {head: head, body: body}); + + let result = eval_bottom_up(db); + assert len(result.facts) >= 3 // 2 original + 1 derived + } + + test eval_top_down_returns_results { + let db = new_database(); + db = add_fact(db, Fact {predicate: "parent", args: ["alice", "bob"], truth_value: K_TRUE}); + let query_fact = Fact {predicate: "parent", args: ["alice", "bob"], truth_value: K_TRUE}; + let result = eval_top_down(db, query_fact); + assert len(result.answers) == 1 + } + + test query_proof_trace_within_bounds { + let db = new_database(); + db = add_fact(db, Fact {predicate: "p", args: ["x"], truth_value: K_TRUE}); + let query_fact = Fact {predicate: "p", args: ["x"], truth_value: K_TRUE}; + let result = query(db, query_fact); + assert len(result.proof_trace.steps) <= MAX_STEPS + } + + // BENCHMARKS + + bench query_latency { + // Target: <5μs for simple lookup + } + + bench add_fact_latency { + // Target: <1μs per fact + } + + bench eval_bottom_up_latency { + // Target: <100μs for 10-rule database + } + + bench eval_top_down_latency { + // Target: <10μs for single derivation + } + + bench memory_per_fact { + // Target: <100 bytes per fact + } +} diff --git a/apps/website/public/t27/specs/ar/explainability.t27 b/apps/website/public/t27/specs/ar/explainability.t27 new file mode 100644 index 0000000000..37f576c88f --- /dev/null +++ b/apps/website/public/t27/specs/ar/explainability.t27 @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: Apache-2.0 +// spec: Explainability +// Explainable AI (XAI) mechanisms for neuro-symbolic reasoning + +spec Explainability { + // Explanation structure + struct Explanation { + conclusion: Trit, + proof_steps: [ProofStep], + confidence: float, + toxicity_flag: bool + } + + // Feature importance structure + struct FeatureImportance { + feature_name: string, + contribution: float, + contribution_type: string // "direct", "indirect", "rule_based" + } + + // Attention weights for neural components + struct AttentionWeights { + inputs: [string], + weights: [float], + normalized: bool + } + + // Maximum explanation length (bounded for human readability) + const MAX_EXPLANATION_STEPS: int = 10 + + // Create new explanation + fn new_explanation() -> Explanation { + return Explanation { + conclusion: K_UNKNOWN, + proof_steps: [], + confidence: 0.0, + toxicity_flag: false + } + } + + // Add reasoning step to explanation + fn add_reasoning_step(expl: Explanation, step: ProofStep) -> Explanation { + if len(expl.proof_steps) >= MAX_EXPLANATION_STEPS { + return expl // Reject additional steps beyond MAX + } + + let new_steps = expl.proof_steps; + new_steps.push(step); + + return Explanation { + conclusion: expl.conclusion, + proof_steps: new_steps, + confidence: expl.confidence, + toxicity_flag: expl.toxicity_flag + } + } + + // Set conclusion with confidence + fn set_conclusion(expl: Explanation, conclusion: Trit, confidence: float) -> Explanation { + return Explanation { + conclusion: conclusion, + proof_steps: expl.proof_steps, + confidence: confidence, + toxicity_flag: expl.toxicity_flag + } + } + + // Check if explanation is toxic (contains contradictions) + fn check_toxicity(expl: Explanation) -> bool { + // Toxicity: T ∧ F both asserted as true in proof + let has_true = false; + let has_false = false; + + for step in expl.proof_steps { + for input in step.inputs { + if input == K_TRUE { + has_true = true + } else if input == K_FALSE { + has_false = true + } + } + if step.output == K_TRUE { + has_true = true + } else if step.output == K_FALSE { + has_false = true + } + } + + return has_true && has_false + } + + // Mark explanation as toxic + fn mark_toxic(expl: Explanation) -> Explanation { + return Explanation { + conclusion: expl.conclusion, + proof_steps: expl.proof_steps, + confidence: expl.confidence, + toxicity_flag: true + } + } + + // Generate feature importance for neural component + fn compute_feature_importance( + attention: AttentionWeights, + outputs: [Trit] + ) -> [FeatureImportance] { + let mut importance: [FeatureImportance] = []; + + for i in range(0, len(attention.inputs)) { + let contrib = attention.weights[i]; + + let imp = FeatureImportance { + feature_name: attention.inputs[i], + contribution: contrib, + contribution_type: if attention.normalized { + "direct" + } else { + "rule_based" + } + }; + + importance.push(imp); + } + + // Normalize contributions + return normalize_importance(importance) + } + + // Normalize feature importance to sum to 1.0 + fn normalize_importance(imp: [FeatureImportance]) -> [FeatureImportance] { + let total = 0.0; + for i in imp { + total = total + i.contribution + } + + if total == 0.0 { + return imp + } + + let mut normalized: [FeatureImportance] = []; + + for i in imp { + let norm = FeatureImportance { + feature_name: i.feature_name, + contribution: i.contribution / total, + contribution_type: i.contribution_type + }; + normalized.push(norm); + } + + return normalized + } + + // Generate human-readable explanation + fn format_explanation(expl: Explanation) -> string { + let lines: [string] = []; + + if expl.toxicity_flag { + lines.push("⚠️ TOXIC EXPLANATION DETECTED ⚠️"); + lines.push("This explanation contains logical contradictions."); + } + + lines.push("=== Neuro-Symbolic Explanation ==="); + lines.push(format("Confidence: {:.1%}%", expl.confidence * 100.0)); + lines.push(format("Proof Trace: {} steps", len(expl.proof_steps))); + lines.push(""); + + for i in range(0, len(expl.proof_steps)) { + let step = expl.proof_steps[i]; + let input_str = join(", ", step.inputs); + + let step_text = format("Step {}: {} → {} ({})", + i + 1, + step.operation, + input_str, + trit_to_string(step.output)); + lines.push(step_text); + } + + lines.push(""); + lines.push(format("Conclusion: {}", trit_to_string(expl.conclusion))); + + return join("\n", lines) + } + + // Convert Trit to string + fn trit_to_string(t: Trit) -> string { + if t == Trit::TRUE { + return "TRUE" + } else if t == Trit::UNKNOWN { + return "UNKNOWN" + } else if t == Trit::FALSE { + return "FALSE" + } else { + return "INVALID" + } + } + + // Extract proof steps from explanation + fn extract_proof_steps(expl: Explanation) -> ProofTrace { + let trace = ProofTrace { + steps: expl.proof_steps, + start_timestamp: 0, + end_timestamp: 0, + verified: true + }; + return trace + } + + // Check if explanation meets explainability criteria + fn verify_explainability(expl: Explanation) -> (bool, string) { + let errors: [string] = []; + + // Criteria 1: Bounded proof steps + if len(expl.proof_steps) > MAX_EXPLANATION_STEPS { + errors.push(format("Proof trace too long: {} > {}", + len(expl.proof_steps), MAX_EXPLANATION_STEPS)); + } + + // Criteria 2: Non-empty proof + if len(expl.proof_steps) == 0 { + errors.push("Empty proof trace"); + } + + // Criteria 3: Valid conclusion + if expl.conclusion == Trit::NULL { + errors.push("Invalid conclusion value"); + } + + // Criteria 4: Reasonable confidence + if expl.confidence < 0.0 || expl.confidence > 1.0 { + errors.push(format("Invalid confidence: {}", expl.confidence)); + } + + // Criteria 5: Toxicity flag consistency + if expl.toxicity_flag != check_toxicity(expl) { + errors.push("Toxicity flag inconsistent"); + } + + if len(errors) == 0 { + return (true, "Valid explanation") + } else { + return (false, join("; ", errors)) + } + } + + // Counterfactual explanation: "what if different input?" + fn counterfactual( + expl: Explanation, + alternative_inputs: [Trit] + ) -> Explanation { + // Generate explanation for alternative input + let alt_expl = Explanation { + conclusion: K_UNKNOWN, + proof_steps: [], + confidence: 0.0, + toxicity_flag: false + }; + + // Apply same reasoning chain to alternative + for step in expl.proof_steps { + let new_step = ProofStep { + step_id: step.step_id, + operation: step.operation, + inputs: alternative_inputs, + output: K_UNKNOWN // Recompute + }; + alt_expl = add_reasoning_step(alt_expl, new_step); + } + + return alt_expl + } + + // INVARIANTS + + // Explanation steps bounded by MAX_EXPLANATION_STEPS + invariant explanation_steps_bounded { + let expl = new_explanation(); + for i in range(0, MAX_EXPLANATION_STEPS + 1) { + let step = ProofStep { + step_id: i, + operation: "test", + inputs: [K_TRUE], + output: K_TRUE + }; + expl = add_reasoning_step(expl, step); + } + assert len(expl.proof_steps) == MAX_EXPLANATION_STEPS + } + + // Toxicity detection catches contradictions + invariant toxicity_detects_contradictions { + let expl = new_explanation(); + + // Add contradictory steps + let step1 = ProofStep {step_id: 0, operation: "assert_T", inputs: [], output: K_TRUE}; + let step2 = ProofStep {step_id: 1, operation: "assert_F", inputs: [], output: K_FALSE}; + + expl = add_reasoning_step(expl, step1); + expl = add_reasoning_step(expl, step2); + + assert check_toxicity(expl) + } + + // Feature importance normalizes to 1.0 + invariant feature_importance_normalizes { + let weights = AttentionWeights { + inputs: ["a", "b", "c"], + weights: [0.2, 0.3, 0.5], + normalized: false + }; + + let outputs = [K_TRUE, K_TRUE, K_TRUE]; + let importance = compute_feature_importance(weights, outputs); + + let sum = 0.0; + for imp in importance { + sum = sum + imp.contribution + } + + assert approx_equal(sum, 1.0, 0.001) + } + + // Confidence in valid range + invariant confidence_in_valid_range { + let expl = set_conclusion(new_explanation(), K_TRUE, 0.5); + assert expl.confidence >= 0.0 && expl.confidence <= 1.0 + } + + // TESTS + + test new_explanation_creates_empty { + let expl = new_explanation(); + assert len(expl.proof_steps) == 0; + assert expl.confidence == 0.0; + assert !expl.toxicity_flag + } + + test add_reasoning_step_increments_count { + let expl = new_explanation(); + let step = ProofStep {step_id: 0, operation: "k3_and", inputs: [K_TRUE], output: K_TRUE}; + expl = add_reasoning_step(expl, step); + assert len(expl.proof_steps) == 1 + } + + test add_reasoning_step_respects_max { + let expl = new_explanation(); + + // Add MAX_EXPLANATION_STEPS + for i in range(0, MAX_EXPLANATION_STEPS) { + let step = ProofStep { + step_id: i, + operation: "op", + inputs: [K_TRUE], + output: K_TRUE + }; + expl = add_reasoning_step(expl, step); + } + + // Try to add one more - should be rejected + let extra_step = ProofStep { + step_id: MAX_EXPLANATION_STEPS, + operation: "extra", + inputs: [K_TRUE], + output: K_TRUE + }; + let expl2 = add_reasoning_step(expl, extra_step); + + // Should have MAX steps, not MAX+1 + assert len(expl2.proof_steps) == MAX_EXPLANATION_STEPS + } + + test set_conclusion_updates_fields { + let expl = new_explanation(); + expl = set_conclusion(expl, K_TRUE, 0.85); + assert expl.conclusion == K_TRUE; + assert expl.confidence == 0.85 + } + + test check_toxicity_detects_contradiction { + let expl = new_explanation(); + + // Non-contradictory proof + let step1 = ProofStep {step_id: 0, operation: "assert", inputs: [K_TRUE], output: K_TRUE}; + expl = add_reasoning_step(expl, step1); + + assert !check_toxicity(expl) + } + + test mark_toxic_sets_flag { + let expl = new_explanation(); + let toxic = mark_toxic(expl); + assert toxic.toxicity_flag + } + + test compute_feature_importance_returns_list { + let weights = AttentionWeights { + inputs: ["a", "b"], + weights: [0.4, 0.6], + normalized: false + }; + + let outputs = [K_TRUE, K_TRUE]; + let importance = compute_feature_importance(weights, outputs); + + assert len(importance) == 2 + } + + test normalize_importance_sums_to_one { + let weights = AttentionWeights { + inputs: ["a", "b"], + weights: [0.3, 0.7], + normalized: false + }; + + let outputs = [K_TRUE, K_TRUE]; + let importance = compute_feature_importance(weights, outputs); + + let sum = 0.0; + for imp in importance { + sum = sum + imp.contribution + } + + assert sum >= 0.99 && sum <= 1.01 // Account for floating point + } + + test format_explanation_produces_readable_output { + let expl = new_explanation(); + expl = set_conclusion(expl, K_TRUE, 0.9); + + let step1 = ProofStep { + step_id: 0, + operation: "k3_and", + inputs: [K_TRUE, K_TRUE], + output: K_TRUE + }; + expl = add_reasoning_step(expl, step1); + + let formatted = format_explanation(expl); + + assert contains(formatted, "Confidence: 90.0%"); + assert contains(formatted, "Step 1: k3_and"); + assert contains(formatted, "Conclusion: TRUE") + } + + test verify_explainability_passes_valid { + let expl = new_explanation(); + expl = set_conclusion(expl, K_TRUE, 0.8); + + let step1 = ProofStep { + step_id: 0, + operation: "k3_and", + inputs: [K_TRUE, K_TRUE], + output: K_TRUE + }; + expl = add_reasoning_step(expl, step1); + + let (valid, _) = verify_explainability(expl); + assert valid + } + + test verify_explainability_fails_empty { + let expl = new_explanation(); + expl = set_conclusion(expl, K_TRUE, 0.8); + + let (valid, msg) = verify_explainability(expl); + assert !valid; + assert contains(msg, "Empty proof trace") + } + + test verify_explainability_fails_toxic { + let expl = new_explanation(); + + // Create toxic explanation + let step1 = ProofStep { + step_id: 0, + operation: "assert_T", + inputs: [], + output: K_TRUE + }; + expl = add_reasoning_step(expl, step1); + + let step2 = ProofStep { + step_id: 1, + operation: "assert_F", + inputs: [], + output: K_FALSE + }; + expl = add_reasoning_step(expl, step2); + + let (valid, _) = verify_explainability(expl); + assert !valid + } + + test counterfactual_generates_alternative { + let expl = new_explanation(); + + let step = ProofStep { + step_id: 0, + operation: "k3_and", + inputs: [K_TRUE, K_TRUE], + output: K_TRUE + }; + expl = add_reasoning_step(expl, step); + + let alt = counterfactual(expl, [K_FALSE, K_TRUE]); + + // Should have same number of steps but different input + assert len(alt.proof_steps) == 1; + assert alt.proof_steps[0].inputs[0] == K_FALSE + } + + test extract_proof_steps_returns_trace { + let expl = new_explanation(); + + let step1 = ProofStep { + step_id: 0, + operation: "op1", + inputs: [K_TRUE], + output: K_TRUE + }; + expl = add_reasoning_step(expl, step1); + + let step2 = ProofStep { + step_id: 1, + operation: "op2", + inputs: [K_TRUE], + output: K_FALSE + }; + expl = add_reasoning_step(expl, step2); + + let trace = extract_proof_steps(expl); + + assert len(trace.steps) == 2; + assert trace.verified + } + + // BENCHMARKS + + bench add_reasoning_step_latency { + // Target: <0.5μs per step + } + + bench check_toxicity_latency { + // Target: <2μs for contradiction detection + } + + bench compute_feature_importance_latency { + // Target: <5μs for 10 features + } + + bench format_explanation_latency { + // Target: <10μs for string generation + } +} diff --git a/apps/website/public/t27/specs/ar/proof_trace.t27 b/apps/website/public/t27/specs/ar/proof_trace.t27 new file mode 100644 index 0000000000..aab89c7601 --- /dev/null +++ b/apps/website/public/t27/specs/ar/proof_trace.t27 @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: Apache-2.0 +// spec: ProofTrace +// Bounded proof trace mechanism for explainable neuro-symbolic reasoning + +spec ProofTrace { + use tritype-base::Trit; + + // Maximum number of steps allowed in proof trace + // DARPA CLARA requirement: ≤10 steps + const MAX_STEPS: int = 10 + + // Proof step structure + struct ProofStep { + step_id: int, + operation: string, + inputs: [Trit], + output: Trit, + timestamp: int + } + + // Complete proof trace + struct ProofTrace { + steps: [ProofStep], + start_timestamp: int, + end_timestamp: int, + verified: bool + } + + // Create new proof trace + fn new_proof_trace() -> ProofTrace { + return ProofTrace { + steps: [], + start_timestamp: now(), + end_timestamp: 0, + verified: false + } + } + + // Add a step to proof trace + fn add_step(trace: ProofTrace, operation: string, inputs: [Trit], output: Trit) -> ProofTrace { + let step = ProofStep { + step_id: len(trace.steps), + operation: operation, + inputs: inputs, + output: output, + timestamp: now() - trace.start_timestamp + }; + let new_steps = trace.steps; + new_steps.push(step); + return ProofTrace { + steps: new_steps, + start_timestamp: trace.start_timestamp, + end_timestamp: trace.end_timestamp, + verified: trace.verified + } + } + + // Verify proof trace is within bounds + fn verify_trace(trace: ProofTrace) -> (bool, string) { + let step_count = len(trace.steps); + + if step_count > MAX_STEPS { + return (false, format("Proof trace exceeded {} steps (max: {})", step_count, MAX_STEPS)) + } + + if step_count == 0 { + return (false, "Empty proof trace") + } + + // Check each step has valid output + for step in trace.steps { + if step.output == Trit::NULL { + return (false, format("Step {} has NULL output", step.step_id)) + } + } + + return (true, format("Valid: {} steps (≤{})", step_count, MAX_STEPS)) + } + + // Get proof trace length + fn trace_length(trace: ProofTrace) -> int { + return len(trace.steps) + } + + // Check if trace is at maximum capacity + fn is_at_capacity(trace: ProofTrace) -> bool { + return len(trace.steps) >= MAX_STEPS + } + + // Finalize and verify trace + fn finalize_trace(trace: ProofTrace) -> ProofTrace { + return ProofTrace { + steps: trace.steps, + start_timestamp: trace.start_timestamp, + end_timestamp: now(), + verified: true + } + } + + // Format trace as human-readable string + fn format_trace(trace: ProofTrace) -> string { + let lines: [string] = []; + lines.push("=== Proof Trace ==="); + + for step in trace.steps { + let input_str = join(", ", step.inputs); + let line = format("{}. {}({}) = {} ({:.2f}μs)", + step.step_id + 1, + step.operation, + input_str, + trit_to_string(step.output), + step.timestamp as float / 1000.0); + lines.push(line); + } + + lines.push(format("\nTotal: {} steps, verified: {}", len(trace.steps), trace.verified)); + return join("\n", lines) + } + + // Convert Trit to string representation + fn trit_to_string(t: Trit) -> string { + if t == Trit::TRUE { + return "T" + } else if t == Trit::UNKNOWN { + return "U" + } else if t == Trit::FALSE { + return "F" + } else { + return "?" + } + } + + // INVARIANTS + + // MAX_STEPS is positive + invariant max_steps_positive { + assert MAX_STEPS > 0 + } + + // Trace verification catches overflow + invariant trace_verification_catches_overflow { + let large_trace = new_proof_trace(); + let trace = large_trace; + for i in range(0, MAX_STEPS + 1) { + trace = add_step(trace, "dummy", [K_TRUE], K_TRUE); + } + let (valid, _) = verify_trace(trace); + assert !valid + } + + // Empty trace verification fails + invariant empty_trace_fails { + let empty_trace = ProofTrace {steps: [], start_timestamp: 0, end_timestamp: 0, verified: false}; + let (valid, _) = verify_trace(empty_trace); + assert !valid + } + + // Valid trace verification passes + invariant valid_trace_passes { + let trace = new_proof_trace(); + trace = add_step(trace, "k3_and", [K_TRUE, K_TRUE], K_TRUE); + trace = add_step(trace, "k3_or", [K_TRUE, K_FALSE], K_TRUE); + let (valid, _) = verify_trace(trace); + assert valid + } + + // Trace length invariant + invariant trace_length_equals_steps { + let trace = new_proof_trace(); + trace = add_step(trace, "op1", [K_TRUE], K_TRUE); + trace = add_step(trace, "op2", [K_FALSE], K_FALSE); + trace = add_step(trace, "op3", [K_UNKNOWN], K_UNKNOWN); + assert trace_length(trace) == 3 + } + + // Finalization sets verified flag + invariant finalization_sets_verified { + let trace = new_proof_trace(); + trace = add_step(trace, "op", [K_TRUE], K_TRUE); + let finalized = finalize_trace(trace); + assert finalized.verified + } + + // TESTS + + test new_proof_trace_creates_empty { + let trace = new_proof_trace(); + assert len(trace.steps) == 0; + assert !trace.verified; + } + + test add_step_increments_count { + let trace = new_proof_trace(); + trace = add_step(trace, "k3_and", [K_TRUE, K_TRUE], K_TRUE); + trace = add_step(trace, "k3_or", [K_TRUE, K_FALSE], K_TRUE); + assert len(trace.steps) == 2; + } + + test verify_trace_valid_small { + let trace = new_proof_trace(); + trace = add_step(trace, "k3_and", [K_TRUE, K_TRUE], K_TRUE); + trace = add_step(trace, "k3_or", [K_TRUE, K_FALSE], K_TRUE); + let (valid, message) = verify_trace(trace); + assert valid; + assert contains(message, "Valid:") + } + + test verify_trace_fails_at_max_plus_one { + let trace = new_proof_trace(); + for i in range(0, MAX_STEPS + 1) { + trace = add_step(trace, "dummy", [K_TRUE], K_TRUE); + } + let (valid, message) = verify_trace(trace); + assert !valid; + assert contains(message, format("exceeded {} steps", MAX_STEPS)) + } + + test verify_trace_accepts_max_steps { + let trace = new_proof_trace(); + for i in range(0, MAX_STEPS) { + trace = add_step(trace, "op", [K_TRUE], K_TRUE); + } + let (valid, _) = verify_trace(trace); + assert valid + } + + test trace_length_reports_correct { + let trace = new_proof_trace(); + trace = add_step(trace, "op1", [K_TRUE], K_TRUE); + trace = add_step(trace, "op2", [K_FALSE], K_FALSE); + trace = add_step(trace, "op3", [K_UNKNOWN], K_UNKNOWN); + trace = add_step(trace, "op4", [K_TRUE], K_FALSE); + assert trace_length(trace) == 4 + } + + test is_at_capacity_detects_limit { + let trace = new_proof_trace(); + for i in range(0, MAX_STEPS) { + trace = add_step(trace, "op", [K_TRUE], K_TRUE); + } + assert is_at_capacity(trace) + } + + test is_at_capacity_false_when_not_full { + let trace = new_proof_trace(); + trace = add_step(trace, "op", [K_TRUE], K_TRUE); + assert !is_at_capacity(trace) + } + + test finalize_trace_sets_verified { + let trace = new_proof_trace(); + trace = add_step(trace, "op", [K_TRUE], K_TRUE); + let finalized = finalize_trace(trace); + assert finalized.verified; + assert len(finalized.steps) == len(trace.steps) + } + + test format_trace_produces_readable_output { + let trace = new_proof_trace(); + trace = add_step(trace, "k3_and", [K_TRUE, K_TRUE], K_TRUE); + trace = add_step(trace, "k3_or", [K_TRUE, K_FALSE], K_TRUE); + let formatted = format_trace(trace); + assert contains(formatted, "1. k3_and(T, T) = T"); + assert contains(formatted, "2. k3_or(T, F) = T"); + assert contains(formatted, "Total: 2 steps") + } + + test trit_to_string_converts_values { + assert trit_to_string(K_TRUE) == "T"; + assert trit_to_string(K_UNKNOWN) == "U"; + assert trit_to_string(K_FALSE) == "F" + } + + test proof_trace_with_actual_reasoning { + // Simulate a real reasoning chain with bounded steps + let trace = new_proof_trace(); + + // Step 1: Input symptoms + trace = add_step(trace, "input_symptom", [K_TRUE], K_TRUE); + + // Step 2: Apply rule 1 + trace = add_step(trace, "k3_and", [K_TRUE, K_TRUE], K_TRUE); + + // Step 3: Apply rule 2 + trace = add_step(trace, "k3_or", [K_TRUE, K_UNKNOWN], K_TRUE); + + // Step 4: Final conclusion + trace = add_step(trace, "conclusion", [K_TRUE], K_TRUE); + + let (valid, message) = verify_trace(trace); + assert valid; + assert len(trace.steps) == 4 + } + + // BENCHMARKS + + bench add_step_latency { + // Target: <0.5μs per step + } + + bench verify_trace_latency { + // Target: <1μs for verification + } + + bench format_trace_latency { + // Target: <5μs for string formatting + } + + bench trace_memory_usage { + // Target: O(1) per trace (fixed 10-step buffer) + } +} diff --git a/apps/website/public/t27/specs/ar/restraint.t27 b/apps/website/public/t27/specs/ar/restraint.t27 new file mode 100644 index 0000000000..5eefe9841b --- /dev/null +++ b/apps/website/public/t27/specs/ar/restraint.t27 @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: Apache-2.0 +// spec: Restraint +// Bounded rationality and restraint mechanisms for neuro-symbolic reasoning + +spec Restraint { + use tritype-base::Trit; + + // Restraint type + enum RestraintType { + UNKNOWN_TO_FALSE = 0, // K_UNKNOWN becomes K_FALSE + BOUNDED_UNCERTAINTY = 1, // Limit number of unknowns + CONFIDENCE_THRESHOLD = 2, // Reject low-confidence conclusions + TEMPORAL_DECAY = 3, // Decay unknown values over time + COMPLEXITY_PENALTY = 4, // Penalize complex proofs + TOXICITY_BLOCK = 5 // Block contradictory reasoning + } + + // Restraint configuration + struct RestraintConfig { + restraint_type: RestraintType, + threshold: float, + enabled: bool + } + + // Restraint result + struct RestraintResult { + transformed_value: Trit, + original_value: Trit, + restraint_applied: bool, + reason: string + } + + // Maximum unknown tolerance (bounded rationality) + const MAX_UNKNOWN_RATIO: float = 0.5 // At most 50% can be unknown + + // Minimum confidence threshold + const MIN_CONFIDENCE: float = 0.3 // 30% confidence required + + // Complexity penalty per step + const COMPLEXITY_PENALTY_PER_STEP: float = 0.1 + + // Create default restraint config + fn default_config() -> RestraintConfig { + return RestraintConfig { + restraint_type: RestraintType::UNKNOWN_TO_FALSE, + threshold: 0.5, + enabled: true + } + } + + // Apply restraint to a single value + fn apply_restraint(value: Trit, config: RestraintConfig) -> RestraintResult { + if !config.enabled { + return RestraintResult { + transformed_value: value, + original_value: value, + restraint_applied: false, + reason: "Restraint disabled" + } + } + + match config.restraint_type { + RestraintType::UNKNOWN_TO_FALSE => unknown_to_false(value), + RestraintType::BOUNDED_UNCERTAINTY => bounded_uncertainty(value, config.threshold), + RestraintType::CONFIDENCE_THRESHOLD => confidence_threshold(value, config.threshold), + RestraintType::TEMPORAL_DECAY => temporal_decay(value, config.threshold), + RestraintType::COMPLEXITY_PENALTY => complexity_penalty(value), + RestraintType::TOXICITY_BLOCK => toxicity_block(value) + } + } + + // Restraint: K_UNKNOWN becomes K_FALSE + fn unknown_to_false(value: Trit) -> RestraintResult { + if value == K_UNKNOWN { + return RestraintResult { + transformed_value: K_FALSE, + original_value: value, + restraint_applied: true, + reason: "Unknown value replaced with False" + } + } + + return RestraintResult { + transformed_value: value, + original_value: value, + restraint_applied: false, + reason: "Value is known" + } + } + + // Restraint: Bounded uncertainty (limit unknowns) + fn bounded_uncertainty(value: Trit, threshold: float) -> RestraintResult { + // Threshold is max ratio of unknowns allowed + if value == K_UNKNOWN { + // Random decision based on threshold + let should_block = random() < threshold; + if should_block { + return RestraintResult { + transformed_value: K_FALSE, + original_value: value, + restraint_applied: true, + reason: format("Uncertainty exceeded threshold ({:.2f})", threshold) + } + } + } + + return RestraintResult { + transformed_value: value, + original_value: value, + restraint_applied: false, + reason: "Within uncertainty bounds" + } + } + + // Restraint: Confidence threshold + fn confidence_threshold(value: Trit, threshold: float) -> RestraintResult { + // Note: K3 values don't have confidence, this is for ML outputs + // This restraint applies when ML component outputs confidence + // Threshold is applied at composition layer + + return RestraintResult { + transformed_value: value, + original_value: value, + restraint_applied: false, + reason: "Confidence threshold not directly applicable to K3" + } + } + + // Restraint: Temporal decay of unknown values + fn temporal_decay(value: Trit, decay_rate: float) -> RestraintResult { + // Unknown values become more likely false over time + if value == K_UNKNOWN { + // Simulate temporal pressure + let pressure = random() * decay_rate; + if pressure > 0.5 { + return RestraintResult { + transformed_value: K_FALSE, + original_value: value, + restraint_applied: true, + reason: format("Temporal decay (rate: {:.2f})", decay_rate) + } + } + } + + return RestraintResult { + transformed_value: value, + original_value: value, + restraint_applied: false, + reason: "No temporal decay" + } + } + + // Restraint: Complexity penalty + fn complexity_penalty(value: Trit) -> RestraintResult { + // Apply penalty based on proof length or reasoning depth + let penalty_factor = 1.0; // Could be derived from proof trace length + + if value == K_UNKNOWN && penalty_factor < MIN_CONFIDENCE { + return RestraintResult { + transformed_value: K_FALSE, + original_value: value, + restraint_applied: true, + reason: format("Complexity penalty (factor: {:.2f})", penalty_factor) + } + } + + return RestraintResult { + transformed_value: value, + original_value: value, + restraint_applied: false, + reason: "Complexity within bounds" + } + } + + // Restraint: Block toxic reasoning + fn toxicity_block(value: Trit, is_toxic: bool) -> RestraintResult { + if is_toxic && value == K_TRUE { + return RestraintResult { + transformed_value: K_FALSE, + original_value: value, + restraint_applied: true, + reason: "Toxic reasoning blocked" + } + } + + return RestraintResult { + transformed_value: value, + original_value: value, + restraint_applied: false, + reason: "Non-toxic" + } + } + + // Apply restraint to all values in a list + fn apply_to_list(values: [Trit], config: RestraintConfig) -> [RestraintResult] { + let mut results: [RestraintResult] = []; + + for value in values { + let result = apply_restraint(value, config); + results.push(result); + } + + return results + } + + // Check if restraint configuration is valid + fn validate_config(config: RestraintConfig) -> bool { + if config.threshold < 0.0 || config.threshold > 1.0 { + return false + } + return true + } + + // Calculate unknown ratio in values + fn unknown_ratio(values: [Trit]) -> float { + if len(values) == 0 { + return 0.0 + } + + let unknown_count = 0; + for v in values { + if v == K_UNKNOWN { + unknown_count = unknown_count + 1 + } + } + + return unknown_count as float / len(values) as float + } + + // Check if unknown ratio exceeds threshold + fn exceeds_unknown_threshold(values: [Trit], threshold: float) -> bool { + let ratio = unknown_ratio(values); + return ratio > threshold + } + + // INVARIANTS + + // Restraint preserves K_TRUE when not triggered + invariant restraint_preserves_true { + let config = default_config(); + let result = apply_restraint(K_TRUE, config); + assert result.transformed_value == K_TRUE; + assert !result.restraint_applied + } + + // Restraint preserves K_FALSE when not triggered + invariant restraint_preserves_false { + let config = default_config(); + let result = apply_restraint(K_FALSE, config); + assert result.transformed_value == K_FALSE; + assert !result.restraint_applied + } + + // Restraint transforms K_UNKNOWN to K_FALSE + invariant restraint_transforms_unknown_to_false { + let config = RestraintConfig { + restraint_type: RestraintType::UNKNOWN_TO_FALSE, + threshold: 0.5, + enabled: true + }; + + let result = apply_restraint(K_UNKNOWN, config); + assert result.transformed_value == K_FALSE; + assert result.restraint_applied + } + + // Unknown ratio calculation correct + invariant unknown_ratio_correct { + let values = [K_TRUE, K_UNKNOWN, K_TRUE, K_FALSE, K_UNKNOWN]; + let ratio = unknown_ratio(values); + assert approx_equal(ratio, 0.4, 0.001) // 2/5 + } + + // TESTS + + test default_config_creates_valid { + let config = default_config(); + assert validate_config(config) + } + + test apply_restraint_preserves_true { + let config = default_config(); + let result = apply_restraint(K_TRUE, config); + assert result.transformed_value == K_TRUE; + assert !result.restraint_applied + } + + test apply_restraint_preserves_false { + let config = default_config(); + let result = apply_restraint(K_FALSE, config); + assert result.transformed_value == K_FALSE; + assert !result.restraint_applied + } + + test unknown_to_false_transforms_unknown { + let config = RestraintConfig { + restraint_type: RestraintType::UNKNOWN_TO_FALSE, + threshold: 0.5, + enabled: true + }; + + let result = apply_restraint(K_UNKNOWN, config); + assert result.transformed_value == K_FALSE; + assert result.restraint_applied + } + + test apply_restraint_disabled_bypasses { + let config = RestraintConfig { + restraint_type: RestraintType::UNKNOWN_TO_FALSE, + threshold: 0.5, + enabled: false + }; + + let result = apply_restraint(K_UNKNOWN, config); + assert result.transformed_value == K_UNKNOWN; + assert !result.restraint_applied + } + + test apply_to_list_processes_all { + let config = default_config(); + let values = [K_TRUE, K_UNKNOWN, K_FALSE, K_TRUE]; + + let results = apply_to_list(values, config); + + assert len(results) == 4; + assert results[0].transformed_value == K_TRUE; + assert results[1].transformed_value == K_FALSE; // Unknown transformed + assert results[2].transformed_value == K_FALSE; + assert results[3].transformed_value == K_TRUE + } + + test validate_config_accepts_valid_threshold { + let config = RestraintConfig { + restraint_type: RestraintType::UNKNOWN_TO_FALSE, + threshold: 0.5, + enabled: true + }; + + assert validate_config(config) + } + + test validate_config_rejects_negative { + let config = RestraintConfig { + restraint_type: RestraintType::UNKNOWN_TO_FALSE, + threshold: -0.1, + enabled: true + }; + + assert !validate_config(config) + } + + test validate_config_rejects_above_one { + let config = RestraintConfig { + restraint_type: RestraintType::UNKNOWN_TO_FALSE, + threshold: 1.1, + enabled: true + }; + + assert !validate_config(config) + } + + test unknown_ratio_calculates_correctly { + let values1 = [K_TRUE, K_TRUE, K_TRUE]; // 0 unknown + assert approx_equal(unknown_ratio(values1), 0.0, 0.001); + + let values2 = [K_UNKNOWN, K_UNKNOWN, K_UNKNOWN]; // 100% unknown + assert approx_equal(unknown_ratio(values2), 1.0, 0.001); + + let values3 = [K_TRUE, K_UNKNOWN, K_FALSE, K_TRUE]; // 25% unknown + assert approx_equal(unknown_ratio(values3), 0.25, 0.001) + } + + test exceeds_unknown_threshold_detects_excess { + let values = [K_UNKNOWN, K_UNKNOWN, K_TRUE]; // 67% unknown + let threshold = 0.5; + assert exceeds_unknown_threshold(values, threshold) + } + + test exceeds_unknown_threshold_allows_within { + let values = [K_TRUE, K_UNKNOWN, K_FALSE]; // 33% unknown + let threshold = 0.5; + assert !exceeds_unknown_threshold(values, threshold) + } + + test temporal_decay_can_transform { + let decay_rate = 0.8; + + // Test requires mocking random() - conceptually correct + // High decay_rate increases chance of transformation + let config = RestraintConfig { + restraint_type: RestraintType::TEMPORAL_DECAY, + threshold: decay_rate, + enabled: true + }; + + // This test demonstrates the mechanism exists + assert config.restraint_type == RestraintType::TEMPORAL_DECAY + } + + test toxicity_block_blocks_toxic { + let config = RestraintConfig { + restraint_type: RestraintType::TOXICITY_BLOCK, + threshold: 0.5, + enabled: true + }; + + let result = apply_restraint(K_TRUE, config, true); + assert result.transformed_value == K_FALSE; + assert result.restraint_applied + } + + test toxicity_block_allows_non_toxic { + let config = RestraintConfig { + restraint_type: RestraintType::TOXICITY_BLOCK, + threshold: 0.5, + enabled: true + }; + + let result = apply_restraint(K_TRUE, config, false); + assert result.transformed_value == K_TRUE; + assert !result.restraint_applied + } + + // BENCHMARKS + + bench apply_restraint_latency { + // Target: <0.5μs per value + } + + bench apply_to_list_latency { + // Target: <5μs for 10 values + } + + bench unknown_ratio_latency { + // Target: <2μs for 100 values + } +} diff --git a/apps/website/public/t27/specs/ar/ternary_logic.t27 b/apps/website/public/t27/specs/ar/ternary_logic.t27 new file mode 100644 index 0000000000..2c9b81cbca --- /dev/null +++ b/apps/website/public/t27/specs/ar/ternary_logic.t27 @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: Apache-2.0 +// spec: TernaryLogic +// K3 Kleene ternary logic operations for neuro-symbolic reasoning + +spec TernaryLogic { + use tritype-base::Trit; + + // K3 ternary values: True, Unknown, False + type Trit = Trit + + // K3 constant values + const K_FALSE: Trit = Trit::FALSE + const K_UNKNOWN: Trit = Trit::UNKNOWN + const K_TRUE: Trit = Trit::TRUE + + // Basic K3 operations + + // AND operation: K3 AND truth table + // T ∧ T = T, T ∧ F = F, F ∧ ? = F, T ∧ ? = ?, ? ∧ ? = ? + fn k3_and(a: Trit, b: Trit) -> Trit { + return Trit::min(a, b) + } + + // OR operation: K3 OR truth table + // T ∨ T = T, T ∨ F = T, F ∨ ? = ?, F ∨ ? = ?, ? ∨ ? = ? + fn k3_or(a: Trit, b: Trit) -> Trit { + return Trit::max(a, b) + } + + // NOT operation: K3 negation + // ¬T = F, ¬F = T, ¬? = ? + fn k3_not(a: Trit) -> Trit { + return Trit::not(a) + } + + // IMPLIES operation: K3 implication (¬a ∨ b) + fn k3_implies(a: Trit, b: Trit) -> Trit { + return k3_or(k3_not(a), b) + } + + // EQUIV operation: K3 logical equivalence ((a → b) ∧ (b → a)) + fn k3_equiv(a: Trit, b: Trit) -> Trit { + let ab = k3_implies(a, b); + let ba = k3_implies(b, a); + return k3_and(ab, ba) + } + + // Forward chaining: apply rule to fact + // If fact matches rule antecedent, derive consequent + fn forward_chain(rule: Rule, fact: Trit) -> Trit { + let fact_matches = k3_equiv(fact, rule.antecedent); + return k3_and(fact_matches, rule.consequent) + } + + // Backward chaining: find support for goal from rules + fn backward_chain(goal: Trit, rules: [Rule]) -> Trit { + let result = K_UNKNOWN; + for rule in rules { + let consequent_matches = k3_equiv(rule.consequent, goal); + let support = k3_and(consequent_matches, rule.antecedent); + result = k3_or(result, support); + } + return result + } + + // Rule structure for logical inference + struct Rule { + antecedent: Trit, + consequent: Trit + } + + // Resolution operator for clause combination + // Resolve two clauses by finding complementary literals + fn resolve(clause_a: [Trit], clause_b: [Trit]) -> [Trit] { + let result: [Trit] = []; + for i in 0..clause_a.len { + let a = clause_a[i]; + let b = clause_b[i]; + // T/F complementary → unknown, otherwise OR + let resolved = if a == K_TRUE && b == K_FALSE { + K_UNKNOWN + } else if a == K_FALSE && b == K_TRUE { + K_UNKNOWN + } else { + k3_or(a, b) + }; + result.push(resolved); + } + return result + } + + // Restraint check: bounded rationality + // Unknown values trigger restraint mechanism + fn is_restraint(t: Trit) -> bool { + return t == K_UNKNOWN + } + + // Apply restraint to values + fn apply_restraint(values: [Trit]) -> [Trit] { + let result: [Trit] = []; + for t in values { + let transformed = if is_restraint(t) { + K_FALSE // Restraint: unknown becomes false + } else { + t // Preserve known values + }; + result.push(transformed); + } + return result + } + + // INVARIANTS - Must hold for all operations + + // K3 AND is commutative + invariant k3_and_commutative { + assert k3_and(a, b) == k3_and(b, a) + } + + // K3 OR is commutative + invariant k3_or_commutative { + assert k3_or(a, b) == k3_or(b, a) + } + + // K3 AND is associatave + invariant k3_and_associative(a, b, c: Trit) { + assert k3_and(k3_and(a, b), c) == k3_and(a, k3_and(b, c)) + } + + // K3 OR is associatave + invariant k3_or_associative(a, b, c: Trit) { + assert k3_or(k3_or(a, b), c) == k3_or(a, k3_or(b, c)) + } + + // K3 AND identity element + invariant k3_and_identity { + assert k3_and(a, K_TRUE) == a + } + + // K3 OR identity element + invariant k3_or_identity { + assert k3_or(a, K_FALSE) == a + } + + // K3 AND annihilator + invariant k3_and_annihilator { + assert k3_and(a, K_FALSE) == K_FALSE + } + + // K3 OR annihilator + invariant k3_or_annihilator { + assert k3_or(a, K_TRUE) == K_TRUE + } + + // Double negation + invariant k3_double_negation { + assert k3_not(k3_not(a)) == a + } + + // Idempotency of AND + invariant k3_idempotent_and { + assert k3_and(a, a) == a + } + + // Idempotency of OR + invariant k3_idempotent_or { + assert k3_or(a, a) == a + } + + // K3 implies transitivity + invariant k3_implies_transitivity { + assert k3_and(k3_implies(a, b), k3_implies(b, c)) <= k3_implies(a, c) + } + + // K3 equiv reflexive + invariant k3_equiv_reflexive { + assert k3_equiv(a, a) + } + + // K3 equiv symmetric + invariant k3_equiv_symmetric { + assert k3_equiv(a, b) == k3_equiv(b, a) + } + + // Restraint preserves type + invariant restraint_preserves_type { + assert is_restraint(K_UNKNOWN) + assert !is_restraint(K_TRUE) + assert !is_restraint(K_FALSE) + } + + // TESTS - Verification of K3 logic + + test k3_and_truth_table { + // Test all 9 combinations of K3 AND + assert k3_and(K_TRUE, K_TRUE) == K_TRUE; + assert k3_and(K_TRUE, K_UNKNOWN) == K_UNKNOWN; + assert k3_and(K_TRUE, K_FALSE) == K_FALSE; + assert k3_and(K_UNKNOWN, K_TRUE) == K_UNKNOWN; + assert k3_and(K_UNKNOWN, K_UNKNOWN) == K_UNKNOWN; + assert k3_and(K_UNKNOWN, K_FALSE) == K_FALSE; + assert k3_and(K_FALSE, K_TRUE) == K_FALSE; + assert k3_and(K_FALSE, K_UNKNOWN) == K_FALSE; + assert k3_and(K_FALSE, K_FALSE) == K_FALSE; + } + + test k3_or_truth_table { + // Test all 9 combinations of K3 OR + assert k3_or(K_TRUE, K_TRUE) == K_TRUE; + assert k3_or(K_TRUE, K_UNKNOWN) == K_TRUE; + assert k3_or(K_TRUE, K_FALSE) == K_TRUE; + assert k3_or(K_UNKNOWN, K_TRUE) == K_TRUE; + assert k3_or(K_UNKNOWN, K_UNKNOWN) == K_UNKNOWN; + assert k3_or(K_UNKNOWN, K_FALSE) == K_UNKNOWN; + assert k3_or(K_FALSE, K_TRUE) == K_TRUE; + assert k3_or(K_FALSE, K_UNKNOWN) == K_UNKNOWN; + assert k3_or(K_FALSE, K_FALSE) == K_FALSE; + } + + test k3_not_truth_table { + // Test K3 NOT + assert k3_not(K_TRUE) == K_FALSE; + assert k3_not(K_UNKNOWN) == K_UNKNOWN; + assert k3_not(K_FALSE) == K_TRUE; + } + + test k3_no_tautology_or_not_false { + // T ∨ ¬F = T ∨ T = T (not a tautology in K3) + assert k3_or(K_TRUE, k3_not(K_FALSE)) != K_TRUE + } + + test k3_no_tautology_or_not_true { + // T ∨ ¬T = T ∨ F = ? (not a tautology) + assert k3_or(K_TRUE, k3_not(K_TRUE)) != K_TRUE + } + + test k3_no_tautology_or_not_unknown_violation { + // ? ∨ ¬? = ? ∨ ? = ? (tautology in K3!) + assert k3_or(K_UNKNOWN, k3_not(K_UNKNOWN)) == K_UNKNOWN + } + + test k3_no_tautology_exists_violating_value { + // Check if any value creates tautology with negation + for a in [K_TRUE, K_UNKNOWN, K_FALSE] { + let result = k3_or(a, k3_not(a)); + if result != K_UNKNOWN { + assert false, "Should not have tautology in K3"; + } + } + } + + test k3_no_tautology_all_values_tested { + // Verify all values tested above + assert true + } + + test k3_restraint_from_no_tautology { + // Restraint mechanism depends on K_UNKNOWN handling + let has_tautology = false; + let values = [K_TRUE, K_UNKNOWN, K_FALSE]; + for a in values { + let result = k3_or(a, k3_not(a)); + if result == K_TRUE { + has_tautology = true; + } + } + assert !has_tautology, "Restraint should not trigger on tautologies"; + } + + test k3_implication_ex_falso { + // F → anything (K3: F → x = ?) + let result = k3_implies(K_FALSE, K_TRUE); + assert result == K_UNKNOWN; + } + + test k3_implication_when_antecedent_true { + // T → x = x (if consequent true, result true) + assert k3_implies(K_TRUE, K_TRUE) == K_TRUE; + assert k3_implies(K_TRUE, K_UNKNOWN) == K_UNKNOWN; + assert k3_implies(K_TRUE, K_FALSE) == K_FALSE; + } + + test k3_implication_when_consequent_true { + // x → T = T (if consequent true, result true) + assert k3_implies(K_TRUE, K_TRUE) == K_TRUE; + assert k3_implies(K_UNKNOWN, K_TRUE) == K_TRUE; + assert k3_implies(K_FALSE, K_TRUE) == K_TRUE; + } + + test k3_implication_with_unknown { + // ? → ? = ? (unknown implies unknown) + assert k3_implies(K_UNKNOWN, K_UNKNOWN) == K_UNKNOWN; + } + + test k3_equiv_reflexive { + // a ↔ a + assert k3_equiv(K_TRUE, K_TRUE); + assert k3_equiv(K_UNKNOWN, K_UNKNOWN); + assert k3_equiv(K_FALSE, K_FALSE); + } + + test k3_equiv_symmetric { + // a ↔ b = b ↔ a + assert k3_equiv(K_TRUE, K_UNKNOWN) == k3_equiv(K_UNKNOWN, K_TRUE); + } + + test k3_equiv_transitive { + // (a ↔ b) ∧ (b ↔ c) → (a ↔ c) + let ab = k3_equiv(K_TRUE, K_UNKNOWN); + let bc = k3_equiv(K_UNKNOWN, K_FALSE); + let ac = k3_equiv(K_TRUE, K_FALSE); + let lhs = k3_and(ab, bc); + assert k3_implies(lhs, ac); + } + + test k3_equiv_when_both_true { + // T ↔ T = T + assert k3_equiv(K_TRUE, K_TRUE) == K_TRUE; + } + + test k3_equiv_when_both_false { + // F ↔ F = T + assert k3_equiv(K_FALSE, K_FALSE) == K_TRUE; + } + + test k3_equiv_when_opposite { + // T ↔ F = F + assert k3_equiv(K_TRUE, K_FALSE) == K_FALSE; + } + + test forward_chain_modus_ponens_true { + // Rule: P → Q, Fact: P, Derive: Q + let rule = Rule {antecedent: K_TRUE, consequent: K_UNKNOWN}; + let fact = K_TRUE; + let result = forward_chain(rule, fact); + assert result == K_UNKNOWN; + } + + test forward_chain_modus_ponens_false_consequent { + // Rule: P → F, Fact: P, Derive: F + let rule = Rule {antecedent: K_TRUE, consequent: K_FALSE}; + let fact = K_TRUE; + let result = forward_chain(rule, fact); + assert result == K_FALSE; + } + + test forward_chain_with_unknown_fact { + // Rule: P → Q, Fact: ?, Derive: ? + let rule = Rule {antecedent: K_TRUE, consequent: K_UNKNOWN}; + let fact = K_UNKNOWN; + let result = forward_chain(rule, fact); + assert result == K_UNKNOWN; + } + + test forward_chain_no_match { + // Rule: P → Q, Fact: F, Derive: ? + let rule = Rule {antecedent: K_TRUE, consequent: K_UNKNOWN}; + let fact = K_FALSE; + let result = forward_chain(rule, fact); + assert result == K_FALSE; + } + + test backward_chain_finds_support { + // Goal: Q, Rules: [P→Q, R→Q], Find: P or R + let rules = [ + Rule {antecedent: K_TRUE, consequent: K_UNKNOWN}, + Rule {antecedent: K_FALSE, consequent: K_UNKNOWN} + ]; + let goal = K_UNKNOWN; + let result = backward_chain(goal, rules); + assert result == K_UNKNOWN; + } + + test backward_chain_no_support { + // Goal: Q, Rules: [P→R, R→S], Find: ? + let rules = [ + Rule {antecedent: K_TRUE, consequent: K_FALSE}, + Rule {antecedent: K_FALSE, consequent: K_TRUE} + ]; + let goal = K_UNKNOWN; + let result = backward_chain(goal, rules); + assert result == K_UNKNOWN; + } + + test backward_chain_multiple_rules { + // Multiple rules supporting same goal + let rules = [ + Rule {antecedent: K_TRUE, consequent: K_UNKNOWN}, + Rule {antecedent: K_FALSE, consequent: K_UNKNOWN} + ]; + let goal = K_UNKNOWN; + let result = backward_chain(goal, rules); + assert result == K_UNKNOWN; + } + + test resolve_complementary_literals { + // Resolve (T) and (F) → (?) + let a = [K_TRUE]; + let b = [K_FALSE]; + let result = resolve(a, b); + assert result[0] == K_UNKNOWN; + } + + test resolve_non_complementary { + // Resolve (T) and (T) → (T) + let a = [K_TRUE]; + let b = [K_TRUE]; + let result = resolve(a, b); + assert result[0] == K_TRUE; + } + + test is_restraint_true_for_unknown { + assert is_restraint(K_UNKNOWN); + } + + test is_restraint_false_for_false { + assert !is_restraint(K_FALSE); + } + + test is_restraint_false_for_true { + assert !is_restraint(K_TRUE); + } + + test apply_restraint_replaces_unknown { + let values = [K_TRUE, K_UNKNOWN, K_FALSE]; + let result = apply_restraint(values); + assert result == [K_TRUE, K_FALSE, K_FALSE]; + } + + test apply_restraint_preserves_known { + let values = [K_TRUE, K_FALSE]; + let result = apply_restraint(values); + assert result == [K_TRUE, K_FALSE]; + } + + // BENCHMARKS - Performance metrics + + bench k3_and_latency { + // Target: <1μs on XC7A100T + } + + bench k3_or_latency { + // Target: <1μs on XC7A100T + } + + bench k3_not_latency { + // Target: <1μs on XC7A100T + } + + bench k3_implies_latency { + // Target: <2μs (two operations) + } + + bench k3_equiv_latency { + // Target: <3μs (three operations) + } + + bench forward_chain_latency { + // Target: <2μs + } + + bench backward_chain_latency { + // Target: <5μs (iterative) + } + + bench resolve_latency { + // Target: <3μs + } + + bench apply_restraint_latency { + // Target: <1μs + } +} diff --git a/apps/website/public/t27/specs/auth/config.t27 b/apps/website/public/t27/specs/auth/config.t27 new file mode 100644 index 0000000000..c2db5bd5b6 --- /dev/null +++ b/apps/website/public/t27/specs/auth/config.t27 @@ -0,0 +1,294 @@ +// specs/auth/config.t27 +// Authentication Configuration Storage +// phi^2 + 1/phi^2 = 3 | TRINITY + +module AuthConfig { + use base::types; + use account::schema; + + // ==================================================================== + // Auth Types + // ==================================================================== + + // AuthType represents the type of authentication + enum AuthType { + Oauth = 0, + Api = 1, + WellKnown = 2, + } + + // ==================================================================== + // OAuth Configuration + // ==================================================================== + + // Oauth represents OAuth authentication configuration + struct Oauth { + type: AuthType, + refresh: RefreshToken, + access: AccessToken, + expires: u64, + account_id: str, + enterprise_url: str?, + } + + // ==================================================================== + // API Key Configuration + // ==================================================================== + + // Api represents API key authentication configuration + struct Api { + type: AuthType, + key: str, + } + + // ==================================================================== + // Well-Known Configuration + // ==================================================================== + + // WellKnown represents well-known authentication configuration + struct WellKnown { + type: AuthType, + key: str, + token: str, + } + + // ==================================================================== + // Auth Info + // ==================================================================== + + // Info represents authentication info (type tag) + struct Info(u8); + + // ==================================================================== + // Config Operations + // ==================================================================== + + // get retrieves auth configuration for a provider + fn get(provider_id: str) -> Result { + // Implementation: Read auth config from storage + } + + // all returns all stored auth configurations + fn all() -> Result<[str: Info], AuthConfigError> { + // Implementation: Read all auth configs from storage + } + + // set stores auth configuration for a provider + fn set(key: str, info: Info) -> Result { + // Implementation: Write auth config to storage + } + + // remove deletes auth configuration for a provider + fn remove(key: str) -> Result { + // Implementation: Delete auth config from storage + } + + // exists checks if auth config exists for a provider + fn exists(provider_id: str) -> Result { + // Implementation: Check if config file exists + } + + // clear removes all auth configurations + fn clear() -> Result { + // Implementation: Delete all auth config files + } + + // ==================================================================== + // Error Types + // ==================================================================== + + // AuthConfigError represents an auth config operation error + struct AuthConfigError { + message: str, + provider_id: str?, + } + + // ==================================================================== + // Type Conversion + // ==================================================================== + + // to_oauth converts Info to Oauth if type matches + fn to_oauth(info: Info) -> Result { + // Implementation: Cast if type is Oauth + } + + // to_api converts Info to Api if type matches + fn to_api(info: Info) -> Result { + // Implementation: Cast if type is Api + } + + // to_well_known converts Info to WellKnown if type matches + fn to_well_known(info: Info) -> Result { + // Implementation: Cast if type is WellKnown + } + + // from_oauth creates Info from Oauth + fn from_oauth(oauth: Oauth) -> Info { + // Implementation: Create Info with Oauth type tag + } + + // from_api creates Info from Api + fn from_api(api: Api) -> Info { + // Implementation: Create Info with Api type tag + } + + // from_well_known creates Info from WellKnown + fn from_well_known(well_known: WellKnown) -> Info { + // Implementation: Create Info with WellKnown type tag + } + + // get_type extracts AuthType from Info + fn get_type(info: Info) -> AuthType { + // Implementation: Extract type tag from Info + } + + // ==================================================================== + // Provider IDs (well-known) + // ==================================================================== + + const PROVIDER_OPENCODE: str = "opencode"; + const PROVIDER_ANTHROPIC: str = "anthropic"; + const PROVIDER_OPENAI: str = "openai"; + const PROVIDER_GOOGLE: str = "google"; + const PROVIDER_GOOGLE_VERTEX: str = "google-vertex"; + const PROVIDER_GITHUB_COPILOT: str = "github-copilot"; + const PROVIDER_AMAZON_BEDROCK: str = "amazon-bedrock"; + const PROVIDER_AZURE: str = "azure"; + const PROVIDER_OPENROUTER: str = "openrouter"; + const PROVIDER_MISTRAL: str = "mistral"; + const PROVIDER_GITLAB: str = "gitlab"; + + // ==================================================================== + // Tests + // ==================================================================== + + test "auth_type_values" { + assert(AuthType::Oauth as u32 == 0); + assert(AuthType::Api as u32 == 1); + assert(AuthType::WellKnown as u32 == 2); + } + + test "oauth_creation" { + var oauth = Oauth { + type = AuthType::Oauth, + refresh = RefreshToken("refresh"), + access = AccessToken("access"), + expires = 3600000, + account_id = "user-123", + enterprise_url = "https://enterprise.example.com", + }; + assert(oauth.type == AuthType::Oauth); + assert(oauth.account_id == "user-123"); + assert(oauth.enterprise_url? == "https://enterprise.example.com"); + } + + test "oauth_without_enterprise_url" { + var oauth = Oauth { + type = AuthType::Oauth, + refresh = RefreshToken("refresh"), + access = AccessToken("access"), + expires = 3600000, + account_id = "user-123", + enterprise_url = null, + }; + assert(oauth.enterprise_url == null); + } + + test "api_creation" { + var api = Api { + type = AuthType::Api, + key = "sk-api-key", + }; + assert(api.type == AuthType::Api); + assert(api.key == "sk-api-key"); + } + + test "well_known_creation" { + var wk = WellKnown { + type = AuthType::WellKnown, + key = "provider-key", + token = "provider-token", + }; + assert(wk.type == AuthType::WellKnown); + assert(wk.key == "provider-key"); + assert(wk.token == "provider-token"); + } + + test "info_creation" { + var info = Info(0); // Oauth type + assert(info.0 == 0); + } + + test "get_type_from_info" { + var oauth_info = Info(0); + var api_info = Info(1); + var wk_info = Info(2); + + assert(get_type(oauth_info) == AuthType::Oauth); + assert(get_type(api_info) == AuthType::Api); + assert(get_type(wk_info) == AuthType::WellKnown); + } + + test "from_oauth_creates_correct_info" { + var oauth = Oauth { + type = AuthType::Oauth, + refresh = RefreshToken("refresh"), + access = AccessToken("access"), + expires = 3600000, + account_id = "user-123", + enterprise_url = null, + }; + var info = from_oauth(oauth); + assert(get_type(info) == AuthType::Oauth); + } + + test "from_api_creates_correct_info" { + var api = Api { + type = AuthType::Api, + key = "sk-api-key", + }; + var info = from_api(api); + assert(get_type(info) == AuthType::Api); + } + + test "from_well_known_creates_correct_info" { + var wk = WellKnown { + type = AuthType::WellKnown, + key = "key", + token = "token", + }; + var info = from_well_known(wk); + assert(get_type(info) == AuthType::WellKnown); + } + + test "auth_config_error_creation" { + var err = AuthConfigError { + message = "Config not found", + provider_id = "unknown", + }; + assert(err.message == "Config not found"); + assert(err.provider_id? == "unknown"); + } + + test "auth_config_error_without_provider" { + var err = AuthConfigError { + message = "Storage error", + provider_id = null, + }; + assert(err.provider_id == null); + } + + test "provider_constants" { + assert(PROVIDER_OPENCODE == "opencode"); + assert(PROVIDER_ANTHROPIC == "anthropic"); + assert(PROVIDER_OPENAI == "openai"); + assert(PROVIDER_GOOGLE == "google"); + assert(PROVIDER_GOOGLE_VERTEX == "google-vertex"); + assert(PROVIDER_GITHUB_COPILOT == "github-copilot"); + assert(PROVIDER_AMAZON_BEDROCK == "amazon-bedrock"); + assert(PROVIDER_AZURE == "azure"); + assert(PROVIDER_OPENROUTER == "openrouter"); + assert(PROVIDER_MISTRAL == "mistral"); + assert(PROVIDER_GITLAB == "gitlab"); + } +} diff --git a/apps/website/public/t27/specs/automation/wrapup-auto.t27 b/apps/website/public/t27/specs/automation/wrapup-auto.t27 new file mode 100644 index 0000000000..6421ab67e4 --- /dev/null +++ b/apps/website/public/t27/specs/automation/wrapup-auto.t27 @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +# specs/automation/wrapup-auto.t27 +# Ring-071 - RAG-Backed Semantic Memory +# phi^2 + 1/phi^2 = 3 | TRINITY + +module automation::wrapup { + use memory::notebooklm; + + const DEFAULT_NOTEBOOK : str = "t27-QUEEN-BRAIN"; + const VENV_PATH : str = ".trinity/notebooklm-venv"; + + // tri_command: "tri wrapup" + // Invoked from Claude Code via /tri wrapup or Skill tool + // Backend: contrib/backend/notebooklm/wrapup_auto.py + + struct WrapUpInput { + summary: str, + decisions: str, + files_modified: [str], + next_steps: str, + session_id: str, + issue_number: u32, // GitHub issue number + issue_title: str, // GitHub issue title + } + + struct WrapUpResult { + notebook_id: str, + notebook_name: str, // "t27 #NNN -- title" + source_id: str, + uploaded_at: u64, + } + + // wrapup_run(input: WrapUpInput) -> (WrapUpResult, ErrorCode) + // Find/create notebook, format markdown, upload source + + test "wrapup_run_uploads_source" + const input = WrapUpInput { + summary: "Test session", + decisions: "Used notebooklm-py", + files_modified: ["specs/automation/wrapup-auto.t27"], + next_steps: "Verify upload", + session_id: "test-001", + issue_number: 350, + issue_title: "NotebookLM Integration", + }; + const (result, err) = wrapup_run(input); + assert(err == ErrorCode::Success); + assert(result.source_id.len > 0); + assert(result.notebook_name == "t27 #350 -- NotebookLM Integration"); +} diff --git a/apps/website/public/t27/specs/base/debounce.t27 b/apps/website/public/t27/specs/base/debounce.t27 new file mode 100644 index 0000000000..7a21a99f62 --- /dev/null +++ b/apps/website/public/t27/specs/base/debounce.t27 @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 +// base/debounce.t27 — φ-Structured Debouncing +// Trinity S³AI — Rate Limiting and Debouncing +// φ² + 1/φ² = 3 | TRINITY + +module base-debounce; + +use math::sacred_physics::{PHI, PHI_INV}; + +// ============================================================================ +// SACRED CONSTANTS +// ============================================================================ + +/// φ (PHI) = 1.618... +pub const PHI : f64 = 1.618033988749895; + +/// φ⁻¹ (PHI_INV) = 0.618... +pub const PHI_INV : f64 = 0.618033988749895; + +/// Default debounce delay (φ-structured: 618ms) +pub const DEBOUNCE_DELAY_MS : u64 = 618; + +/// Debounce window (φ: 1618ms) +pub const DEBOUNCE_WINDOW_MS : u64 = 1618; + +// ============================================================================ +// DEBOUNCE STATE +// ============================================================================ + +/// Debouncer state +pub struct Debouncer { + /// Last execution timestamp + pub last_exec_ms: u64, + /// Cooldown remaining + pub cooldown_ms: u64, + /// Enabled flag + pub enabled: bool, +} + +/// Initialize debouncer +pub fn debouncer_init() -> Debouncer { + return Debouncer{ + .last_exec_ms = 0, + .cooldown_ms = 0, + .enabled = true, + }; +} + +/// Check if debouncer allows execution +pub fn debouncer_should_exec(debouncer: Debouncer) -> bool { + if (!debouncer.enabled) { + return false; + } + + const now = get_timestamp_ms(); + const elapsed = now - debouncer.last_exec_ms; + + return elapsed >= DEBOUNCE_DELAY_MS; +} + +/// Record execution +pub fn debouncer_record_exec(debouncer: Debouncer) -> Debouncer { + var result = debouncer; + result.last_exec_ms = get_timestamp_ms(); + result.cooldown_ms = DEBOUNCE_DELAY_MS; + return result; +} + +/// Get timestamp (placeholder) +pub fn get_timestamp_ms() -> u64 { + return 0; +} + +// ============================================================================ +// TDD: TESTS +// ============================================================================ + +test "debounce_delay_is_phi_inv_structured" { + assert DEBOUNCE_DELAY_MS == 618; +} + +test "debounce_window_is_phi_structured" { + assert DEBOUNCE_WINDOW_MS == 1618; +} + +test "debouncer_init_default" { + const debouncer = debouncer_init(); + assert debouncer.enabled == true; + assert debouncer.cooldown_ms == 0; +} + +// ============================================================================ +// TDD: INVARIANTS +// ============================================================================ + +invariant "debounce_delay_is_phi_inv" { + assert DEBOUNCE_DELAY_MS == 618; +} diff --git a/apps/website/public/t27/specs/base/ops.t27 b/apps/website/public/t27/specs/base/ops.t27 new file mode 100644 index 0000000000..b69dd54018 --- /dev/null +++ b/apps/website/public/t27/specs/base/ops.t27 @@ -0,0 +1,1489 @@ +// SPDX-License-Identifier: Apache-2.0 +; ops.t27 -- Trit Operations for t27 Language +; Trit arithmetic: multiply, add, carry, comparison +; phi^2 + 1/phi^2 = 3 | TRINITY + +module tritype-ops; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const NEGONE : i8 = -1; +pub const ZERO : i8 = 0; +pub const ONE : i8 = 1; + +// Trit enum for type safety +pub const Trit = enum(i8) { + neg = -1, + zero = 0, + pos = 1, +}; + +// ============================================================================ +// Lookup Tables +// ============================================================================ +// Table size: 9 entries (3*3 for each operand) +// Indexed as: table[(a+1)*3 + (b+1)] where a,b in {-1,0,+1} + +// trit_multiply lookup table +// -1 0 +1 +// -1 +1 0 -1 +// 0 0 0 0 +// +1 -1 0 +1 +pub const mult_table : [9]i8 = [1, 0, -1, 0, 0, 0, -1, 0, 1]; + +// trit_add lookup table +// -1 0 +1 +// -1 -1 -1 0 +// 0 -1 0 +1 +// +1 0 +1 +1 +pub const add_table : [9]i8 = [-1, -1, 0, -1, 0, 1, 0, 1, 1]; + +// trit_carry lookup table (for addition overflow) +// Carry = -1 if result < -1, +1 if result > +1, else 0 +// For balanced ternary: a + b = result + 3*carry +pub const carry_table : [9]i8 = [1, 0, 0, 0, 0, 0, 0, 0, -1]; + +// ============================================================================ +// Functions +// ============================================================================ + +// trit_multiply_table(a: Trit, b: Trit) -> Trit +// Fast trit multiplication using lookup table +pub fn trit_multiply_table(a: Trit, b: Trit) Trit { + const idx = (@intFromEnum(a) + 1) * 3 + (@intFromEnum(b) + 1); + return @as(Trit, @enumFromInt(mult_table[idx])); +} + +// trit_add_table(a: Trit, b: Trit) -> Trit +// Fast trit addition using lookup table +pub fn trit_add_table(a: Trit, b: Trit) Trit { + const idx = (@intFromEnum(a) + 1) * 3 + (@intFromEnum(b) + 1); + return @as(Trit, @enumFromInt(add_table[idx])); +} + +// trit_carry_table(a: Trit, b: Trit) -> Trit +// Fast carry computation using lookup table +// Returns carry value for trit addition: -1 if a+b < -1, +1 if a+b > +1, else 0 +pub fn trit_carry_table(a: Trit, b: Trit) Trit { + const idx = (@intFromEnum(a) + 1) * 3 + (@intFromEnum(b) + 1); + return @as(Trit, @enumFromInt(carry_table[idx])); +} + +// AddResult: Result and carry from trit addition +pub struct AddResult { + result : Trit, + carry_out : Trit, +} + +// trit_add_with_carry(a: Trit, b: Trit, carry_in: Trit) -> AddResult +// Full ternary addition with carry propagation +// result = a + b + carry_in +// carry_out = -1 if result < -1, +1 if result > +1, else 0 +// result is normalized to [-1, 0, +1] +pub fn trit_add_with_carry(a: Trit, b: Trit, carry_in: Trit) AddResult { + // First: a + b + var sum: i8 = @intFromEnum(a) + @intFromEnum(b); + var carry: Trit = .zero; + var result: Trit = .zero; + + // Check for overflow + if (sum > 1) { + result = .neg; + carry = .pos; + } else if (sum < -1) { + result = .pos; + carry = .neg; + } else { + result = @as(Trit, @enumFromInt(sum)); + } + + // Add carry_in + sum = @intFromEnum(result) + @intFromEnum(carry_in); + if (sum > 1) { + result = .neg; + carry = .pos; + } else if (sum < -1) { + result = .pos; + carry = .neg; + } else { + result = @as(Trit, @enumFromInt(sum)); + carry = .zero; + } + + return AddResult{ .result = result, .carry_out = carry }; +} + +// trit_compare(a: Trit, b: Trit) -> i8 +// Returns: -1 if a < b, 0 if a == b, +1 if a > b +pub fn trit_compare(a: Trit, b: Trit) i8 { + if (a == b) { + return 0; + } else if (a == .neg or (a == .zero and b == .pos)) { + return -1; + } else { + return 1; + } +} + +// trit_negate(a: Trit) -> Trit +// Returns -a (trit negation) +// -(-1) = +1, -(0) = 0, -(+1) = -1 +pub fn trit_negate(a: Trit) Trit { + return switch (a) { + .neg => .pos, + .zero => .zero, + .pos => .neg, + }; +} + +// trit_abs(a: Trit) -> Trit +// Returns |a| (absolute value, always 0 or +1) +// |-1| = +1, |0| = 0, |+1| = +1 +pub fn trit_abs(a: Trit) Trit { + return if (a == .neg) .pos else a; +} + +// trit_min(a: Trit, b: Trit) -> Trit +// Returns min(a, b) +pub fn trit_min(a: Trit, b: Trit) Trit { + return if (a == .neg or (a == .zero and b == .pos)) a else b; +} + +// trit_max(a: Trit, b: Trit) -> Trit +// Returns max(a, b) +pub fn trit_max(a: Trit, b: Trit) Trit { + return if (a == .pos or (a == .zero and b == .neg)) a else b; +} + +// trit_subtract(a: Trit, b: Trit) -> Trit +// Returns a - b using a + (-b) +pub fn trit_subtract(a: Trit, b: Trit) Trit { + return trit_add_table(a, trit_negate(b)); +} + +// trit_sign(a: Trit) -> i8 +// Returns sign of a: -1 if negative, 0 if zero, +1 if positive +pub fn trit_sign(a: Trit) i8 { + return @intFromEnum(a); +} + +// trit_clamp(a: Trit, min_val: Trit, max_val: Trit) -> Trit +// Clamps a to [min_val, max_val] range (requires min_val <= max_val) +pub fn trit_clamp(a: Trit, min_val: Trit, max_val: Trit) Trit { + if (trit_compare(a, min_val) < 0) return min_val; + if (trit_compare(a, max_val) > 0) return max_val; + return a; +} + +// trit_is_negative(a: Trit) -> bool +// Returns true if a is -1 +pub fn trit_is_negative(a: Trit) bool { + return a == .neg; +} + +// trit_is_zero(a: Trit) -> bool +// Returns true if a is 0 +pub fn trit_is_zero(a: Trit) bool { + return a == .zero; +} + +// trit_is_positive(a: Trit) -> bool +// Returns true if a is +1 +pub fn trit_is_positive(a: Trit) bool { + return a == .pos; +} + +// trit_equal(a: Trit, b: Trit) -> bool +// Returns true if a == b +pub fn trit_equal(a: Trit, b: Trit) bool { + return a == b; +} + +// trit_not_equal(a: Trit, b: Trit) -> bool +// Returns true if a != b +pub fn trit_not_equal(a: Trit, b: Trit) bool { + return a != b; +} + +// trit_lt(a: Trit, b: Trit) -> bool +// Returns true if a < b (less than) +pub fn trit_lt(a: Trit, b: Trit) bool { + return trit_compare(a, b) < 0; +} + +// trit_le(a: Trit, b: Trit) -> bool +// Returns true if a <= b (less than or equal) +pub fn trit_le(a: Trit, b: Trit) bool { + return trit_compare(a, b) <= 0; +} + +// trit_gt(a: Trit, b: Trit) -> bool +// Returns true if a > b (greater than) +pub fn trit_gt(a: Trit, b: Trit) bool { + return trit_compare(a, b) > 0; +} + +// trit_ge(a: Trit, b: Trit) -> bool +// Returns true if a >= b (greater than or equal) +pub fn trit_ge(a: Trit, b: Trit) bool { + return trit_compare(a, b) >= 0; +} + +// trit_multiply_with_carry(a: Trit, b: Trit, carry_in: Trit) -> MultiplyResult +// Full ternary multiplication with carry propagation +// result = a * b + carry_in +// carry_out = -1 if result < -1, +1 if result > +1, else 0 +pub fn trit_multiply_with_carry(a: Trit, b: Trit, carry_in: Trit) AddResult { + // First: a * b + const product = trit_multiply_table(a, b); + + // Add carry_in to product + return trit_add_with_carry(product, carry_in, .zero); +} + +// trit_reverse(a: Trit) -> Trit +// Returns the multiplicative inverse of a in balanced ternary +// In balanced ternary: 1^-1 = -1, -1^-1 = -1, 0 has no inverse +// Returns zero for zero (no inverse for zero) +pub fn trit_reverse(a: Trit) Trit { + return if (a == .zero) .zero else a; // In balanced ternary, 1 * 1 = 1, -1 * -1 = 1, so reverse of +/-1 is +/-1 +} + +// trit_multiply_by_power_of_two(a: Trit, power: u8) -> Trit +// Multiplies a by 2^n using shifts in balanced ternary +// Equivalent to adding a to itself n times (for small n) +pub fn trit_multiply_by_power_of_two(a: Trit, power: u8) Trit { + var result = a; + var i: u8 = 1; + while (i < power) { + const carry = trit_carry_table(result, a); + if (carry != .zero) { + // Overflow beyond trit range, clamp + result = if (carry == .pos) .pos else .neg; + } + result = trit_add_table(result, a); + i += 1; + } + return result; +} + +// trit_power(a: Trit, n: u8) -> Trit +// Raise trit to a small power (n in {0, 1, 2, 3}) +// trit_power(x, 0) = +1 (identity element for multiplication) +// trit_power(x, 1) = x +// trit_power(x, 2) = x * x +// trit_power(x, 3) = x * x * x +// For larger powers, the result cycles based on trit properties +pub fn trit_power(a: Trit, n: u8) Trit { + if (n == 0) { + return .pos; // x^0 = 1 for any x != 0 + } + if (n == 1) { + return a; + } + if (a == .zero) { + return .zero; // 0^n = 0 for n > 0 + } + if (a == .pos) { + return .pos; // 1^n = 1 for any n + } + // a == .neg: (-1)^n = -1 if odd, +1 if even + return if (n % 2 == 1) .neg else .pos; +} + +// trit_from_bool(b: bool) -> Trit +// Convert boolean to trit (true -> +1, false -> 0) +// Maps boolean logic to balanced ternary +pub fn trit_from_bool(b: bool) Trit { + return if (b) .pos else .zero; +} + +// trit_to_bool(a: Trit) -> bool +// Convert trit to boolean (+1 -> true, others -> false) +// Maps trit to binary boolean logic +pub fn trit_to_bool(a: Trit) bool { + return a == .pos; +} + +// trit_abs_diff(a: Trit, b: Trit) -> Trit +// Compute absolute difference between two trits +// Returns 0 if equal, 1 if different (both positive) +pub fn trit_abs_diff(a: Trit, b: Trit) Trit { + return if (a == b) .zero else .pos; +} + +// trit_cond_swap(cond: Trit, a: Trit, b: Trit) -> Trit +// Conditional swap: return b if cond is +1, else a +// This is equivalent to trit_select but with swapped semantics +pub fn trit_cond_swap(cond: Trit, a: Trit, b: Trit) Trit { + return if (cond == .pos) b else a; +} + +// trit_is_unit(a: Trit) -> bool +// Check if trit is a multiplicative unit (+1) +pub fn trit_is_unit(a: Trit) bool { + return a == .pos; +} + +// trit_is_identity(a: Trit) -> bool +// Check if trit is additive identity (0) +pub fn trit_is_identity(a: Trit) bool { + return a == .zero; +} + +// trit_is_negated(a: Trit, b: Trit) -> bool +// Check if b is the negation of a +pub fn trit_is_negated(a: Trit, b: Trit) bool { + return b == trit_negate(a); +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "test_trit_multiply_table_all_combinations" { + // Verify: lookup table returns correct product for all 9 combinations + try std.testing.expectEqual(@as(Trit, .pos), trit_multiply_table(.neg, .neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_multiply_table(.neg, .zero)); + try std.testing.expectEqual(@as(Trit, .neg), trit_multiply_table(.neg, .pos)); + try std.testing.expectEqual(@as(Trit, .zero), trit_multiply_table(.zero, .neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_multiply_table(.zero, .zero)); + try std.testing.expectEqual(@as(Trit, .zero), trit_multiply_table(.zero, .pos)); + try std.testing.expectEqual(@as(Trit, .neg), trit_multiply_table(.pos, .neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_multiply_table(.pos, .zero)); + try std.testing.expectEqual(@as(Trit, .pos), trit_multiply_table(.pos, .pos)); +} + +test "test_trit_multiply_table_commutative" { + // Verify: trit_multiply_table(a, b) == trit_multiply_table(b, a) + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |a| { + for (trits) |b| { + try std.testing.expectEqual(trit_multiply_table(a, b), trit_multiply_table(b, a)); + } + } +} + +test "test_trit_add_table_neg_plus_neg" { + // Verify: -1 + -1 = -1 + try std.testing.expectEqual(@as(Trit, .neg), trit_add_table(.neg, .neg)); +} + +test "test_trit_add_table_neg_plus_zero" { + // Verify: -1 + 0 = -1 + try std.testing.expectEqual(@as(Trit, .neg), trit_add_table(.neg, .zero)); +} + +test "test_trit_add_table_neg_plus_pos" { + // Verify: -1 + +1 = 0 + try std.testing.expectEqual(@as(Trit, .zero), trit_add_table(.neg, .pos)); +} + +test "test_trit_add_table_zero_plus_zero" { + // Verify: 0 + 0 = 0 + try std.testing.expectEqual(@as(Trit, .zero), trit_add_table(.zero, .zero)); +} + +test "test_trit_add_table_zero_plus_pos" { + // Verify: 0 + +1 = +1 + try std.testing.expectEqual(@as(Trit, .pos), trit_add_table(.zero, .pos)); +} + +test "test_trit_add_table_pos_plus_pos" { + // Verify: +1 + +1 = +1 + try std.testing.expectEqual(@as(Trit, .pos), trit_add_table(.pos, .pos)); +} + +test "test_trit_add_table_commutative" { + // Verify: trit_add_table(a, b) == trit_add_table(b, a) + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |a| { + for (trits) |b| { + try std.testing.expectEqual(trit_add_table(a, b), trit_add_table(b, a)); + } + } +} + +test "test_trit_add_table_identity_zero" { + // Verify: trit_add_table(a, 0) == a for all trit values + try std.testing.expectEqual(@as(Trit, .neg), trit_add_table(.neg, .zero)); + try std.testing.expectEqual(@as(Trit, .zero), trit_add_table(.zero, .zero)); + try std.testing.expectEqual(@as(Trit, .pos), trit_add_table(.pos, .zero)); +} + +test "test_trit_carry_table_neg_plus_neg" { + // Verify: -1 + -1 generates carry +1 + try std.testing.expectEqual(@as(Trit, .pos), trit_carry_table(.neg, .neg)); +} + +test "test_trit_carry_table_neg_plus_zero" { + // Verify: -1 + 0 generates no carry + try std.testing.expectEqual(@as(Trit, .zero), trit_carry_table(.neg, .zero)); +} + +test "test_trit_carry_table_neg_plus_pos" { + // Verify: -1 + +1 generates no carry + try std.testing.expectEqual(@as(Trit, .zero), trit_carry_table(.neg, .pos)); +} + +test "test_trit_carry_table_zero_plus_zero" { + // Verify: 0 + 0 generates no carry + try std.testing.expectEqual(@as(Trit, .zero), trit_carry_table(.zero, .zero)); +} + +test "test_trit_carry_table_zero_plus_pos" { + // Verify: 0 + +1 generates no carry + try std.testing.expectEqual(@as(Trit, .zero), trit_carry_table(.zero, .pos)); +} + +test "test_trit_carry_table_pos_plus_pos" { + // Verify: +1 + +1 generates carry -1 + try std.testing.expectEqual(@as(Trit, .neg), trit_carry_table(.pos, .pos)); +} + +test "test_trit_carry_table_commutative" { + // Verify: trit_carry_table(a, b) == trit_carry_table(b, a) + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |a| { + for (trits) |b| { + try std.testing.expectEqual(trit_carry_table(a, b), trit_carry_table(b, a)); + } + } +} + +test "test_trit_add_with_carry_no_carry" { + // Verify: a + b with zero carry_in gives correct result + const result = trit_add_with_carry(.pos, .neg, .zero); + try std.testing.expectEqual(@as(Trit, .zero), result.result); + try std.testing.expectEqual(@as(Trit, .zero), result.carry_out); +} + +test "test_trit_add_with_carry_positive_overflow" { + // Verify: +1 + +1 = -1 with carry +1 + const result = trit_add_with_carry(.pos, .pos, .zero); + try std.testing.expectEqual(@as(Trit, .neg), result.result); + try std.testing.expectEqual(@as(Trit, .pos), result.carry_out); +} + +test "test_trit_add_with_carry_negative_overflow" { + // Verify: -1 + -1 = +1 with carry -1 + const result = trit_add_with_carry(.neg, .neg, .zero); + try std.testing.expectEqual(@as(Trit, .pos), result.result); + try std.testing.expectEqual(@as(Trit, .neg), result.carry_out); +} + +test "test_trit_add_with_carry_propagation" { + // Verify: carry propagates correctly through addition + const result = trit_add_with_carry(.pos, .pos, .pos); + try std.testing.expectEqual(@as(Trit, .zero), result.result); + try std.testing.expectEqual(@as(Trit, .pos), result.carry_out); +} + +test "test_trit_add_with_carry_result_in_range" { + // Verify: trit_add_with_carry result is always in {-1, 0, +1} + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |a| { + for (trits) |b| { + for (trits) |c| { + const result = trit_add_with_carry(a, b, c); + try std.testing.expect(@intFromEnum(result.result) >= -1 and @intFromEnum(result.result) <= 1); + } + } + } +} + +test "test_trit_add_with_carry_carry_in_range" { + // Verify: trit_add_with_carry carry_out is always in {-1, 0, +1} + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |a| { + for (trits) |b| { + for (trits) |c| { + const result = trit_add_with_carry(a, b, c); + try std.testing.expect(@intFromEnum(result.carry_out) >= -1 and @intFromEnum(result.carry_out) <= 1); + } + } + } +} + +test "test_trit_compare_less_than" { + // Verify: trit_compare returns -1 when a < b + try std.testing.expectEqual(@as(i8, -1), trit_compare(.neg, .zero)); + try std.testing.expectEqual(@as(i8, -1), trit_compare(.neg, .pos)); + try std.testing.expectEqual(@as(i8, -1), trit_compare(.zero, .pos)); +} + +test "test_trit_compare_equal" { + // Verify: trit_compare returns 0 when a == b + try std.testing.expectEqual(@as(i8, 0), trit_compare(.neg, .neg)); + try std.testing.expectEqual(@as(i8, 0), trit_compare(.zero, .zero)); + try std.testing.expectEqual(@as(i8, 0), trit_compare(.pos, .pos)); +} + +test "test_trit_compare_greater_than" { + // Verify: trit_compare returns +1 when a > b + try std.testing.expectEqual(@as(i8, 1), trit_compare(.pos, .zero)); + try std.testing.expectEqual(@as(i8, 1), trit_compare(.pos, .neg)); + try std.testing.expectEqual(@as(i8, 1), trit_compare(.zero, .neg)); +} + +test "test_trit_compare_total_ordering" { + // Verify: trit_compare is transitive: if a= b + try std.testing.expect(!trit_lt(.neg, .neg)); + try std.testing.expect(!trit_lt(.zero, .neg)); + try std.testing.expect(!trit_lt(.pos, .pos)); +} + +test "test_trit_le_less_than_or_equal" { + // Verify: trit_le returns true when a <= b + try std.testing.expect(trit_le(.neg, .zero)); + try std.testing.expect(trit_le(.neg, .pos)); + try std.testing.expect(trit_le(.zero, .pos)); + try std.testing.expect(trit_le(.neg, .neg)); + try std.testing.expect(trit_le(.zero, .zero)); + try std.testing.expect(trit_le(.pos, .pos)); +} + +test "test_trit_gt_greater_than" { + // Verify: trit_gt returns true when a > b + try std.testing.expect(trit_gt(.pos, .zero)); + try std.testing.expect(trit_gt(.pos, .neg)); + try std.testing.expect(trit_gt(.zero, .neg)); +} + +test "test_trit_ge_greater_than_or_equal" { + // Verify: trit_ge returns true when a >= b + try std.testing.expect(trit_ge(.pos, .zero)); + try std.testing.expect(trit_ge(.pos, .neg)); + try std.testing.expect(trit_ge(.zero, .neg)); + try std.testing.expect(trit_ge(.neg, .neg)); + try std.testing.expect(trit_ge(.zero, .zero)); + try std.testing.expect(trit_ge(.pos, .pos)); +} + +test "test_trit_comparison_consistency" { + // Verify: lt, le, gt, ge are mutually consistent + const a: Trit = .neg; + const b: Trit = .pos; + try std.testing.expect(trit_lt(a, b)); + try std.testing.expect(trit_le(a, b)); + try std.testing.expect(!trit_gt(a, b)); + try std.testing.expect(!trit_ge(a, b)); +} + +test "test_trit_multiply_with_carry_basic" { + // Verify: 1 * 1 + 0 = 1 + const result = trit_multiply_with_carry(.pos, .pos, .zero); + try std.testing.expectEqual(@as(Trit, .neg), result.result); // 1*1=1, but 1+1 overflows + try std.testing.expectEqual(@as(Trit, .pos), result.carry_out); +} + +test "test_trit_multiply_with_carry_with_carry" { + // Verify: 1 * 1 + 1 = 0 with carry 1 + const result = trit_multiply_with_carry(.pos, .pos, .pos); + try std.testing.expectEqual(@as(Trit, .zero), result.result); + try std.testing.expectEqual(@as(Trit, .pos), result.carry_out); +} + +test "test_trit_multiply_with_carry_zero_annihilates" { + // Verify: 0 * x + 0 = 0 + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |t| { + const result = trit_multiply_with_carry(.zero, t, .zero); + try std.testing.expectEqual(@as(Trit, .zero), result.result); + try std.testing.expectEqual(@as(Trit, .zero), result.carry_out); + } +} + +test "test_trit_reverse_non_zero" { + // Verify: trit_reverse(1) = 1, trit_reverse(-1) = -1 (self-inverse in balanced ternary) + try std.testing.expectEqual(@as(Trit, .pos), trit_reverse(.pos)); + try std.testing.expectEqual(@as(Trit, .neg), trit_reverse(.neg)); +} + +test "test_trit_reverse_zero" { + // Verify: trit_reverse(0) = 0 (no inverse for zero) + try std.testing.expectEqual(@as(Trit, .zero), trit_reverse(.zero)); +} + +test "test_trit_multiply_by_power_of_two_zero" { + // Verify: 0 * 2^n = 0 + try std.testing.expectEqual(@as(Trit, .zero), trit_multiply_by_power_of_two(.zero, 3)); +} + +test "test_trit_multiply_by_power_of_two_one" { + // Verify: 1 * 2^0 = 1, 1 * 2^1 = 1 + 1 = -1 (overflow), etc. + try std.testing.expectEqual(@as(Trit, .pos), trit_multiply_by_power_of_two(.pos, 0)); +} + +test "test_trit_multiply_by_power_of_two_power_one" { + // Verify: a * 2^1 = a + a (may overflow) + const result = trit_multiply_by_power_of_two(.pos, 1); + try std.testing.expect(result == .neg or result == .pos); // 1+1 overflows to -1 +} + +test "test_trit_multiply_by_power_of_two_identity" { + // Verify: a * 2^0 = a + try std.testing.expectEqual(.neg, trit_multiply_by_power_of_two(.neg, 0)); + try std.testing.expectEqual(.zero, trit_multiply_by_power_of_two(.zero, 0)); + try std.testing.expectEqual(.pos, trit_multiply_by_power_of_two(.pos, 0)); +} + +test "test_trit_power_zero_exponent" { + // Verify: x^0 = +1 for any x != 0 + try std.testing.expectEqual(.pos, trit_power(.neg, 0)); + try std.testing.expectEqual(.pos, trit_power(.pos, 0)); +} + +test "test_trit_power_zero_base" { + // Verify: 0^n = 0 for n > 0 + try std.testing.expectEqual(.zero, trit_power(.zero, 1)); + try std.testing.expectEqual(.zero, trit_power(.zero, 2)); + try std.testing.expectEqual(.zero, trit_power(.zero, 3)); +} + +test "test_trit_power_one" { + // Verify: x^1 = x for all x + try std.testing.expectEqual(.neg, trit_power(.neg, 1)); + try std.testing.expectEqual(.zero, trit_power(.zero, 1)); + try std.testing.expectEqual(.pos, trit_power(.pos, 1)); +} + +test "test_trit_power_square" { + // Verify: x^2 = x * x + try std.testing.expectEqual(.pos, trit_power(.neg, 2)); // (-1)^2 = +1 + try std.testing.expectEqual(.zero, trit_power(.zero, 2)); // 0^2 = 0 + try std.testing.expectEqual(.pos, trit_power(.pos, 2)); // (+1)^2 = +1 +} + +test "test_trit_power_cube" { + // Verify: x^3 = x * x * x + try std.testing.expectEqual(.neg, trit_power(.neg, 3)); // (-1)^3 = -1 + try std.testing.expectEqual(.zero, trit_power(.zero, 3)); // 0^3 = 0 + try std.testing.expectEqual(.pos, trit_power(.pos, 3)); // (+1)^3 = +1 +} + +test "test_trit_from_bool_true" { + // Verify: true -> +1 + try std.testing.expectEqual(.pos, trit_from_bool(true)); +} + +test "test_trit_from_bool_false" { + // Verify: false -> 0 + try std.testing.expectEqual(.zero, trit_from_bool(false)); +} + +test "test_trit_to_bool_positive" { + // Verify: +1 -> true + try std.testing.expect(trit_to_bool(.pos)); +} + +test "test_trit_to_bool_non_positive" { + // Verify: 0 and -1 -> false + try std.testing.expect(!trit_to_bool(.zero)); + try std.testing.expect(!trit_to_bool(.neg)); +} + +test "test_trit_to_bool_from_bool_roundtrip" { + // Verify: trit_to_bool(trit_from_bool(b)) == b + try std.testing.expectEqual(trit_to_bool(trit_from_bool(true)), true); + try std.testing.expectEqual(trit_to_bool(trit_from_bool(false)), false); +} + +test "test_trit_abs_diff_equal" { + // Verify: |a - a| = 0 + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |a| { + try std.testing.expectEqual(.zero, trit_abs_diff(a, a)); + } +} + +test "test_trit_abs_diff_different" { + // Verify: |a - b| = 1 for a != b + try std.testing.expectEqual(.pos, trit_abs_diff(.neg, .zero)); + try std.testing.expectEqual(.pos, trit_abs_diff(.neg, .pos)); + try std.testing.expectEqual(.pos, trit_abs_diff(.zero, .pos)); +} + +test "test_trit_abs_diff_commutative" { + // Verify: |a - b| = |b - a| + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |a| { + for (trits) |b| { + try std.testing.expectEqual(trit_abs_diff(a, b), trit_abs_diff(b, a)); + } + } +} + +test "test_trit_cond_swap_condition_true" { + // Verify: cond_swap(+1, a, b) = b + try std.testing.expectEqual(.neg, trit_cond_swap(.pos, .neg, .zero)); + try std.testing.expectEqual(.pos, trit_cond_swap(.pos, .zero, .pos)); +} + +test "test_trit_cond_swap_condition_false" { + // Verify: cond_swap(0 or -1, a, b) = a + try std.testing.expectEqual(.neg, trit_cond_swap(.zero, .neg, .pos)); + try std.testing.expectEqual(.pos, trit_cond_swap(.neg, .pos, .neg)); +} + +test "test_trit_is_unit_true_for_pos" { + // Verify: +1 is a multiplicative unit + try std.testing.expect(trit_is_unit(.pos)); +} + +test "test_trit_is_unit_false_for_others" { + // Verify: only +1 is a unit + try std.testing.expect(!trit_is_unit(.zero)); + try std.testing.expect(!trit_is_unit(.neg)); +} + +test "test_trit_is_identity_true_for_zero" { + // Verify: 0 is additive identity + try std.testing.expect(trit_is_identity(.zero)); +} + +test "test_trit_is_identity_false_for_others" { + // Verify: only 0 is additive identity + try std.testing.expect(!trit_is_identity(.pos)); + try std.testing.expect(!trit_is_identity(.neg)); +} + +test "test_trit_is_negated_true_pair" { + // Verify: trit_is_negated(a, b) returns true when b = -a + try std.testing.expect(trit_is_negated(.neg, .pos)); + try std.testing.expect(trit_is_negated(.pos, .neg)); + try std.testing.expect(trit_is_negated(.zero, .zero)); +} + +test "test_trit_is_negated_false_non_pair" { + // Verify: trit_is_negated returns false for non-negated pairs + try std.testing.expect(!trit_is_negated(.neg, .neg)); + try std.testing.expect(!trit_is_negated(.pos, .pos)); + try std.testing.expect(!trit_is_negated(.zero, .pos)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant trit_subtract_add_negation_identity { + // trit_subtract(a, b) == trit_add_table(a, trit_negate(b)) + // Rationale: Subtraction is addition of negation + @compileAssert(true); +} + +invariant trit_sign_matches_enum_value { + // trit_sign(a) == @intFromEnum(a) for all a + // Rationale: Sign is the integer representation + @compileAssert(true); +} + +invariant trit_clamp_preserves_order { + // min_val <= max_val implies trit_clamp(a, min_val, max_val) is monotonic + // Rationale: Clamping must respect order + @compileAssert(true); +} + +invariant trit_clamp_idempotent { + // trit_clamp(trit_clamp(a, min, max), min, max) == trit_clamp(a, min, max) + // Rationale: Double clamping has no effect + @compileAssert(true); +} + +invariant trit_is_negative_matches_sign { + // trit_is_negative(a) == (trit_sign(a) < 0) + // Rationale: Negative check should match sign + @compileAssert(true); +} + +invariant trit_is_zero_matches_sign { + // trit_is_zero(a) == (trit_sign(a) == 0) + // Rationale: Zero check should match sign + @compileAssert(true); +} + +invariant trit_is_positive_matches_sign { + // trit_is_positive(a) == (trit_sign(a) > 0) + // Rationale: Positive check should match sign + @compileAssert(true); +} + +invariant trit_equal_reflexive { + // trit_equal(a, a) == true for all a + // Rationale: Equality is reflexive + @compileAssert(true); +} + +invariant trit_equal_symmetric { + // trit_equal(a, b) == trit_equal(b, a) + // Rationale: Equality is symmetric + @compileAssert(true); +} + +invariant trit_not_equal_complement { + // trit_not_equal(a, b) == !trit_equal(a, b) + // Rationale: Not-equal is complement of equal + @compileAssert(true); +} + +invariant trit_lt_implies_le { + // trit_lt(a, b) implies trit_le(a, b) + // Rationale: Less than is subset of less-than-or-equal + @compileAssert(true); +} + +invariant trit_gt_implies_ge { + // trit_gt(a, b) implies trit_ge(a, b) + // Rationale: Greater than is subset of greater-than-or-equal + @compileAssert(true); +} + +invariant trit_lt_and_gt_mutually_exclusive { + // not (trit_lt(a, b) and trit_gt(a, b)) + // Rationale: Cannot be both less and greater + @compileAssert(true); +} + +invariant trit_le_and_ge_mutually_inclusive { + // trit_le(a, b) or trit_ge(a, b) for all a, b + // Rationale: One of these must be true + @compileAssert(true); +} + +invariant trit_lt_gt_antisymmetric { + // trit_lt(a, b) == trit_gt(b, a) + // Rationale: Less than reverse is greater than + @compileAssert(true); +} + +invariant trit_le_ge_antisymmetric { + // trit_le(a, b) == trit_ge(b, a) + // Rationale: Less-or-equal reverse is greater-or-equal + @compileAssert(true); +} + +invariant trit_multiply_with_carry_no_overflow_for_zero { + // trit_multiply_with_carry(0, x, 0) carries zero + // Rationale: Zero times anything with zero carry is zero + @compileAssert(true); +} + +invariant trit_reverse_self_inverse_nonzero { + // trit_reverse(trit_reverse(x)) == x for x != 0 + // Rationale: Non-zero trits are self-inverse + @compileAssert(true); +} + +invariant trit_reverse_zero_no_inverse { + // trit_reverse(0) == 0 + // Rationale: Zero has no inverse in balanced ternary + @compileAssert(true); +} + +invariant trit_multiply_by_power_of_two_identity { + // trit_multiply_by_power_of_two(a, 0) == a for all a + // Rationale: 2^0 = 1 is multiplicative identity + @compileAssert(true); +} + +invariant trit_multiply_by_power_of_two_zero_annihilates { + // trit_multiply_by_power_of_two(0, n) == 0 for all n + // Rationale: Zero times anything is zero + @compileAssert(true); +} + +// ============================================================================ +// Legacy Invariants (from before Skill 062) +// ============================================================================ + +invariant trit_multiply_table_commutative { + // trit_multiply_table(a, b) == trit_multiply_table(b, a) + // Rationale: Multiplication is commutative + @compileAssert(true); +} + +invariant trit_add_table_commutative { + // trit_add_table(a, b) == trit_add_table(b, a) + // Rationale: Addition is commutative + @compileAssert(true); +} + +invariant trit_add_table_identity_zero { + // trit_add_table(a, 0) == a for all trit values + // Rationale: Zero is additive identity + @compileAssert(true); +} + +invariant trit_carry_table_commutative { + // trit_carry_table(a, b) == trit_carry_table(b, a) + // Rationale: Carry computation is symmetric + @compileAssert(true); +} + +invariant trit_carry_table_neg_plus_neg_gives_positive { + // trit_carry_table(-1, -1) == +1 + // Rationale: -1 + -1 = -2 overflows to +1 with carry +1 + @compileAssert(true); +} + +invariant trit_carry_table_pos_plus_pos_gives_negative { + // trit_carry_table(+1, +1) == -1 + // Rationale: +1 + +1 = +2 overflows to -1 with carry -1 + @compileAssert(true); +} + +invariant trit_add_with_carry_result_in_range { + // trit_add_with_carry result is always in {-1, 0, +1} + // Rationale: Function must normalize to trit domain + @compileAssert(true); +} + +invariant trit_add_with_carry_carry_in_range { + // trit_add_with_carry carry_out is always in {-1, 0, +1} + // Rationale: Carry is also a trit + @compileAssert(true); +} + +invariant trit_compare_total_ordering { + // trit_compare is transitive: if a 0 + // Rationale: 0^n = 0 + @compileAssert(true); +} + +invariant trit_power_pos_always_pos { + // trit_power(.pos, n) == .pos for any n + // Rationale: 1^n = 1 + @compileAssert(true); +} + +invariant trit_from_bool_to_bool_roundtrip { + // trit_to_bool(trit_from_bool(b)) == b for all boolean b + // Rationale: Conversion roundtrip preserves value + @compileAssert(true); +} + +invariant trit_abs_diff_non_negative { + // trit_abs_diff(a, b) is always 0 or +1 (never -1) + // Rationale: Absolute difference is always non-negative + @compileAssert(true); +} + +invariant trit_abs_diff_zero_iff_equal { + // trit_abs_diff(a, b) == 0 iff a == b + // Rationale: Zero difference means equal + @compileAssert(true); +} + +invariant trit_abs_diff_commutative { + // trit_abs_diff(a, b) == trit_abs_diff(b, a) + // Rationale: Absolute difference is symmetric + @compileAssert(true); +} + +invariant trit_cond_swap_condition_true_swaps { + // trit_cond_swap(.pos, a, b) == b + // Rationale: True condition selects second value + @compileAssert(true); +} + +invariant trit_cond_swap_condition_false_keeps_first { + // trit_cond_swap(x, a, b) == a for x != .pos + // Rationale: False condition keeps first value + @compileAssert(true); +} + +invariant trit_is_unit_only_for_pos { + // trit_is_unit(x) == (x == .pos) + // Rationale: Only +1 is multiplicative unit + @compileAssert(true); +} + +invariant trit_is_identity_only_for_zero { + // trit_is_identity(x) == (x == .zero) + // Rationale: Only 0 is additive identity + @compileAssert(true); +} + +invariant trit_is_negated_symmetric { + // trit_is_negated(a, b) == trit_is_negated(b, a) + // Rationale: Negation relation is symmetric + @compileAssert(true); +} + +invariant trit_is_negated_zero_self { + // trit_is_negated(.zero, .zero) == true + // Rationale: Zero is its own negation + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "bench_trit_multiply_table_latency" { + // Measure: cycles for table-based multiplication + // Target: < 15 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_multiply_table(.pos, .neg); + } +} + +bench "bench_trit_add_table_latency" { + // Measure: cycles for table-based addition + // Target: < 15 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_add_table(.pos, .neg); + } +} + +bench "bench_trit_carry_table_latency" { + // Measure: cycles for carry lookup + // Target: < 15 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_carry_table(.pos, .pos); + } +} + +bench "bench_trit_add_with_carry_latency" { + // Measure: cycles for full addition with carry + // Target: < 25 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result = trit_add_with_carry(.pos, .pos, .zero); + for (0..1000) |_| { + result = trit_add_with_carry(.pos, .pos, .zero); + } +} + +bench "bench_trit_compare_latency" { + // Measure: cycles for trit comparison + // Target: < 15 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: i8 = 0; + for (0..1000) |_| { + result = trit_compare(.pos, .neg); + } +} + +bench "bench_trit_min_max_latency" { + // Measure: cycles for trit_min or trit_max + // Target: < 10 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_min(.pos, .neg); + } +} + +bench "bench_trit_lt_latency" { + // Measure: cycles for trit less-than comparison + // Target: < 15 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = trit_lt(.neg, .pos); + } +} + +bench "bench_trit_le_latency" { + // Measure: cycles for trit less-than-or-equal comparison + // Target: < 15 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = trit_le(.neg, .pos); + } +} + +bench "bench_trit_gt_latency" { + // Measure: cycles for trit greater-than comparison + // Target: < 15 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = trit_gt(.pos, .neg); + } +} + +bench "bench_trit_ge_latency" { + // Measure: cycles for trit greater-than-or-equal comparison + // Target: < 15 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = trit_ge(.pos, .neg); + } +} + +bench "bench_trit_multiply_with_carry_latency" { + // Measure: cycles for multiplication with carry + // Target: < 30 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result = trit_multiply_with_carry(.pos, .pos, .zero); + for (0..1000) |_| { + result = trit_multiply_with_carry(.pos, .pos, .zero); + } +} + +bench "bench_trit_reverse_latency" { + // Measure: cycles for trit reverse (multiplicative inverse) + // Target: < 5 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_reverse(.pos); + } +} + +bench "bench_trit_multiply_by_power_of_two_latency" { + // Measure: cycles for trit multiplication by power of two + // Target: < 20 cycles on t27-hardware (power = 1) + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_multiply_by_power_of_two(.pos, 1); + } +} + +bench "bench_trit_power_latency" { + // Measure: cycles for trit power operation + // Target: < 15 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_power(.neg, 2); + } +} + +bench "bench_trit_from_bool_latency" { + // Measure: cycles for boolean to trit conversion + // Target: < 5 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_from_bool(true); + } +} + +bench "bench_trit_to_bool_latency" { + // Measure: cycles for trit to boolean conversion + // Target: < 5 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = trit_to_bool(.pos); + } +} + +bench "bench_trit_abs_diff_latency" { + // Measure: cycles for absolute difference + // Target: < 10 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_abs_diff(.pos, .neg); + } +} + +bench "bench_trit_cond_swap_latency" { + // Measure: cycles for conditional swap + // Target: < 10 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_cond_swap(.pos, .neg, .zero); + } +} + +bench "bench_trit_is_unit_latency" { + // Measure: cycles for unit check + // Target: < 5 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = trit_is_unit(.pos); + } +} + +bench "bench_trit_is_identity_latency" { + // Measure: cycles for identity check + // Target: < 5 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = trit_is_identity(.zero); + } +} + +bench "bench_trit_is_negated_latency" { + // Measure: cycles for negation check + // Target: < 10 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = trit_is_negated(.neg, .pos); + } +} + diff --git a/apps/website/public/t27/specs/base/ring_32.t27 b/apps/website/public/t27/specs/base/ring_32.t27 new file mode 100644 index 0000000000..b1db8d5ccc --- /dev/null +++ b/apps/website/public/t27/specs/base/ring_32.t27 @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// base/ring_32.t27 — Ring 32 Definition +// φ² + 1/φ² = 3 | TRINITY + +module base-ring-32; + +use math::sacred_physics::{PHI, PHI_INV, TRINITY}; + +pub const RING_NUMBER : u8 = 32; +pub const RING_CAPABILITY : str = "Cloud Orchestration"; +pub const RING_32_SPECS : [4]str = [ + "specs/cloud/railway_deploy.t27", + "specs/base/debounce.t27", + "specs/queen/task_analysis.t27", + "specs/compiler/mod_structure.t27", +]; + +test "ring_number_is_32" { + assert RING_NUMBER == 32; +} + +invariant "ring_32_has_4_specs" { + assert RING_32_SPECS.len == 4; +} diff --git a/apps/website/public/t27/specs/base/seed.t27 b/apps/website/public/t27/specs/base/seed.t27 new file mode 100644 index 0000000000..e4241a6fde --- /dev/null +++ b/apps/website/public/t27/specs/base/seed.t27 @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +; seed.t27 -- Minimal Golden Seed for E2E CI (#150) +; phi^2 + 1/phi^2 = 3 | TRINITY + +module seed; + +// ============================================================================ +// Constants - Trit Values (Balanced Ternary) +// ============================================================================ + +pub const NEGONE : i8 = -1; +pub const ZERO : i8 = 0; +pub const ONE : i8 = 1; + +// Trit enum for type safety +pub const Trit = enum(i8) { + neg = -1, + zero = 0, + pos = 1, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +// trit_negate(a: Trit) -> Trit +// Negate a trit: -1 -> +1, 0 -> 0, +1 -> -1 +pub fn trit_negate(a: Trit) Trit { + return switch (a) { + .neg => .pos, + .zero => .zero, + .pos => .neg, + }; +} + +// trit_add(a: Trit, b: Trit) -> Trit +// Balanced ternary addition +pub fn trit_add(a: Trit, b: Trit) Trit { + return switch (a) { + .neg => switch (b) { + .neg => .neg, + .zero => .neg, + .pos => .zero, + }, + .zero => b, + .pos => switch (b) { + .neg => .zero, + .zero => .pos, + .pos => .pos, + }, + }; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "test_trit_negate_neg_to_pos" { + try std.testing.expectEqual(@as(Trit, .pos), trit_negate(.neg)); +} + +test "test_trit_negate_zero_to_zero" { + try std.testing.expectEqual(@as(Trit, .zero), trit_negate(.zero)); +} + +test "test_trit_negate_pos_to_neg" { + try std.testing.expectEqual(@as(Trit, .neg), trit_negate(.pos)); +} + +test "test_trit_add_neg_plus_pos_equals_zero" { + try std.testing.expectEqual(@as(Trit, .zero), trit_add(.neg, .pos)); +} + +test "test_trit_add_identity" { + try std.testing.expectEqual(@as(Trit, .neg), trit_add(.zero, .neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_add(.zero, .zero)); + try std.testing.expectEqual(@as(Trit, .pos), trit_add(.zero, .pos)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant trit_value_range { + @compileAssert(@intFromEnum(Trit.neg) == -1); + @compileAssert(@intFromEnum(Trit.zero) == 0); + @compileAssert(@intFromEnum(Trit.pos) == 1); +} + +invariant trit_negate_involutive { + @compileAssert(true); +} diff --git a/apps/website/public/t27/specs/base/ternary_add.t27 b/apps/website/public/t27/specs/base/ternary_add.t27 new file mode 100644 index 0000000000..2cd8527637 --- /dev/null +++ b/apps/website/public/t27/specs/base/ternary_add.t27 @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: Apache-2.0 +// ternary_add.t27 -- Balanced Ternary Addition Formal Spec +// Ring 043 -- Formal carry propagation invariants, closure, range formula +// phi^2 + 1/phi^2 = 3 | TRINITY + +module ternary_add; + +use base::types; + +// ============================================================================ +// 1. Full Adder for Balanced Ternary +// ============================================================================ + +// FullAdderResult: Result of trit addition with carry +pub struct FullAdderResult { + sum : Trit, + carry_out : Trit, +} + +// trit_full_adder(a: Trit, b: Trit, c_in: Trit) -> FullAdderResult +// Full ternary adder with carry propagation +// Computes: sum = a + b + c_in (mod 3, mapped to {-1, 0, +1}) +// carry_out = -1 if a+b+c_in < -1, +1 if > +1, else 0 +pub fn trit_full_adder(a: Trit, b: Trit, c_in: Trit) FullAdderResult { + // Raw sum in [-3, +3] + const raw: i8 = @intFromEnum(a) + @intFromEnum(b) + @intFromEnum(c_in); + + // Normalize to trit domain [-1, 0, +1] and compute carry + if (raw > 1) { + // raw = 2 or 3 + // raw = 2 -> sum = -1, carry = +1 (2 = -1 + 3*1) + // raw = 3 -> sum = 0, carry = +1 (3 = 0 + 3*1) + return FullAdderResult{ + .sum = if (raw == 2) .neg else .zero, + .carry_out = .pos, + }; + } else if (raw < -1) { + // raw = -2 or -3 + // raw = -2 -> sum = +1, carry = -1 (-2 = +1 + 3*(-1)) + // raw = -3 -> sum = 0, carry = -1 (-3 = 0 + 3*(-1)) + return FullAdderResult{ + .sum = if (raw == -2) .pos else .zero, + .carry_out = .neg, + }; + } else { + // raw = -1, 0, +1 -> no carry needed + return FullAdderResult{ + .sum = @as(Trit, @enumFromInt(raw)), + .carry_out = .zero, + }; + } +} + +// ============================================================================ +// 2. Range and Capacity Functions +// ============================================================================ + +// max_value(k: u8) -> i64 +// Maximum positive value representable with k trits +// Formula: (3^k - 1) / 2 +pub fn max_value(k: u8) i64 { + var pow3: i64 = 1; + var i: u8 = 0; + while (i < k) : (i += 1) { + pow3 *= 3; + } + return (pow3 - 1) / 2; +} + +// min_value(k: u8) -> i64 +// Minimum negative value representable with k trits +// Formula: -(3^k - 1) / 2 +pub fn min_value(k: u8) i64 { + return -max_value(k); +} + +// total_states(k: u8) -> i64 +// Total number of states representable with k trits +// Formula: 3^k +pub fn total_states(k: u8) i64 { + var result: i64 = 1; + var i: u8 = 0; + while (i < k) : (i += 1) { + result *= 3; + } + return result; +} + +// ============================================================================ +// 3. Trit Multiplication Closure +// ============================================================================ + +// trit_mul_closed(a: Trit, b: Trit) -> Trit +// Trit multiplication is closed: {-1,0,+1} x {-1,0,+1} -> {-1,0,+1} +pub fn trit_mul_closed(a: Trit, b: Trit) Trit { + return switch (a) { + .neg => switch (b) { + .neg => .pos, // (-1) * (-1) = +1 + .zero => .zero, + .pos => .neg, // (-1) * (+1) = -1 + }, + .zero => .zero, // 0 * x = 0 + .pos => b, // (+1) * x = x + }; +} + +// verify_trit_mul_closure() -> bool +// Verify that trit multiplication is closed +pub fn verify_trit_mul_closure() bool { + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |a| { + for (trits) |b| { + const result = trit_mul_closed(a, b); + // Result must be in {-1, 0, +1} + const val = @intFromEnum(result); + if (val < -1 or val > 1) { + return false; + } + } + } + return true; +} + +// ============================================================================ +// 4. Carry Propagation Rules +// ============================================================================ + +// carry_sign(sum: i8) -> Trit +// Determine carry sign from raw sum +// Returns +1 if sum > 1, -1 if sum < -1, else 0 +pub fn carry_sign(sum: i8) Trit { + return if (sum > 1) .pos else if (sum < -1) .neg else .zero; +} + +// normalize_trit(raw: i8) -> Trit +// Normalize raw sum to trit domain [-1, 0, +1] +// Uses modulo 3 arithmetic: raw mod 3 mapped to {-1, 0, +1} +pub fn normalize_trit(raw: i8) Trit { + const mod3: i8 = @rem(raw, 3); + return if (mod3 == 2) .neg else if (mod3 == -2) .pos else @as(Trit, @enumFromInt(mod3)); +} + +// ============================================================================ +// 5. TDD -- Tests +// ============================================================================ + +test ternary_add_full_adder_pos_pos_zero + // +1 + +1 + 0 = +2 -> sum = -1, carry = +1 + given result = trit_full_adder(.pos, .pos, .zero) + then result.sum == .neg and result.carry_out == .pos + +test ternary_add_full_adder_neg_neg_zero + // -1 + -1 + 0 = -2 -> sum = +1, carry = -1 + given result = trit_full_adder(.neg, .neg, .zero) + then result.sum == .pos and result.carry_out == .neg + +test ternary_add_full_adder_pos_neg_zero + // +1 + -1 + 0 = 0 -> sum = 0, carry = 0 + given result = trit_full_adder(.pos, .neg, .zero) + then result.sum == .zero and result.carry_out == .zero + +test ternary_add_full_adder_pos_pos_pos + // +1 + +1 + +1 = +3 -> sum = 0, carry = +1 + given result = trit_full_adder(.pos, .pos, .pos) + then result.sum == .zero and result.carry_out == .pos + +test ternary_add_full_adder_neg_neg_neg + // -1 + -1 + -1 = -3 -> sum = 0, carry = -1 + given result = trit_full_adder(.neg, .neg, .neg) + then result.sum == .zero and result.carry_out == .neg + +test ternary_add_full_adder_identity_zero + // 0 + 0 + 0 = 0 + given result = trit_full_adder(.zero, .zero, .zero) + then result.sum == .zero and result.carry_out == .zero + +test ternary_add_full_adder_pos_zero_zero + // +1 + 0 + 0 = +1 + given result = trit_full_adder(.pos, .zero, .zero) + then result.sum == .pos and result.carry_out == .zero + +test ternary_add_full_adder_neg_zero_zero + // -1 + 0 + 0 = -1 + given result = trit_full_adder(.neg, .zero, .zero) + then result.sum == .neg and result.carry_out == .zero + +test ternary_add_full_adder_commutative + // trit_full_adder(a, b, c) == trit_full_adder(b, a, c) + given r1 = trit_full_adder(.pos, .neg, .zero) + and r2 = trit_full_adder(.neg, .pos, .zero) + then r1.sum == r2.sum and r1.carry_out == r2.carry_out + +test ternary_add_full_adder_all_27_combinations + // Verify all 27 combinations produce valid results + given all_valid = true // Computed at compile time + then all_valid == true + +test ternary_add_range_k1 + // k=1: range [-(3^1-1)/2, +(3^1-1)/2] = [-1, +1] + given max1 = max_value(1) + and min1 = min_value(1) + then max1 == 1 and min1 == -1 + +test ternary_add_range_k2 + // k=2: range [-(3^2-1)/2, +(3^2-1)/2] = [-4, +4] + given max2 = max_value(2) + and min2 = min_value(2) + then max2 == 4 and min2 == -4 + +test ternary_add_range_k3 + // k=3: range [-(3^3-1)/2, +(3^3-1)/2] = [-13, +13] + given max3 = max_value(3) + and min3 = min_value(3) + then max3 == 13 and min3 == -13 + +test ternary_add_range_k27 + // k=27: max value for Coptic word + given max27 = max_value(27) + and min27 = min_value(27) + then max27 == 3936808944 and min27 == -3936808944 + +test ternary_add_total_states_k1 + // 3^1 = 3 states + given states = total_states(1) + then states == 3 + +test ternary_add_total_states_k2 + // 3^2 = 9 states + given states = total_states(2) + then states == 9 + +test ternary_add_total_states_k27 + // 3^27 states for Coptic word + given states = total_states(27) + then states == 7625597484987 + +test ternary_add_trit_mul_closure_all + // Trit multiplication is closed + given closed = verify_trit_mul_closure() + then closed == true + +test ternary_add_trit_mul_pos_pos + // +1 * +1 = +1 + given result = trit_mul_closed(.pos, .pos) + then result == .pos + +test ternary_add_trit_mul_neg_neg + // -1 * -1 = +1 + given result = trit_mul_closed(.neg, .neg) + then result == .pos + +test ternary_add_trit_mul_pos_neg + // +1 * -1 = -1 + given result = trit_mul_closed(.pos, .neg) + then result == .neg + +test ternary_add_trit_mul_zero_annihilates + // 0 * x = 0 for all x + given r1 = trit_mul_closed(.zero, .pos) + and r2 = trit_mul_closed(.zero, .neg) + and r3 = trit_mul_closed(.zero, .zero) + then r1 == .zero and r2 == .zero and r3 == .zero + +test ternary_add_carry_sign_positive_overflow + // carry_sign(2) = +1 + given carry = carry_sign(2) + then carry == .pos + +test ternary_add_carry_sign_negative_overflow + // carry_sign(-2) = -1 + given carry = carry_sign(-2) + then carry == .neg + +test ternary_add_carry_sign_no_overflow + // carry_sign(0) = carry_sign(1) = carry_sign(-1) = 0 + given c0 = carry_sign(0) + and c1 = carry_sign(1) + and c_neg1 = carry_sign(-1) + then c0 == .zero and c1 == .zero and c_neg1 == .zero + +test ternary_add_normalize_trit_positive + // normalize_trit(2) = -1 (2 mod 3 = 2, maps to -1) + given norm = normalize_trit(2) + then norm == .neg + +test ternary_add_normalize_trit_negative + // normalize_trit(-2) = +1 (-2 mod 3 = -2, maps to +1) + given norm = normalize_trit(-2) + then norm == .pos + +test ternary_add_normalize_trit_zero + // normalize_trit(0) = 0 + given norm = normalize_trit(0) + then norm == .zero + +test ternary_add_carry_propagation_chain + // Carry propagates through multiple trits + // 1 + 1 + 0 -> sum=-1, carry=+1 + // Then sum + carry at next position + given r1 = trit_full_adder(.pos, .pos, .zero) + and r2 = trit_full_adder(r1.sum, .zero, r1.carry_out) + then r1.carry_out == .pos and r2.sum == .zero + +test ternary_add_max_value_symmetric + // max_value(k) == -min_value(k) + given max2 = max_value(2) + and min2 = min_value(2) + then max2 == -min2 + +test ternary_add_full_adder_zero_identity + // trit_full_adder(a, 0, 0) == a + given r1 = trit_full_adder(.pos, .zero, .zero) + and r2 = trit_full_adder(.neg, .zero, .zero) + and r3 = trit_full_adder(.zero, .zero, .zero) + then r1.sum == .pos and r2.sum == .neg and r3.sum == .zero + +test ternary_add_full_adder_associative_no_carry + // (a + b) + c = a + (b + c) when no carries generated + given r1 = trit_full_adder(.pos, .neg, .zero) + and r2 = trit_full_adder(r1.sum, .zero, .zero) + and r3 = trit_full_adder(.neg, .zero, .zero) + and r4 = trit_full_adder(.pos, r3.sum, .zero) + then r2.sum == r4.sum + +// ============================================================================ +// 6. TDD -- Invariants +// ============================================================================ + +invariant ternary_add_trit_mul_closure + // Trit multiplication is closed: {-1,0,+1} x {-1,0,+1} -> {-1,0,+1} + // Rationale: Fundamental algebraic property of balanced ternary + assert verify_trit_mul_closure() == true + +invariant ternary_add_trit_mul_commutative + // trit_mul_closed(a, b) == trit_mul_closed(b, a) + // Rationale: Multiplication is commutative in trit domain + assert trit_mul_closed(a, b) == trit_mul_closed(b, a) for all Trit a, b + +invariant ternary_add_range_formula + // max_value(k) == (3^k - 1) / 2 for all k + // Rationale: Balanced ternary range formula + assert max_value(k) == (pow(3, k) - 1) / 2 for all k in u8 where k <= 27 + +invariant ternary_add_range_symmetric + // max_value(k) == -min_value(k) for all k + // Rationale: Balanced ternary is symmetric around zero + assert max_value(k) == -min_value(k) for all k in u8 where k > 0 + +invariant ternary_add_total_states_power_of_three + // total_states(k) == 3^k for all k + // Rationale: Each trit has 3 states, k trits have 3^k combinations + assert total_states(k) == pow(3, k) for all k in u8 where k <= 27 + +invariant ternary_add_full_adder_sum_in_range + // trit_full_adder result sum is always in {-1, 0, +1} + // Rationale: Full adder normalizes to trit domain + assert trit_full_adder(a, b, c).sum in {-1, 0, +1} for all Trit a, b, c + +invariant ternary_add_full_adder_carry_in_range + // trit_full_adder carry_out is always in {-1, 0, +1} + // Rationale: Carry is also a trit + assert trit_full_adder(a, b, c).carry_out in {-1, 0, +1} for all Trit a, b, c + +invariant ternary_add_carry_sign_matches_range + // carry_sign(s) returns +1 if s > 1, -1 if s < -1, else 0 + // Rationale: Carry detection from raw sum + assert carry_sign(s) == .pos implies s > 1 + assert carry_sign(s) == .neg implies s < -1 + assert carry_sign(s) == .zero implies -1 <= s <= 1 + +invariant ternary_add_normalize_trit_idempotent + // normalize_trit(normalize_trit(x)) == normalize_trit(x) + // Rationale: Normalizing twice gives same result + assert normalize_trit(normalize_trit(x)) == normalize_trit(x) for all i8 x + +invariant ternary_add_full_adder_zero_identity + // trit_full_adder(a, 0, 0).sum == a for all a + // Rationale: Zero is additive identity + assert trit_full_adder(a, .zero, .zero).sum == a for all Trit a + +invariant ternary_add_full_adder_commutative + // trit_full_adder(a, b, c) == trit_full_adder(b, a, c) + // Rationale: Addition is commutative + assert trit_full_adder(a, b, c).sum == trit_full_adder(b, a, c).sum for all Trit a, b, c + +// ============================================================================ +// 7. TDD -- Benchmarks +// ============================================================================ + +bench ternary_add_full_adder_latency + // Measure: nanoseconds to compute trit_full_adder + // Target: < 50ns + measure: nanoseconds to compute trit_full_adder(.pos, .neg, .zero) + target: < 50ns + +bench ternary_add_max_value_latency + // Measure: nanoseconds to compute max_value(k=27) + // Target: < 200ns (requires 3^27 computation) + measure: nanoseconds to compute max_value(27) + target: < 200ns + +bench ternary_add_trit_mul_closed_latency + // Measure: nanoseconds to compute trit_mul_closed + // Target: < 20ns + measure: nanoseconds to compute trit_mul_closed(.pos, .neg) + target: < 20ns + +bench ternary_add_carry_chain_latency + // Measure: nanoseconds for 27-trit carry chain + // Target: < 1500ns + measure: nanoseconds to propagate carry through 27 trits + target: < 1500ns diff --git a/apps/website/public/t27/specs/base/ternary_encoding.t27 b/apps/website/public/t27/specs/base/ternary_encoding.t27 new file mode 100644 index 0000000000..befac2bced --- /dev/null +++ b/apps/website/public/t27/specs/base/ternary_encoding.t27 @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/base/ternary_encoding.t27 +// Ternary Encoding/Decoding Specification +// Ring 065 - Encoding schemes for ternary data representation +// Defines how binary data maps to ternary and vice versa +// phi^2 + 1/phi^2 = 3 | TRINITY + +module TernaryEncoding { + use base::types; + + // ===================================================== + // 1. Encoding Constants + // ========================================================================= + + // Trit values + const TRIT_NEG : i32 = -1; + const TRIT_ZERO : i32 = 0; + const TRIT_POS : i32 = 1; + + // Encoding schemes + const ENCODING_BALANCED : u8 = 0; // Balanced ternary (-1, 0, 1) + const ENCODING_UNIPOLAR : u8 = 1; // Unipolar ternary (0, 1, 2) + const ENCODING_BCT : u8 = 2; // Binary Coded Ternary + + // Grouping sizes + const BITS_PER_BYTE : usize = 8; + const TRITS_PER_BYTE : usize = 6; // 6 trits = 3^6 = 729 > 256 + const TRITS_PER_NYBBLE : usize = 3; // 3 trits = 27 > 16 + + // ===================================================== + // 2. Bit to Trit Conversion + // ========================================================================= + + // bit_to_trit_pair(bit: u8) -> [2]i32 + // Convert a single bit (0 or 1) to a pair of trits + // 0 -> [0, 0], 1 -> [1, 0] + fn bit_to_trit_pair(bit: u8) -> [2]i32 { + if (bit == 0) { + return [_]i32{TRIT_ZERO, TRIT_ZERO}; + } + return [_]i32{TRIT_POS, TRIT_ZERO}; + } + + // bits_to_trits(bits: u8) -> [3]i32 + // Convert a nibble (4 bits) to 3 trits + // Maps 0-15 to balanced ternary + fn bits_to_trits(bits: u8) -> [3]i32 { + var result : [3]i32 = [_]i32{TRIT_ZERO, TRIT_ZERO, TRIT_ZERO}; + var value = bits; + var i : usize = 0; + + while (i < 3 and value > 0) { + const rem = @rem(value, 3); + if (rem == 2) { + result[i] = TRIT_NEG; + value = value / 3 + 1; + } else { + result[i] = @as(i32, @intCast(rem)); + value = value / 3; + } + i = i + 1; + } + + return result; + } + + // ===================================================== + // 3. Trit to Bit Conversion + // ========================================================================= + + // trits_to_bits(trits: [3]i32) -> u8 + // Convert 3 trits to a nibble (4 bits) + // Assumes trits represent a valid value 0-15 + fn trits_to_bits(trits: [3]i32) -> u8 { + var result : i32 = 0; + var power : i32 = 1; + var i : usize = 0; + + while (i < 3) { + var trit_val = trits[i]; + // Convert negative to positive for unipolar representation + if (trit_val == TRIT_NEG) { + trit_val = 2; + } + result = result + trit_val * power; + power = power * 3; + i = i + 1; + } + + return @as(u8, @intCast(result)); + } + + // ===================================================== + // 4. Byte to Trit Array Conversion + // ========================================================================= + + // byte_to_trits(byte: u8, trits: []i32, len: usize) -> usize + // Convert a byte to trits + // Returns number of trits used + fn byte_to_trits(byte: u8, trits: []i32, len: usize) -> usize { + // Convert byte (0-255) to balanced ternary + var value : i32 = @as(i32, @intCast(byte)); + var i : usize = 0; + + while (i < len and value != 0) { + const rem = @rem(value, 3); + + if (rem == 2) { + trits[i] = TRIT_NEG; + value = value / 3 + 1; + } else if (rem == -2) { + trits[i] = TRIT_POS; + value = value / 3 - 1; + } else { + trits[i] = rem; + value = value / 3; + } + + i = i + 1; + } + + // Pad remaining trits with zero + while (i < len) { + trits[i] = TRIT_ZERO; + i = i + 1; + } + + return len; + } + + // trits_to_byte(trits: []i32, len: usize) -> u8 + // Convert trits to a byte + // Assumes trits represent a valid value 0-255 + fn trits_to_byte(trits: []i32, len: usize) -> u8 { + var result : i32 = 0; + var power : i32 = 1; + var i : usize = 0; + + while (i < len) { + var trit_val = trits[i]; + // Convert balanced to unipolar + if (trit_val == TRIT_NEG) { + trit_val = 2; + } + result = result + trit_val * power; + power = power * 3; + i = i + 1; + } + + return @as(u8, @intCast(result)); + } + + // ===================================================== + // 5. Balanced to Unipolar Conversion + // ========================================================================= + + // balanced_to_unipolar(trit: i32) -> i32 + // Convert balanced ternary trit to unipolar + // -1 -> 0, 0 -> 1, 1 -> 2 + fn balanced_to_unipolar(trit: i32) -> i32 { + return trit + 1; + } + + // unipolar_to_balanced(trit: i32) -> i32 + // Convert unipolar trit to balanced ternary + // 0 -> -1, 1 -> 0, 2 -> 1 + fn unipolar_to_balanced(trit: i32) -> i32 { + return trit - 1; + } + + // ===================================================== + // 6. String Encoding + // ========================================================================= + + // char_to_trits(c: u8, trits: []i32, len: usize) -> usize + // Convert ASCII character to trits + fn char_to_trits(c: u8, trits: []i32, len: usize) -> usize { + return byte_to_trits(c, trits, len); + } + + // trits_to_char(trits: []i32, len: usize) -> u8 + // Convert trits to ASCII character + fn trits_to_char(trits: []i32, len: usize) -> u8 { + return trits_to_byte(trits, len); + } + + // ===================================================== + // 7. Validation + // ========================================================================= + + // is_valid_trit(t: i32) -> bool + // Check if a value is a valid trit + fn is_valid_trit(t: i32) -> bool { + return t >= TRIT_NEG and t <= TRIT_POS; + } + + // is_valid_unipolar_trit(t: i32) -> bool + // Check if a value is a valid unipolar trit (0, 1, 2) + fn is_valid_unipolar_trit(t: i32) -> bool { + return t >= 0 and t <= 2; + } + + // validate_trits(trits: []i32, len: usize) -> bool + // Validate all trits in an array + fn validate_trits(trits: []i32, len: usize) -> bool { + var i : usize = 0; + while (i < len) { + if (!is_valid_trit(trits[i])) { + return false; + } + i = i + 1; + } + return true; + } + + // ===================================================== + // 8. Encoding Metadata + // ========================================================================= + + struct EncodingInfo { + encoding_type : u8, + trits_used : usize, + byte_value : u8, + is_valid : bool, + } + + // get_encoding_info(trits: []i32, len: usize) -> EncodingInfo + // Get information about a trit encoding + fn get_encoding_info(trits: []i32, len: usize) -> EncodingInfo { + var info : EncodingInfo = undefined; + info.encoding_type = ENCODING_BALANCED; + info.trits_used = len; + info.is_valid = validate_trits(trits, len); + + if (info.is_valid) { + info.byte_value = trits_to_byte(trits, len); + } else { + info.byte_value = 0; + } + + return info; + } + + // ===================================================== + // 9. TDD - Tests + // ========================================================================= + + test bit_to_trit_pair_zero + const result = bit_to_trit_pair(0); + assert result[0] == TRIT_ZERO + assert result[1] == TRIT_ZERO + + test bit_to_trit_pair_one + const result = bit_to_trit_pair(1); + assert result[0] == TRIT_POS + assert result[1] == TRIT_ZERO + + test bits_to_trits_zero + const result = bits_to_trits(0); + assert result[0] == TRIT_ZERO + assert result[1] == TRIT_ZERO + assert result[2] == TRIT_ZERO + + test bits_to_trits_max_nibble + const result = bits_to_trits(15); + const decoded = trits_to_bits(result); + assert decoded == 15 + + test trits_to_bits_roundtrip + var i : u8 = 0; + while (i < 16) { + const trits = bits_to_trits(i); + const decoded = trits_to_bits(trits); + assert decoded == i + i = i + 1; + } + + test byte_to_trits_roundtrip + var i : u8 = 0; + var trits : [10]i32 = undefined; + while (i < 10) { + byte_to_trits(i, &trits, 10); + const decoded = trits_to_byte(&trits, 10); + assert decoded == i + i = i + 1; + } + + test balanced_unipolar_conversion + assert balanced_to_unipolar(TRIT_NEG) == 0 + assert balanced_to_unipolar(TRIT_ZERO) == 1 + assert balanced_to_unipolar(TRIT_POS) == 2 + + assert unipolar_to_balanced(0) == TRIT_NEG + assert unipolar_to_balanced(1) == TRIT_ZERO + assert unipolar_to_balanced(2) == TRIT_POS + + test is_valid_trit_check + assert is_valid_trit(TRIT_NEG) == true + assert is_valid_trit(TRIT_ZERO) == true + assert is_valid_trit(TRIT_POS) == true + assert is_valid_trit(2) == false + assert is_valid_trit(-2) == false + + test is_valid_unipolar_trit_check + assert is_valid_unipolar_trit(0) == true + assert is_valid_unipolar_trit(1) == true + assert is_valid_unipolar_trit(2) == true + assert is_valid_unipolar_trit(3) == false + assert is_valid_unipolar_trit(-1) == false + + test char_encoding_roundtrip + var test_chars : [5]u8 = [_]u8{'A', '0', ' ', '\n', 127}; + var trits : [10]i32 = undefined; + var i : usize = 0; + while (i < 5) { + char_to_trits(test_chars[i], &trits, 10); + const decoded = trits_to_char(&trits, 10); + assert decoded == test_chars[i] + i = i + 1; + } + + // ===================================================== + // 10. TDD - Invariants + // ========================================================================= + + invariant balanced_unipolar_roundtrip + // Converting balanced -> unipolar -> balanced should be identity + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + var i : usize = 0; + while (i < 3) { + const uni = balanced_to_unipolar(vals[i]); + const balanced = unipolar_to_balanced(uni); + assert balanced == vals[i] + i = i + 1; + } + + invariant byte_trits_roundtrip + // byte -> trits -> byte should be identity for all bytes + var trits : [10]i32 = undefined; + var byte_val : u8 = 0; + while (true) { + byte_to_trits(byte_val, &trits, 10); + const decoded = trits_to_byte(&trits, 10); + assert decoded == byte_val + + if (byte_val == 255) { break; } + byte_val = byte_val + 1; + } + + invariant bits_trits_roundtrip + // bits -> trits -> bits should be identity for 0-15 + var i : u8 = 0; + while (i < 16) { + const trits = bits_to_trits(i); + const decoded = trits_to_bits(trits); + assert decoded == i + i = i + 1; + } + + invariant encoding_info_validity + // Encoding info should reflect validity correctly + var valid_trits : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG}; + var invalid_trits : [3]i32 = [_]i32{2, 0, 1}; + + const valid_info = get_encoding_info(&valid_trits, 3); + const invalid_info = get_encoding_info(&invalid_trits, 3); + + assert valid_info.is_valid == true + assert invalid_info.is_valid == false + + invariant trits_used_in_encoding_info + // Encoding info should report correct trits used + var trits : [5]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_ZERO, TRIT_POS}; + const info = get_encoding_info(&trits, 5); + assert info.trits_used == 5 + + // ===================================================== + // 11. TDD - Benchmarks + // ========================================================================= + + bench byte_to_trits_performance + // Measure: cycles to convert 100 bytes to trits + // Target: < 3000 cycles + var trits : [10]i32 = undefined; + @setEvalBranchQuota(10000); + for (0..100) |i| { + byte_to_trits(@as(u8, @intCast(i % 256)), &trits, 10); + } + + bench trits_to_byte_performance + // Measure: cycles to convert 100 trit arrays to bytes + // Target: < 2000 cycles + var trits : [10]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS}; + @setEvalBranchQuota(10000); + var result : u8 = 0; + for (0..100) |_| { + result = trits_to_byte(&trits, 10); + } + _ = result; + + bench balanced_unipolar_performance + // Measure: cycles to convert 1000 trits between representations + // Target: < 500 cycles + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + @setEvalBranchQuota(10000); + var result : i32 = 0; + var idx : usize = 0; + for (0..1000) |_| { + const uni = balanced_to_unipolar(vals[idx % 3]); + result = unipolar_to_balanced(uni); + idx = idx + 1; + } + _ = result; +} diff --git a/apps/website/public/t27/specs/base/ternary_memory.t27 b/apps/website/public/t27/specs/base/ternary_memory.t27 new file mode 100644 index 0000000000..927f8a0aba --- /dev/null +++ b/apps/website/public/t27/specs/base/ternary_memory.t27 @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/base/ternary_memory.t27 +// Ternary Memory Specification +// Ring 066 - Ternary memory cell and array operations +// Defines how trits are stored and accessed in memory +// phi^2 + 1/phi^2 = 3 | TRINITY + +module TernaryMemory { + use base::types; + + // ===================================================== + // 1. Memory Constants + // ========================================================================= + + // Trit values + const TRIT_NEG : i32 = -1; + const TRIT_ZERO : i32 = 0; + const TRIT_POS : i32 = 1; + + // Memory configuration + const TRIT_CAPACITY : usize = 27; // 27 trits per word + const WORD_CAPACITY : usize = 1024; // 1024 words + const PAGE_SIZE : usize = 256; // 256 words per page + + // Memory states + const STATE_FREE : u8 = 0; + const STATE_ALLOCATED : u8 = 1; + const STATE_LOCKED : u8 = 2; + const STATE_DIRTY : u8 = 3; + + // ===================================================== + // 2. Trit Memory Cell + // ========================================================================= + + // TritCell: Single trit storage with metadata + struct TritCell { + value : i32, + state : u8, + last_access : u64, + access_count : u32, + } + + // trit_cell_init() -> TritCell + // Initialize a trit cell + fn trit_cell_init() -> TritCell { + return TritCell{ + .value = TRIT_ZERO, + .state = STATE_FREE, + .last_access = 0, + .access_count = 0, + }; + } + + // trit_cell_write(cell: *TritCell, value: i32) -> bool + // Write a value to a trit cell + fn trit_cell_write(cell: *TritCell, value: i32) -> bool { + if (cell.state == STATE_LOCKED) { + return false; + } + cell.value = value; + cell.state = STATE_DIRTY; + cell.last_access = get_timestamp(); + cell.access_count = cell.access_count + 1; + return true; + } + + // trit_cell_read(cell: *TritCell) -> i32 + // Read a value from a trit cell + fn trit_cell_read(cell: *TritCell) -> i32 { + cell.last_access = get_timestamp(); + cell.access_count = cell.access_count + 1; + return cell.value; + } + + // ===================================================== + // 3. Ternary Word + // ========================================================================= + + // TernaryWord: 27 trits + struct TernaryWord { + trits : [TRIT_CAPACITY]TritCell, + state : u8, + checksum : u32, + } + + // ternary_word_init() -> TernaryWord + // Initialize a ternary word + fn ternary_word_init() -> TernaryWord { + var word : TernaryWord = undefined; + word.state = STATE_FREE; + word.checksum = 0; + + var i : usize = 0; + while (i < TRIT_CAPACITY) { + word.trits[i] = trit_cell_init(); + i = i + 1; + } + + return word; + } + + // ternary_word_write_trit(word: *TernaryWord, index: usize, value: i32) -> bool + // Write a single trit in a word + fn ternary_word_write_trit(word: *TernaryWord, index: usize, value: i32) -> bool { + if (index >= TRIT_CAPACITY) { + return false; + } + if (word.state == STATE_LOCKED) { + return false; + } + + const result = trit_cell_write(&word.trits[index], value); + if (result) { + word.state = STATE_DIRTY; + word.checksum = compute_checksum(word); + } + return result; + } + + // ternary_word_read_trit(word: *TernaryWord, index: usize) -> i32 + // Read a single trit from a word + fn ternary_word_read_trit(word: *TernaryWord, index: usize) -> i32 { + if (index >= TRIT_CAPACITY) { + return TRIT_ZERO; + } + return trit_cell_read(&word.trits[index]); + } + + // compute_checksum(word: *TernaryWord) -> u32 + // Compute checksum for a word + fn compute_checksum(word: *TernaryWord) -> u32 { + var checksum : u32 = 0; + var i : usize = 0; + + while (i < TRIT_CAPACITY) { + const val = @as(u32, @intCast(word.trits[i].value)); + checksum = checksum + val * @as(u32, @intCast(i + 1)); + i = i + 1; + } + + return checksum; + } + + // ===================================================== + // 4. Ternary Memory Bank + // ========================================================================= + + // TernaryMemoryBank: Array of ternary words + struct TernaryMemoryBank { + words : [WORD_CAPACITY]TernaryWord, + allocated : usize, + total_accesses : u64, + } + + // ternary_memory_bank_init() -> TernaryMemoryBank + // Initialize a memory bank + fn ternary_memory_bank_init() -> TernaryMemoryBank { + var bank : TernaryMemoryBank = undefined; + bank.allocated = 0; + bank.total_accesses = 0; + + var i : usize = 0; + while (i < WORD_CAPACITY) { + bank.words[i] = ternary_word_init(); + i = i + 1; + } + + return bank; + } + + // ternary_memory_alloc(bank: *TernaryMemoryBank) -> usize + // Allocate a word in memory bank + // Returns word index or -1 if full + fn ternary_memory_alloc(bank: *TernaryMemoryBank) -> usize { + if (bank.allocated >= WORD_CAPACITY) { + return 0xFFFFFFFFFFFFFFFF; // -1 in usize + } + + const index = bank.allocated; + bank.words[index].state = STATE_ALLOCATED; + bank.allocated = bank.allocated + 1; + return index; + } + + // ternary_memory_free(bank: *TernaryMemoryBank, index: usize) -> bool + // Free a word in memory bank + fn ternary_memory_free(bank: *TernaryMemoryBank, index: usize) -> bool { + if (index >= bank.allocated) { + return false; + } + bank.words[index].state = STATE_FREE; + return true; + } + + // ternary_memory_read(bank: *TernaryMemoryBank, word_index: usize, trit_index: usize) -> i32 + // Read a trit from memory bank + fn ternary_memory_read(bank: *TernaryMemoryBank, word_index: usize, trit_index: usize) -> i32 { + bank.total_accesses = bank.total_accesses + 1; + + if (word_index >= bank.allocated) { + return TRIT_ZERO; + } + + return ternary_word_read_trit(&bank.words[word_index], trit_index); + } + + // ternary_memory_write(bank: *TernaryMemoryBank, word_index: usize, trit_index: usize, value: i32) -> bool + // Write a trit to memory bank + fn ternary_memory_write(bank: *TernaryMemoryBank, word_index: usize, trit_index: usize, value: i32) -> bool { + bank.total_accesses = bank.total_accesses + 1; + + if (word_index >= bank.allocated) { + return false; + } + + return ternary_word_write_trit(&bank.words[word_index], trit_index, value); + } + + // ===================================================== + // 5. Helper Functions + // ========================================================================= + + // get_timestamp() -> u64 + // Get current timestamp (placeholder) + fn get_timestamp() -> u64 { + return 0; + } + + // validate_trit(value: i32) -> bool + // Validate trit value + fn validate_trit(value: i32) -> bool { + return value >= TRIT_NEG and value <= TRIT_POS; + } + + // ===================================================== + // 6. TDD - Tests + // ========================================================================= + + test trit_cell_initialization + const cell = trit_cell_init(); + assert cell.value == TRIT_ZERO + assert cell.state == STATE_FREE + assert cell.access_count == 0 + + test trit_cell_write_read + var cell = trit_cell_init(); + assert trit_cell_write(&cell, TRIT_POS) == true + assert trit_cell_read(&cell) == TRIT_POS + assert trit_cell_write(&cell, TRIT_NEG) == true + assert trit_cell_read(&cell) == TRIT_NEG + + test trit_cell_locked + var cell = trit_cell_init(); + cell.state = STATE_LOCKED; + assert trit_cell_write(&cell, TRIT_POS) == false + assert trit_cell_read(&cell) == TRIT_ZERO + + test ternary_word_initialization + const word = ternary_word_init(); + assert word.state == STATE_FREE + assert word.checksum == 0 + + test ternary_word_write_read_trit + var word = ternary_word_init(); + assert ternary_word_write_trit(&word, 0, TRIT_POS) == true + assert ternary_word_read_trit(&word, 0) == TRIT_POS + + assert ternary_word_write_trit(&word, 10, TRIT_NEG) == true + assert ternary_word_read_trit(&word, 10) == TRIT_NEG + + test ternary_word_bounds_check + var word = ternary_word_init(); + assert ternary_word_write_trit(&word, 100, TRIT_POS) == false + assert ternary_word_read_trit(&word, 100) == TRIT_ZERO + + test ternary_memory_bank_initialization + const bank = ternary_memory_bank_init(); + assert bank.allocated == 0 + assert bank.total_accesses == 0 + + test ternary_memory_alloc_free + var bank = ternary_memory_bank_init(); + + const index1 = ternary_memory_alloc(&bank); + assert index1 == 0 + assert bank.allocated == 1 + + const index2 = ternary_memory_alloc(&bank); + assert index2 == 1 + assert bank.allocated == 2 + + assert ternary_memory_free(&bank, 0) == true + assert bank.words[0].state == STATE_FREE + + test ternary_memory_write_read + var bank = ternary_memory_bank_init(); + const index = ternary_memory_alloc(&bank); + + assert ternary_memory_write(&bank, index, 0, TRIT_POS) == true + assert ternary_memory_read(&bank, index, 0) == TRIT_POS + + // ===================================================== + // 7. TDD - Invariants + // ========================================================================= + + invariant trit_cell_read_increments_count + // Reading a cell should increment access count + var cell = trit_cell_init(); + const before = cell.access_count; + _ = trit_cell_read(&cell); + assert cell.access_count == before + 1 + + invariant trit_cell_write_increments_count + // Writing a cell should increment access count + var cell = trit_cell_init(); + const before = cell.access_count; + _ = trit_cell_write(&cell, TRIT_POS); + assert cell.access_count == before + 1 + + invariant ternary_word_checksum_updates + // Writing a trit should update checksum + var word = ternary_word_init(); + const before = word.checksum; + _ = ternary_word_write_trit(&word, 0, TRIT_POS); + assert word.checksum != before + + invariant memory_bank_allocation_monotonic + // Allocated count should be monotonic increasing + var bank = ternary_memory_bank_init(); + var prev = bank.allocated; + + var i : usize = 0; + while (i < 10) { + _ = ternary_memory_alloc(&bank); + assert bank.allocated >= prev + prev = bank.allocated; + i = i + 1; + } + + invariant memory_bank_access_count_increments + // Every memory access should increment total access count + var bank = ternary_memory_bank_init(); + const index = ternary_memory_alloc(&bank); + + const before = bank.total_accesses; + _ = ternary_memory_read(&bank, index, 0); + assert bank.total_accesses == before + 1 + + const after = bank.total_accesses; + _ = ternary_memory_write(&bank, index, 1, TRIT_POS); + assert bank.total_accesses == after + 1 + + invariant word_state_transitions + // Word state should transition correctly + var word = ternary_word_init(); + assert word.state == STATE_FREE + + _ = ternary_word_write_trit(&word, 0, TRIT_POS); + assert word.state == STATE_DIRTY + + // ===================================================== + // 8. TDD - Benchmarks + // ========================================================================= + + bench trit_cell_write_performance + // Measure: cycles to write 1000 trit cells + // Target: < 500 cycles + var cell = trit_cell_init(); + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + @setEvalBranchQuota(10000); + var result : bool = false; + var idx : usize = 0; + for (0..1000) |_| { + result = trit_cell_write(&cell, vals[idx % 3]); + idx = idx + 1; + } + _ = result; + + bench ternary_word_read_performance + // Measure: cycles to read 1000 trits from word + // Target: < 1000 cycles + var word = ternary_word_init(); + @setEvalBranchQuota(10000); + var result : i32 = 0; + for (0..1000) |i| { + result = ternary_word_read_trit(&word, i % TRIT_CAPACITY); + } + _ = result; + + bench ternary_memory_bank_access_performance + // Measure: cycles to perform 1000 memory accesses + // Target: < 5000 cycles + var bank = ternary_memory_bank_init(); + const index = ternary_memory_alloc(&bank); + @setEvalBranchQuota(10000); + var result : i32 = 0; + for (0..1000) |i| { + if (i % 2 == 0) { + _ = ternary_memory_write(&bank, index, i % TRIT_CAPACITY, TRIT_POS); + } else { + result = ternary_memory_read(&bank, index, i % TRIT_CAPACITY); + } + } + _ = result; + + bench checksum_computation_performance + // Measure: cycles to compute 100 checksums + // Target: < 2000 cycles + var word = ternary_word_init(); + @setEvalBranchQuota(10000); + var checksum : u32 = 0; + for (0..100) |_| { + checksum = compute_checksum(&word); + } + _ = checksum; +} diff --git a/apps/website/public/t27/specs/base/types.t27 b/apps/website/public/t27/specs/base/types.t27 new file mode 100644 index 0000000000..7e27f04729 --- /dev/null +++ b/apps/website/public/t27/specs/base/types.t27 @@ -0,0 +1,1679 @@ +// SPDX-License-Identifier: Apache-2.0 +; types.t27 -- Base Types for t27 Language +; Trit, PackedTrit, TernaryWord definitions +; phi^2 + 1/phi^2 = 3 | TRINITY + +module tritype-base; + +// ============================================================================ +// Constants - Trit Values +// ============================================================================ + +pub const NEGONE : i8 = -1; // Trit = -1 (false in balanced) +pub const ZERO : i8 = 0; // Trit = 0 (unknown in balanced) +pub const ONE : i8 = 1; // Trit = +1 (true in balanced) + +// Trit enum for type safety +pub const Trit = enum(i8) { + neg = -1, + zero = 0, + pos = 1, +}; + +// ============================================================================ +// Constants - PackedTrit +// ============================================================================ + +// PackedTrit: 8 trits packed into u8 +// Layout: [t7 t6 t5 t4 t3 t2 t1 t0] where each t in {-1, 0, +1} +// Encoding: -1 -> 10, 0 -> 00, +1 -> 01 (2 bits per trit) +// So packed byte: [t7_1 t7_0][t6_1 t6_0]...[t0_1 t0_0] +pub const PACKED_BITS_PER_TRIT : u8 = 2; +pub const TRITS_PER_BYTE : u8 = 8; + +// Trit value mapping (2-bit to trit) +// -1 -> 10b = 2, 0 -> 00b = 0, +1 -> 01b = 1 +pub const PACKED_NEG : u8 = 2; +pub const PACKED_ZERO : u8 = 0; +pub const PACKED_ONE : u8 = 1; + +pub const TRIT_MASK : u8 = 0x03; // Lower 2 bits for one trit +pub const PackedTrit = u8; // Type alias + +// ============================================================================ +// Constants - TernaryWord +// ============================================================================ + +// TernaryWord: 27 trits packed (full Coptic word) +// Can represent any value in 3^27 ~= 7.6*10^12 states +// Practical use: vector operations, VSA bindings, weight storage +pub const TRITS_PER_WORD : u8 = 27; +pub const WORD_BYTES : u8 = 5; // ceil(27/8) = 5 bytes for 27 trits + +pub const TernaryWord = [WORD_BYTES]u8; // Type alias + +// ============================================================================ +// Types +// ============================================================================ + +// UnpackResult: Result of unpack operation with error status +pub struct UnpackResult { + value : Trit, + valid : bool, +} + +// ============================================================================ +// Functions +// ============================================================================ + +// trit_add(a: Trit, b: Trit) -> Trit +// Balanced ternary addition +// Truth table: +// -1 + -1 = -1 (carrying overflow handled by TernaryWord) +// -1 + 0 = -1 +// -1 + +1 = 0 +// 0 + -1 = -1 +// 0 + 0 = 0 +// 0 + +1 = +1 +// +1 + -1 = 0 +// +1 + 0 = +1 +// +1 + +1 = +1 +pub fn trit_add(a: Trit, b: Trit) Trit { + return switch (a) { + .neg => switch (b) { + .neg => .neg, + .zero => .neg, + .pos => .zero, + }, + .zero => b, + .pos => switch (b) { + .neg => .zero, + .zero => .pos, + .pos => .pos, + }, + }; +} + +// trit_multiply(a: Trit, b: Trit) -> Trit +// Balanced ternary multiplication +// Truth table: +// -1 * -1 = +1 +// -1 * 0 = 0 +// -1 * +1 = -1 +// 0 * -1 = 0 +// 0 * 0 = 0 +// 0 * +1 = 0 +// +1 * -1 = -1 +// +1 * 0 = 0 +// +1 * +1 = +1 +pub fn trit_multiply(a: Trit, b: Trit) Trit { + return switch (a) { + .neg => switch (b) { + .neg => .pos, + .zero => .zero, + .pos => .neg, + }, + .zero => .zero, + .pos => b, + }; +} + +// trit_negate(a: Trit) -> Trit +// Negate a trit: -1 -> +1, 0 -> 0, +1 -> -1 +// Truth table: +// trit_negate(-1) = +1 +// trit_negate(0) = 0 +// trit_negate(+1) = -1 +pub fn trit_negate(a: Trit) Trit { + return switch (a) { + .neg => .pos, + .zero => .zero, + .pos => .neg, + }; +} + +// trit_to_packed(trit: Trit) -> u8 +// Convert Trit to its 2-bit packed representation +pub fn trit_to_packed(trit: Trit) u8 { + return switch (trit) { + .neg => PACKED_NEG, + .zero => PACKED_ZERO, + .pos => PACKED_ONE, + }; +} + +// packed_to_trit(packed: u8) -> Trit +// Convert 2-bit packed representation to Trit +pub fn packed_to_trit(packed: u8) Trit { + return switch (packed & TRIT_MASK) { + 2 => .neg, + 0 => .zero, + 1 => .pos, + else => .zero, // Should not happen for valid 2-bit values + }; +} + +// pack_trit(trit: Trit, position: u8, packed: PackedTrit) -> PackedTrit +// Pack a single trit into PackedTrit at given position (0-7) +// Returns updated packed value or error sentinel (0xFF) for invalid position +pub fn pack_trit(trit: Trit, position: u8, packed: PackedTrit) PackedTrit { + if (position >= TRITS_PER_BYTE) { + return 0xFF; // Error sentinel + } + + const encoding = trit_to_packed(trit); + const bit_pos: u3 = @intCast(position * PACKED_BITS_PER_TRIT); + + // Clear 2 bits at position + const mask: u8 = ~(TRIT_MASK << bit_pos); + var result = packed & mask; + + // Set new value (OR with encoding shifted) + result |= encoding << bit_pos; + + return result; +} + +// unpack_trit(position: u8, packed: PackedTrit) -> UnpackResult +// Extract a single trit from PackedTrit at given position (0-7) +pub fn unpack_trit(position: u8, packed: PackedTrit) UnpackResult { + if (position >= TRITS_PER_BYTE) { + return UnpackResult{ .value = .zero, .valid = false }; + } + + const bit_pos: u3 = @intCast(position * PACKED_BITS_PER_TRIT); + const encoding = (packed >> bit_pos) & TRIT_MASK; + const value = packed_to_trit(encoding); + + return UnpackResult{ .value = value, .valid = true }; +} + +// ternary_word_pack(src: []Trit, count: u8) -> TernaryWord +// Pack count trits into TernaryWord (max 27 trits) +// Returns error sentinel ([0xFF]*5) for count > 27 +pub fn ternary_word_pack(src: []const Trit, count: u8) TernaryWord { + if (count > TRITS_PER_WORD) { + return [_]u8{0xFF} ** WORD_BYTES; + } + + var result = [_]u8{0} ** WORD_BYTES; + + for (0..@min(count, TRITS_PER_WORD)) |i| { + result = pack_trit(src[i], @intCast(i), result); + } + + return result; +} + +// ternary_word_unpack(word: TernaryWord, count: u8) -> []Trit +// Unpack count trits from TernaryWord +// Returns array of Trit values (error for invalid count) +pub fn ternary_word_unpack(word: TernaryWord, count: u8) []Trit { + if (count > TRITS_PER_WORD) { + return &[_]Trit{.zero}; + } + + var result: [TRITS_PER_WORD]Trit = undefined; + var valid_count: u8 = 0; + + for (0..count) |i| { + const unpacked = unpack_trit(@intCast(i), word[i / TRITS_PER_BYTE]); + if (!unpacked.valid) { + break; + } + result[i] = unpacked.value; + valid_count += 1; + } + + return result[0..valid_count]; +} + +// trit_compare(a: Trit, b: Trit) -> i8 +// Compare two trits: -1 if a < b, 0 if a == b, +1 if a > b +pub fn trit_compare(a: Trit, b: Trit) i8 { + if (a == b) { + return 0; + } else if (a == .neg or (a == .zero and b == .pos)) { + return -1; + } else { + return 1; + } +} + +// trit_min(a: Trit, b: Trit) -> Trit +// Returns the minimum of two trits +pub fn trit_min(a: Trit, b: Trit) Trit { + return if (a == .neg or (a == .zero and b == .pos)) a else b; +} + +// trit_max(a: Trit, b: Trit) -> Trit +// Returns the maximum of two trits +pub fn trit_max(a: Trit, b: Trit) Trit { + return if (a == .pos or (a == .zero and b == .neg)) a else b; +} + +// trit_abs(a: Trit) -> Trit +// Absolute value of a trit (always 0 or +1) +pub fn trit_abs(a: Trit) Trit { + return if (a == .neg) .pos else a; +} + +// trit_from_i8(value: i8) -> Trit +// Safe conversion from i8 to Trit, clamping to valid range [-1, 0, +1] +// Returns .zero for values outside valid range +pub fn trit_from_i8(value: i8) Trit { + return switch (value) { + -1 => .neg, + 0 => .zero, + 1 => .pos, + else => .zero, // Clamp invalid values to zero + }; +} + +// trit_and(a: Trit, b: Trit) -> Trit +// Logical AND for trits (treating .pos as true, others as false) +// Truth table: +// .neg & .neg = .neg +// .neg & .zero = .neg +// .neg & .pos = .neg +// .zero & .zero = .zero +// .zero & .pos = .zero +// .pos & .pos = .pos +pub fn trit_and(a: Trit, b: Trit) Trit { + return switch (a) { + .pos => b, + .zero => if (b == .zero) .zero else .neg, + .neg => .neg, + }; +} + +// trit_or(a: Trit, b: Trit) -> Trit +// Logical OR for trits (treating .pos as true, others as false) +// Truth table: +// .neg | .neg = .neg +// .neg | .zero = .zero +// .neg | .pos = .pos +// .zero | .zero = .zero +// .zero | .pos = .pos +// .pos | .pos = .pos +pub fn trit_or(a: Trit, b: Trit) Trit { + return switch (a) { + .pos => .pos, + .zero => if (b == .zero) .zero else .pos, + .neg => if (b == .neg) .neg else b, + }; +} + +// trit_xor(a: Trit, b: Trit) -> Trit +// Logical XOR for trits (treating .pos as true, others as false) +// Truth table: +// .neg ^ .neg = .neg +// .neg ^ .zero = .zero +// .neg ^ .pos = .pos +// .zero ^ .zero = .zero +// .zero ^ .pos = .pos +// .pos ^ .pos = .zero +pub fn trit_xor(a: Trit, b: Trit) Trit { + return if (a == b) if (a == .neg) .neg else .zero else .pos; +} + +// trit_not(a: Trit) -> Trit +// Logical NOT for trits (treating .pos as true, others as false) +// Truth table: +// !.neg = .pos +// !.zero = .pos +// !.pos = .zero +pub fn trit_not(a: Trit) Trit { + return if (a == .pos) .zero else .pos; +} + +// trit_select(condition: Trit, a: Trit, b: Trit) -> Trit +// Ternary selection: return a if condition is .pos, else b +// Uses .pos as "true", all other values select b +pub fn trit_select(condition: Trit, a: Trit, b: Trit) Trit { + return if (condition == .pos) a else b; +} + +// packed_trit_count(packed: PackedTrit, value: Trit) -> u8 +// Count occurrences of a specific trit value in PackedTrit +pub fn packed_trit_count(packed: PackedTrit, value: Trit) u8 { + var count: u8 = 0; + for (0..TRITS_PER_BYTE) |i| { + const unpacked = unpack_trit(@intCast(i), packed); + if (unpacked.valid and unpacked.value == value) { + count += 1; + } + } + return count; +} + +// packed_trit_all_equal(packed: PackedTrit, value: Trit) -> bool +// Check if all trits in PackedTrit equal a specific value +pub fn packed_trit_all_equal(packed: PackedTrit, value: Trit) bool { + return packed_trit_count(packed, value) == TRITS_PER_BYTE; +} + +// packed_trit_is_zero(packed: PackedTrit) -> bool +// Check if all trits in PackedTrit are zero +pub fn packed_trit_is_zero(packed: PackedTrit) bool { + return packed_trit_all_equal(packed, .zero); +} + +// packed_trit_is_all_same(packed: PackedTrit) -> bool +// Check if all trits in PackedTrit are the same (any value) +pub fn packed_trit_is_all_same(packed: PackedTrit) bool { + const first = unpack_trit(0, packed).value; + return packed_trit_all_equal(packed, first); +} + +// packed_trit_nand(a: PackedTrit, b: PackedTrit) -> PackedTrit +// Element-wise NAND operation on two PackedTrit values +pub fn packed_trit_nand(a: PackedTrit, b: PackedTrit) PackedTrit { + var result: PackedTrit = 0; + for (0..TRITS_PER_BYTE) |i| { + const a_trit = unpack_trit(@intCast(i), a).value; + const b_trit = unpack_trit(@intCast(i), b).value; + const and_result = trit_and(a_trit, b_trit); + const nand_result = trit_not(and_result); + result = pack_trit(nand_result, @intCast(i), result); + } + return result; +} + +// packed_trit_nor(a: PackedTrit, b: PackedTrit) -> PackedTrit +// Element-wise NOR operation on two PackedTrit values +pub fn packed_trit_nor(a: PackedTrit, b: PackedTrit) PackedTrit { + var result: PackedTrit = 0; + for (0..TRITS_PER_BYTE) |i| { + const a_trit = unpack_trit(@intCast(i), a).value; + const b_trit = unpack_trit(@intCast(i), b).value; + const or_result = trit_or(a_trit, b_trit); + const nor_result = trit_not(or_result); + result = pack_trit(nor_result, @intCast(i), result); + } + return result; +} + +// packed_trit_xnor(a: PackedTrit, b: PackedTrit) -> PackedTrit +// Element-wise XNOR operation on two PackedTrit values +// Returns pos when trits are equal, neg when different +pub fn packed_trit_xnor(a: PackedTrit, b: PackedTrit) PackedTrit { + var result: PackedTrit = 0; + for (0..TRITS_PER_BYTE) |i| { + const a_trit = unpack_trit(@intCast(i), a).value; + const b_trit = unpack_trit(@intCast(i), b).value; + const xor_result = trit_xor(a_trit, b_trit); + const xnor_result = trit_not(xor_result); + result = pack_trit(xnor_result, @intCast(i), result); + } + return result; +} + +// packed_trit_shift_left(packed: PackedTrit, shift: u8) -> PackedTrit +// Left shift packed trits by shift positions (fills with zeros) +// shift must be in [0, 8] +pub fn packed_trit_shift_left(packed: PackedTrit, shift: u8) PackedTrit { + if (shift == 0) { + return packed; + } + if (shift >= TRITS_PER_BYTE) { + return 0; // Shift all trits out, return all zeros + } + + var result: PackedTrit = 0; + for (0..TRITS_PER_BYTE) |i| { + if (i >= shift) { + const src_pos: u8 = @intCast(i - shift); + const src_trit = unpack_trit(src_pos, packed).value; + result = pack_trit(src_trit, @intCast(i), result); + } + // Else: leave as zero (default) + } + return result; +} + +// packed_trit_shift_right(packed: PackedTrit, shift: u8) -> PackedTrit +// Right shift packed trits by shift positions (fills with zeros) +// shift must be in [0, 8] +pub fn packed_trit_shift_right(packed: PackedTrit, shift: u8) PackedTrit { + if (shift == 0) { + return packed; + } + if (shift >= TRITS_PER_BYTE) { + return 0; // Shift all trits out, return all zeros + } + + var result: PackedTrit = 0; + for (0..TRITS_PER_BYTE) |i| { + const dst_pos: u8 = @intCast(i + shift); + if (dst_pos < TRITS_PER_BYTE) { + const src_trit = unpack_trit(@intCast(i), packed).value; + result = pack_trit(src_trit, dst_pos, result); + } + // Else: source trit shifts out + } + return result; +} + +// packed_trit_rotate_left(packed: PackedTrit, rotate: u8) -> PackedTrit +// Left rotate packed trits by rotate positions (circular shift) +// rotate must be in [0, 7] +pub fn packed_trit_rotate_left(packed: PackedTrit, rotate: u8) PackedTrit { + if (rotate == 0) { + return packed; + } + + const shift = rotate % TRITS_PER_BYTE; + var result: PackedTrit = 0; + + for (0..TRITS_PER_BYTE) |i| { + const src_pos: u8 = @intCast((i + TRITS_PER_BYTE - shift) % TRITS_PER_BYTE); + const src_trit = unpack_trit(src_pos, packed).value; + result = pack_trit(src_trit, @intCast(i), result); + } + + return result; +} + +// packed_trit_rotate_right(packed: PackedTrit, rotate: u8) -> PackedTrit +// Right rotate packed trits by rotate positions (circular shift) +// rotate must be in [0, 7] +pub fn packed_trit_rotate_right(packed: PackedTrit, rotate: u8) PackedTrit { + if (rotate == 0) { + return packed; + } + + const shift = rotate % TRITS_PER_BYTE; + var result: PackedTrit = 0; + + for (0..TRITS_PER_BYTE) |i| { + const src_pos: u8 = @intCast((i + shift) % TRITS_PER_BYTE); + const src_trit = unpack_trit(src_pos, packed).value; + result = pack_trit(src_trit, @intCast(i), result); + } + + return result; +} + +// ternary_word_is_zero(word: TernaryWord) -> bool +// Check if all 27 trits in TernaryWord are zero +pub fn ternary_word_is_zero(word: TernaryWord) bool { + for (0..TRITS_PER_WORD) |i| { + const byte_idx = i / TRITS_PER_BYTE; + const trit_pos: u8 = @intCast(i % TRITS_PER_BYTE); + const unpacked = unpack_trit(trit_pos, word[byte_idx]); + if (unpacked.valid and unpacked.value != .zero) { + return false; + } + } + return true; +} + +// ternary_word_count(word: TernaryWord, value: Trit) -> u8 +// Count occurrences of a specific trit value in TernaryWord +pub fn ternary_word_count(word: TernaryWord, value: Trit) u8 { + var count: u8 = 0; + for (0..TRITS_PER_WORD) |i| { + const byte_idx = i / TRITS_PER_BYTE; + const trit_pos: u8 = @intCast(i % TRITS_PER_BYTE); + const unpacked = unpack_trit(trit_pos, word[byte_idx]); + if (unpacked.valid and unpacked.value == value) { + count += 1; + } + } + return count; +} + +// ternary_word_eq(a: TernaryWord, b: TernaryWord) -> bool +// Compare two TernaryWords for equality +pub fn ternary_word_eq(a: TernaryWord, b: TernaryWord) bool { + for (0..WORD_BYTES) |i| { + if (a[i] != b[i]) { + return false; + } + } + return true; +} + +// ternary_word_negate(word: TernaryWord) -> TernaryWord +// Negate all trits in TernaryWord +pub fn ternary_word_negate(word: TernaryWord) TernaryWord { + var result: TernaryWord = undefined; + for (0..TRITS_PER_WORD) |i| { + const byte_idx = i / TRITS_PER_BYTE; + const trit_pos: u8 = @intCast(i % TRITS_PER_BYTE); + const unpacked = unpack_trit(trit_pos, word[byte_idx]); + const negated = trit_negate(unpacked.value); + result[byte_idx] = pack_trit(negated, trit_pos, result[byte_idx]); + } + return result; +} + +// ternary_word_is_all_same(word: TernaryWord) -> bool +// Check if all 27 trits in TernaryWord are the same value +pub fn ternary_word_is_all_same(word: TernaryWord) bool { + const first = unpack_trit(0, word[0]).value; + return ternary_word_count(word, first) == TRITS_PER_WORD; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "test_trit_add_neg_plus_pos_equals_zero" { + // Verify: -1 + +1 = 0 in balanced ternary + try std.testing.expectEqual(@as(Trit, .zero), trit_add(.neg, .pos)); +} + +test "test_trit_add_identity" { + // Verify: 0 + x = x for all trit values + try std.testing.expectEqual(@as(Trit, .neg), trit_add(.zero, .neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_add(.zero, .zero)); + try std.testing.expectEqual(@as(Trit, .pos), trit_add(.zero, .pos)); +} + +test "test_trit_mul_neg_times_neg_equals_pos" { + // Verify: -1 * -1 = +1 in balanced ternary + try std.testing.expectEqual(@as(Trit, .pos), trit_multiply(.neg, .neg)); +} + +test "test_trit_mul_zero_annihilates" { + // Verify: 0 * x = 0 for all trit values + try std.testing.expectEqual(@as(Trit, .zero), trit_multiply(.zero, .neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_multiply(.zero, .zero)); + try std.testing.expectEqual(@as(Trit, .zero), trit_multiply(.zero, .pos)); +} + +test "test_pack_unpack_roundtrip" { + // Verify: pack_trit then unpack_trit returns original value + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |trit| { + const packed = pack_trit(trit, 3, 0); + const unpacked = unpack_trit(3, packed); + try std.testing.expectEqual(trit, unpacked.value); + try std.testing.expect(unpacked.valid); + } +} + +test "test_pack_trit_all_positions" { + // Verify: pack_trit works for all valid positions (0-7) + for (0..8) |i| { + const pos: u8 = @intCast(i); + const packed = pack_trit(.pos, pos, 0); + const unpacked = unpack_trit(pos, packed); + try std.testing.expectEqual(@as(Trit, .pos), unpacked.value); + try std.testing.expect(unpacked.valid); + } +} + +test "test_pack_trit_invalid_position_rejected" { + // Verify: pack_trit rejects position >= 8 + const result = pack_trit(.pos, 8, 0); + try std.testing.expectEqual(@as(PackedTrit, 0xFF), result); +} + +test "test_ternary_word_pack_max_trits" { + // Verify: ternary_word_pack accepts exactly 27 trits + const src = [_]Trit{.pos} ** TRITS_PER_WORD; + const result = ternary_word_pack(&src, TRITS_PER_WORD); + try std.testing.expect(result[0] != 0xFF); // Not error sentinel +} + +test "test_ternary_word_pack_exceeds_max" { + // Verify: ternary_word_pack rejects count > 27 + const src = [_]Trit{.pos} ** (TRITS_PER_WORD + 1); + const result = ternary_word_pack(&src, TRITS_PER_WORD + 1); + try std.testing.expectEqual(@as(u8, 0xFF), result[0]); // Error sentinel +} + +test "test_trit_negate_neg_to_pos" { + // Verify: trit_negate(-1) = +1 + try std.testing.expectEqual(@as(Trit, .pos), trit_negate(.neg)); +} + +test "test_trit_negate_zero_to_zero" { + // Verify: trit_negate(0) = 0 + try std.testing.expectEqual(@as(Trit, .zero), trit_negate(.zero)); +} + +test "test_trit_negate_pos_to_neg" { + // Verify: trit_negate(+1) = -1 + try std.testing.expectEqual(@as(Trit, .neg), trit_negate(.pos)); +} + +test "test_trit_negate_double_negate_identity" { + // Verify: trit_negate(trit_negate(x)) = x + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |trit| { + try std.testing.expectEqual(trit, trit_negate(trit_negate(trit))); + } +} + +test "test_trit_multiply_commutative" { + // Verify: trit_multiply(a, b) == trit_multiply(b, a) + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |a| { + for (trits) |b| { + try std.testing.expectEqual(trit_multiply(a, b), trit_multiply(b, a)); + } + } +} + +test "test_trit_compare_less_than" { + try std.testing.expectEqual(@as(i8, -1), trit_compare(.neg, .zero)); + try std.testing.expectEqual(@as(i8, -1), trit_compare(.neg, .pos)); + try std.testing.expectEqual(@as(i8, -1), trit_compare(.zero, .pos)); +} + +test "test_trit_compare_equal" { + try std.testing.expectEqual(@as(i8, 0), trit_compare(.neg, .neg)); + try std.testing.expectEqual(@as(i8, 0), trit_compare(.zero, .zero)); + try std.testing.expectEqual(@as(i8, 0), trit_compare(.pos, .pos)); +} + +test "test_trit_compare_greater_than" { + try std.testing.expectEqual(@as(i8, 1), trit_compare(.pos, .zero)); + try std.testing.expectEqual(@as(i8, 1), trit_compare(.pos, .neg)); + try std.testing.expectEqual(@as(i8, 1), trit_compare(.zero, .neg)); +} + +test "test_trit_min_returns_minimum" { + try std.testing.expectEqual(@as(Trit, .neg), trit_min(.neg, .pos)); + try std.testing.expectEqual(@as(Trit, .neg), trit_min(.neg, .zero)); + try std.testing.expectEqual(@as(Trit, .zero), trit_min(.zero, .pos)); +} + +test "test_trit_max_returns_maximum" { + try std.testing.expectEqual(@as(Trit, .pos), trit_max(.neg, .pos)); + try std.testing.expectEqual(@as(Trit, .pos), trit_max(.zero, .pos)); + try std.testing.expectEqual(@as(Trit, .zero), trit_max(.neg, .zero)); +} + +test "test_trit_abs_non_negative" { + try std.testing.expectEqual(@as(Trit, .pos), trit_abs(.neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_abs(.zero)); + try std.testing.expectEqual(@as(Trit, .pos), trit_abs(.pos)); +} + +test "test_trit_to_packed_conversion" { + try std.testing.expectEqual(@as(u8, PACKED_NEG), trit_to_packed(.neg)); + try std.testing.expectEqual(@as(u8, PACKED_ZERO), trit_to_packed(.zero)); + try std.testing.expectEqual(@as(u8, PACKED_ONE), trit_to_packed(.pos)); +} + +test "test_packed_to_trit_conversion" { + try std.testing.expectEqual(@as(Trit, .neg), packed_to_trit(PACKED_NEG)); + try std.testing.expectEqual(@as(Trit, .zero), packed_to_trit(PACKED_ZERO)); + try std.testing.expectEqual(@as(Trit, .pos), packed_to_trit(PACKED_ONE)); +} + +test "test_ternary_word_pack_unpack_roundtrip" { + const src = [_]Trit{ .neg, .zero, .pos, .neg, .zero, .pos }; + const packed = ternary_word_pack(&src, 6); + const unpacked = ternary_word_unpack(packed, 6); + try std.testing.expectEqual(src.len, unpacked.len); + for (unpacked, src) |u, s| { + try std.testing.expectEqual(s, u); + } +} + +test "test_trit_from_i8_valid_values" { + // Verify: trit_from_i8 returns correct Trit for valid values + try std.testing.expectEqual(@as(Trit, .neg), trit_from_i8(-1)); + try std.testing.expectEqual(@as(Trit, .zero), trit_from_i8(0)); + try std.testing.expectEqual(@as(Trit, .pos), trit_from_i8(1)); +} + +test "test_trit_from_i8_invalid_values_clamp_to_zero" { + // Verify: trit_from_i8 clamps invalid values to zero + try std.testing.expectEqual(@as(Trit, .zero), trit_from_i8(-2)); + try std.testing.expectEqual(@as(Trit, .zero), trit_from_i8(2)); + try std.testing.expectEqual(@as(Trit, .zero), trit_from_i8(100)); + try std.testing.expectEqual(@as(Trit, .zero), trit_from_i8(-100)); +} + +test "test_trit_and_truth_table" { + // Verify: trit_and truth table + try std.testing.expectEqual(@as(Trit, .neg), trit_and(.neg, .neg)); + try std.testing.expectEqual(@as(Trit, .neg), trit_and(.neg, .zero)); + try std.testing.expectEqual(@as(Trit, .neg), trit_and(.neg, .pos)); + try std.testing.expectEqual(@as(Trit, .neg), trit_and(.zero, .neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_and(.zero, .zero)); + try std.testing.expectEqual(@as(Trit, .zero), trit_and(.zero, .pos)); + try std.testing.expectEqual(@as(Trit, .neg), trit_and(.pos, .neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_and(.pos, .zero)); + try std.testing.expectEqual(@as(Trit, .pos), trit_and(.pos, .pos)); +} + +test "test_trit_or_truth_table" { + // Verify: trit_or truth table + try std.testing.expectEqual(@as(Trit, .neg), trit_or(.neg, .neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_or(.neg, .zero)); + try std.testing.expectEqual(@as(Trit, .pos), trit_or(.neg, .pos)); + try std.testing.expectEqual(@as(Trit, .zero), trit_or(.zero, .neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_or(.zero, .zero)); + try std.testing.expectEqual(@as(Trit, .pos), trit_or(.zero, .pos)); + try std.testing.expectEqual(@as(Trit, .pos), trit_or(.pos, .neg)); + try std.testing.expectEqual(@as(Trit, .pos), trit_or(.pos, .zero)); + try std.testing.expectEqual(@as(Trit, .pos), trit_or(.pos, .pos)); +} + +test "test_trit_xor_truth_table" { + // Verify: trit_xor truth table + try std.testing.expectEqual(@as(Trit, .neg), trit_xor(.neg, .neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_xor(.neg, .zero)); + try std.testing.expectEqual(@as(Trit, .pos), trit_xor(.neg, .pos)); + try std.testing.expectEqual(@as(Trit, .zero), trit_xor(.zero, .neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_xor(.zero, .zero)); + try std.testing.expectEqual(@as(Trit, .pos), trit_xor(.zero, .pos)); + try std.testing.expectEqual(@as(Trit, .pos), trit_xor(.pos, .neg)); + try std.testing.expectEqual(@as(Trit, .pos), trit_xor(.pos, .zero)); + try std.testing.expectEqual(@as(Trit, .zero), trit_xor(.pos, .pos)); +} + +test "test_trit_not_truth_table" { + // Verify: trit_not truth table + try std.testing.expectEqual(@as(Trit, .pos), trit_not(.neg)); + try std.testing.expectEqual(@as(Trit, .pos), trit_not(.zero)); + try std.testing.expectEqual(@as(Trit, .zero), trit_not(.pos)); +} + +test "test_trit_select_condition_true" { + // Verify: trit_select returns a when condition is .pos + try std.testing.expectEqual(@as(Trit, .neg), trit_select(.pos, .neg, .pos)); + try std.testing.expectEqual(@as(Trit, .zero), trit_select(.pos, .zero, .neg)); + try std.testing.expectEqual(@as(Trit, .pos), trit_select(.pos, .pos, .zero)); +} + +test "test_trit_select_condition_false" { + // Verify: trit_select returns b when condition is not .pos + try std.testing.expectEqual(@as(Trit, .pos), trit_select(.neg, .neg, .pos)); + try std.testing.expectEqual(@as(Trit, .neg), trit_select(.neg, .zero, .neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_select(.neg, .pos, .zero)); + try std.testing.expectEqual(@as(Trit, .pos), trit_select(.zero, .neg, .pos)); + try std.testing.expectEqual(@as(Trit, .neg), trit_select(.zero, .zero, .neg)); + try std.testing.expectEqual(@as(Trit, .zero), trit_select(.zero, .pos, .zero)); +} + +test "test_trit_and_commutative" { + // Verify: trit_and(a, b) == trit_and(b, a) + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |a| { + for (trits) |b| { + try std.testing.expectEqual(trit_and(a, b), trit_and(b, a)); + } + } +} + +test "test_trit_or_commutative" { + // Verify: trit_or(a, b) == trit_or(b, a) + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |a| { + for (trits) |b| { + try std.testing.expectEqual(trit_or(a, b), trit_or(b, a)); + } + } +} + +test "test_trit_xor_commutative" { + // Verify: trit_xor(a, b) == trit_xor(b, a) + const trits = [_]Trit{ .neg, .zero, .pos }; + for (trits) |a| { + for (trits) |b| { + try std.testing.expectEqual(trit_xor(a, b), trit_xor(b, a)); + } + } +} + +test "test_packed_trit_count_zeros" { + var packed: PackedTrit = 0; + packed = pack_trit(.zero, 0, packed); + packed = pack_trit(.zero, 1, packed); + packed = pack_trit(.zero, 2, packed); + packed = pack_trit(.pos, 3, packed); + try std.testing.expectEqual(@as(u8, 3), packed_trit_count(packed, .zero)); +} + +test "test_packed_trit_count_pos" { + var packed: PackedTrit = 0; + packed = pack_trit(.pos, 0, packed); + packed = pack_trit(.pos, 1, packed); + packed = pack_trit(.zero, 2, packed); + packed = pack_trit(.neg, 3, packed); + try std.testing.expectEqual(@as(u8, 2), packed_trit_count(packed, .pos)); +} + +test "test_packed_trit_count_neg" { + var packed: PackedTrit = 0; + packed = pack_trit(.neg, 0, packed); + packed = pack_trit(.neg, 1, packed); + packed = pack_trit(.zero, 2, packed); + packed = pack_trit(.pos, 3, packed); + try std.testing.expectEqual(@as(u8, 2), packed_trit_count(packed, .neg)); +} + +test "test_packed_trit_all_equal_true" { + var packed: PackedTrit = 0; + for (0..8) |i| { + packed = pack_trit(.pos, @intCast(i), packed); + } + try std.testing.expect(packed_trit_all_equal(packed, .pos)); +} + +test "test_packed_trit_all_equal_false" { + var packed: PackedTrit = 0; + packed = pack_trit(.pos, 0, packed); + packed = pack_trit(.pos, 1, packed); + packed = pack_trit(.zero, 2, packed); + try std.testing.expect(!packed_trit_all_equal(packed, .pos)); +} + +test "test_packed_trit_is_zero_true" { + var packed: PackedTrit = 0; + for (0..8) |i| { + packed = pack_trit(.zero, @intCast(i), packed); + } + try std.testing.expect(packed_trit_is_zero(packed)); +} + +test "test_packed_trit_is_zero_false" { + var packed: PackedTrit = 0; + packed = pack_trit(.zero, 0, packed); + packed = pack_trit(.pos, 1, packed); + try std.testing.expect(!packed_trit_is_zero(packed)); +} + +test "test_packed_trit_is_all_same_true" { + var packed: PackedTrit = 0; + for (0..8) |i| { + packed = pack_trit(.neg, @intCast(i), packed); + } + try std.testing.expect(packed_trit_is_all_same(packed)); +} + +test "test_packed_trit_is_all_same_false" { + var packed: PackedTrit = 0; + packed = pack_trit(.pos, 0, packed); + packed = pack_trit(.zero, 1, packed); + try std.testing.expect(!packed_trit_is_all_same(packed)); +} + +test "test_packed_trit_nand_basic" { + var a: PackedTrit = 0; + var b: PackedTrit = 0; + a = pack_trit(.pos, 0, a); + a = pack_trit(.pos, 1, a); + b = pack_trit(.pos, 0, b); + b = pack_trit(.zero, 1, b); + const result = packed_trit_nand(a, b); + // pos NAND pos = zero, pos NAND zero = pos + try std.testing.expectEqual(@as(Trit, .zero), unpack_trit(0, result).value); + try std.testing.expectEqual(@as(Trit, .pos), unpack_trit(1, result).value); +} + +test "test_packed_trit_nor_basic" { + var a: PackedTrit = 0; + var b: PackedTrit = 0; + a = pack_trit(.pos, 0, a); + a = pack_trit(.zero, 1, a); + b = pack_trit(.zero, 0, b); + b = pack_trit(.zero, 1, b); + const result = packed_trit_nor(a, b); + // pos NOR zero = zero, zero NOR zero = pos + try std.testing.expectEqual(@as(Trit, .zero), unpack_trit(0, result).value); + try std.testing.expectEqual(@as(Trit, .pos), unpack_trit(1, result).value); +} + +test "test_packed_trit_xnor_equal_returns_pos" { + // Verify: XNOR of equal trits returns pos + var packed: PackedTrit = 0; + packed = pack_trit(.pos, 0, packed); + packed = pack_trit(.neg, 1, packed); + packed = pack_trit(.zero, 2, packed); + const result = packed_trit_xnor(packed, packed); + try std.testing.expectEqual(@as(Trit, .pos), unpack_trit(0, result).value); + try std.testing.expectEqual(@as(Trit, .pos), unpack_trit(1, result).value); + try std.testing.expectEqual(@as(Trit, .pos), unpack_trit(2, result).value); +} + +test "test_packed_trit_xnor_different_returns_neg" { + // Verify: XNOR of different trits returns neg + var a: PackedTrit = 0; + var b: PackedTrit = 0; + a = pack_trit(.pos, 0, a); + b = pack_trit(.neg, 0, b); + const result = packed_trit_xnor(a, b); + try std.testing.expectEqual(@as(Trit, .neg), unpack_trit(0, result).value); +} + +test "test_packed_trit_shift_left_basic" { + // Verify: left shift by 1 moves all trits left + var packed: PackedTrit = 0; + packed = pack_trit(.pos, 0, packed); + packed = pack_trit(.neg, 1, packed); + packed = pack_trit(.zero, 2, packed); + const result = packed_trit_shift_left(packed, 1); + try std.testing.expectEqual(@as(Trit, .neg), unpack_trit(1, result).value); + try std.testing.expectEqual(@as(Trit, .zero), unpack_trit(2, result).value); + try std.testing.expectEqual(@as(Trit, .zero), unpack_trit(0, result).value); // Filled with zero +} + +test "test_packed_trit_shift_left_by_eight_returns_zero" { + // Verify: shift by 8 returns all zeros + var packed: PackedTrit = 0; + packed = pack_trit(.pos, 0, packed); + packed = pack_trit(.neg, 1, packed); + const result = packed_trit_shift_left(packed, 8); + try std.testing.expectEqual(@as(PackedTrit, 0), result); +} + +test "test_packed_trit_shift_right_basic" { + // Verify: right shift by 1 moves all trits right + var packed: PackedTrit = 0; + packed = pack_trit(.pos, 1, packed); + packed = pack_trit(.neg, 2, packed); + packed = pack_trit(.zero, 3, packed); + const result = packed_trit_shift_right(packed, 1); + try std.testing.expectEqual(@as(Trit, .pos), unpack_trit(0, result).value); + try std.testing.expectEqual(@as(Trit, .neg), unpack_trit(1, result).value); + try std.testing.expectEqual(@as(Trit, .zero), unpack_trit(2, result).value); +} + +test "test_packed_trit_shift_right_by_eight_returns_zero" { + // Verify: shift by 8 returns all zeros + var packed: PackedTrit = 0; + packed = pack_trit(.pos, 7, packed); + const result = packed_trit_shift_right(packed, 8); + try std.testing.expectEqual(@as(PackedTrit, 0), result); +} + +test "test_packed_trit_rotate_left_basic" { + // Verify: rotate left by 1 is circular + var packed: PackedTrit = 0; + packed = pack_trit(.pos, 7, packed); // Last position + packed = pack_trit(.neg, 0, packed); // First position + const result = packed_trit_rotate_left(packed, 1); + try std.testing.expectEqual(@as(Trit, .pos), unpack_trit(0, result).value); // Wrapped to front + try std.testing.expectEqual(@as(Trit, .neg), unpack_trit(1, result).value); // Shifted left +} + +test "test_packed_trit_rotate_left_by_three" { + // Verify: rotate left by 3 positions + var packed: PackedTrit = 0; + packed = pack_trit(.pos, 5, packed); + const result = packed_trit_rotate_left(packed, 3); + try std.testing.expectEqual(@as(Trit, .pos), unpack_trit(2, result).value); // 5+3=8, wraps to 2 +} + +test "test_packed_trit_rotate_right_basic" { + // Verify: rotate right by 1 is circular + var packed: PackedTrit = 0; + packed = pack_trit(.pos, 0, packed); // First position + packed = pack_trit(.neg, 7, packed); // Last position + const result = packed_trit_rotate_right(packed, 1); + try std.testing.expectEqual(@as(Trit, .pos), unpack_trit(7, result).value); // Wrapped to back + try std.testing.expectEqual(@as(Trit, .zero), unpack_trit(0, result).value); // Last position wrapped here +} + +test "test_packed_trit_rotate_right_by_four" { + // Verify: rotate right by 4 positions + var packed: PackedTrit = 0; + packed = pack_trit(.pos, 2, packed); + const result = packed_trit_rotate_right(packed, 4); + try std.testing.expectEqual(@as(Trit, .pos), unpack_trit(6, result).value); // (2+4) % 8 = 6 +} + +test "test_packed_trit_shift_rotate_are_different" { + // Verify: shift fills with zeros, rotate is circular + var packed: PackedTrit = 0; + packed = pack_trit(.pos, 7, packed); + const shift_result = packed_trit_shift_left(packed, 1); + const rotate_result = packed_trit_rotate_left(packed, 1); + try std.testing.expectEqual(@as(Trit, .zero), unpack_trit(0, shift_result).value); // Shift fills with zero + try std.testing.expectEqual(@as(Trit, .pos), unpack_trit(0, rotate_result).value); // Rotate wraps around +} + +test "test_ternary_word_is_zero_true" { + // Verify: all zeros ternary word is detected as zero + var word: TernaryWord = [_]u8{0} ** WORD_BYTES; + try std.testing.expect(ternary_word_is_zero(word)); +} + +test "test_ternary_word_is_zero_false" { + // Verify: non-zero ternary word is not detected as zero + var word: TernaryWord = [_]u8{0} ** WORD_BYTES; + word[0] = pack_trit(.pos, 0, word[0]); + try std.testing.expect(!ternary_word_is_zero(word)); +} + +test "test_ternary_word_count_zeros" { + // Verify: count zeros in mixed ternary word + var word: TernaryWord = [_]u8{0} ** WORD_BYTES; + word[0] = pack_trit(.zero, 0, word[0]); + word[0] = pack_trit(.zero, 1, word[0]); + word[0] = pack_trit(.pos, 2, word[0]); + try std.testing.expectEqual(@as(u8, 2), ternary_word_count(word, .zero)); + try std.testing.expectEqual(@as(u8, 1), ternary_word_count(word, .pos)); +} + +test "test_ternary_word_count_all_same" { + // Verify: count returns TRITS_PER_WORD for all same value + var word: TernaryWord = [_]u8{0} ** WORD_BYTES; + for (0..TRITS_PER_WORD) |i| { + const byte_idx = i / TRITS_PER_BYTE; + const trit_pos: u8 = @intCast(i % TRITS_PER_BYTE); + word[byte_idx] = pack_trit(.neg, trit_pos, word[byte_idx]); + } + try std.testing.expectEqual(TRITS_PER_WORD, ternary_word_count(word, .neg)); +} + +test "test_ternary_word_equal_same" { + // Verify: identical ternary words are equal + var word: TernaryWord = [_]u8{0} ** WORD_BYTES; + word[0] = pack_trit(.pos, 0, word[0]); + word[1] = pack_trit(.neg, 0, word[1]); + try std.testing.expect(ternary_word_eq(word, word)); +} + +test "test_ternary_word_equal_different" { + // Verify: different ternary words are not equal + var word_a: TernaryWord = [_]u8{0} ** WORD_BYTES; + var word_b: TernaryWord = [_]u8{0} ** WORD_BYTES; + word_a[0] = pack_trit(.pos, 0, word_a[0]); + word_b[0] = pack_trit(.neg, 0, word_b[0]); + try std.testing.expect(!ternary_word_eq(word_a, word_b)); +} + +test "test_ternary_word_negate" { + // Verify: negation inverts all trits + var word: TernaryWord = [_]u8{0} ** WORD_BYTES; + word[0] = pack_trit(.pos, 0, word[0]); + word[0] = pack_trit(.neg, 1, word[0]); + word[0] = pack_trit(.zero, 2, word[0]); + const negated = ternary_word_negate(word); + try std.testing.expectEqual(@as(Trit, .neg), unpack_trit(0, negated[0]).value); + try std.testing.expectEqual(@as(Trit, .pos), unpack_trit(1, negated[0]).value); + try std.testing.expectEqual(@as(Trit, .zero), unpack_trit(2, negated[0]).value); +} + +test "test_ternary_word_negate_double_identity" { + // Verify: double negation returns original + var word: TernaryWord = [_]u8{0} ** WORD_BYTES; + word[0] = pack_trit(.pos, 0, word[0]); + word[1] = pack_trit(.neg, 0, word[1]); + const double_negated = ternary_word_negate(ternary_word_negate(word)); + try std.testing.expect(ternary_word_eq(word, double_negated)); +} + +test "test_ternary_word_is_all_same_true" { + // Verify: all same value ternary word is detected + var word: TernaryWord = [_]u8{0} ** WORD_BYTES; + for (0..TRITS_PER_WORD) |i| { + const byte_idx = i / TRITS_PER_BYTE; + const trit_pos: u8 = @intCast(i % TRITS_PER_BYTE); + word[byte_idx] = pack_trit(.pos, trit_pos, word[byte_idx]); + } + try std.testing.expect(ternary_word_is_all_same(word)); +} + +test "test_ternary_word_is_all_same_false" { + // Verify: mixed ternary word is not detected as all same + var word: TernaryWord = [_]u8{0} ** WORD_BYTES; + word[0] = pack_trit(.pos, 0, word[0]); + word[0] = pack_trit(.neg, 1, word[0]); + try std.testing.expect(!ternary_word_is_all_same(word)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant trit_value_range { + // All Trit values are in {-1, 0, +1} + @compileAssert(@intFromEnum(Trit.neg) == -1); + @compileAssert(@intFromEnum(Trit.zero) == 0); + @compileAssert(@intFromEnum(Trit.pos) == 1); +} + +invariant packed_trit_encoding_valid { + // All packed trit encodings are in {0, 1, 2} + @compileAssert(PACKED_NEG == 2); + @compileAssert(PACKED_ZERO == 0); + @compileAssert(PACKED_ONE == 1); +} + +invariant trit_add_result_in_range { + // trit_add result is always in {-1, 0, +1} + @compileAssert(true); +} + +invariant trit_mul_result_in_range { + // trit_multiply result is always in {-1, 0, +1} + @compileAssert(true); +} + +invariant pack_position_bounds { + // pack_trit position must be in [0, 7] + @compileAssert(TRITS_PER_BYTE == 8); +} + +invariant ternary_word_max_trits { + // TernaryWord contains exactly 27 trits + @compileAssert(TRITS_PER_WORD == 27); +} + +invariant trit_multiply_commutative { + // trit_multiply(a, b) == trit_multiply(b, a) + @compileAssert(true); +} + +invariant trit_negate_involutive { + // trit_negate(trit_negate(x)) = x for all trit values + @compileAssert(true); +} + +invariant trit_negate_double_identity { + // trit_negate(trit_negate(x)) == x + @compileAssert(true); +} + +invariant trit_add_identity_zero { + // trit_add(0, x) == x for all x + @compileAssert(true); +} + +invariant trit_mul_zero_annihilates { + // trit_multiply(0, x) == 0 for all x + @compileAssert(true); +} + +invariant trit_abs_non_negative { + // trit_abs(x) is always in {0, +1} + @compileAssert(true); +} + +invariant trit_from_i8_valid_range { + // trit_from_i8(-1) = .neg, trit_from_i8(0) = .zero, trit_from_i8(1) = .pos + @compileAssert(true); +} + +invariant trit_from_i8_clamps_invalid { + // trit_from_i8 returns .zero for values outside [-1, 0, +1] + @compileAssert(true); +} + +invariant trit_and_commutative { + // trit_and(a, b) == trit_and(b, a) + @compileAssert(true); +} + +invariant trit_or_commutative { + // trit_or(a, b) == trit_or(b, a) + @compileAssert(true); +} + +invariant trit_xor_commutative { + // trit_xor(a, b) == trit_xor(b, a) + @compileAssert(true); +} + +invariant trit_and_identity { + // trit_and(.pos, x) == x (pos acts as identity for AND) + @compileAssert(true); +} + +invariant trit_or_identity { + // trit_or(.neg, x) == x (neg acts as identity for OR) + @compileAssert(true); +} + +invariant trit_xor_identity { + // trit_xor(.neg, x) == x (neg acts as identity for XOR) + @compileAssert(true); +} + +invariant trit_not_double_not { + // trit_not(trit_not(x)) == x for all trit values + @compileAssert(true); +} + +invariant trit_select_condition_true { + // trit_select(.pos, a, b) == a + @compileAssert(true); +} + +invariant trit_select_condition_false { + // trit_select(.neg, a, b) == b + // trit_select(.zero, a, b) == b + @compileAssert(true); +} + +invariant packed_trit_count_range { + // packed_trit_count result is in [0, 8] + @compileAssert(true); +} + +invariant packed_trit_count_sum_equals_trits_per_byte { + // For all packed: count(neg) + count(zero) + count(pos) = 8 + @compileAssert(true); +} + +invariant packed_trit_all_zero_implies_is_zero { + // packed_trit_is_zero(x) = packed_trit_all_equal(x, .zero) + @compileAssert(true); +} + +invariant packed_trit_nand_commutative { + // packed_trit_nand(a, b) = packed_trit_nand(b, a) + @compileAssert(true); +} + +invariant packed_trit_nor_commutative { + // packed_trit_nor(a, b) = packed_trit_nor(b, a) + @compileAssert(true); +} + +invariant packed_trit_xnor_commutative { + // packed_trit_xnor(a, b) = packed_trit_xnor(b, a) + @compileAssert(true); +} + +invariant packed_trit_xnor_equal_returns_pos { + // packed_trit_xnor(x, x) has all trits = pos + @compileAssert(true); +} + +invariant packed_trit_shift_left_by_eight_returns_zero { + // packed_trit_shift_left(x, 8) = 0 for all x + @compileAssert(true); +} + +invariant packed_trit_shift_right_by_eight_returns_zero { + // packed_trit_shift_right(x, 8) = 0 for all x + @compileAssert(true); +} + +invariant packed_trit_shift_left_fills_with_zero { + // packed_trit_shift_left(x, n) has first n trits = 0 + @compileAssert(true); +} + +invariant packed_trit_shift_right_fills_with_zero { + // packed_trit_shift_right(x, n) has last n trits = 0 + @compileAssert(true); +} + +invariant packed_trit_rotate_left_inverse { + // packed_trit_rotate_right(packed_trit_rotate_left(x, n), n) = x + @compileAssert(true); +} + +invariant packed_trit_rotate_right_inverse { + // packed_trit_rotate_left(packed_trit_rotate_right(x, n), n) = x + @compileAssert(true); +} + +invariant packed_trit_rotate_preserves_count { + // trit count is preserved after rotation + @compileAssert(true); +} + +invariant ternary_word_is_zero_idempotent { + // ternary_word_is_zero(x) = (ternary_word_count(x, .zero) == TRITS_PER_WORD) + @compileAssert(true); +} + +invariant ternary_word_count_range { + // ternary_word_count result is in [0, TRITS_PER_WORD] + @compileAssert(true); +} + +invariant ternary_word_count_sum_equals_trits_per_word { + // For all word: count(neg) + count(zero) + count(pos) = TRITS_PER_WORD + @compileAssert(true); +} + +invariant ternary_word_eq_reflexive { + // ternary_word_eq(x, x) = true for all x + @compileAssert(true); +} + +invariant ternary_word_eq_symmetric { + // ternary_word_eq(a, b) = ternary_word_eq(b, a) + @compileAssert(true); +} + +invariant ternary_word_eq_transitive { + // ternary_word_eq(a, b) and ternary_word_eq(b, c) implies ternary_word_eq(a, c) + @compileAssert(true); +} + +invariant ternary_word_negate_involutive { + // ternary_word_negate(ternary_word_negate(x)) = x for all x + @compileAssert(true); +} + +invariant ternary_word_negate_zero_invariant { + // ternary_word_negate(x) is zero iff x is zero + @compileAssert(true); +} + +invariant ternary_word_is_all_same_implies_count_equals_word_size { + // ternary_word_is_all_same(x) implies ternary_word_count(x, first) == TRITS_PER_WORD + @compileAssert(true); +} + +invariant ternary_word_is_all_same_zero_implies_is_zero { + // ternary_word_is_all_same(x) with first=zero implies ternary_word_is_zero(x) + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "bench_trit_add_latency" { + // Measure: cycles for single trit_add operation + // Target: < 10 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_add(.pos, .neg); + } + _ = result; +} + +bench "bench_trit_multiply_latency" { + // Measure: cycles for single trit_multiply operation + // Target: < 10 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_multiply(.pos, .neg); + } + _ = result; +} + +bench "bench_trit_negate_latency" { + // Measure: cycles for single trit_negate operation + // Target: < 5 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_negate(.pos); + } + _ = result; +} + +bench "bench_pack_trit_latency" { + // Measure: cycles for single pack_trit operation + // Target: < 20 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: PackedTrit = 0; + for (0..1000) |_| { + result = pack_trit(.pos, 3, result); + } + _ = result; +} + +bench "bench_unpack_trit_latency" { + // Measure: cycles for single unpack_trit operation + // Target: < 15 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + const unpacked = unpack_trit(3, 0); + result = unpacked.value; + } + _ = result; +} + +bench "bench_trit_and_latency" { + // Measure: cycles for single trit_and operation + // Target: < 10 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_and(.pos, .neg); + } + _ = result; +} + +bench "bench_trit_or_latency" { + // Measure: cycles for single trit_or operation + // Target: < 10 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_or(.pos, .neg); + } + _ = result; +} + +bench "bench_trit_xor_latency" { + // Measure: cycles for single trit_xor operation + // Target: < 10 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_xor(.pos, .neg); + } + _ = result; +} + +bench "bench_trit_not_latency" { + // Measure: cycles for single trit_not operation + // Target: < 5 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_not(.pos); + } + _ = result; +} + +bench "bench_trit_select_latency" { + // Measure: cycles for single trit_select operation + // Target: < 10 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_select(.pos, .neg, .zero); + } + _ = result; +} + +bench "bench_trit_from_i8_latency" { + // Measure: cycles for single trit_from_i8 operation + // Target: < 10 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: Trit = .zero; + for (0..1000) |_| { + result = trit_from_i8(1); + } + _ = result; +} + +bench "bench_packed_trit_count_latency" { + // Measure: cycles for packed_trit_count operation + // Target: < 50 cycles on t27-hardware (unpacks 8 trits) + @setEvalBranchQuota(10000); + var result: u8 = 0; + const packed: PackedTrit = 0xAA; + for (0..1000) |_| { + result = packed_trit_count(packed, .pos); + } + _ = result; +} + +bench "bench_packed_trit_all_equal_latency" { + // Measure: cycles for packed_trit_all_equal operation + // Target: < 50 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: bool = false; + const packed: PackedTrit = 0x55; + for (0..1000) |_| { + result = packed_trit_all_equal(packed, .zero); + } + _ = result; +} + +bench "bench_packed_trit_is_zero_latency" { + // Measure: cycles for packed_trit_is_zero operation + // Target: < 50 cycles on t27-hardware + @setEvalBranchQuota(10000); + var result: bool = false; + const packed: PackedTrit = 0x00; + for (0..1000) |_| { + result = packed_trit_is_zero(packed); + } + _ = result; +} + +bench "bench_packed_trit_nand_latency" { + // Measure: cycles for packed_trit_nand operation + // Target: < 100 cycles on t27-hardware (8 trit operations) + @setEvalBranchQuota(10000); + var result: PackedTrit = 0; + const a: PackedTrit = 0xFF; + const b: PackedTrit = 0xFF; + for (0..1000) |_| { + result = packed_trit_nand(a, b); + } + _ = result; +} + +bench "bench_packed_trit_nor_latency" { + // Measure: cycles for packed_trit_nor operation + // Target: < 100 cycles on t27-hardware (8 trit operations) + @setEvalBranchQuota(10000); + var result: PackedTrit = 0; + const a: PackedTrit = 0x00; + const b: PackedTrit = 0x00; + for (0..1000) |_| { + result = packed_trit_nor(a, b); + } + _ = result; +} + +bench "bench_packed_trit_xnor_latency" { + // Measure: cycles for packed_trit_xnor operation + // Target: < 100 cycles on t27-hardware (8 trit operations) + @setEvalBranchQuota(10000); + var result: PackedTrit = 0; + const a: PackedTrit = 0xAA; + const b: PackedTrit = 0x55; + for (0..1000) |_| { + result = packed_trit_xnor(a, b); + } + _ = result; +} + +bench "bench_packed_trit_shift_left_latency" { + // Measure: cycles for packed_trit_shift_left operation + // Target: < 50 cycles on t27-hardware (8 trit moves) + @setEvalBranchQuota(10000); + var result: PackedTrit = 0; + const packed: PackedTrit = 0xFF; + for (0..1000) |_| { + result = packed_trit_shift_left(packed, 1); + } + _ = result; +} + +bench "bench_packed_trit_shift_right_latency" { + // Measure: cycles for packed_trit_shift_right operation + // Target: < 50 cycles on t27-hardware (8 trit moves) + @setEvalBranchQuota(10000); + var result: PackedTrit = 0; + const packed: PackedTrit = 0xFF; + for (0..1000) |_| { + result = packed_trit_shift_right(packed, 1); + } + _ = result; +} + +bench "bench_packed_trit_rotate_left_latency" { + // Measure: cycles for packed_trit_rotate_left operation + // Target: < 50 cycles on t27-hardware (8 trit moves) + @setEvalBranchQuota(10000); + var result: PackedTrit = 0; + const packed: PackedTrit = 0xFF; + for (0..1000) |_| { + result = packed_trit_rotate_left(packed, 1); + } + _ = result; +} + +bench "bench_packed_trit_rotate_right_latency" { + // Measure: cycles for packed_trit_rotate_right operation + // Target: < 50 cycles on t27-hardware (8 trit moves) + @setEvalBranchQuota(10000); + var result: PackedTrit = 0; + const packed: PackedTrit = 0xFF; + for (0..1000) |_| { + result = packed_trit_rotate_right(packed, 1); + } + _ = result; +} + +bench "bench_ternary_word_is_zero_latency" { + // Measure: cycles for ternary_word_is_zero operation + // Target: < 200 cycles on t27-hardware (27 trit checks) + @setEvalBranchQuota(10000); + var result: bool = false; + const word: TernaryWord = [_]u8{0} ** WORD_BYTES; + for (0..1000) |_| { + result = ternary_word_is_zero(word); + } + _ = result; +} + +bench "bench_ternary_word_count_latency" { + // Measure: cycles for ternary_word_count operation + // Target: < 300 cycles on t27-hardware (27 trit checks) + @setEvalBranchQuota(10000); + var result: u8 = 0; + const word: TernaryWord = [_]u8{0xAA, 0x55, 0xAA, 0x55, 0xAA}; + for (0..1000) |_| { + result = ternary_word_count(word, .pos); + } + _ = result; +} + +bench "bench_ternary_word_eq_latency" { + // Measure: cycles for ternary_word_eq operation + // Target: < 50 cycles on t27-hardware (5 byte compares) + @setEvalBranchQuota(10000); + var result: bool = false; + const word_a: TernaryWord = [_]u8{0xAA} ** WORD_BYTES; + const word_b: TernaryWord = [_]u8{0xAA} ** WORD_BYTES; + for (0..1000) |_| { + result = ternary_word_eq(word_a, word_b); + } + _ = result; +} + +bench "bench_ternary_word_negate_latency" { + // Measure: cycles for ternary_word_negate operation + // Target: < 500 cycles on t27-hardware (27 trit negations + packing) + @setEvalBranchQuota(10000); + var result: TernaryWord = undefined; + const word: TernaryWord = [_]u8{0x55, 0xAA, 0x55, 0xAA, 0x55}; + for (0..1000) |_| { + result = ternary_word_negate(word); + } + _ = result; +} + +bench "bench_ternary_word_is_all_same_latency" { + // Measure: cycles for ternary_word_is_all_same operation + // Target: < 350 cycles on t27-hardware (27 trit checks) + @setEvalBranchQuota(10000); + var result: bool = false; + const word: TernaryWord = [_]u8{0x00} ** WORD_BYTES; + for (0..1000) |_| { + result = ternary_word_is_all_same(word); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/benchmarks/bench_main.t27 b/apps/website/public/t27/specs/benchmarks/bench_main.t27 new file mode 100644 index 0000000000..eb261d16ca --- /dev/null +++ b/apps/website/public/t27/specs/benchmarks/bench_main.t27 @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +# NN INFERENCE BENCHMARK -- Format Comparison + +## Specification + +Compares accuracy degradation when using different number formats. +Tiny MLP: 100 -> 64 -> 10 (simplified MNIST-like). + +## Mathematical Foundation + +``` +phi^2 + 1/phi^2 = 3 = TRINITY +``` + +## Model Architecture + +``` +LayerConfig: + input_size: 100 + hidden_size: 64 + output_size: 10 +``` + +## Inference Functions + +``` +runInferenceF32(inputs, targets, weights, biases, config, num_samples) -> InferenceResult + Baseline inference with f32 weights + +runInferenceF16Soft(inputs, targets, weights, biases, config, num_samples) -> InferenceResult + Soft quantized f16 inference + +runInferenceGF16Soft(inputs, targets, weights, biases, config, num_samples) -> InferenceResult + GF16 soft quantized inference + +runInferenceTernary(inputs, targets, weights, biases, config, num_samples) -> InferenceResult + Ternary quantized inference {-1, 0, +1} +``` + +## Quantization Schemes + +### FP16 (IEEE 754) +- Sign: 1 bit +- Exponent: 5 bits +- Mantissa: 10 bits + +### GF16 (Geometric) +- Positive values: `exp2(x) * frac` where `x in [1, 2)` +- Zero: special case `0` +- Negative: sign flip of positive + +### Ternary +- Direct mapping: `> 0.5 -> +1`, `< -0.5 -> -1`, `else -> 0` +- Accuracy drop expected due to -1,0,+1 limitation + +## Forward Pass Functions + +``` +forwardPassF32(input, weights, biases, config, hidden, output) -> void + f32 forward pass with ReLU + +forwardPassTernary(input, w1, b1, w2, b2, hidden, output) -> void + Ternary forward pass (weights quantized to i8) + +forwardPassGF16(input, w1, b1, w2, b2, hidden, output) -> void + GF16 forward pass (weights quantized to u16) +``` + +## Tests + +``` +test "NN-Bench: f32 accuracy baseline" { + // Verify f32 accuracy is high (> 90%) +} + +test "NN-Bench: format comparison" { + // GF16 and f16_soft should maintain similar accuracy to f32 + // Ternary will show significant accuracy drop due to limited representation +} +``` + +## Expected Results + +| Format | Accuracy | Loss | Size (bytes/weight) | +|----------|----------|---------|-------------------| +| f32 | ~95.2% | 0.048 | 32 | +| f16 soft | ~94.8% | 0.052 | 16 | +| gf16 soft | ~94.9% | 0.051 | 16 | +| ternary | ~88.5% | 0.12 | 2 | + +**Key Findings:** +- GF16 maintains competitive accuracy vs f32 baseline +- Ternary shows significant accuracy drop due to -1,0,+1 limitation +- All soft implementations have overhead vs hardware f32 diff --git a/apps/website/public/t27/specs/benchmarks/bench_nn.t27 b/apps/website/public/t27/specs/benchmarks/bench_nn.t27 new file mode 100644 index 0000000000..4fdf2057b4 --- /dev/null +++ b/apps/website/public/t27/specs/benchmarks/bench_nn.t27 @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: Apache-2.0 +# NN BENCHMARK -- Small NN Inference + +## Specification + +Scientific benchmark for TRI-27 algorithms. +Tests ternary vs FP16/BF16/GF16 on MNIST-like workload. + +## Mathematical Foundation + +``` +phi^2 + 1/phi^2 = 3 = TRINITY +``` + +## Format Conversion + +### Ternary Quantization + +``` +quantizeTernary(x: f32) -> i2 + if x > 0.5 return 1 + if x < -0.5 return -1 + return 0 +``` + +### GF16 Encode/Decode + +``` +f32ToGf16(x: f32) -> u16 + Encodes f32 to GF16 (5-bit exp, 9-bit mantissa, sign) + +gf16ToF32(x: u16) -> f32 + Decodes GF16 back to f32 +``` + +### FP16 Encode/Decode + +``` +f32ToFp16(x: f32) -> u16 + IEEE 754 half precision (5-bit exp, 10-bit mantissa) + +fp16ToF32(x: u16) -> f32 + Decodes FP16 to f32 +``` + +### BF16 Encode/Decode + +``` +f32ToBf16(x: f32) -> u16 + Brain float (8-bit exp, 7-bit mantissa) + +bf16ToF32(x: u16) -> f32 + Decodes BF16 to f32 +``` + +## Model Architecture + +``` +LayerConfig: + input_size: 784 // 28x28 MNIST + hidden_size: 128 + output_size: 10 // digits 0-9 + +denseLayer(input, weights, bias, output, in_size, out_size) -> void + Dense layer with ReLU activation + +relu(x: f32) -> f32 + ReLU activation function +``` + +## Forward Pass Implementations + +``` +forwardTernary(input, w1, b1, w2, b2, hidden, output, config) -> void + Ternary weights {-1, 0, +1} + +forwardGf16(input, w1, b1, w2, b2, hidden, output, config) -> void + GF16 quantized weights + +forwardFp16(input, w1, b1, w2, b2, hidden, output, config) -> void + FP16 quantized weights + +forwardBf16(input, w1, b1, w2, b2, hidden, output, config) -> void + BF16 quantized weights +``` + +## MNIST Loading + +``` +MnistHeader: + magic: u32 + count: u32 + rows: u32 + cols: u32 + +readMnistImages(filename, allocator) -> struct { + data: []f32, + count: usize, + rows: usize, + cols: usize, +} + +readMnistLabels(filename, allocator) -> []u8 +``` + +## Results Structure + +``` +BenchmarkResult: + format: []const u8 + accuracy: f32 + loss: f32 + bytes_per_weight: f32 +``` + +## Accuracy Computation + +``` +computeAccuracy(predictions, labels, num_classes) -> f32 + Returns percentage of correct predictions + +mseLoss(predictions, targets) -> f32 + Returns mean squared error loss +``` + +## Tests + +``` +test "NN-Bench: quantization roundtrip" { + // Verify quantization-dequantization preserves reasonable accuracy +} + +test "NN-Bench: GF16 accuracy" { + // GF16 should maintain > 90% accuracy vs f32 baseline +} + +test "NN-Bench: forward pass consistency" { + // All formats should produce similar outputs +} +``` + +## Benchmarks + +CSV output format: +``` +format,accuracy,loss,bytes_per_weight +f32,95.2,0.048,32 +f16_soft,94.8,0.052,16 +gf16_soft,94.9,0.051,16 +ternary,88.5,0.12,2 +``` + +## Expected Results + +| Format | Accuracy | Loss | Bytes/Weight | +|--------|----------|------|--------------| +| f32 | ~95.2% | 0.048 | 32 | +| f16 | ~94.8% | 0.052 | 16 | +| gf16 | ~94.9% | 0.051 | 16 | +| ternary| ~88.5% | 0.12 | 2 | + +**Key findings:** +- GF16 maintains competitive accuracy vs f32 baseline +- Ternary shows significant accuracy drop due to limited representation +- All soft implementations have overhead vs hardware f32 diff --git a/apps/website/public/t27/specs/benchmarks/gf16_bfloat16_nmse.t27 b/apps/website/public/t27/specs/benchmarks/gf16_bfloat16_nmse.t27 new file mode 100644 index 0000000000..22db88738b --- /dev/null +++ b/apps/website/public/t27/specs/benchmarks/gf16_bfloat16_nmse.t27 @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: Apache-2.0 +# GF16 vs bfloat16 NMSE PROTOCOL BENCHMARK 0 TRI-NET + +## Specification + +Standard comparison protocol for GoldenFloat GF16 vs bfloat16 numeric +fidelity, expressed as Normalised Mean Squared Error (NMSE) over named +reference distributions. Protocol-level only; no silicon numbers asserted. + +Human-readable companion: docs/GF16_BFLOAT16_NMSE_PROTOCOL.md +Schema for results manifest: schemas/nmse-protocol-v1.json +Numeric SSOT: conformance/FORMAT-SPEC-001.json + +## Mathematical Foundation + +``` +phi^2 + 1/phi^2 = 3 = TRINITY +NMSE(F) = E[ (x - Q_F(x))^2 ] / E[ x^2 ] +``` + +## Formats Under Test + +### GF16 (GoldenFloat 16-bit, primary path) + +``` +bit_layout: [ S(1) | E(6) | M(9) ] +bias: 31 +value: (-1)^S * 2^(E - 31) * (1 + M / 2^9) +rounding: round_to_nearest_ties_to_even +source: specs/numeric/gf16.t27 +``` + +### bfloat16 (IEEE 754 binary32 truncated) + +``` +bit_layout: [ S(1) | E(8) | M(7) ] +bias: 127 +value: (-1)^S * 2^(E - 127) * (1 + M / 2^7) +rounding: round_to_nearest_ties_to_even +subnormal_policy: { ieee | ftz } // must be declared in manifest +``` + +## Reference Distributions + +``` +D_NORM : x ~ N(0, 1) +D_LOG : log2|x| ~ U(-10, 10) ; sign uniform +D_RELU : x = max(0, N(0, 1)) +D_PHI : x ~ N(phi, 1/phi) ; phi = (1 + sqrt 5) / 2 +D_DEEP : 0.7 D_NORM + 0.3 D_LOG +``` + +Each distribution: 10_000_000 samples per run unless overridden. + +## Identity Witness (L5 IDENTITY) + +``` +witness phi_squared_identity { + require |phi^2 - (phi + 1)| < 1e-15 +} +witness trinity_identity { + require |phi^2 + 1/phi^2 - 3| < 1e-15 +} +``` + +A run aborts before reporting NMSE if either witness fails. + +## Tests + +``` +test "NMSE-Protocol: identity witness gates run" { + // Both witnesses must hold in IEEE f64 before any measurement. +} + +test "NMSE-Protocol: non-negative" { + // For each format and distribution, NMSE >= 0. +} + +test "NMSE-Protocol: deterministic for fixed seed" { + // Two runs with the same seed and same RNG family produce + // bit-identical per-distribution NMSE values. +} +``` + +## Invariants + +``` +invariant "NMSE non-negative" { + forall F, D : NMSE(F, D) >= 0 +} + +invariant "ratio undefined on zero baseline" { + // If E[x^2] == 0 over the sample, NMSE is undefined and the run + // reports null for that distribution. No division-by-zero is + // silently produced. + forall D : if mean_sq_ref(D) == 0 then NMSE(*, D) == null +} + +invariant "subnormal policy is declared" { + // Manifest must state ieee or ftz for BF16; missing => non-conforming. +} + +invariant "seal hash recorded" { + // Every conforming manifest cites the toolchain seal hash matching + // bootstrap/stage0/FROZEN_HASH. +} +``` + +## Benchmark + +``` +bench "GF16 vs BF16 NMSE over D_NORM, D_LOG, D_RELU, D_PHI, D_DEEP" { + seed: declared_in_manifest + samples_per_distribution: 10_000_000 + output: schemas/nmse-protocol-v1.json + reports: + - nmse_gf16[D] + - nmse_bf16[D] + - ratio[D] = nmse_gf16[D] / nmse_bf16[D] + fail_modes: + - identity_witness_fail -> abort, no manifest emitted + - subnormal_policy_missing -> abort, no manifest emitted + - seal_hash_missing -> emit manifest tagged "informational" +} +``` + +## Reporting Rules + +``` +- protocol_version: semver, MAJOR matches schema MAJOR +- ratio: dimensionless; meaningful only when both NMSE values are + non-null over the same seed and the same distribution +- direction: ratio < 1.0 favours GF16 on that distribution; + ratio > 1.0 favours BF16; ratio == 1.0 ties +- never report a ratio without naming the distribution and seed +- never compare against a commercial-NPU number under this protocol +``` + +## Cross-links + +- docs/GF16_BFLOAT16_NMSE_PROTOCOL.md (human-readable mirror) +- docs/TRI_NET_API.md (artefact consumer contract) +- schemas/nmse-protocol-v1.json (manifest schema) +- specs/numeric/gf16.t27 (GF16 SSOT) +- conformance/FORMAT-SPEC-001.json (numeric registry SSOT) +- LINEUP.md (chip-repo cross-links for runners) + +## Non-Claims (R5-HONEST) + +- This spec does not assert a measured NMSE on silicon. +- This spec does not assert GF16 wins or loses over BF16 in general. +- This spec assumes nothing about ratio sign without a distribution and seed. diff --git a/apps/website/public/t27/specs/benchmarks/ternary_vs_binary.t27 b/apps/website/public/t27/specs/benchmarks/ternary_vs_binary.t27 new file mode 100644 index 0000000000..8aac765c99 --- /dev/null +++ b/apps/website/public/t27/specs/benchmarks/ternary_vs_binary.t27 @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +# TERNARY VS BINARY -- Format Comparison Benchmark + +## Specification + +Minimal research benchmark for TRI-27 algorithms. +Compares accuracy degradation when using different number formats. + +## Mathematical Foundation + +``` +phi^2 + 1/phi^2 = 3 = TRINITY +``` + +## Format Functions + +### Quantization Functions + +``` +quantizeTernary(x: f32) -> i2 + Quantize f32 to ternary {-1, 0, +1} + +quantizeFP16(x: f32) -> u16 + Quantize f32 to FP16 (truncate mantissa to 10 bits) + +quantizeBF16(x: f32) -> u16 + Quantize f32 to BF16 (truncate mantissa to 7 bits) + +decodeFP16(bits: u16) -> f32 + Simple FP16 decode (for testing) + +decodeBF16(bits: u16) -> f32 + Simple BF16 decode (for testing) +``` + +## Neural Network + +``` +relu(x: f32) -> f32 + ReLU activation function + +mlpForward(input, w1, b1, w2, b2, hidden, output, config) -> void + Two-layer MLP forward pass with ReLU activations +``` + +## Configuration + +``` +const LayerConfig = struct { + input_size: usize, + hidden_size: usize, + output_size: usize, +}; +``` + +## Tests + +``` +test "Ternary-Binary: MLP forward baseline" { + const config = LayerConfig{ .input_size = 4, .hidden_size = 8, .output_size = 3 }; + const input = [_]f32{ 1.0, 0.0, 0.0, 0.0 }; + // Run forward and verify output matches expected +} + +test "Ternary-Binary: quantization accuracy" { + // Ternary vs FP32 difference should be < 0.5 + // FP16 vs FP32 difference should be < 0.5 +} +``` + +## Benchmarks + +``` +Experiment 1: FP32 Baseline + Baseline inference with f32 weights + +Experiment 2: Ternary Weights + Weights quantized to ternary {-1, 0, +1} + +Experiment 3: FP16 Weights (not implemented) + Requires full FP16 arithmetic + +Experiment 4: BF16 Weights (not implemented) + Requires full BF16 arithmetic + +Results Summary: + Format | Output[0] | Output[1] | Output[2] + -------+-------+------ + FP32 | x.xx | x.xx | x.xx + Ternary | x.xx | x.xx | x.xx +``` diff --git a/apps/website/public/t27/specs/boards/arty_a7.t27 b/apps/website/public/t27/specs/boards/arty_a7.t27 new file mode 100644 index 0000000000..ed25cddcb1 --- /dev/null +++ b/apps/website/public/t27/specs/boards/arty_a7.t27 @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/boards/arty_a7.t27 +// Digilent Arty A7 Board Profile +// Artix-7 XC7A35T/XC7A100T, 100MHz clock, 4 LEDs, 4 buttons, UART +// phi^2 + 1/phi^2 = 3 | TRINITY + +module BoardArtyA7 { + use base::types; + use base::ops; + + const BOARD_NAME : &str = "Digilent Arty A7-35T"; + const FPGA_FAMILY : &str = "artix7"; + const FPGA_PART_35T : &str = "xc7a35tcsg324-1"; + const FPGA_PART_100T : &str = "xc7a100tcsg324-1"; + const CLOCK_FREQ_HZ : u32 = 100_000_000; + const CLOCK_PERIOD_NS : u32 = 10; + const IO_STANDARD : &str = "LVCMOS33"; + const CONFIG_VOLTAGE : &str = "3.3"; + + const NUM_LEDS : usize = 4; + const NUM_BUTTONS : usize = 4; + const NUM_SWITCHES : usize = 4; + const HAS_UART : bool = true; + const HAS_SPI : bool = true; + + struct PinAssignment { + port_name : &str, + package_pin : &str, + iostandard : &str, + is_clock : bool, + is_input : bool, + is_output : bool, + bank : u8, + } + + const PIN_CLK : PinAssignment = PinAssignment{ + .port_name = "clk", + .package_pin = "E3", + .iostandard = "LVCMOS33", + .is_clock = true, + .is_input = true, + .is_output = false, + .bank = 0, + }; + + const PIN_RST_N : PinAssignment = PinAssignment{ + .port_name = "rst_n", + .package_pin = "C12", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = true, + .is_output = false, + .bank = 0, + }; + + const PIN_UART_TX : PinAssignment = PinAssignment{ + .port_name = "uart_tx", + .package_pin = "A9", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .bank = 0, + }; + + const PIN_UART_RX : PinAssignment = PinAssignment{ + .port_name = "uart_rx", + .package_pin = "C9", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = true, + .is_output = false, + .bank = 0, + }; + + const PIN_LED_0 : PinAssignment = PinAssignment{ + .port_name = "led[0]", + .package_pin = "R5", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .bank = 0, + }; + + const PIN_LED_1 : PinAssignment = PinAssignment{ + .port_name = "led[1]", + .package_pin = "T5", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .bank = 0, + }; + + const PIN_LED_2 : PinAssignment = PinAssignment{ + .port_name = "led[2]", + .package_pin = "T8", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .bank = 0, + }; + + const PIN_LED_3 : PinAssignment = PinAssignment{ + .port_name = "led[3]", + .package_pin = "T9", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .bank = 0, + }; + + const PIN_BTN_0 : PinAssignment = PinAssignment{ + .port_name = "btn[0]", + .package_pin = "D9", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = true, + .is_output = false, + .bank = 0, + }; + + const PIN_BTN_1 : PinAssignment = PinAssignment{ + .port_name = "btn[1]", + .package_pin = "C9", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = true, + .is_output = false, + .bank = 0, + }; + + const PIN_BTN_2 : PinAssignment = PinAssignment{ + .port_name = "btn[2]", + .package_pin = "B9", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = true, + .is_output = false, + .bank = 0, + }; + + const PIN_BTN_3 : PinAssignment = PinAssignment{ + .port_name = "btn[3]", + .package_pin = "B8", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = true, + .is_output = false, + .bank = 0, + }; + + fn count_leds() -> usize { + return NUM_LEDS; + } + + fn count_buttons() -> usize { + return NUM_BUTTONS; + } + + fn has_uart() -> bool { + return HAS_UART; + } + + fn has_spi() -> bool { + return HAS_SPI; + } + + fn clock_freq_mhz() -> u32 { + return CLOCK_FREQ_HZ / 1_000_000; + } + + test board_name_set + given name = BOARD_NAME + then name == "Digilent Arty A7-35T" + + test fpga_family_artix7 + given family = FPGA_FAMILY + then family == "artix7" + + test clock_freq_100mhz + given freq = CLOCK_FREQ_HZ + then freq == 100_000_000 + + test clock_period_10ns + given period = CLOCK_PERIOD_NS + then period == 10 + + test num_leds_is_4 + given n = NUM_LEDS + then n == 4 + + test num_buttons_is_4 + given n = NUM_BUTTONS + then n == 4 + + test has_uart_true + given result = has_uart() + then result == true + + test has_spi_true + given result = has_spi() + then result == true + + test clock_freq_mhz_100 + given mhz = clock_freq_mhz() + then mhz == 100 + + test pin_clk_is_e3 + given pin = PIN_CLK + then pin.package_pin == "E3" and pin.is_clock == true + + test pin_rst_n_is_c12 + given pin = PIN_RST_N + then pin.package_pin == "C12" and pin.is_input == true + + test pin_uart_tx_is_a9 + given pin = PIN_UART_TX + then pin.package_pin == "A9" and pin.is_output == true + + test pin_uart_rx_is_c9 + given pin = PIN_UART_RX + then pin.package_pin == "C9" and pin.is_input == true + + test pin_led0_is_r5 + given pin = PIN_LED_0 + then pin.package_pin == "R5" and pin.port_name == "led[0]" + + test pin_led3_is_t9 + given pin = PIN_LED_3 + then pin.package_pin == "T9" and pin.port_name == "led[3]" + + test pin_btn0_is_d9 + given pin = PIN_BTN_0 + then pin.package_pin == "D9" and pin.is_input == true + + test two_fpga_parts_available + given p35 = FPGA_PART_35T + and p100 = FPGA_PART_100T + then p35 != "" and p100 != "" + + invariant board_name_not_empty + assert BOARD_NAME != "" + + invariant clock_freq_positive + assert CLOCK_FREQ_HZ > 0 + + invariant clock_period_positive + assert CLOCK_PERIOD_NS > 0 + + invariant io_standard_is_lvcmos33 + assert IO_STANDARD == "LVCMOS33" + + invariant led_count_matches_constant + given n = count_leds() + assert n == NUM_LEDS and n == 4 + + invariant clk_is_clock_input + given pin = PIN_CLK + assert pin.is_clock == true and pin.is_input == true + + invariant led_pins_are_output + given p0 = PIN_LED_0.is_output + and p3 = PIN_LED_3.is_output + assert p0 == true and p3 == true + + invariant button_pins_are_input + given b0 = PIN_BTN_0.is_input + and b3 = PIN_BTN_3.is_input + assert b0 == true and b3 == true + + invariant uart_direction_correct + given tx = PIN_UART_TX.is_output + and rx = PIN_UART_RX.is_input + assert tx == true and rx == true + + invariant config_voltage_matches_iostandard + assert CONFIG_VOLTAGE == "3.3" + + invariant clock_freq_period_inverse + given freq = CLOCK_FREQ_HZ + and period = CLOCK_PERIOD_NS + assert freq == 100_000_000 and period == 10 +} diff --git a/apps/website/public/t27/specs/boards/xc7a100t_full.t27 b/apps/website/public/t27/specs/boards/xc7a100t_full.t27 new file mode 100644 index 0000000000..f04935e214 --- /dev/null +++ b/apps/website/public/t27/specs/boards/xc7a100t_full.t27 @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/boards/xc7a100t_full.t27 +// QMTECH XC7A100T-CSG324 Full Board Profile +// LED + UART + SPI + MAC debug, QMTECH Wukong expansion +// Note: 22 pins from full QMTECH XDC are missing in prjxray-db +// phi^2 + 1/phi^2 = 3 | TRINITY + +module BoardFullXC7A100T { + use base::types; + use base::ops; + + const BOARD_NAME : &str = "QMTECH XC7A100T-CSG324 (Wukong)"; + const FPGA_FAMILY : &str = "artix7"; + const FPGA_PART : &str = "xc7a100tcsg324-1"; + const CLOCK_FREQ_HZ : u32 = 12_000_000; + const CLOCK_PERIOD_NS : u32 = 83; + const IO_STANDARD : &str = "LVCMOS33"; + const CONFIG_VOLTAGE : &str = "3.3"; + + const NUM_LEDS : usize = 8; + const HAS_UART : bool = true; + const HAS_SPI : bool = true; + const HAS_MAC_DEBUG : bool = true; + const MAC_RESULT_WIDTH : usize = 32; + + struct PinAssignment { + port_name : &str, + package_pin : &str, + iostandard : &str, + is_clock : bool, + is_input : bool, + is_output : bool, + is_bidir : bool, + bank : u8, + prjxray_verified : bool, + } + + struct ClockConstraint { + port_name : &str, + period_ns : u32, + waveform_high_ns : u32, + freq_hz : u32, + } + + const PIN_CLK : PinAssignment = PinAssignment{ + .port_name = "clk", + .package_pin = "E3", + .iostandard = "LVCMOS33", + .is_clock = true, + .is_input = true, + .is_output = false, + .is_bidir = false, + .bank = 0, + .prjxray_verified = true, + }; + + const PIN_RST_N : PinAssignment = PinAssignment{ + .port_name = "rst_n", + .package_pin = "C18", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = true, + .is_output = false, + .is_bidir = false, + .bank = 0, + .prjxray_verified = false, + }; + + const PIN_UART_RX : PinAssignment = PinAssignment{ + .port_name = "uart_rx", + .package_pin = "T14", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = true, + .is_output = false, + .is_bidir = false, + .bank = 0, + .prjxray_verified = true, + }; + + const PIN_UART_TX : PinAssignment = PinAssignment{ + .port_name = "uart_tx", + .package_pin = "T15", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .is_bidir = false, + .bank = 0, + .prjxray_verified = true, + }; + + const PIN_SPI_CS : PinAssignment = PinAssignment{ + .port_name = "spi_cs", + .package_pin = "G8", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .is_bidir = false, + .bank = 0, + .prjxray_verified = false, + }; + + const PIN_SPI_SCK : PinAssignment = PinAssignment{ + .port_name = "spi_sck", + .package_pin = "G7", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .is_bidir = false, + .bank = 0, + .prjxray_verified = false, + }; + + const PIN_SPI_MOSI : PinAssignment = PinAssignment{ + .port_name = "spi_mosi", + .package_pin = "G5", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .is_bidir = false, + .bank = 0, + .prjxray_verified = false, + }; + + const PIN_SPI_MISO : PinAssignment = PinAssignment{ + .port_name = "spi_miso", + .package_pin = "G6", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = true, + .is_output = false, + .is_bidir = false, + .bank = 0, + .prjxray_verified = false, + }; + + const PIN_LED_0 : PinAssignment = PinAssignment{ + .port_name = "led[0]", + .package_pin = "H17", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .is_bidir = false, + .bank = 1, + .prjxray_verified = true, + }; + + const PIN_LED_1 : PinAssignment = PinAssignment{ + .port_name = "led[1]", + .package_pin = "K15", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .is_bidir = false, + .bank = 1, + .prjxray_verified = true, + }; + + const PIN_LED_2 : PinAssignment = PinAssignment{ + .port_name = "led[2]", + .package_pin = "J13", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .is_bidir = false, + .bank = 1, + .prjxray_verified = true, + }; + + const PIN_LED_3 : PinAssignment = PinAssignment{ + .port_name = "led[3]", + .package_pin = "N14", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .is_bidir = false, + .bank = 1, + .prjxray_verified = true, + }; + + const PIN_LED_4 : PinAssignment = PinAssignment{ + .port_name = "led[4]", + .package_pin = "R18", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .is_bidir = false, + .bank = 1, + .prjxray_verified = true, + }; + + const PIN_LED_5 : PinAssignment = PinAssignment{ + .port_name = "led[5]", + .package_pin = "U18", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .is_bidir = false, + .bank = 1, + .prjxray_verified = true, + }; + + const PIN_LED_6 : PinAssignment = PinAssignment{ + .port_name = "led[6]", + .package_pin = "T13", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .is_bidir = false, + .bank = 1, + .prjxray_verified = true, + }; + + const PIN_LED_7 : PinAssignment = PinAssignment{ + .port_name = "led[7]", + .package_pin = "T11", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .is_bidir = false, + .bank = 1, + .prjxray_verified = true, + }; + + const PIN_MAC_DONE : PinAssignment = PinAssignment{ + .port_name = "mac_done", + .package_pin = "D5", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .is_bidir = false, + .bank = 0, + .prjxray_verified = true, + }; + + const CLOCK_SYS : ClockConstraint = ClockConstraint{ + .port_name = "clk", + .period_ns = 83, + .waveform_high_ns = 41, + .freq_hz = 12_000_000, + }; + + fn has_uart() -> bool { + return HAS_UART; + } + + fn has_spi() -> bool { + return HAS_SPI; + } + + fn has_mac_debug() -> bool { + return HAS_MAC_DEBUG; + } + + fn count_leds() -> usize { + return 8; + } + + test full_board_has_uart + given result = has_uart() + then result == true + + test full_board_has_spi + given result = has_spi() + then result == true + + test full_board_has_mac_debug + given result = has_mac_debug() + then result == true + + test full_board_name_set + given name = BOARD_NAME + then name != "" + + test spi_cs_is_g8 + given pin = PIN_SPI_CS + then pin.package_pin == "G8" and pin.is_output == true + + test spi_sck_is_g7 + given pin = PIN_SPI_SCK + then pin.package_pin == "G7" and pin.is_output == true + + test spi_mosi_is_g5 + given pin = PIN_SPI_MOSI + then pin.package_pin == "G5" and pin.is_output == true + + test spi_miso_is_g6 + given pin = PIN_SPI_MISO + then pin.package_pin == "G6" and pin.is_input == true + + test mac_done_is_d5 + given pin = PIN_MAC_DONE + then pin.package_pin == "D5" and pin.is_output == true + + test rst_n_is_c18 + given pin = PIN_RST_N + then pin.package_pin == "C18" and pin.is_input == true + + test spi_pins_not_prjxray_verified + given cs = PIN_SPI_CS.prjxray_verified + and sck = PIN_SPI_SCK.prjxray_verified + and mosi = PIN_SPI_MOSI.prjxray_verified + and miso = PIN_SPI_MISO.prjxray_verified + then cs == false and sck == false and mosi == false and miso == false + + test rst_n_not_prjxray_verified + given pin = PIN_RST_N + then pin.prjxray_verified == false + + test led_pins_prjxray_verified + given v0 = PIN_LED_0.prjxray_verified + and v1 = PIN_LED_1.prjxray_verified + and v2 = PIN_LED_2.prjxray_verified + and v3 = PIN_LED_3.prjxray_verified + and v4 = PIN_LED_4.prjxray_verified + and v5 = PIN_LED_5.prjxray_verified + and v6 = PIN_LED_6.prjxray_verified + and v7 = PIN_LED_7.prjxray_verified + then v0 and v1 and v2 and v3 and v4 and v5 and v6 and v7 + + test clk_and_uart_prjxray_verified + given clk_ok = PIN_CLK.prjxray_verified + and rx_ok = PIN_UART_RX.prjxray_verified + and tx_ok = PIN_UART_TX.prjxray_verified + then clk_ok and rx_ok and tx_ok + + invariant full_board_has_all_interfaces + assert HAS_UART == true and HAS_SPI == true and HAS_MAC_DEBUG == true + + invariant clock_freq_positive + assert CLOCK_FREQ_HZ > 0 + + invariant mac_result_width_32 + assert MAC_RESULT_WIDTH == 32 + + invariant io_standard_lvcmos33 + assert IO_STANDARD == "LVCMOS33" + + invariant spi_mosi_output_miso_input + given mosi = PIN_SPI_MOSI + and miso = PIN_SPI_MISO + assert mosi.is_output == true and miso.is_input == true +} diff --git a/apps/website/public/t27/specs/boards/xc7a100t_minimal.t27 b/apps/website/public/t27/specs/boards/xc7a100t_minimal.t27 new file mode 100644 index 0000000000..cb4e5af949 --- /dev/null +++ b/apps/website/public/t27/specs/boards/xc7a100t_minimal.t27 @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/boards/xc7a100t_minimal.t27 +// QMTECH XC7A100T-CSG324 Minimal Board Profile +// Heartbeat LED + UART loopback, prjxray-verified pins only +// phi^2 + 1/phi^2 = 3 | TRINITY + +module BoardMinimalXC7A100T { + use base::types; + use base::ops; + + const BOARD_NAME : &str = "QMTECH XC7A100T-CSG324"; + const FPGA_FAMILY : &str = "artix7"; + const FPGA_PART : &str = "xc7a100tcsg324-1"; + const CLOCK_FREQ_HZ : u32 = 12_000_000; + const CLOCK_PERIOD_NS : u32 = 83; + const IO_STANDARD : &str = "LVCMOS33"; + const CONFIG_VOLTAGE : &str = "3.3"; + + const NUM_LEDS : usize = 8; + const HAS_UART : bool = true; + const HAS_SPI : bool = false; + const HAS_MAC_DEBUG : bool = false; + + struct PinAssignment { + port_name : &str, + package_pin : &str, + iostandard : &str, + is_clock : bool, + is_input : bool, + is_output : bool, + bank : u8, + } + + struct ClockConstraint { + port_name : &str, + period_ns : u32, + waveform_high_ns : u32, + freq_hz : u32, + } + + const PIN_CLK : PinAssignment = PinAssignment{ + .port_name = "clk", + .package_pin = "E3", + .iostandard = "LVCMOS33", + .is_clock = true, + .is_input = true, + .is_output = false, + .bank = 0, + }; + + const PIN_RST_N : PinAssignment = PinAssignment{ + .port_name = "rst_n", + .package_pin = "C18", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = true, + .is_output = false, + .bank = 0, + }; + + const PIN_UART_RX : PinAssignment = PinAssignment{ + .port_name = "uart_rx", + .package_pin = "T14", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = true, + .is_output = false, + .bank = 0, + }; + + const PIN_UART_TX : PinAssignment = PinAssignment{ + .port_name = "uart_tx", + .package_pin = "T15", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .bank = 0, + }; + + const PIN_LED_0 : PinAssignment = PinAssignment{ + .port_name = "led[0]", + .package_pin = "H17", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .bank = 1, + }; + + const PIN_LED_1 : PinAssignment = PinAssignment{ + .port_name = "led[1]", + .package_pin = "K15", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .bank = 1, + }; + + const PIN_LED_2 : PinAssignment = PinAssignment{ + .port_name = "led[2]", + .package_pin = "J13", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .bank = 1, + }; + + const PIN_LED_3 : PinAssignment = PinAssignment{ + .port_name = "led[3]", + .package_pin = "N14", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .bank = 1, + }; + + const PIN_LED_4 : PinAssignment = PinAssignment{ + .port_name = "led[4]", + .package_pin = "R18", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .bank = 1, + }; + + const PIN_LED_5 : PinAssignment = PinAssignment{ + .port_name = "led[5]", + .package_pin = "U18", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .bank = 1, + }; + + const PIN_LED_6 : PinAssignment = PinAssignment{ + .port_name = "led[6]", + .package_pin = "T13", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .bank = 1, + }; + + const PIN_LED_7 : PinAssignment = PinAssignment{ + .port_name = "led[7]", + .package_pin = "T11", + .iostandard = "LVCMOS33", + .is_clock = false, + .is_input = false, + .is_output = true, + .bank = 1, + }; + + const CLOCK_SYS : ClockConstraint = ClockConstraint{ + .port_name = "clk", + .period_ns = 83, + .waveform_high_ns = 41, + .freq_hz = 12_000_000, + }; + + var all_pins : [12]PinAssignment = [ + PIN_CLK, PIN_RST_N, PIN_UART_RX, PIN_UART_TX, + PIN_LED_0, PIN_LED_1, PIN_LED_2, PIN_LED_3, + PIN_LED_4, PIN_LED_5, PIN_LED_6, PIN_LED_7, + ]; + + var clocks : [1]ClockConstraint = [CLOCK_SYS]; + + fn count_pins() -> usize { + return 12; + } + + fn count_clocks() -> usize { + return 1; + } + + fn count_leds() -> usize { + return 8; + } + + fn has_uart() -> bool { + return HAS_UART; + } + + fn has_spi() -> bool { + return HAS_SPI; + } + + fn is_prjxray_verified(pin: PinAssignment) -> bool { + if (pin.package_pin == "E3") { return true; } + if (pin.package_pin == "C18") { return true; } + if (pin.package_pin == "T14") { return true; } + if (pin.package_pin == "T15") { return true; } + if (pin.package_pin == "H17") { return true; } + if (pin.package_pin == "K15") { return true; } + if (pin.package_pin == "J13") { return true; } + if (pin.package_pin == "N14") { return true; } + if (pin.package_pin == "R18") { return true; } + if (pin.package_pin == "U18") { return true; } + if (pin.package_pin == "T13") { return true; } + if (pin.package_pin == "T11") { return true; } + return false; + } + + fn find_pin_by_port(name: &str) -> PinAssignment { + var i : usize = 0; + while (i < count_pins()) { + if (all_pins[i].port_name == name) { + return all_pins[i]; + } + i = i + 1; + } + return PinAssignment{ + .port_name = "", + .package_pin = "", + .iostandard = "", + .is_clock = false, + .is_input = false, + .is_output = false, + .bank = 0, + }; + } + + test board_name_set + given name = BOARD_NAME + then name == "QMTECH XC7A100T-CSG324" + + test fpga_family_artix7 + given family = FPGA_FAMILY + then family == "artix7" + + test clock_freq_12mhz + given freq = CLOCK_FREQ_HZ + then freq == 12_000_000 + + test clock_period_83ns + given period = CLOCK_PERIOD_NS + then period == 83 + + test num_leds_is_8 + given n = NUM_LEDS + then n == 8 + + test has_uart_true + given result = has_uart() + then result == true + + test has_spi_false + given result = has_spi() + then result == false + + test has_mac_debug_false + given result = HAS_MAC_DEBUG + then result == false + + test count_pins_is_12 + given n = count_pins() + then n == 12 + + test count_clocks_is_1 + given n = count_clocks() + then n == 1 + + test pin_clk_is_e3 + given pin = PIN_CLK + then pin.package_pin == "E3" and pin.is_clock == true and pin.is_input == true + + test pin_rst_n_is_c18 + given pin = PIN_RST_N + then pin.package_pin == "C18" and pin.is_input == true and pin.is_clock == false + + test pin_uart_rx_is_t14 + given pin = PIN_UART_RX + then pin.package_pin == "T14" and pin.is_input == true + + test pin_uart_tx_is_t15 + given pin = PIN_UART_TX + then pin.package_pin == "T15" and pin.is_output == true + + test pin_led0_is_h17 + given pin = PIN_LED_0 + then pin.package_pin == "H17" and pin.is_output == true and pin.port_name == "led[0]" + + test pin_led7_is_t11 + given pin = PIN_LED_7 + then pin.package_pin == "T11" and pin.port_name == "led[7]" + + test clock_sys_period_matches_freq + given clk = CLOCK_SYS + then clk.freq_hz == 12_000_000 and clk.period_ns == 83 and clk.port_name == "clk" + + test find_clk_pin + given pin = find_pin_by_port("clk") + then pin.package_pin == "E3" + + test find_uart_tx_pin + given pin = find_pin_by_port("uart_tx") + then pin.package_pin == "T15" + + test find_nonexistent_pin_returns_empty + given pin = find_pin_by_port("nonexistent") + then pin.package_pin == "" + + test all_pins_prjxray_verified + given pin = PIN_CLK + and verified = is_prjxray_verified(pin) + then verified == true + + test rst_n_pin_prjxray_verified + given pin = PIN_RST_N + and verified = is_prjxray_verified(pin) + then verified == true + + test all_led_pins_prjxray_verified + given v0 = is_prjxray_verified(PIN_LED_0) + and v1 = is_prjxray_verified(PIN_LED_1) + and v2 = is_prjxray_verified(PIN_LED_2) + and v3 = is_prjxray_verified(PIN_LED_3) + and v4 = is_prjxray_verified(PIN_LED_4) + and v5 = is_prjxray_verified(PIN_LED_5) + and v6 = is_prjxray_verified(PIN_LED_6) + and v7 = is_prjxray_verified(PIN_LED_7) + then v0 and v1 and v2 and v3 and v4 and v5 and v6 and v7 + + invariant board_name_not_empty + assert BOARD_NAME != "" + + invariant fpga_family_not_empty + assert FPGA_FAMILY != "" + + invariant fpga_part_not_empty + assert FPGA_PART != "" + + invariant clock_freq_positive + assert CLOCK_FREQ_HZ > 0 + + invariant clock_period_positive + assert CLOCK_PERIOD_NS > 0 + + invariant io_standard_is_lvcmos33 + assert IO_STANDARD == "LVCMOS33" + + invariant all_clocks_have_period + given clk = CLOCK_SYS + assert clk.period_ns > 0 and clk.freq_hz > 0 + + invariant minimal_no_spi + assert HAS_SPI == false + + invariant minimal_no_mac_debug + assert HAS_MAC_DEBUG == false + + invariant led_count_matches_pin_count + given n = count_leds() + assert n == NUM_LEDS and n == 8 + + invariant total_pin_count_consistent + given n = count_pins() + assert n == 4 + NUM_LEDS + + invariant clk_is_clock_input + given pin = PIN_CLK + assert pin.is_clock == true and pin.is_input == true + + invariant uart_direction_correct + given rx = PIN_UART_RX + and tx = PIN_UART_TX + assert rx.is_input == true and tx.is_output == true + + invariant led_pins_are_output + given p0 = PIN_LED_0.is_output + and p7 = PIN_LED_7.is_output + assert p0 == true and p7 == true + + invariant config_voltage_matches_iostandard + assert CONFIG_VOLTAGE == "3.3" + + bench find_pin_latency + measure: nanoseconds to find_pin_by_port("uart_tx") + target: < 1000ns + + bench prjxray_verify_latency + measure: nanoseconds to is_prjxray_verified(PIN_CLK) + target: < 100ns +} diff --git a/apps/website/public/t27/specs/brain/brain.t27 b/apps/website/public/t27/specs/brain/brain.t27 new file mode 100644 index 0000000000..ad93ae84ad --- /dev/null +++ b/apps/website/public/t27/specs/brain/brain.t27 @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +# BRAIN -- S^3AI Neuroanatomy v5.1 + +## Specification + +Neuroanatomically inspired brain module for Trinity S^3AI. +Aggregator module for all brain regions. Import this file to get +access to all S^3AI neuroanatomy modules at once. + +Sacred Formula: phi^2 + 1/phi^2 = 3 = TRINITY + +## Brain Regions + +| Region | Biological Function | File | +|--------|-------------------|-------| +| Thalamus | Sensory Relay -- Railway live logs relay | thalamus_logs.zig | +| Basal Ganglia | Action Selection -- prevents duplicate task execution | basal_ganglia.zig | +| Reticular Formation | Broadcast Alerting -- event bus for all agents | reticular_formation.zig | +| Locus Coeruleus | Arousal Regulation -- backoff/timing policy | locus_coeruleus.zig | +| Amygdala | Emotional Salience -- prioritizes urgent/critical events | amygdala.zig | +| Prefrontal Cortex | Executive Function -- decision making and planning | prefrontal_cortex.zig | +| Intraparietal Sulcus | Numerical Processing -- f16/GF16/TF3 conversions | intraparietal_sulcus.zig | +| Hippocampus | Memory Persistence -- JSONL event logging | persistence.zig | +| Corpus Callosum | Telemetry -- time-series metrics aggregation | telemetry.zig | +| Microglia | Immune Surveillance -- The Constant Gardeners | microglia.zig | +| State Recovery | Crash Recovery -- Persistent state storage with versioning | state_recovery.zig | +| Hypothalamus | Administrative Control -- brain maintenance | admin.zig | +| Health History | Hippocampal Memory -- brain health snapshots | health_history.zig | +| Metrics Dashboard | Command Center -- aggregates metrics | metrics_dashboard.zig | +| Brain Alerts | Critical Health Notification -- monitors health | alerts.zig | +| Simulation | Synthetic Workload Testing -- realistic workload testing | simulation.zig | +| Observability Export | External Monitoring -- Prometheus/OpenTelemetry | observability_export.zig | +| Cerebellum | Motor Learning & Adaptive Performance | learning.zig | +| Thalamic Async Processor | Non-blocking Operations -- async task claim/release | async_processor.zig | +| Corpus Callosum (Federation) | Inter-Hemispheric Communication -- distributed multi-instance | federation.zig | +| Visual Cortex | Spatial Representation -- ASCII art brain maps | visualization.zig | +| Evolution Simulation | Deterministic Evolution -- parallel brain evolution | evolution_simulation.zig | +| Performance Dashboard | Performance Monitoring -- real-time tracking | perf_dashboard.zig | + +## Sacred Constants + +``` +phi = 1.618033988749894882 +phi^2 + phi^(-2) = 3 = TRINITY +``` + +## Tests + +``` +test "Brain-Atlas: region count" { + expect(BRAIN_ATLAS.len == 23) +} + +test "Brain-Atlas: dependency graph" { + expect(REGION_DEPENDENCIES.len == 23) +} +``` diff --git a/apps/website/public/t27/specs/brain/bus.t27 b/apps/website/public/t27/specs/brain/bus.t27 new file mode 100644 index 0000000000..7ffe0843cf --- /dev/null +++ b/apps/website/public/t27/specs/brain/bus.t27 @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +; bus.t27 -- inter-region messaging contract (spec-first) +; Message shapes and routing rules expand with region specs. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module brain-bus; + +pub const BRAIN_BUS_VERSION : u32 = 1; + +pub fn brain_bus_version() u32 { + return BRAIN_BUS_VERSION; +} + +test "brain_bus_version_stable" { + try std.testing.expectEqual(@as(u32, 1), brain_bus_version()); +} diff --git a/apps/website/public/t27/specs/brain/cognitive_loop.t27 b/apps/website/public/t27/specs/brain/cognitive_loop.t27 new file mode 100644 index 0000000000..53447efaa3 --- /dev/null +++ b/apps/website/public/t27/specs/brain/cognitive_loop.t27 @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +; cognitive_loop.t27 -- sense -> evaluate -> decide -> act -> consolidate (spec-first) +; Phase timing contract lives in phi_timing.t27; this module holds loop identity constants. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module brain-cognitive-loop; + +pub const COGNITIVE_PHASE_COUNT : u8 = 5; + +pub fn cognitive_loop_phase_count() u8 { + return COGNITIVE_PHASE_COUNT; +} + +test "cognitive_loop_five_phases" { + try std.testing.expectEqual(@as(u8, 5), cognitive_loop_phase_count()); +} diff --git a/apps/website/public/t27/specs/brain/neural_gamma.t27 b/apps/website/public/t27/specs/brain/neural_gamma.t27 new file mode 100644 index 0000000000..ff231d79e3 --- /dev/null +++ b/apps/website/public/t27/specs/brain/neural_gamma.t27 @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +# NEURAL GAMMA -- Consciousness and Golden Ratio + +## Specification + +This module explores how neural gamma rhythm (40 Hz) relates to +Barbero-Immirzi parameter gamma = phi^-^3 and consciousness thresholds. + +## Mathematical Foundation + +Golden Ratio: + phi = (1 + sqrt5)/2 ~= 1.61803398874989482 + gamma = phi^-^3 ~= 0.23606797749978969641 + +Trinity Identity: + phi^2 + phi^-^2 = 3 + +## Hypotheses + +1. Neural gamma rhythm (40 Hz) encodes via phi and gamma +2. Consciousness threshold C_thr = gamma * phi^2 ~= 0.618 (phi^-^1) +3. Quantum coherence time tau_phi = phi^4 * gamma * t_Planck +4. Gamma synchrony is fundamental to consciousness + +## Constants + +``` +PHI = 1.61803398874989482 +GAMMA = phi^-^3 ~= 0.23606797749978969641 +TRINITY = phi^2 + phi^-^2 = 3 +PI = 3.14159265358979323846 +GAMMA_FREQ = 40.0 +PLANCK_TIME = 5.391247e-44 +``` + +## Consciousness States + +``` +enum ConsciousnessState { + unconscious = 0, + minimal = 1, + normal = 2, + enhanced = 3, +} +``` + +## Key Functions + +``` +consciousnessThreshold() -> f64 + Returns gamma * phi^2 ~= 0.618 = phi^-^1 + +neuralGammaFrequency() -> f64 + Returns f_gamma = phi^3 * pi / gamma ~= 40 Hz + +bindingWindow() -> f64 + Returns 2 * T_gamma ~= 50 ms + +integrationTime() -> f64 + Returns 3 * T_gamma * phi ~= 100-200 ms + +consciousnessEmergence(gamma_sync, integrated_info, workspace_saliency) -> ConsciousnessState + Returns consciousness level based on synchrony and integration +``` + +## Tests + +``` +test "Neural-gamma: phi cubed and gamma" { + expect(PHI_CUBED ~= 4.23606797749978969641) + expect(GAMMA ~= 0.23606797749978969641) + expect(PHI_CUBED - 4.0 ~= GAMMA) +} + +test "Neural-gamma: TRINITY identity" { + expect(TRINITY ~= 3.0) +} + +test "Neural-gamma: consciousness threshold" { + expect(consciousnessThreshold() ~= 0.618) + expect(consciousnessThresholdPhiInv() ~= 0.618) +} + +test "Neural-gamma: gamma frequency" { + expect(neuralGammaFrequency() > 50.0) + expect(neuralGammaFrequency() < 60.0) +} +``` diff --git a/apps/website/public/t27/specs/brain/phi_timing.t27 b/apps/website/public/t27/specs/brain/phi_timing.t27 new file mode 100644 index 0000000000..b2d1f6ebc5 --- /dev/null +++ b/apps/website/public/t27/specs/brain/phi_timing.t27 @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 +; phi_timing.t27 -- phi-structured cognitive cycle timing (spec-first) +; Phase duration ratios follow INV-1; integer ms sum may differ slightly from 3*base_ms. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module brain-phi-timing; + +// ============================================================================ +// Constants +// ============================================================================ +pub const PHI : f64 = 1.6180339887498948482; +pub const PHI_INV : f64 = 0.6180339887498948482; +pub const PHI_INV_SQ : f64 = 0.3819660112501051518; +pub const TRINITY : f64 = 3.0; +pub const DEFAULT_BASE_MS : u64 = 146; + +// ============================================================================ +// Types +// ============================================================================ +pub const Phase = enum { sense, evaluate, decide, act, consolidate }; + +pub const PhiTiming = struct { + base_ms: u64, +}; + +// ============================================================================ +// Functions +// ============================================================================ +pub fn phi_timing_init() PhiTiming { + return PhiTiming{ .base_ms = DEFAULT_BASE_MS }; +} + +pub fn phi_timing_phase_duration(timing: PhiTiming, phase: Phase) u64 { + const base = @as(f64, @floatFromInt(timing.base_ms)); + return switch (phase) { + .sense => @intFromFloat(base * PHI_INV_SQ), + .evaluate => @intFromFloat(base * PHI_INV), + .decide => timing.base_ms, + .act => @intFromFloat(base * PHI_INV), + .consolidate => @intFromFloat(base * PHI_INV_SQ), + }; +} + +pub fn phi_timing_total_cycle_ms_float(timing: PhiTiming) f64 { + const base = @as(f64, @floatFromInt(timing.base_ms)); + return base * (PHI_INV_SQ + PHI_INV + 1.0 + PHI_INV + PHI_INV_SQ); +} + +pub fn phi_timing_total_cycle(timing: PhiTiming) u64 { + return phi_timing_phase_duration(timing, .sense) + + phi_timing_phase_duration(timing, .evaluate) + + phi_timing_phase_duration(timing, .decide) + + phi_timing_phase_duration(timing, .act) + + phi_timing_phase_duration(timing, .consolidate); +} + +// ============================================================================ +// Tests +// ============================================================================ +test "phi_timing_sum_equals_trinity_float" { + const timing = phi_timing_init(); + const base = @as(f64, @floatFromInt(timing.base_ms)); + const expected = base * TRINITY; + const actual = phi_timing_total_cycle_ms_float(timing); + try std.testing.expectApproxEqAbs(expected, actual, 0.001); +} + +test "phi_timing_decide_is_base" { + const timing = phi_timing_init(); + try std.testing.expectEqual(timing.base_ms, phi_timing_phase_duration(timing, .decide)); +} + +test "phi_timing_ratio_decide_over_sense_equals_phi_sq" { + const timing = phi_timing_init(); + const decide = @as(f64, @floatFromInt(phi_timing_phase_duration(timing, .decide))); + const sense = @as(f64, @floatFromInt(phi_timing_phase_duration(timing, .sense))); + const ratio = decide / sense; + try std.testing.expectApproxEqAbs(PHI * PHI, ratio, 0.15); +} diff --git a/apps/website/public/t27/specs/brain/unified_state.t27 b/apps/website/public/t27/specs/brain/unified_state.t27 new file mode 100644 index 0000000000..cdfde3c3c3 --- /dev/null +++ b/apps/website/public/t27/specs/brain/unified_state.t27 @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +; unified_state.t27 -- Trinity Brain unified state (spec-first) +; Normative types for Strand VI. Zig/C/Verilog are generated under gen/ via t27c. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module brain-unified-state; + +// ============================================================================ +// Constants +// ============================================================================ +pub const PHI : f64 = 1.6180339887498948482; +pub const PHI_INV : f64 = 0.6180339887498948482; +pub const PHI_SQ : f64 = 2.6180339887498948482; +pub const PHI_INV_SQ : f64 = 0.3819660112501051518; +pub const TRINITY : f64 = 3.0; +pub const REGION_COUNT : u8 = 27; +pub const LAYER_COUNT : u8 = 3; +pub const REGIONS_PER_LAYER : u8 = 9; + +// ============================================================================ +// Types +// ============================================================================ +pub const Layer = enum { cognitive, limbic, brainstem }; + +pub const ArousalLevel = enum { sleep, rest, alert, crisis }; + +pub const ConsciousnessState = struct { + awareness: f32, + self_model_active: bool, + default_mode: bool, +}; + +pub const Mood = struct { + valence: f32, + arousal: f32, + dominance: f32, +}; + +pub const BrainState = struct { + // Cognitive layer + consciousness: ConsciousnessState, + mood: Mood, + conflict_level: f32, + + // Limbic layer + arousal: ArousalLevel, + fear_level: f32, + reward_signal: f32, + + // Brainstem layer + phi_coherence: f64, + cycle_count: u64, + timestamp: i64, +}; + +// ============================================================================ +// Functions +// ============================================================================ +pub fn brain_state_init() BrainState { + return BrainState{ + .consciousness = ConsciousnessState{ + .awareness = 0.0, + .self_model_active = false, + .default_mode = true, + }, + .mood = Mood{ .valence = 0.0, .arousal = 0.0, .dominance = 0.0 }, + .conflict_level = 0.0, + .arousal = .rest, + .fear_level = 0.0, + .reward_signal = 0.0, + .phi_coherence = PHI_INV, + .cycle_count = 0, + .timestamp = 0, + }; +} + +pub fn brain_state_phi_coherence(state: BrainState) f64 { + return state.phi_coherence; +} + +// ============================================================================ +// Tests +// ============================================================================ +test "brain_state_init_defaults" { + const state = brain_state_init(); + try std.testing.expectEqual(@as(ArousalLevel, .rest), state.arousal); + try std.testing.expectApproxEqAbs(PHI_INV, state.phi_coherence, 0.001); +} + +test "brain_region_count_is_3_cubed" { + try std.testing.expectEqual(@as(u8, 27), REGION_COUNT); + try std.testing.expectEqual(REGION_COUNT, LAYER_COUNT * REGIONS_PER_LAYER); +} diff --git a/apps/website/public/t27/specs/bus/pubsub.t27 b/apps/website/public/t27/specs/bus/pubsub.t27 new file mode 100644 index 0000000000..a299f394de --- /dev/null +++ b/apps/website/public/t27/specs/bus/pubsub.t27 @@ -0,0 +1,669 @@ +// SPDX-License-Identifier: Apache-2.0 +// bus/pubsub.t27 — Publish/Subscribe Patterns +// Pub/sub interface for event-driven communication +// φ² + 1/φ² = 3 | TRINITY + +module bus-pubsub; + +// ============================================================================ +// Imports +// ============================================================================ + +use tritype-base::usize; +use bus-schema::Event; +use bus-schema::Subscription; +use bus-schema::TopicPattern; +use bus-schema::BusState; +use bus-schema::topic_pattern_create; +use bus-schema::event_create; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default publish timeout in milliseconds +pub const DEFAULT_PUBLISH_TIMEOUT_MS : usize = 5000; + +/// Default subscribe timeout in milliseconds +pub const DEFAULT_SUBSCRIBE_TIMEOUT_MS : usize = 5000; + +/// Maximum pending publishes +pub const MAX_PENDING_PUBLISHES : usize = 10000; + +/// Maximum topics per bus +pub const MAX_TOPICS : usize = 1000; + +/// Default retry attempts for publish +pub const DEFAULT_PUBLISH_RETRIES : usize = 3; + +/// Topic hierarchy separator +pub const TOPIC_SEPARATOR : [1]u8 = "/"; + +/// Topic wildcard for single level +pub const SINGLE_LEVEL_WILDCARD : [1]u8 = "+"; + +/// Topic wildcard for multi-level +pub const MULTI_LEVEL_WILDCARD : [1]u8 = "#"; + +// ============================================================================ +// Types +// ============================================================================ + +/// Publish result +pub const PublishResult = struct { + success : bool, + event_id : usize, + error : []u8, +}; + +/// Subscribe result +pub const SubscribeResult = struct { + success : bool, + subscription_id : usize, + error : []u8, +}; + +/// Unsubscribe result +pub const UnsubscribeResult = struct { + success : bool, + subscriptions_removed : usize, + error : []u8, +}; + +/// Topic tree node +pub const TopicNode = struct { + topic : []u8, + subscriptions : []Subscription, + children : []TopicNode, +}; + +/// Pub/sub configuration +pub const PubSubConfig = struct { + max_pending_publishes : usize, + max_topics : usize, + max_subscribers_per_topic : usize, + enable_wildcards : bool, +}; + +/// Subscriber context +pub const SubscriberContext = struct { + subscriber_id : []u8, + topic_pattern : TopicPattern, + callback : []u8, +}; + +/// Topic match result +pub const TopicMatchResult = struct { + matches : bool, + matched_topic : []u8, + wildcard_matched : bool, +}; + +/// Bus info +pub const BusInfo = struct { + state : BusState, + topic_count : usize, + subscriber_count : usize, + pending_publish_count : usize, +}; + +/// Delivery queue +pub const DeliveryQueue = struct { + events : []Event, + capacity : usize, + head : usize, + tail : usize, +}; + +/// Pending publish +pub const PendingPublish = struct { + event : Event, + retries : usize, + created_at : usize, +}; + +/// Topic hierarchy +pub const TopicHierarchy = struct { + root : TopicNode, + separator : []u8, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create publish result +pub fn publish_result_create(success: bool, event_id: usize) PublishResult { + return PublishResult{ + .success = success, + .event_id = event_id, + .error = "", + }; +} + +/// Create publish result with error +pub fn publish_result_error(error: []u8) PublishResult { + return PublishResult{ + .success = false, + .event_id = 0, + .error = error, + }; +} + +/// Create subscribe result +pub fn subscribe_result_create(success: bool, subscription_id: usize) SubscribeResult { + return SubscribeResult{ + .success = success, + .subscription_id = subscription_id, + .error = "", + }; +} + +/// Create subscribe result with error +pub fn subscribe_result_error(error: []u8) SubscribeResult { + return SubscribeResult{ + .success = false, + .subscription_id = 0, + .error = error, + }; +} + +/// Create unsubscribe result +pub fn unsubscribe_result_create(success: bool, removed: usize) UnsubscribeResult { + return UnsubscribeResult{ + .success = success, + .subscriptions_removed = removed, + .error = "", + }; +} + +/// Create unsubscribe result with error +pub fn unsubscribe_result_error(error: []u8) UnsubscribeResult { + return UnsubscribeResult{ + .success = false, + .subscriptions_removed = 0, + .error = error, + }; +} + +/// Create topic node +pub fn topic_node_create(topic: []u8) TopicNode { + return TopicNode{ + .topic = topic, + .subscriptions = &[_]Subscription{}, + .children = &[_]TopicNode{}, + }; +} + +/// Create pub/sub config +pub fn pubsub_config_default() PubSubConfig { + return PubSubConfig{ + .max_pending_publishes = MAX_PENDING_PUBLISHES, + .max_topics = MAX_TOPICS, + .max_subscribers_per_topic = 100, + .enable_wildcards = true, + }; +} + +/// Create subscriber context +pub fn subscriber_context_create(subscriber_id: []u8, topic_pattern: TopicPattern, callback: []u8) SubscriberContext { + return SubscriberContext{ + .subscriber_id = subscriber_id, + .topic_pattern = topic_pattern, + .callback = callback, + }; +} + +/// Create topic match result +pub fn topic_match_result_create(matches: bool, topic: []u8) TopicMatchResult { + return TopicMatchResult{ + .matches = matches, + .matched_topic = topic, + .wildcard_matched = false, + }; +} + +/// Create bus info +pub fn bus_info_create(state: BusState) BusInfo { + return BusInfo{ + .state = state, + .topic_count = 0, + .subscriber_count = 0, + .pending_publish_count = 0, + }; +} + +/// Create delivery queue +pub fn delivery_queue_create(capacity: usize) DeliveryQueue { + return DeliveryQueue{ + .events = &[_]Event{} ** capacity, + .capacity = capacity, + .head = 0, + .tail = 0, + }; +} + +/// Create pending publish +pub fn pending_publish_create(event: Event) PendingPublish { + return PendingPublish{ + .event = event, + .retries = 0, + .created_at = 0, + }; +} + +/// Match topic against pattern +pub fn match_topic_pattern(topic: []u8, pattern: TopicPattern) TopicMatchResult { + if (pattern.is_wildcard) { + return topic_match_result_create(true, topic); + } + return topic_match_result_create(topic == pattern.pattern, topic); +} + +/// Check if topic is hierarchical +pub fn is_hierarchical_topic(topic: []u8) bool { + for (topic) |c| { + if (c == TOPIC_SEPARATOR[0]) { + return true; + } + } + return false; +} + +/// Get topic parent +pub fn get_topic_parent(topic: []u8) []u8 { + var last_sep : usize = topic.len; + for (0..topic.len) |i| { + if (topic[i] == TOPIC_SEPARATOR[0]) { + last_sep = i; + } + } + if (last_sep == topic.len) { + return ""; + } + return topic[0..last_sep]; +} + +/// Get topic depth +pub fn get_topic_depth(topic: []u8) usize { + var depth : usize = 1; + for (topic) |c| { + if (c == TOPIC_SEPARATOR[0]) { + depth += 1; + } + } + return depth; +} + +/// Join topic parts +pub fn join_topic(parts: [][]u8) []u8 { + if (parts.len == 0) { + return ""; + } + var result : []u8 = parts[0]; + for (1..parts.len) |i| { + result = result ++ TOPIC_SEPARATOR ++ parts[i]; + } + return result; +} + +/// Split topic into parts +pub fn split_topic(topic: []u8) [][]u8 { + var parts : [][]u8 = undefined; + var current : []u8 = undefined; + for (topic) |c| { + if (c == TOPIC_SEPARATOR[0]) { + if (current.len > 0) { + parts = parts ++ &[_][]u8{ current }; + } + current = &[_]u8{}; + } else { + current = current ++ &[_]u8{ c }; + } + } + if (current.len > 0) { + parts = parts ++ &[_][]u8{ current }; + } + return parts; +} + +/// Check if delivery queue is empty +pub fn is_delivery_queue_empty(queue: DeliveryQueue) bool { + return queue.head == queue.tail; +} + +/// Check if delivery queue is full +pub fn is_delivery_queue_full(queue: DeliveryQueue) bool { + return ((queue.tail + 1) % queue.capacity) == queue.head; +} + +/// Add event to delivery queue +pub fn enqueue_delivery(queue: DeliveryQueue, event: Event) DeliveryQueue { + queue.events[queue.tail] = event; + const new_tail = (queue.tail + 1) % queue.capacity; + return DeliveryQueue{ + .events = queue.events, + .capacity = queue.capacity, + .head = queue.head, + .tail = new_tail, + }; +} + +/// Remove event from delivery queue +pub fn dequeue_delivery(queue: DeliveryQueue) DeliveryQueue { + if (is_delivery_queue_empty(queue)) { + return queue; + } + const new_head = (queue.head + 1) % queue.capacity; + return DeliveryQueue{ + .events = queue.events, + .capacity = queue.capacity, + .head = new_head, + .tail = queue.tail, + }; +} + +/// Check if pattern contains wildcard +pub fn pattern_contains_wildcard(pattern: TopicPattern) bool { + return pattern.pattern == "*" or pattern.pattern == "+" or pattern.pattern == "#"; +} + +/// Expand wildcard pattern to matching topics +pub fn expand_wildcard_pattern(pattern: TopicPattern, known_topics: [][]u8) [][]u8 { + if (!pattern.is_wildcard) { + return &[_][]u8{ pattern.pattern }; + } + var result : [][]u8 = undefined; + for (known_topics) |topic| { + if (match_topic_pattern(topic, pattern).matches) { + result = result ++ &[_][]u8{ topic }; + } + } + return result; +} + +/// Get publish result string +pub fn publish_result_to_string(result: PublishResult) []u8 { + if (result.success) { + return "success"; + } else { + return "error: " ++ result.error; + }; +} + +/// Get subscribe result string +pub fn subscribe_result_to_string(result: SubscribeResult) []u8 { + if (result.success) { + return "success"; + } else { + return "error: " ++ result.error; + }; +} + +/// Get unsubscribe result string +pub fn unsubscribe_result_to_string(result: UnsubscribeResult) []u8 { + if (result.success) { + return "success"; + } else { + return "error: " ++ result.error; + }; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "bus_publish_result_create" { + const result = publish_result_create(true, 123); + try std.testing.expect(result.success == true); +} + +test "bus_subscribe_result_create" { + const result = subscribe_result_create(true, 456); + try std.testing.expect(result.success == true); +} + +test "bus_unsubscribe_result_create" { + const result = unsubscribe_result_create(true, 3); + try std.testing.expect(result.subscriptions_removed == 3); +} + +test "bus_topic_node_create" { + const node = topic_node_create("test/topic"); + try std.testing.expectEqual(@as(usize, node.topic.len), @as(usize, 10)); +} + +test "bus_pubsub_config_default" { + const config = pubsub_config_default(); + try std.testing.expect(config.max_topics == MAX_TOPICS); +} + +test "bus_subscriber_context_create" { + const pattern = topic_pattern_create("test/*"); + const ctx = subscriber_context_create("sub1", pattern, "handler"); + try std.testing.expect(ctx.subscriber_id == "sub1"); +} + +test "bus_topic_match_result_create" { + const result = topic_match_result_create(true, "test/topic"); + try std.testing.expect(result.matches == true); +} + +test "bus_bus_info_create" { + const info = bus_info_create(.running); + try std.testing.expect(info.state == .running); +} + +test "bus_delivery_queue_create" { + const queue = delivery_queue_create(10); + try std.testing.expect(queue.capacity == 10); +} + +test "bus_pending_publish_create" { + const event = event_create(.lsp_request, "test", ""); + const pending = pending_publish_create(event); + try std.testing.expect(pending.retries == 0); +} + +test "bus_match_topic_pattern" { + const pattern = topic_pattern_create("test/topic"); + const result = match_topic_pattern("test/topic", pattern); + try std.testing.expect(result.matches == true); +} + +test "bus_is_hierarchical_topic" { + try std.testing.expect(is_hierarchical_topic("a/b/c")); +} + +test "bus_get_topic_parent" { + const parent = get_topic_parent("a/b/c"); + try std.testing.expect(parent == "a/b"); +} + +test "bus_get_topic_depth" { + const depth = get_topic_depth("a/b/c"); + try std.testing.expect(depth == 3); +} + +test "bus_is_delivery_queue_empty" { + const queue = delivery_queue_create(10); + try std.testing.expect(is_delivery_queue_empty(queue)); +} + +test "bus_pattern_contains_wildcard" { + const pattern = topic_pattern_create("*"); + try std.testing.expect(pattern_contains_wildcard(pattern)); +} + +test "bus_publish_result_to_string" { + const result = publish_result_create(true, 123); + const str = publish_result_to_string(result); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 7)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant default_publish_timeout_positive { + // DEFAULT_PUBLISH_TIMEOUT_MS is positive + @compileAssert(DEFAULT_PUBLISH_TIMEOUT_MS > 0); +} + +invariant default_subscribe_timeout_positive { + // DEFAULT_SUBSCRIBE_TIMEOUT_MS is positive + @compileAssert(DEFAULT_SUBSCRIBE_TIMEOUT_MS > 0); +} + +invariant max_pending_publishes_positive { + // MAX_PENDING_PUBLISHES is positive + @compileAssert(MAX_PENDING_PUBLISHES > 0); +} + +invariant max_topics_positive { + // MAX_TOPICS is positive + @compileAssert(MAX_TOPICS > 0); +} + +invariant default_publish_retries_valid { + // DEFAULT_PUBLISH_RETRIES is in valid range + @compileAssert(DEFAULT_PUBLISH_RETRIES > 0); +} + +invariant delivery_queue_capacity_positive { + // DeliveryQueue capacity is positive + @compileAssert(true); +} + +invariant pending_publish_has_event { + // PendingPublish has valid event + @compileAssert(true); +} + +invariant topic_separator_single_char { + // TOPIC_SEPARATOR is single character + @compileAssert(TOPIC_SEPARATOR.len == 1); +} + +invariant wildcard_patterns_valid { + // Wildcard patterns are valid + @compileAssert(SINGLE_LEVEL_WILDCARD.len == 1); + @compileAssert(MULTI_LEVEL_WILDCARD.len == 1); +} + +invariant subscriber_context_has_id { + // SubscriberContext has non-empty subscriber_id + @compileAssert(true); +} + +invariant publish_result_valid { + // PublishResult has valid fields + @compileAssert(true); +} + +invariant subscribe_result_valid { + // SubscribeResult has valid fields + @compileAssert(true); +} + +invariant unsubscribe_result_valid { + // UnsubscribeResult has valid fields + @compileAssert(true); +} + +invariant bus_info_valid { + // BusInfo has valid state + @compileAssert(true); +} + +invariant topic_node_valid { + // TopicNode has valid topic + @compileAssert(true); +} + +invariant topic_match_result_valid { + // TopicMatchResult has valid matches flag + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "bus_publish_result_create_latency" { + // Measure: cycles for publish result creation + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : PublishResult = undefined; + for (0..1000) |_| { + result = publish_result_create(true, 123); + } + _ = result; +} + +bench "bus_subscribe_result_create_latency" { + // Measure: cycles for subscribe result creation + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : SubscribeResult = undefined; + for (0..1000) |_| { + result = subscribe_result_create(true, 456); + } + _ = result; +} + +bench "bus_topic_node_create_latency" { + // Measure: cycles for topic node creation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : TopicNode = undefined; + for (0..1000) |_| { + result = topic_node_create("test/topic"); + } + _ = result; +} + +bench "bus_match_topic_pattern_latency" { + // Measure: cycles for pattern matching + // Target: < 50 cycles + @setEvalBranchQuota(10000); + const pattern = topic_pattern_create("test"); + var result : TopicMatchResult = undefined; + for (0..1000) |_| { + result = match_topic_pattern("test", pattern); + } + _ = result; +} + +bench "bus_get_topic_parent_latency" { + // Measure: cycles for parent topic calculation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = get_topic_parent("a/b/c"); + } + _ = result; +} + +bench "bus_get_topic_depth_latency" { + // Measure: cycles for topic depth calculation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : usize = 0; + for (0..1000) |_| { + result = get_topic_depth("a/b/c"); + } + _ = result; +} + +bench "bus_is_delivery_queue_empty_latency" { + // Measure: cycles for queue empty check + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..1000) |_| { + result = is_delivery_queue_empty(delivery_queue_create(10)); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/bus/schema.t27 b/apps/website/public/t27/specs/bus/schema.t27 new file mode 100644 index 0000000000..6bf322f93a --- /dev/null +++ b/apps/website/public/t27/specs/bus/schema.t27 @@ -0,0 +1,656 @@ +// SPDX-License-Identifier: Apache-2.0 +// bus/schema.t27 — Event Type Definitions +// Core event types and structures for the event bus +// φ² + 1/φ² = 3 | TRINITY + +module bus-schema; + +// ============================================================================ +// Imports +// ============================================================================ + +use tritype-base::usize; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default event bus channel size +pub const DEFAULT_CHANNEL_SIZE : usize = 1000; + +/// Maximum event payload size in bytes +pub const MAX_PAYLOAD_SIZE : usize = 1048576; + +/// Default subscriber buffer size +pub const DEFAULT_SUBSCRIBER_BUFFER : usize = 100; + +/// Maximum number of subscribers per topic +pub const MAX_SUBSCRIBERS_PER_TOPIC : usize = 100; + +/// Default event TTL in milliseconds +pub const DEFAULT_EVENT_TTL_MS : usize = 60000; + +/// Wildcard topic pattern +pub const WILDCARD_TOPIC : [1]u8 = "*"; + +/// Topic separator +pub const TOPIC_SEPARATOR : [1]u8 = "/"; + +// ============================================================================ +// Types +// ============================================================================ + +/// Event type +pub const EventType = enum(u8) { + lsp_request = 0, + lsp_response = 1, + lsp_notification = 2, + provider_request = 3, + provider_response = 4, + provider_stream_start = 5, + provider_stream_chunk = 6, + provider_stream_end = 7, + provider_stream_error = 8, + system_error = 9, + system_info = 10, + custom = 11, +}; + +/// Event priority +pub const EventPriority = enum(u8) { + low = 0, + normal = 1, + high = 2, + urgent = 3, +}; + +/// Event +pub const Event = struct { + id : usize, + type : EventType, + topic : []u8, + payload : []u8, + timestamp : usize, + priority : EventPriority, + ttl_ms : usize, +}; + +/// Event filter +pub const EventFilter = struct { + event_types : []EventType, + topics : [][]u8, + min_priority : EventPriority, +}; + +/// Event result +pub const EventResult = struct { + success : bool, + error : []u8, +}; + +/// Event batch +pub const EventBatch = struct { + events : []Event, + batch_id : usize, +}; + +/// Event metadata +pub const EventMetadata = struct { + source : []u8, + correlation_id : []u8, + reply_to : []u8, +}; + +/// Topic pattern +pub const TopicPattern = struct { + pattern : []u8, + is_wildcard : bool, +}; + +/// Event stats +pub const EventStats = struct { + events_published : usize, + events_delivered : usize, + events_dropped : usize, + subscribers_count : usize, + topics_count : usize, +}; + +/// Bus state +pub const BusState = enum(u8) { + stopped = 0, + starting = 1, + running = 2, + stopping = 3, + error = 4, +}; + +/// Bus configuration +pub const BusConfig = struct { + channel_size : usize, + max_payload_size : usize, + default_ttl_ms : usize, + enable_metrics : bool, +}; + +/// Event acknowledgment +pub const EventAck = enum(u8) { + none = 0, + received = 1, + processed = 2, + failed = 3, +}; + +/// Delivery mode +pub const DeliveryMode = enum(u8) { + fire_and_forget = 0, + at_least_once = 1, + exactly_once = 2, +}; + +/// Subscription +pub const Subscription = struct { + id : usize, + topic_pattern : TopicPattern, + handler : []u8, + filter : EventFilter, + delivery_mode : DeliveryMode, + buffer_size : usize, +}; + +/// Subscription info +pub const SubscriptionInfo = struct { + subscription_id : usize, + subscriber_id : []u8, + topic : []u8, + created_at : usize, +}; + +/// Unsubscription result +pub const UnsubscribeResult = struct { + success : bool, + subscriptions_removed : usize, + error : []u8, +}; + +/// Bus error +pub const BusError = struct { + code : i32, + message : []u8, + event_id : usize, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create event +pub fn event_create(event_type: EventType, topic: []u8, payload: []u8) Event { + return Event{ + .id = 0, + .type = event_type, + .topic = topic, + .payload = payload, + .timestamp = 0, + .priority = .normal, + .ttl_ms = DEFAULT_EVENT_TTL_MS, + }; +} + +/// Create event with priority +pub fn event_with_priority(event_type: EventType, topic: []u8, payload: []u8, priority: EventPriority) Event { + return Event{ + .id = 0, + .type = event_type, + .topic = topic, + .payload = payload, + .timestamp = 0, + .priority = priority, + .ttl_ms = DEFAULT_EVENT_TTL_MS, + }; +} + +/// Create event filter +pub fn event_filter_create(topics: [][]u8, min_priority: EventPriority) EventFilter { + return EventFilter{ + .event_types = &[_]EventType{}, + .topics = topics, + .min_priority = min_priority, + }; +} + +/// Create event result +pub fn event_result_create(success: bool) EventResult { + return EventResult{ + .success = success, + .error = "", + }; +} + +/// Create event result with error +pub fn event_result_error(message: []u8) EventResult { + return EventResult{ + .success = false, + .error = message, + }; +} + +/// Create topic pattern +pub fn topic_pattern_create(pattern: []u8) TopicPattern { + return TopicPattern{ + .pattern = pattern, + .is_wildcard = pattern == WILDCARD_TOPIC, + }; +} + +/// Create bus config +pub fn bus_config_default() BusConfig { + return BusConfig{ + .channel_size = DEFAULT_CHANNEL_SIZE, + .max_payload_size = MAX_PAYLOAD_SIZE, + .default_ttl_ms = DEFAULT_EVENT_TTL_MS, + .enable_metrics = true, + }; +} + +/// Create subscription +pub fn subscription_create(topic_pattern: []u8, handler: []u8) Subscription { + return Subscription{ + .id = 0, + .topic_pattern = topic_pattern_create(topic_pattern), + .handler = handler, + .filter = event_filter_create(&[_][]u8{}, .low), + .delivery_mode = .fire_and_forget, + .buffer_size = DEFAULT_SUBSCRIBER_BUFFER, + }; +} + +/// Create subscription info +pub fn subscription_info_create(subscription_id: usize, subscriber_id: []u8, topic: []u8) SubscriptionInfo { + return SubscriptionInfo{ + .subscription_id = subscription_id, + .subscriber_id = subscriber_id, + .topic = topic, + .created_at = 0, + }; +} + +/// Create unsubscription result +pub fn unsubscribe_result_create(success: bool, removed: usize) UnsubscribeResult { + return UnsubscribeResult{ + .success = success, + .subscriptions_removed = removed, + .error = "", + }; +} + +/// Create bus error +pub fn bus_error_create(code: i32, message: []u8) BusError { + return BusError{ + .code = code, + .message = message, + .event_id = 0, + }; +} + +/// Create event stats +pub fn event_stats_create() EventStats { + return EventStats{ + .events_published = 0, + .events_delivered = 0, + .events_dropped = 0, + .subscribers_count = 0, + .topics_count = 0, + }; +} + +/// Get event type string +pub fn event_type_to_string(event_type: EventType) []u8 { + return switch (event_type) { + .lsp_request => "lsp_request", + .lsp_response => "lsp_response", + .lsp_notification => "lsp_notification", + .provider_request => "provider_request", + .provider_response => "provider_response", + .provider_stream_start => "provider_stream_start", + .provider_stream_chunk => "provider_stream_chunk", + .provider_stream_end => "provider_stream_end", + .provider_stream_error => "provider_stream_error", + .system_error => "system_error", + .system_info => "system_info", + .custom => "custom", + }; +} + +/// Get priority string +pub fn priority_to_string(priority: EventPriority) []u8 { + return switch (priority) { + .low => "low", + .normal => "normal", + .high => "high", + .urgent => "urgent", + }; +} + +/// Check if topic pattern is wildcard +pub fn is_wildcard_pattern(pattern: TopicPattern) bool { + return pattern.is_wildcard; +} + +/// Check if topic matches pattern +pub fn topic_matches_pattern(topic: []u8, pattern: TopicPattern) bool { + if (pattern.is_wildcard) { + return true; + } + return topic == pattern.pattern; +} + +/// Check if event matches filter +pub fn event_matches_filter(event: Event, filter: EventFilter) bool { + var matches_type = false; + for (filter.event_types) |et| { + if (event.type == et) { + matches_type = true; + } + } + if (filter.event_types.len == 0) { + matches_type = true; + } + + var matches_topic = false; + for (filter.topics) |topic| { + if (event.topic == topic) { + matches_topic = true; + } + } + if (filter.topics.len == 0) { + matches_topic = true; + } + + const matches_priority = event.priority >= filter.min_priority; + return matches_type and matches_topic and matches_priority; +} + +/// Check if bus is running +pub fn is_bus_running(state: BusState) bool { + return state == .running; +} + +/// Get bus state string +pub fn bus_state_to_string(state: BusState) []u8 { + return switch (state) { + .stopped => "stopped", + .starting => "starting", + .running => "running", + .stopping => "stopping", + .error => "error", + }; +} + +/// Check if payload size is valid +pub fn is_payload_valid(size: usize) bool { + return size <= MAX_PAYLOAD_SIZE; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "bus_event_create" { + const event = event_create(.lsp_request, "topic", "payload"); + try std.testing.expect(event.type == .lsp_request); +} + +test "bus_event_with_priority" { + const event = event_with_priority(.lsp_request, "topic", "payload", .urgent); + try std.testing.expect(event.priority == .urgent); +} + +test "bus_event_filter_create" { + const filter = event_filter_create(&[_][]u8{"topic1", "topic2"}, .normal); + try std.testing.expect(filter.topics.len == 2); +} + +test "bus_event_result_create" { + const result = event_result_create(true); + try std.testing.expect(result.success == true); +} + +test "bus_topic_pattern_create" { + const pattern = topic_pattern_create("test/*"); + try std.testing.expectEqual(@as(usize, pattern.pattern.len), @as(usize, 6)); +} + +test "bus_bus_config_default" { + const config = bus_config_default(); + try std.testing.expect(config.channel_size == DEFAULT_CHANNEL_SIZE); +} + +test "bus_subscription_create" { + const sub = subscription_create("test/topic", "handler"); + try std.testing.expect(sub.delivery_mode == .fire_and_forget); +} + +test "bus_subscription_info_create" { + const info = subscription_info_create(1, "sub1", "test/topic"); + try std.testing.expect(info.subscription_id == 1); +} + +test "bus_unsubscribe_result_create" { + const result = unsubscribe_result_create(true, 5); + try std.testing.expect(result.subscriptions_removed == 5); +} + +test "bus_bus_error_create" { + const error = bus_error_create(-1, "test error"); + try std.testing.expect(error.code == -1); +} + +test "bus_event_stats_create" { + const stats = event_stats_create(); + try std.testing.expect(stats.events_published == 0); +} + +test "bus_event_type_to_string" { + const str = event_type_to_string(.lsp_request); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 10)); +} + +test "bus_priority_to_string" { + const str = priority_to_string(.urgent); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 6)); +} + +test "bus_is_wildcard_pattern" { + const pattern = topic_pattern_create(WILDCARD_TOPIC); + try std.testing.expect(is_wildcard_pattern(pattern)); +} + +test "bus_topic_matches_pattern" { + const pattern = topic_pattern_create("test/topic"); + try std.testing.expect(topic_matches_pattern("test/topic", pattern)); +} + +test "bus_event_matches_filter" { + const event = event_create(.lsp_request, "test/topic", ""); + const filter = event_filter_create(&[_][]u8{"test/topic"}, .low); + try std.testing.expect(event_matches_filter(event, filter)); +} + +test "bus_is_bus_running" { + try std.testing.expect(is_bus_running(.running)); +} + +test "bus_bus_state_to_string" { + const str = bus_state_to_string(.running); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 6)); +} + +test "bus_is_payload_valid" { + try std.testing.expect(is_payload_valid(MAX_PAYLOAD_SIZE)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant event_type_in_range { + // EventType is in [0, 11] + @compileAssert(@as(u8, EventType.lsp_request) == 0); + @compileAssert(@as(u8, EventType.custom) == 11); +} + +invariant event_priority_in_range { + // EventPriority is in [0, 3] + @compileAssert(@as(u8, EventPriority.low) == 0); + @compileAssert(@as(u8, EventPriority.urgent) == 3); +} + +invariant default_channel_size_positive { + // DEFAULT_CHANNEL_SIZE is positive + @compileAssert(DEFAULT_CHANNEL_SIZE > 0); +} + +invariant max_payload_size_positive { + // MAX_PAYLOAD_SIZE is positive + @compileAssert(MAX_PAYLOAD_SIZE > 0); +} + +invariant default_subscriber_buffer_positive { + // DEFAULT_SUBSCRIBER_BUFFER is positive + @compileAssert(DEFAULT_SUBSCRIBER_BUFFER > 0); +} + +invariant max_subscribers_per_topic_positive { + // MAX_SUBSCRIBERS_PER_TOPIC is positive + @compileAssert(MAX_SUBSCRIBERS_PER_TOPIC > 0); +} + +invariant default_event_ttl_positive { + // DEFAULT_EVENT_TTL_MS is positive + @compileAssert(DEFAULT_EVENT_TTL_MS > 0); +} + +invariant bus_state_in_range { + // BusState is in [0, 4] + @compileAssert(@as(u8, BusState.stopped) == 0); + @compileAssert(@as(u8, BusState.error) == 4); +} + +invariant event_ack_in_range { + // EventAck is in [0, 3] + @compileAssert(@as(u8, EventAck.none) == 0); +} + +invariant delivery_mode_in_range { + // DeliveryMode is in [0, 2] + @compileAssert(@as(u8, DeliveryMode.fire_and_forget) == 0); +} + +invariant subscription_has_id { + // Subscription has valid id + @compileAssert(true); +} + +invariant subscription_has_handler { + // Subscription has non-empty handler + @compileAssert(true); +} + +invariant topic_pattern_valid { + // TopicPattern has valid pattern + @compileAssert(true); +} + +invariant bus_error_valid { + // BusError has valid code + @compileAssert(true); +} + +invariant event_has_type { + // Event has valid type + @compileAssert(true); +} + +invariant event_has_topic { + // Event has non-empty topic + @compileAssert(true); +} + +invariant event_timestamp_valid { + // Event has valid timestamp (will be set on publish) + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "bus_event_create_latency" { + // Measure: cycles for event creation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : Event = undefined; + for (0..1000) |_| { + result = event_create(.lsp_request, "topic", "payload"); + } + _ = result; +} + +bench "bus_topic_pattern_create_latency" { + // Measure: cycles for topic pattern creation + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : TopicPattern = undefined; + for (0..1000) |_| { + result = topic_pattern_create("test/*"); + } + _ = result; +} + +bench "bus_topic_matches_pattern_latency" { + // Measure: cycles for pattern matching + // Target: < 50 cycles + @setEvalBranchQuota(10000); + const pattern = topic_pattern_create("test/topic"); + var result : bool = false; + for (0..1000) |_| { + result = topic_matches_pattern("test/topic", pattern); + } + _ = result; +} + +bench "bus_event_matches_filter_latency" { + // Measure: cycles for filter matching + // Target: < 100 cycles + @setEvalBranchQuota(10000); + const event = event_create(.lsp_request, "test", ""); + const filter = event_filter_create(&[_][]u8{"test"}, .low); + var result : bool = false; + for (0..1000) |_| { + result = event_matches_filter(event, filter); + } + _ = result; +} + +bench "bus_is_bus_running_latency" { + // Measure: cycles for bus state check + // Target: < 20 cycles + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..1000) |_| { + result = is_bus_running(.running); + } + _ = result; +} + +bench "bus_event_type_to_string_latency" { + // Measure: cycles for event type to string + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = event_type_to_string(.lsp_request); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/cloud/railway_deploy.t27 b/apps/website/public/t27/specs/cloud/railway_deploy.t27 new file mode 100644 index 0000000000..a37f8cf89a --- /dev/null +++ b/apps/website/public/t27/specs/cloud/railway_deploy.t27 @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: Apache-2.0 +// cloud/railway_deploy.t27 — Autonomous Railway Deployment +// Trinity S³AI — φ-Structured Cloud Orchestration +// φ² + 1/φ² = 3 | TRINITY + +module cloud-railway-deploy; + +use base::types::Trit; +use math::sacred_physics::{PHI, PHI_INV, TRINITY, GAMMA_LQG}; +use vsa::vsa_core::Hypervector; + +// ============================================================================ +// SACRED CONSTANTS — Railway Deployment +// ============================================================================ + +/// φ (PHI) — Golden Ratio +pub const PHI : f64 = 1.618033988749895; + +/// φ⁻¹ (PHI_INV) — Consciousness threshold +pub const PHI_INV : f64 = 0.618033988749895; + +/// TRINITY — φ² + φ⁻² = 3 +pub const TRINITY : f64 = 3.0; + +/// γ (GAMMA_LQG) — Barbero-Immirzi constant +pub const GAMMA_LQG : f64 = 0.2360679775; + +/// Sacred bee count — 27 Coptic registers +pub const HIVE_BEE_COUNT : u8 = 27; + +/// Queen port — φ-structured +pub const QUEEN_PORT : u16 = 6978; + +// ============================================================================ +// RAILWAY CONSTANTS +// ============================================================================ + +/// Railway GraphQL API endpoint +pub const RAILWAY_GRAPHQL_URL : str = "https://backpack.railway.com/graphql"; + +/// Sandbox environment identifier +pub const RAILWAY_SANDBOX_ENV : str = "sandbox"; + +/// Deployment timeout (φ-structured: 1618ms) +pub const DEPLOY_TIMEOUT_MS : u64 = 1618; + +/// Health check interval (φ⁻¹ structured: 618ms) +pub const HEALTH_CHECK_INTERVAL_MS : u64 = 618; + +/// Health check attempts (TRINITY = 3) +pub const HEALTH_CHECK_MAX_ATTEMPTS : u8 = 3; + +// ============================================================================ +// STRUCTS: Railway Deployment +// ============================================================================ + +/// Railway service configuration +pub struct RailwayServiceConfig { + /// Service name + pub service_name: str, + /// Base service ID to clone + pub base_service_id: str, + /// Project ID + pub project_id: str, + /// Port + pub port: u16, + /// Memory in MiB + pub memory_mb: u32, + /// CPU cores + pub cpu_cores: f64, +} + +/// Environment variable entry +pub struct EnvVar { + /// Variable name + pub key: str, + /// Variable value + pub value: str, + /// Is secret? + pub is_secret: bool, +} + +/// Deployment state machine +pub enum DeployState { + idle = 0, + validating = 1, + creating = 2, + configuring = 3, + building = 4, + deploying = 5, + health_checking = 6, + success = 7, + failed = 8, +} + +/// Deployment result +pub struct DeployResult { + pub success: bool, + pub service_id: str, + pub service_url: str, + pub error: str, + pub deployment_time_ms: u64, + pub final_state: DeployState, +} + +/// Health check status +pub struct HealthStatus { + pub is_healthy: bool, + pub status_code: u16, + pub response_time_ms: u64, + pub last_check_ts: u64, + pub healthy_streak: u8, +} + +// ============================================================================ +// FUNCTIONS: Deployment Orchestrator +// ============================================================================ + +/// Initialize sacred environment variables (27 for Coptic registers) +pub fn init_sacred_env_vars() [27]EnvVar { + var env_vars: [27]EnvVar = undefined; + + // r0: PHI constant + env_vars[0] = EnvVar{ + .key = "PHI", + .value = "1.618033988749895", + .is_secret = false, + }; + + // r1: PHI_INV constant + env_vars[1] = EnvVar{ + .key = "PHI_INV", + .value = "0.618033988749895", + .is_secret = false, + }; + + // r2: TRINITY constant + env_vars[2] = EnvVar{ + .key = "TRINITY", + .value = "3.0", + .is_secret = false, + }; + + // r3: GAMMA_LQG constant + env_vars[3] = EnvVar{ + .key = "GAMMA_LQG", + .value = "0.2360679775", + .is_secret = false, + }; + + // r4: HIVE_BEE_COUNT + env_vars[4] = EnvVar{ + .key = "HIVE_BEE_COUNT", + .value = "27", + .is_secret = false, + }; + + // r5: Queen mode + env_vars[5] = EnvVar{ + .key = "HIVE_QUEEN_MODE", + .value = "queen", + .is_secret = false, + }; + + // r6: φ-structure enabled + env_vars[6] = EnvVar{ + .key = "PHI_STRUCTURED", + .value = "true", + .is_secret = false, + }; + + // r7: T27 VSA enabled + env_vars[7] = EnvVar{ + .key = "T27_ENABLED", + .value = "true", + .is_secret = false, + }; + + // r8: Trinity S³AI enabled + env_vars[8] = EnvVar{ + .key = "TRINITY_ENABLED", + .value = "true", + .is_secret = false, + }; + + // r9: Queen port + env_vars[9] = EnvVar{ + .key = "HIVE_QUEEN_PORT", + .value = "6978", + .is_secret = false, + }; + + // r10-r26: Reserved for future sacred variables + var i: u8 = 10; + while (i < 27) { + env_vars[i] = EnvVar{ + .key = "HIVE_RESERVED_" + i.to_string(), + .value = "", + .is_secret = false, + }; + i = i + 1; + } + + return env_vars; +} + +/// Get deploy state count +pub fn deploy_state_count() u8 { + return 9; +} + +/// Get HIVE_BEE_COUNT +pub fn get_hive_bee_count() u8 { + return HIVE_BEE_COUNT; +} + +/// Get QUEEN_PORT +pub fn get_queen_port() u16 { + return QUEEN_PORT; +} + +// ============================================================================ +// TDD: TESTS (Article II) +// ============================================================================ + +test "sacred_constants_phi_value" { + assert PHI == 1.618033988749895; +} + +test "sacred_constants_phi_inv_value" { + assert PHI_INV == 0.618033988749895; +} + +test "sacred_constants_trinity_value" { + assert TRINITY == 3.0; +} + +test "sacred_constants_gamma_value" { + assert GAMMA_LQG == 0.2360679775; +} + +test "sacred_constants_trinity_identity" { + const phi_sq = PHI * PHI; + const phi_inv_sq = PHI_INV * PHI_INV; + const sum = phi_sq + phi_inv_sq; + assert (sum - TRINITY) < 0.0001; +} + +test "hive_bee_count_is_sacred" { + assert HIVE_BEE_COUNT == 27; +} + +test "queen_port_is_phi_structured" { + assert QUEEN_PORT == 6978; +} + +test "env_vars_init_count" { + const env_vars = init_sacred_env_vars(); + var count: u8 = 0; + var i: u8 = 0; + while (i < 27) { + if (env_vars[i].key.len > 0) { + count = count + 1; + } + i = i + 1; + } + assert count >= 10; +} + +test "env_vars_phi_constant" { + const env_vars = init_sacred_env_vars(); + assert env_vars[0].key == "PHI"; + assert env_vars[0].value == "1.618033988749895"; +} + +test "env_vars_bee_count" { + const env_vars = init_sacred_env_vars(); + assert env_vars[4].key == "HIVE_BEE_COUNT"; + assert env_vars[4].value == "27"; +} + +test "deploy_state_enum_values" { + assert @intFromEnum(DeployState.idle) == 0; + assert @intFromEnum(DeployState.success) == 7; + assert @intFromEnum(DeployState.failed) == 8; +} + +test "deploy_state_count_is_nine" { + assert deploy_state_count() == 9; +} + +test "railway_graphql_url_set" { + assert RAILWAY_GRAPHQL_URL == "https://backpack.railway.com/graphql"; +} + +test "railway_sandbox_env_set" { + assert RAILWAY_SANDBOX_ENV == "sandbox"; +} + +test "deploy_timeout_is_phi_structured" { + assert DEPLOY_TIMEOUT_MS == 1618; +} + +test "health_check_interval_is_phi_inv_structured" { + assert HEALTH_CHECK_INTERVAL_MS == 618; +} + +test "health_check_max_attempts_is_trinity" { + assert HEALTH_CHECK_MAX_ATTEMPTS == 3; +} + +// ============================================================================ +// TDD: INVARIANTS (Article II) +// ============================================================================ + +invariant "trinity_always_holds" { + // φ² + φ⁻² must equal 3 + const phi_sq = PHI * PHI; + const phi_inv_sq = PHI_INV * PHI_INV; + const sum = phi_sq + phi_inv_sq; + assert (sum - TRINITY) < 0.0001; +} + +invariant "gamma_is_phi_cubed_inverse" { + // γ = φ⁻³ + const gamma_calc = PHI_INV * PHI_INV * PHI_INV; + assert (GAMMA_LQG - gamma_calc) < 0.0001; +} + +invariant "phi_inv_plus_one_equals_phi" { + // φ⁻¹ + 1 = φ + assert (PHI_INV + 1.0 - PHI) < 0.0001; +} + +invariant "bee_count_matches_coptic_registers" { + // Hive bee count must equal Coptic register count + assert HIVE_BEE_COUNT == 27; +} + +invariant "deploy_state_enum_complete" { + // All 9 deployment states must be defined + assert @intFromEnum(DeployState.failed) == 8; +} + +invariant "env_vars_array_size_is_sacred" { + // Environment variables must have 27 elements + var dummy: [27]EnvVar = undefined; + _ = dummy; +} + +// ============================================================================ +// TDD: BENCHMARKS (Article II) +// ============================================================================ + +bench init_sacred_env_vars_latency { + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var env_vars: [27]EnvVar = undefined; + for (0..1000) |_| { + env_vars = init_sacred_env_vars(); + } +} diff --git a/apps/website/public/t27/specs/compiler/diagnostics.t27 b/apps/website/public/t27/specs/compiler/diagnostics.t27 new file mode 100644 index 0000000000..7148703d9a --- /dev/null +++ b/apps/website/public/t27/specs/compiler/diagnostics.t27 @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: Apache-2.0 +module Diagnostics { + enum ErrorCode { + ParseError = 1000, + UnexpectedToken = 1001, + MissingSemicolon = 1002, + UnclosedBrace = 1003, + InvalidType = 1004, + + TypeMismatch = 2000, + UndefinedSymbol = 2001, + DuplicateSymbol = 2002, + ReturnTypeMismatch = 2003, + ArgCountMismatch = 2004, + + LinkError = 3000, + ModuleNotFound = 3001, + CyclicImport = 3002, + SymbolNotExported = 3003, + + InternalError = 9000, + } + + enum Severity { + Error, + Warning, + Info, + } + + struct Diagnostic { + code: ErrorCode; + severity: Severity; + message: str; + file_path: str; + line: u32; + col: u32; + } + + fn diagnostic_new(code: ErrorCode, sev: Severity, msg: str, file: str, ln: u32, c: u32) -> Diagnostic { + return Diagnostic{ + code = code, + severity = sev, + message = msg, + file_path = file, + line = ln, + col = c, + }; + } + + fn is_error(d: Diagnostic) -> bool { + return d.severity == Severity::Error; + } + + fn is_warning(d: Diagnostic) -> bool { + return d.severity == Severity::Warning; + } + + fn is_parse_error(code: ErrorCode) -> bool { + return code == ErrorCode::ParseError + or code == ErrorCode::UnexpectedToken + or code == ErrorCode::MissingSemicolon + or code == ErrorCode::UnclosedBrace + or code == ErrorCode::InvalidType; + } + + fn is_type_error(code: ErrorCode) -> bool { + return code == ErrorCode::TypeMismatch + or code == ErrorCode::UndefinedSymbol + or code == ErrorCode::DuplicateSymbol + or code == ErrorCode::ReturnTypeMismatch + or code == ErrorCode::ArgCountMismatch; + } + + fn is_link_error(code: ErrorCode) -> bool { + return code == ErrorCode::LinkError + or code == ErrorCode::ModuleNotFound + or code == ErrorCode::CyclicImport + or code == ErrorCode::SymbolNotExported; + } + + fn error_code_range(code: ErrorCode) -> u32 { + if (is_parse_error(code)) { return 1; } + if (is_type_error(code)) { return 2; } + if (is_link_error(code)) { return 3; } + return 9; + } + + fn format_diagnostic(d: Diagnostic) -> str { + return d.file_path; + } + + test parse_error_detected + given result = is_parse_error(ErrorCode::UnexpectedToken) + then result == true + + test type_error_detected + given result = is_type_error(ErrorCode::TypeMismatch) + then result == true + + test link_error_detected + given result = is_link_error(ErrorCode::ModuleNotFound) + then result == true + + test internal_not_parse + given result = is_parse_error(ErrorCode::InternalError) + then result == false + + test error_is_error + given d = diagnostic_new(ErrorCode::ParseError, Severity::Error, "bad", "test.t27", 1, 1) + then is_error(d) == true + + test warning_not_error + given d = diagnostic_new(ErrorCode::ParseError, Severity::Warning, "hmm", "test.t27", 1, 1) + then is_error(d) == false + + test warning_is_warning + given d = diagnostic_new(ErrorCode::ParseError, Severity::Warning, "hmm", "test.t27", 1, 1) + then is_warning(d) == true + + test parse_range + given r = error_code_range(ErrorCode::UnexpectedToken) + then r == 1 + + test type_range + given r = error_code_range(ErrorCode::UndefinedSymbol) + then r == 2 + + test link_range + given r = error_code_range(ErrorCode::CyclicImport) + then r == 3 + + test internal_range + given r = error_code_range(ErrorCode::InternalError) + then r == 9 + + invariant parse_codes_are_1xxx + assert ErrorCode::ParseError >= 1000 + assert ErrorCode::InvalidType < 2000 + + invariant type_codes_are_2xxx + assert ErrorCode::TypeMismatch >= 2000 + assert ErrorCode::ArgCountMismatch < 3000 + + invariant link_codes_are_3xxx + assert ErrorCode::LinkError >= 3000 + assert ErrorCode::SymbolNotExported < 4000 +} diff --git a/apps/website/public/t27/specs/compiler/lexer.t27 b/apps/website/public/t27/specs/compiler/lexer.t27 new file mode 100644 index 0000000000..1d10b71007 --- /dev/null +++ b/apps/website/public/t27/specs/compiler/lexer.t27 @@ -0,0 +1,582 @@ +// SPDX-License-Identifier: Apache-2.0 +module Lexing { + use base::types; + + enum TokenKind { + KwPub = 0, KwConst = 1, KwFn = 2, KwEnum = 3, KwStruct = 4, + KwTest = 5, KwInvariant = 6, KwBench = 7, KwModule = 8, + KwIf = 9, KwElse = 10, KwFor = 11, KwWhile = 12, KwSwitch = 13, + KwReturn = 14, KwVar = 15, KwUsing = 16, KwVoid = 17, + KwTrue = 18, KwFalse = 19, KwUse = 20, KwOr = 21, KwAnd = 22, + KwTry = 23, KwBreak = 24, KwContinue = 25, + + Ident = 30, Number = 31, StringLit = 32, CharLiteral = 33, + + Plus = 40, Minus = 41, Star = 42, Slash = 43, Percent = 44, + Amp = 45, Pipe = 46, Caret = 47, Tilde = 48, + + Lt = 50, Gt = 51, Lte = 52, Gte = 53, Eq = 54, Neq = 55, + + Colon = 60, Comma = 61, Equals = 62, LParen = 63, RParen = 64, + LBrace = 65, RBrace = 66, LBracket = 67, RBracket = 68, + Dot = 69, Bang = 70, Semicolon = 71, + + Arrow = 80, FatArrow = 81, Power = 82, DotDot = 83, PlusPlus = 84, + ShiftLeft = 85, ShiftRight = 86, PlusEquals = 87, PlusPercent = 88, + + Eof = 255, + } + + struct Token { + kind: TokenKind; + lexeme: [256]u8; + lexeme_len: u32; + line: u32; + col: u32; + } + + struct Lexer { + source: [65536]u8; + source_len: u32; + pos: u32; + line: u32; + col: u32; + } + + fn lexer_init(src: [65536]u8, len: u32) -> Lexer { + return Lexer{ + source = src, + source_len = len, + pos = 0, + line = 1, + col = 1, + }; + } + + fn peek(lex: Lexer) -> u8 { + if (lex.pos >= lex.source_len) { + return 0; + } + return lex.source[lex.pos]; + } + + fn peek_offset(lex: Lexer, offset: u32) -> u8 { + const new_pos = lex.pos + offset; + if (new_pos >= lex.source_len) { + return 0; + } + return lex.source[new_pos]; + } + + fn advance(lex: Lexer) -> Lexer { + var new_lex = lex; + const ch = peek(lex); + if (ch == 10) { + new_lex.line = lex.line + 1; + new_lex.col = 1; + } else { + new_lex.col = lex.col + 1; + } + new_lex.pos = lex.pos + 1; + return new_lex; + } + + fn is_alpha(ch: u8) -> bool { + return (ch >= 65 and ch <= 90) or (ch >= 97 and ch <= 122) or (ch == 95); + } + + fn is_digit(ch: u8) -> bool { + return ch >= 48 and ch <= 57; + } + + fn is_hex_digit(ch: u8) -> bool { + return (ch >= 48 and ch <= 57) or (ch >= 65 and ch <= 70) or (ch >= 97 and ch <= 102); + } + + fn is_alnum(ch: u8) -> bool { + return is_alpha(ch) or is_digit(ch); + } + + fn is_whitespace(ch: u8) -> bool { + return ch == 32 or ch == 9 or ch == 10 or ch == 13; + } + + fn skip_whitespace_and_comments(lex: Lexer) -> Lexer { + var l = lex; + var done = false; + while (done == false) { + while (l.pos < l.source_len and is_whitespace(peek(l))) { + l = advance(l); + } + + if (l.pos + 1 < l.source_len and l.source[l.pos] == 47 and l.source[l.pos + 1] == 47) { + while (l.pos < l.source_len and peek(l) != 10) { + l = advance(l); + } + } else if (l.pos + 1 < l.source_len and l.source[l.pos] == 47 and l.source[l.pos + 1] == 42) { + l = advance(l); + l = advance(l); + var depth: u32 = 1; + while (depth > 0 and l.pos < l.source_len) { + if (l.pos + 1 < l.source_len and l.source[l.pos] == 42 and l.source[l.pos + 1] == 47) { + l = advance(l); + l = advance(l); + depth = depth - 1; + } else if (l.pos + 1 < l.source_len and l.source[l.pos] == 47 and l.source[l.pos + 1] == 42) { + l = advance(l); + l = advance(l); + depth = depth + 1; + } else { + l = advance(l); + } + } + } else { + done = true; + } + } + return l; + } + + fn lexeme_equals(lexeme: [256]u8, len: u32, target: str) -> bool { + var i: u32 = 0; + while (i < len) { + if (lexeme[i] != target[i]) { + return false; + } + i = i + 1; + } + return true; + } + + fn check_keyword(lexeme: [256]u8, len: u32) -> TokenKind { + if (len == 2) { + if (lexeme[0] == 102 and lexeme[1] == 110) { return TokenKind::KwFn; } + if (lexeme[0] == 105 and lexeme[1] == 102) { return TokenKind::KwIf; } + if (lexeme[0] == 111 and lexeme[1] == 114) { return TokenKind::KwOr; } + return TokenKind::Ident; + } + if (len == 3) { + if (lexeme[0] == 112 and lexeme[1] == 117 and lexeme[2] == 98) { return TokenKind::KwPub; } + if (lexeme[0] == 118 and lexeme[1] == 97 and lexeme[2] == 114) { return TokenKind::KwVar; } + if (lexeme[0] == 116 and lexeme[1] == 114 and lexeme[2] == 121) { return TokenKind::KwTry; } + if (lexeme[0] == 117 and lexeme[1] == 115 and lexeme[2] == 101) { return TokenKind::KwUse; } + if (lexeme[0] == 97 and lexeme[1] == 110 and lexeme[2] == 100) { return TokenKind::KwAnd; } + if (lexeme[0] == 102 and lexeme[1] == 111 and lexeme[2] == 114) { return TokenKind::KwFor; } + return TokenKind::Ident; + } + if (len == 4) { + if (lexeme[0] == 101 and lexeme[1] == 108 and lexeme[2] == 115 and lexeme[3] == 101) { return TokenKind::KwElse; } + if (lexeme[0] == 101 and lexeme[1] == 110 and lexeme[2] == 117 and lexeme[3] == 109) { return TokenKind::KwEnum; } + if (lexeme[0] == 116 and lexeme[1] == 114 and lexeme[2] == 117 and lexeme[3] == 101) { return TokenKind::KwTrue; } + if (lexeme[0] == 118 and lexeme[1] == 111 and lexeme[2] == 105 and lexeme[3] == 100) { return TokenKind::KwVoid; } + if (lexeme[0] == 116 and lexeme[1] == 101 and lexeme[2] == 115 and lexeme[3] == 116) { return TokenKind::KwTest; } + return TokenKind::Ident; + } + if (len == 5) { + if (lexeme[0] == 99 and lexeme[1] == 111 and lexeme[2] == 110 and lexeme[3] == 115 and lexeme[4] == 116) { return TokenKind::KwConst; } + if (lexeme[0] == 119 and lexeme[1] == 104 and lexeme[2] == 105 and lexeme[3] == 108 and lexeme[4] == 101) { return TokenKind::KwWhile; } + if (lexeme[0] == 117 and lexeme[1] == 115 and lexeme[2] == 105 and lexeme[3] == 110 and lexeme[4] == 103) { return TokenKind::KwUsing; } + if (lexeme[0] == 102 and lexeme[1] == 97 and lexeme[2] == 108 and lexeme[3] == 115 and lexeme[4] == 101) { return TokenKind::KwFalse; } + if (lexeme[0] == 98 and lexeme[1] == 114 and lexeme[2] == 101 and lexeme[3] == 97 and lexeme[4] == 107) { return TokenKind::KwBreak; } + return TokenKind::Ident; + } + if (len == 6) { + if (lexeme[0] == 115 and lexeme[1] == 116 and lexeme[2] == 114 and lexeme[3] == 117 and lexeme[4] == 99 and lexeme[5] == 116) { return TokenKind::KwStruct; } + if (lexeme[0] == 114 and lexeme[1] == 101 and lexeme[2] == 116 and lexeme[3] == 117 and lexeme[4] == 114 and lexeme[5] == 110) { return TokenKind::KwReturn; } + if (lexeme[0] == 115 and lexeme[1] == 119 and lexeme[2] == 105 and lexeme[3] == 116 and lexeme[4] == 99 and lexeme[5] == 104) { return TokenKind::KwSwitch; } + if (lexeme[0] == 109 and lexeme[1] == 111 and lexeme[2] == 100 and lexeme[3] == 117 and lexeme[4] == 108 and lexeme[5] == 101) { return TokenKind::KwModule; } + return TokenKind::Ident; + } + if (len == 8) { + if (lexeme[0] == 99 and lexeme[1] == 111 and lexeme[2] == 110 + and lexeme[3] == 116 and lexeme[4] == 105 and lexeme[5] == 110 + and lexeme[6] == 117 and lexeme[7] == 101) { return TokenKind::KwContinue; } + return TokenKind::Ident; + } + if (len == 9) { + if (lexeme[0] == 105 and lexeme[1] == 110 and lexeme[2] == 118 + and lexeme[3] == 97 and lexeme[4] == 114 and lexeme[5] == 105 + and lexeme[6] == 97 and lexeme[7] == 110 and lexeme[8] == 116) { return TokenKind::KwInvariant; } + return TokenKind::Ident; + } + if (len == 5) { + if (lexeme[0] == 98 and lexeme[1] == 101 and lexeme[2] == 110 and lexeme[3] == 99 and lexeme[4] == 104) { return TokenKind::KwBench; } + return TokenKind::Ident; + } + return TokenKind::Ident; + } + + fn make_token_simple(kind: TokenKind, line: u32, col: u32, text: str, text_len: u32) -> Token { + var tok = Token{ + kind = kind, + lexeme = [0; 256], + lexeme_len = text_len, + line = line, + col = col, + }; + var i: u32 = 0; + while (i < text_len) { + tok.lexeme[i] = text[i]; + i = i + 1; + } + return tok; + } + + fn make_token_from_source(lex: Lexer, kind: TokenKind, start_pos: u32, start_line: u32, start_col: u32) -> Token { + var tok = Token{ + kind = kind, + lexeme = [0; 256], + lexeme_len = lex.pos - start_pos, + line = start_line, + col = start_col, + }; + var i: u32 = 0; + while (i < tok.lexeme_len) { + tok.lexeme[i] = lex.source[start_pos + i]; + i = i + 1; + } + return tok; + } + + fn scan_identifier(lex: Lexer) -> Token { + const start_line = lex.line; + const start_col = lex.col; + const start_pos = lex.pos; + var l = lex; + + while (l.pos < l.source_len) { + const ch = peek(l); + if (is_alnum(ch) or ch == 95 or ch == 64) { + l = advance(l); + } else { + break; + } + } + + var tok = make_token_from_source(l, TokenKind::Ident, start_pos, start_line, start_col); + tok.kind = check_keyword(tok.lexeme, tok.lexeme_len); + return tok; + } + + fn scan_number(lex: Lexer) -> Token { + const start_line = lex.line; + const start_col = lex.col; + const start_pos = lex.pos; + var l = lex; + var is_hex = false; + + while (l.pos < l.source_len) { + const ch = peek(l); + if (is_hex_digit(ch) or ch == 95) { + if (ch == 120 or ch == 88) { + is_hex = true; + } + l = advance(l); + } else if (ch == 46) { + const next_ch = peek_offset(l, 1); + if (next_ch == 46) { + break; + } + l = advance(l); + } else if (ch == 98 or ch == 66) { + l = advance(l); + break; + } else { + break; + } + } + + return make_token_from_source(l, TokenKind::Number, start_pos, start_line, start_col); + } + + fn scan_string(lex: Lexer) -> Token { + const start_line = lex.line; + const start_col = lex.col; + var l = lex; + l = advance(l); + + var tok = Token{ + kind = TokenKind::StringLit, + lexeme = [0; 256], + lexeme_len = 0, + line = start_line, + col = start_col, + }; + + while (l.pos < l.source_len and peek(l) != 34) { + if (peek(l) == 92) { + l = advance(l); + if (l.pos < l.source_len) { + const escaped = peek(l); + if (escaped == 110) { + tok.lexeme[tok.lexeme_len] = 10; + } else if (escaped == 116) { + tok.lexeme[tok.lexeme_len] = 9; + } else if (escaped == 92) { + tok.lexeme[tok.lexeme_len] = 92; + } else if (escaped == 34) { + tok.lexeme[tok.lexeme_len] = 34; + } else { + tok.lexeme[tok.lexeme_len] = 92; + tok.lexeme_len = tok.lexeme_len + 1; + tok.lexeme[tok.lexeme_len] = escaped; + } + tok.lexeme_len = tok.lexeme_len + 1; + l = advance(l); + } + } else { + tok.lexeme[tok.lexeme_len] = peek(l); + tok.lexeme_len = tok.lexeme_len + 1; + l = advance(l); + } + } + + if (l.pos < l.source_len) { + l = advance(l); + } + + return tok; + } + + fn scan_char(lex: Lexer) -> Token { + const start_line = lex.line; + const start_col = lex.col; + var l = lex; + l = advance(l); + + var tok = Token{ + kind = TokenKind::CharLiteral, + lexeme = [0; 256], + lexeme_len = 0, + line = start_line, + col = start_col, + }; + + if (l.pos < l.source_len) { + if (peek(l) == 92) { + tok.lexeme[0] = 92; + tok.lexeme_len = 1; + l = advance(l); + if (l.pos < l.source_len) { + tok.lexeme[1] = peek(l); + tok.lexeme_len = 2; + l = advance(l); + } + } else { + tok.lexeme[0] = peek(l); + tok.lexeme_len = 1; + l = advance(l); + } + } + + if (l.pos < l.source_len and peek(l) == 39) { + l = advance(l); + } + + return tok; + } + + fn next_token(lex: Lexer) -> (Lexer, Token) { + var l = skip_whitespace_and_comments(lex); + + const start_line = l.line; + const start_col = l.col; + const start_pos = l.pos; + + if (l.pos >= l.source_len) { + return (l, make_token_simple(TokenKind::Eof, start_line, start_col, "", 0)); + } + + const ch = peek(l); + + if (ch == 59) { + l = advance(l); + return (l, make_token_simple(TokenKind::Semicolon, start_line, start_col, ";", 1)); + } + + if (l.pos + 1 < l.source_len) { + const next = l.source[l.pos + 1]; + + if (ch == 45 and next == 62) { + l = advance(l); l = advance(l); + return (l, make_token_simple(TokenKind::Arrow, start_line, start_col, "->", 2)); + } + if (ch == 61 and next == 62) { + l = advance(l); l = advance(l); + return (l, make_token_simple(TokenKind::FatArrow, start_line, start_col, "=>", 2)); + } + if (ch == 42 and next == 42) { + l = advance(l); l = advance(l); + return (l, make_token_simple(TokenKind::Power, start_line, start_col, "**", 2)); + } + if (ch == 60 and next == 61) { + l = advance(l); l = advance(l); + return (l, make_token_simple(TokenKind::Lte, start_line, start_col, "<=", 2)); + } + if (ch == 62 and next == 61) { + l = advance(l); l = advance(l); + return (l, make_token_simple(TokenKind::Gte, start_line, start_col, ">=", 2)); + } + if (ch == 61 and next == 61) { + l = advance(l); l = advance(l); + return (l, make_token_simple(TokenKind::Eq, start_line, start_col, "==", 2)); + } + if (ch == 33 and next == 61) { + l = advance(l); l = advance(l); + return (l, make_token_simple(TokenKind::Neq, start_line, start_col, "!=", 2)); + } + if (ch == 60 and next == 60) { + l = advance(l); l = advance(l); + return (l, make_token_simple(TokenKind::ShiftLeft, start_line, start_col, "<<", 2)); + } + if (ch == 62 and next == 62) { + l = advance(l); l = advance(l); + return (l, make_token_simple(TokenKind::ShiftRight, start_line, start_col, ">>", 2)); + } + if (ch == 43 and next == 43) { + l = advance(l); l = advance(l); + return (l, make_token_simple(TokenKind::PlusPlus, start_line, start_col, "++", 2)); + } + if (ch == 43 and next == 61) { + l = advance(l); l = advance(l); + return (l, make_token_simple(TokenKind::PlusEquals, start_line, start_col, "+=", 2)); + } + if (ch == 43 and next == 37) { + l = advance(l); l = advance(l); + return (l, make_token_simple(TokenKind::PlusPercent, start_line, start_col, "+%", 2)); + } + if (ch == 46 and next == 46) { + l = advance(l); l = advance(l); + return (l, make_token_simple(TokenKind::DotDot, start_line, start_col, "..", 2)); + } + } + + if (ch == 58) { l = advance(l); return (l, make_token_simple(TokenKind::Colon, start_line, start_col, ":", 1)); } + if (ch == 44) { l = advance(l); return (l, make_token_simple(TokenKind::Comma, start_line, start_col, ",", 1)); } + if (ch == 61) { l = advance(l); return (l, make_token_simple(TokenKind::Equals, start_line, start_col, "=", 1)); } + if (ch == 40) { l = advance(l); return (l, make_token_simple(TokenKind::LParen, start_line, start_col, "(", 1)); } + if (ch == 41) { l = advance(l); return (l, make_token_simple(TokenKind::RParen, start_line, start_col, ")", 1)); } + if (ch == 123) { l = advance(l); return (l, make_token_simple(TokenKind::LBrace, start_line, start_col, "{", 1)); } + if (ch == 125) { l = advance(l); return (l, make_token_simple(TokenKind::RBrace, start_line, start_col, "}", 1)); } + if (ch == 91) { l = advance(l); return (l, make_token_simple(TokenKind::LBracket, start_line, start_col, "[", 1)); } + if (ch == 93) { l = advance(l); return (l, make_token_simple(TokenKind::RBracket, start_line, start_col, "]", 1)); } + if (ch == 46) { l = advance(l); return (l, make_token_simple(TokenKind::Dot, start_line, start_col, ".", 1)); } + if (ch == 33) { l = advance(l); return (l, make_token_simple(TokenKind::Bang, start_line, start_col, "!", 1)); } + if (ch == 43) { l = advance(l); return (l, make_token_simple(TokenKind::Plus, start_line, start_col, "+", 1)); } + if (ch == 45) { l = advance(l); return (l, make_token_simple(TokenKind::Minus, start_line, start_col, "-", 1)); } + if (ch == 42) { l = advance(l); return (l, make_token_simple(TokenKind::Star, start_line, start_col, "*", 1)); } + if (ch == 47) { l = advance(l); return (l, make_token_simple(TokenKind::Slash, start_line, start_col, "/", 1)); } + if (ch == 37) { l = advance(l); return (l, make_token_simple(TokenKind::Percent, start_line, start_col, "%", 1)); } + if (ch == 38) { l = advance(l); return (l, make_token_simple(TokenKind::Amp, start_line, start_col, "&", 1)); } + if (ch == 124) { l = advance(l); return (l, make_token_simple(TokenKind::Pipe, start_line, start_col, "|", 1)); } + if (ch == 94) { l = advance(l); return (l, make_token_simple(TokenKind::Caret, start_line, start_col, "^", 1)); } + if (ch == 126) { l = advance(l); return (l, make_token_simple(TokenKind::Tilde, start_line, start_col, "~", 1)); } + if (ch == 60) { l = advance(l); return (l, make_token_simple(TokenKind::Lt, start_line, start_col, "<", 1)); } + if (ch == 62) { l = advance(l); return (l, make_token_simple(TokenKind::Gt, start_line, start_col, ">", 1)); } + + if (is_alpha(ch) or ch == 95 or ch == 64) { + const tok = scan_identifier(l); + l.pos = start_pos + tok.lexeme_len; + return (l, tok); + } + + if (is_digit(ch)) { + const tok = scan_number(l); + return (l, tok); + } + + if (ch == 34) { + const tok = scan_string(l); + return (l, tok); + } + + if (ch == 39) { + const tok = scan_char(l); + return (l, tok); + } + + l = advance(l); + return (l, make_token_simple(TokenKind::Eof, start_line, start_col, "", 0)); + } + + test is_alpha_lowercase + given result = is_alpha(97) + then result == true + + test is_alpha_uppercase + given result = is_alpha(65) + then result == true + + test is_alpha_underscore + given result = is_alpha(95) + then result == true + + test is_digit_zero + given result = is_digit(48) + then result == true + + test is_digit_nine + given result = is_digit(57) + then result == true + + test is_whitespace_space + given result = is_whitespace(32) + then result == true + + test is_whitespace_tab + given result = is_whitespace(9) + then result == true + + test is_whitespace_newline + given result = is_whitespace(10) + then result == true + + test keyword_fn + given lexeme = "fn" + given result = check_keyword(lexeme, 2) + then result == TokenKind::KwFn + + test keyword_const + given lexeme = "const" + given result = check_keyword(lexeme, 5) + then result == TokenKind::KwConst + + test keyword_struct + given lexeme = "struct" + given result = check_keyword(lexeme, 6) + then result == TokenKind::KwStruct + + test keyword_return + given lexeme = "return" + given result = check_keyword(lexeme, 6) + then result == TokenKind::KwReturn + + test keyword_invariant + given lexeme = "invariant" + given result = check_keyword(lexeme, 9) + then result == TokenKind::KwInvariant + + test keyword_break + given lexeme = "break" + given result = check_keyword(lexeme, 5) + then result == TokenKind::KwBreak + + test keyword_continue + given lexeme = "continue" + given result = check_keyword(lexeme, 8) + then result == TokenKind::KwContinue + + test identifier_not_keyword + given lexeme = "hello" + given result = check_keyword(lexeme, 5) + then result == TokenKind::Ident + + invariant eof_is_255 + assert TokenKind::Eof == 255 + + invariant keyword_count_is_26 + assert TokenKind::KwContinue == 25 + + invariant total_token_kinds + assert TokenKind::Eof == 255 +} diff --git a/apps/website/public/t27/specs/compiler/linker.t27 b/apps/website/public/t27/specs/compiler/linker.t27 new file mode 100644 index 0000000000..90d68641a8 --- /dev/null +++ b/apps/website/public/t27/specs/compiler/linker.t27 @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: Apache-2.0 +module Linking { + use compiler::parser; + + enum LinkError { + ModuleNotFound, + SymbolNotFound, + CyclicImport, + TypeMismatch, + DuplicateSymbol, + } + + struct ModuleRef { + module_name: str; + file_path: str; + resolved: bool; + } + + struct SymbolRef { + symbol_name: str; + module_name: str; + symbol_type: str; + is_pub: bool; + line: u32; + } + + struct LinkResult { + ok: bool; + error: LinkError; + error_msg: str; + modules_linked: u32; + symbols_resolved: u32; + } + + const MAX_MODULES: u32 = 128; + const MAX_SYMBOLS: u32 = 1024; + + fn link_result_ok(modules: u32, symbols: u32) -> LinkResult { + return LinkResult{ + ok = true, + error = LinkError::ModuleNotFound, + error_msg = "", + modules_linked = modules, + symbols_resolved = symbols, + }; + } + + fn link_result_err(err: LinkError, msg: str) -> LinkResult { + return LinkResult{ + ok = false, + error = err, + error_msg = msg, + modules_linked = 0, + symbols_resolved = 0, + }; + } + + fn module_ref_new(name: str, path: str) -> ModuleRef { + return ModuleRef{ + module_name = name, + file_path = path, + resolved = false, + }; + } + + fn symbol_ref_new(name: str, module: str, sym_type: str, pub_flag: bool, ln: u32) -> SymbolRef { + return SymbolRef{ + symbol_name = name, + module_name = module, + symbol_type = sym_type, + is_pub = pub_flag, + line = ln, + }; + } + + fn resolve_import(use_path: str) -> ModuleRef { + var result = module_ref_new(use_path, ""); + var i: u32 = 0; + var slash_pos: u32 = 0; + while (i < 256) { + result.file_path = ""; + i = i + 1; + } + result.file_path = use_path; + result.resolved = true; + return result; + } + + fn is_cyclic(chain_len: u32, max_depth: u32) -> bool { + return chain_len >= max_depth; + } + + fn validate_pub_access(sym: SymbolRef) -> bool { + return sym.is_pub; + } + + fn count_pub_symbols(symbols: [16]SymbolRef, count: u32) -> u32 { + var result: u32 = 0; + var i: u32 = 0; + while (i < count) { + if (symbols[i].is_pub) { + result = result + 1; + } + i = i + 1; + } + return result; + } + + test link_result_ok_is_ok + given r = link_result_ok(3, 10) + then r.ok == true + + test link_result_ok_counts + given r = link_result_ok(3, 10) + then r.modules_linked == 3 + + test link_result_err_is_err + given r = link_result_err(LinkError::ModuleNotFound, "not found") + then r.ok == false + + test link_result_err_msg + given r = link_result_err(LinkError::CyclicImport, "cycle detected") + then r.error_msg == "cycle detected" + + test module_ref_new_unresolved + given m = module_ref_new("compiler::lexer", "specs/compiler/lexer.t27") + then m.resolved == false + + test symbol_ref_new_pub + given s = symbol_ref_new("Token", "Lexing", "struct", true, 10) + then s.is_pub == true + + test is_cyclic_below_limit + given result = is_cyclic(3, 10) + then result == false + + test is_cyclic_at_limit + given result = is_cyclic(10, 10) + then result == true + + test validate_pub_access_true + given s = symbol_ref_new("foo", "bar", "fn", true, 1) + then validate_pub_access(s) == true + + test validate_pub_access_false + given s = symbol_ref_new("foo", "bar", "fn", false, 1) + then validate_pub_access(s) == false + + invariant link_err_is_not_ok + given r = link_result_err(LinkError::TypeMismatch, "") + assert r.ok == false + + invariant cyclic_detection + assert is_cyclic(100, 50) == true +} diff --git a/apps/website/public/t27/specs/compiler/meta_compile.t27 b/apps/website/public/t27/specs/compiler/meta_compile.t27 new file mode 100644 index 0000000000..063d304978 --- /dev/null +++ b/apps/website/public/t27/specs/compiler/meta_compile.t27 @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +module MetaCompilation { + use compiler::parser; + use compiler::lexer; + + struct CompileResult { + parse_ok: bool; + zig_ok: bool; + verilog_ok: bool; + c_ok: bool; + rust_ok: bool; + zig_lines: u32; + verilog_lines: u32; + c_lines: u32; + rust_lines: u32; + } + + const SAMPLE_SPEC: str = "module Test { fn add(a: i32, b: i32) -> i32 { return a + b; } }"; + + fn compile_result_init() -> CompileResult { + return CompileResult{ + parse_ok = false, + zig_ok = false, + verilog_ok = false, + c_ok = false, + rust_ok = false, + zig_lines = 0, + verilog_lines = 0, + c_lines = 0, + rust_lines = 0, + }; + } + + fn is_full_success(r: CompileResult) -> bool { + return r.parse_ok and r.zig_ok and r.verilog_ok and r.c_ok and r.rust_ok; + } + + fn total_lines(r: CompileResult) -> u32 { + return r.zig_lines + r.verilog_lines + r.c_lines + r.rust_lines; + } + + fn any_backend_ok(r: CompileResult) -> bool { + return r.zig_ok or r.verilog_ok or r.c_ok or r.rust_ok; + } + + test compile_result_init_defaults + given r = compile_result_init() + then r.parse_ok == false + + test is_full_success_requires_all + given r = compile_result_init() + then is_full_success(r) == false + + test total_lines_init_zero + given r = compile_result_init() + then total_lines(r) == 0 + + test any_backend_init_false + given r = compile_result_init() + then any_backend_ok(r) == false + + invariant total_lines_non_negative + given r = compile_result_init() + assert total_lines(r) >= 0 + + invariant init_not_full_success + given r = compile_result_init() + assert is_full_success(r) == false +} diff --git a/apps/website/public/t27/specs/compiler/mod_structure.t27 b/apps/website/public/t27/specs/compiler/mod_structure.t27 new file mode 100644 index 0000000000..1f466ffbe1 --- /dev/null +++ b/apps/website/public/t27/specs/compiler/mod_structure.t27 @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: Apache-2.0 +// compiler/mod_structure.t27 — Module Structure and Ring Validation +// Trinity S³AI — Spec-First Architecture +// φ² + 1/φ² = 3 | TRINITY + +module compiler-mod-structure; + +use math::sacred_physics::{PHI, PHI_INV, TRINITY}; + +// ============================================================================ +// SACRED CONSTANTS +// ============================================================================ + +pub const PHI : f64 = 1.618033988749895; +pub const PHI_INV : f64 = 0.618033988749895; +pub const TRINITY : f64 = 3.0; + +/// Ring count for base module expansion +pub const BASE_MODULE_COUNT : u8 = 27; + +// ============================================================================ +// MODULE REGISTRY +// ============================================================================ + +/// Module category +pub enum ModuleCategory { + base = 0, + cloud = 1, + queen = 2, + compiler = 3, +} + +/// Module entry +pub struct ModuleEntry { + pub name: str, + pub category: ModuleCategory, + pub ring_number: u8, + pub spec_file: str, +} + +/// Get module list +pub fn get_module_list() []ModuleEntry { + var modules: []ModuleEntry = []; + + // Base modules + modules.append(ModuleEntry{ + .name = "types", + .category = ModuleCategory.base, + .ring_number = 0, + .spec_file = "specs/base/types.t27", + }); + + modules.append(ModuleEntry{ + .name = "debounce", + .category = ModuleCategory.base, + .ring_number = 32, + .spec_file = "specs/base/debounce.t27", + }); + + // Cloud modules + modules.append(ModuleEntry{ + .name = "railway_deploy", + .category = ModuleCategory.cloud, + .ring_number = 32, + .spec_file = "specs/cloud/railway_deploy.t27", + }); + + // Queen modules + modules.append(ModuleEntry{ + .name = "consciousness", + .category = ModuleCategory.queen, + .ring_number = 32, + .spec_file = "specs/queen/consciousness.t27", + }); + + modules.append(ModuleEntry{ + .name = "self_evolution", + .category = ModuleCategory.queen, + .ring_number = 32, + .spec_file = "specs/queen/self_evolution.t27", + }); + + modules.append(ModuleEntry{ + .name = "task_analysis", + .category = ModuleCategory.queen, + .ring_number = 32, + .spec_file = "specs/queen/task_analysis.t27", + }); + + return modules; +} + +/// Validate ring number (must be φ-structured) +pub fn validate_ring_number(ring: u8) -> bool { + // Rings must be multiples of 3 (TRINITY) or follow φ pattern + return ring % 3 == 0 or ring == 32 or ring == 618; +} + +// ============================================================================ +// TDD: TESTS +// ============================================================================ + +test "module_list_contains_core_modules" { + const modules = get_module_list(); + assert modules.len >= 5; +} + +test "ring_validation_accepts_trinity_multiples" { + assert validate_ring_number(0) == true; + assert validate_ring_number(3) == true; + assert validate_ring_number(6) == true; + assert validate_ring_number(32) == true; +} + +// ============================================================================ +// TDD: INVARIANTS +// ============================================================================ + +invariant "trinity_always_holds" { + const phi_sq = PHI * PHI; + const phi_inv_sq = PHI_INV * PHI_INV; + assert (phi_sq + phi_inv_sq - TRINITY) < 0.0001; +} diff --git a/apps/website/public/t27/specs/compiler/optimizer.t27 b/apps/website/public/t27/specs/compiler/optimizer.t27 new file mode 100644 index 0000000000..55f6037556 --- /dev/null +++ b/apps/website/public/t27/specs/compiler/optimizer.t27 @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: Apache-2.0 +module Optimization { + use compiler::parser; + + enum OptPass { + ConstantFolding, + DeadCodeElimination, + CopyPropagation, + StrengthReduction, + Inlining, + LoopInvariantCodeMotion, + } + + struct OptConfig { + enable_folding: bool; + enable_dce: bool; + enable_copy_prop: bool; + enable_strength: bool; + enable_inline: bool; + enable_licm: bool; + max_inline_depth: u32; + opt_level: u32; + } + + struct OptStats { + folds_performed: u32; + dead_code_removed: u32; + copies_propagated: u32; + strengths_reduced: u32; + inlines_done: u32; + licm_moved: u32; + total_passes: u32; + } + + fn default_config() -> OptConfig { + return OptConfig{ + enable_folding = true, + enable_dce = true, + enable_copy_prop = true, + enable_strength = true, + enable_inline = false, + enable_licm = false, + max_inline_depth = 3, + opt_level = 1, + }; + } + + fn opt_stats_init() -> OptStats { + return OptStats{ + folds_performed = 0, + dead_code_removed = 0, + copies_propagated = 0, + strengths_reduced = 0, + inlines_done = 0, + licm_moved = 0, + total_passes = 0, + }; + } + + fn is_pure_op(op: str) -> bool { + return op == "+" or op == "-" or op == "*" or op == "/" + or op == "==" or op == "!=" or op == "<" or op == ">" + or op == "<=" or op == ">=" or op == "and" or op == "or" + or op == "**"; + } + + fn is_const_literal(node: parser::Node) -> bool { + if (node.kind == parser::NodeKind::ExprLiteral) { + return true; + } + if (node.kind == parser::NodeKind::ExprUnary) { + if (node.children_count >= 1) { + return is_const_literal(node.children[0]); + } + } + return false; + } + + fn eval_binary_const(op: str, left_val: i64, right_val: i64) -> i64 { + if (op == "+") { return left_val + right_val; } + if (op == "-") { return left_val - right_val; } + if (op == "*") { return left_val * right_val; } + if (op == "/") { + if (right_val == 0) { return 0; } + return left_val / right_val; + } + if (op == "%") { + if (right_val == 0) { return 0; } + return left_val % right_val; + } + if (op == "**") { + if (right_val < 0) { return 0; } + if (right_val > 31) { return 0; } + var result: i64 = 1; + var i: i64 = 0; + while (i < right_val) { + result = result * left_val; + i = i + 1; + } + return result; + } + return 0; + } + + fn eval_compare_const(op: str, left_val: i64, right_val: i64) -> bool { + if (op == "==") { return left_val == right_val; } + if (op == "!=") { return left_val != right_val; } + if (op == "<") { return left_val < right_val; } + if (op == ">") { return left_val > right_val; } + if (op == "<=") { return left_val <= right_val; } + if (op == ">=") { return left_val >= right_val; } + return false; + } + + fn is_dead_stmt(stmt: parser::Node) -> bool { + if (stmt.kind == parser::NodeKind::StmtLocal) { + if (stmt.children_count == 0 and stmt.extra_type != "") { + return true; + } + } + return false; + } + + fn is_strength_reducible(op: str, right: parser::Node) -> bool { + if (op == "*" and is_const_literal(right)) { + return true; + } + if (op == "/" and is_const_literal(right)) { + return true; + } + return false; + } + + fn can_inline(fn_node: parser::Node, call_depth: u32, max_depth: u32) -> bool { + if (call_depth >= max_depth) { + return false; + } + var stmt_count: u32 = 0; + var i: u32 = 0; + while (i < fn_node.children_count) { + if (fn_node.children[i].kind != parser::NodeKind::StmtExpr) { + stmt_count = stmt_count + 1; + } + i = i + 1; + } + return stmt_count <= 5; + } + + fn optimize_expr(expr: parser::Node, config: OptConfig, stats: *OptStats) -> parser::Node { + if (config.enable_folding and expr.kind == parser::NodeKind::ExprBinary) { + if (expr.children_count >= 2) { + var left = optimize_expr(expr.children[0], config, stats); + var right = optimize_expr(expr.children[1], config, stats); + if (is_const_literal(left) and is_const_literal(right)) { + stats.folds_performed = stats.folds_performed + 1; + var result = parser::Node{ + kind = parser::NodeKind::ExprLiteral, + name = "", + value = "", + extra_type = "", + }; + return result; + } + } + } + return expr; + } + + fn optimize_stmt(stmt: parser::Node, config: OptConfig, stats: *OptStats) -> parser::Node { + if (config.enable_dce and is_dead_stmt(stmt)) { + stats.dead_code_removed = stats.dead_code_removed + 1; + return stmt; + } + if (config.enable_strength and stmt.kind == parser::NodeKind::StmtAssign) { + if (stmt.children_count >= 2) { + var val = stmt.children[1]; + if (val.kind == parser::NodeKind::ExprBinary) { + if (val.children_count >= 2 and is_strength_reducible(val.extra_op, val.children[1])) { + stats.strengths_reduced = stats.strengths_reduced + 1; + } + } + } + } + return stmt; + } + + fn optimize_fn(fn_node: parser::Node, config: OptConfig) -> OptStats { + var stats = opt_stats_init(); + var i: u32 = 0; + while (i < fn_node.children_count) { + var optimized = optimize_stmt(fn_node.children[i], config, &stats); + i = i + 1; + } + stats.total_passes = 1; + return stats; + } + + fn optimize(module: parser::Node, config: OptConfig) -> OptStats { + var stats = opt_stats_init(); + var i: u32 = 0; + while (i < module.children_count) { + var child = module.children[i]; + if (child.kind == parser::NodeKind::FnDecl) { + var fn_stats = optimize_fn(child, config); + stats.folds_performed = stats.folds_performed + fn_stats.folds_performed; + stats.dead_code_removed = stats.dead_code_removed + fn_stats.dead_code_removed; + stats.strengths_reduced = stats.strengths_reduced + fn_stats.strengths_reduced; + stats.total_passes = stats.total_passes + 1; + } + i = i + 1; + } + return stats; + } + + test is_pure_add + given result = is_pure_op("+") + then result == true + + test is_pure_assign + given result = is_pure_op("=") + then result == false + + test eval_binary_add + given result = eval_binary_const("+", 3, 4) + then result == 7 + + test eval_binary_mul + given result = eval_binary_const("*", 5, 6) + then result == 30 + + test eval_binary_div + given result = eval_binary_const("/", 10, 2) + then result == 5 + + test eval_binary_div_zero + given result = eval_binary_const("/", 10, 0) + then result == 0 + + test eval_binary_mod + given result = eval_binary_const("%", 10, 3) + then result == 1 + + test eval_compare_eq + given result = eval_compare_const("==", 5, 5) + then result == true + + test eval_compare_neq + given result = eval_compare_const("!=", 5, 3) + then result == true + + test eval_compare_lt + given result = eval_compare_const("<", 3, 5) + then result == true + + test eval_compare_gt_false + given result = eval_compare_const(">", 3, 5) + then result == false + + test default_config_level + given c = default_config() + then c.opt_level == 1 + + test default_config_folding + given c = default_config() + then c.enable_folding == true + + test default_config_inline + given c = default_config() + then c.enable_inline == false + + invariant pure_ops_are_not_assignment + assert is_pure_op("=") == false + + invariant div_zero_safe + assert eval_binary_const("/", 1, 0) == 0 + + invariant mod_zero_safe + assert eval_binary_const("%", 1, 0) == 0 + + invariant compare_eq_reflexive + assert eval_compare_const("==", 42, 42) == true + + invariant compare_lt_transitive_hint + assert eval_compare_const("<", 1, 100) == true +} diff --git a/apps/website/public/t27/specs/compiler/parser.t27 b/apps/website/public/t27/specs/compiler/parser.t27 new file mode 100644 index 0000000000..67502c31dd --- /dev/null +++ b/apps/website/public/t27/specs/compiler/parser.t27 @@ -0,0 +1,1618 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/compiler/parser.t27 +// T27 Parser Specification -- Self-hosting compiler core +// phi^2 + 1/phi^2 = 3 | TRINITY +// +// This module defines the complete recursive descent parser for the T27 language. +// It is a 1:1 port of bootstrap/src/compiler.rs Parser to t27 spec format. + +module Parsing { + use base::types; + use compiler::lexer; + + // ==================================================================== + // 1. AST Node Types + // ==================================================================== + + const MAX_CHILDREN: u32 = 32; + + enum NodeKind { + Module = 0, + UseDecl = 1, + ConstDecl = 2, + VarDecl = 3, + FnDecl = 4, + EnumDecl = 5, + StructDecl = 6, + TestBlock = 7, + InvariantBlock = 8, + BenchBlock = 9, + + StmtLocal = 10, + StmtAssign = 11, + StmtIf = 12, + StmtWhile = 13, + StmtFor = 14, + StmtExpr = 15, + StmtReturn = 16, + StmtBreak = 17, + StmtContinue = 18, + + ExprLiteral = 20, + ExprIdentifier = 21, + ExprBinary = 22, + ExprUnary = 23, + ExprCall = 24, + ExprFieldAccess = 25, + ExprIndex = 26, + ExprSwitch = 27, + ExprIf = 28, + ExprStructLit = 29, + ExprEnumValue = 30, + ExprReturn = 31, + ExprArrayLiteral = 32, + } + + struct Node { + kind: NodeKind; + name: str; + value: str; + extra_type: str; + extra_field: str; + extra_size: str; + extra_kind: str; + extra_op: str; + extra_pub: bool; + extra_mutable: bool; + extra_return_type: str; + child_count: u32; + children: [MAX_CHILDREN]Node; + } + + // ==================================================================== + // 2. Parser State + // ==================================================================== + + struct Parser { + lexer: lexer::Lexer; + current: lexer::Token; + peek: lexer::Token; + had_error: bool; + error_msg: str; + } + + fn parser_init(lex: lexer::Lexer) -> Parser { + var p = Parser{}; + p.lexer = lex; + p.advance(); + p.advance(); + return p; + } + + // ==================================================================== + // 3. Movement Primitives + // ==================================================================== + + fn advance(self: *Parser) -> void { + self.current = self.peek; + self.peek = lexer::next_token(self.lexer); + } + + fn check(self: *Parser, kind: lexer::TokenKind) -> bool { + return self.current.kind == kind; + } + + fn check_peek(self: *Parser, kind: lexer::TokenKind) -> bool { + return self.peek.kind == kind; + } + + fn expect(self: *Parser, kind: lexer::TokenKind) -> bool { + if (self.check(kind)) { + self.advance(); + return true; + } + self.had_error = true; + self.error_msg = "Expected token"; + return false; + } + + fn error(self: *Parser, msg: str) -> void { + self.had_error = true; + self.error_msg = msg; + } + + fn get_error(self: Parser) -> str { + return self.error_msg; + } + + // ==================================================================== + // 4. Error Recovery + // ==================================================================== + + fn skip_brace_body(self: *Parser) -> bool { + var depth: i32 = 1; + while (depth > 0) { + if (self.current.kind == lexer::TokenKind::Eof) { + self.error("Unexpected EOF inside brace body"); + return false; + } + if (self.current.kind == lexer::TokenKind::LBrace) { + depth = depth + 1; + } else if (self.current.kind == lexer::TokenKind::RBrace) { + depth = depth - 1; + if (depth == 0) { + return true; + } + } + self.advance(); + } + return true; + } + + fn skip_to_semicolon(self: *Parser) -> void { + var bracket_depth: i32 = 0; + var paren_depth: i32 = 0; + while (self.current.kind != lexer::TokenKind::Eof) { + if (self.current.kind == lexer::TokenKind::Semicolon + and bracket_depth == 0 + and paren_depth == 0) { + self.advance(); + return; + } + if (self.current.kind == lexer::TokenKind::LBrace) { + self.advance(); + self.skip_brace_body(); + if (self.current.kind == lexer::TokenKind::RBrace) { + self.advance(); + } + } else if (self.current.kind == lexer::TokenKind::LBracket) { + bracket_depth = bracket_depth + 1; + self.advance(); + } else if (self.current.kind == lexer::TokenKind::RBracket) { + bracket_depth = bracket_depth - 1; + self.advance(); + } else if (self.current.kind == lexer::TokenKind::LParen) { + paren_depth = paren_depth + 1; + self.advance(); + } else if (self.current.kind == lexer::TokenKind::RParen) { + paren_depth = paren_depth - 1; + self.advance(); + } else { + self.advance(); + } + } + } + + fn is_top_level_start(self: Parser) -> bool { + return self.current.kind == lexer::TokenKind::KwPub + or self.current.kind == lexer::TokenKind::KwFn + or self.current.kind == lexer::TokenKind::KwEnum + or self.current.kind == lexer::TokenKind::KwStruct + or self.current.kind == lexer::TokenKind::KwTest + or self.current.kind == lexer::TokenKind::KwInvariant + or self.current.kind == lexer::TokenKind::KwBench + or self.current.kind == lexer::TokenKind::KwUse + or self.current.kind == lexer::TokenKind::KwUsing + or self.current.kind == lexer::TokenKind::KwModule + or self.current.kind == lexer::TokenKind::RBrace + or self.current.kind == lexer::TokenKind::Eof; + } + + fn skip_to_next_top_level(self: *Parser) -> void { + var paren_depth: i32 = 0; + var bracket_depth: i32 = 0; + while (true) { + if (self.current.kind == lexer::TokenKind::Eof) { + break; + } + if (self.current.kind == lexer::TokenKind::LBrace) { + self.advance(); + self.skip_brace_body(); + if (self.current.kind == lexer::TokenKind::RBrace) { + self.advance(); + } + } + if (self.current.kind == lexer::TokenKind::LParen) { + paren_depth = paren_depth + 1; + self.advance(); + } + if (self.current.kind == lexer::TokenKind::RParen) { + paren_depth = paren_depth - 1; + self.advance(); + } + if (self.current.kind == lexer::TokenKind::LBracket) { + bracket_depth = bracket_depth + 1; + self.advance(); + } + if (self.current.kind == lexer::TokenKind::RBracket) { + bracket_depth = bracket_depth - 1; + self.advance(); + } + if (paren_depth == 0 and bracket_depth == 0 and self.is_top_level_start()) { + break; + } + self.advance(); + } + } + + fn recover_to_stmt_boundary(self: *Parser) -> void { + var brace_depth: i32 = 0; + while (true) { + if (self.current.kind == lexer::TokenKind::Eof) { + break; + } + if (self.current.kind == lexer::TokenKind::Semicolon and brace_depth == 0) { + self.advance(); + break; + } + if (self.current.kind == lexer::TokenKind::RBrace and brace_depth == 0) { + break; + } + if (self.current.kind == lexer::TokenKind::LBrace) { + brace_depth = brace_depth + 1; + self.advance(); + } else if (self.current.kind == lexer::TokenKind::RBrace) { + brace_depth = brace_depth - 1; + self.advance(); + } else { + self.advance(); + } + } + } + + // ==================================================================== + // 5. Type Annotation Parser + // ==================================================================== + + fn parse_type_annotation(self: *Parser) -> str { + var ty: str = ""; + + if (self.check(lexer::TokenKind::Star)) { + self.advance(); + ty = ty ++ "*"; + if (self.check(lexer::TokenKind::KwConst)) { + self.advance(); + ty = ty ++ "const "; + } + if (self.check(lexer::TokenKind::Ident)) { + ty = ty ++ self.current.lexeme; + self.advance(); + } + return ty; + } + + while (self.check(lexer::TokenKind::LBracket)) { + self.advance(); + ty = ty ++ "["; + while (self.current.kind != lexer::TokenKind::RBracket + and self.current.kind != lexer::TokenKind::Eof) { + ty = ty ++ self.current.lexeme; + self.advance(); + } + ty = ty ++ "]"; + if (self.check(lexer::TokenKind::RBracket)) { + self.advance(); + } + } + + if (self.check(lexer::TokenKind::KwConst)) { + if (ty != "") { + ty = ty ++ "const "; + } else { + ty = "const "; + } + self.advance(); + } + + if (self.check(lexer::TokenKind::Star)) { + self.advance(); + ty = ty ++ "*"; + if (self.check(lexer::TokenKind::KwConst)) { + self.advance(); + ty = ty ++ "const "; + } + } + + if (self.check(lexer::TokenKind::Ident)) { + ty = ty ++ self.current.lexeme; + self.advance(); + } else if (self.check(lexer::TokenKind::KwVoid)) { + ty = ty ++ "void"; + self.advance(); + } + + return ty; + } + + // ==================================================================== + // 6. Expression Parser (Precedence Levels) + // ==================================================================== + + fn node_new(kind: NodeKind) -> Node { + return Node{ + kind = kind, + name = "", + value = "", + extra_type = "", + extra_field = "", + extra_size = "", + extra_kind = "", + extra_op = "", + extra_pub = false, + extra_mutable = false, + extra_return_type = "", + child_count = 0, + children = [Node{}; MAX_CHILDREN], + }; + } + + fn add_child(parent: *Node, child: Node) -> void { + if (parent.child_count < MAX_CHILDREN) { + parent.children[parent.child_count] = child; + parent.child_count = parent.child_count + 1; + } + } + + fn parse_expr(self: *Parser) -> Node { + return self.parse_expr_or(); + } + + fn parse_expr_or(self: *Parser) -> Node { + var left = self.parse_expr_and(); + while (self.check(lexer::TokenKind::KwOr)) { + self.advance(); + const right = self.parse_expr_and(); + var node = node_new(NodeKind::ExprBinary); + node.extra_op = "or"; + add_child(&node, left); + add_child(&node, right); + left = node; + } + return left; + } + + fn parse_expr_and(self: *Parser) -> Node { + var left = self.parse_expr_comparison(); + while (self.check(lexer::TokenKind::KwAnd)) { + self.advance(); + const right = self.parse_expr_comparison(); + var node = node_new(NodeKind::ExprBinary); + node.extra_op = "and"; + add_child(&node, left); + add_child(&node, right); + left = node; + } + return left; + } + + fn parse_expr_comparison(self: *Parser) -> Node { + var left = self.parse_expr_bitor(); + while (self.check(lexer::TokenKind::Eq) + or self.check(lexer::TokenKind::Neq) + or self.check(lexer::TokenKind::Lt) + or self.check(lexer::TokenKind::Gt) + or self.check(lexer::TokenKind::Lte) + or self.check(lexer::TokenKind::Gte) + or self.check(lexer::TokenKind::DotDot)) { + const op = self.current.lexeme; + self.advance(); + const right = self.parse_expr_bitor(); + var node = node_new(NodeKind::ExprBinary); + node.extra_op = op; + add_child(&node, left); + add_child(&node, right); + left = node; + } + return left; + } + + fn parse_expr_bitor(self: *Parser) -> Node { + var left = self.parse_expr_bitxor(); + while (self.check(lexer::TokenKind::Pipe)) { + const op = self.current.lexeme; + self.advance(); + const right = self.parse_expr_bitxor(); + var node = node_new(NodeKind::ExprBinary); + node.extra_op = op; + add_child(&node, left); + add_child(&node, right); + left = node; + } + return left; + } + + fn parse_expr_bitxor(self: *Parser) -> Node { + var left = self.parse_expr_bitand(); + while (self.check(lexer::TokenKind::Caret)) { + const op = self.current.lexeme; + self.advance(); + const right = self.parse_expr_bitand(); + var node = node_new(NodeKind::ExprBinary); + node.extra_op = op; + add_child(&node, left); + add_child(&node, right); + left = node; + } + return left; + } + + fn parse_expr_bitand(self: *Parser) -> Node { + var left = self.parse_expr_shift(); + while (self.check(lexer::TokenKind::Amp)) { + const op = self.current.lexeme; + self.advance(); + const right = self.parse_expr_shift(); + var node = node_new(NodeKind::ExprBinary); + node.extra_op = op; + add_child(&node, left); + add_child(&node, right); + left = node; + } + return left; + } + + fn parse_expr_shift(self: *Parser) -> Node { + var left = self.parse_expr_additive(); + while (self.check(lexer::TokenKind::ShiftLeft) or self.check(lexer::TokenKind::ShiftRight)) { + const op = self.current.lexeme; + self.advance(); + const right = self.parse_expr_additive(); + var node = node_new(NodeKind::ExprBinary); + node.extra_op = op; + add_child(&node, left); + add_child(&node, right); + left = node; + } + return left; + } + + fn parse_expr_additive(self: *Parser) -> Node { + var left = self.parse_expr_multiplicative(); + while (self.check(lexer::TokenKind::Plus) + or self.check(lexer::TokenKind::Minus) + or self.check(lexer::TokenKind::PlusPercent)) { + const op = self.current.lexeme; + self.advance(); + const right = self.parse_expr_multiplicative(); + var node = node_new(NodeKind::ExprBinary); + node.extra_op = op; + add_child(&node, left); + add_child(&node, right); + left = node; + } + return left; + } + + fn parse_expr_multiplicative(self: *Parser) -> Node { + var left = self.parse_expr_unary(); + while (self.check(lexer::TokenKind::Star) + or self.check(lexer::TokenKind::Slash) + or self.check(lexer::TokenKind::Percent) + or self.check(lexer::TokenKind::Power)) { + const op = self.current.lexeme; + self.advance(); + const right = self.parse_expr_unary(); + var node = node_new(NodeKind::ExprBinary); + node.extra_op = op; + add_child(&node, left); + add_child(&node, right); + left = node; + } + return left; + } + + fn parse_expr_unary(self: *Parser) -> Node { + if (self.check(lexer::TokenKind::Minus) + or self.check(lexer::TokenKind::Bang) + or self.check(lexer::TokenKind::Tilde) + or self.check(lexer::TokenKind::Amp)) { + const op = self.current.lexeme; + self.advance(); + const operand = self.parse_expr_unary(); + var node = node_new(NodeKind::ExprUnary); + node.extra_op = op; + add_child(&node, operand); + return node; + } + return self.parse_expr_postfix(); + } + + fn parse_expr_postfix(self: *Parser) -> Node { + var expr = self.parse_expr_primary(); + + while (true) { + if (self.check(lexer::TokenKind::Dot)) { + self.advance(); + if (self.check(lexer::TokenKind::Star)) { + self.advance(); + var deref = node_new(NodeKind::ExprFieldAccess); + deref.name = "*"; + add_child(&deref, expr); + expr = deref; + } else if (self.check(lexer::TokenKind::Ident)) { + const field = self.current.lexeme; + self.advance(); + if (self.check(lexer::TokenKind::LParen)) { + expr = self.parse_call_args_internal(field, expr); + } else { + var fa = node_new(NodeKind::ExprFieldAccess); + fa.name = field; + add_child(&fa, expr); + expr = fa; + } + } else { + break; + } + } else if (self.check(lexer::TokenKind::LBracket)) { + self.advance(); + const index = self.parse_expr(); + self.expect(lexer::TokenKind::RBracket); + var idx_node = node_new(NodeKind::ExprIndex); + add_child(&idx_node, expr); + add_child(&idx_node, index); + expr = idx_node; + } else if (self.check(lexer::TokenKind::LParen)) { + break; + } else { + break; + } + } + return expr; + } + + fn parse_call_args_internal(self: *Parser, name: str, base_expr: Node) -> Node { + self.advance(); + var call = node_new(NodeKind::ExprCall); + call.name = name; + add_child(&call, base_expr); + + while (self.current.kind != lexer::TokenKind::RParen + and self.current.kind != lexer::TokenKind::Eof) { + const arg = self.parse_expr(); + add_child(&call, arg); + if (self.check(lexer::TokenKind::Comma)) { + self.advance(); + } + } + self.expect(lexer::TokenKind::RParen); + return call; + } + + fn parse_expr_primary(self: *Parser) -> Node { + if (self.check(lexer::TokenKind::Number)) { + var node = node_new(NodeKind::ExprLiteral); + node.value = self.current.lexeme; + self.advance(); + return node; + } + + if (self.check(lexer::TokenKind::CharLiteral)) { + var node = node_new(NodeKind::ExprLiteral); + node.value = self.current.lexeme; + self.advance(); + return node; + } + + if (self.check(lexer::TokenKind::StringLit)) { + var node = node_new(NodeKind::ExprLiteral); + node.value = self.current.lexeme; + self.advance(); + return node; + } + + if (self.check(lexer::TokenKind::KwTrue) or self.check(lexer::TokenKind::KwFalse)) { + var node = node_new(NodeKind::ExprLiteral); + node.value = self.current.lexeme; + self.advance(); + return node; + } + + if (self.check(lexer::TokenKind::Dot)) { + self.advance(); + if (self.check(lexer::TokenKind::Ident)) { + var node = node_new(NodeKind::ExprEnumValue); + node.name = self.current.lexeme; + self.advance(); + return node; + } + return node_new(NodeKind::ExprLiteral); + } + + if (self.check(lexer::TokenKind::Ident)) { + const name = self.current.lexeme; + self.advance(); + if (self.check(lexer::TokenKind::LBrace)) { + return self.parse_struct_literal(name); + } + if (self.check(lexer::TokenKind::LParen)) { + return self.parse_call_args(name); + } + var node = node_new(NodeKind::ExprIdentifier); + node.name = name; + return node; + } + + if (self.check(lexer::TokenKind::LParen)) { + self.advance(); + const inner = self.parse_expr(); + self.expect(lexer::TokenKind::RParen); + return inner; + } + + if (self.check(lexer::TokenKind::KwIf)) { + return self.parse_if_expr(); + } + + if (self.check(lexer::TokenKind::KwSwitch)) { + return self.parse_switch_expr(); + } + + if (self.check(lexer::TokenKind::KwTry)) { + self.advance(); + const inner = self.parse_expr_postfix(); + var node = node_new(NodeKind::ExprUnary); + node.extra_op = "try "; + add_child(&node, inner); + return node; + } + + if (self.check(lexer::TokenKind::LBracket)) { + return self.parse_array_literal(); + } + + return node_new(NodeKind::ExprLiteral); + } + + fn parse_call_args(self: *Parser, name: str) -> Node { + self.advance(); + var call = node_new(NodeKind::ExprCall); + call.name = name; + + while (self.current.kind != lexer::TokenKind::RParen + and self.current.kind != lexer::TokenKind::Eof) { + const arg = self.parse_expr(); + add_child(&call, arg); + if (self.check(lexer::TokenKind::Comma)) { + self.advance(); + } + } + self.expect(lexer::TokenKind::RParen); + return call; + } + + fn parse_struct_literal(self: *Parser, name: str) -> Node { + self.advance(); + var lit = node_new(NodeKind::ExprStructLit); + lit.name = name; + + while (self.current.kind != lexer::TokenKind::RBrace + and self.current.kind != lexer::TokenKind::Eof) { + var field_name = ""; + if (self.check(lexer::TokenKind::Dot)) { + self.advance(); + if (self.check(lexer::TokenKind::Ident)) { + field_name = self.current.lexeme; + self.advance(); + } + } else if (self.check(lexer::TokenKind::Ident)) { + field_name = self.current.lexeme; + self.advance(); + } + + if (self.check(lexer::TokenKind::Equals)) { + self.advance(); + } + + const val = self.parse_expr(); + var field = node_new(NodeKind::ExprFieldAccess); + field.name = field_name; + add_child(&field, val); + add_child(&lit, field); + + if (self.check(lexer::TokenKind::Comma)) { + self.advance(); + } + } + self.expect(lexer::TokenKind::RBrace); + return lit; + } + + fn parse_array_literal(self: *Parser) -> Node { + var node = node_new(NodeKind::ExprArrayLiteral); + self.advance(); + + if (self.current.kind == lexer::TokenKind::RBracket) { + self.advance(); + } else { + var bracket_text = ""; + while (self.current.kind != lexer::TokenKind::RBracket + and self.current.kind != lexer::TokenKind::Eof) { + bracket_text = bracket_text ++ self.current.lexeme; + self.advance(); + } + node.extra_size = bracket_text; + self.expect(lexer::TokenKind::RBracket); + } + + if (self.check(lexer::TokenKind::Ident)) { + node.extra_type = self.current.lexeme; + self.advance(); + } + + if (self.check(lexer::TokenKind::LBrace)) { + self.advance(); + if (self.current.kind != lexer::TokenKind::RBrace) { + var elem = self.parse_expr(); + add_child(&node, elem); + while (self.check(lexer::TokenKind::Comma)) { + self.advance(); + if (self.current.kind == lexer::TokenKind::RBrace) { + break; + } + elem = self.parse_expr(); + add_child(&node, elem); + } + } + self.expect(lexer::TokenKind::RBrace); + } + + if (self.check(lexer::TokenKind::Power)) { + self.advance(); + var count = self.parse_expr(); + var repeat_node = node_new(NodeKind::ExprBinary); + repeat_node.extra_op = "**"; + add_child(&repeat_node, node); + add_child(&repeat_node, count); + return repeat_node; + } + + return node; + } + + fn parse_if_expr(self: *Parser) -> Node { + self.advance(); + var node = node_new(NodeKind::ExprIf); + + self.expect(lexer::TokenKind::LParen); + const cond = self.parse_expr(); + self.expect(lexer::TokenKind::RParen); + add_child(&node, cond); + + const then_expr = self.parse_expr(); + add_child(&node, then_expr); + + if (self.check(lexer::TokenKind::KwElse)) { + self.advance(); + const else_expr = self.parse_expr(); + add_child(&node, else_expr); + } + + return node; + } + + fn parse_switch_expr(self: *Parser) -> Node { + self.advance(); + var sw = node_new(NodeKind::ExprSwitch); + + self.expect(lexer::TokenKind::LParen); + const val = self.parse_expr(); + self.expect(lexer::TokenKind::RParen); + add_child(&sw, val); + + self.expect(lexer::TokenKind::LBrace); + + while (self.current.kind != lexer::TokenKind::RBrace + and self.current.kind != lexer::TokenKind::Eof) { + var arm = node_new(NodeKind::ConstDecl); + + if (self.check(lexer::TokenKind::Dot)) { + self.advance(); + if (self.check(lexer::TokenKind::Ident)) { + arm.name = self.current.lexeme; + self.advance(); + } + } else if (self.check(lexer::TokenKind::KwElse)) { + arm.name = "else"; + self.advance(); + } else if (self.check(lexer::TokenKind::Minus)) { + arm.name = "-"; + self.advance(); + if (self.check(lexer::TokenKind::Number)) { + arm.name = arm.name ++ self.current.lexeme; + self.advance(); + } + } else if (self.check(lexer::TokenKind::Ident) or self.check(lexer::TokenKind::Number)) { + arm.name = self.current.lexeme; + self.advance(); + } + + if (self.check(lexer::TokenKind::FatArrow)) { + self.advance(); + } + + const arm_expr = self.parse_expr(); + add_child(&arm, arm_expr); + add_child(&sw, arm); + + if (self.check(lexer::TokenKind::Comma)) { + self.advance(); + } + } + + self.expect(lexer::TokenKind::RBrace); + return sw; + } + + // ==================================================================== + // 7. Statement Parser + // ==================================================================== + + fn parse_body_stmt(self: *Parser) -> Node { + if (self.check(lexer::TokenKind::KwConst) or self.check(lexer::TokenKind::KwVar)) { + return self.parse_local_decl(); + } + + if (self.check(lexer::TokenKind::KwReturn)) { + return self.parse_return_statement(); + } + + if (self.check(lexer::TokenKind::KwIf)) { + return self.parse_if_stmt(); + } + + if (self.check(lexer::TokenKind::KwWhile)) { + return self.parse_while_stmt(); + } + + if (self.check(lexer::TokenKind::KwFor)) { + return self.parse_for_stmt(); + } + + if (self.check(lexer::TokenKind::KwBreak)) { + self.advance(); + if (self.check(lexer::TokenKind::Semicolon)) { + self.advance(); + } + return node_new(NodeKind::StmtBreak); + } + + if (self.check(lexer::TokenKind::KwContinue)) { + self.advance(); + if (self.check(lexer::TokenKind::Semicolon)) { + self.advance(); + } + return node_new(NodeKind::StmtContinue); + } + + const expr = self.parse_expr(); + + if (self.check(lexer::TokenKind::Equals)) { + self.advance(); + const rhs = self.parse_expr(); + if (self.check(lexer::TokenKind::Semicolon)) { + self.advance(); + } + var assign = node_new(NodeKind::StmtAssign); + add_child(&assign, expr); + add_child(&assign, rhs); + return assign; + } + + if (self.check(lexer::TokenKind::PlusEquals)) { + self.advance(); + const rhs = self.parse_expr(); + if (self.check(lexer::TokenKind::Semicolon)) { + self.advance(); + } + var assign = node_new(NodeKind::StmtAssign); + assign.extra_op = "+="; + add_child(&assign, expr); + add_child(&assign, rhs); + return assign; + } + + if (self.check(lexer::TokenKind::Semicolon)) { + self.advance(); + } + var stmt = node_new(NodeKind::StmtExpr); + add_child(&stmt, expr); + return stmt; + } + + fn parse_local_decl(self: *Parser) -> Node { + var decl = node_new(NodeKind::StmtLocal); + decl.extra_mutable = self.check(lexer::TokenKind::KwVar); + self.advance(); + + if (self.check(lexer::TokenKind::Ident)) { + decl.name = self.current.lexeme; + self.advance(); + } + + if (self.check(lexer::TokenKind::Colon)) { + self.advance(); + decl.extra_type = self.parse_type_annotation(); + } + + if (self.check(lexer::TokenKind::Equals)) { + self.advance(); + const init = self.parse_expr(); + add_child(&decl, init); + } + + if (self.check(lexer::TokenKind::Semicolon)) { + self.advance(); + } + return decl; + } + + fn parse_return_statement(self: *Parser) -> Node { + var stmt = node_new(NodeKind::ExprReturn); + self.advance(); + + if (self.current.kind != lexer::TokenKind::Semicolon + and self.current.kind != lexer::TokenKind::RBrace) { + const expr = self.parse_expr(); + add_child(&stmt, expr); + } + + if (self.check(lexer::TokenKind::Semicolon)) { + self.advance(); + } + return stmt; + } + + fn parse_if_stmt(self: *Parser) -> Node { + var if_node = node_new(NodeKind::StmtIf); + self.advance(); + + self.expect(lexer::TokenKind::LParen); + const cond = self.parse_expr(); + self.expect(lexer::TokenKind::RParen); + add_child(&if_node, cond); + + if (self.check(lexer::TokenKind::LBrace)) { + self.advance(); + var then_block = node_new(NodeKind::Module); + then_block.name = "then"; + while (self.current.kind != lexer::TokenKind::RBrace + and self.current.kind != lexer::TokenKind::Eof) { + const s = self.parse_body_stmt(); + add_child(&then_block, s); + } + self.expect(lexer::TokenKind::RBrace); + add_child(&if_node, then_block); + } else { + const stmt = self.parse_body_stmt(); + var then_block = node_new(NodeKind::Module); + then_block.name = "then"; + add_child(&then_block, stmt); + add_child(&if_node, then_block); + } + + if (self.check(lexer::TokenKind::KwElse)) { + self.advance(); + if (self.check(lexer::TokenKind::KwIf)) { + const else_if = self.parse_if_stmt(); + var else_block = node_new(NodeKind::Module); + else_block.name = "else"; + add_child(&else_block, else_if); + add_child(&if_node, else_block); + } else if (self.check(lexer::TokenKind::LBrace)) { + self.advance(); + var else_block = node_new(NodeKind::Module); + else_block.name = "else"; + while (self.current.kind != lexer::TokenKind::RBrace + and self.current.kind != lexer::TokenKind::Eof) { + const s = self.parse_body_stmt(); + add_child(&else_block, s); + } + self.expect(lexer::TokenKind::RBrace); + add_child(&if_node, else_block); + } + } + + return if_node; + } + + fn parse_while_stmt(self: *Parser) -> Node { + var while_node = node_new(NodeKind::StmtWhile); + self.advance(); + + self.expect(lexer::TokenKind::LParen); + const cond = self.parse_expr(); + self.expect(lexer::TokenKind::RParen); + add_child(&while_node, cond); + + self.expect(lexer::TokenKind::LBrace); + var body_block = node_new(NodeKind::Module); + body_block.name = "body"; + while (self.current.kind != lexer::TokenKind::RBrace + and self.current.kind != lexer::TokenKind::Eof) { + const s = self.parse_body_stmt(); + add_child(&body_block, s); + } + self.expect(lexer::TokenKind::RBrace); + add_child(&while_node, body_block); + + return while_node; + } + + fn parse_for_stmt(self: *Parser) -> Node { + var for_node = node_new(NodeKind::StmtFor); + self.advance(); + + self.expect(lexer::TokenKind::LParen); + while (self.current.kind != lexer::TokenKind::RParen + and self.current.kind != lexer::TokenKind::Eof) { + const iter_expr = self.parse_expr(); + add_child(&for_node, iter_expr); + if (self.check(lexer::TokenKind::Comma)) { + self.advance(); + } + } + self.expect(lexer::TokenKind::RParen); + + if (self.check(lexer::TokenKind::Pipe)) { + self.advance(); + while (self.current.kind != lexer::TokenKind::Pipe + and self.current.kind != lexer::TokenKind::Eof) { + if (self.check(lexer::TokenKind::Star)) { + self.advance(); + } + if (self.check(lexer::TokenKind::Ident)) { + const param_name = self.current.lexeme; + self.advance(); + } + if (self.check(lexer::TokenKind::Comma)) { + self.advance(); + } + } + self.expect(lexer::TokenKind::Pipe); + } + + self.expect(lexer::TokenKind::LBrace); + var body_block = node_new(NodeKind::Module); + body_block.name = "body"; + while (self.current.kind != lexer::TokenKind::RBrace + and self.current.kind != lexer::TokenKind::Eof) { + const s = self.parse_body_stmt(); + add_child(&body_block, s); + } + self.expect(lexer::TokenKind::RBrace); + add_child(&for_node, body_block); + + return for_node; + } + + fn parse_fn_body(self: *Parser) -> void { + while (self.current.kind != lexer::TokenKind::RBrace + and self.current.kind != lexer::TokenKind::Eof) { + const s = self.parse_body_stmt(); + if (s.kind != NodeKind::ExprLiteral) { + // statement consumed + } + } + } + + // ==================================================================== + // 8. Struct and Enum Body Parsers + // ==================================================================== + + fn parse_struct_body(self: *Parser, decl: *Node) -> void { + while (self.current.kind != lexer::TokenKind::RBrace + and self.current.kind != lexer::TokenKind::Eof) { + if (self.check(lexer::TokenKind::Ident)) { + const field_name = self.current.lexeme; + self.advance(); + + var type_str = ""; + if (self.check(lexer::TokenKind::Colon)) { + self.advance(); + while (self.current.kind != lexer::TokenKind::Comma + and self.current.kind != lexer::TokenKind::Semicolon + and self.current.kind != lexer::TokenKind::RBrace + and self.current.kind != lexer::TokenKind::Eof) { + type_str = type_str ++ self.current.lexeme; + self.advance(); + } + } + + var field = node_new(NodeKind::ExprIdentifier); + field.name = field_name; + field.extra_type = type_str; + add_child(decl, field); + + if (self.check(lexer::TokenKind::Comma) or self.check(lexer::TokenKind::Semicolon)) { + self.advance(); + } + } else { + self.advance(); + } + } + } + + fn parse_enum_body(self: *Parser, decl: *Node) -> void { + while (self.current.kind != lexer::TokenKind::RBrace + and self.current.kind != lexer::TokenKind::Eof) { + if (self.check(lexer::TokenKind::Ident)) { + const name = self.current.lexeme; + self.advance(); + + var value_str = ""; + if (self.check(lexer::TokenKind::Equals)) { + self.advance(); + if (self.check(lexer::TokenKind::Minus)) { + value_str = "-"; + self.advance(); + } + if (self.check(lexer::TokenKind::Number)) { + value_str = value_str ++ self.current.lexeme; + self.advance(); + } else if (self.check(lexer::TokenKind::Ident)) { + value_str = value_str ++ self.current.lexeme; + self.advance(); + } + } + + var variant = node_new(NodeKind::ExprLiteral); + variant.name = name; + variant.value = value_str; + add_child(decl, variant); + + if (self.check(lexer::TokenKind::Comma)) { + self.advance(); + } + } else { + self.advance(); + } + } + } + + // ==================================================================== + // 9. Top-Level Declaration Parsers + // ==================================================================== + + fn parse_top_level_decl(self: *Parser) -> Node { + var is_pub = false; + if (self.check(lexer::TokenKind::KwPub)) { + is_pub = true; + self.advance(); + } + + if (self.check(lexer::TokenKind::KwConst)) { + return self.parse_const_decl(is_pub); + } + if (self.check(lexer::TokenKind::KwVar)) { + return self.parse_var_decl(is_pub); + } + if (self.check(lexer::TokenKind::KwFn)) { + return self.parse_fn_decl(is_pub); + } + if (self.check(lexer::TokenKind::KwEnum)) { + return self.parse_enum_decl(is_pub); + } + if (self.check(lexer::TokenKind::KwStruct)) { + return self.parse_struct_decl(is_pub); + } + + return node_new(NodeKind::Module); + } + + fn parse_const_decl(self: *Parser, is_pub: bool) -> Node { + var decl = node_new(NodeKind::ConstDecl); + decl.extra_pub = is_pub; + + self.advance(); + + if (self.check(lexer::TokenKind::Ident)) { + decl.name = self.current.lexeme; + self.advance(); + } + + if (self.check(lexer::TokenKind::Colon)) { + self.advance(); + var type_str = ""; + if (self.check(lexer::TokenKind::LBracket)) { + type_str = "["; + self.advance(); + while (self.current.kind != lexer::TokenKind::RBracket + and self.current.kind != lexer::TokenKind::Eof) { + type_str = type_str ++ self.current.lexeme; + self.advance(); + } + type_str = type_str ++ "]"; + if (self.check(lexer::TokenKind::RBracket)) { + self.advance(); + } + } + if (self.check(lexer::TokenKind::Ident)) { + type_str = type_str ++ self.current.lexeme; + self.advance(); + } + decl.extra_type = type_str; + } + + if (self.check(lexer::TokenKind::Equals)) { + self.advance(); + + if (self.check(lexer::TokenKind::KwEnum)) { + decl.kind = NodeKind::EnumDecl; + self.advance(); + if (self.check(lexer::TokenKind::LParen)) { + self.advance(); + if (self.check(lexer::TokenKind::Ident)) { + decl.extra_type = self.current.lexeme; + self.advance(); + } + self.expect(lexer::TokenKind::RParen); + } + self.expect(lexer::TokenKind::LBrace); + self.parse_enum_body(&decl); + self.expect(lexer::TokenKind::RBrace); + } else if (self.check(lexer::TokenKind::KwStruct)) { + decl.kind = NodeKind::StructDecl; + self.advance(); + self.expect(lexer::TokenKind::LBrace); + self.parse_struct_body(&decl); + self.expect(lexer::TokenKind::RBrace); + } else if (self.check(lexer::TokenKind::Minus)) { + self.advance(); + if (self.check(lexer::TokenKind::Number)) { + var val_node = node_new(NodeKind::ExprLiteral); + val_node.value = "-" ++ self.current.lexeme; + add_child(&decl, val_node); + self.advance(); + } + } else if (self.check(lexer::TokenKind::Number)) { + var val_node = node_new(NodeKind::ExprLiteral); + val_node.value = self.current.lexeme; + add_child(&decl, val_node); + self.advance(); + } else if (self.check(lexer::TokenKind::Ident)) { + var val_node = node_new(NodeKind::ExprIdentifier); + val_node.name = self.current.lexeme; + add_child(&decl, val_node); + self.advance(); + } else if (self.check(lexer::TokenKind::KwTrue) or self.check(lexer::TokenKind::KwFalse)) { + var val_node = node_new(NodeKind::ExprLiteral); + val_node.value = self.current.lexeme; + add_child(&decl, val_node); + self.advance(); + } else if (self.check(lexer::TokenKind::StringLit)) { + var val_node = node_new(NodeKind::ExprLiteral); + val_node.value = self.current.lexeme; + add_child(&decl, val_node); + self.advance(); + } else { + self.skip_to_semicolon(); + return decl; + } + + if (self.current.kind != lexer::TokenKind::Semicolon) { + self.skip_to_semicolon(); + } + } + + if (self.check(lexer::TokenKind::Semicolon)) { + self.advance(); + } + return decl; + } + + fn parse_var_decl(self: *Parser, is_pub: bool) -> Node { + var decl = node_new(NodeKind::VarDecl); + decl.extra_pub = is_pub; + + self.advance(); + + if (self.check(lexer::TokenKind::Ident)) { + decl.name = self.current.lexeme; + self.advance(); + } + + self.skip_to_semicolon(); + return decl; + } + + fn parse_enum_decl(self: *Parser, is_pub: bool) -> Node { + var decl = node_new(NodeKind::EnumDecl); + decl.extra_pub = is_pub; + + self.advance(); + + if (self.check(lexer::TokenKind::Ident)) { + decl.name = self.current.lexeme; + self.advance(); + } + + if (self.check(lexer::TokenKind::LParen)) { + self.advance(); + while (self.current.kind != lexer::TokenKind::RParen + and self.current.kind != lexer::TokenKind::Eof) { + self.advance(); + } + self.expect(lexer::TokenKind::RParen); + } + + self.expect(lexer::TokenKind::LBrace); + self.skip_brace_body(); + self.expect(lexer::TokenKind::RBrace); + + return decl; + } + + fn parse_struct_decl(self: *Parser, is_pub: bool) -> Node { + var decl = node_new(NodeKind::StructDecl); + decl.extra_pub = is_pub; + + self.advance(); + + if (self.check(lexer::TokenKind::Ident)) { + decl.name = self.current.lexeme; + self.advance(); + } + + self.expect(lexer::TokenKind::LBrace); + self.parse_struct_body(&decl); + self.expect(lexer::TokenKind::RBrace); + + return decl; + } + + fn parse_fn_decl(self: *Parser, is_pub: bool) -> Node { + var decl = node_new(NodeKind::FnDecl); + decl.extra_pub = is_pub; + + self.advance(); + + if (self.check(lexer::TokenKind::Ident)) { + decl.name = self.current.lexeme; + self.advance(); + while (self.check(lexer::TokenKind::Dot)) { + self.advance(); + if (self.check(lexer::TokenKind::Ident)) { + decl.name = decl.name ++ "." ++ self.current.lexeme; + self.advance(); + } + } + } + + self.expect(lexer::TokenKind::LParen); + while (self.current.kind != lexer::TokenKind::RParen + and self.current.kind != lexer::TokenKind::Eof) { + if (self.check(lexer::TokenKind::Ident)) { + const param_name = self.current.lexeme; + self.advance(); + if (self.check(lexer::TokenKind::Colon)) { + self.advance(); + } + const param_type = self.parse_type_annotation(); + } + if (self.check(lexer::TokenKind::Comma)) { + self.advance(); + } + } + self.expect(lexer::TokenKind::RParen); + + if (self.check(lexer::TokenKind::Arrow)) { + self.advance(); + } + + if (self.check(lexer::TokenKind::Bang)) { + self.advance(); + } + + if (self.check(lexer::TokenKind::Ident)) { + decl.extra_return_type = self.current.lexeme; + self.advance(); + } else if (self.check(lexer::TokenKind::LBracket)) { + var rt = ""; + while (self.check(lexer::TokenKind::LBracket)) { + rt = rt ++ "["; + self.advance(); + while (self.current.kind != lexer::TokenKind::RBracket + and self.current.kind != lexer::TokenKind::Eof) { + rt = rt ++ self.current.lexeme; + self.advance(); + } + rt = rt ++ "]"; + if (self.check(lexer::TokenKind::RBracket)) { + self.advance(); + } + } + if (self.check(lexer::TokenKind::KwConst)) { + rt = rt ++ "const "; + self.advance(); + } + if (self.check(lexer::TokenKind::Star)) { + rt = rt ++ "*"; + self.advance(); + } + if (self.check(lexer::TokenKind::Ident)) { + rt = rt ++ self.current.lexeme; + self.advance(); + } + decl.extra_return_type = rt; + } else if (self.check(lexer::TokenKind::KwVoid)) { + decl.extra_return_type = "void"; + self.advance(); + } + + if (self.check(lexer::TokenKind::KwConst)) { + self.advance(); + } + + self.expect(lexer::TokenKind::LBrace); + self.parse_fn_body(); + self.expect(lexer::TokenKind::RBrace); + + return decl; + } + + // ==================================================================== + // 10. Module Parser + // ==================================================================== + + fn parse(self: *Parser) -> Node { + var module = node_new(NodeKind::Module); + + if (self.check(lexer::TokenKind::KwModule)) { + self.advance(); + if (self.check(lexer::TokenKind::Ident)) { + module.name = self.current.lexeme; + self.advance(); + while (self.check(lexer::TokenKind::Minus)) { + module.name = module.name ++ "-"; + self.advance(); + if (self.check(lexer::TokenKind::Ident) or self.check(lexer::TokenKind::Number)) { + module.name = module.name ++ self.current.lexeme; + self.advance(); + } + } + } + if (self.check(lexer::TokenKind::Semicolon)) { + self.advance(); + } else if (self.check(lexer::TokenKind::LBrace)) { + self.advance(); + self.parse_module_body(&module); + self.expect(lexer::TokenKind::RBrace); + return module; + } + } + + self.parse_module_body(&module); + return module; + } + + fn parse_module_body(self: *Parser, module: *Node) -> void { + while (self.current.kind != lexer::TokenKind::Eof + and self.current.kind != lexer::TokenKind::RBrace) { + + if (self.check(lexer::TokenKind::KwUse) or self.check(lexer::TokenKind::KwUsing)) { + self.advance(); + var full_path = ""; + var alias_name = ""; + if (self.check(lexer::TokenKind::Ident)) { + full_path = self.current.lexeme; + self.advance(); + + if (self.check(lexer::TokenKind::Colon) and not self.check_peek(lexer::TokenKind::Colon)) { + alias_name = full_path; + self.advance(); + if (self.check(lexer::TokenKind::Ident)) { + self.advance(); + } + full_path = alias_name; + } else { + while (self.check(lexer::TokenKind::Colon)) { + self.advance(); + if (self.check(lexer::TokenKind::Colon)) { + self.advance(); + full_path = full_path ++ "::"; + if (self.check(lexer::TokenKind::Ident)) { + full_path = full_path ++ self.current.lexeme; + self.advance(); + } + } + } + } + } + if (self.check(lexer::TokenKind::Semicolon)) { + self.advance(); + } + const import_name = alias_name; + var use_node = node_new(NodeKind::UseDecl); + use_node.name = import_name; + use_node.value = full_path; + add_child(module, use_node); + continue; + } + + const decl = self.parse_top_level_decl(); + if (decl.kind != NodeKind::Module) { + add_child(module, decl); + } + } + } + + // ==================================================================== + // 11. Tests + // ==================================================================== + + test "kind_values" { + assert(NodeKind::Module == 0) + assert(NodeKind::FnDecl == 4) + assert(NodeKind::ExprBinary == 22) + assert(NodeKind::ExprCall == 24) + } + + test "precedence_order" { + const or_level = 0 + const and_level = 1 + const comp_level = 2 + const add_level = 3 + const mul_level = 4 + const unary_level = 5 + assert(or_level < and_level) + assert(and_level < comp_level) + assert(comp_level < add_level) + assert(add_level < mul_level) + assert(mul_level < unary_level) + } + + test "max_children_constant" { + assert(MAX_CHILDREN == 32) + } + + test "node_kind_count" { + assert(NodeKind::ExprReturn == 31) + assert(NodeKind::StmtLocal == 10) + } + + test "node_kind_coverage" { + assert(NodeKind::Module == NodeKind::Module) + assert(NodeKind::FnDecl != NodeKind::StructDecl) + } + + test "expression_precedence_levels" { + assert(10 > 9) + assert(9 > 8) + assert(8 > 7) + assert(7 > 6) + assert(6 > 5) + assert(5 > 4) + assert(4 > 3) + assert(3 > 2) + assert(2 > 1) + assert(1 > 0) + } + + test "break_continue_node_kinds" { + assert(NodeKind::StmtBreak == 17) + assert(NodeKind::StmtContinue == 18) + assert(NodeKind::StmtBreak != NodeKind::StmtContinue) + } + + test "error_recovery_skip_to_semicolon" { + var p = parser_init("fn foo() { x; }", 16); + assert(p.current.kind != lexer::TokenKind::Eof) + } + + test "struct_decl_distinct_from_enum" { + assert(NodeKind::StructDecl == 6) + assert(NodeKind::EnumDecl == 5) + assert(NodeKind::StructDecl != NodeKind::EnumDecl) + } + + test "all_statement_kinds_unique" { + assert(NodeKind::StmtLocal != NodeKind::StmtAssign) + assert(NodeKind::StmtAssign != NodeKind::StmtIf) + assert(NodeKind::StmtIf != NodeKind::StmtWhile) + assert(NodeKind::StmtWhile != NodeKind::StmtFor) + assert(NodeKind::StmtFor != NodeKind::StmtExpr) + assert(NodeKind::StmtExpr != NodeKind::ExprReturn) + assert(NodeKind::ExprReturn != NodeKind::StmtBreak) + assert(NodeKind::StmtBreak != NodeKind::StmtContinue) + } +} diff --git a/apps/website/public/t27/specs/compiler/pipeline.t27 b/apps/website/public/t27/specs/compiler/pipeline.t27 new file mode 100644 index 0000000000..e15ef4d7ed --- /dev/null +++ b/apps/website/public/t27/specs/compiler/pipeline.t27 @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: Apache-2.0 +module Pipeline { + use compiler::lexer; + use compiler::parser; + use compiler::typechecker; + use compiler::optimizer; + use compiler::stdlib; + use compiler::diagnostics; + use compiler::linker; + + enum PipelineStage { + Lex = 0, + Parse = 1, + TypeCheck = 2, + Optimize = 3, + Codegen = 4, + Link = 5, + } + + struct PipelineConfig { + target_backend: str; + opt_level: u32; + emit_debug: bool; + emit_comments: bool; + check_only: bool; + } + + struct PipelineResult { + success: bool; + stage_reached: u32; + lex_tokens: u32; + parse_nodes: u32; + type_errors: u32; + opt_folds: u32; + opt_dead: u32; + opt_copies: u32; + opt_strengths: u32; + gen_lines: u32; + error_msg: str; + } + + fn default_pipeline_config() -> PipelineConfig { + return PipelineConfig{ + target_backend = "zig", + opt_level = 1, + emit_debug = false, + emit_comments = false, + check_only = false, + }; + } + + fn pipeline_result_ok(stage: u32) -> PipelineResult { + return PipelineResult{ + success = true, + stage_reached = stage, + lex_tokens = 0, + parse_nodes = 0, + type_errors = 0, + opt_folds = 0, + opt_dead = 0, + opt_copies = 0, + opt_strengths = 0, + gen_lines = 0, + error_msg = "", + }; + } + + fn pipeline_result_fail(stage: u32, msg: str) -> PipelineResult { + return PipelineResult{ + success = false, + stage_reached = stage, + lex_tokens = 0, + parse_nodes = 0, + type_errors = 0, + opt_folds = 0, + opt_dead = 0, + opt_copies = 0, + opt_strengths = 0, + gen_lines = 0, + error_msg = msg, + }; + } + + fn stage_name(stage: u32) -> str { + if (stage == 0) { return "lex"; } + if (stage == 1) { return "parse"; } + if (stage == 2) { return "typecheck"; } + if (stage == 3) { return "optimize"; } + if (stage == 4) { return "codegen"; } + if (stage == 5) { return "link"; } + return "unknown"; + } + + fn is_full_pipeline(r: PipelineResult) -> bool { + return r.success and r.stage_reached >= 4; + } + + fn total_optimizations(r: PipelineResult) -> u32 { + return r.opt_folds + r.opt_dead + r.opt_copies + r.opt_strengths; + } + + test default_config_backend + given c = default_pipeline_config() + then c.target_backend == "zig" + + test default_config_opt_level + given c = default_pipeline_config() + then c.opt_level == 1 + + test pipeline_ok_success + given r = pipeline_result_ok(4) + then r.success == true + + test pipeline_ok_stage + given r = pipeline_result_ok(4) + then r.stage_reached == 4 + + test pipeline_fail_not_success + given r = pipeline_result_fail(1, "bad token") + then r.success == false + + test pipeline_fail_stage + given r = pipeline_result_fail(1, "bad token") + then r.stage_reached == 1 + + test stage_name_lex + given n = stage_name(0) + then n == "lex" + + test stage_name_codegen + given n = stage_name(4) + then n == "codegen" + + test stage_name_unknown + given n = stage_name(99) + then n == "unknown" + + test is_full_pipeline_true + given r = pipeline_result_ok(5) + then is_full_pipeline(r) == true + + test is_full_pipeline_false + given r = pipeline_result_ok(2) + then is_full_pipeline(r) == false + + test total_optimizations_zero + given r = pipeline_result_ok(4) + then total_optimizations(r) == 0 + + test total_optimizations_sum + var r = pipeline_result_ok(4) + r.opt_folds = 3 + r.opt_dead = 1 + r.opt_copies = 2 + r.opt_strengths = 4 + then total_optimizations(r) == 10 + + invariant stage_names_complete + assert stage_name(0) == "lex" + assert stage_name(1) == "parse" + assert stage_name(2) == "typecheck" + assert stage_name(3) == "optimize" + assert stage_name(4) == "codegen" + assert stage_name(5) == "link" + + invariant fail_never_full_pipeline + given r = pipeline_result_fail(5, "error") + assert is_full_pipeline(r) == false +} diff --git a/apps/website/public/t27/specs/compiler/stdlib.t27 b/apps/website/public/t27/specs/compiler/stdlib.t27 new file mode 100644 index 0000000000..2e79524db1 --- /dev/null +++ b/apps/website/public/t27/specs/compiler/stdlib.t27 @@ -0,0 +1,662 @@ +// SPDX-License-Identifier: Apache-2.0 +module Stdlib { + use compiler::parser; + use compiler::lexer; + + enum StdlibResult { + Ok, + Err, + Empty, + } + + struct Vec { + data: [256]u8; + len: u32; + capacity: u32; + } + + struct Str { + data: [4096]u8; + len: u32; + } + + struct Map { + keys: [64]Str; + values: [64]Str; + count: u32; + } + + struct Opt { + has_value: bool; + value: u64; + } + + struct Result { + is_ok: bool; + value: u64; + error_code: u32; + } + + struct Iterator { + data: [256]u8; + data_len: u32; + pos: u32; + } + + const DEFAULT_CAPACITY: u32 = 16; + const MAX_MAP_ENTRIES: u32 = 64; + const MAX_STRING_LEN: u32 = 4096; + + fn vec_new() -> Vec { + return Vec{ data = [0; 256], len = 0, capacity = DEFAULT_CAPACITY }; + } + + fn vec_with_capacity(cap: u32) -> Vec { + return Vec{ data = [0; 256], len = 0, capacity = cap }; + } + + fn vec_push(v: *Vec, val: u8) -> StdlibResult { + if (v.len >= v.capacity) { + return StdlibResult::Err; + } + v.data[v.len] = val; + v.len = v.len + 1; + return StdlibResult::Ok; + } + + fn vec_pop(v: *Vec) -> u8 { + if (v.len == 0) { + return 0; + } + v.len = v.len - 1; + return v.data[v.len]; + } + + fn vec_get(v: Vec, idx: u32) -> u8 { + if (idx >= v.len) { + return 0; + } + return v.data[idx]; + } + + fn vec_set(v: *Vec, idx: u32, val: u8) -> StdlibResult { + if (idx >= v.len) { + return StdlibResult::Err; + } + v.data[idx] = val; + return StdlibResult::Ok; + } + + fn vec_len(v: Vec) -> u32 { + return v.len; + } + + fn vec_clear(v: *Vec) -> void { + v.len = 0; + } + + fn vec_is_empty(v: Vec) -> bool { + return v.len == 0; + } + + fn vec_contains(v: Vec, val: u8) -> bool { + var i: u32 = 0; + while (i < v.len) { + if (v.data[i] == val) { + return true; + } + i = i + 1; + } + return false; + } + + fn vec_index_of(v: Vec, val: u8) -> i32 { + var i: u32 = 0; + while (i < v.len) { + if (v.data[i] == val) { + return i as i32; + } + i = i + 1; + } + return -1; + } + + fn vec_reverse(v: *Vec) -> void { + var i: u32 = 0; + var j: u32 = v.len; + while (i < j) { + j = j - 1; + var tmp = v.data[i]; + v.data[i] = v.data[j]; + v.data[j] = tmp; + i = i + 1; + } + } + + fn str_new() -> Str { + return Str{ data = [0; 4096], len = 0 }; + } + + fn str_from(raw: str, raw_len: u32) -> Str { + var s = Str{ data = [0; 4096], len = raw_len }; + var i: u32 = 0; + while (i < raw_len and i < MAX_STRING_LEN) { + s.data[i] = raw[i]; + i = i + 1; + } + s.len = i; + return s; + } + + fn str_len(s: Str) -> u32 { + return s.len; + } + + fn str_is_empty(s: Str) -> bool { + return s.len == 0; + } + + fn str_eq(a: Str, b: Str) -> bool { + if (a.len != b.len) { + return false; + } + var i: u32 = 0; + while (i < a.len) { + if (a.data[i] != b.data[i]) { + return false; + } + i = i + 1; + } + return true; + } + + fn str_concat(a: Str, b: Str) -> Str { + var result = Str{ data = [0; 4096], len = 0 }; + var i: u32 = 0; + while (i < a.len and result.len < MAX_STRING_LEN) { + result.data[result.len] = a.data[i]; + result.len = result.len + 1; + i = i + 1; + } + i = 0; + while (i < b.len and result.len < MAX_STRING_LEN) { + result.data[result.len] = b.data[i]; + result.len = result.len + 1; + i = i + 1; + } + return result; + } + + fn str_char_at(s: Str, idx: u32) -> u8 { + if (idx >= s.len) { + return 0; + } + return s.data[idx]; + } + + fn str_contains(s: Str, ch: u8) -> bool { + var i: u32 = 0; + while (i < s.len) { + if (s.data[i] == ch) { + return true; + } + i = i + 1; + } + return false; + } + + fn str_index_of(s: Str, ch: u8) -> i32 { + var i: u32 = 0; + while (i < s.len) { + if (s.data[i] == ch) { + return i as i32; + } + i = i + 1; + } + return -1; + } + + fn str_sub(s: Str, start: u32, length: u32) -> Str { + var result = Str{ data = [0; 4096], len = 0 }; + var i: u32 = 0; + while (i < length and start + i < s.len and result.len < MAX_STRING_LEN) { + result.data[result.len] = s.data[start + i]; + result.len = result.len + 1; + i = i + 1; + } + return result; + } + + fn map_new() -> Map { + return Map{ + keys = [Str{ data = [0; 4096], len = 0 }; MAX_MAP_ENTRIES], + values = [Str{ data = [0; 4096], len = 0 }; MAX_MAP_ENTRIES], + count = 0, + }; + } + + fn map_insert(m: *Map, key: Str, val: Str) -> StdlibResult { + var i: u32 = 0; + while (i < m.count) { + if (str_eq(m.keys[i], key)) { + m.values[i] = val; + return StdlibResult::Ok; + } + i = i + 1; + } + if (m.count >= MAX_MAP_ENTRIES) { + return StdlibResult::Err; + } + m.keys[m.count] = key; + m.values[m.count] = val; + m.count = m.count + 1; + return StdlibResult::Ok; + } + + fn map_get(m: Map, key: Str) -> Opt { + var i: u32 = 0; + while (i < m.count) { + if (str_eq(m.keys[i], key)) { + return Opt{ has_value = true, value = 0 }; + } + i = i + 1; + } + return Opt{ has_value = false, value = 0 }; + } + + fn map_contains(m: Map, key: Str) -> bool { + var i: u32 = 0; + while (i < m.count) { + if (str_eq(m.keys[i], key)) { + return true; + } + i = i + 1; + } + return false; + } + + fn map_len(m: Map) -> u32 { + return m.count; + } + + fn opt_some(val: u64) -> Opt { + return Opt{ has_value = true, value = val }; + } + + fn opt_none() -> Opt { + return Opt{ has_value = false, value = 0 }; + } + + fn opt_is_some(o: Opt) -> bool { + return o.has_value; + } + + fn opt_is_none(o: Opt) -> bool { + return o.has_value == false; + } + + fn opt_unwrap(o: Opt) -> u64 { + return o.value; + } + + fn opt_unwrap_or(o: Opt, default: u64) -> u64 { + if (o.has_value) { + return o.value; + } + return default; + } + + fn result_ok(val: u64) -> Result { + return Result{ is_ok = true, value = val, error_code = 0 }; + } + + fn result_err(code: u32) -> Result { + return Result{ is_ok = false, value = 0, error_code = code }; + } + + fn result_is_ok(r: Result) -> bool { + return r.is_ok; + } + + fn result_is_err(r: Result) -> bool { + return r.is_ok == false; + } + + fn result_unwrap(r: Result) -> u64 { + return r.value; + } + + fn result_unwrap_or(r: Result, default: u64) -> u64 { + if (r.is_ok) { + return r.value; + } + return default; + } + + fn result_error_code(r: Result) -> u32 { + return r.error_code; + } + + fn iter_new(data: [256]u8, len: u32) -> Iterator { + return Iterator{ data = data, data_len = len, pos = 0 }; + } + + fn iter_has_next(it: Iterator) -> bool { + return it.pos < it.data_len; + } + + fn iter_next(it: *Iterator) -> u8 { + if (it.pos >= it.data_len) { + return 0; + } + var val = it.data[it.pos]; + it.pos = it.pos + 1; + return val; + } + + fn iter_peek(it: Iterator) -> u8 { + if (it.pos >= it.data_len) { + return 0; + } + return it.data[it.pos]; + } + + fn iter_pos(it: Iterator) -> u32 { + return it.pos; + } + + fn iter_remaining(it: Iterator) -> u32 { + if (it.pos >= it.data_len) { + return 0; + } + return it.data_len - it.pos; + } + + fn fmt_u32(val: u32) -> Str { + if (val == 0) { + var s = str_new(); + s.data[0] = 48; + s.len = 1; + return s; + } + var buf: [16]u8; + var pos: u32 = 16; + var n = val; + while (n > 0) { + pos = pos - 1; + buf[pos] = 48 + (n % 10) as u8; + n = n / 10; + } + var result = str_new(); + var i: u32 = pos; + while (i < 16) { + result.data[result.len] = buf[i]; + result.len = result.len + 1; + i = i + 1; + } + return result; + } + + fn fmt_i32(val: i32) -> Str { + if (val < 0) { + var result = str_new(); + result.data[0] = 45; + result.len = 1; + var uval = (0 - val) as u32; + var digits = fmt_u32(uval); + var i: u32 = 0; + while (i < digits.len and result.len < MAX_STRING_LEN) { + result.data[result.len] = digits.data[i]; + result.len = result.len + 1; + i = i + 1; + } + return result; + } + return fmt_u32(val as u32); + } + + fn fmt_bool(val: bool) -> Str { + if (val) { + var s = str_new(); + s.data[0] = 116; s.data[1] = 114; s.data[2] = 117; s.data[3] = 101; + s.len = 4; + return s; + } + var s = str_new(); + s.data[0] = 102; s.data[1] = 97; s.data[2] = 108; s.data[3] = 115; s.data[4] = 101; + s.len = 5; + return s; + } + + fn fmt_hex(val: u32) -> Str { + var hex_chars: [16]u8; + hex_chars[0] = 48; hex_chars[1] = 49; hex_chars[2] = 50; hex_chars[3] = 51; + hex_chars[4] = 52; hex_chars[5] = 53; hex_chars[6] = 54; hex_chars[7] = 55; + hex_chars[8] = 56; hex_chars[9] = 57; hex_chars[10] = 97; hex_chars[11] = 98; + hex_chars[12] = 99; hex_chars[13] = 100; hex_chars[14] = 101; hex_chars[15] = 102; + var result = str_new(); + if (val == 0) { + result.data[0] = 48; result.data[1] = 120; result.data[2] = 48; + result.len = 3; + return result; + } + result.data[0] = 48; result.data[1] = 120; + result.len = 2; + var started: bool = false; + var shift: i32 = 28; + while (shift >= 0) { + var nibble = (val >> shift) as u32 and 15; + if (nibble != 0 or started) { + result.data[result.len] = hex_chars[nibble]; + result.len = result.len + 1; + started = true; + } + shift = shift - 4; + } + return result; + } + + test vec_new_empty + given v = vec_new() + then vec_is_empty(v) == true + + test vec_push_len + given v = vec_new() + given _ = vec_push(&v, 42) + then vec_len(v) == 1 + + test vec_push_pop + given v = vec_new() + given _ = vec_push(&v, 42) + given val = vec_pop(&v) + then val == 42 + + test vec_get_out_of_bounds + given v = vec_new() + then vec_get(v, 0) == 0 + + test vec_contains_found + given v = vec_new() + given _ = vec_push(&v, 7) + then vec_contains(v, 7) == true + + test vec_contains_not_found + given v = vec_new() + then vec_contains(v, 99) == false + + test vec_index_of_found + given v = vec_new() + given _ = vec_push(&v, 10) + given _ = vec_push(&v, 20) + then vec_index_of(v, 20) == 1 + + test vec_index_of_not_found + given v = vec_new() + then vec_index_of(v, 5) == -1 + + test str_new_empty + given s = str_new() + then str_is_empty(s) == true + + test str_len_basic + given s = str_from("hello", 5) + then str_len(s) == 5 + + test str_eq_same + given a = str_from("abc", 3) + given b = str_from("abc", 3) + then str_eq(a, b) == true + + test str_eq_diff + given a = str_from("abc", 3) + given b = str_from("xyz", 3) + then str_eq(a, b) == false + + test str_char_at + given s = str_from("hello", 5) + then str_char_at(s, 0) == 104 + + test str_contains_char + given s = str_from("hello", 5) + then str_contains(s, 101) == true + + test opt_some_is_some + given o = opt_some(42) + then opt_is_some(o) == true + + test opt_none_is_none + given o = opt_none() + then opt_is_none(o) == true + + test opt_unwrap_or_some + given o = opt_some(42) + then opt_unwrap_or(o, 0) == 42 + + test opt_unwrap_or_none + given o = opt_none() + then opt_unwrap_or(o, 99) == 99 + + test map_new_empty + given m = map_new() + then map_len(m) == 0 + + test opt_none_not_some + given o = opt_none() + then opt_is_some(o) == false + + test result_ok_is_ok + given r = result_ok(42) + then result_is_ok(r) == true + + test result_ok_is_not_err + given r = result_ok(42) + then result_is_err(r) == false + + test result_err_is_err + given r = result_err(1) + then result_is_err(r) == true + + test result_err_is_not_ok + given r = result_err(1) + then result_is_ok(r) == false + + test result_unwrap_ok + given r = result_ok(42) + then result_unwrap(r) == 42 + + test result_unwrap_or_ok + given r = result_ok(42) + then result_unwrap_or(r, 0) == 42 + + test result_unwrap_or_err + given r = result_err(1) + then result_unwrap_or(r, 99) == 99 + + test result_error_code_ok + given r = result_ok(42) + then result_error_code(r) == 0 + + test result_error_code_err + given r = result_err(7) + then result_error_code(r) == 7 + + test iter_new_has_next + given it = iter_new([1, 2, 3], 3) + then iter_has_next(it) == true + + test iter_new_at_zero + given it = iter_new([1, 2, 3], 3) + then iter_pos(it) == 0 + + test iter_remaining_full + given it = iter_new([1, 2, 3], 3) + then iter_remaining(it) == 3 + + test iter_peek_first + given it = iter_new([10, 20, 30], 3) + then iter_peek(it) == 10 + + test fmt_u32_zero + given s = fmt_u32(0) + then str_char_at(s, 0) == 48 + + test fmt_u32_42 + given s = fmt_u32(42) + then str_len(s) == 2 + + test fmt_i32_neg + given s = fmt_i32(-5) + then str_char_at(s, 0) == 45 + + test fmt_bool_true + given s = fmt_bool(true) + then str_len(s) == 4 + + test fmt_bool_false + given s = fmt_bool(false) + then str_len(s) == 5 + + test fmt_hex_zero + given s = fmt_hex(0) + then str_len(s) == 3 + + test fmt_hex_255 + given s = fmt_hex(255) + then str_len(s) == 4 + + invariant vec_new_len_zero + given v = vec_new() + assert vec_len(v) == 0 + + invariant str_new_len_zero + given s = str_new() + assert str_len(s) == 0 + + invariant map_new_count_zero + given m = map_new() + assert map_len(m) == 0 + + invariant opt_some_has_value + given o = opt_some(1) + assert opt_is_some(o) == true + + invariant result_ok_always_ok + given r = result_ok(0) + assert result_is_ok(r) == true + + invariant result_err_never_ok + given r = result_err(1) + assert result_is_ok(r) == false + + invariant iter_new_pos_zero + given it = iter_new([], 0) + assert iter_pos(it) == 0 + + invariant fmt_u32_zero_is_zero_char + given s = fmt_u32(0) + assert str_char_at(s, 0) == 48 + + invariant fmt_bool_true_starts_t + given s = fmt_bool(true) + assert str_char_at(s, 0) == 116 +} diff --git a/apps/website/public/t27/specs/compiler/typechecker.t27 b/apps/website/public/t27/specs/compiler/typechecker.t27 new file mode 100644 index 0000000000..044be01465 --- /dev/null +++ b/apps/website/public/t27/specs/compiler/typechecker.t27 @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: Apache-2.0 +module TypeChecking { + use compiler::parser; + use compiler::lexer; + + enum TypeInfo { + Void, + Bool, + I8, I16, I32, I64, + U8, U16, U32, U64, + F32, F64, + Str, + Array, + Pointer, + Optional, + Function, + Custom, + Unknown, + Error, + } + + struct TypeEntry { + name: str; + type_info: TypeInfo; + is_mutable: bool; + is_pub: bool; + line: u32; + col: u32; + } + + const MAX_SYMBOLS: u32 = 1024; + const MAX_SCOPES: u32 = 64; + + struct ScopeStack { + scope_starts: [MAX_SCOPES]u32; + scope_count: u32; + } + + struct SymbolTable { + entries: [MAX_SYMBOLS]TypeEntry; + count: u32; + scopes: ScopeStack; + } + + struct TypeCheckResult { + ok: bool; + error_count: u32; + first_error_line: u32; + first_error_col: u32; + first_error_msg: str; + } + + struct FnSignature { + name: str; + params: [16]TypeInfo; + param_count: u32; + return_type: TypeInfo; + } + + const MAX_FNS: u32 = 256; + + struct FnRegistry { + fns: [MAX_FNS]FnSignature; + count: u32; + } + + fn symbol_table_init() -> SymbolTable { + return SymbolTable{ + entries = [TypeEntry{ name = "", type_info = TypeInfo::Unknown, is_mutable = false, is_pub = false, line = 0, col = 0 }; MAX_SYMBOLS], + count = 0, + scopes = ScopeStack{ scope_starts = [0; MAX_SCOPES], scope_count = 0 }, + }; + } + + fn fn_registry_init() -> FnRegistry { + return FnRegistry{ + fns = [FnSignature{ name = "", params = [TypeInfo::Unknown; 16], param_count = 0, return_type = TypeInfo::Void }; MAX_FNS], + count = 0, + }; + } + + fn ok_result() -> TypeCheckResult { + return TypeCheckResult{ + ok = true, + error_count = 0, + first_error_line = 0, + first_error_col = 0, + first_error_msg = "", + }; + } + + fn error_result(line: u32, col: u32, msg: str) -> TypeCheckResult { + return TypeCheckResult{ + ok = false, + error_count = 1, + first_error_line = line, + first_error_col = col, + first_error_msg = msg, + }; + } + + fn push_scope(table: *SymbolTable) { + if (table.scopes.scope_count < MAX_SCOPES) { + table.scopes.scope_starts[table.scopes.scope_count] = table.count; + table.scopes.scope_count = table.scopes.scope_count + 1; + } + } + + fn pop_scope(table: *SymbolTable) { + if (table.scopes.scope_count > 0) { + table.scopes.scope_count = table.scopes.scope_count - 1; + table.count = table.scopes.scope_starts[table.scopes.scope_count]; + } + } + + fn lookup(table: SymbolTable, name: str) -> TypeInfo { + var i: u32 = table.count; + while (i > 0) { + i = i - 1; + if (table.entries[i].name == name) { + return table.entries[i].type_info; + } + } + return TypeInfo::Unknown; + } + + fn lookup_mutable(table: SymbolTable, name: str) -> bool { + var i: u32 = table.count; + while (i > 0) { + i = i - 1; + if (table.entries[i].name == name) { + return table.entries[i].is_mutable; + } + } + return false; + } + + fn insert(table: *SymbolTable, name: str, info: TypeInfo, mutable: bool, pub_val: bool, line: u32, col: u32) -> bool { + if (table.count >= MAX_SYMBOLS) { + return false; + } + table.entries[table.count] = TypeEntry{ + name = name, + type_info = info, + is_mutable = mutable, + is_pub = pub_val, + line = line, + col = col, + }; + table.count = table.count + 1; + return true; + } + + fn register_fn(reg: *FnRegistry, name: str, ret: TypeInfo) { + if (reg.count < MAX_FNS) { + reg.fns[reg.count] = FnSignature{ + name = name, + params = [TypeInfo::Unknown; 16], + param_count = 0, + return_type = ret, + }; + reg.count = reg.count + 1; + } + } + + fn set_fn_param(reg: *FnRegistry, fn_idx: u32, param_idx: u32, ptype: TypeInfo) { + if (fn_idx < reg.count and param_idx < 16) { + reg.fns[fn_idx].params[param_idx] = ptype; + if (param_idx >= reg.fns[fn_idx].param_count) { + reg.fns[fn_idx].param_count = param_idx + 1; + } + } + } + + fn lookup_fn(reg: FnRegistry, name: str) -> TypeInfo { + var i: u32 = reg.count; + while (i > 0) { + i = i - 1; + if (reg.fns[i].name == name) { + return reg.fns[i].return_type; + } + } + return TypeInfo::Unknown; + } + + fn lookup_fn_sig(reg: FnRegistry, name: str) -> FnSignature { + var i: u32 = reg.count; + while (i > 0) { + i = i - 1; + if (reg.fns[i].name == name) { + return reg.fns[i]; + } + } + return FnSignature{ name = "", params = [TypeInfo::Unknown; 16], param_count = 0, return_type = TypeInfo::Unknown }; + } + + fn resolve_type(annotation: str) -> TypeInfo { + if (annotation == "void") { return TypeInfo::Void; } + if (annotation == "bool") { return TypeInfo::Bool; } + if (annotation == "i8") { return TypeInfo::I8; } + if (annotation == "i16") { return TypeInfo::I16; } + if (annotation == "i32") { return TypeInfo::I32; } + if (annotation == "i64") { return TypeInfo::I64; } + if (annotation == "u8") { return TypeInfo::U8; } + if (annotation == "u16") { return TypeInfo::U16; } + if (annotation == "u32") { return TypeInfo::U32; } + if (annotation == "u64") { return TypeInfo::U64; } + if (annotation == "f32") { return TypeInfo::F32; } + if (annotation == "f64") { return TypeInfo::F64; } + if (annotation == "str") { return TypeInfo::Str; } + if (annotation == "") { return TypeInfo::Unknown; } + return TypeInfo::Custom; + } + + fn is_numeric(t: TypeInfo) -> bool { + return t == TypeInfo::I8 or t == TypeInfo::I16 or t == TypeInfo::I32 or t == TypeInfo::I64 + or t == TypeInfo::U8 or t == TypeInfo::U16 or t == TypeInfo::U32 or t == TypeInfo::U64 + or t == TypeInfo::F32 or t == TypeInfo::F64; + } + + fn is_integer(t: TypeInfo) -> bool { + return t == TypeInfo::I8 or t == TypeInfo::I16 or t == TypeInfo::I32 or t == TypeInfo::I64 + or t == TypeInfo::U8 or t == TypeInfo::U16 or t == TypeInfo::U32 or t == TypeInfo::U64; + } + + fn is_float(t: TypeInfo) -> bool { + return t == TypeInfo::F32 or t == TypeInfo::F64; + } + + fn is_signed(t: TypeInfo) -> bool { + return t == TypeInfo::I8 or t == TypeInfo::I16 or t == TypeInfo::I32 or t == TypeInfo::I64; + } + + fn type_name(t: TypeInfo) -> str { + if (t == TypeInfo::Void) { return "void"; } + if (t == TypeInfo::Bool) { return "bool"; } + if (t == TypeInfo::I32) { return "i32"; } + if (t == TypeInfo::I64) { return "i64"; } + if (t == TypeInfo::U8) { return "u8"; } + if (t == TypeInfo::U32) { return "u32"; } + if (t == TypeInfo::U64) { return "u64"; } + if (t == TypeInfo::F32) { return "f32"; } + if (t == TypeInfo::F64) { return "f64"; } + if (t == TypeInfo::Str) { return "str"; } + if (t == TypeInfo::Array) { return "array"; } + if (t == TypeInfo::Bool) { return "bool"; } + if (t == TypeInfo::Error) { return "error"; } + return "unknown"; + } + + fn numeric_promotion(a: TypeInfo, b: TypeInfo) -> TypeInfo { + if (a == TypeInfo::F64 or b == TypeInfo::F64) { return TypeInfo::F64; } + if (a == TypeInfo::F32 or b == TypeInfo::F32) { return TypeInfo::F64; } + if (a == TypeInfo::I64 or b == TypeInfo::I64) { return TypeInfo::I64; } + if (a == TypeInfo::U64 or b == TypeInfo::U64) { return TypeInfo::U64; } + return TypeInfo::I32; + } + + fn is_comparison_op(op: str) -> bool { + return op == "==" or op == "!=" or op == "<" or op == ">" or op == "<=" or op == ">="; + } + + fn is_logical_op(op: str) -> bool { + return op == "and" or op == "or"; + } + + fn is_arithmetic_op(op: str) -> bool { + return op == "+" or op == "-" or op == "*" or op == "/" or op == "%" + or op == "**" or op == "<<" or op == ">>"; + } + + fn check_assign(lhs: TypeInfo, rhs: TypeInfo) -> bool { + if (lhs == rhs) { return true; } + if (is_numeric(lhs) and is_numeric(rhs)) { return true; } + if (lhs == TypeInfo::Unknown or rhs == TypeInfo::Unknown) { return true; } + if (lhs == TypeInfo::Custom or rhs == TypeInfo::Custom) { return true; } + if (lhs == TypeInfo::Optional) { return true; } + return false; + } + + fn can_implicitly_cast(from: TypeInfo, to: TypeInfo) -> bool { + if (from == to) { return true; } + if (is_integer(from) and is_integer(to)) { return true; } + if (is_integer(from) and is_float(to)) { return true; } + if (is_float(from) and is_float(to)) { return true; } + return false; + } + + fn infer_literal(value: str) -> TypeInfo { + if (value == "true" or value == "false") { return TypeInfo::Bool; } + if (value == "null") { return TypeInfo::Optional; } + if (value == "") { return TypeInfo::Void; } + if (value == "0") { return TypeInfo::I32; } + return TypeInfo::I32; + } + + fn infer_binary_result(op: str, left: TypeInfo, right: TypeInfo) -> TypeInfo { + if (is_comparison_op(op)) { return TypeInfo::Bool; } + if (is_logical_op(op)) { return TypeInfo::Bool; } + if (op == "++") { return TypeInfo::Str; } + if (is_arithmetic_op(op)) { + if (is_numeric(left) and is_numeric(right)) { + return numeric_promotion(left, right); + } + return TypeInfo::Unknown; + } + return TypeInfo::Unknown; + } + + fn infer_expr(expr: parser::Node, table: SymbolTable, reg: FnRegistry) -> TypeInfo { + if (expr.kind == parser::NodeKind::ExprLiteral) { + return infer_literal(expr.value); + } + if (expr.kind == parser::NodeKind::ExprIdentifier) { + return lookup(table, expr.name); + } + if (expr.kind == parser::NodeKind::ExprBinary) { + if (expr.children_count >= 2) { + var left = infer_expr(expr.children[0], table, reg); + var right = infer_expr(expr.children[1], table, reg); + return infer_binary_result(expr.extra_op, left, right); + } + return TypeInfo::Unknown; + } + if (expr.kind == parser::NodeKind::ExprUnary) { + if (expr.children_count >= 1) { + var inner = infer_expr(expr.children[0], table, reg); + if (expr.extra_op == "!") { return TypeInfo::Bool; } + if (expr.extra_op == "-") { + if (is_numeric(inner)) { return inner; } + } + if (is_numeric(inner)) { return inner; } + if (inner == TypeInfo::Bool) { return TypeInfo::Bool; } + } + return TypeInfo::Unknown; + } + if (expr.kind == parser::NodeKind::ExprCall) { + return lookup_fn(reg, expr.name); + } + if (expr.kind == parser::NodeKind::ExprArrayLiteral) { + return TypeInfo::Array; + } + if (expr.kind == parser::NodeKind::ExprStructLit) { + return TypeInfo::Custom; + } + if (expr.kind == parser::NodeKind::ExprEnumValue) { + return TypeInfo::Custom; + } + if (expr.kind == parser::NodeKind::ExprIf) { + if (expr.children_count >= 2) { + return infer_expr(expr.children[1], table, reg); + } + return TypeInfo::Unknown; + } + if (expr.kind == parser::NodeKind::ExprFieldAccess) { + return TypeInfo::Unknown; + } + if (expr.kind == parser::NodeKind::ExprIndex) { + return TypeInfo::Unknown; + } + if (expr.kind == parser::NodeKind::ExprSwitch) { + if (expr.children_count >= 2) { + return infer_expr(expr.children[1], table, reg); + } + return TypeInfo::Unknown; + } + return TypeInfo::Unknown; + } + + fn check_stmt(stmt: parser::Node, table: *SymbolTable, reg: FnRegistry, expected_ret: TypeInfo) -> TypeCheckResult { + if (stmt.kind == parser::NodeKind::ExprReturn) { + if (stmt.children_count >= 1) { + var ret_type = infer_expr(stmt.children[0], *table, reg); + if (expected_ret != TypeInfo::Unknown and expected_ret != TypeInfo::Void) { + if (check_assign(expected_ret, ret_type) == false) { + return error_result(0, 0, "return type mismatch"); + } + } + } + return ok_result(); + } + if (stmt.kind == parser::NodeKind::StmtLocal) { + var var_type = resolve_type(stmt.extra_type); + if (stmt.children_count >= 1) { + var init_type = infer_expr(stmt.children[0], *table, reg); + if (var_type == TypeInfo::Unknown) { + var_type = init_type; + } else if (check_assign(var_type, init_type) == false) { + return error_result(0, 0, "type mismatch in local declaration"); + } + } + insert(table, stmt.name, var_type, stmt.extra_mutable, false, 0, 0); + return ok_result(); + } + if (stmt.kind == parser::NodeKind::StmtAssign) { + if (stmt.children_count >= 2) { + var target_type = infer_expr(stmt.children[0], *table, reg); + var val_type = infer_expr(stmt.children[1], *table, reg); + if (check_assign(target_type, val_type) == false) { + return error_result(0, 0, "assignment type mismatch"); + } + } + return ok_result(); + } + if (stmt.kind == parser::NodeKind::StmtIf) { + push_scope(table); + if (stmt.children_count >= 2) { + var i: u32 = 0; + while (i < stmt.children[1].children_count) { + var r = check_stmt(stmt.children[1].children[i], table, reg, expected_ret); + if (r.ok == false) { pop_scope(table); return r; } + i = i + 1; + } + } + if (stmt.children_count >= 3) { + var j: u32 = 0; + while (j < stmt.children[2].children_count) { + var r = check_stmt(stmt.children[2].children[j], table, reg, expected_ret); + if (r.ok == false) { pop_scope(table); return r; } + j = j + 1; + } + } + pop_scope(table); + return ok_result(); + } + if (stmt.kind == parser::NodeKind::StmtWhile) { + push_scope(table); + if (stmt.children_count >= 2) { + var i: u32 = 0; + while (i < stmt.children[1].children_count) { + var r = check_stmt(stmt.children[1].children[i], table, reg, expected_ret); + if (r.ok == false) { pop_scope(table); return r; } + i = i + 1; + } + } + pop_scope(table); + return ok_result(); + } + if (stmt.kind == parser::NodeKind::StmtFor) { + push_scope(table); + if (stmt.children_count >= 3) { + var i: u32 = 0; + while (i < stmt.children[2].children_count) { + var r = check_stmt(stmt.children[2].children[i], table, reg, expected_ret); + if (r.ok == false) { pop_scope(table); return r; } + i = i + 1; + } + } + pop_scope(table); + return ok_result(); + } + if (stmt.kind == parser::NodeKind::StmtExpr) { + return ok_result(); + } + if (stmt.kind == parser::NodeKind::StmtBreak) { + return ok_result(); + } + if (stmt.kind == parser::NodeKind::StmtContinue) { + return ok_result(); + } + return ok_result(); + } + + fn typecheck_fn_body(fn_node: parser::Node, table: *SymbolTable, reg: FnRegistry) -> TypeCheckResult { + var ret_type = resolve_type(fn_node.extra_return_type); + var i: u32 = 0; + while (i < fn_node.children_count) { + var r = check_stmt(fn_node.children[i], table, reg, ret_type); + if (r.ok == false) { return r; } + i = i + 1; + } + return ok_result(); + } + + fn typecheck_module(module: parser::Node, table: *SymbolTable, reg: *FnRegistry) -> TypeCheckResult { + var i: u32 = 0; + while (i < module.children_count) { + var child = module.children[i]; + if (child.kind == parser::NodeKind::ConstDecl) { + var const_type = resolve_type(child.extra_type); + if (const_type == TypeInfo::Unknown and child.children_count > 0) { + const_type = infer_expr(child.children[0], *table, *reg); + } + insert(table, child.name, const_type, false, child.extra_pub, 0, 0); + } else if (child.kind == parser::NodeKind::StructDecl) { + insert(table, child.name, TypeInfo::Custom, false, child.extra_pub, 0, 0); + } else if (child.kind == parser::NodeKind::EnumDecl) { + insert(table, child.name, TypeInfo::Custom, false, child.extra_pub, 0, 0); + } else if (child.kind == parser::NodeKind::FnDecl) { + var ret_type = resolve_type(child.extra_return_type); + insert(table, child.name, TypeInfo::Function, false, child.extra_pub, 0, 0); + register_fn(reg, child.name, ret_type); + } + i = i + 1; + } + var j: u32 = 0; + while (j < module.children_count) { + var child = module.children[j]; + if (child.kind == parser::NodeKind::FnDecl) { + push_scope(table); + var p: u32 = 0; + while (p < child.params_count) { + var param_type = resolve_type(child.params[p].type_str); + insert(table, child.params[p].name, param_type, false, false, 0, 0); + p = p + 1; + } + var r = typecheck_fn_body(child, table, *reg); + pop_scope(table); + if (r.ok == false) { return r; } + } + j = j + 1; + } + return ok_result(); + } + + fn typecheck(ast: parser::Node) -> TypeCheckResult { + var table = symbol_table_init(); + var reg = fn_registry_init(); + if (ast.kind == parser::NodeKind::Module) { + return typecheck_module(ast, &table, ®); + } + var i: u32 = 0; + while (i < ast.children_count) { + var child = ast.children[i]; + if (child.kind == parser::NodeKind::Module) { + var result = typecheck_module(child, &table, ®); + if (result.ok == false) { + return result; + } + } + i = i + 1; + } + return ok_result(); + } + + test resolve_type_i32 + given result = resolve_type("i32") + then result == TypeInfo::I32 + + test resolve_type_bool + given result = resolve_type("bool") + then result == TypeInfo::Bool + + test resolve_type_f64 + given result = resolve_type("f64") + then result == TypeInfo::F64 + + test resolve_type_unknown + given result = resolve_type("") + then result == TypeInfo::Unknown + + test resolve_type_custom + given result = resolve_type("MyStruct") + then result == TypeInfo::Custom + + test is_numeric_i32 + given result = is_numeric(TypeInfo::I32) + then result == true + + test is_numeric_bool + given result = is_numeric(TypeInfo::Bool) + then result == false + + test is_integer_u32 + given result = is_integer(TypeInfo::U32) + then result == true + + test is_integer_f64 + given result = is_integer(TypeInfo::F64) + then result == false + + test is_float_f32 + given result = is_float(TypeInfo::F32) + then result == true + + test is_float_i32 + given result = is_float(TypeInfo::I32) + then result == false + + test is_signed_i32 + given result = is_signed(TypeInfo::I32) + then result == true + + test is_signed_u32 + given result = is_signed(TypeInfo::U32) + then result == false + + test check_assign_same_type + given result = check_assign(TypeInfo::I32, TypeInfo::I32) + then result == true + + test check_assign_numeric_compat + given result = check_assign(TypeInfo::I32, TypeInfo::F64) + then result == true + + test check_assign_incompatible + given result = check_assign(TypeInfo::Bool, TypeInfo::Str) + then result == false + + test check_assign_optional + given result = check_assign(TypeInfo::Optional, TypeInfo::I32) + then result == true + + test numeric_promotion_f64 + given result = numeric_promotion(TypeInfo::I32, TypeInfo::F64) + then result == TypeInfo::F64 + + test numeric_promotion_i32 + given result = numeric_promotion(TypeInfo::I32, TypeInfo::I32) + then result == TypeInfo::I32 + + test infer_literal_int + given result = infer_literal("42") + then result == TypeInfo::I32 + + test infer_literal_bool + given result = infer_literal("true") + then result == TypeInfo::Bool + + test infer_literal_false + given result = infer_literal("false") + then result == TypeInfo::Bool + + test infer_literal_null + given result = infer_literal("null") + then result == TypeInfo::Optional + + test is_comparison_eq + given result = is_comparison_op("==") + then result == true + + test is_comparison_plus + given result = is_comparison_op("+") + then result == false + + test is_logical_and + given result = is_logical_op("and") + then result == true + + test is_arithmetic_plus + given result = is_arithmetic_op("+") + then result == true + + test is_arithmetic_eq + given result = is_arithmetic_op("==") + then result == false + + test can_cast_i32_to_f64 + given result = can_implicitly_cast(TypeInfo::I32, TypeInfo::F64) + then result == true + + test can_cast_bool_to_i32 + given result = can_implicitly_cast(TypeInfo::Bool, TypeInfo::I32) + then result == false + + test infer_binary_add_i32 + given result = infer_binary_result("+", TypeInfo::I32, TypeInfo::I32) + then result == TypeInfo::I32 + + test infer_binary_compare + given result = infer_binary_result("==", TypeInfo::I32, TypeInfo::I32) + then result == TypeInfo::Bool + + test infer_binary_logical + given result = infer_binary_result("and", TypeInfo::Bool, TypeInfo::Bool) + then result == TypeInfo::Bool + + test infer_binary_concat + given result = infer_binary_result("++", TypeInfo::Str, TypeInfo::Str) + then result == TypeInfo::Str + + test infer_binary_mixed + given result = infer_binary_result("+", TypeInfo::I32, TypeInfo::F64) + then result == TypeInfo::F64 + + invariant void_not_numeric + assert is_numeric(TypeInfo::Void) == false + + invariant bool_not_numeric + assert is_numeric(TypeInfo::Bool) == false + + invariant promotion_symmetric_f64 + assert numeric_promotion(TypeInfo::F64, TypeInfo::I32) == TypeInfo::F64 + + invariant self_assign_ok + assert check_assign(TypeInfo::I32, TypeInfo::I32) == true + + invariant comparison_returns_bool + assert infer_binary_result("==", TypeInfo::I32, TypeInfo::I32) == TypeInfo::Bool + + invariant logical_returns_bool + assert infer_binary_result("or", TypeInfo::Bool, TypeInfo::Bool) == TypeInfo::Bool +} diff --git a/apps/website/public/t27/specs/config/load.t27 b/apps/website/public/t27/specs/config/load.t27 new file mode 100644 index 0000000000..48c9620cdc --- /dev/null +++ b/apps/website/public/t27/specs/config/load.t27 @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: Apache-2.0 +// config/load.t27 — Config Load/Save Specification +// Configuration file I/O, merging, validation +// φ² + 1/φ² = 3 | TRINITY + +module config-load; + +// ============================================================================ +// Imports +// ============================================================================ + +use std; +use config-schema::{Config, ValidationResult, PathsConfig, ConfigError, + AgentConfig, ProviderConfig, config_default, error_create, + validation_success, validation_failure, has_api_key}; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default config directory name +pub const CONFIG_DIR_NAME : [6]u8 = ".t27"; + +/// Config file extension +pub const CONFIG_FILE_EXT : [5]u8 = ".json"; + +/// Environment file name +pub const ENV_FILE_NAME : [5]u8 = ".env"; + +/// Maximum config file size in bytes +pub const MAX_CONFIG_SIZE : u32 = 1048576; // 1MB + +/// Config format version +pub const CONFIG_FORMAT_VERSION : u16 = 1; + +/// JSON format indicator +pub const FORMAT_JSON : []u8 = "json"; + +/// YAML format indicator +pub const FORMAT_YAML : []u8 = "yaml"; + +// ============================================================================ +// Types +// ============================================================================ + +/// Config source type +pub const ConfigSource = enum(u8) { + file = 0, + env = 1, + cli = 2, + defaults = 3, +}; + +/// Config file format +pub const ConfigFormat = enum(u8) { + json = 0, + yaml = 1, +}; + +/// Load result +pub const LoadResult = struct { + config : Config, + source : ConfigSource, + format : ConfigFormat, + errors : []LoadError, +}; + +/// Load error +pub const LoadError = struct { + path : []u8, + message : []u8, + recoverable : bool, +}; + +/// Save result +pub const SaveResult = struct { + success : bool, + path : []u8, + error : ?[]u8, +}; + +/// Merge strategy +pub const MergeStrategy = enum(u8) { + replace = 0, // Override existing values + merge = 1, // Merge arrays and objects + keep_existing = 2, // Keep existing values on conflict +}; + +/// Merge result +pub const MergeResult = struct { + merged : Config, + conflicts : []MergeConflict, + strategy : MergeStrategy, +}; + +/// Merge conflict +pub const MergeConflict = struct { + path : []u8, + field : []u8, + local_value : []u8, + remote_value : []u8, +}; + +/// Validation context +pub const ValidationContext = struct { + check_api_keys : bool, + check_paths : bool, + check_agents : bool, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Load config from default paths +pub fn load_default() LoadResult { + const paths = get_default_paths(); + return load_from_file(paths.config_file); +} + +/// Load config from file path +pub fn load_from_file(path: []u8) LoadResult { + // Read and parse config file (simplified) + return LoadResult{ + .config = config_default(), + .source = .file, + .format = .json, + .errors = &[_]LoadError{}, + }; +} + +/// Load config from environment variables +pub fn load_from_env() LoadResult { + // Read environment variables (simplified) + return LoadResult{ + .config = config_default(), + .source = .env, + .format = .json, + .errors = &[_]LoadError{}, + }; +} + +/// Save config to default path +pub fn save_default(config: Config) SaveResult { + const paths = get_default_paths(); + return save_to_file(paths.config_file, config); +} + +/// Save config to file path +pub fn save_to_file(path: []u8, config: Config) SaveResult { + // Serialize and write config (simplified) + return SaveResult{ + .success = true, + .path = path, + .error = null, + }; +} + +/// Merge two configs with strategy +pub fn merge(base: Config, overlay: Config, strategy: MergeStrategy) MergeResult { + const merged = apply_merge_strategy(&base, &overlay, strategy); + const conflicts = find_merge_conflicts(&base, &overlay); + + return MergeResult{ + .merged = merged, + .conflicts = conflicts, + .strategy = strategy, + }; +} + +/// Merge multiple configs +pub fn merge_multiple(base: Config, others: []Config, strategy: MergeStrategy) MergeResult { + var result = base; + + for (others) |config| { + const merge_result = merge(result, config, strategy); + result = merge_result.merged; + } + + return MergeResult{ + .merged = result, + .conflicts = &[_]MergeConflict{}, + .strategy = strategy, + }; +} + +/// Validate config with context +pub fn validate(config: Config, context: ValidationContext) ValidationResult { + var errors : []ConfigError = &[_]ConfigError{}; + + if (context.check_api_keys) { + const api_error = validate_api_key(&config.provider); + if (api_error != null) { + errors = append_errors(errors, api_error.?); + } + } + + if (context.check_paths) { + const path_errors = validate_paths(&config.paths); + for (path_errors) |err| { + errors = append_errors(errors, err); + } + } + + if (context.check_agents) { + const agent_errors = validate_agents(&config.agents); + for (agent_errors) |err| { + errors = append_errors(errors, err); + } + } + + return if (errors.len > 0) + validation_failure(errors) + else + validation_success(); +} + +/// Validate provider API key +pub fn validate_api_key(provider: *ProviderConfig) ?ConfigError { + if (!has_api_key(provider.*)) { + return error_create("provider.api_key", "API key is required"); + } + return null; +} + +/// Validate paths configuration +pub fn validate_paths(paths: *PathsConfig) []ConfigError { + var errors : []ConfigError = &[_]ConfigError{}; + + if (paths.config_dir.len == 0) { + const err = error_create("paths.config_dir", "Config directory is required"); + errors = append_errors(errors, err); + } + + return errors; +} + +/// Validate agents configuration +pub fn validate_agents(agents: *[]AgentConfig) []ConfigError { + var errors : []ConfigError = &[_]ConfigError{}; + + for (agents) |agent| { + if (agent.name.len == 0) { + const err = error_create("agent.name", "Agent name is required"); + errors = append_errors(errors, err); + } + } + + return errors; +} + +/// Create default validation context +pub fn validation_context_default() ValidationContext { + return ValidationContext{ + .check_api_keys = true, + .check_paths = true, + .check_agents = true, + }; +} + +/// Create validation context for specific checks +pub fn validation_context_select(api_keys: bool, paths: bool, agents: bool) ValidationContext { + return ValidationContext{ + .check_api_keys = api_keys, + .check_paths = paths, + .check_agents = agents, + }; +} + +/// Create load error +pub fn load_error_create(path: []u8, message: []u8, recoverable: bool) LoadError { + return LoadError{ + .path = path, + .message = message, + .recoverable = recoverable, + }; +} + +/// Create merge conflict +pub fn merge_conflict_create(path: []u8, field: []u8, local: []u8, remote: []u8) MergeConflict { + return MergeConflict{ + .path = path, + .field = field, + .local_value = local, + .remote_value = remote, + }; +} + +/// Apply merge strategy +pub fn apply_merge_strategy(base: *Config, overlay: *Config, strategy: MergeStrategy) Config { + return switch (strategy) { + .replace => overlay.*, + .keep_existing => base.*, + .merge => merge_configs(base, overlay), + }; +} + +/// Merge two configs (deep merge) +pub fn merge_configs(base: *Config, overlay: *Config) Config { + // Deep merge implementation (simplified) + return overlay.*; +} + +/// Find merge conflicts +pub fn find_merge_conflicts(base: *Config, overlay: *Config) []MergeConflict { + // Find conflicts between base and overlay (simplified) + return &[_]MergeConflict{}; +} + +/// Get default config paths +pub fn get_default_paths() PathsConfig { + return PathsConfig{ + .config_dir = CONFIG_DIR_NAME, + .data_dir = "data", + .cache_dir = "cache", + .log_dir = "logs", + }; +} + +/// Append error to slice +pub fn append_errors(slice: []ConfigError, item: ConfigError) []ConfigError { + var result : []ConfigError = slice; + var new_slice : []ConfigError = &[_]ConfigError{item}; + for (result) |_| { + new_slice = append_errors_slice(new_slice, _); + } + return new_slice; +} + +/// Append errors slice +pub fn append_errors_slice(slice: []ConfigError, item: ConfigError) []ConfigError { + var result : []ConfigError = slice; + result = concat_errors(result, item); + return result; +} + +/// Concatenate errors +pub fn concat_errors(a: []ConfigError, b: ConfigError) []ConfigError { + var result : []ConfigError = a; + var new_slice : []ConfigError = &[_]ConfigError{b}; + for (result) |_| { + new_slice = append_errors_slice(new_slice, _); + } + return new_slice; +} + +/// Concat errors +pub fn concat_errors_string(a: []u8, b: []u8) []u8 { + var result : []u8 = a; + for (b) |byte| { + result = append_byte_string(result, byte); + } + return result; +} + +/// Append byte to string +pub fn append_byte_string(slice: []u8, byte: u8) []u8 { + var result : []u8 = slice; + result = concat_errors_string(result, &[_]u8{byte}); + return result; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "config_load_default" { + const result = load_default(); + try std.testing.expect(result.config.version == 1); +} + +test "config_load_from_file" { + const result = load_from_file("/fake/path"); + try std.testing.expect(result.source == .file); +} + +test "config_load_from_env" { + const result = load_from_env(); + try std.testing.expect(result.source == .env); +} + +test "config_merge_replace" { + const base = config_default(); + const overlay = config_default(); + const result = merge(base, overlay, .replace); + try std.testing.expect(result.strategy == .replace); +} + +test "config_merge_keep_existing" { + const base = config_default(); + const overlay = config_default(); + const result = merge(base, overlay, .keep_existing); + try std.testing.expect(result.strategy == .keep_existing); +} + +test "config_merge_merge" { + const base = config_default(); + const overlay = config_default(); + const result = merge(base, overlay, .merge); + try std.testing.expect(result.strategy == .merge); +} + +test "config_validate_default_context" { + const context = validation_context_default(); + try std.testing.expect(context.check_api_keys); +} + +test "config_validate_select_context" { + const context = validation_context_select(true, false, true); + try std.testing.expect(context.check_api_keys); + try std.testing.expect(!context.check_paths); + try std.testing.expect(context.check_agents); +} + +test "config_validate_success" { + const config = config_default(); + const context = validation_context_select(false, false, false); + const result = validate(config, context); + try std.testing.expect(result.valid); +} + +test "config_validate_failure_missing_api_key" { + var config = config_default(); + config.provider.api_key = ""; + const context = validation_context_select(true, false, false); + const result = validate(config, context); + try std.testing.expect(!result.valid); +} + +test "config_save_default" { + const config = config_default(); + const result = save_default(config); + try std.testing.expect(result.success); +} + +test "config_get_default_paths" { + const paths = get_default_paths(); + try std.testing.expect(paths.data_dir.len > 0); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant config_dir_name_valid { + // CONFIG_DIR_NAME is not empty + @compileAssert(CONFIG_DIR_NAME.len > 0); +} + +invariant config_file_ext_valid { + // CONFIG_FILE_EXT is not empty + @compileAssert(CONFIG_FILE_EXT.len > 0); +} + +invariant max_config_size_positive { + // MAX_CONFIG_SIZE is positive + @compileAssert(MAX_CONFIG_SIZE > 0); +} + +invariant config_format_version_positive { + // CONFIG_FORMAT_VERSION is positive + @compileAssert(CONFIG_FORMAT_VERSION > 0); +} + +invariant format_json_is_string { + // FORMAT_JSON is valid string + @compileAssert(FORMAT_JSON.len > 0); +} + +invariant format_yaml_is_string { + // FORMAT_YAML is valid string + @compileAssert(FORMAT_YAML.len > 0); +} + +invariant source_type_enum_valid { + // ConfigSource enum has valid values + @compileAssert(@intFromEnum(ConfigSource.defaults) == 3); +} + +invariant config_format_enum_valid { + // ConfigFormat enum has valid values + @compileAssert(@intFromEnum(ConfigFormat.yaml) == 1); +} + +invariant merge_strategy_enum_valid { + // MergeStrategy enum has valid values + @compileAssert(@intFromEnum(MergeStrategy.keep_existing) == 2); +} + +invariant load_result_has_config { + // LoadResult has config field + @compileAssert(true); +} + +invariant load_result_has_source { + // LoadResult has source field + @compileAssert(true); +} + +invariant load_result_has_format { + // LoadResult has format field + @compileAssert(true); +} + +invariant save_result_has_success { + // SaveResult has success field + @compileAssert(true); +} + +invariant save_result_has_path { + // SaveResult has path field + @compileAssert(true); +} + +invariant merge_result_has_merged { + // MergeResult has merged config + @compileAssert(true); +} + +invariant merge_result_has_strategy { + // MergeResult has strategy field + @compileAssert(true); +} + +invariant validation_context_booleans { + // ValidationContext has boolean fields + @compileAssert(true); +} + +invariant load_error_recoverable_for_parse_errors { + // Parse errors are not recoverable + @compileAssert(true); +} + +invariant merge_strategy_replaces_all { + // Replace strategy replaces all values + @compileAssert(true); +} + +invariant merge_strategy_keep_preserves { + // Keep_existing strategy preserves local values + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "config_load_latency" { + // Measure: cycles for config load operation + // Target: < 500 cycles (file I/O) + @setEvalBranchQuota(10000); + var result : LoadResult = undefined; + for (0..1000) |_| { + result = load_default(); + } + _ = result.source; +} + +bench "config_save_latency" { + // Measure: cycles for config save operation + // Target: < 500 cycles (file I/O) + @setEvalBranchQuota(10000); + const config = config_default(); + var result : SaveResult = undefined; + for (0..1000) |_| { + result = save_default(config); + } + _ = result.success; +} + +bench "config_merge_latency" { + // Measure: cycles for config merge operation + // Target: < 200 cycles + @setEvalBranchQuota(10000); + const base = config_default(); + const overlay = config_default(); + var result : MergeResult = undefined; + for (0..1000) |_| { + result = merge(base, overlay, .replace); + } + _ = result.strategy; +} + +bench "config_validate_latency" { + // Measure: cycles for config validation + // Target: < 150 cycles + @setEvalBranchQuota(10000); + const config = config_default(); + const context = validation_context_default(); + var result : ValidationResult = undefined; + for (0..1000) |_| { + result = validate(config, context); + } + _ = result.valid; +} + +bench "config_get_paths_latency" { + // Measure: cycles for getting default paths + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : PathsConfig = undefined; + for (0..1000) |_| { + result = get_default_paths(); + } + _ = result.config_dir.len; +} diff --git a/apps/website/public/t27/specs/config/migrate.t27 b/apps/website/public/t27/specs/config/migrate.t27 new file mode 100644 index 0000000000..45936e283f --- /dev/null +++ b/apps/website/public/t27/specs/config/migrate.t27 @@ -0,0 +1,662 @@ +// SPDX-License-Identifier: Apache-2.0 +// config/migrate.t27 — Config Migration Specification +// Version detection, upgrade, compatibility handling +// φ² + 1/φ² = 3 | TRINITY + +module config-migrate; + +// ============================================================================ +// Imports +// ============================================================================ + +use std; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Current migration schema version +pub const MIGRATION_VERSION : u16 = 1; + +/// Minimum supported config version +pub const MIN_SUPPORTED_VERSION : u16 = 1; + +/// Maximum migration steps +pub const MAX_MIGRATION_STEPS : u8 = 10; + +/// Migration timeout in seconds +pub const MIGRATION_TIMEOUT_SEC : u16 = 60; + +/// Config version key +pub const VERSION_KEY : [7]u8 = "version"; + +/// Backup file suffix +pub const BACKUP_SUFFIX : [4]u8 = ".bak"; + +/// Migration result type +pub const MigrationResultType = enum(u8) { + success = 0, + partial = 1, // Some migrations failed + failed = 2, // Migration could not complete + skipped = 3, // No migration needed +}; + +/// Migration action +pub const MigrationAction = enum(u8) { + add_field = 0, + remove_field = 1, + rename_field = 2, + change_type = 3, + set_default = 4, + migrate_value = 5, +}; + +/// Migration severity +pub const MigrationSeverity = enum(u8) { + info = 0, + warning = 1, + error = 2, + critical = 3, +}; + +// ============================================================================ +// Types +// ============================================================================ + +/// Migration step +pub const MigrationStep = struct { + from_version : u16, + to_version : u16, + action : MigrationAction, + field_name : []u8, + old_value : []u8, + new_value : []u8, + description : []u8, +}; + +/// Migration rule +pub const MigrationRule = struct { + applies_to_version : ?u16, + check_fn : fn(*u8) bool, + migrate_fn : fn(*u8) MigrationAction, + description : []u8, +}; + +/// Migration result +pub const MigrationResult = struct { + result_type : MigrationResultType, + steps : []MigrationStep, + errors : []MigrationError, + from_version : u16, + to_version : u16, + backup_path : ?[]u8, +}; + +/// Migration error +pub const MigrationError = struct { + step : u8, + field : []u8, + message : []u8, + severity : MigrationSeverity, +}; + +/// Config version info +pub const VersionInfo = struct { + version : u16, + schema_version : u16, + migration_version : u16, +}; + +/// Migration context +pub const MigrationContext = struct { + dry_run : bool, + backup_enabled : bool, + force_migration : bool, + verbose : bool, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Detect config version from structure +pub fn detect_version(config_data: *u8) u16 { + // Simplified: would parse and extract version + return MIN_SUPPORTED_VERSION; +} + +/// Check if version is supported +pub fn is_version_supported(version: u16) bool { + return version >= MIN_SUPPORTED_VERSION; +} + +/// Check if migration is needed +pub fn needs_migration(current_version: u16, target_version: u16) bool { + return current_version < target_version; +} + +/// Create migration step +pub fn step_create(from_v: u16, to_v: u16, action: MigrationAction, field: []u8, old_val: []u8, new_val: []u8) MigrationStep { + return MigrationStep{ + .from_version = from_v, + .to_version = to_v, + .action = action, + .field_name = field, + .old_value = old_val, + .new_value = new_val, + .description = "", + }; +} + +/// Create migration step with description +pub fn step_with_description(step: MigrationStep, desc: []u8) MigrationStep { + return MigrationStep{ + .from_version = step.from_version, + .to_version = step.to_version, + .action = step.action, + .field_name = step.field_name, + .old_value = step.old_value, + .new_value = step.new_value, + .description = desc, + }; +} + +/// Create migration result +pub fn result_success(from: u16, to: u16, steps: []MigrationStep) MigrationResult { + return MigrationResult{ + .result_type = .success, + .steps = steps, + .errors = &[_]MigrationError{}, + .from_version = from, + .to_version = to, + .backup_path = null, + }; +} + +/// Create migration result with errors +pub fn result_failure(from: u16, to: u16, errors: []MigrationError) MigrationResult { + return MigrationResult{ + .result_type = .failed, + .steps = &[_]MigrationStep{}, + .errors = errors, + .from_version = from, + .to_version = to, + .backup_path = null, + }; +} + +/// Create migration error +pub fn error_create(step_idx: u8, field: []u8, message: []u8, severity: MigrationSeverity) MigrationError { + return MigrationError{ + .step = step_idx, + .field = field, + .message = message, + .severity = severity, + }; +} + +/// Create migration error info +pub fn error_info(message: []u8) MigrationError { + return error_create(0, "general", message, .error); +} + +/// Create migration error warning +pub fn error_warning(message: []u8) MigrationError { + return error_create(0, "general", message, .warning); +} + +/// Run migration plan +pub fn migrate(config_data: *u8, rules: []MigrationRule, context: MigrationContext) MigrationResult { + const current_version = detect_version(config_data); + const target_version = MIGRATION_VERSION; + + if (!needs_migration(current_version, target_version)) { + return MigrationResult{ + .result_type = .skipped, + .steps = &[_]MigrationStep{}, + .errors = &[_]MigrationError{}, + .from_version = current_version, + .to_version = target_version, + .backup_path = null, + }; + } + + var steps : []MigrationStep = &[_]MigrationStep{}; + var errors : []MigrationError = &[_]MigrationError{}; + + for (rules) |rule| { + const step = apply_rule(rule, config_data); + steps = append_steps(steps, step); + } + + return if (errors.len == 0) + result_success(current_version, target_version, steps) + else + result_failure(current_version, target_version, errors); +} + +/// Apply single migration rule +pub fn apply_rule(rule: MigrationRule, config_data: *u8) MigrationStep { + // Check if rule applies and migrate (simplified) + return step_create( + MIN_SUPPORTED_VERSION, + MIGRATION_VERSION, + .set_default, + "version", + "", + rule.description, + ); +} + +/// Create backup path +pub fn backup_path_create(config_path: []u8) []u8 { + return concat(config_path, BACKUP_SUFFIX); +} + +/// Verify backup was created +pub fn backup_verify(backup_path: []u8) bool { + // Check if backup exists (simplified) + return false; +} + +/// Create default migration context +pub fn context_default() MigrationContext { + return MigrationContext{ + .dry_run = false, + .backup_enabled = true, + .force_migration = false, + .verbose = false, + }; +} + +/// Create migration context for dry run +pub fn context_dry_run() MigrationContext { + const base = context_default(); + return MigrationContext{ + .dry_run = true, + .backup_enabled = false, + .force_migration = false, + .verbose = false, + }; +} + +/// Create migration context with force +pub fn context_force() MigrationContext { + const base = context_default(); + return MigrationContext{ + .dry_run = false, + .backup_enabled = true, + .force_migration = true, + .verbose = false, + }; +} + +/// Create migration context verbose +pub fn context_verbose() MigrationContext { + const base = context_default(); + return MigrationContext{ + .dry_run = false, + .backup_enabled = true, + .force_migration = false, + .verbose = true, + }; +} + +/// Append step to slice +pub fn append_steps(slice: []MigrationStep, item: MigrationStep) []MigrationStep { + var result : []MigrationStep = slice; + var new_slice : []MigrationStep = &[_]MigrationStep{item}; + for (result) |_| { + new_slice = append_steps_slice(new_slice, _); + } + return new_slice; +} + +/// Append steps slice +pub fn append_steps_slice(slice: []MigrationStep, item: MigrationStep) []MigrationStep { + var result : []MigrationStep = slice; + result = concat_steps(result, item); + return result; +} + +/// Concatenate steps +pub fn concat_steps(a: []MigrationStep, b: MigrationStep) []MigrationStep { + var result : []MigrationStep = a; + var new_slice : []MigrationStep = &[_]MigrationStep{b}; + for (result) |_| { + new_slice = append_steps_slice(new_slice, _); + } + return new_slice; +} + +/// Concatenate steps with string +pub fn concat_steps_string(a: []u8, b: []u8) []u8 { + var result : []u8 = a; + for (b) |byte| { + result = append_byte_steps(result, byte); + } + return result; +} + +/// Append byte to steps +pub fn append_byte_steps(slice: []MigrationStep, byte: u8) []MigrationStep { + var result : []MigrationStep = slice; + result = concat_steps_string(result, &[_]u8{byte}); + return result; +} + +/// Append errors to slice +pub fn append_errors(slice: []MigrationError, item: MigrationError) []MigrationError { + var result : []MigrationError = slice; + var new_slice : []MigrationError = &[_]MigrationError{item}; + for (result) |_| { + new_slice = append_errors_slice(new_slice, _); + } + return new_slice; +} + +/// Append errors slice +pub fn append_errors_slice(slice: []MigrationError, item: MigrationError) []MigrationError { + var result : []MigrationError = slice; + result = concat_errors(result, item); + return result; +} + +/// Concatenate errors +pub fn concat_errors(a: []MigrationError, b: MigrationError) []MigrationError { + var result : []MigrationError = a; + var new_slice : []MigrationError = &[_]MigrationError{b}; + for (result) |_| { + new_slice = append_errors_slice(new_slice, _); + } + return new_slice; +} + +/// Concatenate errors with string +pub fn concat_errors_string(a: []u8, b: []u8) []u8 { + var result : []u8 = a; + for (b) |byte| { + result = append_byte_errors(result, byte); + } + return result; +} + +/// Append byte to errors +pub fn append_byte_errors(slice: []MigrationError, byte: u8) []MigrationError { + var result : []MigrationError = slice; + result = concat_errors_string(result, &[_]u8{byte}); + return result; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "migrate_detect_version" { + const version = detect_version(@ptrFrom([]u8{"version":1})); + try std.testing.expect(version >= MIN_SUPPORTED_VERSION); +} + +test "migrate_is_version_supported" { + try std.testing.expect(is_version_supported(MIN_SUPPORTED_VERSION)); +} + +test "migrate_needs_migration_true" { + try std.testing.expect(needs_migration(1, 2)); +} + +test "migrate_needs_migration_false" { + try std.testing.expect(!needs_migration(2, 2)); +} + +test "migrate_step_create" { + const step = step_create(1, 2, .set_default, "key", "old", "new"); + try std.testing.expect(step.from_version == 1); +} + +test "migrate_step_with_description" { + const base = step_create(1, 2, .set_default, "key", "old", "new"); + const step = step_with_description(base, "Migrate key field"); + try std.testing.expect(step.description.len > 0); +} + +test "migrate_result_success" { + const steps = &[_]MigrationStep{}; + const result = result_success(1, 2, steps); + try std.testing.expect(result.result_type == .success); +} + +test "migrate_result_failure" { + const errors = &[_]MigrationError{error_info("test error")}; + const result = result_failure(1, 2, errors); + try std.testing.expect(result.result_type == .failed); +} + +test "migrate_error_create" { + const error = error_create(0, "field", "message", .error); + try std.testing.expect(error.step == 0); +} + +test "migrate_error_info" { + const error = error_info("test message"); + try std.testing.expect(error.severity == .error); +} + +test "migrate_error_warning" { + const error = error_warning("test warning"); + try std.testing.expect(error.severity == .warning); +} + +test "migrate_backup_path_create" { + const path = backup_path_create("/path/to/config.json"); + try std.testing.expect(std.mem.indexOf(path, BACKUP_SUFFIX) < path.len); +} + +test "migrate_context_default" { + const context = context_default(); + try std.testing.expect(context.backup_enabled); +} + +test "migrate_context_dry_run" { + const context = context_dry_run(); + try std.testing.expect(context.dry_run); +} + +test "migrate_context_force" { + const context = context_force(); + try std.testing.expect(context.force_migration); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant migration_version_positive { + // MIGRATION_VERSION is positive + @compileAssert(MIGRATION_VERSION > 0); +} + +invariant min_supported_version_positive { + // MIN_SUPPORTED_VERSION is positive + @compileAssert(MIN_SUPPORTED_VERSION > 0); +} + +invariant max_migration_steps_positive { + // MAX_MIGRATION_STEPS is positive + @compileAssert(MAX_MIGRATION_STEPS > 0); +} + +invariant migration_timeout_positive { + // MIGRATION_TIMEOUT_SEC is positive + @compileAssert(MIGRATION_TIMEOUT_SEC > 0); +} + +invariant version_key_not_empty { + // VERSION_KEY is not empty + @compileAssert(VERSION_KEY.len > 0); +} + +invariant backup_suffix_not_empty { + // BACKUP_SUFFIX is not empty + @compileAssert(BACKUP_SUFFIX.len > 0); +} + +invariant result_type_enum_valid { + // MigrationResultType enum has valid values + @compileAssert(@intFromEnum(MigrationResultType.skipped) == 3); +} + +invariant action_enum_valid { + // MigrationAction enum has valid values + @compileAssert(@intFromEnum(MigrationAction.migrate_value) == 5); +} + +invariant severity_enum_valid { + // MigrationSeverity enum has valid values + @compileAssert(@intFromEnum(MigrationSeverity.critical) == 3); +} + +invariant migration_step_has_from_version { + // MigrationStep has from_version field + @compileAssert(true); +} + +invariant migration_step_has_to_version { + // MigrationStep has to_version field + @compileAssert(true); +} + +invariant migration_step_has_action { + // MigrationStep has action field + @compileAssert(true); +} + +invariant migration_step_has_field_name { + // MigrationStep has field_name field + @compileAssert(true); +} + +invariant migration_step_has_values { + // MigrationStep has old_value and new_value fields + @compileAssert(true); +} + +invariant migration_error_has_step { + // MigrationError has step field + @compileAssert(true); +} + +invariant migration_error_has_field { + // MigrationError has field field + @compileAssert(true); +} + +invariant migration_error_has_message { + // MigrationError has message field + @compileAssert(true); +} + +invariant migration_error_has_severity { + // MigrationError has severity field + @compileAssert(true); +} + +invariant migration_result_has_result_type { + // MigrationResult has result_type field + @compileAssert(true); +} + +invariant migration_result_has_steps { + // MigrationResult has steps array + @compileAssert(true); +} + +invariant migration_result_has_errors { + // MigrationResult has errors array + @compileAssert(true); +} + +invariant migration_result_has_versions { + // MigrationResult has from_version and to_version fields + @compileAssert(true); +} + +invariant context_has_dry_run { + // MigrationContext has dry_run field + @compileAssert(true); +} + +invariant context_has_backup_enabled { + // MigrationContext has backup_enabled field + @compileAssert(true); +} + +invariant context_has_force_migration { + // MigrationContext has force_migration field + @compileAssert(true); +} + +invariant context_has_verbose { + // MigrationContext has verbose field + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "migrate_detect_version_latency" { + // Measure: cycles for version detection + // Target: < 50 cycles + @setEvalBranchQuota(10000); + const config = @ptrFrom([]u8{"version":1}); + var result : u16 = undefined; + for (0..1000) |_| { + result = detect_version(config); + } + _ = result; +} + +bench "migrate_needs_migration_latency" { + // Measure: cycles for migration check + // Target: < 20 cycles + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..1000) |_| { + result = needs_migration(1, 2); + } + _ = result; +} + +bench "migrate_result_create_latency" { + // Measure: cycles for result creation + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : MigrationResult = undefined; + for (0..1000) |_| { + result = result_success(1, 2, &[_]MigrationStep{}); + } + _ = result.result_type; +} + +bench "migrate_error_create_latency" { + // Measure: cycles for error creation + // Target: < 20 cycles + @setEvalBranchQuota(10000); + var result : MigrationError = undefined; + for (0..1000) |_| { + result = error_info("test"); + } + _ = result.severity; +} + +bench "migrate_backup_path_latency" { + // Measure: cycles for backup path creation + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = backup_path_create("/config.json"); + } + _ = result.len; +} diff --git a/apps/website/public/t27/specs/config/paths.t27 b/apps/website/public/t27/specs/config/paths.t27 new file mode 100644 index 0000000000..765d0ced25 --- /dev/null +++ b/apps/website/public/t27/specs/config/paths.t27 @@ -0,0 +1,640 @@ +// SPDX-License-Identifier: Apache-2.0 +// config/paths.t27 — Config Paths Specification +// Path resolution, directory creation, validation +// φ² + 1/φ² = 3 | TRINITY + +module config-paths; + +// ============================================================================ +// Imports +// ============================================================================ + +use std; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default config directory name +pub const DEFAULT_CONFIG_DIR : [6]u8 = ".t27"; + +/// Default data directory name +pub const DEFAULT_DATA_DIR : [4]u8 = "data"; + +/// Default cache directory name +pub const DEFAULT_CACHE_DIR : [5]u8 = "cache"; + +/// Default logs directory name +pub const DEFAULT_LOG_DIR : [4]u8 = "logs"; + +/// Maximum path length +pub const MAX_PATH_LENGTH : u16 = 4096; + +/// Path separator +pub const PATH_SEPARATOR : u8 = 47; // '/' + +/// Home directory environment variable +pub const ENV_HOME : [4]u8 = "HOME"; + +/// XDG config directory environment variable +pub const ENV_XDG_CONFIG : [12]u8 = "XDG_CONFIG_HOME"; + +/// XDG data directory environment variable +pub const ENV_XDG_DATA : [11]u8 = "XDG_DATA_HOME"; + +// ============================================================================ +// Types +// ============================================================================ + +/// Path type +pub const PathType = enum(u8) { + config = 0, + data = 1, + cache = 2, + log = 3, + temp = 4, +}; + +/// Path validation result +pub const PathValidation = enum(u8) { + valid = 0, + not_exists = 1, + not_writable = 2, + not_a_directory = 3, + invalid_character = 4, + too_long = 5, +}; + +/// Path resolution result +pub const PathResult = struct { + path : []u8, + type : PathType, + validation : PathValidation, + absolute : bool, +}; + +/// Resolved paths structure +pub const ResolvedPaths = struct { + config_dir : []u8, + config_file : []u8, + data_dir : []u8, + cache_dir : []u8, + log_dir : []u8, + temp_dir : []u8, +}; + +/// Directory check result +pub const DirCheckResult = struct { + exists : bool, + writable : bool, + is_directory : bool, + created : bool, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create default resolved paths +pub fn paths_default() ResolvedPaths { + return ResolvedPaths{ + .config_dir = DEFAULT_CONFIG_DIR, + .config_file = "", // Would be resolved + .data_dir = DEFAULT_DATA_DIR, + .cache_dir = DEFAULT_CACHE_DIR, + .log_dir = DEFAULT_LOG_DIR, + .temp_dir = "tmp", + }; +} + +/// Resolve config directory +pub fn resolve_config_dir(base: ?[]u8) []u8 { + const dir = if (base != null) base.? else DEFAULT_CONFIG_DIR; + return resolve_directory(dir); +} + +/// Resolve data directory +pub fn resolve_data_dir(base: ?[]u8) []u8 { + const dir = if (base != null) base.? else DEFAULT_DATA_DIR; + return resolve_directory(dir); +} + +/// Resolve cache directory +pub fn resolve_cache_dir(base: ?[]u8) []u8 { + const dir = if (base != null) base.? else DEFAULT_CACHE_DIR; + return resolve_directory(dir); +} + +/// Resolve logs directory +pub fn resolve_log_dir(base: ?[]u8) []u8 { + const dir = if (base != null) base.? else DEFAULT_LOG_DIR; + return resolve_directory(dir); +} + +/// Resolve temp directory +pub fn resolve_temp_dir(base: ?[]u8) []u8 { + return resolve_directory("tmp"); +} + +/// Resolve directory with validation +pub fn resolve_directory(name: []u8) PathResult { + const path = join(home_directory(), name); + + if (path.len > MAX_PATH_LENGTH) { + return PathResult{ + .path = path, + .type = .config, + .validation = .too_long, + .absolute = is_absolute(path), + }; + } + + if (has_invalid_characters(path)) { + return PathResult{ + .path = path, + .type = .config, + .validation = .invalid_character, + .absolute = is_absolute(path), + }; + } + + return PathResult{ + .path = path, + .type = .config, + .validation = .valid, + .absolute = is_absolute(path), + }; +} + +/// Check if path is absolute +pub fn is_absolute(path: []u8) bool { + return path.len > 0 and path[0] == PATH_SEPARATOR; +} + +/// Check if path has invalid characters +pub fn has_invalid_characters(path: []u8) bool { + // Check for null bytes and other invalid characters (simplified) + for (path) |byte| { + if (byte == 0) { + return true; + } + } + return false; +} + +/// Get home directory +pub fn home_directory() []u8 { + // Simplified: would read from environment + return "~"; +} + +/// Join path segments +pub fn join(base: []u8, name: []u8) []u8 { + var result : []u8 = base; + + if (result.len > 0 and result[result.len - 1] != PATH_SEPARATOR) { + result = concat(result, &[_]u8{PATH_SEPARATOR}); + } + + result = concat(result, name); + return result; +} + +/// Normalize path +pub fn normalize(path: []u8) []u8 { + // Remove redundant separators (simplified) + var result : []u8 = ""; + var prev_was_sep : bool = false; + + for (path) |byte| { + const is_sep = byte == PATH_SEPARATOR; + + if (is_sep) { + if (!prev_was_sep) { + result = concat(result, &[_]u8{PATH_SEPARATOR}); + } + } + + prev_was_sep = is_sep; + } + + return result; +} + +/// Get path parent directory +pub fn parent(path: []u8) []u8 { + const normalized = normalize(path); + + for (0..normalized.len) |i| { + if (i == 0) { + return normalized; + } + + if (normalized[i] == PATH_SEPARATOR and i < normalized.len - 1) { + return normalized[0..i]; + } + } + + return normalized; +} + +/// Get path basename +pub fn basename(path: []u8) []u8 { + const normalized = normalize(path); + var last_sep : usize = 0; + + for (0..normalized.len) |i| { + if (normalized[i] == PATH_SEPARATOR) { + last_sep = i; + } + } + + if (last_sep == 0) { + return normalized; + } + + return normalized[last_sep + 1..]; +} + +/// Check if directory exists +pub fn directory_exists(path: []u8) bool { + // Simplified: would check filesystem + return false; +} + +/// Check if directory is writable +pub fn directory_writable(path: []u8) bool { + // Simplified: would check filesystem + return false; +} + +/// Create directory if not exists +pub fn ensure_directory(path: []u8) DirCheckResult { + if (directory_exists(path)) { + return DirCheckResult{ + .exists = true, + .writable = directory_writable(path), + .is_directory = true, + .created = false, + }; + } + + // Would create directory + return DirCheckResult{ + .exists = false, + .writable = true, // Assumed after creation + .is_directory = true, + .created = true, + }; +} + +/// Resolve all paths +pub fn resolve_all(base: ?[]u8) ResolvedPaths { + const config_dir = resolve_config_dir(base); + const data_dir = resolve_data_dir(base); + const cache_dir = resolve_cache_dir(base); + const log_dir = resolve_log_dir(base); + const temp_dir = resolve_temp_dir(base); + const config_file = join(config_dir, "config.json"); + + return ResolvedPaths{ + .config_dir = config_dir, + .config_file = config_file, + .data_dir = data_dir, + .cache_dir = cache_dir, + .log_dir = log_dir, + .temp_dir = temp_dir, + }; +} + +/// Validate path +pub fn validate_path(path: []u8, expected_type: PathType) PathValidation { + if (path.len > MAX_PATH_LENGTH) { + return .too_long; + } + + if (has_invalid_characters(path)) { + return .invalid_character; + } + + if (!directory_exists(path)) { + return .not_exists; + } + + if (!directory_writable(path)) { + return .not_writable; + } + + return .valid; +} + +/// Get path extension +pub fn extension(path: []u8) []u8 { + const normalized = normalize(path); + var last_dot : usize = 0; + + for (0..normalized.len) |i| { + if (normalized[i] == 46) { // '.' + last_dot = i; + } + } + + if (last_dot == 0) { + return ""; + } + + return normalized[last_dot..]; +} + +/// Concatenate paths +pub fn concat(a: []u8, b: []u8) []u8 { + var result : []u8 = a; + + if (result.len > 0 and result[result.len - 1] != PATH_SEPARATOR) { + result = concat_path_separator(result); + } + + result = concat_path_separator(result); + result = concat(result, b); + + return result; +} + +/// Concatenate with path separator +pub fn concat_path_separator(path: []u8) []u8 { + return concat(path, &[_]u8{PATH_SEPARATOR}); +} + +/// Concatenate with byte +pub fn concat_with_byte(a: []u8, byte: u8) []u8 { + var result : []u8 = a; + result = concat(result, &[_]u8{byte}); + return result; +} + +/// Create path result +pub fn path_result_create(path: []u8, type: PathType, validation: PathValidation) PathResult { + return PathResult{ + .path = path, + .type = type, + .validation = validation, + .absolute = is_absolute(path), + }; +} + +/// Create valid path result +pub fn path_result_valid(path: []u8, type: PathType) PathResult { + return path_result_create(path, type, .valid); +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "paths_join_simple" { + const result = join("/base", "file"); + try std.testing.expect(std.mem.eql(result, "/base/file")); +} + +test "paths_join_with_separator" { + const result = join("/base/", "file"); + try std.testing.expect(std.mem.eql(result, "/base/file")); +} + +test "paths_is_absolute_true" { + try std.testing.expect(is_absolute("/path/to/file")); +} + +test "paths_is_absolute_false" { + try std.testing.expect(!is_absolute("relative/path")); +} + +test "paths_normalize_multiple_separators" { + const result = normalize("/path//to/file"); + try std.testing.expect(!std.mem.indexOf(result, &[_]u8{PATH_SEPARATOR, PATH_SEPARATOR}) < result.len); +} + +test "paths_parent_simple" { + const result = parent("/path/to/file"); + try std.testing.expect(std.mem.eql(result, "/path/to")); +} + +test "paths_parent_no_separator" { + const result = parent("file"); + try std.testing.expect(std.mem.eql(result, "")); +} + +test "paths_basename_simple" { + const result = basename("/path/to/file"); + try std.testing.expect(std.mem.eql(result, "file")); +} + +test "paths_basename_no_separator" { + const result = basename("file"); + try std.testing.expect(std.mem.eql(result, "file")); +} + +test "paths_extension_json" { + const result = extension("config.json"); + try std.testing.expect(std.mem.eql(result, "json")); +} + +test "paths_extension_none" { + const result = extension("config"); + try std.testing.expect(result.len == 0); +} + +test "paths_resolve_all" { + const result = resolve_all(null); + try std.testing.expect(result.config_dir.len > 0); +} + +test "paths_validate_path_valid" { + const path = "/valid/path"; + const result = validate_path(path, .data); + try std.testing.expect(result == .valid); +} + +test "paths_validate_path_too_long" { + const path = "x"; // 65536 chars + const result = validate_path(path, .data); + try std.testing.expect(result == .too_long); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant default_config_dir_not_empty { + // DEFAULT_CONFIG_DIR is not empty + @compileAssert(DEFAULT_CONFIG_DIR.len > 0); +} + +invariant default_data_dir_not_empty { + // DEFAULT_DATA_DIR is not empty + @compileAssert(DEFAULT_DATA_DIR.len > 0); +} + +invariant default_cache_dir_not_empty { + // DEFAULT_CACHE_DIR is not empty + @compileAssert(DEFAULT_CACHE_DIR.len > 0); +} + +invariant default_log_dir_not_empty { + // DEFAULT_LOG_DIR is not empty + @compileAssert(DEFAULT_LOG_DIR.len > 0); +} + +invariant max_path_length_positive { + // MAX_PATH_LENGTH is positive + @compileAssert(MAX_PATH_LENGTH > 0); +} + +invariant path_separator_is_slash { + // PATH_SEPARATOR is ASCII '/' + @compileAssert(PATH_SEPARATOR == 47); +} + +invariant path_type_enum_valid { + // PathType enum has valid values + @compileAssert(@intFromEnum(PathType.temp) == 4); +} + +invariant path_validation_enum_valid { + // PathValidation enum has valid values + @compileAssert(@intFromEnum(PathValidation.too_long) == 5); +} + +invariant path_result_has_type { + // PathResult has type field + @compileAssert(true); +} + +invariant path_result_has_validation { + // PathResult has validation field + @compileAssert(true); +} + +invariant path_result_has_absolute { + // PathResult has absolute boolean + @compileAssert(true); +} + +invariant resolved_paths_complete { + // ResolvedPaths has all directory fields + @compileAssert(true); +} + +invariant resolved_paths_has_config_file { + // ResolvedPaths has config_file field + @compileAssert(true); +} + +invariant dir_check_result_has_exists { + // DirCheckResult has exists boolean + @compileAssert(true); +} + +invariant dir_check_result_has_writable { + // DirCheckResult has writable boolean + @compileAssert(true); +} + +invariant dir_check_result_is_directory { + // DirCheckResult has is_directory boolean + @compileAssert(true); +} + +invariant dir_check_result_has_created { + // DirCheckResult has created boolean + @compileAssert(true); +} + +invariant normalize_removes_redundant_separators { + // normalize removes duplicate separators + @compileAssert(true); +} + +invariant join_preserves_base { + // join preserves base path + @compileAssert(true); +} + +invariant join_appends_name { + // join appends name to base + @compileAssert(true); +} + +invariant parent_returns_path { + // parent returns parent directory + @compileAssert(true); +} + +invariant basename_returns_filename { + // basename returns filename without directory + @compileAssert(true); +} + +invariant concat_preserves_first { + // concat preserves first argument + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "paths_join_latency" { + // Measure: cycles for path joining + // Target: < 80 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = join("/base/path", "file"); + } + _ = result.len; +} + +bench "paths_normalize_latency" { + // Measure: cycles for path normalization + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = normalize("/path//to/file"); + } + _ = result.len; +} + +bench "paths_parent_latency" { + // Measure: cycles for parent extraction + // Target: < 60 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = parent("/path/to/file"); + } + _ = result.len; +} + +bench "paths_basename_latency" { + // Measure: cycles for basename extraction + // Target: < 60 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = basename("/path/to/file"); + } + _ = result.len; +} + +bench "paths_resolve_all_latency" { + // Measure: cycles for resolving all paths + // Target: < 200 cycles + @setEvalBranchQuota(10000); + var result : ResolvedPaths = undefined; + for (0..1000) |_| { + result = resolve_all(null); + } + _ = result.config_dir.len; +} diff --git a/apps/website/public/t27/specs/config/schema.t27 b/apps/website/public/t27/specs/config/schema.t27 new file mode 100644 index 0000000000..f5757efb65 --- /dev/null +++ b/apps/website/public/t27/specs/config/schema.t27 @@ -0,0 +1,702 @@ +// SPDX-License-Identifier: Apache-2.0 +// config/schema.t27 — Config Schema Specification +// Configuration structures for providers, agents, LSP +// φ² + 1/φ² = 3 | TRINITY + +module config-schema; + +// ============================================================================ +// Imports +// ============================================================================ + +use std; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default configuration file name +pub const DEFAULT_CONFIG_FILE : [10]u8 = "config.json"; + +/// Default environment file name +pub const DEFAULT_ENV_FILE : [9]u8 = ".env"; + +/// Default LLM provider +pub const DEFAULT_PROVIDER : [8]u8 = "anthropic"; + +/// Default LLM model +pub const DEFAULT_MODEL : [18]u8 = "claude-sonnet-4-20250514"; + +/// Maximum API key length +pub const MAX_API_KEY_LENGTH : u16 = 256; + +/// Maximum agent name length +pub const MAX_AGENT_NAME_LENGTH : u16 = 128; + +/// Maximum timeout in seconds +pub const MAX_TIMEOUT_SEC : u16 = 300; + +/// Default request timeout in seconds +pub const DEFAULT_TIMEOUT_SEC : u16 = 30; + +/// Default max tokens per request +pub const DEFAULT_MAX_TOKENS : u32 = 4096; + +/// Default temperature for sampling +pub const DEFAULT_TEMPERATURE : f64 = 0.7; + +/// Provider type enum +pub const ProviderType = enum(u8) { + anthropic = 0, + openai = 1, + custom = 2, +}; + +/// Agent capability enum +pub const AgentCapability = enum(u8) { + chat = 0, + code_edit = 1, + file_operation = 2, + web_search = 3, + execute = 4, +}; + +// ============================================================================ +// Types +// ============================================================================ + +/// Provider configuration +pub const ProviderConfig = struct { + name : ProviderType, + api_key : []u8, + base_url : []u8, + model : []u8, + max_tokens : u32, + timeout_sec : u16, + temperature : f64, +}; + +/// Agent configuration +pub const AgentConfig = struct { + name : []u8, + description : []u8, + capabilities : []AgentCapability, + system_prompt : []u8, + model_override : ?[]u8, + enabled : bool, +}; + +/// MCP server configuration +pub const MCPServerConfig = struct { + name : []u8, + command : []u8, + args : [][]u8, + timeout_sec : u16, + enabled : bool, +}; + +/// LSP configuration +pub const LSPConfig = struct { + enabled : bool, + language_servers : []LanguageServer, + workspace_roots : []u8, + initialization_options : []u8, + timeout_sec : u16, +}; + +/// Language server configuration +pub const LanguageServer = struct { + language : []u8, // e.g., "t27", "zig", "python" + command : []u8, + args : [][]u8, + workspace_root : ?[]u8, +}; + +/// TUI configuration +pub const TuiConfig = struct { + theme : []u8, + keybindings : []Keybinding, + mouse_enabled : bool, + confirm_dangerous : bool, + timeout_ms : u32, +}; + +/// Keybinding definition +pub const Keybinding = struct { + name : []u8, + keys : []u8, + action : []u8, +}; + +/// Logging configuration +pub const LoggingConfig = struct { + level : []u8, // "debug", "info", "warn", "error" + file : ?[]u8, // File path, null for stdout + format : []u8, // "json", "text" + max_size_mb : u16, +}; + +/// File paths configuration +pub const PathsConfig = struct { + config_dir : []u8, + data_dir : []u8, + cache_dir : []u8, + log_dir : []u8, +}; + +/// Main configuration structure +pub const Config = struct { + version : u16, + provider : ProviderConfig, + agents : []AgentConfig, + mcp_servers : []MCPServerConfig, + lsp : LSPConfig, + tui : TuiConfig, + logging : LoggingConfig, + paths : PathsConfig, +}; + +/// Config validation result +pub const ValidationResult = struct { + valid : bool, + errors : []ConfigError, +}; + +/// Configuration error +pub const ConfigError = struct { + field : []u8, + message : []u8, + severity : []u8, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create default provider config +pub fn provider_default() ProviderConfig { + return ProviderConfig{ + .name = .anthropic, + .api_key = "", + .base_url = "", + .model = DEFAULT_MODEL, + .max_tokens = DEFAULT_MAX_TOKENS, + .timeout_sec = DEFAULT_TIMEOUT_SEC, + .temperature = DEFAULT_TEMPERATURE, + }; +} + +/// Create provider with API key +pub fn provider_with_key(api_key: []u8) ProviderConfig { + const base = provider_default(); + return ProviderConfig{ .api_key = api_key, .base_url = base.base_url, .model = base.model, .max_tokens = base.max_tokens, .timeout_sec = base.timeout_sec, .temperature = base.temperature }; +} + +/// Create default agent config +pub fn agent_default(name: []u8) AgentConfig { + return AgentConfig{ + .name = name, + .description = "", + .capabilities = &[_]AgentCapability{.chat}, + .system_prompt = "", + .model_override = null, + .enabled = true, + }; +} + +/// Create agent with capabilities +pub fn agent_with_capabilities(name: []u8, capabilities: []AgentCapability) AgentConfig { + const base = agent_default(name); + return AgentConfig{ .name = base.name, .description = base.description, .capabilities = capabilities, .system_prompt = base.system_prompt, .model_override = base.model_override, .enabled = base.enabled }; +} + +/// Create LSP server config +pub fn lsp_server_create(language: []u8, command: []u8, args: [][]u8) LanguageServer { + return LanguageServer{ + .language = language, + .command = command, + .args = args, + .workspace_root = null, + }; +} + +/// Create default TUI config +pub fn tui_default() TuiConfig { + return TuiConfig{ + .theme = "dark", + .keybindings = &[_]Keybinding{}, + .mouse_enabled = true, + .confirm_dangerous = true, + .timeout_ms = 5000, + }; +} + +/// Create default logging config +pub fn logging_default() LoggingConfig { + return LoggingConfig{ + .level = "info", + .file = null, + .format = "text", + .max_size_mb = 100, + }; +} + +/// Create default paths config +pub fn paths_default() PathsConfig { + return PathsConfig{ + .config_dir = "", + .data_dir = "", + .cache_dir = "", + .log_dir = "", + }; +} + +/// Create default config +pub fn config_default() Config { + return Config{ + .version = 1, + .provider = provider_default(), + .agents = &[_]AgentConfig{}, + .mcp_servers = &[_]MCPServerConfig{}, + .lsp = LSPConfig{ .enabled = false, .language_servers = &[_]LanguageServer{}, .workspace_roots = &[_]u8{}, .initialization_options = &[_]u8{}, .timeout_sec = 30 }, + .tui = tui_default(), + .logging = logging_default(), + .paths = paths_default(), + }; +} + +/// Create validation result with success +pub fn validation_success() ValidationResult { + return ValidationResult{ + .valid = true, + .errors = &[_]ConfigError{}, + }; +} + +/// Create validation result with errors +pub fn validation_failure(errors: []ConfigError) ValidationResult { + return ValidationResult{ + .valid = false, + .errors = errors, + }; +} + +/// Create config error +pub fn error_create(field: []u8, message: []u8) ConfigError { + return ConfigError{ + .field = field, + .message = message, + .severity = "error", + }; +} + +/// Create config error with severity +pub fn error_with_severity(field: []u8, message: []u8, severity: []u8) ConfigError { + return ConfigError{ + .field = field, + .message = message, + .severity = severity, + }; +} + +/// Check if provider is Anthropic +pub fn is_anthropic(provider: ProviderConfig) bool { + return provider.name == .anthropic; +} + +/// Check if provider is OpenAI +pub fn is_openai(provider: ProviderConfig) bool { + return provider.name == .openai; +} + +/// Check if provider is custom +pub fn is_custom(provider: ProviderConfig) bool { + return provider.name == .custom; +} + +/// Check if API key is present +pub fn has_api_key(provider: ProviderConfig) bool { + return provider.api_key.len > 0; +} + +/// Check if agent has capability +pub fn agent_has_capability(agent: AgentConfig, capability: AgentCapability) bool { + for (agent.capabilities) |cap| { + if (cap == capability) { + return true; + } + } + return false; +} + +/// Check if LSP is enabled +pub fn lsp_is_enabled(config: Config) bool { + return config.lsp.enabled; +} + +/// Add agent to config +pub fn config_add_agent(config: *Config, agent: AgentConfig) void { + config.agents = append_agents(config.agents, agent); +} + +/// Remove agent from config +pub fn config_remove_agent(config: *Config, index: usize) void { + // Remove at index (simplified) +} + +/// Get agent count +pub fn config_agent_count(config: Config) usize { + return config.agents.len; +} + +/// Append agents to slice +pub fn append_agents(slice: []AgentConfig, item: AgentConfig) []AgentConfig { + var result : []AgentConfig = slice; + var new_slice : []AgentConfig = &[_]AgentConfig{item}; + for (result) |_| { + new_slice = append_agents_slice(new_slice, _); + } + return new_slice; +} + +/// Append agents slice +pub fn append_agents_slice(slice: []AgentConfig, item: AgentConfig) []AgentConfig { + var result : []AgentConfig = slice; + result = concat_agents(result, item); + return result; +} + +/// Concatenate agents +pub fn concat_agents(a: []AgentConfig, b: AgentConfig) []AgentConfig { + var result : []AgentConfig = a; + var new_slice : []AgentConfig = &[_]AgentConfig{b}; + for (result) |_| { + new_slice = append_agents_slice(new_slice, _); + } + return new_slice; +} + +/// Concat byte to string +pub fn concat_string(a: []u8, b: []u8) []u8 { + var result : []u8 = a; + for (b) |byte| { + result = append_byte(result, byte); + } + return result; +} + +/// Append byte to string +pub fn append_byte(slice: []u8, byte: u8) []u8 { + var result : []u8 = slice; + result = concat_string(result, &[_]u8{byte}); + return result; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "config_provider_default" { + const provider = provider_default(); + try std.testing.expect(is_anthropic(provider)); +} + +test "config_provider_with_key" { + const provider = provider_with_key("sk-api-key"); + try std.testing.expect(has_api_key(provider)); +} + +test "config_agent_default" { + const agent = agent_default("test-agent"); + try std.testing.expect(std.mem.eql(agent.name, "test-agent")); +} + +test "config_agent_with_capabilities" { + const caps = &[_]AgentCapability{.chat, .code_edit}; + const agent = agent_with_capabilities("test", caps); + try std.testing.expect(agent_has_capability(agent, .chat)); + try std.testing.expect(agent_has_capability(agent, .code_edit)); +} + +test "config_lsp_server_create" { + const lsp = lsp_server_create("t27", "t27c", &[_][]u8{}); + try std.testing.expect(std.mem.eql(lsp.language, "t27")); +} + +test "config_tui_default" { + const tui = tui_default(); + try std.testing.expect(std.mem.eql(tui.theme, "dark")); +} + +test "config_logging_default" { + const logging = logging_default(); + try std.testing.expect(std.mem.eql(logging.level, "info")); +} + +test "config_paths_default" { + const paths = paths_default(); + try std.testing.expect(paths.data_dir.len == 0); +} + +test "config_default_complete" { + const config = config_default(); + try std.testing.expect(config.version == 1); +} + +test "config_validation_success" { + const result = validation_success(); + try std.testing.expect(result.valid); +} + +test "config_validation_failure" { + const error = error_create("api_key", "API key is required"); + const result = validation_failure(&[_]ConfigError{error}); + try std.testing.expect(!result.valid); +} + +test "config_add_agent" { + var config = config_default(); + const agent = agent_default("new-agent"); + config_add_agent(&config, agent); + try std.testing.expect(config_agent_count(&config) == 1); +} + +test "config_agent_has_capability_true" { + const caps = &[_]AgentCapability{.chat, .code_edit}; + const agent = agent_with_capabilities("test", caps); + try std.testing.expect(agent_has_capability(agent, .code_edit)); +} + +test "config_agent_has_capability_false" { + const caps = &[_]AgentCapability{.chat}; + const agent = agent_with_capabilities("test", caps); + try std.testing.expect(!agent_has_capability(agent, .code_edit)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant max_api_key_length_positive { + // MAX_API_KEY_LENGTH is positive + @compileAssert(MAX_API_KEY_LENGTH > 0); +} + +invariant max_agent_name_length_positive { + // MAX_AGENT_NAME_LENGTH is positive + @compileAssert(MAX_AGENT_NAME_LENGTH > 0); +} + +invariant max_timeout_positive { + // MAX_TIMEOUT_SEC is positive + @compileAssert(MAX_TIMEOUT_SEC > 0); +} + +invariant provider_type_enum_valid { + // ProviderType enum has valid values + @compileAssert(@intFromEnum(ProviderType.custom) == 2); +} + +invariant capability_enum_valid { + // AgentCapability enum has valid values + @compileAssert(@intFromEnum(AgentCapability.execute) == 4); +} + +invariant provider_config_has_name { + // ProviderConfig has name field + @compileAssert(true); +} + +invariant provider_config_has_api_key { + // ProviderConfig has api_key field + @compileAssert(true); +} + +invariant provider_config_has_model { + // ProviderConfig has model field + @compileAssert(true); +} + +invariant agent_config_has_name { + // AgentConfig has name field + @compileAssert(true); +} + +invariant agent_config_has_capabilities { + // AgentConfig has capabilities array + @compileAssert(true); +} + +invariant lsp_config_has_enabled { + // LSPConfig has enabled field + @compileAssert(true); +} + +invariant tui_config_has_theme { + // TuiConfig has theme field + @compileAssert(true); +} + +invariant logging_config_has_level { + // LoggingConfig has level field + @compileAssert(true); +} + +invariant paths_config_has_dirs { + // PathsConfig has directory fields + @compileAssert(true); +} + +invariant config_has_version { + // Config has version field + @compileAssert(true); +} + +invariant validation_result_has_valid { + // ValidationResult has valid boolean + @compileAssert(true); +} + +invariant validation_result_has_errors { + // ValidationResult has errors array + @compileAssert(true); +} + +invariant config_error_has_fields { + // ConfigError has all required fields + @compileAssert(true); +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "config_provider_create" { + const provider = provider_default(); + try std.testing.expect(is_anthropic(provider)); +} + +test "config_provider_with_key" { + const provider = provider_with_key("sk-api-key"); + try std.testing.expect(has_api_key(provider)); +} + +test "config_agent_default" { + const agent = agent_default("test-agent"); + try std.testing.expect(std.mem.eql(agent.name, "test-agent")); +} + +test "config_agent_with_capabilities" { + const caps = &[_]AgentCapability{.chat, .code_edit}; + const agent = agent_with_capabilities("test", caps); + try std.testing.expect(agent_has_capability(agent, .chat)); + try std.testing.expect(agent_has_capability(agent, .code_edit)); +} + +test "config_lsp_server_create" { + const lsp = lsp_server_create("t27", "t27c", &[_][]u8{}); + try std.testing.expect(std.mem.eql(lsp.language, "t27")); +} + +test "config_tui_default" { + const tui = tui_default(); + try std.testing.expect(std.mem.eql(tui.theme, "dark")); +} + +test "config_logging_default" { + const logging = logging_default(); + try std.testing.expect(std.mem.eql(logging.level, "info")); +} + +test "config_paths_default" { + const paths = paths_default(); + try std.testing.expect(paths.data_dir.len == 0); +} + +test "config_default_complete" { + const config = config_default(); + try std.testing.expect(config.version == 1); +} + +test "config_validation_success" { + const context = validation_context_default(); + const result = validate(config, context); + try std.testing.expect(result.valid); +} + +test "config_validation_failure_missing_api_key" { + var config = config_default(); + config.provider.api_key = ""; + const context = validation_context_default(); + const result = validate(config, context); + try std.testing.expect(!result.valid); +} + +test "config_add_agent" { + var config = config_default(); + const agent = agent_default("new-agent"); + config_add_agent(&config, agent); + try std.testing.expect(config_agent_count(&config) == 1); +} + +test "config_agent_has_capability_true" { + const caps = &[_]AgentCapability{.chat, .code_edit}; + const agent = agent_with_capabilities("test", caps); + try std.testing.expect(agent_has_capability(agent, .code_edit)); +} + +test "config_agent_has_capability_false" { + const caps = &[_]AgentCapability{.chat}; + const agent = agent_with_capabilities("test", caps); + try std.testing.expect(!agent_has_capability(agent, .code_edit)); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "config_provider_create_latency" { + // Measure: cycles for provider config creation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : ProviderConfig = undefined; + for (0..1000) |_| { + result = provider_default(); + } + _ = result.max_tokens; +} + +bench "config_agent_create_latency" { + // Measure: cycles for agent config creation + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : AgentConfig = undefined; + for (0..1000) |_| { + result = agent_default("test"); + } + _ = result.capabilities.len; +} + +bench "config_validation_success_latency" { + // Measure: cycles for validation success result + // Target: < 20 cycles + @setEvalBranchQuota(10000); + var result : ValidationResult = undefined; + for (0..1000) |_| { + result = validation_success(); + } + _ = result.valid; +} + +bench "config_capability_check_latency" { + // Measure: cycles for capability check + // Target: < 30 cycles + @setEvalBranchQuota(10000); + const caps = &[_]AgentCapability{.chat, .code_edit}; + const agent = agent_with_capabilities("test", caps); + var result : bool = false; + for (0..1000) |_| { + result = agent_has_capability(agent, .code_edit); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/conformance/e2e_scenarios.t27 b/apps/website/public/t27/specs/conformance/e2e_scenarios.t27 new file mode 100644 index 0000000000..a7b875a35c --- /dev/null +++ b/apps/website/public/t27/specs/conformance/e2e_scenarios.t27 @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: Apache-2.0 +# E2E TEST SCENARIOS -- Full Pipeline Verification + +## Specification + +Tests full pipeline: VSA -> VM -> SDK -> Codebook -> Verdict. +Part of Phase 4: Quality & Performance (Issue #48). + +## Mathematical Foundation + +``` +phi^2 + 1/phi^2 = 3 = TRINITY +``` + +## Test Scenarios + +### E2E: VSA create -> VM execute -> SDK verify + +``` +test "E2E: VSA create -> VM execute -> SDK verify" { + // Stage 1: Create vectors via VSA core + var a = vsa.randomVector(256, 42); + var b = vsa.randomVector(256, 84); + + // Stage 2: Execute bind in VM + var machine = vm.VSAVM.init(allocator); + machine.registers.v0 = a; + machine.registers.v1 = b; + try machine.loadProgram(&[_]vm.VSAInstruction{ + .{ .opcode = .v_bind, .dst = 2, .src1 = 0, .src2 = 1 }, + .{ .opcode = .v_cosine, .dst = 0, .src1 = 2, .src2 = 0 }, + .{ .opcode = .halt }, + }); + try machine.run(); + + // Stage 3: Verify via SDK -- bind via VSA core, compare with VM result + var bound_sdk = vsa.bind(&a, &b); + var vm_result_raw = machine.registers.v2; + + // VM bind and VSA bind should produce identical results + const sim = vsa.cosineSimilarity(&vm_result_raw, &bound_sdk); + try expect(sim > 0.99); +} +``` + +### E2E: Codebook encode -> bind -> decode roundtrip + +``` +test "E2E: Codebook encode -> bind -> decode roundtrip" { + // Stage 1: Create codebook via SDK + var codebook = sdk.Codebook.init(allocator, 512); + defer codebook.deinit(); + + // Stage 2: Encode symbols + const cat = try codebook.encode("cat"); + const sits = try codebook.encode("sits"); + const mat = try codebook.encode("mat"); + + // Stage 3: Bind role-filler pairs + var subject_role = sdk.Hypervector.random(512, 0xAABB); + var verb_role = sdk.Hypervector.random(512, 0xCCDD); + var object_role = sdk.Hypervector.random(512, 0xEEFF); + + var s_bound = subject_role.bind(cat); + var v_bound = verb_role.bind(sits); + var o_bound = object_role.bind(mat); + + // Stage 4: Bundle into sentence + var temp = s_bound.bundle(&v_bound); + var sentence = temp.bundle(&o_bound); + + // Stage 5: Decode -- query subject + var retrieved_subject = sentence.unbind(&subject_role); + const decoded = codebook.decode(&retrieved_subject); + try expect(decoded != null); + // Decoded should be "cat" (nearest neighbor in codebook) + try expectEqualStrings("cat", decoded.?); +} +``` + +### E2E: AssociativeMemory store -> retrieve with VM vectors + +``` +test "E2E: AssociativeMemory store -> retrieve with VM vectors" { + // Stage 1: Create vectors via VM + var machine = vm.VSAVM.init(allocator); + defer machine.deinit(); + + try machine.loadProgram(&[_]vm.VSAInstruction{ + .{ .opcode = .v_random, .dst = 0, .imm = 111 }, + .{ .opcode = .v_random, .dst = 1, .imm = 222 }, + .{ .opcode = .halt }, + }); + try machine.run(); + + // Stage 2: Store in AssociativeMemory + var key = sdk.Hypervector.fromRaw(machine.registers.v0); + var value = sdk.Hypervector.fromRaw(machine.registers.v1); + + var memory = sdk.AssociativeMemory.init(vsa.MAX_TRITS); + memory.store(&key, &value); + try expectEqual(@as(usize, 1), memory.count()); + + // Stage 3: Retrieve + var retrieved = memory.retrieve(&key); + const sim = retrieved.similarity(&value); + // Retrieved should resemble stored value + try expect(sim > 0.15); +} +``` + +### E2E: Sequence encode -> probe position recovery + +``` +test "E2E: Sequence encode -> probe position recovery" { + // Stage 1: Create symbol vectors + var apple = sdk.Hypervector.random(512, 10); + + // Stage 2: Encode sequence [apple, banana, cherry] + var encoder = sdk.SequenceEncoder.init(512); + var items = [_]sdk.Hypervector{ + apple, + sdk.Hypervector.random(512, 20), + sdk.Hypervector.random(512, 30), + }; + var seq = encoder.encode(&items); + + // Stage 3: Probe -- apple should be at position 0 + const sim_apple_0 = encoder.probe(&seq, &apple, 0); + const sim_apple_1 = encoder.probe(&seq, &apple, 1); + const sim_apple_2 = encoder.probe(&seq, &apple, 2); + + // Correct position should have highest similarity + try expect(sim_apple_0 > sim_apple_1); + try expect(sim_apple_0 > sim_apple_2); +} +``` + +### E2E: Classifier train -> predict + +``` +test "E2E: Classifier train -> predict" { + var classifier = sdk.Classifier.init(allocator, 512); + defer classifier.deinit(); + + // Train: 3 samples per class + var fruit1 = sdk.Hypervector.random(512, 1001); + var fruit2 = sdk.Hypervector.random(512, 1002); + var fruit3 = sdk.Hypervector.random(512, 1003); + try classifier.train("fruit", &fruit1); + try classifier.train("fruit", &fruit2); + try classifier.train("fruit", &fruit3); + + var veggie1 = sdk.Hypervector.random(512, 2001); + var veggie2 = sdk.Hypervector.random(512, 2002); + try classifier.train("veggie", &veggie1); + try classifier.train("veggie", &veggie2); + + try expectEqual(@as(usize, 2), classifier.classCount()); + + // Predict: fruit sample should classify as fruit + const prediction = classifier.predictWithConfidence(&fruit1); + try expect(prediction.class != null); + try expectEqualStrings("fruit", prediction.class.?); + try expect(prediction.confidence > 0.3); +} +``` + +### E2E: HybridBigInt pack -> unpack -> VM execute + +``` +test "E2E: HybridBigInt pack -> unpack -> VM execute" { + // Stage 1: Create and pack vector + var v = vsa.randomVector(256, 777); + v.pack(); + try expect(v.mode == .packed_mode); + + // Stage 2: Load into VM (forces unpack) + var machine = vm.VSAVM.init(allocator); + machine.registers.v0 = v; + try machine.loadProgram(&[_]vm.VSAInstruction{ + .{ .opcode = .v_random, .dst = 0, .imm = 42 }, + .{ .opcode = .v_cosine, .dst = 0, .src1 = 0, .src2 = 0 }, + .{ .opcode = .halt }, + }); + try machine.run(); + + // Self-similarity must be 1.0 + try expect(machine.registers.f0 > 0.99); +} +``` + +## Benchmarks + +``` +BENCH: VSA bind throughput + Dimension: 1024 + Iterations: 1000 + Target: < 1,000,000 ns/op + +BENCH: VSA bundle2 throughput + Dimension: 1024 + Iterations: 1000 + Target: < 1,000,000 ns/op + +BENCH: VSA cosineSimilarity throughput + Dimension: 1024 + Iterations: 1000 + Target: < 1,000,000 ns/op + +BENCH: VSA hammingDistance throughput + Dimension: 1024 + Iterations: 1000 + Target: < 1,000,000 ns/op + +BENCH: VSA permute throughput + Dimension: 1024 + Iterations: 1000 + Target: < 1,000,000 ns/op +``` + +## Verdict System + +``` +const VerdictResult = struct { + pass: bool, + score: f64, // 0.0 - 100.0 + vsa_score: f64, + vm_score: f64, + sdk_score: f64, + memory_score: f64, + perf_score: f64, +}; +``` + +## Expected Verdict Breakdown + +| Test | vsa_score | vm_score | sdk_score | memory_score | perf_score | +|------|-----------|----------|------------|-------------|------------| +| VSA create/verify | > 0.99 | > 0.99 | > 0.99 | N/A | > 0.9 | +| Codebook roundtrip | > 0.9 | N/A | > 0.9 | N/A | > 0.9 | +| AssociativeMemory | > 0.15 | N/A | N/A | > 0.15 | N/A | > 0.9 | +| Sequence encoder | > 0.9 | N/A | N/A | N/A | > 0.9 | +| Classifier | > 0.3 | N/A | N/A | N/A | > 0.9 | +| Pack/unpack | > 0.99 | N/A | N/A | N/A | > 0.9 | +| VM program | > 0.99 | N/A | N/A | N/A | > 0.9 | + +**Total verdict:** PASS (all scores > 0.9) + +## Tests + +``` +test "E2E: VSA bind throughput" { + // bind should take less than 1ms per op +} + +test "E2E: VSA bundle2 throughput" { + // bundle2 should be similar to both inputs +} + +test "E2E: VM program execution" { + // Full VM program should complete in under 100ms +} +``` diff --git a/apps/website/public/t27/specs/demos/jones_topology_decision_gate.t27 b/apps/website/public/t27/specs/demos/jones_topology_decision_gate.t27 new file mode 100644 index 0000000000..fb4cad9ca5 --- /dev/null +++ b/apps/website/public/t27/specs/demos/jones_topology_decision_gate.t27 @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/demos/jones_topology_decision_gate.t27 +// Decision Gate TH-01..TH-05 for H_1: Structure Similarity Classifier +// Tests if VSA dot_product + fixed phi constant can classify structures by complexity +// phi^2 + 1/phi^2 = 3 | TRINITY + +module JonesTopologyDecisionGate { + use base::types; + use math::constants; + use vsa::ops; + use demos::jones_topology_filter; + + // ================================================================= + // Decision Gate Hypotheses + // ===================================================================== + + // TH-01: Self-similarity -> d = 1.0 + // Same structure compared with itself should have perfect similarity + const TH_01_TARGET : f64 = 1.0; + + // TH-02: Inversion similarity -> d < 0.95 + // Inverted structure should have low similarity + const TH_02_THRESHOLD : f64 = 0.95; + + // TH-03: Different types -> d < 0.5 + // Tree vs cycle structures should have low similarity + const TH_03_THRESHOLD : f64 = 0.5; + + // TH-04: Simple -> complex monotonic + // Complex structure should have HIGHER complexity level than simple + const TH_04_EXPECTED : u8 = jones_topology_filter::COMPLEXITY_HIGH; + + // TH-05: Random orthogonality -> |d| ~= 0 + // Random structures should be orthogonal (similarity near 0) + const TH_05_THRESHOLD : f64 = 0.2; + + // ================================================================= + // Test Structures + // ===================================================================== + + // Tree-like structure (sequential pattern) + fn tree_structure() -> []Trit { + var result : []Trit = []; + result.reserve(1024); + + var i : usize = 0; + while (i < 1024) { + const t = if (i % 4 == 0) { Trit.pos } + else if (i % 2 == 0) { Trit.zero } + else { Trit.neg }; + result.push(t); + i = i + 1; + } + + return result; + } + + // Cycle-like structure (repeating pattern) + fn cycle_structure() -> []Trit { + var result : []Trit = []; + result.reserve(1024); + + var i : usize = 0; + while (i < 1024) { + const t = switch (i % 3) { + 0 => Trit.pos, + 1 => Trit.neg, + _ => Trit.zero, + }; + result.push(t); + i = i + 1; + } + + return result; + } + + // Simple structure (mostly zeros) + fn simple_structure() -> []Trit { + var result : []Trit = []; + result.reserve(1024); + + var i : usize = 0; + while (i < 1024) { + const t = if (i % 10 == 0) { Trit.pos } + else if (i % 10 == 1) { Trit.neg } + else { Trit.zero }; + result.push(t); + i = i + 1; + } + + return result; + } + + // Complex structure (mostly non-zero) + fn complex_structure() -> []Trit { + var result : []Trit = []; + result.reserve(1024); + + var i : usize = 0; + while (i < 1024) { + const t = if (i % 2 == 0) { Trit.pos } + else { Trit.neg }; + result.push(t); + i = i + 1; + } + + return result; + } + + // Random structure A (deterministic pseudo-random) + fn random_structure_a() -> []Trit { + var result : []Trit = []; + result.reserve(1024); + + var i : usize = 0; + while (i < 1024) { + const t = switch (i % 7) { + 0 => Trit.pos, + 1 => Trit.neg, + 2 => Trit.zero, + 3 => Trit.pos, + 4 => Trit.neg, + 5 => Trit.zero, + _ => Trit.pos, + }; + result.push(t); + i = i + 1; + } + + return result; + } + + // Random structure B (different pattern) + fn random_structure_b() -> []Trit { + var result : []Trit = []; + result.reserve(1024); + + var i : usize = 0; + while (i < 1024) { + const t = switch (i % 11) { + 0 => Trit.neg, + 1 => Trit.zero, + 2 => Trit.pos, + 3 => Trit.neg, + 4 => Trit.zero, + 5 => Trit.pos, + 6 => Trit.neg, + 7 => Trit.zero, + 8 => Trit.pos, + 9 => Trit.neg, + _ => Trit.zero, + }; + result.push(t); + i = i + 1; + } + + return result; + } + + // ================================================================= + // Cosine Similarity Helper + // ========================================================================= + + fn cosine_similarity(a: []Trit, b: []Trit) -> f64 { + const dim = a.len(); + const dot = vsa::ops::dot_product(a, b, dim); + const norm_a = vsa::ops::vector_norm(a, dim); + const norm_b = vsa::ops::vector_norm(b, dim); + + if (norm_a == 0.0 || norm_b == 0.0) { + return 0.0; + } + + return dot / (norm_a * norm_b); + } + + // ========================================================================= + // Decision Gate Tests + // ============================================================================= + + test TH_01_self_similarity_one + given A = jones_topology_filter::standard_structure() + when sig_a = jones_topology_filter::jones_signature(A) + and sig_a2 = jones_topology_filter::jones_signature(A) + then constants::abs(sig_a.dot_product - sig_a2.dot_product) < 1e-10 + + test TH_02_inverted_low_similarity + given A = jones_topology_filter::standard_structure() + and A_prime = jones_topology_filter::invert_structure(A) + when sig_a = jones_topology_filter::jones_signature(A) + and sig_prime = jones_topology_filter::jones_signature(A_prime) + and sim = cosine_similarity(A, A_prime) + then sim < TH_02_THRESHOLD + + test TH_03_tree_cycle_low_similarity + given tree = tree_structure() + and cycle = cycle_structure() + and sig_tree = jones_topology_filter::jones_signature(tree) + and sig_cycle = jones_topology_filter::jones_signature(cycle) + and sim = cosine_similarity(tree, cycle) + then sim < TH_03_THRESHOLD + + test TH_04_simple_complex_monotonic + given simple = simple_structure() + and complex = complex_structure() + and sig_simple = jones_topology_filter::jones_signature(simple) + and sig_complex = jones_topology_filter::jones_signature(complex) + then sig_simple.complexity_level < sig_complex.complexity_level + + test TH_05_random_orthogonality + given rand_a = random_structure_a() + and rand_b = random_structure_b() + and sig_a = jones_topology_filter::jones_signature(rand_a) + and sig_b = jones_topology_filter::jones_signature(rand_b) + and sim = cosine_similarity(rand_a, rand_b) + then constants::abs(sim) < TH_05_THRESHOLD + + // ===================================================================================== + // Cosine Similarity Matrix Test + // ======================================================================================= + + test similarity_matrix_computed + // Verify all pairwise similarities are computed + given standard = jones_topology_filter::standard_structure() + and inverted = jones_topology_filter::invert_structure(standard) + and tree = tree_structure() + and cycle = cycle_structure() + and simple = simple_structure() + when sim_stan_inv = cosine_similarity(standard, inverted) + and sim_stan_tree = cosine_similarity(standard, tree) + and sim_stan_cyc = cosine_similarity(standard, cycle) + and sim_tree_cyc = cosine_similarity(tree, cycle) + then true // All similarities computed without error + + test similarity_matrix_values_in_range + // Verify all similarities are in valid cosine range [-1, 1] + given structures = [ + jones_topology_filter::standard_structure(), + jones_topology_filter::invert_structure( + jones_topology_filter::standard_structure() + ), + tree_structure(), + cycle_structure(), + simple_structure(), + complex_structure(), + ] + when all_valid = true + // Check each pair is in [-1, 1] + then all_valid + + // ======================================================================================= + // Complexity Distribution Test + // =========================================================================================== + + test complexity_distribution_valid + given structures = [ + jones_topology_filter::standard_structure(), + jones_topology_filter::invert_structure( + jones_topology_filter::standard_structure() + ), + tree_structure(), + cycle_structure(), + simple_structure(), + complex_structure(), + ] + when levels = [] + // All complexity levels should be in {0, 1, 2} + then true + + // ===================================================================================================== + // Invariants + // ========================================================================================================= + + invariant decision_gate_constants_valid + assert TH_01_TARGET == 1.0 + assert TH_02_THRESHOLD > 0.0 and TH_02_THRESHOLD < 1.0 + assert TH_03_THRESHOLD >= 0.0 and TH_03_THRESHOLD < 1.0 + assert TH_04_EXPECTED == jones_topology_filter::COMPLEXITY_HIGH + assert TH_05_THRESHOLD >= 0.0 + + invariant self_similarity_is_perfect + when A = jones_topology_filter::standard_structure() + and sig_a = jones_topology_filter::jones_signature(A) + and sig_a2 = jones_topology_filter::jones_signature(A) + assert constants::abs(sig_a.dot_product - sig_a2.dot_product) < 1e-10 + + invariant inverted_has_low_similarity + when A = jones_topology_filter::standard_structure() + and A_prime = jones_topology_filter::invert_structure(A) + and sim = cosine_similarity(A, A_prime) + assert sim < TH_02_THRESHOLD + + invariant tree_cycle_different + when tree = tree_structure() + and cycle = cycle_structure() + and sim = cosine_similarity(tree, cycle) + assert sim < TH_03_THRESHOLD + + invariant complex_greater_than_simple + when simple = simple_structure() + and complex = complex_structure() + and sig_simple = jones_topology_filter::jones_signature(simple) + and sig_complex = jones_topology_filter::jones_signature(complex) + assert sig_simple.complexity_level < sig_complex.complexity_level + + invariant random_orthogonal + when rand_a = random_structure_a() + and rand_b = random_structure_b() + and sim = cosine_similarity(rand_a, rand_b) + assert constants::abs(sim) < TH_05_THRESHOLD + + invariant cosine_similarity_bounds + when structures = [ + jones_topology_filter::standard_structure(), + jones_topology_filter::invert_structure( + jones_topology_filter::standard_structure() + ), + ] + and sim = cosine_similarity(structures[0], structures[1]) + assert sim >= -1.0 and sim <= 1.0 + + // ======================================================================================================================= + // Benchmarks + // =============================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================14 bench jones_signature_throughput + measure: nanoseconds to compute jones_signature(standard_structure()) + target: < 5000ns + + bench decision_gate_TH_02_throughput + measure: nanoseconds to compute cosine_similarity( + standard_structure(), + invert_structure(standard_structure()) + ) + target: < 2000ns + + bench decision_gate_TH_03_throughput + measure: nanoseconds to compute cosine_similarity(tree_structure(), cycle_structure()) + target: < 2000ns + + bench decision_gate_TH_04_throughput + measure: nanoseconds to compute complexity levels for simple vs complex + target: < 3000ns + + bench decision_gate_TH_05_throughput + measure: nanoseconds to compute cosine_similarity(random_structure_a(), random_structure_b()) + target: < 2000ns +} diff --git a/apps/website/public/t27/specs/demos/jones_topology_filter.t27 b/apps/website/public/t27/specs/demos/jones_topology_filter.t27 new file mode 100644 index 0000000000..e97491bc13 --- /dev/null +++ b/apps/website/public/t27/specs/demos/jones_topology_filter.t27 @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/demos/jones_topology_filter.t27 +// MVP: Structure Similarity Classifier using VSA + CS Constants +// WHAT THIS CODE ACTUALLY DOES: +// - Takes a hypervector representing a structure +// - Computes dot_product similarity with a reference structure +// - Classifies complexity based on similarity thresholds +// - Uses a fixed Chern-Simons constant (phi) as reference value +// WHAT THIS CODE DOES NOT DO: +// - Does NOT compute Jones polynomial from input topology +// - Does NOT provide "topological acceleration" + +module JonesTopologyFilter { + use base::types; + use math::constants; + use vsa::ops; + use physics::su2_chern_simons; + use math::sacred_physics; + + // Jones Signature - Compact Topological Representation + struct JonesSignature { + jones_value : f64, + dot_product : f64, + complexity_level : u8, + } + + // Classification Constants + const COMPLEXITY_LOW : u8 = 0; + const COMPLEXITY_MID : u8 = 1; + const COMPLEXITY_HIGH : u8 = 2; + + const PHI_THRESHOLD_LOW : f64 = 0.5; + const PHI_THRESHOLD_HIGH : f64 = 2.0; + + // Compute Jones Signature for a Structure + fn jones_signature(structure: []Trit) -> JonesSignature { + const dim = structure.len(); + + // 1. Compute Jones polynomial + const jones = su2_chern_simons::jones_polynomial_at_5th_root(); + + // 2. Dot product with reference structure + const standard = standard_structure(); + const dot = vsa::ops::dot_product(structure, standard, dim); + + // 3. Classify by proximity to phi and dot product + const jones_diff = constants::abs(jones - sacred_physics::PHI); + + let level : u8 = COMPLEXITY_LOW; + + if (jones_diff > 0.2) { + level = COMPLEXITY_LOW; + } else if (dot < PHI_THRESHOLD_LOW) { + level = COMPLEXITY_MID; + } else { + level = COMPLEXITY_HIGH; + } + + return JonesSignature{ + jones_value = jones, + dot_product = dot, + complexity_level = level, + }; + } + + // Reference Structure (half +1, half -1) + fn standard_structure() -> []Trit { + var result : []Trit = []; + + var i : usize = 0; + while (i < 512) { + result.push(Trit.pos); + i = i + 1; + } + while (i < 1024) { + result.push(Trit.neg); + i = i + 1; + } + + return result; + } + + // Inverted Structure for testing + fn invert_structure(structure: []Trit) -> []Trit { + var result : []Trit = []; + result.reserve(structure.len()); + + var i : usize = 0; + while (i < structure.len()) { + const t = structure[i]; + if (t == Trit.pos) { + result.push(Trit.neg); + } else if (t == Trit.neg) { + result.push(Trit.pos); + } else { + result.push(Trit.zero); + } + i = i + 1; + } + + return result; + } + + // Complex Structure for HIGH level + fn complex_structure() -> []Trit { + var result : []Trit = []; + result.reserve(1024); + + var i : usize = 0; + while (i < 1024) { + const t = if (i % 2 == 0) { Trit.pos } + else if (i % 3 == 0) { Trit.neg } + else { Trit.zero }; + result.push(t); + i = i + 1; + } + + return result; + } + + // Compare Signatures + fn signatures_match(sig_a: JonesSignature, sig_b: JonesSignature) -> bool { + const jones_close = constants::abs(sig_a.jones_value - sig_b.jones_value) < 0.1; + const dot_close = constants::abs(sig_a.dot_product - sig_b.dot_product) < 10.0; + const level_match = sig_a.complexity_level == sig_b.complexity_level; + + return jones_close && dot_close && level_match; + } + + // Complexity Level Name + fn complexity_name(level: u8) -> string { + if (level == COMPLEXITY_LOW) { + return "LOW"; + } else if (level == COMPLEXITY_MID) { + return "MEDIUM"; + } else { + return "HIGH"; + } + } + + // TDD Tests + test jones_signature_returns_phi + given structure = standard_structure() + when sig = jones_signature(structure) + then constants::abs(sig.jones_value - sacred_physics::PHI) < 1e-10 + + test standard_structure_consistent + given s1 = standard_structure() + and s2 = standard_structure() + when sig1 = jones_signature(s1) + and sig2 = jones_signature(s2) + then signatures_match(sig1, sig2) == true + + test different_structures_different_signatures + given s1 = standard_structure() + and s2 = invert_structure(s1) + when sig1 = jones_signature(s1) + and sig2 = jones_signature(s2) + then signatures_match(sig1, sig2) == false + + test complex_structure_high_level + given complex = complex_structure() + when sig = jones_signature(complex) + then sig.complexity_level == COMPLEXITY_HIGH + + test standard_structure_mid_level + given standard = standard_structure() + when sig = jones_signature(standard) + then sig.complexity_level == COMPLEXITY_MID + + test inverted_structure_different_dot_product + given s1 = standard_structure() + and s2 = invert_structure(s1) + when sig1 = jones_signature(s1) + and sig2 = jones_signature(s2) + then constants::abs(sig1.dot_product - sig2.dot_product) > 100.0 + + test signatures_match_different_levels + given s1 = standard_structure() + and s2 = complex_structure() + when sig1 = jones_signature(s1) + and sig2 = jones_signature(s2) + then signatures_match(sig1, sig2) == false + + test jones_value_is_consistent + given s1 = standard_structure() + and s2 = standard_structure() + when sig1 = jones_signature(s1) + and sig2 = jones_signature(s2) + then constants::abs(sig1.jones_value - sig2.jones_value) < 1e-15 + + test complexity_name_low + given name = complexity_name(COMPLEXITY_LOW) + then name == "LOW" + + test complexity_name_medium + given name = complexity_name(COMPLEXITY_MID) + then name == "MEDIUM" + + test complexity_name_high + given name = complexity_name(COMPLEXITY_HIGH) + then name == "HIGH" + + test jones_signature_preserves_jones_value + given structure = standard_structure() + and jones = su2_chern_simons::jones_polynomial_at_5th_root() + when sig = jones_signature(structure) + then constants::abs(sig.jones_value - jones) < 1e-10 + + test standard_structure_dimension_correct + given structure = standard_structure() + then structure.len() == 1024 + + test inverted_structure_same_dimension + given original = standard_structure() + and inverted = invert_structure(original) + then inverted.len() == original.len() + + test complex_structure_dimension_correct + given structure = complex_structure() + then structure.len() == 1024 + + test signatures_match_close_jones + given s1 = standard_structure() + and s2 = standard_structure() + when sig1 = jones_signature(s1) + and sig2 = jones_signature(s2) + then signatures_match(sig1, sig2) == true + + test dot_product_positive_for_standard + given structure = standard_structure() + when sig = jones_signature(structure) + then sig.dot_product > 0.0 + + // Invariants + invariant jones_value_always_phi + assert constants::abs(jones_signature(standard_structure()).jones_value - sacred_physics::PHI) < 1e-10 + + invariant complexity_levels_valid + assert jones_signature(standard_structure()).complexity_level in {0, 1, 2} + assert jones_signature(complex_structure()).complexity_level in {0, 1, 2} + + invariant signature_jones_value_positive + when sig = jones_signature(standard_structure()) + assert sig.jones_value > 0.0 + + invariant complexity_levels_exclusive + when sig = jones_signature(standard_structure()) + let has_low = sig.complexity_level == COMPLEXITY_LOW; + let has_mid = sig.complexity_level == COMPLEXITY_MID; + let has_high = sig.complexity_level == COMPLEXITY_HIGH; + let sum = if (has_low) { 1 } else { 0 } + if (has_mid) { 1 } else { 0 }; + sum = if (has_high) { 1 } else { 0 }; + assert sum == 1 + + invariant jones_value_in_phi_range + when sig = jones_signature(standard_structure()) + assert sig.jones_value > 1.5 and sig.jones_value < 1.7 + + invariant standard_structure_size_fixed + assert standard_structure().len() == 1024 + + invariant complex_structure_size_fixed + assert complex_structure().len() == 1024 + + invariant inverted_structure_size_preserved + given original = standard_structure() + when inverted = invert_structure(original) + assert inverted.len() == original.len() + + invariant signatures_match_symmetric + given s1 = standard_structure() + and s2 = standard_structure() + when sig1 = jones_signature(s1) + and sig2 = jones_signature(s2) + assert signatures_match(sig1, sig2) == signatures_match(sig2, sig1) + + invariant jones_signature_is_deterministic + given s = standard_structure() + when sig1 = jones_signature(s) + and sig2 = jones_signature(s) + assert sig1.jones_value == sig2.jones_value + assert sig1.dot_product == sig2.dot_product + + invariant complexity_name_returns_valid_string + assert complexity_name(0) == "LOW" + assert complexity_name(1) == "MEDIUM" + assert complexity_name(2) == "HIGH" + + // Benchmarks + bench jones_signature_computation + measure: nanoseconds to compute jones_signature(standard_structure()) + target: < 5000ns + + bench standard_structure_creation + measure: nanoseconds to compute standard_structure() + target: < 2000ns + + bench invert_structure_computation + measure: nanoseconds to compute invert_structure(standard_structure()) + target: < 3000ns + + bench complex_structure_creation + measure: nanoseconds to compute complex_structure() + target: < 2500ns + + bench signatures_match_computation + measure: nanoseconds to compute signatures_match( + jones_signature(standard_structure()), + jones_signature(standard_structure()) + ) + target: < 1000ns + + bench complexity_name_lookup + measure: nanoseconds to compute complexity_name(1) + target: < 100ns +} diff --git a/apps/website/public/t27/specs/demos/simple_test.t27 b/apps/website/public/t27/specs/demos/simple_test.t27 new file mode 100644 index 0000000000..5de4ed6f9e --- /dev/null +++ b/apps/website/public/t27/specs/demos/simple_test.t27 @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// Simple test spec +module SimpleTest { + use base::types; + + const TEST_VALUE : u8 = 42; + + test simple_test + given value = TEST_VALUE + then value == 42 + + invariant value_constant + assert TEST_VALUE == 42 +} diff --git a/apps/website/public/t27/specs/depin/prove.t27 b/apps/website/public/t27/specs/depin/prove.t27 new file mode 100644 index 0000000000..2e6572a88b --- /dev/null +++ b/apps/website/public/t27/specs/depin/prove.t27 @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: Apache-2.0 +// DePIN proof-of-useful-compute spec +// Issue #40 — L-TRI-1: POST /prove endpoint +// phi^2 + phi^-2 = 3 | TRINITY + +module depin.prove; + +// ============================================================================ +// Types +// ============================================================================ + +pub struct ProveRequest { + node_id : [u8; 32], + epoch : u64, + phi_response : Vec, + merkle_proof : MerkleProof, + merkle_leaf_index : usize, + peer_sample_sig : [u8; 64], + version : u8, +} + +pub struct MerkleProof { + root : [u8; 32], + leaf : [u8; 32], + siblings : Vec<[u8; 32]>, +} + +pub struct ProveResponse { + valid : bool, + reward_lamports : u64, + epoch_hash : [u8; 32], + next_challenge : [u8; 16], + tokens_count : u64, + reason : ?[]const u8, +} + +pub struct MiningEpoch { + epoch_id : u64, + phi_seed : [u8; 16], + start_ts : u64, + block_reward : u64, +} + +// ============================================================================ +// Functions +// ============================================================================ + +pub fn derive_phi_challenge(epoch: u64, node_id: &[u8; 32]) [u8; 16] { + // SHA256("TRI_PHI_CHALLENGE_V1" || epoch_le || node_id)[..16] +} + +pub fn verify_phi_response(challenge: &[u8; 16], response: &[u8; 4], node_id: &[u8; 32]) bool { + // gf16_dot4(challenge[..4], node_id[..4]) == response +} + +pub fn gf16_mul(a: u8, b: u8) u8 { + // GF(2^4) multiplication, reduction polynomial 0x3 (x^4 + x + 1) +} + +pub fn gf16_dot4(w: &[u8; 4], x: &[u8; 4]) [u8; 4] { + // Element-wise GF16 multiplication of 4-element vectors +} + +pub fn derive_phi_challenge_v2(epoch: u64, node_id: &[u8; 32]) [[u8; 16]; 16] { + // 16 rows, each = SHA256("TRI_PHI_CHALLENGE_V2" || epoch_le8 || node_id || row_index)[j*2] >> 4 +} + +pub fn compute_phi_response_v2(challenge: &[[u8; 16]; 16]) [u8; 32] { + // SHA256(pack(gf16_matmul(CHAMPION_WEIGHTS, challenge))) +} + +pub fn verify_phi_response_v2(challenge: &[[u8; 16]; 16], response: &[u8; 32]) bool { + // compute_phi_response_v2(challenge) == response +} + +pub fn gf16_matmul(a: &[[u8; 16]; 16], b: &[[u8; 16]; 16]) [[u8; 16]; 16] { + // 16x16 GF(2^4) matrix multiplication +} + +pub fn pack_gf16_matrix(m: &[[u8; 16]; 16]) [u8; 128] { + // Pack 16x16 nibble matrix into 128 bytes (2 nibbles per byte) +} + +pub fn merkle_root(leaves: &Vec<[u8; 32]>) [u8; 32] { + // Binary Merkle tree root computation +} + +pub fn verify_merkle(root: &[u8; 32], leaf: &[u8; 32], siblings: &Vec<[u8; 32]>, index: usize) bool { + // Merkle inclusion proof verification +} + +// ============================================================================ +// TDD — Tests +// ============================================================================ + +test "test_phi_challenge_deterministic" { + // derive_phi_challenge(e, id) == derive_phi_challenge(e, id) +} + +test "test_phi_challenge_epoch_unique" { + // derive_phi_challenge(e1, id) != derive_phi_challenge(e2, id) for e1 != e2 +} + +test "test_phi_challenge_node_unique" { + // derive_phi_challenge(e, id1) != derive_phi_challenge(e, id2) for id1 != id2 +} + +test "test_gf16_dot4_identity" { + // gf16_dot4([1,1,1,1], x) == x +} + +test "test_gf16_mul_commutative" { + // gf16_mul(a, b) == gf16_mul(b, a) for all a, b in GF(2^4) +} + +test "test_verify_phi_response_correct" { + // verify_phi_response returns true for correct gf16_dot4 result +} + +test "test_verify_phi_response_wrong_epoch" { + // verify_phi_response fails for response computed with wrong epoch +} + +test "test_merkle_single_leaf_roundtrip" { + // merkle_root([leaf]) -> verify_merkle(root, leaf, [], 0) == true +} + +test "test_merkle_four_leaves_roundtrip" { + // merkle_root([l0,l1,l2,l3]) -> verify_merkle for each leaf +} + +test "test_merkle_wrong_root_fails" { + // verify_merkle with wrong root returns false +} + +test "test_merkle_wrong_siblings_fails" { + // verify_merkle with wrong siblings returns false +} + +test "test_post_prove_valid_proof" { + // Full E2E: epoch-challenge -> compute phi_response -> sign -> POST /prove -> valid +} + +test "test_post_prove_invalid_merkle" { + // POST /prove with invalid merkle proof returns valid: false, reason: merkle_proof_invalid +} + +test "test_v2_derive_challenge_deterministic" { + // derive_phi_challenge_v2(epoch, node_id) is deterministic +} + +test "test_v2_response_correct" { + // compute_phi_response_v2(challenge) passes verify_phi_response_v2 +} + +test "test_v2_wrong_response_fails" { + // verify_phi_response_v2 rejects wrong 32-byte response +} + +test "test_v2_wrong_epoch_fails" { + // V2 verification fails for response computed with wrong epoch +} + +test "test_v2_wrong_node_fails" { + // V2 verification fails for response computed with wrong node_id +} + +test "test_post_prove_v2_valid" { + // Full E2E V2: version=2, 32-byte SHA256 response, valid proof +} + +test "test_post_prove_v2_wrong_response" { + // POST /prove version=2 with wrong phi_response returns phi_challenge_mismatch +} + +// ============================================================================ +// TDD — Invariants +// ============================================================================ + +invariant phi_challenge_deterministic { + // derive_phi_challenge(epoch, node_id) is deterministic for identical inputs + // Rationale: SHA256 is a deterministic PRF + @compileAssert(true); +} + +invariant phi_challenge_epoch_binding { + // derive_phi_challenge(e1, id) != derive_phi_challenge(e2, id) for e1 != e2 + // Rationale: epoch is mixed into SHA256 input, collisions are negligible + @compileAssert(true); +} + +invariant phi_challenge_node_binding { + // derive_phi_challenge(e, id1) != derive_phi_challenge(e, id2) for id1 != id2 + // Rationale: node_id is mixed into SHA256 input, collisions are negligible + @compileAssert(true); +} + +invariant gf16_mul_field_closure { + // gf16_mul(a, b) in [0..15] for all a, b in [0..15] + // Rationale: GF(2^4) multiplication is closed + @compileAssert(true); +} + +invariant gf16_mul_commutative { + // gf16_mul(a, b) == gf16_mul(b, a) + // Rationale: Finite field multiplication is commutative + @compileAssert(true); +} + +invariant gf16_dot4_elementwise { + // gf16_dot4(w, x)[i] == gf16_mul(w[i], x[i]) for i in [0..3] + // Rationale: dot4 is element-wise multiplication + @compileAssert(true); +} + +invariant merkle_root_empty_is_zero { + // merkle_root([]) == [0u8; 32] + // Rationale: Empty tree has zero root + @compileAssert(true); +} + +invariant merkle_verify_single_leaf_no_siblings { + // verify_merkle(root, leaf, [], 0) for single-leaf tree + // Rationale: Single leaf is its own root, no siblings needed + @compileAssert(true); +} + +invariant merkle_verify_tamper_detection { + // verify_merkle returns false for any tampered root, leaf, or siblings + // Rationale: SHA256 collision resistance ensures tamper detection + @compileAssert(true); +} + +invariant prove_requires_valid_phi_challenge { + // POST /prove rejects if phi_response != gf16_dot4(challenge[..4], node_id[..4]) + // V2: rejects if SHA256(pack(gf16_matmul(CHAMPION_WEIGHTS, challenge))) != response + // Rationale: Layer 1 of PoUC must be enforced + @compileAssert(true); +} + +invariant v2_response_is_sha256_of_matmul { + // V2 response = SHA256(pack(gf16_matmul(CHAMPION_WEIGHTS, challenge))) + // Rationale: Forces full 16x16 matrix multiply, 2^256 security + @compileAssert(true); +} + +invariant v2_champion_weights_derived_from_seed { + // CHAMPION_WEIGHTS[i][j] = SHA256("TRI_PHI_CHAMPION_SEED_V1" || i_le8)[j*2] >> 4 + // Rationale: Deterministic, publicly verifiable bootstrap + @compileAssert(true); +} + +invariant prove_requires_valid_merkle { + // POST /prove rejects if merkle proof verification fails + // Rationale: Layer 2 of PoUC must be enforced + @compileAssert(true); +} + +// ============================================================================ +// TDD — Benchmarks +// ============================================================================ + +bench "bench_derive_phi_challenge" { + // Measure: SHA256 challenge derivation latency + // Target: < 500ns on commodity CPU + @setEvalBranchQuota(10000); + var node_id = [1u8; 32]; + _ = derive_phi_challenge(1, &node_id); +} + +bench "bench_gf16_dot4" { + // Measure: GF16 dot product of 4-element vectors + // Target: < 20ns on commodity CPU, < 5 cycles on FPGA + @setEvalBranchQuota(10000); + var w = [1u8, 2, 3, 4]; + var x = [5u8, 6, 7, 8]; + _ = gf16_dot4(&w, &x); +} + +bench "bench_merkle_verify_8_leaves" { + // Measure: Merkle proof verification for 8-leaf tree + // Target: < 2us on commodity CPU + @setEvalBranchQuota(10000); +} diff --git a/apps/website/public/t27/specs/enrichment/audio_overview.t27 b/apps/website/public/t27/specs/enrichment/audio_overview.t27 new file mode 100644 index 0000000000..9e7739836e --- /dev/null +++ b/apps/website/public/t27/specs/enrichment/audio_overview.t27 @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: Apache-2.0 +// audio_overview.t27 — Bilingual Audio Overview for NotebookLM +// Ring 091 — API-only multilingual enrichment +// phi^2 + 1/phi^2 = 3 | TRINITY + +module enrichment::audio_overview; + +// ============================================================================ +// 1. Constants +// ============================================================================ + +const API_CREATE_ENDPOINT : str = "/v1alpha/projects/PROJECT/locations/LOCATION/notebooks/NOTEBOOK_ID/audioOverviews"; +const API_DELETE_ENDPOINT : str = "/v1alpha/projects/PROJECT/locations/LOCATION/notebooks/NOTEBOOK_ID/audioOverviews/default"; +const API_POLL_ENDPOINT : str = "/v1alpha/projects/PROJECT/locations/LOCATION/notebooks/NOTEBOOK_ID/audioOverviews/default"; +const AUDIO_DIR : str = ".trinity/audio"; +const POLL_INTERVAL_MS : u64 = 30000; // 30 seconds +const POLL_TIMEOUT_MS : u64 = 600000; // 10 minutes + +// ============================================================================ +// 2. Types +// ============================================================================ + +enum Lang { + Ru, + En, +} + +enum AudioStatus { + InProgress, + Completed, + Failed, +} + +struct AudioOverviewRequest { + notebook_id : str, + language_code : Lang, + episode_focus : str, + source_ids : Option<[0]str>, // None = all sources +} + +struct AudioOverviewResponse { + status : str, + audio_overview_id : str, + name : str, +} + +struct AudioOverviewStatus { + status : AudioStatus, + audio_overview_id : str, + name : str, +} + +struct AudioFile { + notebook_id : str, + lang : Lang, + path : str, + duration_secs : u64, +} + +struct AudioReport { + notebooks_processed : u32, + notebooks_success : u32, + notebooks_failed : u32, + notebooks_skipped : u32, + total_duration_secs : u64, + errors : [0]str, +} + +// ============================================================================ +// 3. HTTP Client Functions +// ============================================================================ + +// create_audio_overview(notebook_id: str, config: AudioOverviewRequest) -> AudioOverviewStatus +// POST /notebooks/ID/audioOverviews to create audio overview +// Returns status, audio_overview_id, and name from response +pub fn create_audio_overview(notebook_id : str, config : AudioOverviewRequest) -> AudioOverviewStatus; + +// poll_audio_status(nb_id: str, timeout_ms: u64) -> AudioOverviewStatus +// GET /notebooks/ID/audioOverviews/default to poll generation status +// Returns when audio_overview is completed +pub fn poll_audio_status(nb_id : str, timeout_ms : u64) -> AudioOverviewStatus; + +// delete_default_audio(nb_id: str) -> () +// DELETE /notebooks/ID/audioOverviews/default to clear default audio +pub fn delete_default_audio(nb_id : str) -> (); + +// ============================================================================ +// 4. Audio File Operations +// ============================================================================ + +// ensure_audio_dir() -> () +// Create .trinity/audio/ directory if not exists +pub fn ensure_audio_dir() -> (); + +// save_audio_file(notebook_id: str, lang: Lang, content: [u8]) -> AudioFile +// Save generated audio content to .trinity/audio/{ID}/{lang}.wav +// Returns file info including duration +pub fn save_audio_file(notebook_id : str, lang : Lang, content : [u8]) -> AudioFile; + +// ============================================================================ +// 5. Orchestrator Functions +// ============================================================================ + +// generate_bilingual_audio(notebook_id: str, title: str) -> (AudioFile, AudioFile) +// Generate audio for both languages (EN and RU) +// Returns tuple of (english_file, russian_file) +pub fn generate_bilingual_audio(notebook_id : str, title : str) -> (AudioFile, AudioFile); + +// generate_all(notebooks: Vec, workers: usize, token: str) -> AudioReport +// Main orchestrator: process all notebooks in parallel +// Returns report with success/failure counts, total duration, and error messages +pub fn generate_all(notebooks : [0]str, workers : usize, token: str) -> AudioReport; + +// ============================================================================ +// 6. TDD — Tests +// ============================================================================ + +test "lang_to_code_ru" { + assert @langToCode("ru") == "ru"; + assert @langToCode("en") == "en"; +} + +test "lang_to_code_invalid" { + assert @langToCode("fr") == ""; + assert @langToCode("english") == ""; +} + +test "lang_from_code_ru" { + assert @langFromCode("ru") == Ru; + assert @langFromCode("en") == En; +} + +test "bilingual_filename" { + let file = @concat(".trinity/audio/nb123/", @langFilename(Ru)); + assert @contains(file, "/ru.wav"); + assert !@contains(file, "/en.wav"); +} + +// ============================================================================ +// 7. TDD — Invariants +// ============================================================================ + +invariant "lang_code_valid" { + forall lang : str where @langToCode(lang) != "", + @langFromCode(@langToCode(lang)) == lang +} + +invariant "audio_files_have_duration" { + forall file : AudioFile where @len(file.content) > 0, + file.duration_secs > 0 +} + +// ============================================================================ +// 8. TDD — Benchmarks +// ============================================================================ + +bench "http_request_latency" { + // Measure: milliseconds for HTTP POST to create audio overview + // Target: < 5000ms + measure: milliseconds to call create_audio_overview("nb123", { language_code: "en" }) + target: < 5000ms +} diff --git a/apps/website/public/t27/specs/enrichment/youtube_transcript.t27 b/apps/website/public/t27/specs/enrichment/youtube_transcript.t27 new file mode 100644 index 0000000000..b2b902db72 --- /dev/null +++ b/apps/website/public/t27/specs/enrichment/youtube_transcript.t27 @@ -0,0 +1,605 @@ +// SPDX-License-Identifier: Apache-2.0 +// youtube_transcript.t27 — YouTube Transcript Extraction for NotebookLM Enrichment +// Ring 090 — Fallback for blocked YouTube URL uploads +// phi^2 + 1/phi^2 = 3 | TRINITY + +module enrichment::youtube_transcript; + +// ============================================================================ +// 1. Constants +// ============================================================================ + +const VERSION : u32 = 1; +const MAX_TRANSCRIPT_SIZE : u32 = 10 * 1024 * 1024; // 10MB +const YOUTUBE_TIMEOUT_SECONDS : u32 = 60; +const YTDLP_CHECK_TIMEOUT_SECONDS : u32 = 10; + +// ============================================================================ +// 2. Error Codes +// ============================================================================ + +enum ErrorCode { + Success = 0, + YtDlpNotFound = 1, + VideoUnavailable = 2, + NoSubtitles = 3, + TranscriptTooLarge = 4, + InvalidYouTubeUrl = 5, + TranscriptTimeout = 6, + EncodingError = 7, + ApiAuthFailed = 8, + NetworkError = 9, + UnknownError = 99, +} + +// ============================================================================ +// 3. Structs +// ============================================================================ + +pub struct YouTubeSource { + url : str, + video_id : Option, + title : str, +} + +pub struct Transcript { + video_id : str, + title : str, + text : str, + lang : str, + size_bytes : u32, +} + +pub struct EnrichmentReport { + sources_added : u32, + transcripts_added : u32, + transcripts_failed : u32, + errors : [0]str, +} + +// ============================================================================ +// 4. Constants — YouTube Domains +// ============================================================================ + +const YOUTUBE_DOMAINS : [5]str = [ + "youtube.com", + "www.youtube.com", + "m.youtube.com", + "music.youtube.com", + "youtu.be", +]; + +// ============================================================================ +// 5. URL Detection Functions +// ============================================================================ + +// is_youtube_url(url: str) -> bool +// Check if URL is a YouTube URL +pub fn is_youtube_url(url : str) -> bool { + let trimmed = @trim(url); + let lower = @toLower(trimmed); + + for (domain in YOUTUBE_DOMAINS) { + if (@contains(lower, domain)) { + return true; + } + } + return false; +} + +// extract_video_id(url: str) -> Option +// Extract YouTube video ID from URL +// Handles: youtu.be/VIDEO_ID, youtube.com/watch?v=VIDEO_ID, shorts/, embed/, live/, v/ +pub fn extract_video_id(url : str) -> Option { + let trimmed = @trim(url); + + // youtu.be short URLs + if (@startsWith(trimmed, "youtu.be/")) { + let path = @substring(trimmed, 9, @len(trimmed)); + let parts = @split(path, "/"); + if (@len(parts) > 0) { + return @some(parts[0]); + } + } + + // youtube.com path-based formats + if (@contains(trimmed, "youtube.com/")) { + let path_start = @indexOf(trimmed, "youtube.com/") + 11; + let path = @substring(trimmed, path_start, @len(trimmed)); + let segments = @split(@trim(path, "/"); + + // Handle shorts/, embed/, live/, v/ prefixes + if (@len(segments) >= 2) { + let first = segments[0]; + if (first == "shorts" || first == "embed" || first == "live" || first == "v") { + if (@len(segments) >= 2) { + return @some(segments[1]); + } + } + } + } + + // Query param ?v=VIDEO_ID + let v_param = @indexOf(trimmed, "v="); + if (v_param != -1) { + let amp_idx = @indexOf(trimmed, "&", v_param); + if (amp_idx == -1) { + return @some(@substring(trimmed, v_param + 2, @len(trimmed))); + } else { + return @some(@substring(trimmed, v_param + 2, amp_idx)); + } + } + + return none; +} + +// ============================================================================ +// 6. SRT Parsing Functions +// ============================================================================ + +// srt_to_text(srt_content: str) -> str +// Convert SRT subtitle format to plain text +// Filters out timestamps, line numbers, keeps only subtitle text +pub fn srt_to_text(srt_content : str) -> str { + let lines = @split(srt_content, "\n"); + var result_lines : [0]str = undefined; + var in_text_block : bool = false; + var was_empty_line : bool = true; + + for (line in lines) { + let trimmed = @trim(line); + + // Skip timestamps (contain -->) + if (@contains(trimmed, "-->")) { + in_text_block = false; + continue; + } + + // Skip line numbers (standalone digits) + var is_line_number : bool = false; + var i : u32 = 0; + while (i < @len(trimmed)) { + let ch = @charAt(trimmed, i); + if (ch >= '0' && ch <= '9') { + i += 1; + } else { + is_line_number = true; + i = @len(trimmed); + } + } + if (!is_line_number && @len(trimmed) > 0) { + if (!in_text_block && was_empty_line) { + in_text_block = true; + } + if (in_text_block && @len(trimmed) > 0) { + result_lines = @push(result_lines, trimmed); + } + } + + was_empty_line = @len(trimmed) == 0; + } + + return @join(result_lines, "\n"); +} + +// ============================================================================ +// 7. Transcript Extraction Functions +// ============================================================================ + +// extract_transcript(video_url: str) -> (Transcript, ErrorCode) +// Extract transcript using yt-dlp subprocess +// Returns (transcript, error_code) tuple +pub fn extract_transcript(video_url : str) -> (Transcript, ErrorCode) { + var error_result : Transcript = undefined; + + // First extract video ID for identification + let video_id_opt = extract_video_id(video_url); + let video_id : str = if (video_id_opt != none) { + @unwrap(video_id_opt) + } else { + "unknown" + }; + + // Run yt-dlp --version to check availability (timeout 10s) + let ytdlp_check = @subprocessRun(["yt-dlp", "--version"], YTDLP_CHECK_TIMEOUT_SECONDS); + if (ytdlp_check.status != 0) { + error_result = Transcript{ + .video_id = video_id, + .title = "", + .text = "", + .lang = "", + .size_bytes = 0, + }; + return (error_result, ErrorCode::YtDlpNotFound); + } + + // Create temp directory for SRT files + let temp_dir_result = @tempDirCreate(); + if (temp_dir_result.error != "") { + error_result = Transcript{ + .video_id = video_id, + .title = "", + .text = "", + .lang = "", + .size_bytes = 0, + }; + return (error_result, ErrorCode::UnknownError); + } + let temp_dir = temp_dir_result.path; + + // Build yt-dlp command + // Skip download, write subtitles, auto-subs, English only, SRT format + let output_template = @concat(temp_dir, "/%(title)s.%(ext)s"); + + let cmd_parts : [8]str = [ + "yt-dlp", + "--no-update", + "--skip-download", + "--write-subs", + "--write-auto-subs", + "--sub-langs", "en", + "--sub-format", "srt", + "-o", output_template, + video_url, + ]; + + let result = @subprocessRun(cmd_parts, YOUTUBE_TIMEOUT_SECONDS); + + // Check for errors in stderr + let stderr = result.stderr; + let stderr_lower = @toLower(stderr); + + if (result.status != 0) { + if (@contains(stderr_lower, "unavailable") || @contains(stderr_lower, "video has been removed")) { + error_result = Transcript{ + .video_id = video_id, + .title = "", + .text = "", + .lang = "", + .size_bytes = 0, + }; + return (error_result, ErrorCode::VideoUnavailable); + } + if (@contains(stderr_lower, "subtitles") || @contains(stderr_lower, "no subtitles available")) { + error_result = Transcript{ + .video_id = video_id, + .title = "", + .text = "", + .lang = "", + .size_bytes = 0, + }; + return (error_result, ErrorCode::NoSubtitles); + } + // Other error + error_result = Transcript{ + .video_id = video_id, + .title = "", + .text = "", + .lang = "", + .size_bytes = 0, + }; + return (error_result, ErrorCode::UnknownError); + } + + // Find SRT files in temp directory + let srt_files_result = @glob(temp_dir, "*.srt"); + if (@len(srt_files_result.files) == 0) { + error_result = Transcript{ + .video_id = video_id, + .title = "", + .text = "", + .lang = "", + .size_bytes = 0, + }; + return (error_result, ErrorCode::NoSubtitles); + } + + // Read the first SRT file + let srt_path = srt_files_result.files[0]; + let srt_content_result = @readFile(srt_path); + + if (srt_content_result.error != "") { + error_result = Transcript{ + .video_id = video_id, + .title = "", + .text = "", + .lang = "", + .size_bytes = 0, + }; + return (error_result, ErrorCode::EncodingError); + } + + let srt_content = srt_content_result.content; + + // Convert SRT to plain text + let transcript_text = srt_to_text(srt_content); + + // Check if transcript is empty + if (@len(@trim(transcript_text)) == 0) { + error_result = Transcript{ + .video_id = video_id, + .title = "", + .text = "", + .lang = "", + .size_bytes = 0, + }; + return (error_result, ErrorCode::NoSubtitles); + } + + // Check size limit + let size_bytes = @len(transcript_text); + if (size_bytes > MAX_TRANSCRIPT_SIZE) { + error_result = Transcript{ + .video_id = video_id, + .title = "", + .text = "", + .lang = "", + .size_bytes = 0, + }; + return (error_result, ErrorCode::TranscriptTooLarge); + } + + // Extract title from filename (stem) + let title_parts = @split(srt_path, "/"); + let filename = title_parts[@len(title_parts) - 1]; + let title = @substring(filename, 0, @len(filename) - 4); // Remove .srt + + let transcript = Transcript{ + .video_id = video_id, + .title = title, + .text = transcript_text, + .lang = "en", + .size_bytes = size_bytes, + }; + + return (transcript, ErrorCode::Success); +} + +// ============================================================================ +// 8. NotebookLM Upload Functions +// ============================================================================ + +// upload_transcript(notebook_id: str, transcript: Transcript, api_token: str) -> ErrorCode +// Upload transcript to NotebookLM via Discovery Engine REST API +pub fn upload_transcript( + notebook_id : str, + transcript : Transcript, + api_token : str +) -> ErrorCode { + // Build REST API URL + let url = @concat("https://discoveryengine.googleapis.com/v1alpha/", notebook_id, ":addSource"); + + // Build request body with base64 encoded content + let encoded_content = @base64Encode(transcript.text); + let display_title = @concat(" ", transcript.title, " (transcript)"); // Note: emoji handling + + let body_json = @concat( + "{\"rawContent\":\"", + encoded_content, + "\",\"mimeType\":\"text/plain\",\"title\":\"", + display_title, + "\"}" + ); + + // Send HTTP POST request + let http_result = @httpPost( + url, + body_json, + @concat("Bearer ", api_token) + ); + + // Check response + if (http_result.status_code == 401 || http_result.status_code == 403) { + return ErrorCode::ApiAuthFailed; + } + if (http_result.status_code < 200 || http_result.status_code >= 300) { + return ErrorCode::NetworkError; + } + + return ErrorCode::Success; +} + +// ============================================================================ +// 9. Enrichment Orchestration +// ============================================================================ + +// enrich_with_transcripts(notebook_id: str, sources: [0]str, api_token: str) -> EnrichmentReport +// Enrich notebook with YouTube transcripts +// Iterates through sources, extracts and uploads transcripts for YouTube URLs +pub fn enrich_with_transcripts( + notebook_id : str, + sources : [0]str, + api_token : str +) -> EnrichmentReport { + var report : EnrichmentReport = { + .sources_added = 0, + .transcripts_added = 0, + .transcripts_failed = 0, + .errors = undefined, + }; + + var error_list : [0]str = undefined; + + for (url in sources) { + // Check if URL is YouTube + if (!is_youtube_url(url)) { + continue; // Skip non-YouTube URLs + } + + // Extract transcript + let (transcript, error) = extract_transcript(url); + + if (error != ErrorCode::Success) { + // Extract failed + report.transcripts_failed += 1; + let error_msg = if (error == ErrorCode::VideoUnavailable) { + @concat("Video unavailable: ", url) + } else if (error == ErrorCode::NoSubtitles) { + @concat("No subtitles: ", url) + } else if (error == ErrorCode::YtDlpNotFound) { + "yt-dlp not found" + } else if (error == ErrorCode::TranscriptTooLarge) { + @concat("Transcript too large: ", url) + } else if (error == ErrorCode::TranscriptTimeout) { + @concat("Timeout: ", url) + } else { + @concat("Unknown error: ", url) + }; + error_list = @push(error_list, error_msg); + continue; + } + + // Upload transcript + let upload_error = upload_transcript(notebook_id, transcript, api_token); + + if (upload_error != ErrorCode::Success) { + report.transcripts_failed += 1; + let error_msg = if (upload_error == ErrorCode::ApiAuthFailed) { + @concat("API auth failed: ", transcript.title) + } else if (upload_error == ErrorCode::NetworkError) { + @concat("Network error: ", transcript.title) + } else { + @concat("Upload failed: ", transcript.title) + }; + error_list = @push(error_list, error_msg); + continue; + } + + // Success + report.transcripts_added += 1; + } + + report.errors = error_list; + return report; +} + +// ============================================================================ +// 10. TDD — Tests +// ============================================================================ + +test is_youtube_url_standard + assert is_youtube_url("https://youtube.com/watch?v=abc123") == true; + assert is_youtube_url("https://www.youtube.com/watch?v=abc123") == true; + assert is_youtube_url("https://m.youtube.com/watch?v=abc123") == true; + assert is_youtube_url("https://example.com") == false; + assert is_youtube_url("https://vimeo.com/watch?v=abc123") == false; + +test is_youtube_url_short + assert is_youtube_url("https://youtu.be/abc123") == true; + assert is_youtube_url("https://youtu.be/abc123") == true; + assert is_youtube_url("https://bit.ly/abc123") == false; + +test extract_video_id_standard + assert extract_video_id("https://youtube.com/watch?v=abc123") == @some("abc123"); + assert extract_video_id("https://www.youtube.com/watch?v=abc123&feature=share") == @some("abc123"); + +test extract_video_id_short + assert extract_video_id("https://youtu.be/abc123") == @some("abc123"); + assert extract_video_id("https://youtu.be/abc123") == @some("abc123"); + +test extract_video_id_shorts + assert extract_video_id("https://youtube.com/shorts/abc123") == @some("abc123"); + assert extract_video_id("https://www.youtube.com/shorts/abc123") == @some("abc123"); + +test extract_video_id_embed + assert extract_video_id("https://youtube.com/embed/abc123") == @some("abc123"); + +test extract_video_id_live + assert extract_video_id("https://youtube.com/live/abc123") == @some("abc123"); + +test extract_video_id_v_param + assert extract_video_id("https://youtube.com/watch?v=abc123") == @some("abc123"); + assert extract_video_id("https://youtube.com/v/abc123") == @some("abc123"); + +test extract_video_id_invalid + assert extract_video_id("https://example.com/watch?v=abc123") == none; + assert extract_video_id("https://youtube.com") == none; + +test srt_to_text_filters_timestamps + let srt = "1\n00:00:00 --> 00:00:05\nHello world\n\n2\n00:00:05 --> 00:00:10\nTest"; + let result = srt_to_text(srt); + assert @contains(result, "Hello world"); + assert @contains(result, "Test"); + assert !@contains(result, "-->"); + assert !@contains(result, "00:00:00"); + +test srt_to_text_filters_line_numbers + let srt = "1\n00:00:00 --> 00:00:05\nHello\n2\n00:00:05 --> 00:00:10\nWorld"; + let result = srt_to_text(srt); + assert @contains(result, "Hello"); + assert @contains(result, "World"); + assert !@contains(result, "\n1\n"); + assert !@contains(result, "\n2\n"); + +test srt_to_text_empty_lines + let srt = "\n1\n00:00:00 --> 00:00:05\n\n\nHello\n\n2\n00:00:05 --> 00:00:10\n\n\nWorld\n\n"; + let result = srt_to_text(srt); + assert result == "Hello\nWorld"; + +test srt_to_text_preserves_content + let srt = "1\n00:00:00 --> 00:00:10\nThis is a test subtitle\n2\n00:00:10 --> 00:00:15\nWith multiple lines"; + let result = srt_to_text(srt); + assert @contains(result, "This is a test subtitle"); + assert @contains(result, "With multiple lines"); + +// ============================================================================ +// 11. TDD — Invariants +// ============================================================================ + +invariant transcript_size_limit + // All transcripts must respect MAX_TRANSCRIPT_SIZE + // Note: Cannot verify for all possible transcripts in pure spec + // Enforced in extract_transcript() + assert MAX_TRANSCRIPT_SIZE > 0; + +invariant youtube_domains_non_empty + // YOUTUBE_DOMAINS must contain valid domains + assert @len(YOUTUBE_DOMAINS) > 0; + +invariant error_codes_positive + // All error codes except Success must be positive + assert ErrorCode::Success == 0; + assert ErrorCode::YtDlpNotFound > 0; + assert ErrorCode::VideoUnavailable > 0; + assert ErrorCode::NoSubtitles > 0; + assert ErrorCode::TranscriptTooLarge > 0; + assert ErrorCode::InvalidYouTubeUrl > 0; + assert ErrorCode::TranscriptTimeout > 0; + assert ErrorCode::EncodingError > 0; + assert ErrorCode::ApiAuthFailed > 0; + assert ErrorCode::NetworkError > 0; + assert ErrorCode::UnknownError > 0; + +invariant enrichment_report_fields_initialized + // EnrichmentReport fields start at 0 for successful case + var report : EnrichmentReport = { + .sources_added = 0, + .transcripts_added = 0, + .transcripts_failed = 0, + .errors = undefined, + }; + assert report.sources_added == 0; + assert report.transcripts_added == 0; + assert report.transcripts_failed == 0; + +// ============================================================================ +// 12. TDD — Benchmarks +// ============================================================================ + +bench is_youtube_url_latency + // Measure: nanoseconds to check URL + // Target: < 1000ns + measure: nanoseconds to compute is_youtube_url("https://youtube.com/watch?v=abc123") + target: < 1000ns + +bench extract_video_id_latency + // Measure: nanoseconds to extract video ID + // Target: < 500ns + measure: nanoseconds to compute extract_video_id("https://youtube.com/watch?v=abc123") + target: < 500ns + +bench srt_to_text_latency + // Measure: nanoseconds to convert SRT to text + // Target: < 2000ns for typical SRT + measure: nanoseconds to compute srt_to_text("1\n00:00:00 --> 00:00:05\nHello world\n2\n00:00:05 --> 00:00:10\nTest") + target: < 2000ns diff --git a/apps/website/public/t27/specs/file/operations.t27 b/apps/website/public/t27/specs/file/operations.t27 new file mode 100644 index 0000000000..ab9f38a610 --- /dev/null +++ b/apps/website/public/t27/specs/file/operations.t27 @@ -0,0 +1,445 @@ +// specs/file/operations.t27 +// File Operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module FileOperations { + use base::types; + use file::schema; + + // ==================================================================== + // Read Operations + // ==================================================================== + + // read reads a file's contents + fn read(path: str, includeDiff: bool) -> Result { + // Implementation: Read file and return content + } + + // read_range reads a range of lines from a file + fn read_range(path: str, startLine: u32, endLine: u32) -> Result { + // Implementation: Read specific line range + } + + // read_binary reads binary file content + fn read_binary(path: str) -> Result<[u8], FileError> { + // Implementation: Read binary file + } + + // exists checks if a file exists + fn exists(path: str) -> Result { + // Implementation: Check file existence + } + + // stat returns file information + fn stat(path: str) -> Result { + // Implementation: Get file metadata + } + + // ==================================================================== + // Write Operations + // ==================================================================== + + // write writes content to a file + fn write(path: str, content: str) -> Result { + // Implementation: Write content to file + } + + // write_binary writes binary content to a file + fn write_binary(path: str, data: [u8]) -> Result { + // Implementation: Write binary data to file + } + + // append appends content to a file + fn append(path: str, content: str) -> Result { + // Implementation: Append content to file + } + + // edit edits a file with replacements + fn edit(path: str, oldText: str, newText: str) -> Result { + // Implementation: Replace text in file + } + + // edit_all edits all occurrences in a file + fn edit_all(path: str, oldText: str, newText: str) -> Result { + // Implementation: Replace all occurrences, return count + } + + // insert inserts text at a line number + fn insert(path: str, line: u32, content: str) -> Result { + // Implementation: Insert content at line + } + + // delete_lines removes a line range from a file + fn delete_lines(path: str, startLine: u32, endLine: u32) -> Result { + // Implementation: Delete line range + } + + // ==================================================================== + // Directory Operations + // ==================================================================== + + // list lists contents of a directory + fn list(path: str) -> Result<[FileNode], FileError> { + // Implementation: List directory contents + } + + // list_recursive lists directory contents recursively + fn list_recursive(path: str, maxDepth: u32) -> Result<[FileNode], FileError> { + // Implementation: List directory recursively + } + + // create_dir creates a directory + fn create_dir(path: str, recursive: bool) -> Result { + // Implementation: Create directory + } + + // delete_dir deletes a directory + fn delete_dir(path: str, recursive: bool) -> Result { + // Implementation: Delete directory + } + + // move_path moves a file or directory + fn move_path(from: str, to: str) -> Result { + // Implementation: Move file/directory + } + + // copy copies a file + fn copy(from: str, to: str) -> Result { + // Implementation: Copy file + } + + // delete deletes a file + fn delete(path: str) -> Result { + // Implementation: Delete file + } + + // ==================================================================== + // Ignore Operations + // ==================================================================== + + // is_ignored checks if a path is ignored + fn is_ignored(path: str, rules: IgnoreRules) -> bool { + // Implementation: Check if path matches ignore rules + } + + // load_ignore_rules loads ignore rules from .gitignore/.ignore files + fn load_ignore_rules(root: str) -> Result { + // Implementation: Load and parse ignore files + } + + // add_ignore_pattern adds a pattern to ignore rules + fn add_ignore_pattern(rules: IgnoreRules, pattern: str) -> IgnoreRules { + // Implementation: Add ignore pattern + } + + // ==================================================================== + // Path Operations + // ==================================================================== + + // normalize normalizes a file path + fn normalize(path: str) -> str { + // Implementation: Normalize path separators and resolve . and .. + } + + // join joins path components + fn join(parts: [str]) -> str { + // Implementation: Join path parts + } + + // dirname returns the directory name of a path + fn dirname(path: str) -> str { + // Implementation: Get directory name + } + + // basename returns the file name of a path + fn basename(path: str) -> str { + // Implementation: Get file name + } + + // extname returns the file extension + fn extname(path: str) -> str { + // Implementation: Get file extension + } + + // relative returns relative path from base to path + fn relative(from: str, to: str) -> Result { + // Implementation: Get relative path + } + + // absolute returns absolute path + fn absolute(path: str) -> Result { + // Implementation: Get absolute path + } + + // resolve resolves a path to absolute form + fn resolve(base: str, path: str) -> str { + // Implementation: Resolve path relative to base + } + + // ==================================================================== + // Content Operations + // ==================================================================== + + // detect_type detects content type + fn detect_type(path: str, content: str) -> ContentType { + // Implementation: Detect if text, binary, or image + } + + // count_lines counts lines in content + fn count_lines(content: str) -> u32 { + // Implementation: Count line breaks + } + + // truncate truncates content to max length + fn truncate(content: str, maxLength: u32) -> str { + // Implementation: Truncate and add ellipsis + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "file_type_values" { + assert(FileType::File as u32 == 0); + assert(FileType::Directory as u32 == 1); + assert(FileType::Symlink as u32 == 2); + } + + test "file_status_values" { + assert(FileStatus::Added as u32 == 0); + assert(FileStatus::Deleted as u32 == 1); + assert(FileStatus::Modified as u32 == 2); + } + + test "content_type_values" { + assert(ContentType::Text as u32 == 0); + assert(ContentType::Binary as u32 == 1); + assert(ContentType::Image as u32 == 2); + } + + test "file_error_values" { + assert(FileError::NotFound as u32 == 0); + assert(FileError::PermissionDenied as u32 == 1); + assert(FileError::IsDirectory as u32 == 2); + assert(FileError::NotDirectory as u32 == 3); + assert(FileError::AccessDenied as u32 == 4); + assert(FileError::InvalidPath as u32 == 5); + assert(FileError::ReadError as u32 == 6); + assert(FileError::WriteError as u32 == 7); + assert(FileError::DeleteError as u32 == 8); + } + + test "file_info_creation" { + var info = FileInfo { + path = "test.txt", + fileType = FileType::File, + size = 100, + modified = 1234567890, + permissions = 644, + isHidden = false, + isIgnored = false, + }; + assert(info.path == "test.txt"); + assert(info.fileType == FileType::File); + } + + test "file_content_text" { + var content = FileContent { + type = ContentType::Text, + content = "hello", + diff = null, + mimeType = "text/plain", + encoding = null, + lineCount = 1, + }; + assert(is_text(content.type)); + } + + test "file_node_creation" { + var node = FileNode { + name = "dir", + path = "dir", + absolute = "/home/dir", + fileType = FileType::Directory, + ignored = false, + children = null, + }; + assert(node.fileType == FileType::Directory); + } + + test "file_change_creation" { + var change = FileChange { + path = "test.txt", + status = FileStatus::Modified, + additions = 1, + deletions = 0, + }; + assert(change.status == FileStatus::Modified); + } + + test "ignore_pattern_creation" { + var pattern = IgnorePattern { + pattern = "*.log", + isDir = false, + }; + assert(pattern.pattern == "*.log"); + } + + test "ignore_rules_creation" { + var patterns = [ + IgnorePattern { + pattern = "node_modules", + isDir = true, + }, + ]; + var rules = IgnoreRules { + patterns = patterns, + whitelists: [], + }; + assert(rules.patterns.len == 1); + } + + test "search_match_creation" { + var match = SearchMatch { + path = "test.txt", + line = 1, + content = "test", + start = 0, + end = 4, + }; + assert(match.path == "test.txt"); + } + + test "search_options_creation" { + var options = SearchOptions { + query = "pattern", + pattern = "*.ts", + caseSensitive = true, + maxResults = 50, + includeHidden = false, + fileType = FileType::File, + }; + assert(options.caseSensitive); + assert(options.maxResults == 50); + } + + test "constants_values" { + assert(MAX_FILE_SIZE == 10485760); + assert(MAX_SEARCH_RESULTS == 1000); + assert(DEFAULT_READ_CHUNK == 8192); + } + + test "file_content_text" { + var content = FileContent { + type = ContentType::Text, + content = "hello", + diff = null, + mimeType = "text/plain", + encoding = null, + lineCount = 1, + }; + assert(is_text(content.type)); + } + + test "file_change_creation" { + var change = FileChange { + path = "test.txt", + status = FileStatus::Modified, + additions = 1, + deletions = 0, + }; + assert(change.status == FileStatus::Modified); + } + + test "watcher_handle_creation" { + var handle = WatcherHandle { + id = "watch-1", + paths = ["/home/project"], + active = true, + }; + assert(handle.active); + } + + test "submatch_creation" { + var submatch = Submatch { + match = "match", + start = 0, + end = 5, + }; + assert(submatch.match == "match"); + } + + test "ripgrep_match_creation" { + var submatches = [ + Submatch { + match = "test", + start = 0, + end = 4, + }, + ]; + var match = RipgrepMatch { + path = "file.ts", + lineNumber = 1, + content = "test line", + submatches = submatches, + }; + assert(match.path == "file.ts"); + } + + test "ripgrep_options_creation" { + var globs = ["*.ts"]; + var options = RipgrepOptions { + glob = globs, + hidden = true, + follow = false, + maxDepth = 5, + limit = 10, + }; + assert(options.glob?.len == 1); + } + + test "content_type_values" { + assert(ContentType::Text as u32 == 0); + assert(ContentType::Binary as u32 == 1); + assert(ContentType::Image as u32 == 2); + } + + test "file_status_values" { + assert(FileStatus::Added as u32 == 0); + assert(FileStatus::Deleted as u32 == 1); + assert(FileStatus::Modified as u32 == 2); + } + + test "is_text_true" { + assert(is_text(ContentType::Text)); + } + + test "is_binary_true" { + assert(is_binary(ContentType::Binary)); + } + + test "is_image_true" { + assert(is_image(ContentType::Image)); + } + + test "is_hidden_true" { + assert(is_hidden(".git")); + } + + test "is_hidden_false" { + assert(!is_hidden("visible")); + } + + test "get_extension_simple" { + assert(get_extension("test.txt") == "txt"); + } + + test "watch_event_creation" { + var event = WatchEvent { + path = "test.txt", + eventType = WatchEventType::Change, + timestamp = 1234567890, + }; + assert(event.eventType == WatchEventType::Change); + } +} diff --git a/apps/website/public/t27/specs/file/schema.t27 b/apps/website/public/t27/specs/file/schema.t27 new file mode 100644 index 0000000000..1f8b14832e --- /dev/null +++ b/apps/website/public/t27/specs/file/schema.t27 @@ -0,0 +1,333 @@ +// specs/file/schema.t27 +// File Types Specification +// phi^2 + 1/phi^2 = 3 | TRINITY + +module File { + use base::types; + + // ==================================================================== + // File Type + // ==================================================================== + + // FileType represents the type of a file system entry + enum FileType { + File = 0, // Regular file + Directory = 1, // Directory + Symlink = 2, // Symbolic link + } + + // ==================================================================== + // File Status + // ==================================================================== + + // FileStatus represents the status of a file change + enum FileStatus { + Added = 0, // File was added + Deleted = 1, // File was deleted + Modified = 2, // File was modified + } + + // ==================================================================== + // Content Type + // ==================================================================== + + // ContentType represents the type of file content + enum ContentType { + Text = 0, // Text content + Binary = 1, // Binary content + Image = 2, // Image content + } + + // ==================================================================== + // Watch Event Type + // ==================================================================== + + // WatchEventType represents the type of file system event + enum WatchEventType { + Add = 0, // File/directory added + Change = 1, // File/directory changed + Unlink = 2, // File/directory removed + } + + // ==================================================================== + // File Error + // ==================================================================== + + // FileError represents errors in file operations + enum FileError { + NotFound = 0, + PermissionDenied = 1, + IsDirectory = 2, + NotDirectory = 3, + AccessDenied = 4, + InvalidPath = 5, + ReadError = 6, + WriteError = 7, + DeleteError = 8, + } + + // ==================================================================== + // File Info + // ==================================================================== + + // FileInfo represents metadata about a file + struct FileInfo { + path: str, // File path + fileType: FileType, // Type of file + size: u64, // File size in bytes + modified: u64, // Last modified timestamp (Unix epoch) + permissions: u32, // File permissions (octal) + isHidden: bool, // Whether file is hidden + isIgnored: bool, // Whether file is ignored + } + + // ==================================================================== + // File Content + // ==================================================================== + + // FileContent represents the content of a file + struct FileContent { + type: ContentType, // Type of content + content: str, // File content (text or encoded) + diff: str?, // Git diff if available + mimeType: str, // MIME type + encoding: str?, // Encoding (e.g., "base64" for images) + lineCount: u32?, // Number of lines (text only) + } + + // ==================================================================== + // File Node + // ==================================================================== + + // FileNode represents a node in the file tree + struct FileNode { + name: str, // File/directory name + path: str, // Relative path + absolute: str, // Absolute path + fileType: FileType, // Type of node + ignored: bool, // Whether node is ignored + children: [FileNode]?, // Children (directories only) + } + + // ==================================================================== + // File Change + // ==================================================================== + + // FileChange represents a change to a file + struct FileChange { + path: str, // File path + status: FileStatus, // Change status + additions: u32, // Number of lines added + deletions: u32, // Number of lines deleted + } + + // ==================================================================== + // Ignore Rules + // ==================================================================== + + // IgnorePattern represents a single ignore pattern + struct IgnorePattern { + pattern: str, // Ignore pattern (gitignore style) + isDir: bool, // Whether pattern applies only to directories + } + + // IgnoreRules represents a collection of ignore rules + struct IgnoreRules { + patterns: [IgnorePattern], // Ignore patterns + whitelists: [str], // Whitelisted paths (exceptions) + } + + // ==================================================================== + // Search + // ==================================================================== + + // SearchMatch represents a search result + struct SearchMatch { + path: str, // File path + line: u32, // Line number (1-indexed) + content: str, // Line content + start: u32, // Start position of match in line + end: u32, // End position of match in line + } + + // SearchOptions represents options for file search + struct SearchOptions { + query: str, // Search query/pattern + pattern: str?, // File glob pattern (e.g., "*.ts") + caseSensitive: bool, // Whether search is case sensitive + maxResults: u32, // Maximum results to return + includeHidden: bool, // Whether to include hidden files + fileType: FileType?, // Filter by file type + } + + // ==================================================================== + // Watch Event + // ==================================================================== + + // WatchEvent represents a file system watch event + struct WatchEvent { + path: str, // Path that changed + eventType: WatchEventType, // Type of event + timestamp: u64, // Event timestamp (Unix epoch) + } + + // WatcherHandle represents a watcher handle + struct WatcherHandle { + id: str, // Watcher ID + paths: [str], // Watched paths + active: bool, // Whether watcher is active + } + + // ==================================================================== + // Ripgrep Integration + // ==================================================================== + + // Submatch represents a regex submatch + struct Submatch { + match: str, // Matched text + start: u32, // Start position in content + end: u32, // End position in content + } + + // RipgrepMatch represents a ripgrep search result + struct RipgrepMatch { + path: str, // File path + lineNumber: u32, // Line number (1-indexed) + content: str, // Line content + submatches: [Submatch], // All submatches in the line + } + + // RipgrepOptions represents options for ripgrep search + struct RipgrepOptions { + glob: [str]?, // File glob patterns + hidden: bool, // Whether to search hidden files + follow: bool, // Whether to follow symlinks + maxDepth: u32, // Maximum search depth + limit: u32, // Maximum number of results + } + + // ==================================================================== + // Constants + // ==================================================================== + + const MAX_FILE_SIZE: u64 = 10485760; // 10MB max file size + const MAX_SEARCH_RESULTS: u32 = 1000; // Max search results + const DEFAULT_READ_CHUNK: u32 = 8192; // 8KB default read chunk + + // ==================================================================== + // Helper Functions + // ==================================================================== + + // is_text checks if content type is text + fn is_text(contentType: ContentType) -> bool { + return contentType == ContentType::Text; + } + + // is_binary checks if content type is binary + fn is_binary(contentType: ContentType) -> bool { + return contentType == ContentType::Binary; + } + + // is_image checks if content type is image + fn is_image(contentType: ContentType) -> bool { + return contentType == ContentType::Image; + } + + // is_hidden checks if a path is hidden + fn is_hidden(path: str) -> bool { + var parts = split(path, "/"); + for part in parts { + if part.len > 0 && part[0] == '.' && part != "." && part != ".." { + return true; + } + } + return false; + } + + // get_extension returns the file extension + fn get_extension(path: str) -> str { + var parts = split(path, "."); + if parts.len <= 1 { + return ""; + } + return parts[parts.len - 1]; + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "file_type_values" { + assert(FileType::File as u32 == 0); + assert(FileType::Directory as u32 == 1); + assert(FileType::Symlink as u32 == 2); + } + + test "file_status_values" { + assert(FileStatus::Added as u32 == 0); + assert(FileStatus::Deleted as u32 == 1); + assert(FileStatus::Modified as u32 == 2); + } + + test "content_type_values" { + assert(ContentType::Text as u32 == 0); + assert(ContentType::Binary as u32 == 1); + assert(ContentType::Image as u32 == 2); + } + + test "watch_event_type_values" { + assert(WatchEventType::Add as u32 == 0); + assert(WatchEventType::Change as u32 == 1); + assert(WatchEventType::Unlink as u32 == 2); + } + + test "file_error_values" { + assert(FileError::NotFound as u32 == 0); + assert(FileError::PermissionDenied as u32 == 1); + assert(FileError::IsDirectory as u32 == 2); + assert(FileError::NotDirectory as u32 == 3); + assert(FileError::AccessDenied as u32 == 4); + assert(FileError::InvalidPath as u32 == 5); + assert(FileError::ReadError as u32 == 6); + assert(FileError::WriteError as u32 == 7); + assert(FileError::DeleteError as u32 == 8); + } + + test "constants_values" { + assert(MAX_FILE_SIZE == 10485760); + assert(MAX_SEARCH_RESULTS == 1000); + assert(DEFAULT_READ_CHUNK == 8192); + } + + test "is_text_true" { + assert(is_text(ContentType::Text)); + } + + test "is_binary_true" { + assert(is_binary(ContentType::Binary)); + } + + test "is_image_true" { + assert(is_image(ContentType::Image)); + } + + test "is_hidden_true" { + assert(is_hidden(".git")); + assert(is_hidden("path/.hidden")); + } + + test "is_hidden_false" { + assert(!is_hidden("visible")); + assert(!is_hidden(".")); + } + + test "get_extension_simple" { + assert(get_extension("test.txt") == "txt"); + assert(get_extension("file.tar.gz") == "gz"); + } + + test "get_extension_none" { + assert(get_extension("README") == ""); + assert(get_extension("path/to/file") == ""); + } +} diff --git a/apps/website/public/t27/specs/file/watcher.t27 b/apps/website/public/t27/specs/file/watcher.t27 new file mode 100644 index 0000000000..c759b9b4bd --- /dev/null +++ b/apps/website/public/t27/specs/file/watcher.t27 @@ -0,0 +1,550 @@ +// specs/file/watcher.t27 +// File Watcher Operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module FileWatcher { + use base::types; + use file::schema; + + // ==================================================================== + // Watcher ID Type + // ==================================================================== + + // WatcherID is a branded string representing a watcher identifier + struct WatcherID(str); + + // ==================================================================== + // Watcher Operations + // ==================================================================== + + // create creates a new file watcher + fn create() -> Result { + // Implementation: Create watcher instance + } + + // watch starts watching a path + fn watch(watcherID: WatcherID, path: str, recursive: bool) -> Result { + // Implementation: Start watching path + } + + // unwatch stops watching a path + fn unwatch(watcherID: WatcherID, path: str) -> Result { + // Implementation: Stop watching path + } + + // close closes a watcher + fn close(watcherID: WatcherID) -> Result { + // Implementation: Close watcher and release resources + } + + // is_active checks if a watcher is active + fn is_active(watcherID: WatcherID) -> Result { + // Implementation: Check if watcher is active + } + + // get_watched_paths returns paths being watched + fn get_watched_paths(watcherID: WatcherID) -> Result<[str], FileError> { + // Implementation: Get list of watched paths + } + + // ==================================================================== + // Event Operations + // ==================================================================== + + // next gets the next event from the watcher + fn next(watcherID: WatcherID) -> Result { + // Implementation: Get next event or null if timeout + } + + // poll checks for events without blocking + fn poll(watcherID: WatcherID) -> Result<[WatchEvent], FileError> { + // Implementation: Get all pending events + } + + // wait waits for an event with timeout + fn wait(watcherID: WatcherID, timeout: u64) -> Result { + // Implementation: Wait for event with timeout in ms + } + + // ==================================================================== + // Filter Operations + // ==================================================================== + + // WatchFilter represents a filter for watch events + struct WatchFilter { + paths: [str], // Paths to watch + ignore: [str], // Paths/patterns to ignore + includeHidden: bool, // Whether to include hidden files + includeDirs: bool, // Whether to include directory events + extensions: [str]?, // File extensions to watch (null = all) + } + + // set_filter sets the watch filter + fn set_filter(watcherID: WatcherID, filter: WatchFilter) -> Result { + // Implementation: Set watch filter + } + + // get_filter gets the current watch filter + fn get_filter(watcherID: WatcherID) -> Result { + // Implementation: Get current filter + } + + // matches_filter checks if an event matches the filter + fn matches_filter(event: WatchEvent, filter: WatchFilter) -> bool { + // Implementation: Check if event matches filter + } + + // ==================================================================== + // Backend Types + // ==================================================================== + + // WatcherBackend represents the watcher backend implementation + enum WatcherBackend { + Inotify = 0, // Linux inotify + FSEvents = 1, // macOS FSEvents + ReadDirectoryChangesW = 2, // Windows ReadDirectoryChangesW + Poll = 3, // Polling fallback + } + + // get_backend returns the preferred backend for the platform + fn get_backend() -> WatcherBackend { + // Implementation: Return platform-specific backend + } + + // backend_available checks if a backend is available + fn backend_available(backend: WatcherBackend) -> bool { + // Implementation: Check if backend is available + } + + // ==================================================================== + // Debounce Operations + // ==================================================================== + + // DebounceMode represents the debounce mode + enum DebounceMode { + None = 0, // No debouncing + Immediate = 1, // Immediate debouncing + Coalesce = 2, // Coalesce events for same path + } + + // set_debounce sets the debounce mode and delay + fn set_debounce(watcherID: WatcherID, mode: DebounceMode, delay: u64) -> Result { + // Implementation: Set debounce settings + } + + // get_debounce gets the current debounce settings + fn get_debounce(watcherID: WatcherID) -> Result<(DebounceMode, u64), FileError> { + // Implementation: Get debounce mode and delay + } + + // ==================================================================== + // Batch Operations + // ==================================================================== + + // watch_batch watches multiple paths at once + fn watch_batch(watcherID: WatcherID, paths: [str], recursive: bool) -> Result { + // Implementation: Watch multiple paths + } + + // unwatch_batch stops watching multiple paths + fn unwatch_batch(watcherID: WatcherID, paths: [str]) -> Result { + // Implementation: Stop watching multiple paths + } + + // ==================================================================== + // Statistics + // ==================================================================== + + // WatcherStats represents watcher statistics + struct WatcherStats { + paths: u32, // Number of watched paths + events: u64, // Total events received + errors: u32, // Total errors + startTime: u64, // When watcher started + lastEvent: u64?, // Last event timestamp + } + + // get_stats returns watcher statistics + fn get_stats(watcherID: WatcherID) -> Result { + // Implementation: Get watcher statistics + } + + // reset_stats resets watcher statistics + fn reset_stats(watcherID: WatcherID) -> Result { + // Implementation: Reset event/error counters + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "watcher_id_creation" { + var id = WatcherID("watch-123"); + assert(id.0 == "watch-123"); + } + + test "file_type_values" { + assert(FileType::File as u32 == 0); + assert(FileType::Directory as u32 == 1); + assert(FileType::Symlink as u32 == 2); + } + + test "watch_event_type_values" { + assert(WatchEventType::Add as u32 == 0); + assert(WatchEventType::Change as u32 == 1); + assert(WatchEventType::Unlink as u32 == 2); + } + + test "file_error_values" { + assert(FileError::NotFound as u32 == 0); + assert(FileError::PermissionDenied as u32 == 1); + assert(FileError::IsDirectory as u32 == 2); + assert(FileError::NotDirectory as u32 == 3); + assert(FileError::AccessDenied as u32 == 4); + assert(FileError::InvalidPath as u32 == 5); + assert(FileError::ReadError as u32 == 6); + assert(FileError::WriteError as u32 == 7); + assert(FileError::DeleteError as u32 == 8); + } + + test "watcher_backend_values" { + assert(WatcherBackend::Inotify as u32 == 0); + assert(WatcherBackend::FSEvents as u32 == 1); + assert(WatcherBackend::ReadDirectoryChangesW as u32 == 2); + assert(WatcherBackend::Poll as u32 == 3); + } + + test "debounce_mode_values" { + assert(DebounceMode::None as u32 == 0); + assert(DebounceMode::Immediate as u32 == 1); + assert(DebounceMode::Coalesce as u32 == 2); + } + + test "watch_event_creation" { + var event = WatchEvent { + path = "test.txt", + eventType = WatchEventType::Change, + timestamp = 1234567890, + }; + assert(event.path == "test.txt"); + assert(event.eventType == WatchEventType::Change); + } + + test "watch_event_add" { + var event = WatchEvent { + path = "new.txt", + eventType = WatchEventType::Add, + timestamp = 1234567890, + }; + assert(event.eventType == WatchEventType::Add); + } + + test "watch_event_unlink" { + var event = WatchEvent { + path = "deleted.txt", + eventType = WatchEventType::Unlink, + timestamp = 1234567890, + }; + assert(event.eventType == WatchEventType::Unlink); + } + + test "watch_filter_creation" { + var filter = WatchFilter { + paths = ["/home/project"], + ignore = ["node_modules", ".git"], + includeHidden = false, + includeDirs = false, + extensions = [".ts", ".js"], + }; + assert(filter.paths.len == 1); + assert(filter.ignore.len == 2); + assert(filter.extensions?.len == 2); + } + + test "watch_filter_all_extensions" { + var filter = WatchFilter { + paths = ["."], + ignore = [], + includeHidden = true, + includeDirs = true, + extensions = null, + }; + assert(filter.extensions == null); + assert(filter.includeHidden); + } + + test "watcher_stats_creation" { + var stats = WatcherStats { + paths = 5, + events = 100, + errors = 2, + startTime = 1234567890, + lastEvent = 1234567990, + }; + assert(stats.paths == 5); + assert(stats.events == 100); + assert(stats.errors == 2); + } + + test "watcher_stats_no_events" { + var stats = WatcherStats { + paths = 1, + events = 0, + errors = 0, + startTime = 1234567890, + lastEvent = null, + }; + assert(stats.events == 0); + assert(stats.lastEvent == null); + } + + test "file_info_creation" { + var info = FileInfo { + path = "test.txt", + fileType = FileType::File, + size = 100, + modified = 1234567890, + permissions = 644, + isHidden = false, + isIgnored = false, + }; + assert(info.path == "test.txt"); + } + + test "file_node_creation" { + var node = FileNode { + name = "dir", + path = "dir", + absolute = "/home/dir", + fileType = FileType::Directory, + ignored = false, + children = null, + }; + assert(node.fileType == FileType::Directory); + } + + test "ignore_pattern_creation" { + var pattern = IgnorePattern { + pattern = "*.log", + isDir = false, + }; + assert(pattern.pattern == "*.log"); + } + + test "ignore_rules_creation" { + var patterns = [ + IgnorePattern { + pattern = "node_modules", + isDir = true, + }, + ]; + var rules = IgnoreRules { + patterns = patterns, + whitelists: [], + }; + assert(rules.patterns.len == 1); + } + + test "search_match_creation" { + var match = SearchMatch { + path = "test.txt", + line = 1, + content = "test", + start = 0, + end = 4, + }; + assert(match.path == "test.txt"); + } + + test "search_options_creation" { + var options = SearchOptions { + query = "pattern", + pattern = "*.ts", + caseSensitive = true, + maxResults = 50, + includeHidden = false, + fileType = FileType::File, + }; + assert(options.caseSensitive); + assert(options.maxResults == 50); + } + + test "constants_values" { + assert(MAX_FILE_SIZE == 10485760); + assert(MAX_SEARCH_RESULTS == 1000); + assert(DEFAULT_READ_CHUNK == 8192); + } + + test "file_content_text" { + var content = FileContent { + type = ContentType::Text, + content = "text", + diff = null, + mimeType = "text/plain", + encoding = null, + lineCount = 1, + }; + assert(is_text(content.type)); + } + + test "file_change_creation" { + var change = FileChange { + path = "test.txt", + status = FileStatus::Modified, + additions = 1, + deletions = 0, + }; + assert(change.status == FileStatus::Modified); + } + + test "watcher_handle_creation" { + var handle = WatcherHandle { + id = "watch-1", + paths = ["/home/project"], + active = true, + }; + assert(handle.active); + } + + test "submatch_creation" { + var submatch = Submatch { + match = "match", + start = 0, + end = 5, + }; + assert(submatch.match == "match"); + } + + test "ripgrep_match_creation" { + var submatches = [ + Submatch { + match = "test", + start = 0, + end = 4, + }, + ]; + var match = RipgrepMatch { + path = "file.ts", + lineNumber = 1, + content = "test line", + submatches = submatches, + }; + assert(match.path == "file.ts"); + } + + test "ripgrep_options_creation" { + var globs = ["*.ts"]; + var options = RipgrepOptions { + glob = globs, + hidden = true, + follow = false, + maxDepth = 5, + limit = 10, + }; + assert(options.glob?.len == 1); + } + + test "content_type_values" { + assert(ContentType::Text as u32 == 0); + assert(ContentType::Binary as u32 == 1); + assert(ContentType::Image as u32 == 2); + } + + test "file_status_values" { + assert(FileStatus::Added as u32 == 0); + assert(FileStatus::Deleted as u32 == 1); + assert(FileStatus::Modified as u32 == 2); + } + + test "is_text_true" { + assert(is_text(ContentType::Text)); + } + + test "is_binary_true" { + assert(is_binary(ContentType::Binary)); + } + + test "is_image_true" { + assert(is_image(ContentType::Image)); + } + + test "is_hidden_true" { + assert(is_hidden(".git")); + } + + test "is_hidden_false" { + assert(!is_hidden("visible")); + } + + test "get_extension_simple" { + assert(get_extension("test.txt") == "txt"); + } + + test "watcher_stats_with_high_events" { + var stats = WatcherStats { + paths = 10, + events = 1000000, + errors = 50, + startTime = 1234560000, + lastEvent = 1234569000, + }; + assert(stats.events == 1000000); + assert(stats.errors == 50); + } + + test "watch_filter_multiple_paths" { + var filter = WatchFilter { + paths = ["src", "test"], + ignore = [".git"], + includeHidden = false, + includeDirs = false, + extensions = [".ts"], + }; + assert(filter.paths.len == 2); + } + + test "watch_filter_multiple_extensions" { + var filter = WatchFilter { + paths = ["."], + ignore = [], + includeHidden = false, + includeDirs = false, + extensions: [".ts", ".js", ".json"], + }; + assert(filter.extensions?.len == 3); + } + + test "watch_filter_no_ignore" { + var filter = WatchFilter { + paths = ["."], + ignore = [], + includeHidden = false, + includeDirs = false, + extensions = null, + }; + assert(filter.ignore.len == 0); + } + + test "watcher_stats_single_path" { + var stats = WatcherStats { + paths = 1, + events = 10, + errors = 0, + startTime = 1234567890, + lastEvent = 1234567900, + }; + assert(stats.paths == 1); + } + + test "watcher_stats_with_errors" { + var stats = WatcherStats { + paths = 3, + events = 50, + errors = 5, + startTime = 1234567890, + lastEvent = null, + }; + assert(stats.errors == 5); + assert(stats.lastEvent == null); + } +} diff --git a/apps/website/public/t27/specs/fpga/apb_bridge.t27 b/apps/website/public/t27/specs/fpga/apb_bridge.t27 new file mode 100644 index 0000000000..9900b7821c --- /dev/null +++ b/apps/website/public/t27/specs/fpga/apb_bridge.t27 @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/apb_bridge.t27 +// APB (Advanced Peripheral Bus) Bridge Specification for Trinity T27 FPGA HIR +// Register-mapped peripheral bridge for low-bandwidth peripherals +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module ApbBridge { + + // === APB bus width constants === + + pub const APB_ADDR_WIDTH : u32 = 32; + pub const APB_DATA_WIDTH : u32 = 32; + pub const APB_STRB_WIDTH : u32 = 4; + + // === APB transfer kind === + + pub const ApbTransfer = enum(i8) { + idle = 0, + setup = 1, + access = 2, + } + + // === APB bridge configuration === + + pub struct ApbConfig { + name : &str, + addr_width : u32, + data_width : u32, + num_peripherals : u32, + base_addr : u32, + addr_mask : u32, + has_pslverr : bool, + has_pprot : bool, + } + + // === Peripheral address range === + + pub struct PeripheralMap { + name : &str, + base_addr : u32, + size : u32, + index : u32, + } + + // === APB read/write request (for simulation) === + + pub struct ApbRequest { + addr : u32, + wdata : u32, + write : bool, + strb : u32, + valid : bool, + } + + // === APB response (for simulation) === + + pub struct ApbResponse { + rdata : u32, + ready : bool, + slverr : bool, + } + + // === Constructor helpers === + + fn apb_bridge(name: &str, addr_width: u32, data_width: u32, num_peripherals: u32) -> ApbConfig { + return ApbConfig{ + .name = name, + .addr_width = addr_width, + .data_width = data_width, + .num_peripherals = num_peripherals, + .base_addr = 0, + .addr_mask = 0, + .has_pslverr = false, + .has_pprot = false, + }; + } + + fn apb_bridge_with_error(name: &str, addr_width: u32, data_width: u32, num_peripherals: u32) -> ApbConfig { + return ApbConfig{ + .name = name, + .addr_width = addr_width, + .data_width = data_width, + .num_peripherals = num_peripherals, + .base_addr = 0, + .addr_mask = 0, + .has_pslverr = true, + .has_pprot = true, + }; + } + + fn peripheral_map(name: &str, base_addr: u32, size: u32, index: u32) -> PeripheralMap { + return PeripheralMap{ + .name = name, + .base_addr = base_addr, + .size = size, + .index = index, + }; + } + + fn apb_read_request(addr: u32) -> ApbRequest { + return ApbRequest{ + .addr = addr, + .wdata = 0, + .write = false, + .strb = 15, + .valid = true, + }; + } + + fn apb_write_request(addr: u32, data: u32, strb: u32) -> ApbRequest { + return ApbRequest{ + .addr = addr, + .wdata = data, + .write = true, + .strb = strb, + .valid = true, + }; + } + + fn apb_ok_response(data: u32) -> ApbResponse { + return ApbResponse{ + .rdata = data, + .ready = true, + .slverr = false, + }; + } + + fn apb_error_response() -> ApbResponse { + return ApbResponse{ + .rdata = 0, + .ready = true, + .slverr = true, + }; + } + + // === Query functions === + + fn strb_width(cfg: ApbConfig) -> u32 { + return cfg.data_width / 8; + } + + fn addr_bits_for_peripherals(cfg: ApbConfig) -> u32 { + var n : u32 = cfg.num_peripherals; + if (n <= 1) { + return 0; + } + var bits : u32 = 0; + while (n > 1) { + bits = bits + 1; + n = n / 2; + } + return bits; + } + + fn peripheral_addr_offset(cfg: ApbConfig, periph_index: u32) -> u32 { + var offset_bits : u32 = addr_bits_for_peripherals(cfg); + return periph_index << offset_bits; + } + + fn is_read(req: ApbRequest) -> bool { + return req.valid and req.write == false; + } + + fn is_write(req: ApbRequest) -> bool { + return req.valid and req.write; + } + + fn apb_port_count(cfg: ApbConfig) -> u32 { + var count : u32 = 0; + count = count + 1; + count = count + 1; + count = count + cfg.addr_width; + count = count + cfg.data_width; + count = count + cfg.data_width / 8; + count = count + cfg.data_width; + count = count + 1; + if (cfg.has_pslverr) { + count = count + 1; + } + if (cfg.has_pprot) { + count = count + 3; + } + return count; + } + + fn select_peripheral(cfg: ApbConfig, addr: u32, maps: [16]PeripheralMap, map_count: u32) -> u32 { + var i : u32 = 0; + while (i < map_count) { + var base : u32 = maps[i].base_addr; + var size : u32 = maps[i].size; + if (addr >= base and addr < base + size) { + return i; + } + i = i + 1; + } + return 65535; + } + + // === Validation === + + fn validate_apb(cfg: ApbConfig) -> u32 { + var errors : u32 = 0; + if (cfg.name == "") { + errors = errors + 1; + } + if (cfg.addr_width == 0) { + errors = errors + 1; + } + if (cfg.data_width == 0) { + errors = errors + 1; + } + if (cfg.data_width % 8 != 0) { + errors = errors + 1; + } + if (cfg.num_peripherals == 0) { + errors = errors + 1; + } + return errors; + } + + fn validate_peripheral_map(m: PeripheralMap) -> u32 { + var errors : u32 = 0; + if (m.name == "") { + errors = errors + 1; + } + if (m.size == 0) { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test apb_bridge_creation + given cfg = apb_bridge("apb0", 32, 32, 4) + then cfg.name == "apb0" + and cfg.addr_width == 32 + and cfg.data_width == 32 + and cfg.num_peripherals == 4 + and cfg.has_pslverr == false + + test apb_bridge_with_error + given cfg = apb_bridge_with_error("apb1", 32, 32, 8) + then cfg.has_pslverr == true + and cfg.has_pprot == true + + test strb_width_32bit + given cfg = apb_bridge("apb0", 32, 32, 4) + then strb_width(cfg) == 4 + + test strb_width_16bit + given cfg = apb_bridge("apb0", 16, 16, 4) + then strb_width(cfg) == 2 + + test addr_bits_for_1_peripheral + given cfg = apb_bridge("apb0", 32, 32, 1) + then addr_bits_for_peripherals(cfg) == 0 + + test addr_bits_for_4_peripherals + given cfg = apb_bridge("apb0", 32, 32, 4) + then addr_bits_for_peripherals(cfg) == 2 + + test addr_bits_for_8_peripherals + given cfg = apb_bridge("apb0", 32, 32, 8) + then addr_bits_for_peripherals(cfg) == 3 + + test read_request + given req = apb_read_request(256) + then is_read(req) == true + and is_write(req) == false + and req.addr == 256 + + test write_request + given req = apb_write_request(256, 42, 15) + then is_read(req) == false + and is_write(req) == true + and req.wdata == 42 + + test ok_response + given resp = apb_ok_response(99) + then resp.rdata == 99 + and resp.ready == true + and resp.slverr == false + + test error_response + given resp = apb_error_response() + then resp.slverr == true + + test validate_ok + given cfg = apb_bridge("apb0", 32, 32, 4) + then validate_apb(cfg) == 0 + + test validate_empty_name + given cfg = apb_bridge("", 32, 32, 4) + then validate_apb(cfg) > 0 + + test validate_zero_addr + given cfg = apb_bridge("apb0", 0, 32, 4) + then validate_apb(cfg) > 0 + + test validate_zero_peripherals + given cfg = apb_bridge("apb0", 32, 32, 0) + then validate_apb(cfg) > 0 + + test validate_peripheral_map_ok + given m = peripheral_map("uart0", 4096, 256, 0) + then validate_peripheral_map(m) == 0 + + test validate_peripheral_map_no_name + given m = peripheral_map("", 4096, 256, 0) + then validate_peripheral_map(m) > 0 + + test apb_port_count_basic + given cfg = apb_bridge("apb0", 32, 32, 4) + then apb_port_count(cfg) > 0 + + // === Invariants === + + invariant addr_width_positive + given cfg = apb_bridge("inv", 32, 32, 4) + assert cfg.addr_width > 0 + + invariant data_width_byte_aligned + given cfg = apb_bridge("inv", 32, 32, 4) + assert cfg.data_width % 8 == 0 + + invariant strb_matches_data + given cfg = apb_bridge("inv", 32, 32, 4) + assert strb_width(cfg) == cfg.data_width / 8 + + invariant num_peripherals_positive + given cfg = apb_bridge("inv", 32, 32, 4) + assert cfg.num_peripherals > 0 + + invariant validate_non_negative + given cfg = apb_bridge("inv", 32, 32, 4) + assert validate_apb(cfg) >= 0 + + // === Benchmarks === + + bench validate_latency + measure: nanoseconds to validate_apb(apb_bridge("b", 32, 32, 4)) + target: < 100ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/assembler.t27 b/apps/website/public/t27/specs/fpga/assembler.t27 new file mode 100644 index 0000000000..c7eff4161f --- /dev/null +++ b/apps/website/public/t27/specs/fpga/assembler.t27 @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/assembler.t27 +// T27 Ternary Assembler Specification +// High-level assembler for the ternary ISA, compiles to machine code +// Supports R-type, I-type, and GF16 extended instructions +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Assembler { + + // === Assembler section kind === + + pub const SectionKind = enum(i8) { + text = 0, + data = 1, + bss = 2, + rodata = 3, + } + + // === Relocation kind === + + pub const RelocKind = enum(i8) { + abs32 = 0, + rel21 = 1, + gf16_label = 2, + } + + // === Assembled instruction === + + pub struct AssembledInstr { + address : u32, + opcode : u32, + rd : u32, + rs1 : u32, + rs2 : u32, + imm : u32, + label : &str, + is_gf16 : bool, + } + + // === Relocation entry === + + pub struct RelocEntry { + offset : u32, + kind : i8, + symbol : &str, + addend : u32, + } + + // === Symbol table entry === + + pub struct Symbol { + name : &str, + address : u32, + size : u32, + section : i8, + is_global : bool, + } + + // === Assembler section === + + pub struct AsmSection { + name : &str, + kind : i8, + base_address : u32, + size : u32, + } + + // === Assembler config === + + pub struct AsmConfig { + name : &str, + text_base : u32, + data_base : u32, + word_size : u32, + has_gf16_ext : bool, + has_ternary_ext : bool, + } + + // === Constructor helpers === + + fn asm_config(name: &str) -> AsmConfig { + return AsmConfig{ + .name = name, + .text_base = 0, + .data_base = 4096, + .word_size = 4, + .has_gf16_ext = true, + .has_ternary_ext = true, + }; + } + + fn text_section(base: u32) -> AsmSection { + return AsmSection{ + .name = ".text", + .kind = 0, + .base_address = base, + .size = 0, + }; + } + + fn data_section(base: u32) -> AsmSection { + return AsmSection{ + .name = ".data", + .kind = 1, + .base_address = base, + .size = 0, + }; + } + + fn r_instruction(opcode: u32, rd: u32, rs1: u32, rs2: u32) -> AssembledInstr { + return AssembledInstr{ + .address = 0, + .opcode = opcode, + .rd = rd, + .rs1 = rs1, + .rs2 = rs2, + .imm = 0, + .label = "", + .is_gf16 = false, + }; + } + + fn i_instruction(opcode: u32, rd: u32, rs1: u32, imm: u32) -> AssembledInstr { + return AssembledInstr{ + .address = 0, + .opcode = opcode, + .rd = rd, + .rs1 = rs1, + .rs2 = 0, + .imm = imm, + .label = "", + .is_gf16 = false, + }; + } + + fn gf16_instruction(opcode: u32, rd: u32, rs1: u32, rs2: u32) -> AssembledInstr { + return AssembledInstr{ + .address = 0, + .opcode = opcode, + .rd = rd, + .rs1 = rs1, + .rs2 = rs2, + .imm = 0, + .label = "", + .is_gf16 = true, + }; + } + + fn symbol(name: &str, address: u32, section: i8, is_global: bool) -> Symbol { + return Symbol{ + .name = name, + .address = address, + .size = 0, + .section = section, + .is_global = is_global, + }; + } + + fn reloc(offset: u32, kind: i8, symbol_name: &str, addend: u32) -> RelocEntry { + return RelocEntry{ + .offset = offset, + .kind = kind, + .symbol = symbol_name, + .addend = addend, + }; + } + + // === Query functions === + + fn is_r_type(instr: AssembledInstr) -> bool { + return instr.imm == 0 and instr.rs2 > 0; + } + + fn is_i_type(instr: AssembledInstr) -> bool { + return instr.imm > 0; + } + + fn is_gf16_instr(instr: AssembledInstr) -> bool { + return instr.is_gf16; + } + + fn encode_r_type(instr: AssembledInstr) -> u32 { + return (instr.opcode << 26) | (instr.rd << 21) | (instr.rs1 << 16) | (instr.rs2 << 11); + } + + fn encode_i_type(instr: AssembledInstr) -> u32 { + return (instr.opcode << 26) | (instr.rd << 21) | (instr.rs1 << 16) | (instr.imm & 65535); + } + + fn section_end(sec: AsmSection) -> u32 { + return sec.base_address + sec.size; + } + + fn align_address(addr: u32, alignment: u32) -> u32 { + if (alignment == 0) { + return addr; + } + var remainder : u32 = addr % alignment; + if (remainder == 0) { + return addr; + } + return addr + alignment - remainder; + } + + fn instr_count(cfg: AsmConfig, bytes: u32) -> u32 { + if (cfg.word_size == 0) { + return 0; + } + return bytes / cfg.word_size; + } + + // === Validation === + + fn validate_config(cfg: AsmConfig) -> u32 { + var errors : u32 = 0; + if (cfg.name == "") { + errors = errors + 1; + } + if (cfg.word_size == 0) { + errors = errors + 1; + } + return errors; + } + + fn validate_symbol(sym: Symbol) -> u32 { + var errors : u32 = 0; + if (sym.name == "") { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test asm_config_creation + given cfg = asm_config("t27_asm") + then cfg.text_base == 0 + and cfg.data_base == 4096 + and cfg.word_size == 4 + and cfg.has_gf16_ext == true + and cfg.has_ternary_ext == true + + test text_section_creation + given sec = text_section(0) + then sec.kind == 0 + and sec.base_address == 0 + + test data_section_creation + given sec = data_section(4096) + then sec.kind == 1 + and sec.base_address == 4096 + + test r_instruction_creation + given instr = r_instruction(1, 5, 6, 7) + then is_r_type(instr) == true + and is_i_type(instr) == false + and is_gf16_instr(instr) == false + + test i_instruction_creation + given instr = i_instruction(2, 5, 6, 42) + then is_i_type(instr) == true + and is_r_type(instr) == false + + test gf16_instruction_creation + given instr = gf16_instruction(16, 5, 6, 7) + then is_gf16_instr(instr) == true + and is_r_type(instr) == true + + test encode_r_type + given instr = r_instruction(1, 5, 6, 7) + then encode_r_type(instr) > 0 + + test encode_i_type + given instr = i_instruction(2, 5, 6, 42) + then encode_i_type(instr) > 0 + + test section_end + given sec = AsmSection{.name = ".text", .kind = 0, .base_address = 0, .size = 128} + then section_end(sec) == 128 + + test align_address_zero + then align_address(5, 4) == 8 + + test align_address_already_aligned + then align_address(8, 4) == 8 + + test align_address_zero_alignment + then align_address(5, 0) == 5 + + test instr_count + given cfg = asm_config("test") + then instr_count(cfg, 128) == 32 + + test symbol_creation + given sym = symbol("main", 0, 0, true) + then sym.name == "main" + and sym.is_global == true + + test reloc_creation + given r = reloc(64, 0, "data_start", 0) + then r.offset == 64 + and r.symbol == "data_start" + + test validate_config_ok + given cfg = asm_config("test") + then validate_config(cfg) == 0 + + test validate_config_empty_name + given cfg = AsmConfig{.name = "", .text_base = 0, .data_base = 4096, .word_size = 4, .has_gf16_ext = true, .has_ternary_ext = true} + then validate_config(cfg) > 0 + + test validate_symbol_ok + given sym = symbol("main", 0, 0, true) + then validate_symbol(sym) == 0 + + test validate_symbol_empty_name + given sym = symbol("", 0, 0, true) + then validate_symbol(sym) > 0 + + // === Invariants === + + invariant word_size_positive + given cfg = asm_config("inv") + assert cfg.word_size > 0 + + invariant data_base_above_text + given cfg = asm_config("inv") + assert cfg.data_base >= cfg.text_base + + invariant align_never_decreases + given a = align_address(100, 16) + assert a >= 100 + + invariant validate_non_negative + given cfg = asm_config("inv") + assert validate_config(cfg) >= 0 + + // === Benchmarks === + + bench encode_latency + measure: nanoseconds for encode_r_type(r_instruction(1, 5, 6, 7)) + target: < 100ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/axi4.t27 b/apps/website/public/t27/specs/fpga/axi4.t27 new file mode 100644 index 0000000000..eb41537e22 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/axi4.t27 @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/axi4.t27 +// AXI4-Lite and AXI4-Full Bus Interface Specification for Trinity T27 FPGA HIR +// Defines bus port groups for AW/AR/W/R/B channels +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Axi4 { + + // === Bus kind === + + pub const AxiKind = enum(i8) { + axi4_lite = 0, + axi4_full = 1, + } + + // === AXI4 channel signals (described as flat arrays) === + + pub const MAX_BUS_PORTS : u32 = 32; + pub const AXI_ADDR_WIDTH : u32 = 32; + pub const AXI_DATA_WIDTH : u32 = 32; + pub const AXI_STRB_WIDTH : u32 = 4; + pub const AXI_ID_WIDTH : u32 = 4; + pub const AXI_LEN_WIDTH : u32 = 8; + pub const AXI_SIZE_WIDTH : u32 = 3; + pub const AXI_BURST_WIDTH : u32 = 2; + pub const AXI_RESP_WIDTH : u32 = 2; + pub const AXI_CACHE_WIDTH : u32 = 4; + pub const AXI_PROT_WIDTH : u32 = 3; + pub const AXI_QOS_WIDTH : u32 = 4; + pub const AXI_REGION_WIDTH : u32 = 4; + pub const AXI_USER_WIDTH : u32 = 1; + + // === Bus port configuration === + + pub struct AxiBusConfig { + name : &str, + kind : i8, + addr_width : u32, + data_width : u32, + id_width : u32, + has_region : bool, + has_cache : bool, + has_prot : bool, + has_qos : bool, + has_user : bool, + has_lock : bool, + } + + // === Bus port group (flat) === + + pub struct BusPort { + name : &str, + direction : i8, + width : u32, + channel : i8, + } + + // Channel codes + pub const CH_AW : i8 = 0; + pub const CH_AR : i8 = 1; + pub const CH_W : i8 = 2; + pub const CH_R : i8 = 3; + pub const CH_B : i8 = 4; + + // === Constructor helpers === + + fn axi4_lite_slave(name: &str, addr_width: u32, data_width: u32) -> AxiBusConfig { + return AxiBusConfig{ + .name = name, + .kind = 0, + .addr_width = addr_width, + .data_width = data_width, + .id_width = 0, + .has_region = false, + .has_cache = false, + .has_prot = true, + .has_qos = false, + .has_user = false, + .has_lock = false, + }; + } + + fn axi4_lite_master(name: &str, addr_width: u32, data_width: u32) -> AxiBusConfig { + return AxiBusConfig{ + .name = name, + .kind = 0, + .addr_width = addr_width, + .data_width = data_width, + .id_width = 0, + .has_region = false, + .has_cache = false, + .has_prot = true, + .has_qos = false, + .has_user = false, + .has_lock = false, + }; + } + + fn axi4_full_slave(name: &str, addr_width: u32, data_width: u32, id_width: u32) -> AxiBusConfig { + return AxiBusConfig{ + .name = name, + .kind = 1, + .addr_width = addr_width, + .data_width = data_width, + .id_width = id_width, + .has_region = true, + .has_cache = true, + .has_prot = true, + .has_qos = true, + .has_user = false, + .has_lock = true, + }; + } + + fn axi4_full_master(name: &str, addr_width: u32, data_width: u32, id_width: u32) -> AxiBusConfig { + return AxiBusConfig{ + .name = name, + .kind = 1, + .addr_width = addr_width, + .data_width = data_width, + .id_width = id_width, + .has_region = true, + .has_cache = true, + .has_prot = true, + .has_qos = true, + .has_user = false, + .has_lock = true, + }; + } + + // === Query functions === + + fn is_lite(cfg: AxiBusConfig) -> bool { + return cfg.kind == 0; + } + + fn is_full(cfg: AxiBusConfig) -> bool { + return cfg.kind == 1; + } + + fn strb_width(cfg: AxiBusConfig) -> u32 { + return cfg.data_width / 8; + } + + fn is_master(cfg: AxiBusConfig) -> bool { + return true; + } + + // === Port count calculation === + + fn slave_port_count(cfg: AxiBusConfig) -> u32 { + var count : u32 = 0; + // AW channel inputs + count = count + 1; + if cfg.id_width > 0 { + count = count + 1; + } + count = count + 1; + count = count + 1; + if cfg.has_cache { + count = count + 1; + } + if cfg.has_prot { + count = count + 1; + } + if cfg.has_qos { + count = count + 1; + } + if cfg.has_region { + count = count + 1; + } + if cfg.has_lock { + count = count + 1; + } + // AWREADY output + count = count + 1; + // W channel + count = count + 1; + count = count + 1; + // WREADY output + count = count + 1; + // B channel + count = count + 1; + // BRESP output + count = count + 1; + if cfg.id_width > 0 { + count = count + 1; + } + // BREADY input + count = count + 1; + // AR channel inputs + count = count + 1; + if cfg.id_width > 0 { + count = count + 1; + } + count = count + 1; + count = count + 1; + if cfg.has_cache { + count = count + 1; + } + if cfg.has_prot { + count = count + 1; + } + if cfg.has_qos { + count = count + 1; + } + if cfg.has_region { + count = count + 1; + } + if cfg.has_lock { + count = count + 1; + } + // ARREADY output + count = count + 1; + // R channel outputs + count = count + 1; + count = count + 1; + if cfg.id_width > 0 { + count = count + 1; + } + // RREADY input + count = count + 1; + return count; + } + + fn total_bus_bits(cfg: AxiBusConfig) -> u32 { + var bits : u32 = 0; + bits = bits + cfg.addr_width; + bits = bits + cfg.data_width; + bits = bits + cfg.data_width / 8; + bits = bits + 2; + bits = bits + 2; + if cfg.id_width > 0 { + bits = bits + cfg.id_width * 3; + } + if cfg.has_cache { + bits = bits + 4 * 2; + } + if cfg.has_prot { + bits = bits + 3 * 2; + } + if cfg.has_qos { + bits = bits + 4 * 2; + } + if cfg.has_region { + bits = bits + 4 * 2; + } + if cfg.has_lock { + bits = bits + 2; + } + bits = bits + 8; + bits = bits + 3; + bits = bits + 2; + bits = bits + 8; + return bits; + } + + // === Validation === + + fn validate_axi(cfg: AxiBusConfig) -> u32 { + var errors : u32 = 0; + if (cfg.name == "") { + errors = errors + 1; + } + if (cfg.addr_width == 0) { + errors = errors + 1; + } + if (cfg.data_width == 0) { + errors = errors + 1; + } + if (cfg.data_width % 8 != 0) { + errors = errors + 1; + } + if (cfg.kind == 1 and cfg.id_width == 0) { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test axi4_lite_is_lite + given cfg = axi4_lite_slave("s0", 32, 32) + then is_lite(cfg) == true + and is_full(cfg) == false + + test axi4_full_is_full + given cfg = axi4_full_slave("s1", 32, 64, 4) + then is_full(cfg) == true + and is_lite(cfg) == false + + test strb_width_32bit + given cfg = axi4_lite_slave("s0", 32, 32) + then strb_width(cfg) == 4 + + test strb_width_64bit + given cfg = axi4_full_slave("s1", 32, 64, 4) + then strb_width(cfg) == 8 + + test validate_lite_ok + given cfg = axi4_lite_slave("s0", 32, 32) + then validate_axi(cfg) == 0 + + test validate_full_ok + given cfg = axi4_full_slave("s1", 32, 64, 4) + then validate_axi(cfg) == 0 + + test validate_empty_name + given cfg = axi4_lite_slave("", 32, 32) + then validate_axi(cfg) > 0 + + test validate_zero_addr + given cfg = axi4_lite_slave("s0", 0, 32) + then validate_axi(cfg) > 0 + + test validate_zero_data + given cfg = axi4_lite_slave("s0", 32, 0) + then validate_axi(cfg) > 0 + + test validate_non_byte_data + given cfg = AxiBusConfig{.name = "s0", .kind = 0, .addr_width = 32, .data_width = 12, .id_width = 0, .has_region = false, .has_cache = false, .has_prot = true, .has_qos = false, .has_user = false, .has_lock = false} + then validate_axi(cfg) > 0 + + test validate_full_no_id + given cfg = axi4_full_slave("s1", 32, 32, 0) + then validate_axi(cfg) > 0 + + test slave_port_count_lite + given cfg = axi4_lite_slave("s0", 32, 32) + then slave_port_count(cfg) > 0 + + test total_bus_bits_positive + given cfg = axi4_lite_slave("s0", 32, 32) + then total_bus_bits(cfg) > 0 + + test lite_no_id + given cfg = axi4_lite_slave("s0", 32, 32) + then cfg.id_width == 0 + + test full_has_id + given cfg = axi4_full_slave("s1", 32, 64, 4) + then cfg.id_width == 4 + + test full_has_extras + given cfg = axi4_full_slave("s1", 32, 64, 4) + then cfg.has_cache == true + and cfg.has_region == true + and cfg.has_qos == true + and cfg.has_lock == true + + // === Invariants === + + invariant addr_width_positive + given cfg = axi4_lite_slave("inv", 32, 32) + assert cfg.addr_width > 0 + + invariant data_width_byte_aligned + given cfg = axi4_lite_slave("inv", 32, 32) + assert cfg.data_width % 8 == 0 + + invariant strb_width_matches_data + given cfg = axi4_lite_slave("inv", 32, 32) + assert strb_width(cfg) == cfg.data_width / 8 + + invariant lite_has_zero_id + given cfg = axi4_lite_slave("inv", 32, 32) + assert cfg.id_width == 0 + + invariant full_has_nonzero_id + given cfg = axi4_full_slave("inv", 32, 32, 4) + assert cfg.id_width > 0 + + invariant validate_non_negative + given cfg = axi4_lite_slave("inv", 32, 32) + assert validate_axi(cfg) >= 0 + + invariant total_bus_bits_positive + given cfg = axi4_lite_slave("inv", 32, 32) + assert total_bus_bits(cfg) > 0 + + // === Benchmarks === + + bench validate_latency + measure: nanoseconds to validate_axi(axi4_lite_slave("b", 32, 32)) + target: < 100ns + + bench port_count_latency + measure: nanoseconds to slave_port_count(axi4_full_slave("b", 32, 64, 4)) + target: < 200ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/boards/arty_a7_integration.t27 b/apps/website/public/t27/specs/fpga/boards/arty_a7_integration.t27 new file mode 100644 index 0000000000..233d4eb576 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/boards/arty_a7_integration.t27 @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/boards/arty_a7_integration.t27 +// Arty A7 Board-Level Integration Spec +// Full system: MAC + UART + SPI + Memory + Bridge + GF16 + TernaryISA +// Pin mappings match specs/fpga/constraints/arty_a7.xdc +// phi^2 + 1/phi^2 = 3 | TRINITY + +module ArtyA7_Integration { + use fpga::top_level::ZeroDSP_TopLevel; + use fpga::mac::ZeroDSP_MAC; + use fpga::uart::ZeroDSP_UART; + use fpga::spi::SPI_Master; + use fpga::memory::Memory; + use fpga::bridge::FPGA_Bridge; + use fpga::gf16_accel::Gf16Accel; + use fpga::fifo::Fifo; + use fpga::axi4::Axi4; + + const BOARD_NAME : str = "arty-a7-100t"; + const FPGA_PART : str = "xc7a100t"; + const CLOCK_FREQ_HZ : u32 = 100_000_000; + const UART_BAUD : u32 = 115_200; + const SPI_CLK_DIV : u32 = 4; + const MEMORY_SIZE : u32 = 0x10000; + const FIFO_DEPTH : u32 = 16; + const NUM_LEDS : u32 = 4; + const NUM_SWITCHES : u32 = 4; + const NUM_BUTTONS : u32 = 4; + + struct PinMapping { + clk_pin : str; + rst_pin : str; + uart_tx_pin : str; + uart_rx_pin : str; + spi_cs_pin : str; + spi_sck_pin : str; + spi_mosi_pin : str; + spi_miso_pin : str; + led_pins : str; + switch_pins : str; + button_pins : str; + } + + const ARTY_A7_PINS : PinMapping = PinMapping{ + .clk_pin = "E3", + .rst_pin = "C12", + .uart_tx_pin = "A9", + .uart_rx_pin = "C9", + .spi_cs_pin = "G13", + .spi_sck_pin = "K13", + .spi_mosi_pin = "H13", + .spi_miso_pin = "J13", + .led_pins = "R5,T5,T8,T9", + .switch_pins = "A15,C16,C15,P15", + .button_pins = "D9,C9,B9,B8", + }; + + struct SystemConfig { + clock_hz : u32; + baud_rate : u32; + spi_div : u32; + mem_size : u32; + fifo_depth : u32; + num_peripherals : u32; + } + + const SYS_CONFIG : SystemConfig = SystemConfig{ + .clock_hz = 100_000_000, + .baud_rate = 115_200, + .spi_div = 4, + .mem_size = 0x10000, + .fifo_depth = 16, + .num_peripherals = 7, + }; + + fn calc_baud_divisor(clock_hz : u32, baud : u32) -> u32 { + return clock_hz / (16 * baud); + } + + fn calc_spi_prescaler(clock_hz : u32, target_sclk_hz : u32) -> u32 { + return clock_hz / (2 * target_sclk_hz); + } + + fn verify_pin_config(pins : PinMapping) -> bool { + var valid : bool = true; + if pins.clk_pin == "" { valid = false; } + if pins.rst_pin == "" { valid = false; } + if pins.uart_tx_pin == "" { valid = false; } + if pins.uart_rx_pin == "" { valid = false; } + return valid; + } + + test test_baud_divisor { + var divisor : u32 = calc_baud_divisor(CLOCK_FREQ_HZ, UART_BAUD); + invariant divisor > 0; + invariant divisor == 54; + } + + test test_spi_prescaler { + var prescaler : u32 = calc_spi_prescaler(CLOCK_FREQ_HZ, 25_000_000); + invariant prescaler == 2; + } + + test test_system_config { + invariant SYS_CONFIG.clock_hz == CLOCK_FREQ_HZ; + invariant SYS_CONFIG.baud_rate == UART_BAUD; + invariant SYS_CONFIG.num_peripherals == 7; + } + + test test_pin_config_valid { + var valid : bool = verify_pin_config(ARTY_A7_PINS); + invariant valid == true; + } + + test test_fpga_part { + invariant FPGA_PART == "xc7a100t"; + } + + test test_clock_freq { + invariant CLOCK_FREQ_HZ == 100_000_000; + } + + invariant clock_positive : CLOCK_FREQ_HZ > 0; + invariant baud_valid : UART_BAUD > 0 && UART_BAUD <= 921600; + invariant spi_div_positive : SPI_CLK_DIV > 0; + invariant mem_size_power_of_2 : MEMORY_SIZE > 0; + invariant fifo_depth_valid : FIFO_DEPTH > 0; + invariant board_name_set : BOARD_NAME != ""; + invariant fpga_part_set : FPGA_PART != ""; + + bench bench_integration_config { + calc_baud_divisor(CLOCK_FREQ_HZ, UART_BAUD); + calc_spi_prescaler(CLOCK_FREQ_HZ, 25_000_000); + verify_pin_config(ARTY_A7_PINS); + } +} diff --git a/apps/website/public/t27/specs/fpga/boards/qmtech_a100t_integration.t27 b/apps/website/public/t27/specs/fpga/boards/qmtech_a100t_integration.t27 new file mode 100644 index 0000000000..50bdfcc63d --- /dev/null +++ b/apps/website/public/t27/specs/fpga/boards/qmtech_a100t_integration.t27 @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/boards/qmtech_a100t_integration.t27 +// QMTech XC7A100T Board-Level Integration Spec +// Full system for QMTech A100T development board +// phi^2 + 1/phi^2 = 3 | TRINITY + +module QMTech_A100T_Integration { + use fpga::top_level::ZeroDSP_TopLevel; + use fpga::gf16_accel::Gf16Accel; + use fpga::memory::Memory; + use fpga::fifo::Fifo; + + const BOARD_NAME : str = "qmtech-xc7a100t"; + const FPGA_PART : str = "xc7a100tcsg324-1"; + const CLOCK_FREQ_HZ : u32 = 12_000_000; + const UART_BAUD : u32 = 115_200; + const MEMORY_SIZE : u32 = 0x8000; + const FIFO_DEPTH : u32 = 8; + const NUM_LEDS : u32 = 8; + const IO_STANDARD : str = "LVCMOS33"; + + struct PinMapping { + clk_pin : str; + rst_pin : str; + uart_tx_pin : str; + uart_rx_pin : str; + led_pins : str; + } + + const QMTECH_PINS : PinMapping = PinMapping{ + .clk_pin = "E3", + .rst_pin = "C18", + .uart_tx_pin = "T15", + .uart_rx_pin = "T14", + .led_pins = "H17,K15,J13,N14,R18,U18,T13,T11", + }; + + struct SystemConfig { + clock_hz : u32; + baud_rate : u32; + mem_size : u32; + fifo_depth : u32; + } + + const SYS_CONFIG : SystemConfig = SystemConfig{ + .clock_hz = 12_000_000, + .baud_rate = 115_200, + .mem_size = 0x8000, + .fifo_depth = 8, + }; + + fn calc_baud_divisor(clock_hz : u32, baud : u32) -> u32 { + return clock_hz / (16 * baud); + } + + test test_baud_divisor { + var divisor : u32 = calc_baud_divisor(CLOCK_FREQ_HZ, UART_BAUD); + invariant divisor > 0; + invariant divisor == 6; + } + + test test_system_config { + invariant SYS_CONFIG.clock_hz == CLOCK_FREQ_HZ; + invariant SYS_CONFIG.mem_size == MEMORY_SIZE; + } + + test test_fpga_part { + invariant FPGA_PART == "xc7a100tcsg324-1"; + } + + test test_clock_freq { + invariant CLOCK_FREQ_HZ == 12_000_000; + } + + test test_clk_pin_is_e3 { + invariant QMTECH_PINS.clk_pin == "E3"; + } + + test test_rst_pin_is_c18 { + invariant QMTECH_PINS.rst_pin == "C18"; + } + + test test_uart_pins_match_xdc { + invariant QMTECH_PINS.uart_tx_pin == "T15"; + invariant QMTECH_PINS.uart_rx_pin == "T14"; + } + + invariant clock_positive : CLOCK_FREQ_HZ > 0; + invariant baud_valid : UART_BAUD > 0 && UART_BAUD <= 921600; + invariant board_name_set : BOARD_NAME != ""; + + bench bench_qmtech_config { + calc_baud_divisor(CLOCK_FREQ_HZ, UART_BAUD); + } +} diff --git a/apps/website/public/t27/specs/fpga/bootrom.t27 b/apps/website/public/t27/specs/fpga/bootrom.t27 new file mode 100644 index 0000000000..53a7b96181 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/bootrom.t27 @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/bootrom.t27 +// T27 Boot ROM Specification +// Boot sequence stages, init vectors, integrity checksum +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module BootROM { + + pub struct BootStage { + name : &str, + index : u32, + size_bytes : u32, + entry_addr : u32, + } + + fn boot_stage(name: &str, idx: u32, size: u32, entry: u32) -> BootStage { + return BootStage{ + .name = name, + .index = idx, + .size_bytes = size, + .entry_addr = entry, + }; + } + + fn stage_end(s: BootStage) -> u32 { + return s.entry_addr + s.size_bytes; + } + + pub struct BootConfig { + name : &str, + rom_base : u32, + rom_size : u32, + has_integrity_check : bool, + has_chain_loader : bool, + } + + fn boot_config(name: &str, rom_size: u32) -> BootConfig { + return BootConfig{ + .name = name, + .rom_base = 0, + .rom_size = rom_size, + .has_integrity_check = true, + .has_chain_loader = true, + }; + } + + fn validate_config(cfg: BootConfig) -> u32 { + var errors : u32 = 0; + if cfg.name == "" { errors = errors + 1; } + if cfg.rom_size == 0 { errors = errors + 1; } + return errors; + } + + fn config_end(cfg: BootConfig) -> u32 { + return cfg.rom_base + cfg.rom_size; + } + + fn fits(cfg: BootConfig, stages: [BootStage], count: u32) -> bool { + var total : u32 = 0; + var i : u32 = 0; + while i < count { + total = total + stages[i].size_bytes; + i = i + 1; + } + return total <= cfg.rom_size; + } + + // === Tests === + + test boot_stage_creation + given s = boot_stage("fsbl", 0, 4096, 0) + then s.name == "fsbl" + and s.index == 0 + and stage_end(s) == 4096 + + test boot_config_creation + given c = boot_config("trinity_boot", 32768) + then c.rom_size == 32768 + and c.has_integrity_check == true + and config_end(c) == 32768 + + test validate_config_ok + given c = boot_config("ok", 4096) + then validate_config(c) == 0 + + test validate_config_empty + given c = BootConfig{.name = "", .rom_base = 0, .rom_size = 0, .has_integrity_check = true, .has_chain_loader = true} + then validate_config(c) > 0 + + test fits_yes + given c = boot_config("test", 8192) + and s1 = boot_stage("s1", 0, 4096, 0) + and s2 = boot_stage("s2", 1, 2048, 4096) + then fits(c, [s1, s2], 2) == true + + test fits_no + given c = boot_config("test", 1024) + and s1 = boot_stage("s1", 0, 4096, 0) + then fits(c, [s1], 1) == false + + test stage_end_nonzero + given s = boot_stage("app", 2, 8192, 0x1000) + then stage_end(s) == 0x1000 + 8192 + + test fits_exact + given c = boot_config("exact", 4096) + and s1 = boot_stage("s1", 0, 4096, 0) + then fits(c, [s1], 1) == true + + test fits_empty_stages + given c = boot_config("empty", 4096) + then fits(c, [], 0) == true + + test validate_config_zero_size + given c = BootConfig{.name = "bad", .rom_base = 0, .rom_size = 0, .has_integrity_check = true, .has_chain_loader = true} + then validate_config(c) > 0 + + test validate_config_valid_name_size + given c = boot_config("valid", 65536) + then validate_config(c) == 0 + + test boot_stage_indexing + given s0 = boot_stage("fsbl", 0, 1024, 0) + and s1 = boot_stage("ssbl", 1, 2048, 1024) + then s0.index == 0 + and s1.index == 1 + and stage_end(s0) == s1.entry_addr + + invariant rom_size_positive + given c = boot_config("inv", 4096) + assert c.rom_size > 0 + + bench fits_check_latency + measure: nanoseconds to fits(boot_config("bench", 8192), [boot_stage("s1", 0, 4096, 0)], 1) + target: < 100ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/bridge.t27 b/apps/website/public/t27/specs/fpga/bridge.t27 new file mode 100644 index 0000000000..113f717029 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/bridge.t27 @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/bridge.t27 +// FPGA Communication Bridge Specification +// Combines UART and SPI for host and peripheral communication +// phi^2 + 1/phi^2 = 3 | TRINITY + +module FPGA_Bridge; + // Import base types and submodules + use base::types; + use fpga::uart::UART_Bridge; + use fpga::spi::SPI_Master; + use fpga::mac::ZeroDSP_MAC; + + // =============================================================== + // 1. Bridge Configuration + // ========================================================================= + + // Buffer configuration + const RX_BUFFER_SIZE : usize = 256; // UART RX buffer size + const TX_BUFFER_SIZE : usize = 256; // UART TX buffer size + const SPI_BUFFER_SIZE : usize = 64; // SPI transfer buffer + + // Protocol configuration + const MAX_PACKET_SIZE : usize = 128; // Max bytes per packet + const PACKET_TIMEOUT : u32 = 10_000; // 10ms timeout (cycles) + + // MAC operation codes (mirrors fpga::mac::ZeroDSP_MAC) + const OP_MAC_MUL : u8 = 0; + const OP_MAC_MAC : u8 = 1; + const OP_MAC_MACC : u8 = 2; + const OP_MAC_DOT : u8 = 3; + const NUM_MAC_UNITS : usize = 8; + + // =============================================================== + // 2. Bridge State + // ========================================================================= + + // Bridge state machine + const BRIDGE_IDLE : u8 = 0; + const BRIDGE_RX : u8 = 1; + const BRIDGE_PARSE : u8 = 2; + const BRIDGE_TX : u8 = 3; + const BRIDGE_SPI : u8 = 4; + const BRIDGE_MAC : u8 = 5; + + // Bridge unit state + struct Bridge_Unit { + state : u8, // Current state + rx_head : usize, // RX buffer head + rx_tail : usize, // RX buffer tail + tx_head : usize, // TX buffer head + tx_tail : usize, // TX buffer tail + + // Packet state + packet_len : u8, // Current packet length + packet_type : u8, // Current packet type + timeout_cnt : u32, // Packet timeout counter + + // Mode selection + spi_enabled : bool, // SPI peripheral mode + mac_enabled : bool, // MAC operation mode + } + + // Initialize bridge + var bridge : Bridge_Unit = Bridge_Unit{ + .state = BRIDGE_IDLE, + .rx_head = 0, + .rx_tail = 0, + .tx_head = 0, + .tx_tail = 0, + .packet_len = 0, + .packet_type = 0, + .timeout_cnt = 0, + .spi_enabled = true, + .mac_enabled = true, + }; + + // =============================================================== + // 3. Buffer Management + // ========================================================================= + + // RX and TX buffers + var rx_buffer : [RX_BUFFER_SIZE]u8 = [0u8; RX_BUFFER_SIZE]; + var tx_buffer : [TX_BUFFER_SIZE]u8 = [0u8; TX_BUFFER_SIZE]; + + // buffer_write(buf: []u8, size: usize, head: usize, data: u8) -> bool + // Write byte to circular buffer + fn buffer_write(buf_in: []u8, size: usize, head: usize, data: u8) -> bool { + const new_head = (head + 1) % size; + if (new_head == 0 && head == size - 1) { + return false; // Buffer full + } + buf_in[head] = data; + return true; + } + + // buffer_read(buf: []u8, size: usize, tail: usize) -> (u8, usize) + // Read byte from circular buffer, return (data, new_tail) + fn buffer_read(buf: []u8, size: usize, tail: usize) -> (u8, usize) { + if (tail == size) { + return (0u8, 0); + } + const data = buf[tail]; + const new_tail = (tail + 1) % size; + return (data, new_tail); + } + + // buffer_count(head: usize, tail: usize, size: usize) -> usize + // Count bytes in circular buffer + fn buffer_count(head: usize, tail: usize, size: usize) -> usize { + if (head >= tail) { + return head - tail; + } else { + return head + size - tail; + } + } + + // bridge_rx_available() -> usize + // Get available bytes in RX buffer + fn bridge_rx_available() -> usize { + return buffer_count(bridge.rx_head, bridge.rx_tail, RX_BUFFER_SIZE); + } + + // bridge_tx_space() -> usize + // Get available space in TX buffer + fn bridge_tx_space() -> usize { + return TX_BUFFER_SIZE - buffer_count(bridge.tx_head, bridge.tx_tail, TX_BUFFER_SIZE); + } + + // =============================================================== + // 4. Packet Protocol + // ========================================================================= + + // Packet types + const PKT_UART_DATA : u8 = 0x00; + const PKT_SPI_XFER : u8 = 0x10; + const PKT_MAC_OP : u8 = 0x20; + const PKT_STATUS : u8 = 0x30; + const PKT_CONFIG : u8 = 0x40; + + // Packet format: [TYPE][LEN][DATA...][CRC] + + // bridge_parse_header() -> bool + // Parse packet header from RX buffer + fn bridge_parse_header() -> bool { + if (bridge_rx_available() < 2) { + return false; + } + + const ptype = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge.rx_tail); + const plen = buffer_read(rx_buffer, RX_BUFFER_SIZE, ptype); + bridge.rx_tail = plen; + + bridge.packet_type = ptype; + bridge.packet_len = plen; + + // Validate packet + if (plen > MAX_PACKET_SIZE) { + return false; // Invalid length + } + + bridge.state = BRIDGE_PARSE; + bridge.timeout_cnt = 0; + return true; + } + + // bridge_process_payload() -> bool + // Process packet payload based on type + fn bridge_process_payload() -> bool { + if (bridge_rx_available() < bridge.packet_len as usize) { + // Wait for more data + bridge.timeout_cnt = bridge.timeout_cnt + 1; + if (bridge.timeout_cnt > PACKET_TIMEOUT) { + // Timeout, reset to idle + bridge.state = BRIDGE_IDLE; + bridge.rx_tail = bridge.rx_head; // Clear buffer + } + return false; + } + + match bridge.packet_type { + PKT_UART_DATA => bridge_handle_uart_data(), + PKT_SPI_XFER => bridge_handle_spi_xfer(), + PKT_MAC_OP => bridge_handle_mac_op(), + PKT_STATUS => bridge_handle_status(), + PKT_CONFIG => bridge_handle_config(), + _ => { + // Unknown packet type + bridge.state = BRIDGE_IDLE; + bridge.rx_tail = bridge.rx_head; + return false; + } + } + + bridge.state = BRIDGE_IDLE; + return true; + } + + // =============================================================== + // 5. Packet Handlers + // ========================================================================= + + // bridge_handle_uart_data() -> void + // Handle UART data packet (echo back) + fn bridge_handle_uart_data() -> void { + var i : usize = 0; + while (i < bridge.packet_len as usize) { + const result_read = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge.rx_tail); + const data = result_read; + bridge.rx_tail = result_read; + + // Echo back via TX + if (bridge_tx_space() > 0) { + const ok = true; + const new_head = (bridge.tx_head + 1) % TX_BUFFER_SIZE; + tx_buffer[bridge.tx_head] = data; + bridge.tx_head = new_head; + } + i = i + 1; + } + } + + // bridge_handle_spi_xfer() -> void + // Handle SPI transfer packet + fn bridge_handle_spi_xfer() -> void { + if (!bridge.spi_enabled || spi_is_busy()) { + return; + } + + const cs_sel = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge.rx_tail); + const data_l = buffer_read(rx_buffer, RX_BUFFER_SIZE, cs_sel); + const data_h = buffer_read(rx_buffer, RX_BUFFER_SIZE, data_l); + bridge.rx_tail = data_h; + + const data = (data_h as u32) << 8 | data_l as u32; + if (spi_transfer(data)) { + // Transfer started, will complete asynchronously + } + } + + // bridge_handle_mac_op() -> void + // Handle MAC operation packet + // Packet format: [OP][UNIT][A_L][A_H][B_L][B_H] + // OP: MAC opcode (0=mul, 1=mac, 2=macc, 3=dot) + // UNIT: MAC unit index (0..7) + // A_L,A_H: operand A (low/high byte of 16-bit value) + // B_L,B_H: operand B (low/high byte of 16-bit value) + fn bridge_handle_mac_op() -> void { + if (!bridge.mac_enabled) { + return; + } + + if (bridge_rx_available() < 6) { + return; + } + + const op_byte = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge.rx_tail); + bridge.rx_tail = op_byte; + + const unit_byte = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge.rx_tail); + bridge.rx_tail = unit_byte; + + const a_l = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge.rx_tail); + bridge.rx_tail = a_l; + + const a_h = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge.rx_tail); + bridge.rx_tail = a_h; + + const b_l = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge.rx_tail); + bridge.rx_tail = b_l; + + const b_h = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge.rx_tail); + bridge.rx_tail = b_h; + + // Validate unit index + if (unit_byte >= NUM_MAC_UNITS) { + return; + } + + // Reconstruct 16-bit operands + const operand_a = (a_h as u16) << 8 | a_l as u16; + const operand_b = (b_h as u16) << 8 | b_l as u16; + + // Dispatch MAC operation + if (op_byte == OP_MAC_MUL) { + mac_multiply(operand_a, operand_b, unit_byte); + } else if (op_byte == OP_MAC_MAC) { + mac_cycle(operand_a, operand_b, unit_byte, mac_get_accumulator(unit_byte)); + } else if (op_byte == OP_MAC_DOT) { + mac_dot_product([operand_a], [operand_b], 1, unit_byte); + } + + // Send result back via TX + if (bridge_tx_space() >= 4) { + const acc = mac_get_accumulator(unit_byte) as u32; + tx_buffer[bridge.tx_head] = (acc & 0xFF) as u8; + bridge.tx_head = (bridge.tx_head + 1) % TX_BUFFER_SIZE; + tx_buffer[bridge.tx_head] = ((acc >> 8) & 0xFF) as u8; + bridge.tx_head = (bridge.tx_head + 1) % TX_BUFFER_SIZE; + tx_buffer[bridge.tx_head] = ((acc >> 16) & 0xFF) as u8; + bridge.tx_head = (bridge.tx_head + 1) % TX_BUFFER_SIZE; + tx_buffer[bridge.tx_head] = ((acc >> 24) & 0xFF) as u8; + bridge.tx_head = (bridge.tx_head + 1) % TX_BUFFER_SIZE; + } + } + + // bridge_handle_status() -> void + // Handle status request + fn bridge_handle_status() -> void { + // Send status response + const status = [ + if (bridge.spi_enabled) 1u8 else 0u8, + if (bridge.mac_enabled) 1u8 else 0u8, + 0u8, 0u8, // Reserved + ]; + + var i : usize = 0; + while (i < 4) { + if (bridge_tx_space() > 0) { + tx_buffer[bridge.tx_head] = status[i]; + bridge.tx_head = (bridge.tx_head + 1) % TX_BUFFER_SIZE; + } + i = i + 1; + } + } + + // bridge_handle_config() -> void + // Handle configuration packet + fn bridge_handle_config() -> void { + const cfg_byte = buffer_read(rx_buffer, RX_BUFFER_SIZE, bridge.rx_tail); + bridge.rx_tail = cfg_byte; + + // Bit 0: SPI enable + // Bit 1: MAC enable + bridge.spi_enabled = (cfg_byte & 0x01) != 0; + bridge.mac_enabled = (cfg_byte & 0x02) != 0; + } + + // =========================================================================================== + // TDD-Inside-Spec: Tests and Invariants for FPGA_Bridge + // =========================================================================================== + + test bridge_initially_idle + given state = bridge.state + then state == BRIDGE_IDLE + + test bridge_rx_buffers_empty + given rx_avail = bridge_rx_available() + then rx_avail == 0 + + test bridge_tx_buffer_full_space + given tx_space = bridge_tx_space() + then tx_space == TX_BUFFER_SIZE + + test bridge_rx_write_success + given result = buffer_write(rx_buffer, RX_BUFFER_SIZE, bridge.rx_head, 0xAA) + then result == true + + test bridge_buffer_count_empty + given count = buffer_count(0, 0, RX_BUFFER_SIZE) + then count == 0 + + test bridge_buffer_count_wrap + given count = buffer_count(RX_BUFFER_SIZE - 1, 0, RX_BUFFER_SIZE) + then count == RX_BUFFER_SIZE - 1 + + test bridge_buffer_count_wrap2 + given count = buffer_count(0, RX_BUFFER_SIZE - 1, RX_BUFFER_SIZE) + then count == 1 + + test bridge_packet_types_defined + given uart_pkt = PKT_UART_DATA + and spi_pkt = PKT_SPI_XFER + and mac_pkt = PKT_MAC_OP + then uart_pkt == 0x00 and spi_pkt == 0x10 and mac_pkt == 0x20 + + test bridge_max_packet_size + given max_pkt = MAX_PACKET_SIZE + then max_pkt == 128 + + test bridge_timeout_defined + given timeout = PACKET_TIMEOUT + then timeout == 10_000 + + test bridge_rx_tx_buffer_sizes + given rx_size = RX_BUFFER_SIZE + and tx_size = TX_BUFFER_SIZE + then rx_size == 256 and tx_size == 256 + + test bridge_spi_enabled_by_default + given spi_en = bridge.spi_enabled + and mac_en = bridge.mac_enabled + then spi_en == true and mac_en == true + + test bridge_parse_header_requires_2_bytes + given bridge_rx_available() == 1 + and result = bridge_parse_header() + then result == false + + test bridge_config_enables_spi + given bridge.handle_config(0x01) + then bridge.spi_enabled == true + + test bridge_config_enables_mac + given bridge.handle_config(0x02) + then bridge.mac_enabled == true + + test bridge_config_disables_spi + given bridge.handle_config(0x00) + then bridge.spi_enabled == false + + test bridge_mac_opcodes_defined + given mul_op = OP_MAC_MUL + and mac_op = OP_MAC_MAC + and macc_op = OP_MAC_MACC + and dot_op = OP_MAC_DOT + then mul_op == 0 and mac_op == 1 and macc_op == 2 and dot_op == 3 + + test bridge_mac_unit_count + given units = NUM_MAC_UNITS + then units == 8 + + test bridge_mac_handler_disabled_when_mac_off + given bridge.mac_enabled = false + and bridge_handle_mac_op() + then bridge.state == BRIDGE_IDLE + + test bridge_mac_handler_rejects_insufficient_data + given bridge.mac_enabled = true + and bridge_rx_available() == 3 + and bridge_handle_mac_op() + then bridge.state == BRIDGE_IDLE + + test bridge_mac_handler_rejects_invalid_unit + given bridge.mac_enabled = true + and bridge_rx_available() >= 6 + and rx_buffer[bridge.rx_tail + 1] == 8 + and bridge_handle_mac_op() + then mac_get_accumulator(0) == 0 + + invariant bridge_states_valid + given state = bridge.state + assert state == BRIDGE_IDLE or state == BRIDGE_RX or state == BRIDGE_PARSE + or state == BRIDGE_TX or state == BRIDGE_SPI or state == BRIDGE_MAC + + invariant bridge_rx_tail_never_exceeds_head + // Circular buffer invariant + assert bridge.rx_head < RX_BUFFER_SIZE and bridge.rx_tail < RX_BUFFER_SIZE + + invariant bridge_tx_tail_never_exceeds_head + assert bridge.tx_head < TX_BUFFER_SIZE and bridge.tx_tail < TX_BUFFER_SIZE + + invariant bridge_rx_available_bounds + given avail = bridge_rx_available() + assert avail <= RX_BUFFER_SIZE + + invariant bridge_tx_space_bounds + given space = bridge_tx_space() + assert space <= TX_BUFFER_SIZE + + invariant bridge_packet_length_bounds + given plen = bridge.packet_len + assert plen <= MAX_PACKET_SIZE + + invariant bridge_timeout_counter_increments + given old_cnt = bridge.timeout_cnt + when bridge.state == BRIDGE_PARSE and bridge_process_payload() == false + then bridge.timeout_cnt >= old_cnt + + invariant bridge_timeout_resets_on_expiration + given bridge.timeout_cnt = PACKET_TIMEOUT + 1 + and bridge.state == BRIDGE_PARSE + when bridge_process_payload() == false + then bridge.state == BRIDGE_IDLE + + invariant bridge_config_affects_flags + given old_spi = bridge.spi_enabled + and old_mac = bridge.mac_enabled + when bridge.handle_config(0x03) + then (bridge.spi_enabled || !bridge.spi_enabled) // May have changed + + invariant bridge_modes_mutable + assert true // SPI and MAC can be toggled at runtime + + bench bridge_rx_write_latency + measure: nanoseconds to buffer_write(rx_buffer, RX_BUFFER_SIZE, bridge.rx_head, 0xAA) + target: < 50ns + + bench bridge_tx_read_latency + measure: nanoseconds to buffer_read(tx_buffer, TX_BUFFER_SIZE, bridge.tx_tail) + target: < 50ns + + bench bridge_parse_header_latency + measure: nanoseconds to bridge_parse_header() when 2 bytes available + target: < 200ns + + bench bridge_packet_processing_latency + measure: nanoseconds to process PKT_STATUS packet + target: < 500ns + diff --git a/apps/website/public/t27/specs/fpga/clock_domain.t27 b/apps/website/public/t27/specs/fpga/clock_domain.t27 new file mode 100644 index 0000000000..b2597da7a8 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/clock_domain.t27 @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/clock_domain.t27 +// Clock Domain Abstraction for Trinity T27 FPGA HIR +// Defines clock sources, PLL configs, and cross-domain crossing +// Uses flat structs (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module ClockDomain { + + // === Clock source kind === + + pub const ClkSrcKind = enum(i8) { + external = 0, + pll = 1, + dcm = 2, + mmcm = 3, + }; + + // === Clock edge === + + pub const ClkEdge = enum(i8) { + posedge = 0, + negedge = 1, + }; + + // === Crossing strategy === + + pub const CrossStrategy = enum(i8) { + no_cross = 0, + two_flop = 1, + fifo_async = 2, + handshake = 3, + }; + + // === Clock source descriptor === + + pub struct ClkSource { + name : &str, + kind : i8, + freq_hz : u32, + phase_deg : u32, + jitter_ps : u32, + } + + // === Clock domain descriptor === + + pub struct ClkDomain { + name : &str, + source_name : &str, + freq_hz : u32, + edge : i8, + } + + // === Cross-domain crossing descriptor === + + pub struct ClockCrossing { + src_domain : &str, + dst_domain : &str, + strategy : i8, + data_width : u32, + } + + // === Constructor helpers === + + fn ext_clock(name: &str, freq_hz: u32) -> ClkSource { + return ClkSource{ + .name = name, + .kind = 0, + .freq_hz = freq_hz, + .phase_deg = 0, + .jitter_ps = 0, + }; + } + + fn pll_clock(name: &str, freq_hz: u32, phase_deg: u32) -> ClkSource { + return ClkSource{ + .name = name, + .kind = 1, + .freq_hz = freq_hz, + .phase_deg = phase_deg, + .jitter_ps = 0, + }; + } + + fn make_domain(name: &str, source_name: &str, freq_hz: u32) -> ClkDomain { + return ClkDomain{ + .name = name, + .source_name = source_name, + .freq_hz = freq_hz, + .edge = 0, + }; + } + + fn make_crossing(src: &str, dst: &str, strategy: i8, data_width: u32) -> ClockCrossing { + return ClockCrossing{ + .src_domain = src, + .dst_domain = dst, + .strategy = strategy, + .data_width = data_width, + }; + } + + // === Query functions === + + fn is_external(src: ClkSource) -> bool { + return src.kind == 0; + } + + fn is_pll(src: ClkSource) -> bool { + return src.kind == 1; + } + + fn period_ns(domain: ClkDomain) -> u32 { + if (domain.freq_hz == 0) { + return 0; + } + return 1000000000 / domain.freq_hz; + } + + fn half_period_ns(domain: ClkDomain) -> u32 { + return period_ns(domain) / 2; + } + + fn same_domain(a: ClkDomain, b: ClkDomain) -> bool { + return a.name == b.name; + } + + fn needs_crossing(a: ClkDomain, b: ClkDomain) -> bool { + if (a.name == b.name) { + return false; + } + if (a.freq_hz == b.freq_hz and a.source_name == b.source_name) { + return false; + } + return true; + } + + fn crossing_data_bits(cross: ClockCrossing) -> u32 { + return cross.data_width; + } + + fn is_async_cross(cross: ClockCrossing) -> bool { + return cross.strategy == 2; + } + + // === Tests === + + test ext_clock_is_external + given c = ext_clock("sys_clk", 12000000) + then is_external(c) == true + and is_pll(c) == false + + test pll_clock_is_pll + given c = pll_clock("pll_clk", 100000000, 0) + then is_pll(c) == true + and is_external(c) == false + + test period_12mhz + given d = make_domain("sys", "sys_clk", 12000000) + then period_ns(d) == 83 + + test period_100mhz + given d = make_domain("fast", "pll_clk", 100000000) + then period_ns(d) == 10 + + test half_period + given d = make_domain("sys", "sys_clk", 12000000) + then half_period_ns(d) == 41 + + test same_domain_true + given a = make_domain("sys", "clk", 12000000) + then same_domain(a, a) == true + + test same_domain_false + given a = make_domain("sys", "clk", 12000000) + and b = make_domain("fast", "pll", 100000000) + then same_domain(a, b) == false + + test needs_crossing_diff_freq + given a = make_domain("sys", "clk", 12000000) + and b = make_domain("fast", "pll", 100000000) + then needs_crossing(a, b) == true + + test needs_crossing_same + given a = make_domain("sys", "clk", 12000000) + then needs_crossing(a, a) == false + + test crossing_data_bits + given c = make_crossing("sys", "fast", 1, 32) + then crossing_data_bits(c) == 32 + + test async_cross_fifo + given c = make_crossing("sys", "fast", 2, 16) + then is_async_cross(c) == true + + test sync_cross_not_async + given c = make_crossing("sys", "fast", 1, 8) + then is_async_cross(c) == false + + // === Invariants === + + invariant ext_clock_has_freq + given c = ext_clock("inv", 12000000) + assert c.freq_hz > 0 + + invariant period_positive_for_valid_freq + given d = make_domain("inv", "clk", 12000000) + assert period_ns(d) > 0 + + invariant half_period_half_of_period + given d = make_domain("inv", "clk", 12000000) + assert half_period_ns(d) == period_ns(d) / 2 + + invariant same_domain_reflexive + given d = make_domain("inv", "clk", 12000000) + assert same_domain(d, d) == true + + invariant needs_crossing_symmetric + given a = make_domain("a", "clk", 12000000) + and b = make_domain("b", "pll", 100000000) + assert needs_crossing(a, b) == needs_crossing(b, a) + + // === Benchmarks === + + bench period_ns_latency + measure: nanoseconds to period_ns(make_domain("b", "c", 100000000)) + target: < 50ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/crossopt.t27 b/apps/website/public/t27/specs/fpga/crossopt.t27 new file mode 100644 index 0000000000..dd2a701b7a --- /dev/null +++ b/apps/website/public/t27/specs/fpga/crossopt.t27 @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/crossopt.t27 +// T27 Cross-Module Optimization Specification +// Inter-module constant propagation, dead signal elimination, instance merging +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module CrossOpt { + + pub struct CrossOptPass { + name : &str, + num_modules : u32, + constants_propagated : u32, + dead_signals_removed : u32, + instances_merged : u32, + } + + fn pass_zero() -> CrossOptPass { + return CrossOptPass{ + .name = "empty", + .num_modules = 0, + .constants_propagated = 0, + .dead_signals_removed = 0, + .instances_merged = 0, + }; + } + + fn pass_result(name: &str, mods: u32, consts: u32, dead: u32, merged: u32) -> CrossOptPass { + return CrossOptPass{ + .name = name, + .num_modules = mods, + .constants_propagated = consts, + .dead_signals_removed = dead, + .instances_merged = merged, + }; + } + + fn total_improvements(p: CrossOptPass) -> u32 { + return p.constants_propagated + p.dead_signals_removed + p.instances_merged; + } + + fn has_improvements(p: CrossOptPass) -> bool { + return total_improvements(p) > 0; + } + + fn improvement_density(p: CrossOptPass) -> u32 { + if p.num_modules == 0 { + return 0; + } + return total_improvements(p) / p.num_modules; + } + + pub struct CrossOptReport { + total_passes : u32, + total_constants : u32, + total_dead : u32, + total_merged : u32, + total_modules : u32, + } + + fn report_ok(passes: u32, consts: u32, dead: u32, merged: u32, mods: u32) -> CrossOptReport { + return CrossOptReport{ + .total_passes = passes, + .total_constants = consts, + .total_dead = dead, + .total_merged = merged, + .total_modules = mods, + }; + } + + fn total_optimizations(r: CrossOptReport) -> u32 { + return r.total_constants + r.total_dead + r.total_merged; + } + + fn is_effective(r: CrossOptReport) -> bool { + return total_optimizations(r) > 0; + } + + // === Tests === + + test pass_zero + given p = pass_zero() + then p.num_modules == 0 + and total_improvements(p) == 0 + and has_improvements(p) == false + + test pass_result_creation + given p = pass_result("const_prop", 3, 10, 5, 2) + then p.name == "const_prop" + and p.num_modules == 3 + and total_improvements(p) == 17 + and has_improvements(p) == true + + test improvement_density + given p = pass_result("opt", 5, 20, 10, 5) + then improvement_density(p) == 7 + + test improvement_density_zero + given p = pass_zero() + then improvement_density(p) == 0 + + test report_creation + given r = report_ok(3, 30, 15, 5, 10) + then r.total_passes == 3 + and total_optimizations(r) == 50 + and is_effective(r) == true + + test report_empty + given r = report_ok(0, 0, 0, 0, 0) + then is_effective(r) == false + + invariant improvements_non_negative + given p = pass_zero() + assert total_improvements(p) >= 0 + + bench improvement_density_latency + measure: nanoseconds to improvement_density(pass_result("bench", 5, 20, 10, 5)) + target: < 50ns + + bench total_improvements_latency + measure: nanoseconds to total_improvements(pass_result("bench", 10, 50, 25, 10)) + target: < 50ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/cts.t27 b/apps/website/public/t27/specs/fpga/cts.t27 new file mode 100644 index 0000000000..91c6f9ab39 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/cts.t27 @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/cts.t27 +// T27 Clock Tree Synthesis Specification +// PLL configuration, clock buffer trees, skew estimation +// Artix-7: BUFH=0.05ns, BUFG=0.1ns, PLL jitter=50ps, max skew=100ps +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module CTS { + + pub struct PllConfig { + name : &str, + input_mhz : u32, + output_mhz : u32, + multiply : u32, + divide : u32, + jitter_ps : u32, + } + + fn pll_config(name: &str, input_mhz: u32, output_mhz: u32) -> PllConfig { + var m : u32 = 1; + var d : u32 = 1; + if input_mhz > 0 { + d = input_mhz; + m = output_mhz; + } + return PllConfig{ + .name = name, + .input_mhz = input_mhz, + .output_mhz = output_mhz, + .multiply = m, + .divide = d, + .jitter_ps = 50, + }; + } + + fn pll_period_ps(pll: PllConfig) -> u32 { + if pll.output_mhz == 0 { + return 0; + } + return 1000000000 / pll.output_mhz; + } + + pub struct ClockBuffer { + name : &str, + delay_ps : u32, + fanout : u32, + } + + fn bufg(name: &str) -> ClockBuffer { + return ClockBuffer{ .name = name, .delay_ps = 100, .fanout = 32 }; + } + + fn bufh(name: &str) -> ClockBuffer { + return ClockBuffer{ .name = name, .delay_ps = 50, .fanout = 16 }; + } + + fn bufg_has_higher_fanout(b: ClockBuffer) -> bool { + return b.fanout >= 32; + } + + pub struct ClockTree { + root : &str, + num_levels : u32, + total_buffers : u32, + max_skew_ps : u32, + } + + fn clock_tree(root: &str, levels: u32, bufs: u32) -> ClockTree { + return ClockTree{ + .root = root, + .num_levels = levels, + .total_buffers = bufs, + .max_skew_ps = 100, + }; + } + + fn tree_delay_ps(tree: ClockTree, buf_delay: u32) -> u32 { + return tree.num_levels * buf_delay; + } + + fn skew_ok(tree: ClockTree, max_allowed_ps: u32) -> bool { + return tree.max_skew_ps <= max_allowed_ps; + } + + pub struct CtsReport { + num_clocks : u32, + num_plls : u32, + total_buffers : u32, + worst_skew_ps : u32, + worst_latency_ps : u32, + has_violations : bool, + } + + fn cts_ok(clocks: u32, plls: u32, bufs: u32, skew: u32, latency: u32) -> CtsReport { + return CtsReport{ + .num_clocks = clocks, + .num_plls = plls, + .total_buffers = bufs, + .worst_skew_ps = skew, + .worst_latency_ps = latency, + .has_violations = false, + }; + } + + fn passed(r: CtsReport) -> bool { + return r.has_violations == false; + } + + // === Auto tree estimation === + + fn est_buffers_needed(num_sinks: u32) -> u32 { + if num_sinks <= 16 { + return 1; + } + return num_sinks / 16 + 1; + } + + fn est_tree_levels(num_sinks: u32) -> u32 { + if num_sinks <= 16 { + return 1; + } + if num_sinks <= 256 { + return 2; + } + return 3; + } + + // === Validation === + + fn validate_pll(pll: PllConfig) -> u32 { + var errors : u32 = 0; + if pll.name == "" { errors = errors + 1; } + if pll.output_mhz == 0 { errors = errors + 1; } + return errors; + } + + // === Tests === + + test pll_config_creation + given p = pll_config("sys_pll", 100, 200) + then p.input_mhz == 100 + and p.output_mhz == 200 + and pll_period_ps(p) == 5000000 + + test bufg_creation + given b = bufg("clk_buf") + then b.delay_ps == 100 + and b.fanout == 32 + and bufg_has_higher_fanout(b) == true + + test bufh_creation + given b = bufh("clk_h") + then b.delay_ps == 50 + and b.fanout == 16 + and bufg_has_higher_fanout(b) == false + + test clock_tree_creation + given t = clock_tree("clk", 2, 5) + then t.root == "clk" + and t.num_levels == 2 + and t.total_buffers == 5 + and t.max_skew_ps == 100 + + test tree_delay + given t = clock_tree("clk", 3, 8) + then tree_delay_ps(t, 100) == 300 + + test skew_ok_yes + given t = clock_tree("clk", 2, 5) + then skew_ok(t, 200) == true + + test skew_ok_no + given t = clock_tree("clk", 2, 5) + then skew_ok(t, 50) == false + + test cts_report_ok + given r = cts_ok(2, 1, 10, 80, 300) + then passed(r) == true + and r.has_violations == false + + test est_buffers_one + then est_buffers_needed(10) == 1 + + test est_buffers_many + then est_buffers_needed(100) == 7 + + test est_tree_levels_one + then est_tree_levels(10) == 1 + + test est_tree_levels_two + then est_tree_levels(100) == 2 + + test est_tree_levels_three + then est_tree_levels(500) == 3 + + test validate_pll_ok + given p = pll_config("ok", 100, 200) + then validate_pll(p) == 0 + + test validate_pll_empty + given p = PllConfig{.name = "", .input_mhz = 100, .output_mhz = 0, .multiply = 1, .divide = 1, .jitter_ps = 50} + then validate_pll(p) > 0 + + // === Invariants === + + invariant bufg_delay_positive + given b = bufg("inv") + assert b.delay_ps > 0 + + invariant skew_non_negative + given t = clock_tree("inv", 2, 5) + assert t.max_skew_ps >= 0 + + bench buffer_estimation_latency + measure: nanoseconds to est_buffers_needed(50) + target: < 50ns + + bench tree_level_estimation_latency + measure: nanoseconds to est_tree_levels(200) + target: < 50ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/dft.t27 b/apps/website/public/t27/specs/fpga/dft.t27 new file mode 100644 index 0000000000..35818b8a5f --- /dev/null +++ b/apps/website/public/t27/specs/fpga/dft.t27 @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/dft.t27 +// T27 Design-for-Test Specification +// Scan chains, BIST controllers, JTAG TAP, test coverage estimation +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module DFT { + + // === Scan chain === + + pub struct ScanChain { + name : &str, + num_regs : u32, + chain_length_bits : u32, + } + + fn scan_chain(name: &str, regs: u32) -> ScanChain { + return ScanChain{ + .name = name, + .num_regs = regs, + .chain_length_bits = regs * 32, + }; + } + + fn scan_chain_cycles(chain: ScanChain) -> u32 { + return chain.chain_length_bits + 10; + } + + fn scan_chain_bytes(chain: ScanChain) -> u32 { + return chain.chain_length_bits / 8; + } + + // === BIST kind === + + pub const BistKind = enum(i8) { + memory_bist = 0, + logic_bist = 1, + io_bist = 2, + } + + // === BIST controller === + + pub struct BistCtrl { + name : &str, + kind : i8, + patterns : u32, + pass_threshold : u32, + } + + fn memory_bist(name: &str, patterns: u32) -> BistCtrl { + return BistCtrl{ + .name = name, + .kind = 0, + .patterns = patterns, + .pass_threshold = patterns, + }; + } + + fn logic_bist(name: &str, patterns: u32) -> BistCtrl { + return BistCtrl{ + .name = name, + .kind = 1, + .patterns = patterns, + .pass_threshold = patterns, + }; + } + + fn bist_cycles(ctrl: BistCtrl) -> u32 { + return ctrl.patterns * 2; + } + + fn bist_coverage(ctrl: BistCtrl, total_faults: u32) -> u32 { + if total_faults == 0 { + return 100; + } + return ctrl.patterns * 100 / total_faults; + } + + // === JTAG TAP === + + pub struct JtagTap { + name : &str, + ir_width : u32, + num_dr_regs : u32, + bypass_code : u32, + idcode : u32, + } + + fn jtag_tap(name: &str, ir_width: u32, idcode: u32) -> JtagTap { + return JtagTap{ + .name = name, + .ir_width = ir_width, + .num_dr_regs = 3, + .bypass_code = 255, + .idcode = idcode, + }; + } + + fn tap_total_bits(tap: JtagTap) -> u32 { + return tap.ir_width + 32 * tap.num_dr_regs; + } + + fn tap_state_count() -> u32 { + return 16; + } + + // === Test coverage === + + pub struct TestCoverage { + scan_coverage : u32, + bist_coverage : u32, + atpg_coverage : u32, + total_coverage : u32, + } + + fn test_coverage(scan: u32, bist: u32, atpg: u32) -> TestCoverage { + return TestCoverage{ + .scan_coverage = scan, + .bist_coverage = bist, + .atpg_coverage = atpg, + .total_coverage = (scan + bist + atpg) / 3, + }; + } + + fn is_acceptable(cov: TestCoverage) -> bool { + return cov.total_coverage >= 90; + } + + // === Validation === + + fn validate_chain(chain: ScanChain) -> u32 { + var errors : u32 = 0; + if chain.name == "" { + errors = errors + 1; + } + if chain.num_regs == 0 { + errors = errors + 1; + } + return errors; + } + + fn validate_bist(ctrl: BistCtrl) -> u32 { + var errors : u32 = 0; + if ctrl.name == "" { + errors = errors + 1; + } + if ctrl.patterns == 0 { + errors = errors + 1; + } + return errors; + } + + fn validate_tap(tap: JtagTap) -> u32 { + var errors : u32 = 0; + if tap.name == "" { + errors = errors + 1; + } + if tap.ir_width == 0 { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test scan_chain_creation + given c = scan_chain("core_chain", 100) + then c.num_regs == 100 + and c.chain_length_bits == 3200 + and scan_chain_cycles(c) == 3210 + and scan_chain_bytes(c) == 400 + + test memory_bist_creation + given b = memory_bist("bram_bist", 8) + then b.kind == 0 + and b.patterns == 8 + and bist_cycles(b) == 16 + + test logic_bist_creation + given b = logic_bist("logic_bist", 16) + then b.kind == 1 + and b.patterns == 16 + + test bist_coverage_full + given b = memory_bist("bist", 100) + then bist_coverage(b, 100) == 100 + + test bist_coverage_partial + given b = memory_bist("bist", 50) + then bist_coverage(b, 200) == 25 + + test bist_coverage_zero_faults + given b = memory_bist("bist", 10) + then bist_coverage(b, 0) == 100 + + test jtag_tap_creation + given t = jtag_tap("main_tap", 8, 305419896) + then t.ir_width == 8 + and t.num_dr_regs == 3 + and t.bypass_code == 255 + and tap_total_bits(t) == 104 + + test tap_state_count + then tap_state_count() == 16 + + test test_coverage_creation + given c = test_coverage(95, 90, 85) + then c.scan_coverage == 95 + and c.bist_coverage == 90 + and c.atpg_coverage == 85 + and c.total_coverage == 90 + + test test_coverage_acceptable + given c = test_coverage(95, 95, 90) + then is_acceptable(c) == true + + test test_coverage_not_acceptable + given c = test_coverage(80, 80, 80) + then is_acceptable(c) == false + + test validate_chain_ok + given c = scan_chain("ok", 10) + then validate_chain(c) == 0 + + test validate_chain_empty + given c = ScanChain{.name = "", .num_regs = 0, .chain_length_bits = 0} + then validate_chain(c) > 0 + + test validate_bist_ok + given b = memory_bist("ok", 8) + then validate_bist(b) == 0 + + test validate_bist_empty + given b = BistCtrl{.name = "", .kind = 0, .patterns = 0, .pass_threshold = 0} + then validate_bist(b) > 0 + + test validate_tap_ok + given t = jtag_tap("ok", 4, 0) + then validate_tap(t) == 0 + + test validate_tap_empty + given t = JtagTap{.name = "", .ir_width = 0, .num_dr_regs = 0, .bypass_code = 0, .idcode = 0} + then validate_tap(t) > 0 + + // === Invariants === + + invariant scan_cycles_positive + given c = scan_chain("inv", 10) + assert scan_chain_cycles(c) > 0 + + invariant coverage_bounded + given c = test_coverage(100, 100, 100) + assert c.total_coverage <= 100 + + // === Benchmarks === + + bench dft_latency + measure: nanoseconds for scan_chain("bench", 1000) + target: < 50ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/e2e_demo.t27 b/apps/website/public/t27/specs/fpga/e2e_demo.t27 new file mode 100644 index 0000000000..7f70259e34 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/e2e_demo.t27 @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/e2e_demo.t27 +// T27 End-to-End Demo Specification +// Exercises the full toolchain: assembler -> ternary core -> GF16 -> VCD trace +// Validates the complete FPGA pipeline from spec to hardware simulation +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module E2eDemo { + + // === Demo program (trivial ternary kernel) === + + pub struct DemoKernel { + name : &str, + instr_count : u32, + gf16_ops : u32, + alu_ops : u32, + mem_ops : u32, + } + + fn hello_kernel() -> DemoKernel { + return DemoKernel{ + .name = "hello_trinity", + .instr_count = 12, + .gf16_ops = 4, + .alu_ops = 6, + .mem_ops = 2, + }; + } + + fn gf16_mac_kernel() -> DemoKernel { + return DemoKernel{ + .name = "gf16_mac_demo", + .instr_count = 20, + .gf16_ops = 10, + .alu_ops = 6, + .mem_ops = 4, + }; + } + + // === Pipeline simulation result === + + pub struct PipeResult { + cycles : u32, + instr_retired : u32, + stalls : u32, + gf16_results : u32, + errors : u32, + } + + fn pipe_result_ok(cycles: u32, retired: u32, gf16: u32) -> PipeResult { + return PipeResult{ + .cycles = cycles, + .instr_retired = retired, + .stalls = 0, + .gf16_results = gf16, + .errors = 0, + }; + } + + fn pipe_result_error(cycles: u32, errors: u32) -> PipeResult { + return PipeResult{ + .cycles = cycles, + .instr_retired = 0, + .stalls = 0, + .gf16_results = 0, + .errors = errors, + }; + } + + // === Demo config === + + pub struct DemoConfig { + kernel : DemoKernel, + clock_mhz : u32, + max_cycles : u32, + trace_enabled : bool, + formal_check : bool, + } + + fn demo_config(kernel: DemoKernel) -> DemoConfig { + return DemoConfig{ + .kernel = kernel, + .clock_mhz = 100, + .max_cycles = 100000, + .trace_enabled = true, + .formal_check = true, + }; + } + + // === Query functions === + + fn ipc(result: PipeResult) -> u32 { + if result.cycles == 0 { + return 0; + } + return result.instr_retired * 100 / result.cycles; + } + + fn cpi(result: PipeResult) -> u32 { + if result.instr_retired == 0 { + return 0; + } + return result.cycles / result.instr_retired; + } + + fn gf16_throughput(result: PipeResult, clock_mhz: u32) -> u32 { + if result.cycles == 0 { + return 0; + } + return result.gf16_results * clock_mhz * 1000 / result.cycles; + } + + fn sim_time_us(cfg: DemoConfig, cycles: u32) -> u32 { + if cfg.clock_mhz == 0 { + return 0; + } + return cycles / cfg.clock_mhz; + } + + fn kernel_size_bytes(kernel: DemoKernel) -> u32 { + return kernel.instr_count * 4; + } + + fn passed(result: PipeResult) -> bool { + return result.errors == 0 and result.instr_retired > 0; + } + + // === Validation === + + fn validate_kernel(kernel: DemoKernel) -> u32 { + var errors : u32 = 0; + if kernel.name == "" { + errors = errors + 1; + } + if kernel.instr_count == 0 { + errors = errors + 1; + } + return errors; + } + + fn validate_config(cfg: DemoConfig) -> u32 { + var errors : u32 = 0; + errors = errors + validate_kernel(cfg.kernel); + if cfg.clock_mhz == 0 { + errors = errors + 1; + } + if cfg.max_cycles == 0 { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test hello_kernel_creation + given k = hello_kernel() + then k.name == "hello_trinity" + and k.instr_count == 12 + and k.gf16_ops == 4 + and k.alu_ops == 6 + and k.mem_ops == 2 + + test gf16_mac_kernel_creation + given k = gf16_mac_kernel() + then k.name == "gf16_mac_demo" + and k.instr_count == 20 + and k.gf16_ops == 10 + + test pipe_result_ok + given r = pipe_result_ok(100, 95, 10) + then r.cycles == 100 + and r.instr_retired == 95 + and r.stalls == 0 + and r.gf16_results == 10 + and r.errors == 0 + + test pipe_result_error + given r = pipe_result_error(50, 2) + then r.errors == 2 + and r.instr_retired == 0 + + test ipc_calculation + given r = pipe_result_ok(100, 50, 0) + then ipc(r) == 50 + + test ipc_zero_cycles + given r = pipe_result_ok(0, 0, 0) + then ipc(r) == 0 + + test cpi_calculation + given r = pipe_result_ok(200, 100, 0) + then cpi(r) == 2 + + test cpi_zero_retired + given r = pipe_result_ok(100, 0, 0) + then cpi(r) == 0 + + test gf16_throughput_calc + given r = pipe_result_ok(1000, 500, 100) + then gf16_throughput(r, 100) == 10000 + + test sim_time_us + given cfg = demo_config(hello_kernel()) + then sim_time_us(cfg, 100000) == 1000 + + test kernel_size_bytes + given k = hello_kernel() + then kernel_size_bytes(k) == 48 + + test passed_ok + given r = pipe_result_ok(100, 50, 10) + then passed(r) == true + + test passed_with_errors + given r = pipe_result_error(100, 1) + then passed(r) == false + + test validate_hello_kernel + given k = hello_kernel() + then validate_kernel(k) == 0 + + test validate_empty_kernel + given k = DemoKernel{.name = "", .instr_count = 0, .gf16_ops = 0, .alu_ops = 0, .mem_ops = 0} + then validate_kernel(k) > 0 + + test validate_demo_config + given cfg = demo_config(hello_kernel()) + then validate_config(cfg) == 0 + + // === Invariants === + + invariant kernel_size_positive + given k = hello_kernel() + assert kernel_size_bytes(k) > 0 + + invariant ipc_non_negative + given r = pipe_result_ok(100, 50, 0) + assert ipc(r) >= 0 + + invariant sim_time_non_negative + given cfg = demo_config(hello_kernel()) + assert sim_time_us(cfg, 100000) >= 0 + + // === Benchmarks === + + bench e2e_latency + measure: nanoseconds for pipe_result_ok(1000, 500, 50) + target: < 100ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/fifo.t27 b/apps/website/public/t27/specs/fpga/fifo.t27 new file mode 100644 index 0000000000..42589e36ab --- /dev/null +++ b/apps/website/public/t27/specs/fpga/fifo.t27 @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/fifo.t27 +// Synchronous and Asynchronous FIFO Stdlib for Trinity T27 FPGA HIR +// Defines FIFO configuration with depth, data width, and flags +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Fifo { + + // === FIFO kind === + + pub const FifoKind = enum(i8) { + sync_fifo = 0, + async_fifo = 1, + } + + // === FIFO status flags === + + pub struct FifoFlags { + empty : bool, + full : bool, + almost_empty : bool, + almost_full : bool, + } + + // === FIFO configuration === + + pub struct FifoConfig { + name : &str, + kind : i8, + depth : u32, + data_width : u32, + has_almost_empty : bool, + has_almost_full : bool, + almost_empty_threshold : u32, + almost_full_threshold : u32, + use_bram : bool, + } + + // === FIFO runtime state (for simulation) === + + pub const MAX_FIFO_DEPTH : u32 = 65536; + + pub struct FifoState { + fill_count : u32, + head_ptr : u32, + tail_ptr : u32, + flags : FifoFlags, + } + + // === Constructor helpers === + + fn sync_fifo(name: &str, depth: u32, data_width: u32) -> FifoConfig { + return FifoConfig{ + .name = name, + .kind = 0, + .depth = depth, + .data_width = data_width, + .has_almost_empty = false, + .has_almost_full = false, + .almost_empty_threshold = 0, + .almost_full_threshold = 0, + .use_bram = true, + }; + } + + fn async_fifo(name: &str, depth: u32, data_width: u32) -> FifoConfig { + return FifoConfig{ + .name = name, + .kind = 1, + .depth = depth, + .data_width = data_width, + .has_almost_empty = false, + .has_almost_full = false, + .almost_empty_threshold = 0, + .almost_full_threshold = 0, + .use_bram = true, + }; + } + + fn with_almost_empty(cfg: FifoConfig, threshold: u32) -> FifoConfig { + var result = cfg; + result.has_almost_empty = true; + result.almost_empty_threshold = threshold; + return result; + } + + fn with_almost_full(cfg: FifoConfig, threshold: u32) -> FifoConfig { + var result = cfg; + result.has_almost_full = true; + result.almost_full_threshold = threshold; + return result; + } + + fn empty_fifo_state() -> FifoState { + return FifoState{ + .fill_count = 0, + .head_ptr = 0, + .tail_ptr = 0, + .flags = FifoFlags{ + .empty = true, + .full = false, + .almost_empty = false, + .almost_full = false, + }, + }; + } + + // === Query functions === + + fn is_sync(cfg: FifoConfig) -> bool { + return cfg.kind == 0; + } + + fn is_async(cfg: FifoConfig) -> bool { + return cfg.kind == 1; + } + + fn addr_width(cfg: FifoConfig) -> u32 { + var d : u32 = cfg.depth; + var w : u32 = 0; + while (d > 1) { + w = w + 1; + d = d / 2; + } + if (w == 0) { + w = 1; + } + return w; + } + + fn total_storage_bits(cfg: FifoConfig) -> u32 { + return cfg.depth * cfg.data_width; + } + + fn bram18_count(cfg: FifoConfig) -> u32 { + var bits : u32 = cfg.depth * cfg.data_width; + var count : u32 = bits / 18432; + if (bits % 18432 > 0) { + count = count + 1; + } + return count; + } + + fn is_empty(state: FifoState) -> bool { + return state.flags.empty; + } + + fn is_full(state: FifoState) -> bool { + return state.flags.full; + } + + fn fill_count(state: FifoState) -> u32 { + return state.fill_count; + } + + fn has_space(state: FifoState) -> bool { + return state.flags.full == false; + } + + fn has_data(state: FifoState) -> bool { + return state.flags.empty == false; + } + + // === Push/Pop state updates === + + fn push(state: FifoState, cfg: FifoConfig) -> FifoState { + var result = state; + if (state.flags.full) { + return result; + } + result.tail_ptr = (state.tail_ptr + 1) % cfg.depth; + result.fill_count = state.fill_count + 1; + result.flags.empty = false; + if (result.fill_count == cfg.depth) { + result.flags.full = true; + } + return result; + } + + fn pop(state: FifoState, cfg: FifoConfig) -> FifoState { + var result = state; + if (state.flags.empty) { + return result; + } + result.head_ptr = (state.head_ptr + 1) % cfg.depth; + result.fill_count = state.fill_count - 1; + result.flags.full = false; + if (result.fill_count == 0) { + result.flags.empty = true; + } + return result; + } + + // === Validation === + + fn validate_fifo(cfg: FifoConfig) -> u32 { + var errors : u32 = 0; + + if (cfg.name == "") { + errors = errors + 1; + } + if (cfg.depth == 0 { + errors = errors + 1; + } + if (cfg.data_width == 0 { + errors = errors + 1; + } + if (cfg.has_almost_empty and cfg.almost_empty_threshold >= cfg.depth { + errors = errors + 1; + } + if (cfg.has_almost_full and cfg.almost_full_threshold >= cfg.depth { + errors = errors + 1; + } + + return errors; + } + + // === Tests === + + test sync_fifo_is_sync + given f = sync_fifo("tx_fifo", 16, 8) + then is_sync(f) == true + and is_async(f) == false + + test async_fifo_is_async + given f = async_fifo("cross_fifo", 32, 16) + then is_async(f) == true + and is_sync(f) == false + + test addr_width_16 + given f = sync_fifo("f", 16, 8) + then addr_width(f) == 4 + + test addr_width_256 + given f = sync_fifo("f", 256, 32) + then addr_width(f) == 8 + + test total_storage_bits + given f = sync_fifo("f", 16, 32) + then total_storage_bits(f) == 512 + + test bram18_small + given f = sync_fifo("f", 16, 32) + then bram18_count(f) == 1 + + test empty_state_is_empty + given s = empty_fifo_state() + then is_empty(s) == true + and is_full(s) == false + and fill_count(s) == 0 + and has_data(s) == false + and has_space(s) == true + + test push_increments_fill + given cfg = sync_fifo("f", 4, 8) + and s = empty_fifo_state() + and s2 = push(s, cfg) + then fill_count(s2) == 1 + and is_empty(s2) == false + + test push_to_full + given cfg = sync_fifo("f", 2, 8) + and s = empty_fifo_state() + and s2 = push(s, cfg) + and s3 = push(s2, cfg) + then is_full(s3) == true + and fill_count(s3) == 2 + + test push_on_full_noop + given cfg = sync_fifo("f", 2, 8) + and s = empty_fifo_state() + and s2 = push(s, cfg) + and s3 = push(s2, cfg) + and s4 = push(s3, cfg) + then fill_count(s4) == 2 + + test pop_decrements_fill + given cfg = sync_fifo("f", 4, 8) + and s = empty_fifo_state() + and s2 = push(s, cfg) + and s3 = pop(s2, cfg) + then fill_count(s3) == 0 + and is_empty(s3) == true + + test pop_on_empty_noop + given cfg = sync_fifo("f", 4, 8) + and s = empty_fifo_state() + and s2 = pop(s, cfg) + then fill_count(s2) == 0 + + test push_pop_roundtrip + given cfg = sync_fifo("f", 4, 8) + and s = empty_fifo_state() + and s2 = push(s, cfg) + and s3 = push(s2, cfg) + and s4 = pop(s3, cfg) + then fill_count(s4) == 1 + and has_data(s4) == true + and has_space(s4) == true + + test with_almost_empty + given f = sync_fifo("f", 16, 8) + and f2 = with_almost_empty(f, 2) + then f2.has_almost_empty == true + and f2.almost_empty_threshold == 2 + + test with_almost_full + given f = sync_fifo("f", 16, 8) + and f2 = with_almost_full(f, 14) + then f2.has_almost_full == true + and f2.almost_full_threshold == 14 + + test validate_ok + given f = sync_fifo("f", 16, 8) + then validate_fifo(f) == 0 + + test validate_empty_name + given f = sync_fifo("", 16, 8) + then validate_fifo(f) > 0 + + test validate_zero_depth + given f = FifoConfig{.name = "x", .kind = 0, .depth = 0, .data_width = 8, .has_almost_empty = false, .has_almost_full = false, .almost_empty_threshold = 0, .almost_full_threshold = 0, .use_bram = true} + then validate_fifo(f) > 0 + + // === Invariants === + + invariant fill_count_non_negative + given s = empty_fifo_state() + assert fill_count(s) >= 0 + + invariant fill_count_le_depth + given cfg = sync_fifo("inv", 16, 8) + and s = push(empty_fifo_state(), cfg) + assert fill_count(s) <= cfg.depth + + invariant empty_xor_full + given s = empty_fifo_state() + assert (is_empty(s) and is_full(s)) == false + + invariant has_space_iff_not_full + given s = empty_fifo_state() + assert has_space(s) == (is_full(s) == false) + + invariant has_data_iff_not_empty + given s = empty_fifo_state() + assert has_data(s) == (is_empty(s) == false) + + invariant bram18_positive + given f = sync_fifo("inv", 16, 8) + assert bram18_count(f) > 0 + + // === Benchmarks === + + bench push_latency + measure: nanoseconds to push(empty_fifo_state(), sync_fifo("b", 16, 8)) + target: < 100ns + + bench pop_latency + measure: nanoseconds to pop(push(empty_fifo_state(), sync_fifo("b", 16, 8)), sync_fifo("b", 16, 8)) + target: < 100ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/formal.t27 b/apps/website/public/t27/specs/fpga/formal.t27 new file mode 100644 index 0000000000..bc053976d6 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/formal.t27 @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/formal.t27 +// Formal Verification Specification for Trinity T27 FPGA HIR +// Defines assertion kinds, properties, and coverage points +// Generates SystemVerilog Assertions (SVA) alongside Verilog +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Formal { + + // === Assertion kind === + + pub const AssertKind = enum(i8) { + immediate = 0, + concurrent = 1, + cover = 2, + assume = 3, + } + + // === Assertion severity === + + pub const AssertSeverity = enum(i8) { + info = 0, + warning = 1, + error = 2, + fatal = 3, + } + + // === Clocking mode === + + pub const ClockMode = enum(i8) { + posedge = 0, + negedge = 1, + both_edges = 2, + } + + // === Capacity constants === + + pub const MAX_ASSERTIONS : u32 = 64; + pub const MAX_COVER_POINTS : u32 = 32; + pub const MAX_ASSUME_POINTS : u32 = 16; + + // === Formal assertion === + + pub struct FormalAssert { + name : &str, + kind : i8, + severity : i8, + condition : &str, + clock : &str, + reset : &str, + description : &str, + } + + // === Cover point === + + pub struct CoverPoint { + name : &str, + condition : &str, + clock : &str, + description : &str, + } + + // === Assumption === + + pub struct FormalAssume { + name : &str, + condition : &str, + clock : &str, + description : &str, + } + + // === Formal verification config === + + pub struct FormalConfig { + name : &str, + module_name : &str, + clock : &str, + reset : &str, + clock_mode : i8, + depth : u32, + timeout_cycles : u32, + } + + // === Constructor helpers === + + fn formal_config(name: &str, module_name: &str, clock: &str, reset: &str) -> FormalConfig { + return FormalConfig{ + .name = name, + .module_name = module_name, + .clock = clock, + .reset = reset, + .clock_mode = 0, + .depth = 20, + .timeout_cycles = 100, + }; + } + + fn with_depth(cfg: FormalConfig, depth: u32) -> FormalConfig { + var result = cfg; + result.depth = depth; + return result; + } + + fn with_timeout(cfg: FormalConfig, timeout: u32) -> FormalConfig { + var result = cfg; + result.timeout_cycles = timeout; + return result; + } + + fn immediate_assert(name: &str, condition: &str, severity: i8, description: &str) -> FormalAssert { + return FormalAssert{ + .name = name, + .kind = 0, + .severity = severity, + .condition = condition, + .clock = "", + .reset = "", + .description = description, + }; + } + + fn concurrent_assert(name: &str, condition: &str, clock: &str, reset: &str, description: &str) -> FormalAssert { + return FormalAssert{ + .name = name, + .kind = 1, + .severity = 2, + .condition = condition, + .clock = clock, + .reset = reset, + .description = description, + }; + } + + fn cover_point(name: &str, condition: &str, clock: &str, description: &str) -> CoverPoint { + return CoverPoint{ + .name = name, + .condition = condition, + .clock = clock, + .description = description, + }; + } + + fn assume(name: &str, condition: &str, clock: &str, description: &str) -> FormalAssume { + return FormalAssume{ + .name = name, + .condition = condition, + .clock = clock, + .description = description, + }; + } + + // === Query functions === + + fn is_immediate(a: FormalAssert) -> bool { + return a.kind == 0; + } + + fn is_concurrent(a: FormalAssert) -> bool { + return a.kind == 1; + } + + fn is_cover(a: FormalAssert) -> bool { + return a.kind == 2; + } + + fn is_assume(a: FormalAssert) -> bool { + return a.kind == 3; + } + + fn severity_str(sev: i8) -> &str { + return "error"; + } + + fn clock_mode_str(mode: i8) -> &str { + return "posedge"; + } + + fn is_posedge(cfg: FormalConfig) -> bool { + return cfg.clock_mode == 0; + } + + // === Validation === + + fn validate_assertion(a: FormalAssert) -> u32 { + var errors : u32 = 0; + if (a.name == "") { + errors = errors + 1; + } + if (a.condition == "") { + errors = errors + 1; + } + if (a.kind == 1 and a.clock == "") { + errors = errors + 1; + } + return errors; + } + + fn validate_cover(c: CoverPoint) -> u32 { + var errors : u32 = 0; + if (c.name == "") { + errors = errors + 1; + } + if (c.condition == "") { + errors = errors + 1; + } + return errors; + } + + fn validate_assume(a: FormalAssume) -> u32 { + var errors : u32 = 0; + if (a.name == "") { + errors = errors + 1; + } + if (a.condition == "") { + errors = errors + 1; + } + return errors; + } + + fn validate_config(cfg: FormalConfig) -> u32 { + var errors : u32 = 0; + if (cfg.name == "") { + errors = errors + 1; + } + if (cfg.module_name == "") { + errors = errors + 1; + } + if (cfg.clock == "") { + errors = errors + 1; + } + if (cfg.depth == 0) { + errors = errors + 1; + } + if (cfg.timeout_cycles == 0) { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test immediate_assert_creation + given a = immediate_assert("no_overflow", "count < MAX", 2, "counter never overflows") + then is_immediate(a) == true + and is_concurrent(a) == false + and a.condition == "count < MAX" + + test concurrent_assert_creation + given a = concurrent_assert("handshake", "valid ##1 ready", "clk", "rst_n", "valid followed by ready") + then is_concurrent(a) == true + and is_immediate(a) == false + and a.clock == "clk" + + test cover_point_creation + given c = cover_point("all_states", "state == S0 || state == S1", "clk", "cover all states") + then c.name == "all_states" + and c.condition != "" + + test assume_creation + given a = assume("stable_reset", "(!$isunknown(rst_n))", "clk", "reset is never X") + then a.name == "stable_reset" + and a.condition != "" + + test formal_config_creation + given cfg = formal_config("uart_props", "UART_TX", "clk", "rst_n") + then cfg.name == "uart_props" + and cfg.module_name == "UART_TX" + and cfg.clock == "clk" + and cfg.reset == "rst_n" + and is_posedge(cfg) == true + + test with_depth + given cfg = formal_config("f", "M", "clk", "rst_n") + and cfg2 = with_depth(cfg, 50) + then cfg2.depth == 50 + + test with_timeout + given cfg = formal_config("f", "M", "clk", "rst_n") + and cfg2 = with_timeout(cfg, 500) + then cfg2.timeout_cycles == 500 + + test validate_assertion_ok + given a = immediate_assert("ok", "x > 0", 2, "desc") + then validate_assertion(a) == 0 + + test validate_assertion_empty_name + given a = immediate_assert("", "x > 0", 2, "desc") + then validate_assertion(a) > 0 + + test validate_assertion_empty_condition + given a = immediate_assert("a", "", 2, "desc") + then validate_assertion(a) > 0 + + test validate_concurrent_no_clock + given a = concurrent_assert("a", "x ##1 y", "", "rst_n", "desc") + then validate_assertion(a) > 0 + + test validate_cover_ok + given c = cover_point("cp", "x", "clk", "desc") + then validate_cover(c) == 0 + + test validate_cover_empty_name + given c = cover_point("", "x", "clk", "desc") + then validate_cover(c) > 0 + + test validate_assume_ok + given a = assume("a", "x", "clk", "desc") + then validate_assume(a) == 0 + + test validate_assume_empty + given a = assume("", "", "clk", "desc") + then validate_assume(a) > 0 + + test validate_config_ok + given cfg = formal_config("f", "M", "clk", "rst_n") + then validate_config(cfg) == 0 + + test validate_config_empty_name + given cfg = formal_config("", "M", "clk", "rst_n") + then validate_config(cfg) > 0 + + test validate_config_empty_clock + given cfg = formal_config("f", "M", "", "rst_n") + then validate_config(cfg) > 0 + + // === Invariants === + + invariant depth_positive + given cfg = formal_config("inv", "M", "clk", "rst_n") + assert cfg.depth > 0 + + invariant timeout_positive + given cfg = formal_config("inv", "M", "clk", "rst_n") + assert cfg.timeout_cycles > 0 + + invariant validate_non_negative + given a = immediate_assert("inv", "x", 2, "d") + assert validate_assertion(a) >= 0 + + invariant config_validate_non_negative + given cfg = formal_config("inv", "M", "clk", "rst_n") + assert validate_config(cfg) >= 0 + + // === Benchmarks === + + bench validate_latency + measure: nanoseconds to validate_assertion(immediate_assert("b", "x > 0", 2, "d")) + target: < 100ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/gf16_accel.t27 b/apps/website/public/t27/specs/fpga/gf16_accel.t27 new file mode 100644 index 0000000000..2d4787c266 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/gf16_accel.t27 @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/gf16_accel.t27 +// GF(16) Hardware Accelerator Specification for Trinity T27 FPGA HIR +// Defines GF16 MAC, FFT, and VSA (Vector Space Architecture) operations +// Connects phi-identity (phi^2 = phi + 1, phi^2 + phi^-2 = 3) to silicon +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Gf16Accel { + + // === GF16 parameters === + + pub const GF16_BITS : u32 = 4; + pub const GF16_ELEMENTS : u32 = 16; + pub const GF16_FIELD_POLY : u32 = 19; + pub const PHI_WIDTH : u32 = 64; + + // === Accelerator operation kind === + + pub const Gf16Op = enum(i8) { + gf_mul = 0, + gf_add = 1, + gf_mac = 2, + gf_dot = 3, + gf_fft = 4, + gf_ifft = 5, + gf_matmul = 6, + gf_inverse = 7, + } + + // === GF16 accelerator configuration === + + pub struct Gf16Config { + name : &str, + num_multipliers : u32, + vector_width : u32, + has_mac : bool, + has_fft : bool, + has_dot_product : bool, + has_matmul : bool, + clock_freq_hz : u32, + } + + // === GF16 multiply-accumulate unit === + + pub struct Gf16MacUnit { + name : &str, + accumulator_width : u32, + pipeline_stages : u32, + throughput : u32, + } + + // === GF16 FFT butterfly unit === + + pub struct Gf16FftConfig { + name : &str, + num_points : u32, + radix : u32, + pipeline_stages : u32, + } + + // === Accelerator status (for simulation) === + + pub struct Gf16Status { + busy : bool, + operation : i8, + cycle_count : u32, + result_valid : bool, + } + + // === Constructor helpers === + + fn gf16_basic(name: &str, num_mult: u32) -> Gf16Config { + return Gf16Config{ + .name = name, + .num_multipliers = num_mult, + .vector_width = num_mult, + .has_mac = true, + .has_fft = false, + .has_dot_product = false, + .has_matmul = false, + .clock_freq_hz = 100000000, + }; + } + + fn gf16_full(name: &str, num_mult: u32, vec_width: u32) -> Gf16Config { + return Gf16Config{ + .name = name, + .num_multipliers = num_mult, + .vector_width = vec_width, + .has_mac = true, + .has_fft = true, + .has_dot_product = true, + .has_matmul = true, + .clock_freq_hz = 100000000, + }; + } + + fn gf16_mac_unit(name: &str, acc_width: u32, stages: u32) -> Gf16MacUnit { + return Gf16MacUnit{ + .name = name, + .accumulator_width = acc_width, + .pipeline_stages = stages, + .throughput = 1, + }; + } + + fn gf16_fft(name: &str, num_points: u32, radix: u32) -> Gf16FftConfig { + return Gf16FftConfig{ + .name = name, + .num_points = num_points, + .radix = radix, + .pipeline_stages = 0, + }; + } + + fn gf16_idle_status() -> Gf16Status { + return Gf16Status{ + .busy = false, + .operation = 0, + .cycle_count = 0, + .result_valid = false, + }; + } + + fn gf16_busy_status(op: i8) -> Gf16Status { + return Gf16Status{ + .busy = true, + .operation = op, + .cycle_count = 0, + .result_valid = false, + }; + } + + // === Query functions === + + fn total_gf16_bits(cfg: Gf16Config) -> u32 { + return cfg.num_multipliers * 4; + } + + fn mac_unit_count(cfg: Gf16Config) -> u32 { + if (cfg.has_mac) { + return cfg.num_multipliers; + } + return 0; + } + + fn fft_stages(fft: Gf16FftConfig) -> u32 { + var n : u32 = fft.num_points; + var r : u32 = fft.radix; + var stages : u32 = 0; + while (n > 1) { + stages = stages + 1; + n = n / r; + } + return stages; + } + + fn fft_twiddle_count(fft: Gf16FftConfig) -> u32 { + return fft.num_points / 2; + } + + fn matmul_cycles(cfg: Gf16Config, n: u32) -> u32 { + if (cfg.has_matmul) { + return n * n * n / cfg.num_multipliers; + } + return 0; + } + + fn dot_product_cycles(cfg: Gf16Config, vec_len: u32) -> u32 { + if (cfg.has_dot_product) { + return vec_len / cfg.num_multipliers + 2; + } + return 0; + } + + fn dsp48_count(cfg: Gf16Config) -> u32 { + return cfg.num_multipliers; + } + + fn bram_count(cfg: Gf16Config) -> u32 { + var count : u32 = 0; + if (cfg.has_fft) { + count = count + cfg.vector_width / 4; + } + if (cfg.has_matmul) { + count = count + 2; + } + return count; + } + + fn is_busy(status: Gf16Status) -> bool { + return status.busy; + } + + fn result_ready(status: Gf16Status) -> bool { + return status.result_valid; + } + + // === Phi identity checks === + + fn phi_squared_plus_phi_inverse_squared() -> u32 { + return 3; + } + + fn phi_squared_equals_phi_plus_one() -> bool { + return true; + } + + // === Validation === + + fn validate_gf16_config(cfg: Gf16Config) -> u32 { + var errors : u32 = 0; + if (cfg.name == "") { + errors = errors + 1; + } + if (cfg.num_multipliers == 0) { + errors = errors + 1; + } + if (cfg.vector_width == 0) { + errors = errors + 1; + } + if (cfg.clock_freq_hz == 0) { + errors = errors + 1; + } + return errors; + } + + fn validate_mac_unit(mac: Gf16MacUnit) -> u32 { + var errors : u32 = 0; + if (mac.name == "") { + errors = errors + 1; + } + if (mac.accumulator_width == 0) { + errors = errors + 1; + } + return errors; + } + + fn validate_fft(fft: Gf16FftConfig) -> u32 { + var errors : u32 = 0; + if (fft.name == "") { + errors = errors + 1; + } + if (fft.num_points == 0) { + errors = errors + 1; + } + if (fft.num_points == 1) { + errors = errors + 1; + } + if (fft.radix == 0) { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test basic_config_creation + given cfg = gf16_basic("gf0", 8) + then cfg.num_multipliers == 8 + and cfg.has_mac == true + and cfg.has_fft == false + + test full_config_creation + given cfg = gf16_full("gf1", 16, 32) + then cfg.num_multipliers == 16 + and cfg.vector_width == 32 + and cfg.has_mac == true + and cfg.has_fft == true + and cfg.has_dot_product == true + and cfg.has_matmul == true + + test total_gf16_bits_basic + given cfg = gf16_basic("gf0", 8) + then total_gf16_bits(cfg) == 32 + + test mac_unit_count_with_mac + given cfg = gf16_basic("gf0", 8) + then mac_unit_count(cfg) == 8 + + test mac_unit_count_without_mac + given cfg = Gf16Config{.name = "gf0", .num_multipliers = 4, .vector_width = 4, .has_mac = false, .has_fft = false, .has_dot_product = false, .has_matmul = false, .clock_freq_hz = 100000000} + then mac_unit_count(cfg) == 0 + + test dsp48_count + given cfg = gf16_basic("gf0", 8) + then dsp48_count(cfg) == 8 + + test gf16_mac_unit_creation + given mac = gf16_mac_unit("mac0", 32, 3) + then mac.accumulator_width == 32 + and mac.pipeline_stages == 3 + + test fft_creation + given fft = gf16_fft("fft0", 16, 2) + then fft.num_points == 16 + and fft.radix == 2 + + test fft_stages_16pt_radix2 + given fft = gf16_fft("fft0", 16, 2) + then fft_stages(fft) == 4 + + test fft_stages_64pt_radix4 + given fft = gf16_fft("fft0", 64, 4) + then fft_stages(fft) == 3 + + test fft_twiddle_count + given fft = gf16_fft("fft0", 16, 2) + then fft_twiddle_count(fft) == 8 + + test matmul_cycles + given cfg = gf16_full("gf0", 8, 16) + then matmul_cycles(cfg, 4) > 0 + + test dot_product_cycles + given cfg = gf16_full("gf0", 8, 16) + then dot_product_cycles(cfg, 16) > 0 + + test idle_status + given s = gf16_idle_status() + then is_busy(s) == false + and result_ready(s) == false + + test busy_status + given s = gf16_busy_status(2) + then is_busy(s) == true + and s.operation == 2 + + test phi_identity + then phi_squared_plus_phi_inverse_squared() == 3 + + test phi_squared_identity + then phi_squared_equals_phi_plus_one() == true + + test validate_config_ok + given cfg = gf16_basic("gf0", 8) + then validate_gf16_config(cfg) == 0 + + test validate_config_empty_name + given cfg = gf16_basic("", 8) + then validate_gf16_config(cfg) > 0 + + test validate_config_zero_mult + given cfg = gf16_basic("gf0", 0) + then validate_gf16_config(cfg) > 0 + + test validate_mac_ok + given mac = gf16_mac_unit("mac0", 32, 3) + then validate_mac_unit(mac) == 0 + + test validate_mac_empty_name + given mac = gf16_mac_unit("", 32, 3) + then validate_mac_unit(mac) > 0 + + test validate_fft_ok + given fft = gf16_fft("fft0", 16, 2) + then validate_fft(fft) == 0 + + test validate_fft_empty_name + given fft = gf16_fft("", 16, 2) + then validate_fft(fft) > 0 + + test validate_fft_zero_points + given fft = gf16_fft("fft0", 0, 2) + then validate_fft(fft) > 0 + + test bram_count_basic + given cfg = gf16_basic("gf0", 8) + then bram_count(cfg) == 0 + + test bram_count_full + given cfg = gf16_full("gf0", 16, 32) + then bram_count(cfg) > 0 + + // === Invariants === + + invariant gf16_is_4_bits + assert GF16_BITS == 4 + + invariant field_poly_value + assert GF16_FIELD_POLY == 19 + + invariant phi_squared_identity + assert phi_squared_plus_phi_inverse_squared() == 3 + + invariant multipliers_positive + given cfg = gf16_basic("inv", 8) + assert cfg.num_multipliers > 0 + + invariant vector_width_positive + given cfg = gf16_full("inv", 8, 16) + assert cfg.vector_width > 0 + + invariant dsp48_equals_multipliers + given cfg = gf16_basic("inv", 8) + assert dsp48_count(cfg) == cfg.num_multipliers + + invariant fft_stages_positive_for_power_of_2 + given fft = gf16_fft("inv", 16, 2) + assert fft_stages(fft) > 0 + + invariant validate_non_negative + given cfg = gf16_basic("inv", 8) + assert validate_gf16_config(cfg) >= 0 + + // === Benchmarks === + + bench gf16_mul_latency + measure: nanoseconds for total_gf16_bits(gf16_basic("b", 8)) + target: < 100ns + + bench fft_stages_latency + measure: nanoseconds for fft_stages(gf16_fft("b", 256, 2)) + target: < 200ns + + bench matmul_cycles_latency + measure: nanoseconds for matmul_cycles(gf16_full("b", 16, 32), 16) + target: < 200ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/hir.t27 b/apps/website/public/t27/specs/fpga/hir.t27 new file mode 100644 index 0000000000..e752dfaab3 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/hir.t27 @@ -0,0 +1,691 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/hir.t27 +// Hardware Intermediate Representation (HIR) for Trinity T27 +// Decouples .t27 spec semantics from Verilog/SystemVerilog emission +// Uses flat arrays + count fields (parser-compatible, no Vec/generics) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Hir { + + // === Capacity constants === + + pub const MAX_PORTS : u32 = 64; + pub const MAX_SIGNALS : u32 = 256; + pub const MAX_ASSIGNS : u32 = 256; + pub const MAX_INSTANCES : u32 = 32; + pub const MAX_ERRORS : u32 = 64; + + // === Edge sensitivity === + + pub const Edge = enum(i8) { + posedge = 0, + negedge = 1, + comb = 2, + }; + + // === Port direction === + + pub const PortDir = enum(i8) { + input_dir = 0, + output_dir = 1, + inout_dir = 2, + }; + + // === Signal storage kind === + + pub const SignalKind = enum(i8) { + wire_kind = 0, + reg_kind = 1, + }; + + // === Port declaration (flat) === + + pub struct Port { + name : &str, + dir : i8, + width : u32, + is_signed : bool, + is_clock : bool, + is_reset : bool, + } + + // === Internal signal declaration (flat) === + + pub struct Signal { + name : &str, + kind : i8, + width : u32, + is_signed : bool, + reset_value : &str, + } + + // === Combinational assignment === + + pub struct Assign { + target : &str, + value : &str, + } + + // === Module instance === + + pub struct Instance { + name : &str, + module_name : &str, + } + + // === Top-level HIR Module (flat arrays) === + + pub struct HirModule { + name : &str, + ports : [64]Port, + port_count : u32, + signals : [256]Signal, + signal_count : u32, + assigns : [256]Assign, + assign_count : u32, + instances : [32]Instance, + instance_count : u32, + mems : [16]Mem, + mem_count : u32, + clock_domains : [8]ClockDomain, + clock_domain_count : u32, + bus_ports : [8]BusPort, + bus_port_count : u32, + } + + // === Memory kind === + + pub const MemKind = enum(i8) { + bram = 0, + dram = 1, + rom = 2, + } + + // === Memory port mode === + + pub const MemPortMode = enum(i8) { + read_first = 0, + write_first = 1, + no_change = 2, + } + + // === Memory node (BRAM/DRAM/ROM) === + + pub const MAX_MEMS : u32 = 16; + pub const MAX_MEM_PORTS : u32 = 4; + + pub struct MemPort { + name : &str, + is_write : bool, + width : u32, + addr_width : u32, + } + + pub struct Mem { + name : &str, + kind : i8, + depth : u32, + data_width : u32, + ports : [4]MemPort, + port_count : u32, + init_file : &str, + } + + fn empty_mem_port() -> MemPort { + return MemPort{ + .name = "", + .is_write = false, + .width = 0, + .addr_width = 0, + }; + } + + fn empty_mem() -> Mem { + return Mem{ + .name = "", + .kind = 0, + .depth = 0, + .data_width = 0, + .ports = [empty_mem_port(); 4], + .port_count = 0, + .init_file = "", + }; + } + + fn make_mem(name: &str, kind: i8, depth: u32, data_width: u32) -> Mem { + return Mem{ + .name = name, + .kind = kind, + .depth = depth, + .data_width = data_width, + .ports = [empty_mem_port(); 4], + .port_count = 0, + .init_file = "", + }; + } + + fn mem_add_port(mem: Mem, name: &str, is_write: bool, width: u32, addr_width: u32) -> Mem { + var result = mem; + if (result.port_count < MAX_MEM_PORTS) { + result.ports[result.port_count] = MemPort{ + .name = name, + .is_write = is_write, + .width = width, + .addr_width = addr_width, + }; + result.port_count = result.port_count + 1; + } + return result; + } + + fn mem_total_bits(mem: Mem) -> u32 { + return mem.depth * mem.data_width; + } + + fn mem_bram18_count(mem: Mem) -> u32 { + const BRAM18_BITS : u32 = 18 * 1024; + const total = mem_total_bits(mem); + if (total == 0) { + return 0; + } + return (total + BRAM18_BITS - 1) / BRAM18_BITS; + } + + // === Clock domain crossing strategy === + + pub const CdcStrategy = enum(i8) { + two_flop = 0, + async_fifo = 1, + handshake = 2, + gray_code = 3, + } + + // === ClockDomain node === + + pub const MAX_CLOCK_DOMAINS : u32 = 8; + + pub struct ClockDomain { + name : &str, + freq_hz : u32, + phase_deg : u32, + is_primary : bool, + cdc_strategy : i8, + } + + fn empty_clock_domain() -> ClockDomain { + return ClockDomain{ + .name = "", + .freq_hz = 0, + .phase_deg = 0, + .is_primary = false, + .cdc_strategy = 0, + }; + } + + fn make_clock_domain(name: &str, freq_hz: u32, is_primary: bool) -> ClockDomain { + return ClockDomain{ + .name = name, + .freq_hz = freq_hz, + .phase_deg = 0, + .is_primary = is_primary, + .cdc_strategy = 0, + }; + } + + // === Bus protocol kind === + + pub const BusKind = enum(i8) { + axi4_lite = 0, + axi4_full = 1, + apb = 2, + wishbone = 3, + } + + // === BusPort node (AXI/APB/Wishbone) === + + pub const MAX_BUS_PORTS : u32 = 8; + + pub struct BusPort { + name : &str, + bus_kind : i8, + addr_width : u32, + data_width : u32, + is_master : bool, + base_addr : u32, + } + + fn empty_bus_port() -> BusPort { + return BusPort{ + .name = "", + .bus_kind = 0, + .addr_width = 0, + .data_width = 0, + .is_master = false, + .base_addr = 0, + }; + } + + fn make_bus_port(name: &str, bus_kind: i8, addr_width: u32, data_width: u32, is_master: bool, base_addr: u32) -> BusPort { + return BusPort{ + .name = name, + .bus_kind = bus_kind, + .addr_width = addr_width, + .data_width = data_width, + .is_master = is_master, + .base_addr = base_addr, + }; + } + + fn bus_port_total_signals(bp: BusPort) -> u32 { + // AXI4-Lite: AW(2) + AR(2) + W(1) + R(2) + B(2) = 9 + addr + data + if (bp.bus_kind == 0) { + return bp.addr_width + bp.data_width * 2 + 9; + } + // APB: PADDR + PWDATA + PRDATA + PSEL + PENABLE + PWRITE + PREADY + if (bp.bus_kind == 2) { + return bp.addr_width + bp.data_width * 2 + 5; + } + return bp.addr_width + bp.data_width * 2; + } + + // === Sentinel port/signal for initialization === + + fn empty_port() -> Port { + return Port{ + .name = "", + .dir = 0, + .width = 0, + .is_signed = false, + .is_clock = false, + .is_reset = false, + }; + } + + fn empty_signal() -> Signal { + return Signal{ + .name = "", + .kind = 0, + .width = 0, + .is_signed = false, + .reset_value = "", + }; + } + + fn empty_assign() -> Assign { + return Assign{.target = "", .value = ""}; + } + + fn empty_instance() -> Instance { + return Instance{.name = "", .module_name = ""}; + } + + // === Constructor helpers === + + fn empty_module(name: &str) -> HirModule { + return HirModule{ + .name = name, + .ports = [empty_port(); 64], + .port_count = 0, + .signals = [empty_signal(); 256], + .signal_count = 0, + .assigns = [empty_assign(); 256], + .assign_count = 0, + .instances = [empty_instance(); 32], + .instance_count = 0, + .mems = [empty_mem(); 16], + .mem_count = 0, + .clock_domains = [empty_clock_domain(); 8], + .clock_domain_count = 0, + .bus_ports = [empty_bus_port(); 8], + .bus_port_count = 0, + }; + } + + fn make_port(name: &str, dir: i8, width: u32, is_clock: bool, is_reset: bool) -> Port { + return Port{ + .name = name, + .dir = dir, + .width = width, + .is_signed = false, + .is_clock = is_clock, + .is_reset = is_reset, + }; + } + + fn make_signal(name: &str, kind: i8, width: u32) -> Signal { + return Signal{ + .name = name, + .kind = kind, + .width = width, + .is_signed = false, + .reset_value = "0", + }; + } + + // === Mutation helpers (return new module) === + + fn add_port(mod: HirModule, name: &str, dir: i8, width: u32, is_clock: bool, is_reset: bool) -> HirModule { + var result = mod; + if (result.port_count < 64) { + result.ports[result.port_count] = make_port(name, dir, width, is_clock, is_reset); + result.port_count = result.port_count + 1; + } + return result; + } + + fn add_signal(mod: HirModule, name: &str, kind: i8, width: u32) -> HirModule { + var result = mod; + if (result.signal_count < 256) { + result.signals[result.signal_count] = make_signal(name, kind, width); + result.signal_count = result.signal_count + 1; + } + return result; + } + + fn add_assign(mod: HirModule, target: &str, value: &str) -> HirModule { + var result = mod; + if (result.assign_count < 256) { + result.assigns[result.assign_count] = Assign{.target = target, .value = value}; + result.assign_count = result.assign_count + 1; + } + return result; + } + + fn add_instance(mod: HirModule, name: &str, module_name: &str) -> HirModule { + var result = mod; + if (result.instance_count < 32) { + result.instances[result.instance_count] = Instance{.name = name, .module_name = module_name}; + result.instance_count = result.instance_count + 1; + } + return result; + } + + fn add_mem(mod: HirModule, mem: Mem) -> HirModule { + var result = mod; + if (result.mem_count < MAX_MEMS) { + result.mems[result.mem_count] = mem; + result.mem_count = result.mem_count + 1; + } + return result; + } + + fn add_clock_domain(mod: HirModule, cd: ClockDomain) -> HirModule { + var result = mod; + if (result.clock_domain_count < MAX_CLOCK_DOMAINS) { + result.clock_domains[result.clock_domain_count] = cd; + result.clock_domain_count = result.clock_domain_count + 1; + } + return result; + } + + fn add_bus_port(mod: HirModule, bp: BusPort) -> HirModule { + var result = mod; + if (result.bus_port_count < MAX_BUS_PORTS) { + result.bus_ports[result.bus_port_count] = bp; + result.bus_port_count = result.bus_port_count + 1; + } + return result; + } + + // === Query functions === + + fn port_count(mod: HirModule) -> u32 { + return mod.port_count; + } + + fn signal_count(mod: HirModule) -> u32 { + return mod.signal_count; + } + + fn assign_count(mod: HirModule) -> u32 { + return mod.assign_count; + } + + fn has_clock_port(mod: HirModule) -> bool { + var i : u32 = 0; + while (i < mod.port_count) { + if (mod.ports[i].is_clock) { + return true; + } + i = i + 1; + } + return false; + } + + fn has_reset_port(mod: HirModule) -> bool { + var i : u32 = 0; + while (i < mod.port_count) { + if (mod.ports[i].is_reset) { + return true; + } + i = i + 1; + } + return false; + } + + fn total_port_bits(mod: HirModule) -> u32 { + var total : u32 = 0; + var i : u32 = 0; + while (i < mod.port_count) { + total = total + mod.ports[i].width; + i = i + 1; + } + return total; + } + + // === Validation === + + fn validate_module(mod: HirModule) -> u32 { + var error_count : u32 = 0; + + if (mod.name == "") { + error_count = error_count + 1; + } + + var i : u32 = 0; + while (i < mod.port_count) { + var j : u32 = i + 1; + while (j < mod.port_count) { + if (mod.ports[i].name == mod.ports[j].name) { + error_count = error_count + 1; + } + j = j + 1; + } + i = i + 1; + } + + i = 0; + while (i < mod.signal_count) { + var j : u32 = i + 1; + while (j < mod.signal_count) { + if (mod.signals[i].name == mod.signals[j].name) { + error_count = error_count + 1; + } + j = j + 1; + } + i = i + 1; + } + + return error_count; + } + + // === Tests === + + test empty_module_has_zero_ports + given m = empty_module("test_mod") + then port_count(m) == 0 + and signal_count(m) == 0 + and assign_count(m) == 0 + + test add_port_increments_count + given m = empty_module("test_mod") + and m2 = add_port(m, "clk", 0, 1, true, false) + then port_count(m2) == 1 + + test add_two_ports + given m = empty_module("test_mod") + and m2 = add_port(m, "clk", 0, 1, true, false) + and m3 = add_port(m2, "data", 1, 8, false, false) + then port_count(m3) == 2 + and total_port_bits(m3) == 9 + + test add_signal_increments_count + given m = empty_module("test_mod") + and m2 = add_signal(m, "counter", 1, 16) + then signal_count(m2) == 1 + + test add_assign_increments_count + given m = empty_module("test_mod") + and m2 = add_assign(m, "led", "counter[7]") + then assign_count(m2) == 1 + + test has_clock_port_true + given m = empty_module("test_mod") + and m2 = add_port(m, "clk", 0, 1, true, false) + then has_clock_port(m2) == true + + test has_clock_port_false + given m = empty_module("test_mod") + and m2 = add_port(m, "data", 0, 8, false, false) + then has_clock_port(m2) == false + + test has_reset_port_true + given m = empty_module("test_mod") + and m2 = add_port(m, "rst_n", 0, 1, false, true) + then has_reset_port(m2) == true + + test validate_empty_module_ok + given m = empty_module("test_mod") + and errors = validate_module(m) + then errors == 0 + + test validate_unnamed_module_fails + given m = empty_module("") + and errors = validate_module(m) + then errors > 0 + + test validate_duplicate_ports_fails + given m = empty_module("dup_test") + and m2 = add_port(m, "a", 0, 1, false, false) + and m3 = add_port(m2, "a", 1, 1, false, false) + and errors = validate_module(m3) + then errors > 0 + + test mem_empty_has_zero_bits + given mem = empty_mem() + then mem_total_bits(mem) == 0 + + test mem_make_sets_fields + given mem = make_mem("ram0", 0, 1024, 32) + then mem.depth == 1024 and mem.data_width == 32 + + test mem_total_bits + given mem = make_mem("ram0", 0, 1024, 32) + then mem_total_bits(mem) == 32768 + + test mem_bram18_count_single + given mem = make_mem("ram0", 0, 1024, 18) + then mem_bram18_count(mem) == 1 + + test mem_bram18_count_two + given mem = make_mem("ram0", 0, 2048, 18) + then mem_bram18_count(mem) == 2 + + test mem_add_port_increments + given mem = make_mem("ram0", 0, 512, 32) + and mem2 = mem_add_port(mem, "port_a", true, 32, 9) + then mem2.port_count == 1 + + test mem_add_two_ports + given mem = make_mem("ram0", 0, 512, 32) + and mem2 = mem_add_port(mem, "port_a", true, 32, 9) + and mem3 = mem_add_port(mem2, "port_b", false, 32, 9) + then mem3.port_count == 2 + + test add_mem_to_module + given m = empty_module("mem_test") + and mem = make_mem("bram0", 0, 1024, 32) + and m2 = add_mem(m, mem) + then m2.mem_count == 1 + + test add_clock_domain_to_module + given m = empty_module("clk_test") + and cd = make_clock_domain("sys_clk", 100_000_000, true) + and m2 = add_clock_domain(m, cd) + then m2.clock_domain_count == 1 + + test clock_domain_primary + given cd = make_clock_domain("sys_clk", 100_000_000, true) + then cd.is_primary == true + + test clock_domain_secondary + given cd = make_clock_domain("periph_clk", 50_000_000, false) + then cd.is_primary == false + + test add_bus_port_to_module + given m = empty_module("bus_test") + and bp = make_bus_port("axi0", 0, 32, 32, true, 0x4000_0000) + and m2 = add_bus_port(m, bp) + then m2.bus_port_count == 1 + + test bus_port_axi4_lite_signals + given bp = make_bus_port("axi0", 0, 32, 32, true, 0) + then bus_port_total_signals(bp) == 32 + 64 + 9 + + test bus_port_apb_signals + given bp = make_bus_port("apb0", 2, 16, 32, false, 0) + then bus_port_total_signals(bp) == 16 + 64 + 5 + + test empty_module_zero_mems_and_clocks + given m = empty_module("inv_test") + then m.mem_count == 0 + and m.clock_domain_count == 0 + and m.bus_port_count == 0 + + // === Invariants === + + invariant empty_module_zero_ports + given m = empty_module("inv_test") + assert port_count(m) == 0 + + invariant empty_module_zero_signals + given m = empty_module("inv_test") + assert signal_count(m) == 0 + + invariant port_count_non_negative + given m = empty_module("inv_test") + and m2 = add_port(m, "x", 0, 1, false, false) + assert port_count(m2) >= 0 + + invariant total_port_bits_non_negative + given m = empty_module("inv_test") + assert total_port_bits(m) >= 0 + + invariant validate_returns_non_negative + given m = empty_module("inv_test") + and errors = validate_module(m) + assert errors >= 0 + + invariant mem_bram18_count_non_negative + given mem = make_mem("inv_mem", 0, 1024, 32) + assert mem_bram18_count(mem) >= 0 + + invariant mem_port_count_within_bounds + given mem = make_mem("inv_mem", 0, 512, 32) + and mem2 = mem_add_port(mem, "p0", true, 32, 9) + assert mem2.port_count <= MAX_MEM_PORTS + + invariant bus_port_total_signals_positive + given bp = make_bus_port("inv_bus", 0, 32, 32, true, 0) + assert bus_port_total_signals(bp) > 0 + + bench mem_bram18_estimation_latency + measure: nanoseconds to mem_bram18_count(make_mem("bench_mem", 0, 4096, 36)) + target: < 100ns + + bench hir_module_construction + measure: nanoseconds to empty_module("bench_mod") + target: < 50ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/hw_types.t27 b/apps/website/public/t27/specs/fpga/hw_types.t27 new file mode 100644 index 0000000000..f6eb81e82e --- /dev/null +++ b/apps/website/public/t27/specs/fpga/hw_types.t27 @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/hw_types.t27 +// Hardware Type System for Trinity T27 FPGA HIR +// Defines signal-level types with bit-accurate widths and signedness +// phi^2 + 1/phi^2 = 3 | TRINITY + +module HwTypes { + + // === Reset configuration === + + pub const ResetKind = enum(i8) { + async_r = 0, + sync_r = 1, + }; + + pub const ResetPolarity = enum(i8) { + active_high = 0, + active_low = 1, + }; + + // === HwType tag === + + pub const HwTypeTag = enum(i8) { + bits_tag = 0, + uint_tag = 1, + sint_tag = 2, + bool_tag = 3, + clock_tag = 4, + reset_tag = 5, + vector_tag = 6, + bundle_tag = 7, + enum_tag = 8, + gf16_tag = 9, + }; + + // === Hardware signal type (flat struct) === + + pub struct HwType { + tag: i8, + width: u32, + is_signed_flag: bool, + is_clock_flag: bool, + is_reset_flag: bool, + elem_tag: i8, + vec_len: u32, + field_count: u32, + } + + pub fn hw_bits(w: u32) -> HwType { + return HwType{ + .tag = 0, + .width = w, + .is_signed_flag = false, + .is_clock_flag = false, + .is_reset_flag = false, + .elem_tag = 0, + .vec_len = 0, + .field_count = 0, + }; + } + + pub fn hw_uint(w: u32) -> HwType { + return HwType{ + .tag = 1, + .width = w, + .is_signed_flag = false, + .is_clock_flag = false, + .is_reset_flag = false, + .elem_tag = 0, + .vec_len = 0, + .field_count = 0, + }; + } + + pub fn hw_sint(w: u32) -> HwType { + return HwType{ + .tag = 2, + .width = w, + .is_signed_flag = true, + .is_clock_flag = false, + .is_reset_flag = false, + .elem_tag = 0, + .vec_len = 0, + .field_count = 0, + }; + } + + pub fn hw_bool() -> HwType { + return HwType{ + .tag = 3, + .width = 1, + .is_signed_flag = false, + .is_clock_flag = false, + .is_reset_flag = false, + .elem_tag = 0, + .vec_len = 0, + .field_count = 0, + }; + } + + pub fn hw_clock() -> HwType { + return HwType{ + .tag = 4, + .width = 1, + .is_signed_flag = false, + .is_clock_flag = true, + .is_reset_flag = false, + .elem_tag = 0, + .vec_len = 0, + .field_count = 0, + }; + } + + pub fn hw_reset() -> HwType { + return HwType{ + .tag = 5, + .width = 1, + .is_signed_flag = false, + .is_clock_flag = false, + .is_reset_flag = true, + .elem_tag = 0, + .vec_len = 0, + .field_count = 0, + }; + } + + pub fn hw_vector(elem: HwType, len: u32) -> HwType { + return HwType{ + .tag = 6, + .width = elem.width * len, + .is_signed_flag = elem.is_signed_flag, + .is_clock_flag = false, + .is_reset_flag = false, + .elem_tag = elem.tag, + .vec_len = len, + .field_count = 0, + }; + } + + pub fn hw_gf16() -> HwType { + return HwType{ + .tag = 9, + .width = 16, + .is_signed_flag = false, + .is_clock_flag = false, + .is_reset_flag = false, + .elem_tag = 0, + .vec_len = 0, + .field_count = 0, + }; + } + + // === Width query === + + pub fn hw_width(ty: HwType) -> u32 { + return ty.width; + } + + // === Signedness query === + + pub fn is_signed(ty: HwType) -> bool { + return ty.is_signed_flag; + } + + // === Clock-like detection === + + pub fn is_clock_like(ty: HwType) -> bool { + return ty.is_clock_flag; + } + + // === Reset-like detection === + + pub fn is_reset_like(ty: HwType) -> bool { + return ty.is_reset_flag; + } + + // === Type equality (structural) === + + pub fn types_equal(a: HwType, b: HwType) -> bool { + if (a.width != b.width) { + return false; + } + if (a.is_signed_flag != b.is_signed_flag) { + return false; + } + if (a.is_clock_flag != b.is_clock_flag) { + return false; + } + if (a.is_reset_flag != b.is_reset_flag) { + return false; + } + return true; + } + + // === Verilog range string === + + pub fn verilog_range(ty: HwType) -> bool { + return ty.width > 1; + } + + // === Connection compatibility === + + pub fn is_connectable(target: HwType, source: HwType) -> bool { + if (target.width != source.width) { + return false; + } + if (target.is_clock_flag != source.is_clock_flag) { + return false; + } + if (target.is_reset_flag != source.is_reset_flag) { + return false; + } + return true; + } + + // === Tests === + + test bits8_width + given w = hw_width(hw_bits(8)) + then w == 8 + + test uint3_width + given w = hw_width(hw_uint(3)) + then w == 3 + + test bool_width + given w = hw_width(hw_bool()) + then w == 1 + + test clock_width + given w = hw_width(hw_clock()) + then w == 1 + + test reset_width + given w = hw_width(hw_reset()) + then w == 1 + + test vector_width + given w = hw_width(hw_vector(hw_uint(8), 4)) + then w == 32 + + test gf16_width + given w = hw_width(hw_gf16()) + then w == 16 + + test uint_not_signed + then is_signed(hw_uint(8)) == false + + test sint_is_signed + then is_signed(hw_sint(32)) == true + + test bool_not_signed + then is_signed(hw_bool()) == false + + test clock_is_clock_like + then is_clock_like(hw_clock()) == true + + test uint_not_clock_like + then is_clock_like(hw_uint(1)) == false + + test reset_is_reset_like + then is_reset_like(hw_reset()) == true + + test bool_not_reset_like + then is_reset_like(hw_bool()) == false + + test vector_of_bools_width + given w = hw_width(hw_vector(hw_bool(), 8)) + then w == 8 + + test vector_uint16_2_width + given w = hw_width(hw_vector(hw_uint(16), 2)) + then w == 32 + + test types_equal_same + then types_equal(hw_uint(8), hw_uint(8)) == true + + test types_equal_diff_width + then types_equal(hw_uint(8), hw_uint(16)) == false + + test types_equal_diff_signed + then types_equal(hw_uint(8), hw_sint(8)) == false + + test is_connectable_same + then is_connectable(hw_uint(8), hw_uint(8)) == true + + test is_connectable_diff_width + then is_connectable(hw_uint(8), hw_uint(16)) == false + + test is_connectable_clock_mismatch + then is_connectable(hw_clock(), hw_bool()) == false + + // === Invariants === + + invariant bool_width_is_1 + assert hw_width(hw_bool()) == 1 + + invariant clock_width_is_1 + assert hw_width(hw_clock()) == 1 + + invariant gf16_width_is_16 + assert hw_width(hw_gf16()) == 16 + + invariant uint_signedness + assert is_signed(hw_uint(32)) == false + + invariant sint_signedness + assert is_signed(hw_sint(32)) == true + + invariant width_non_negative + assert hw_width(hw_bits(0)) >= 0 + + invariant width_monotonic + assert hw_width(hw_uint(8)) <= hw_width(hw_uint(16)) + + invariant vector_width_is_product + assert hw_width(hw_vector(hw_uint(8), 4)) == 32 + + invariant connectable_reflexive + given t = hw_uint(8) + assert is_connectable(t, t) == true + + // === Benchmarks === + + bench hw_width_latency + measure: nanoseconds to hw_width(hw_uint(32)) + target: < 100ns + + bench is_clock_like_latency + measure: nanoseconds to is_clock_like(hw_clock()) + target: < 50ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/linker.t27 b/apps/website/public/t27/specs/fpga/linker.t27 new file mode 100644 index 0000000000..a181cfdb46 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/linker.t27 @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/linker.t27 +// T27 Linker Specification +// Links assembled object files into executable images for ternary core +// Handles section merging, symbol resolution, address assignment, relocations +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Linker { + + // === Linker section === + + pub struct LinkSection { + name : &str, + vaddr : u32, + size : u32, + flags : u32, + align : u32, + } + + fn link_text(vaddr: u32, size: u32) -> LinkSection { + return LinkSection{ + .name = ".text", + .vaddr = vaddr, + .size = size, + .flags = 5, + .align = 4, + }; + } + + fn link_data(vaddr: u32, size: u32) -> LinkSection { + return LinkSection{ + .name = ".data", + .vaddr = vaddr, + .size = size, + .flags = 3, + .align = 4, + }; + } + + fn link_bss(vaddr: u32, size: u32) -> LinkSection { + return LinkSection{ + .name = ".bss", + .vaddr = vaddr, + .size = size, + .flags = 2, + .align = 4, + }; + } + + fn section_end(sec: LinkSection) -> u32 { + return sec.vaddr + sec.size; + } + + fn section_aligned(sec: LinkSection) -> u32 { + if sec.align == 0 { + return sec.vaddr; + } + var rem : u32 = sec.vaddr % sec.align; + if rem == 0 { + return sec.vaddr; + } + return sec.vaddr + sec.align - rem; + } + + // === Linked symbol === + + pub struct LinkedSymbol { + name : &str, + value : u32, + size : u32, + section_idx : u32, + bind : u32, + kind : u32, + } + + fn linked_symbol(name: &str, value: u32, sec: u32) -> LinkedSymbol { + return LinkedSymbol{ + .name = name, + .value = value, + .size = 0, + .section_idx = sec, + .bind = 1, + .kind = 2, + }; + } + + fn is_global(sym: LinkedSymbol) -> bool { + return sym.bind == 1; + } + + fn is_local(sym: LinkedSymbol) -> bool { + return sym.bind == 0; + } + + // === Linker segment === + + pub struct LinkSegment { + kind : u32, + vaddr : u32, + memsz : u32, + filesz : u32, + align : u32, + } + + fn text_segment(vaddr: u32, size: u32) -> LinkSegment { + return LinkSegment{ + .kind = 1, + .vaddr = vaddr, + .memsz = size, + .filesz = size, + .align = 4096, + }; + } + + fn data_segment(vaddr: u32, memsz: u32, filesz: u32) -> LinkSegment { + return LinkSegment{ + .kind = 1, + .vaddr = vaddr, + .memsz = memsz, + .filesz = filesz, + .align = 4096, + }; + } + + // === Linker config === + + pub struct LinkerConfig { + entry : &str, + text_base : u32, + data_base : u32, + stack_size : u32, + heap_size : u32, + output_format : i8, + } + + fn linker_config(entry: &str) -> LinkerConfig { + return LinkerConfig{ + .entry = entry, + .text_base = 0, + .data_base = 4096, + .stack_size = 1024, + .heap_size = 4096, + .output_format = 0, + }; + } + + // === Link result === + + pub struct LinkResult { + entry_addr : u32, + total_text : u32, + total_data : u32, + total_bss : u32, + num_symbols : u32, + num_segments : u32, + errors : u32, + } + + fn link_ok(entry: u32, text: u32, data: u32, bss: u32) -> LinkResult { + return LinkResult{ + .entry_addr = entry, + .total_text = text, + .total_data = data, + .total_bss = bss, + .num_symbols = 0, + .num_segments = 2, + .errors = 0, + }; + } + + fn link_fail(errors: u32) -> LinkResult { + return LinkResult{ + .entry_addr = 0, + .total_text = 0, + .total_data = 0, + .total_bss = 0, + .num_symbols = 0, + .num_segments = 0, + .errors = errors, + }; + } + + // === Query functions === + + fn total_image_size(r: LinkResult) -> u32 { + return r.total_text + r.total_data + r.total_bss; + } + + fn passed(r: LinkResult) -> bool { + return r.errors == 0; + } + + fn stack_top(cfg: LinkerConfig) -> u32 { + return cfg.data_base + cfg.stack_size; + } + + fn heap_start(cfg: LinkerConfig) -> u32 { + return cfg.data_base + cfg.data_base + cfg.stack_size; + } + + // === Validation === + + fn validate_config(cfg: LinkerConfig) -> u32 { + var errors : u32 = 0; + if cfg.entry == "" { + errors = errors + 1; + } + return errors; + } + + fn validate_symbol(sym: LinkedSymbol) -> u32 { + var errors : u32 = 0; + if sym.name == "" { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test link_text_creation + given sec = link_text(0, 128) + then sec.name == ".text" + and sec.vaddr == 0 + and sec.size == 128 + and section_end(sec) == 128 + + test link_data_creation + given sec = link_data(4096, 256) + then sec.name == ".data" + and sec.vaddr == 4096 + + test link_bss_creation + given sec = link_bss(8192, 512) + then sec.name == ".bss" + and sec.flags == 2 + + test section_aligned_exact + given sec = link_text(0, 128) + then section_aligned(sec) == 0 + + test linked_symbol_creation + given sym = linked_symbol("_start", 0, 0) + then sym.name == "_start" + and sym.value == 0 + and is_global(sym) == true + + fn is_local_test() + given sym = LinkedSymbol{.name = "x", .value = 0, .size = 0, .section_idx = 0, .bind = 0, .kind = 0} + then is_local(sym) == true + and is_global(sym) == false + + test linker_config_creation + given cfg = linker_config("_start") + then cfg.entry == "_start" + and cfg.text_base == 0 + and cfg.data_base == 4096 + and cfg.stack_size == 1024 + + test link_ok_creation + given r = link_ok(0, 128, 256, 64) + then r.entry_addr == 0 + and r.total_text == 128 + and r.errors == 0 + and passed(r) == true + + test link_fail_creation + given r = link_fail(3) + then r.errors == 3 + and passed(r) == false + + test total_image_size + given r = link_ok(0, 128, 256, 64) + then total_image_size(r) == 448 + + test stack_top + given cfg = linker_config("_start") + then stack_top(cfg) == 5120 + + test validate_config_ok + given cfg = linker_config("_start") + then validate_config(cfg) == 0 + + test validate_config_no_entry + given cfg = LinkerConfig{.entry = "", .text_base = 0, .data_base = 4096, .stack_size = 1024, .heap_size = 4096, .output_format = 0} + then validate_config(cfg) > 0 + + test validate_symbol_ok + given sym = linked_symbol("main", 0, 0) + then validate_symbol(sym) == 0 + + test validate_symbol_empty + given sym = LinkedSymbol{.name = "", .value = 0, .size = 0, .section_idx = 0, .bind = 0, .kind = 0} + then validate_symbol(sym) > 0 + + test text_segment_creation + given seg = text_segment(0, 1024) + then seg.vaddr == 0 + and seg.memsz == 1024 + and seg.filesz == 1024 + + test data_segment_creation + given seg = data_segment(4096, 2048, 1024) + then seg.vaddr == 4096 + and seg.memsz == 2048 + and seg.filesz == 1024 + + // === Invariants === + + invariant total_size_non_negative + given r = link_ok(0, 100, 200, 50) + assert total_image_size(r) >= 0 + + invariant validate_non_negative + given cfg = linker_config("_start") + assert validate_config(cfg) >= 0 + + // === Benchmarks === + + bench link_latency + measure: nanoseconds for link_ok(0, 1024, 512, 256) + target: < 100ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/mac.t27 b/apps/website/public/t27/specs/fpga/mac.t27 new file mode 100644 index 0000000000..7037a8e4f7 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/mac.t27 @@ -0,0 +1,622 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/mac.t27 +// ZeroDSP FPGA Multiply-Accumulate Specification +// Ternary MAC operations for FPGA implementation +// phi^2 + 1/phi^2 = 3 | TRINITY + +module ZeroDSP_MAC; + // Import base types, operations, and ISA registers + use base::types; + use base::ops; + use isa::registers; + + // ========================================================== + // 1. MAC Configuration + // ========================================================== + + // MAC unit configuration + const MAC_WIDTH : usize = 27; // 27 trits per operand (TernaryWord) + const MAC_ACC_BITS : usize = 32; // 32-bit accumulator + const NUM_MAC_UNITS : usize = 8; // 8 parallel MAC units + const PIPELINE_STAGES : usize = 4; // 4-stage pipeline + + // MAC operation codes + const OP_MAC_MUL : u8 = 0; // Multiply only + const OP_MAC_MAC : u8 = 1; // Multiply-accumulate + const OP_MAC_MACC : u8 = 2; // Multiply-accumulate with carry + const OP_MAC_DOT : u8 = 3; // Dot product (full vector) + + // MAC status flags + const STATUS_READY : u8 = 0; // Ready for new operation + const STATUS_BUSY : u8 = 1; // Operation in progress + const STATUS_DONE : u8 = 2; // Operation complete + + // ========================================================== + // 2. Ternary Multiplication LUT + // ========================================================== + + // LUT configuration for ternary multiplication + // LUT size: 9 entries (3*3 for balanced ternary) + // Input: (a + 1) * 3 + (b + 1), where a,b in {-1, 0, +1} + // Output: ternary result (i8: -1, 0, +1) + // Index mapping: + // 0: (-1,-1) -> +1, 1: (-1, 0) -> 0, 2: (-1,+1) -> -1 + // 3: ( 0,-1) -> 0, 4: ( 0, 0) -> 0, 5: ( 0,+1) -> 0 + // 6: (+1,-1) -> -1, 7: (+1, 0) -> 0, 8: (+1,+1) -> +1 + const MAC_LUT : [9]i8 = [ + 1, // (-1,-1) -> +1 + 0, // (-1, 0) -> 0 + -1, // (-1,+1) -> -1 + 0, // ( 0,-1) -> 0 + 0, // ( 0, 0) -> 0 + 0, // ( 0,+1) -> 0 + -1, // (+1,-1) -> -1 + 0, // (+1, 0) -> 0 + 1, // (+1,+1) -> +1 + ]; + + // ========================================================== + // 3. MAC Unit State + // ========================================================== + + // MAC unit state + struct MACUnit { + accumulator : i32, // Accumulator value + status : u8, // Current status (READY/BUSY/DONE) + pipeline : [PIPELINE_STAGES]TernaryWord, // Pipeline registers + } + + // All MAC units + var mac_units : [NUM_MAC_UNITS]MACUnit = [ + MACUnit{ .accumulator = 0, .status = STATUS_READY, .pipeline = [TernaryWord{.raw = 0}; PIPELINE_STAGES] }, + MACUnit{ .accumulator = 0, .status = STATUS_READY, .pipeline = [TernaryWord{.raw = 0}; PIPELINE_STAGES] }, + MACUnit{ .accumulator = 0, .status = STATUS_READY, .pipeline = [TernaryWord{.raw = 0}; PIPELINE_STAGES] }, + MACUnit{ .accumulator = 0, .status = STATUS_READY, .pipeline = [TernaryWord{.raw = 0}; PIPELINE_STAGES] }, + MACUnit{ .accumulator = 0, .status = STATUS_READY, .pipeline = [TernaryWord{.raw = 0}; PIPELINE_STAGES] }, + MACUnit{ .accumulator = 0, .status = STATUS_READY, .pipeline = [TernaryWord{.raw = 0}; PIPELINE_STAGES] }, + MACUnit{ .accumulator = 0, .status = STATUS_READY, .pipeline = [TernaryWord{.raw = 0}; PIPELINE_STAGES] }, + MACUnit{ .accumulator = 0, .status = STATUS_READY, .pipeline = [TernaryWord{.raw = 0}; PIPELINE_STAGES] }, + ]; + + // ========================================================== + // 4. Trit Extraction + // ========================================================== + + // extract_trit(word: TernaryWord, index: usize) -> Trit + // Extract trit at given index from TernaryWord + // For packed ternary: each trit uses 2 bits + // Mapping: 0 -> 0, 1 -> +1, 2 -> -1 (encoding) + fn extract_trit(word: TernaryWord, index: usize) -> Trit { + const bit_pos = index * 2; + const mask = 3u32 << bit_pos; + const encoded = (word.raw >> bit_pos) & 3; + + if (encoded == 2) { + return Trit.neg; + } else if (encoded == 1) { + return Trit.pos; + } else { + return Trit.zero; + } + } + + // pack_trit(trit: Trit, index: usize) -> u32 + // Pack a trit into a word at the given position + fn pack_trit(trit: Trit, index: usize) -> u32 { + const bit_pos = index * 2; + const encoded : u32 = if (trit == Trit.neg) { 2 } + else if (trit == Trit.pos) { 1 } + else { 0 }; + return encoded << bit_pos; + } + + // ========================================================== + // 5. MAC Operations + // ========================================================== + + // mac_multiply(a: TernaryWord, b: TernaryWord, unit: u8) -> TernaryWord + // Ternary multiplication using LUT + // a[i] * b[i] for each trit, packed into TernaryWord + fn mac_multiply(a: TernaryWord, b: TernaryWord, unit: u8) -> TernaryWord { + if (unit >= NUM_MAC_UNITS) { + return TernaryWord{ .raw = 0 }; + } + + mac_units[unit].status = STATUS_BUSY; + + var result : u32 = 0; + var i : usize = 0; + + while (i < MAC_WIDTH) { + const a_trit = extract_trit(a, i); + const b_trit = extract_trit(b, i); + + // Compute LUT index: (a + 1) * 3 + (b + 1) + const a_idx = (a_trit as i8) + 1; // -1->0, 0->1, +1->2 + const b_idx = (b_trit as i8) + 1; + const lut_idx = (a_idx * 3) + (b_idx as usize); + + const product = MAC_LUT[lut_idx]; // i8: -1, 0, +1 + + // Pack product into result + if (product == 1) { + result = result | pack_trit(Trit.pos, i); + } else if (product == -1) { + result = result | pack_trit(Trit.neg, i); + } + + i = i + 1; + } + + mac_units[unit].status = STATUS_DONE; + return TernaryWord{ .raw = result }; + } + + // mac_cycle(a: TernaryWord, b: TernaryWord, unit: u8, acc: i32) -> i32 + // Single MAC cycle: acc = acc + Sigma (a[i] * b[i]) + // Uses signed integer accumulation + fn mac_cycle(a: TernaryWord, b: TernaryWord, unit: u8, acc: i32) -> i32 { + if (unit >= NUM_MAC_UNITS) { + return 0; + } + + mac_units[unit].status = STATUS_BUSY; + + // Compute dot product: Sigma a[i] * b[i] + var dot : i32 = 0; + var i : usize = 0; + + while (i < MAC_WIDTH) { + const a_trit = extract_trit(a, i); + const b_trit = extract_trit(b, i); + const product = (a_trit as i8) * (b_trit as i8); + dot = dot + product; + i = i + 1; + } + + // Add to accumulator + mac_units[unit].accumulator = acc + dot; + + mac_units[unit].status = STATUS_DONE; + return mac_units[unit].accumulator; + } + + // mac_dot_product(a: []TernaryWord, b: []TernaryWord, len: usize, unit: u8) -> i32 + // Full vector dot product using MAC unit + // result = Sigma_i Sigma_j (a[i][j] * b[i][j]) + fn mac_dot_product(a: []TernaryWord, b: []TernaryWord, len: usize, unit: u8) -> i32 { + if (unit >= NUM_MAC_UNITS) { + return 0; + } + + mac_units[unit].status = STATUS_BUSY; + + // Reset accumulator + mac_units[unit].accumulator = 0; + + // Process each vector element + var i : usize = 0; + + while (i < len) { + mac_units[unit].accumulator = mac_cycle( + a[i], b[i], unit, mac_units[unit].accumulator + ); + i = i + 1; + } + + mac_units[unit].status = STATUS_DONE; + return mac_units[unit].accumulator; + } + + // mac_matrix_vector( + // mat: []TernaryWord, // Matrix [rows][cols] + // vec: []TernaryWord, // Vector [cols] + // rows: usize, + // cols: usize, + // result: []i32, // [rows] output + // unit_assign: []u8 // MAC unit assignment per row + // ) -> void + // Matrix-vector multiplication using MAC units + // result[i] = Sigma_j mat[i][j] * vec[j] + fn mac_matrix_vector( + mat: []TernaryWord, + vec: []TernaryWord, + rows: usize, + cols: usize, + result: []i32, + unit_assign: []u8, + ) -> void { + var row : usize = 0; + + while (row < rows) { + const unit = unit_assign[row]; + + // Reset accumulator for this row's MAC unit + mac_units[unit].accumulator = 0; + + var col : usize = 0; + + while (col < cols) { + const mat_idx = row * cols + col; + mac_units[unit].accumulator = mac_cycle( + mat[mat_idx], vec[col], unit, mac_units[unit].accumulator + ); + col = col + 1; + } + + result[row] = mac_units[unit].accumulator; + row = row + 1; + } + } + + // ========================================================== + // 6. MAC Unit Management + // ========================================================== + + // mac_status_read(unit: u8) -> u8 + // Read MAC unit status + fn mac_status_read(unit: u8) -> u8 { + if (unit >= NUM_MAC_UNITS) { + return 0xFF; // Error sentinel + } + return mac_units[unit].status; + } + + // mac_status_write(unit: u8, status: u8) -> bool + // Write MAC unit status + fn mac_status_write(unit: u8, status: u8) -> bool { + if (unit >= NUM_MAC_UNITS) { + return false; + } + mac_units[unit].status = status; + return true; + } + + // mac_reset(unit: u8) -> bool + // Reset MAC unit accumulator and status + fn mac_reset(unit: u8) -> bool { + if (unit >= NUM_MAC_UNITS) { + return false; + } + mac_units[unit].accumulator = 0; + mac_units[unit].status = STATUS_READY; + mac_units[unit].pipeline = [TernaryWord{.raw = 0}; PIPELINE_STAGES]; + return true; + } + + // mac_reset_all() -> void + // Reset all MAC units + fn mac_reset_all() -> void { + var i : usize = 0; + + while (i < NUM_MAC_UNITS) { + mac_reset(i); + i = i + 1; + } + } + + // mac_get_accumulator(unit: u8) -> i32 + // Get accumulator value from MAC unit + fn mac_get_accumulator(unit: u8) -> i32 { + if (unit >= NUM_MAC_UNITS) { + return 0; + } + return mac_units[unit].accumulator; + } + + // mac_set_accumulator(unit: u8, value: i32) -> bool + // Set accumulator value for MAC unit + fn mac_set_accumulator(unit: u8, value: i32) -> bool { + if (unit >= NUM_MAC_UNITS) { + return false; + } + mac_units[unit].accumulator = value; + return true; + } + + // ========================================================== + // 7. Parallel MAC Operations + // ========================================================== + + // mac_parallel_multiply( + // a: []TernaryWord, + // b: []TernaryWord, + // results: []TernaryWord, + // count: usize + // ) -> void + // Parallel multiplication using multiple MAC units + // results[i] = mac_multiply(a[i], b[i], i % NUM_MAC_UNITS) + fn mac_parallel_multiply( + a: []TernaryWord, + b: []TernaryWord, + results: []TernaryWord, + count: usize, + ) -> void { + var i : usize = 0; + + while (i < count) { + const unit = (i % NUM_MAC_UNITS) as u8; + results[i] = mac_multiply(a[i], b[i], unit); + i = i + 1; + } + } + + // ========================================================== + // TDD-Inside-Spec: Tests and Invariants for ZeroDSP_MAC + // ========================================================== + + test mac_lut_multiply_pos_pos + given a = TernaryWord{.raw = 0} + and b = TernaryWord{.raw = 0} + and set_trit = pack_trit(Trit.pos, 0) + when a = TernaryWord{.raw = set_trit} + and b = TernaryWord{.raw = set_trit} + and result = mac_multiply(a, b, 0) + and result_trit = extract_trit(result, 0) + then result_trit == Trit.pos + + test mac_lut_multiply_neg_neg + given a = TernaryWord{.raw = 0} + and b = TernaryWord{.raw = 0} + and set_trit = pack_trit(Trit.neg, 0) + when a = TernaryWord{.raw = set_trit} + and b = TernaryWord{.raw = set_trit} + and result = mac_multiply(a, b, 0) + and result_trit = extract_trit(result, 0) + then result_trit == Trit.pos + + test mac_lut_multiply_pos_neg + given a = TernaryWord{.raw = pack_trit(Trit.pos, 0)} + and b = TernaryWord{.raw = pack_trit(Trit.neg, 0)} + and result = mac_multiply(a, b, 0) + and result_trit = extract_trit(result, 0) + then result_trit == Trit.neg + + test mac_lut_multiply_with_zero + given a = TernaryWord{.raw = pack_trit(Trit.pos, 0)} + and b = TernaryWord{.raw = pack_trit(Trit.zero, 0)} + and result = mac_multiply(a, b, 0) + and result_trit = extract_trit(result, 0) + then result_trit == Trit.zero + + test mac_lut_size_9 + given size = MAC_LUT.len() + then size == 9 + + test mac_num_units_8 + given units = NUM_MAC_UNITS + then units == 8 + + test mac_width_27 + given width = MAC_WIDTH + then width == 27 + + test mac_pipeline_stages_4 + given stages = PIPELINE_STAGES + then stages == 4 + + test mac_cycle_with_zero_accumulator + given a = TernaryWord{.raw = 0} + and b = TernaryWord{.raw = 0} + and a = TernaryWord{.raw = pack_trit(Trit.pos, 0) | pack_trit(Trit.pos, 1)} + and b = TernaryWord{.raw = pack_trit(Trit.pos, 0) | pack_trit(Trit.pos, 1)} + and result = mac_cycle(a, b, 0, 0) + then result == 2 + + test mac_cycle_with_initial_accumulator + given a = TernaryWord{.raw = pack_trit(Trit.pos, 0)} + and b = TernaryWord{.raw = pack_trit(Trit.pos, 0)} + and result = mac_cycle(a, b, 0, 5) + then result == 6 + + test mac_dot_product_simple + given a = [TernaryWord{.raw = pack_trit(Trit.pos, 0)}, TernaryWord{.raw = pack_trit(Trit.pos, 0)}] + and b = [TernaryWord{.raw = pack_trit(Trit.pos, 0)}, TernaryWord{.raw = pack_trit(Trit.pos, 0)}] + and result = mac_dot_product(a, b, 2, 0) + then result == 2 + + test mac_dot_product_with_negatives + given a = [TernaryWord{.raw = pack_trit(Trit.pos, 0)}, TernaryWord{.raw = pack_trit(Trit.neg, 0)}] + and b = [TernaryWord{.raw = pack_trit(Trit.pos, 0)}, TernaryWord{.raw = pack_trit(Trit.pos, 0)}] + and result = mac_dot_product(a, b, 2, 0) + then result == 0 + + test mac_status_initially_ready + given status = mac_status_read(0) + then status == STATUS_READY + + test mac_status_after_operation_is_done + given a = TernaryWord{.raw = pack_trit(Trit.pos, 0)} + and b = TernaryWord{.raw = pack_trit(Trit.pos, 0)} + and mac_multiply(a, b, 0) + and status = mac_status_read(0) + then status == STATUS_DONE + + test mac_reset_clears_accumulator + given a = TernaryWord{.raw = pack_trit(Trit.pos, 0)} + and b = TernaryWord{.raw = pack_trit(Trit.pos, 0)} + and mac_cycle(a, b, 0, 0) + and mac_reset(0) + and acc = mac_get_accumulator(0) + then acc == 0 + + test mac_reset_clears_status + given a = TernaryWord{.raw = pack_trit(Trit.pos, 0)} + and b = TernaryWord{.raw = pack_trit(Trit.pos, 0)} + and mac_multiply(a, b, 0) + and mac_reset(0) + and status = mac_status_read(0) + then status == STATUS_READY + + test mac_reset_all_clears_all_units + given mac_multiply(TernaryWord{.raw = pack_trit(Trit.pos, 0)}, TernaryWord{.raw = 0}, 0) + and mac_multiply(TernaryWord{.raw = pack_trit(Trit.pos, 0)}, TernaryWord{.raw = 0}, 1) + and mac_reset_all() + and acc0 = mac_get_accumulator(0) + and acc1 = mac_get_accumulator(1) + then acc0 == 0 and acc1 == 0 + + test mac_invalid_unit_returns_zero + given result = mac_multiply(TernaryWord{.raw = 0}, TernaryWord{.raw = 0}, 99) + then result.raw == 0 + + test mac_matrix_vector_2x2 + given mat = [TernaryWord{.raw = pack_trit(Trit.pos, 0)}, TernaryWord{.raw = pack_trit(Trit.zero, 0)}, + TernaryWord{.raw = pack_trit(Trit.zero, 0)}, TernaryWord{.raw = pack_trit(Trit.pos, 0)}] + and vec = [TernaryWord{.raw = pack_trit(Trit.pos, 0)}, TernaryWord{.raw = pack_trit(Trit.pos, 0)}] + and result = [0i32; 2] + and units = [0u8, 1u8] + when mac_matrix_vector(mat, vec, 2, 2, result, units) + then result[0] == 1 and result[1] == 1 + + test mac_extract_trit_zero + given word = TernaryWord{.raw = 0} + and trit = extract_trit(word, 0) + then trit == Trit.zero + + test mac_extract_trit_pos + given word = TernaryWord{.raw = 1} // 0b01 + and trit = extract_trit(word, 0) + then trit == Trit.pos + + test mac_extract_trit_neg + given word = TernaryWord{.raw = 2} // 0b10 + and trit = extract_trit(word, 0) + then trit == Trit.neg + + test mac_pack_trit_roundtrip + given original = Trit.pos + and packed = pack_trit(original, 0) + and word = TernaryWord{.raw = packed} + and extracted = extract_trit(word, 0) + then extracted == original + + test mac_parallel_multiply_independence + given a = [TernaryWord{.raw = pack_trit(Trit.pos, 0)}; 8] + and b = [TernaryWord{.raw = pack_trit(Trit.pos, 0)}; 8] + and results = [TernaryWord{.raw = 0}; 8] + when mac_parallel_multiply(a, b, results, 8) + then results[0].raw != 0 and results[1].raw != 0 + + invariant mac_lut_size_constant + assert MAC_LUT.len() == 9 + + invariant mac_lut_covers_all_combinations + // All 9 combinations of trit multiplication are covered + assert MAC_LUT.len() == 3 * 3 + + invariant mac_num_units_constant + assert NUM_MAC_UNITS == 8 + + invariant mac_width_constant + assert MAC_WIDTH == 27 + + invariant mac_pipeline_stages_constant + assert PIPELINE_STAGES == 4 + + invariant mac_lut_correctness_pos_pos + assert MAC_LUT[8] == 1 // (+1,+1) -> +1 + + invariant mac_lut_correctness_neg_neg + assert MAC_LUT[0] == 1 // (-1,-1) -> +1 + + invariant mac_lut_correctness_pos_neg + assert MAC_LUT[6] == -1 // (+1,-1) -> -1 + + invariant mac_lut_correctness_with_zero + assert MAC_LUT[1] == 0 and MAC_LUT[2] == -1 // (-1,0) -> 0, (-1,+1) -> -1 + assert MAC_LUT[3] == 0 and MAC_LUT[4] == 0 // (0,-1) -> 0, (0,0) -> 0 + assert MAC_LUT[5] == 0 and MAC_LUT[7] == 0 // (0,+1) -> 0, (+1,0) -> 0 + + invariant mac_status_range_valid + given status = mac_status_read(0) + assert status == STATUS_READY or status == STATUS_BUSY or status == STATUS_DONE + + invariant mac_reset_clears_accumulator + given mac_cycle(TernaryWord{.raw = pack_trit(Trit.pos, 0)}, TernaryWord{.raw = pack_trit(Trit.pos, 0)}, 0, 42) + and mac_reset(0) + and acc = mac_get_accumulator(0) + assert acc == 0 + + invariant mac_reset_sets_status_ready + given mac_multiply(TernaryWord{.raw = 0}, TernaryWord{.raw = 0}, 0) + and mac_reset(0) + and status = mac_status_read(0) + assert status == STATUS_READY + + invariant mac_multiply_preserves_width + given a = TernaryWord{.raw = 0xFFFFFFFF} + and b = TernaryWord{.raw = 0xFFFFFFFF} + and result = mac_multiply(a, b, 0) + // Result should be valid TernaryWord (top bits should be zero) + assert result.raw < (1u32 << (2 * MAC_WIDTH)) + + invariant mac_dot_product_commutes_with_scalar + given a = [TernaryWord{.raw = pack_trit(Trit.pos, 0)}] + and b = [TernaryWord{.raw = pack_trit(Trit.pos, 0)}] + and dot1 = mac_dot_product(a, b, 1, 0) + and dot2 = mac_dot_product(b, a, 1, 0) + assert dot1 == dot2 + + invariant mac_accumulator_32_bit_range + // Accumulator should fit in 32-bit signed range + given acc = mac_get_accumulator(0) + // In real implementation, would check for overflow + assert true // Placeholder for actual range check + + invariant mac_all_units_initially_ready + var all_ready = true + var i = 0 + while (i < NUM_MAC_UNITS) { + if (mac_status_read(i) != STATUS_READY) { + all_ready = false; + break; + } + i = i + 1; + } + assert all_ready + + invariant mac_units_independent + given a0 = TernaryWord{.raw = pack_trit(Trit.pos, 0)} + and b0 = TernaryWord{.raw = pack_trit(Trit.pos, 0)} + and a1 = TernaryWord{.raw = pack_trit(Trit.neg, 0)} + and b1 = TernaryWord{.raw = pack_trit(Trit.neg, 0)} + and res0 = mac_multiply(a0, b0, 0) + and res1 = mac_multiply(a1, b1, 1) + and acc0 = mac_get_accumulator(0) + and acc1 = mac_get_accumulator(1) + // Unit 0 operation shouldn't affect unit 1 + assert acc0 == 0 and acc1 == 0 + + bench mac_multiply_latency + measure: nanoseconds to mac_multiply(TernaryWord{.raw = 0xFFFFFFFF}, TernaryWord{.raw = 0xFFFFFFFF}, 0) + target: < 200ns + + bench mac_cycle_latency + measure: nanoseconds to mac_cycle(TernaryWord{.raw = 0xFFFFFFFF}, TernaryWord{.raw = 0xFFFFFFFF}, 0, 0) + target: < 300ns + + bench mac_dot_product_latency + measure: nanoseconds to mac_dot_product([TernaryWord{.raw = 0xFFFFFFFF}; 10], [TernaryWord{.raw = 0xFFFFFFFF}; 10], 10, 0) + target: < 3000ns + + bench mac_extract_trit_latency + measure: nanoseconds to extract_trit(TernaryWord{.raw = 0xFFFFFFFF}, 13) + target: < 20ns + + bench mac_status_read_latency + measure: nanoseconds to mac_status_read(0) + target: < 20ns + + bench mac_reset_latency + measure: nanoseconds to mac_reset(0) + target: < 50ns + + bench mac_parallel_multiply_throughput + measure: nanoseconds for 8 parallel multiplies + target: < 500ns + + bench mac_matrix_vector_latency + measure: nanoseconds for 4x4 matrix-vector multiply + target: < 5000ns + diff --git a/apps/website/public/t27/specs/fpga/memory.t27 b/apps/website/public/t27/specs/fpga/memory.t27 new file mode 100644 index 0000000000..32ed079315 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/memory.t27 @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/memory.t27 +// Memory (BRAM/DRAM/ROM) Abstraction for Trinity T27 FPGA HIR +// Defines block memory primitives with read/write ports +// Uses flat arrays + count fields (parser-compatible, no Vec/generics) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Memory { + + // === Memory kind === + + pub const MemKind = enum(i8) { + bram = 0, + dram = 1, + rom = 2, + }; + + // === Port kind (read vs write vs read-write) === + + pub const MemPortKind = enum(i8) { + read_port = 0, + write_port = 1, + readwrite_port = 2, + }; + + // === Latency (combinational vs registered) === + + pub const MemLatency = enum(i8) { + comb_read = 0, + reg_read = 1, + }; + + // === Memory port descriptor === + + pub struct MemPort { + name : &str, + kind : i8, + addr_width : u32, + data_width : u32, + latency : i8, + } + + // === Memory block descriptor === + + pub const MAX_MEM_PORTS : u32 = 8; + + pub struct MemDesc { + name : &str, + kind : i8, + depth : u32, + data_width : u32, + addr_width : u32, + ports : [8]MemPort, + port_count : u32, + } + + // === Constructor helpers === + + fn empty_mem_port() -> MemPort { + return MemPort{ + .name = "", + .kind = 0, + .addr_width = 0, + .data_width = 0, + .latency = 0, + }; + } + + fn make_mem_port(name: &str, kind: i8, addr_width: u32, data_width: u32) -> MemPort { + return MemPort{ + .name = name, + .kind = kind, + .addr_width = addr_width, + .data_width = data_width, + .latency = 0, + }; + } + + fn empty_mem(name: &str, kind: i8) -> MemDesc { + return MemDesc{ + .name = name, + .kind = kind, + .depth = 0, + .data_width = 0, + .addr_width = 0, + .ports = [empty_mem_port(); 8], + .port_count = 0, + }; + } + + fn make_bram(name: &str, depth: u32, data_width: u32) -> MemDesc { + var addr_width : u32 = 0; + var d : u32 = depth; + while (d > 1) { + addr_width = addr_width + 1; + d = d / 2; + } + if (addr_width == 0) { + addr_width = 1; + } + return MemDesc{ + .name = name, + .kind = 0, + .depth = depth, + .data_width = data_width, + .addr_width = addr_width, + .ports = [empty_mem_port(); 8], + .port_count = 0, + }; + } + + fn make_rom(name: &str, depth: u32, data_width: u32) -> MemDesc { + var addr_width : u32 = 0; + var d : u32 = depth; + while (d > 1) { + addr_width = addr_width + 1; + d = d / 2; + } + if (addr_width == 0) { + addr_width = 1; + } + return MemDesc{ + .name = name, + .kind = 2, + .depth = depth, + .data_width = data_width, + .addr_width = addr_width, + .ports = [empty_mem_port(); 8], + .port_count = 0, + }; + } + + fn add_read_port(mem: MemDesc, name: &str) -> MemDesc { + var result = mem; + if (result.port_count < 8) { + result.ports[result.port_count] = make_mem_port(name, 0, result.addr_width, result.data_width); + result.port_count = result.port_count + 1; + } + return result; + } + + fn add_write_port(mem: MemDesc, name: &str) -> MemDesc { + var result = mem; + if (result.port_count < 8) { + result.ports[result.port_count] = make_mem_port(name, 1, result.addr_width, result.data_width); + result.port_count = result.port_count + 1; + } + return result; + } + + fn add_rw_port(mem: MemDesc, name: &str) -> MemDesc { + var result = mem; + if (result.port_count < 8) { + result.ports[result.port_count] = make_mem_port(name, 2, result.addr_width, result.data_width); + result.port_count = result.port_count + 1; + } + return result; + } + + // === Query functions === + + fn port_count(mem: MemDesc) -> u32 { + return mem.port_count; + } + + fn total_bits(mem: MemDesc) -> u32 { + return mem.depth * mem.data_width; + } + + fn has_read_port(mem: MemDesc) -> bool { + var i : u32 = 0; + while (i < mem.port_count) { + if (mem.ports[i].kind == 0 or mem.ports[i].kind == 2) { + return true; + } + i = i + 1; + } + return false; + } + + fn has_write_port(mem: MemDesc) -> bool { + var i : u32 = 0; + while (i < mem.port_count) { + if (mem.ports[i].kind == 1 or mem.ports[i].kind == 2) { + return true; + } + i = i + 1; + } + return false; + } + + fn is_rom(mem: MemDesc) -> bool { + return mem.kind == 2; + } + + fn is_bram(mem: MemDesc) -> bool { + return mem.kind == 0; + } + + fn addr_width(mem: MemDesc) -> u32 { + return mem.addr_width; + } + + // === BRAM18E1 resource estimation === + + fn bram18_count(mem: MemDesc) -> u32 { + var bits : u32 = mem.depth * mem.data_width; + var count : u32 = bits / 18432; + if (bits % 18432 > 0) { + count = count + 1; + } + return count; + } + + // === Validation === + + fn validate_mem(mem: MemDesc) -> u32 { + var errors : u32 = 0; + + if (mem.name == "") { + errors = errors + 1; + } + + if (mem.depth == 0) { + errors = errors + 1; + } + + if (mem.data_width == 0) { + errors = errors + 1; + } + + if (mem.kind == 2 and has_write_port(mem)) { + errors = errors + 1; + } + + return errors; + } + + // === Tests === + + test empty_mem_has_no_ports + given m = empty_mem("test", 0) + then port_count(m) == 0 + + test make_bram_has_depth + given m = make_bram("ram1", 1024, 32) + then m.depth == 1024 + and m.data_width == 32 + and m.addr_width == 10 + + test make_bram_small + given m = make_bram("ram2", 4, 8) + then m.addr_width == 2 + + test make_bram_single + given m = make_bram("ram3", 1, 16) + then m.addr_width == 1 + + test add_read_port_increments + given m = make_bram("ram1", 256, 16) + and m2 = add_read_port(m, "rda") + then port_count(m2) == 1 + and has_read_port(m2) == true + and has_write_port(m2) == false + + test add_write_port_increments + given m = make_bram("ram1", 256, 16) + and m2 = add_write_port(m, "wra") + then port_count(m2) == 1 + and has_write_port(m2) == true + and has_read_port(m2) == false + + test add_rw_port + given m = make_bram("ram1", 256, 16) + and m2 = add_rw_port(m, "rwa") + then port_count(m2) == 1 + and has_read_port(m2) == true + and has_write_port(m2) == true + + test is_rom + given m = make_rom("rom1", 512, 8) + then is_rom(m) == true + and is_bram(m) == false + + test total_bits + given m = make_bram("ram1", 1024, 32) + then total_bits(m) == 32768 + + test bram18_count_small + given m = make_bram("ram1", 1024, 18) + then bram18_count(m) == 1 + + test bram18_count_large + given m = make_bram("ram1", 4096, 36) + then bram18_count(m) >= 8 + + test validate_ok + given m = make_bram("ram1", 256, 16) + and m2 = add_read_port(m, "rda") + and errors = validate_mem(m2) + then errors == 0 + + test validate_empty_name + given m = empty_mem("", 0) + and m2 = MemDesc{.name = "", .kind = 0, .depth = 16, .data_width = 8, .addr_width = 4, .ports = [empty_mem_port(); 8], .port_count = 0} + and errors = validate_mem(m2) + then errors > 0 + + test validate_zero_depth + given m = MemDesc{.name = "x", .kind = 0, .depth = 0, .data_width = 8, .addr_width = 1, .ports = [empty_mem_port(); 8], .port_count = 0} + and errors = validate_mem(m) + then errors > 0 + + test rom_with_write_port_invalid + given m = make_rom("rom1", 256, 16) + and m2 = add_write_port(m, "wra") + and errors = validate_mem(m2) + then errors > 0 + + // === Invariants === + + invariant bram_depth_positive + given m = make_bram("inv", 256, 16) + assert m.depth > 0 + + invariant bram_data_width_positive + given m = make_bram("inv", 256, 16) + assert m.data_width > 0 + + invariant addr_width_positive_for_nonzero_depth + given m = make_bram("inv", 256, 16) + assert m.addr_width > 0 + + invariant total_bits_non_negative + given m = make_bram("inv", 256, 16) + assert total_bits(m) >= 0 + + invariant port_count_within_bounds + given m = make_bram("inv", 256, 16) + and m2 = add_read_port(m, "rda") + assert port_count(m2) <= 8 + + invariant bram18_count_positive + given m = make_bram("inv", 1024, 32) + assert bram18_count(m) > 0 + + // === Benchmarks === + + bench bram18_count_latency + measure: nanoseconds to bram18_count(make_bram("bench", 4096, 32)) + target: < 100ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/partition.t27 b/apps/website/public/t27/specs/fpga/partition.t27 new file mode 100644 index 0000000000..c6c04a9ae4 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/partition.t27 @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/partition.t27 +// T27 Multi-FPGA Partition Specification +// Automatically partitions HIR modules across multiple FPGAs +// Estimates inter-FPGA bandwidth and latency +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Partition { + + // === FPGA node === + + pub struct FpgaNode { + name : &str, + device : &str, + luts : u32, + ffs : u32, + bram18 : u32, + dsp48 : u32, + io_pins : u32, + } + + fn fpga_node(name: &str, device: &str, luts: u32, ffs: u32, bram18: u32, dsp48: u32, io: u32) -> FpgaNode { + return FpgaNode{ + .name = name, + .device = device, + .luts = luts, + .ffs = ffs, + .bram18 = bram18, + .dsp48 = dsp48, + .io_pins = io, + }; + } + + fn arty_a7_node(name: &str) -> FpgaNode { + return fpga_node(name, "xc7a100t", 63400, 126800, 135, 240, 300); + } + + // === Inter-FPGA link === + + pub struct InterFpgaLink { + fpga_a : u32, + fpga_b : u32, + width : u32, + protocol : i8, + max_mbps : u32, + } + + fn lvds_link(a: u32, b: u32, width: u32) -> InterFpgaLink { + return InterFpgaLink{ + .fpga_a = a, + .fpga_b = b, + .width = width, + .protocol = 0, + .max_mbps = 1000, + }; + } + + fn serdes_link(a: u32, b: u32) -> InterFpgaLink { + return InterFpgaLink{ + .fpga_a = a, + .fpga_b = b, + .width = 4, + .protocol = 1, + .max_mbps = 6250, + }; + } + + fn link_bandwidth_mbps(link: InterFpgaLink) -> u32 { + return link.width * link.max_mbps; + } + + // === Partition assignment === + + pub struct PartitionAssign { + module_name : &str, + fpga_idx : u32, + luts : u32, + ffs : u32, + bram18 : u32, + dsp48 : u32, + } + + // NB: the parameter is `mod_name`, not `module`. `module` is the keyword + // that opens a declaration, and a parameter carrying that name aborts the + // body parse -- silently, taking every declaration after it, tests + // included, out of the emitted module. + fn assignment(mod_name: &str, fpga: u32, luts: u32, ffs: u32, bram: u32, dsp: u32) -> PartitionAssign { + return PartitionAssign{ + .module_name = mod_name, + .fpga_idx = fpga, + .luts = luts, + .ffs = ffs, + .bram18 = bram, + .dsp48 = dsp, + }; + } + + // === Partition result === + + pub struct PartitionResult { + num_fpgas : u32, + num_assignments : u32, + num_links : u32, + total_bandwidth_mbps : u32, + balanced : bool, + errors : u32, + } + + fn partition_ok(fpgas: u32, assigns: u32, links: u32, bw: u32) -> PartitionResult { + return PartitionResult{ + .num_fpgas = fpgas, + .num_assignments = assigns, + .num_links = links, + .total_bandwidth_mbps = bw, + .balanced = true, + .errors = 0, + }; + } + + fn partition_fail(errors: u32) -> PartitionResult { + return PartitionResult{ + .num_fpgas = 0, + .num_assignments = 0, + .num_links = 0, + .total_bandwidth_mbps = 0, + .balanced = false, + .errors = errors, + }; + } + + fn passed(r: PartitionResult) -> bool { + return r.errors == 0; + } + + // === Query === + + // Read-only over the array, so the parameter is const: `[InterFpgaLink]` + // emits a mutable slice, which no expression a test can write will coerce + // to, and the test below could not be called at all. + fn total_link_bandwidth(links: []const InterFpgaLink, count: u32) -> u32 { + var total : u32 = 0; + var i : u32 = 0; + while i < count { + total = total + link_bandwidth_mbps(links[i]); + i = i + 1; + } + return total; + } + + fn fpga_util(fpga: FpgaNode, used_luts: u32) -> u32 { + if fpga.luts == 0 { + return 0; + } + return used_luts * 100 / fpga.luts; + } + + fn fpga_remaining(fpga: FpgaNode, used_luts: u32) -> u32 { + if used_luts > fpga.luts { + return 0; + } + return fpga.luts - used_luts; + } + + // === Validation === + + fn validate_node(n: FpgaNode) -> u32 { + var errors : u32 = 0; + if n.name == "" { + errors = errors + 1; + } + if n.luts == 0 { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test fpga_node_creation + given n = arty_a7_node("fpga0") + then n.name == "fpga0" + and n.device == "xc7a100t" + and n.luts == 63400 + + test lvds_link_creation + given l = lvds_link(0, 1, 8) + then l.fpga_a == 0 + and l.fpga_b == 1 + and l.width == 8 + and link_bandwidth_mbps(l) == 8000 + + test serdes_link_creation + given l = serdes_link(0, 1) + then l.protocol == 1 + and link_bandwidth_mbps(l) == 25000 + + test assignment_creation + given a = assignment("uart", 0, 200, 100, 1, 0) + then a.module_name == "uart" + and a.fpga_idx == 0 + + test partition_ok_creation + given r = partition_ok(2, 5, 1, 8000) + then r.num_fpgas == 2 + and r.balanced == true + and passed(r) == true + + test partition_fail_creation + given r = partition_fail(1) + then passed(r) == false + + test total_link_bandwidth + then total_link_bandwidth(&[_]InterFpgaLink{lvds_link(0, 1, 8), lvds_link(1, 2, 4)}, 2) == 12000 + + // count, not length, decides how much of the array is summed: the flat + // array + count convention means a short count must ignore the tail. + test total_link_bandwidth_respects_count + then total_link_bandwidth(&[_]InterFpgaLink{lvds_link(0, 1, 8), lvds_link(1, 2, 4)}, 1) == 8000 + and total_link_bandwidth(&[_]InterFpgaLink{lvds_link(0, 1, 8), lvds_link(1, 2, 4)}, 0) == 0 + + test fpga_util_calc + given n = arty_a7_node("fpga0") + then fpga_util(n, 31700) == 50 + + test fpga_remaining_calc + given n = arty_a7_node("fpga0") + then fpga_remaining(n, 10000) == 53400 + + test fpga_remaining_over + given n = arty_a7_node("fpga0") + then fpga_remaining(n, 100000) == 0 + + test validate_node_ok + given n = arty_a7_node("fpga0") + then validate_node(n) == 0 + + test validate_node_empty + given n = FpgaNode{.name = "", .device = "xc7a100t", .luts = 0, .ffs = 0, .bram18 = 0, .dsp48 = 0, .io_pins = 0} + then validate_node(n) > 0 + + // Both faults are counted, not just the first one found. + test validate_node_counts_every_fault + given n = FpgaNode{.name = "", .device = "xc7a100t", .luts = 0, .ffs = 0, .bram18 = 0, .dsp48 = 0, .io_pins = 0} + then validate_node(n) == 2 + + // The three ends of the utilisation range. + test fpga_util_endpoints + given n = arty_a7_node("fpga0") + then fpga_util(n, 0) == 0 + and fpga_util(n, 63400) == 100 + + // A zero-LUT node would divide by zero; the guard returns 0 instead. + test fpga_util_zero_lut_node + given n = FpgaNode{.name = "empty", .device = "none", .luts = 0, .ffs = 0, .bram18 = 0, .dsp48 = 0, .io_pins = 0} + then fpga_util(n, 1000) == 0 + + // Exactly full is 0 remaining, not an underflow. + test fpga_remaining_exact_fit + given n = arty_a7_node("fpga0") + then fpga_remaining(n, 63400) == 0 + and fpga_remaining(n, 0) == 63400 + + // used + remaining == capacity, for any load within capacity. + test fpga_remaining_complements_use + given n = arty_a7_node("fpga0") + then fpga_remaining(n, 20000) + 20000 == n.luts + + // partition_fail carries the error count through and never reports passed. + test partition_fail_carries_count + given r = partition_fail(3) + then r.errors == 3 + and r.balanced == false + and r.num_fpgas == 0 + and passed(r) == false + + // SERDES is 4 lanes at 6250 Mbps; the protocol tag separates it from LVDS. + test serdes_link_shape + given s = serdes_link(2, 3) + and l = lvds_link(2, 3, 4) + then s.width == 4 + and s.fpga_a == 2 + and s.fpga_b == 3 + and s.protocol != l.protocol + + // === Invariants === + + invariant bandwidth_non_negative + given l = lvds_link(0, 1, 8) + assert link_bandwidth_mbps(l) >= 0 + + invariant util_bounded + given n = arty_a7_node("inv") + assert fpga_util(n, 0) == 0 + + bench fpga_util_latency + measure: nanoseconds to fpga_util(arty_a7_node("bench"), 15850) + target: < 50ns + + bench link_bandwidth_latency + measure: nanoseconds to link_bandwidth_mbps(lvds_link(0, 1, 8)) + target: < 50ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/placement.t27 b/apps/website/public/t27/specs/fpga/placement.t27 new file mode 100644 index 0000000000..f1120cf1b7 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/placement.t27 @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/placement.t27 +// T27 Placement Constraint Generator Specification +// Auto-generates placement hints and routing constraints from HIR connectivity +// Groups related modules into floorplan regions for optimal routing +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Placement { + + // === Region kind === + + pub const RegionKind = enum(i8) { + clock_region = 0, + io_bank = 1, + bram_column = 2, + dsp_column = 3, + logic_cluster = 4, + } + + // === Placement region === + + pub struct PlacementRegion { + name : &str, + kind : i8, + x0 : u32, + y0 : u32, + x1 : u32, + y1 : u32, + } + + fn region(name: &str, kind: i8, x0: u32, y0: u32, x1: u32, y1: u32) -> PlacementRegion { + return PlacementRegion{ + .name = name, + .kind = kind, + .x0 = x0, + .y0 = y0, + .x1 = x1, + .y1 = y1, + }; + } + + fn logic_cluster(name: &str, x0: u32, y0: u32, x1: u32, y1: u32) -> PlacementRegion { + return region(name, 4, x0, y0, x1, y1); + } + + fn bram_column(name: &str, col: u32, y0: u32, y1: u32) -> PlacementRegion { + return region(name, 2, col, y0, col + 1, y1); + } + + fn dsp_column(name: &str, col: u32, y0: u32, y1: u32) -> PlacementRegion { + return region(name, 3, col, y0, col + 1, y1); + } + + // === Placement hint === + + pub struct PlacementHint { + module_name : &str, + region_name : &str, + priority : u32, + } + + fn hint(module_name: &str, region_name: &str, priority: u32) -> PlacementHint { + return PlacementHint{ + .module_name = module_name, + .region_name = region_name, + .priority = priority, + }; + } + + // === Routing constraint === + + pub struct RouteConstraint { + source : &str, + sink : &str, + max_delay_ps : u32, + kind : i8, + } + + fn route_constraint(source: &str, sink: &str, max_delay_ps: u32) -> RouteConstraint { + return RouteConstraint{ + .source = source, + .sink = sink, + .max_delay_ps = max_delay_ps, + .kind = 0, + }; + } + + // === Floorplan === + + pub struct Floorplan { + name : &str, + device : &str, + } + + fn floorplan(name: &str, device: &str) -> Floorplan { + return Floorplan{ + .name = name, + .device = device, + }; + } + + // === Query functions === + + fn region_width(r: PlacementRegion) -> u32 { + if r.x1 > r.x0 { + return r.x1 - r.x0; + } + return 0; + } + + fn region_height(r: PlacementRegion) -> u32 { + if r.y1 > r.y0 { + return r.y1 - r.y0; + } + return 0; + } + + fn region_area(r: PlacementRegion) -> u32 { + return region_width(r) * region_height(r); + } + + fn regions_overlap(a: PlacementRegion, b: PlacementRegion) -> bool { + return a.x0 < b.x1 and a.x1 > b.x0 and a.y0 < b.y1 and a.y1 > b.y0; + } + + // === Validation === + + fn validate_region(r: PlacementRegion) -> u32 { + var errors : u32 = 0; + if r.name == "" { + errors = errors + 1; + } + if r.x1 < r.x0 { + errors = errors + 1; + } + if r.y1 < r.y0 { + errors = errors + 1; + } + return errors; + } + + fn validate_hint(h: PlacementHint) -> u32 { + var errors : u32 = 0; + if h.module_name == "" { + errors = errors + 1; + } + if h.region_name == "" { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test region_creation + given r = region("core", 4, 10, 20, 30, 40) + then r.name == "core" + and r.kind == 4 + and region_width(r) == 20 + and region_height(r) == 20 + + test logic_cluster_creation + given r = logic_cluster("logic0", 0, 0, 10, 10) + then r.kind == 4 + + test bram_column_creation + given r = bram_column("bram0", 5, 0, 50) + then r.kind == 2 + and region_width(r) == 1 + + test dsp_column_creation + given r = dsp_column("dsp0", 8, 0, 50) + then r.kind == 3 + + test region_area + given r = logic_cluster("big", 0, 0, 20, 30) + then region_area(r) == 600 + + test regions_overlap_yes + given a = logic_cluster("a", 0, 0, 10, 10) + and b = logic_cluster("b", 5, 5, 15, 15) + then regions_overlap(a, b) == true + + test regions_overlap_no + given a = logic_cluster("a", 0, 0, 10, 10) + and b = logic_cluster("b", 20, 20, 30, 30) + then regions_overlap(a, b) == false + + test hint_creation + given h = hint("uart_tx", "io_region", 1) + then h.module_name == "uart_tx" + and h.region_name == "io_region" + and h.priority == 1 + + test route_constraint_creation + given rc = route_constraint("uart_tx", "uart_rx", 500) + then rc.source == "uart_tx" + and rc.max_delay_ps == 500 + + test floorplan_creation + given f = floorplan("arty_soc", "xc7a100t") + then f.name == "arty_soc" + and f.device == "xc7a100t" + + test validate_region_ok + given r = logic_cluster("ok", 0, 0, 10, 10) + then validate_region(r) == 0 + + test validate_region_bad_coords + given r = region("bad", 0, 30, 0, 20, 10) + then validate_region(r) > 0 + + test validate_hint_ok + given h = hint("mod", "reg", 1) + then validate_hint(h) == 0 + + test validate_hint_empty + given h = PlacementHint{.module_name = "", .region_name = "", .priority = 0} + then validate_hint(h) > 0 + + // === Invariants === + + invariant area_non_negative + given r = logic_cluster("inv", 0, 0, 10, 10) + assert region_area(r) >= 0 + + invariant validate_non_negative + given r = logic_cluster("inv", 0, 0, 10, 10) + assert validate_region(r) >= 0 + + bench region_area_latency + measure: nanoseconds to region_area(logic_cluster("bench", 0, 0, 50, 50)) + target: < 50ns + + bench floorplan_construction + measure: nanoseconds to floorplan("bench", "xc7a100t") + target: < 100ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/power.t27 b/apps/website/public/t27/specs/fpga/power.t27 new file mode 100644 index 0000000000..09eefb1d09 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/power.t27 @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/power.t27 +// T27 Power Estimation Specification +// Estimates dynamic and static power consumption for FPGA designs +// Artix-7 power model: LUT=10uW/MHz, FF=5uW/MHz, BRAM=50uW/MHz, DSP=100uW/MHz +// Static: 50mW base + 0.1uW per resource unit +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Power { + + // === Power domain === + + pub struct PowerDomain { + name : &str, + voltage_mv : u32, + clock_mhz : u32, + toggle_rate : u32, + } + + fn power_domain(name: &str, clock_mhz: u32) -> PowerDomain { + return PowerDomain{ + .name = name, + .voltage_mv = 1000, + .clock_mhz = clock_mhz, + .toggle_rate = 12, + }; + } + + // === Power constants === + + fn lut_power_uw_per_mhz() -> u32 { + return 10; + } + + fn ff_power_uw_per_mhz() -> u32 { + return 5; + } + + fn bram_power_uw_per_mhz() -> u32 { + return 50; + } + + fn dsp_power_uw_per_mhz() -> u32 { + return 100; + } + + fn io_power_uw_per_mhz() -> u32 { + return 20; + } + + fn static_base_mw() -> u32 { + return 50; + } + + fn static_per_resource_uw() -> u32 { + return 100; + } + + // === Power estimate === + + pub struct PowerEstimate { + dynamic_mw : u32, + static_mw : u32, + total_mw : u32, + lut_power_uw : u32, + ff_power_uw : u32, + bram_power_uw : u32, + dsp_power_uw : u32, + } + + fn power_estimate(dynamic: u32, static_p: u32) -> PowerEstimate { + return PowerEstimate{ + .dynamic_mw = dynamic, + .static_mw = static_p, + .total_mw = dynamic + static_p, + .lut_power_uw = 0, + .ff_power_uw = 0, + .bram_power_uw = 0, + .dsp_power_uw = 0, + }; + } + + fn zero_power() -> PowerEstimate { + return power_estimate(0, 0); + } + + // === Estimation functions === + + fn est_lut_dynamic(luts: u32, clock_mhz: u32, toggle_rate: u32) -> u32 { + return luts * lut_power_uw_per_mhz() * clock_mhz * toggle_rate / 1000 / 100; + } + + fn est_ff_dynamic(ffs: u32, clock_mhz: u32, toggle_rate: u32) -> u32 { + return ffs * ff_power_uw_per_mhz() * clock_mhz * toggle_rate / 1000 / 100; + } + + fn est_bram_dynamic(brams: u32, clock_mhz: u32) -> u32 { + return brams * bram_power_uw_per_mhz() * clock_mhz / 1000; + } + + fn est_dsp_dynamic(dsps: u32, clock_mhz: u32) -> u32 { + return dsps * dsp_power_uw_per_mhz() * clock_mhz / 1000; + } + + fn est_static(total_resources: u32) -> u32 { + return static_base_mw() + total_resources * static_per_resource_uw() / 1000; + } + + fn total_resources(luts: u32, ffs: u32, brams: u32, dsps: u32) -> u32 { + return luts + ffs + brams + dsps; + } + + fn est_total_power(luts: u32, ffs: u32, brams: u32, dsps: u32, clock_mhz: u32, toggle_rate: u32) -> PowerEstimate { + var lut_p = est_lut_dynamic(luts, clock_mhz, toggle_rate); + var ff_p = est_ff_dynamic(ffs, clock_mhz, toggle_rate); + var bram_p = est_bram_dynamic(brams, clock_mhz); + var dsp_p = est_dsp_dynamic(dsps, clock_mhz); + var dyn = lut_p + ff_p + bram_p + dsp_p; + var stat = est_static(total_resources(luts, ffs, brams, dsps)); + return PowerEstimate{ + .dynamic_mw = dyn, + .static_mw = stat, + .total_mw = dyn + stat, + .lut_power_uw = lut_p, + .ff_power_uw = ff_p, + .bram_power_uw = bram_p, + .dsp_power_uw = dsp_p, + }; + } + + // === Validation === + + fn validate_domain(d: PowerDomain) -> u32 { + var errors : u32 = 0; + if d.name == "" { + errors = errors + 1; + } + if d.clock_mhz == 0 { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test power_domain_creation + given d = power_domain("core", 100) + then d.name == "core" + and d.voltage_mv == 1000 + and d.clock_mhz == 100 + and d.toggle_rate == 12 + + test zero_power + given p = zero_power() + then p.dynamic_mw == 0 + and p.static_mw == 0 + and p.total_mw == 0 + + test power_estimate_creation + given p = power_estimate(200, 100) + then p.dynamic_mw == 200 + and p.static_mw == 100 + and p.total_mw == 300 + + test est_lut_dynamic + then est_lut_dynamic(1000, 100, 12) > 0 + + test est_ff_dynamic + then est_ff_dynamic(2000, 100, 12) > 0 + + test est_bram_dynamic + then est_bram_dynamic(10, 100) == 50 + + test est_dsp_dynamic + then est_dsp_dynamic(8, 100) == 80 + + test est_static + then est_static(5000) > static_base_mw() + + test est_static_base + then est_static(0) == static_base_mw() + + test total_resources_calc + then total_resources(1000, 2000, 10, 8) == 3018 + + test est_total_power_arty + given p = est_total_power(15000, 30000, 30, 50, 100, 12) + then p.dynamic_mw > 0 + and p.static_mw > 0 + and p.total_mw > p.dynamic_mw + and p.total_mw > p.static_mw + + test power_constants + then lut_power_uw_per_mhz() == 10 + and ff_power_uw_per_mhz() == 5 + and bram_power_uw_per_mhz() == 50 + and dsp_power_uw_per_mhz() == 100 + and static_base_mw() == 50 + + test validate_domain_ok + given d = power_domain("core", 100) + then validate_domain(d) == 0 + + test validate_domain_empty + given d = PowerDomain{.name = "", .voltage_mv = 1000, .clock_mhz = 100, .toggle_rate = 12} + then validate_domain(d) > 0 + + test validate_domain_zero_clock + given d = PowerDomain{.name = "core", .voltage_mv = 1000, .clock_mhz = 0, .toggle_rate = 12} + then validate_domain(d) > 0 + + // === Invariants === + + invariant power_non_negative + given p = zero_power() + assert p.total_mw >= 0 + + invariant static_base_positive + assert static_base_mw() > 0 + + // === Benchmarks === + + bench power_est_latency + measure: nanoseconds for est_total_power(15000, 30000, 30, 50, 100, 12) + target: < 100ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/power_analysis.t27 b/apps/website/public/t27/specs/fpga/power_analysis.t27 new file mode 100644 index 0000000000..6a03d7fe99 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/power_analysis.t27 @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/power_analysis.t27 +// T27 Power Analysis Specification +// Connects power.t27 estimation model to utilization reports from synthesis +// Parses LUT/FF/BRAM/DSP counts from Vivado/Yosys reports +// Feeds utilization into Power.est_total_power() for estimation +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module PowerAnalysis { + + // === Utilization data from synthesis report === + + pub struct Utilization { + luts : u32, + ffs : u32, + brams : u32, + dsps : u32, + ios : u32, + clock_mhz : u32, + } + + fn utilization(luts: u32, ffs: u32, brams: u32, dsps: u32) -> Utilization { + return Utilization{ + .luts = luts, + .ffs = ffs, + .brams = brams, + .dsps = dsps, + .ios = 0, + .clock_mhz = 50, + }; + } + + fn utilization_full(luts: u32, ffs: u32, brams: u32, dsps: u32, ios: u32, clk: u32) -> Utilization { + return Utilization{ + .luts = luts, + .ffs = ffs, + .brams = brams, + .dsps = dsps, + .ios = ios, + .clock_mhz = clk, + }; + } + + fn zero_utilization() -> Utilization { + return utilization(0, 0, 0, 0); + } + + fn total_resources(u: Utilization) -> u32 { + return u.luts + u.ffs + u.brams + u.dsps; + } + + // === Device limits === + + pub struct DeviceLimits { + name : &str, + max_luts : u32, + max_ffs : u32, + max_brams : u32, + max_dsps : u32, + max_ios : u32, + } + + fn xc7a100t_limits() -> DeviceLimits { + return DeviceLimits{ + .name = "xc7a100t", + .max_luts = 63400, + .max_ffs = 126800, + .max_brams = 135, + .max_dsps = 240, + .max_ios = 210, + }; + } + + fn xc7a35t_limits() -> DeviceLimits { + return DeviceLimits{ + .name = "xc7a35t", + .max_luts = 20800, + .max_ffs = 41600, + .max_brams = 50, + .max_dsps = 90, + .max_ios = 100, + }; + } + + // === Utilization percentage === + + fn lut_percent(u: Utilization, lim: DeviceLimits) -> u32 { + if lim.max_luts == 0 { return 0; } + return u.luts * 100 / lim.max_luts; + } + + fn ff_percent(u: Utilization, lim: DeviceLimits) -> u32 { + if lim.max_ffs == 0 { return 0; } + return u.ffs * 100 / lim.max_ffs; + } + + fn bram_percent(u: Utilization, lim: DeviceLimits) -> u32 { + if lim.max_brams == 0 { return 0; } + return u.brams * 100 / lim.max_brams; + } + + fn dsp_percent(u: Utilization, lim: DeviceLimits) -> u32 { + if lim.max_dsps == 0 { return 0; } + return u.dsps * 100 / lim.max_dsps; + } + + fn overall_percent(u: Utilization, lim: DeviceLimits) -> u32 { + var total = total_resources(u); + var max_total = lim.max_luts + lim.max_ffs + lim.max_brams + lim.max_dsps; + if max_total == 0 { return 0; } + return total * 100 / max_total; + } + + // === Power estimation from utilization === + + fn est_dynamic_power_mw(u: Utilization, toggle_rate: u32) -> u32 { + var lut_p = u.luts * 10 * u.clock_mhz * toggle_rate / 1000 / 100; + var ff_p = u.ffs * 5 * u.clock_mhz * toggle_rate / 1000 / 100; + var bram_p = u.brams * 50 * u.clock_mhz / 1000; + var dsp_p = u.dsps * 100 * u.clock_mhz / 1000; + return lut_p + ff_p + bram_p + dsp_p; + } + + fn est_static_power_mw(u: Utilization) -> u32 { + var total = total_resources(u); + return 50 + total * 100 / 1000; + } + + fn est_total_power_mw(u: Utilization, toggle_rate: u32) -> u32 { + return est_dynamic_power_mw(u, toggle_rate) + est_static_power_mw(u); + } + + // === Power budget check === + + pub struct PowerBudget { + target_mw : u32, + warning_threshold_pct : u32, + critical_threshold_pct : u32, + } + + fn power_budget(target_mw: u32) -> PowerBudget { + return PowerBudget{ + .target_mw = target_mw, + .warning_threshold_pct = 80, + .critical_threshold_pct = 95, + }; + } + + fn power_pct_of_budget(power_mw: u32, budget: PowerBudget) -> u32 { + if budget.target_mw == 0 { return 0; } + return power_mw * 100 / budget.target_mw; + } + + fn is_within_budget(power_mw: u32, budget: PowerBudget) -> bool { + return power_mw <= budget.target_mw; + } + + fn is_warning(power_mw: u32, budget: PowerBudget) -> bool { + var pct = power_pct_of_budget(power_mw, budget); + return pct >= budget.warning_threshold_pct and pct < budget.critical_threshold_pct; + } + + fn is_critical(power_mw: u32, budget: PowerBudget) -> bool { + var pct = power_pct_of_budget(power_mw, budget); + return pct >= budget.critical_threshold_pct; + } + + // === Toggle rate estimation from activity === + + fn default_toggle_rate() -> u32 { + return 12; + } + + fn est_toggle_rate_from_activity(switching_pct: u32) -> u32 { + if switching_pct > 100 { return 100; } + return switching_pct; + } + + // === Clock domain power contribution === + + pub struct ClockDomainPower { + domain_name : &str, + clock_mhz : u32, + luts : u32, + ffs : u32, + power_mw : u32, + } + + fn clock_domain_power(name: &str, clk_mhz: u32, luts: u32, ffs: u32) -> ClockDomainPower { + var lut_p = luts * 10 * clk_mhz * 12 / 1000 / 100; + var ff_p = ffs * 5 * clk_mhz * 12 / 1000 / 100; + return ClockDomainPower{ + .domain_name = name, + .clock_mhz = clk_mhz, + .luts = luts, + .ffs = ffs, + .power_mw = lut_p + ff_p, + }; + } + + fn total_domain_power(domains: [ClockDomainPower], count: u32) -> u32 { + var total : u32 = 0; + var i : u32 = 0; + while i < count { + total = total + domains[i].power_mw; + i = i + 1; + } + return total; + } + + // === Validation === + + fn validate_utilization(u: Utilization) -> u32 { + var errors : u32 = 0; + if u.clock_mhz == 0 { errors = errors + 1; } + return errors; + } + + fn validate_budget(b: PowerBudget) -> u32 { + var errors : u32 = 0; + if b.target_mw == 0 { errors = errors + 1; } + if b.warning_threshold_pct > b.critical_threshold_pct { errors = errors + 1; } + return errors; + } + + // === Tests === + + test utilization_creation { + given u = utilization(1000, 2000, 10, 8) + then u.luts == 1000 + and u.ffs == 2000 + and u.brams == 10 + and u.dsps == 8 + } + + test utilization_full_creation { + given u = utilization_full(500, 1000, 5, 4, 20, 100) + then u.luts == 500 + and u.ios == 20 + and u.clock_mhz == 100 + } + + test zero_utilization_all_zero { + given u = zero_utilization() + then u.luts == 0 + and u.ffs == 0 + and total_resources(u) == 0 + } + + test total_resources_sum { + given u = utilization(1000, 2000, 10, 8) + then total_resources(u) == 3018 + } + + test xc7a100t_limits { + given lim = xc7a100t_limits() + then lim.max_luts == 63400 + and lim.max_ffs == 126800 + and lim.max_brams == 135 + and lim.max_dsps == 240 + } + + test xc7a35t_limits { + given lim = xc7a35t_limits() + then lim.max_luts == 20800 + and lim.name == "xc7a35t" + } + + test lut_percent_calc { + given u = utilization(6340, 0, 0, 0) + and lim = xc7a100t_limits() + then lut_percent(u, lim) == 10 + } + + test overall_percent_calc { + given u = utilization(6340, 12680, 13, 24) + and lim = xc7a100t_limits() + then overall_percent(u, lim) == 10 + } + + test percent_zero_limits { + given u = utilization(100, 0, 0, 0) + and lim = DeviceLimits{.name = "test", .max_luts = 0, .max_ffs = 0, .max_brams = 0, .max_dsps = 0, .max_ios = 0} + then lut_percent(u, lim) == 0 + } + + test est_dynamic_power_basic { + given u = utilization(1000, 2000, 10, 8) + then est_dynamic_power_mw(u, 12) > 0 + } + + test est_static_power_basic { + given u = utilization(1000, 2000, 10, 8) + then est_static_power_mw(u) > 50 + } + + test est_total_power_gt_dynamic { + given u = utilization(1000, 2000, 10, 8) + then est_total_power_mw(u, 12) > est_dynamic_power_mw(u, 12) + } + + test est_total_power_gt_static { + given u = utilization(1000, 2000, 10, 8) + then est_total_power_mw(u, 12) > est_static_power_mw(u) + } + + test power_budget_creation { + given b = power_budget(2000) + then b.target_mw == 2000 + and b.warning_threshold_pct == 80 + and b.critical_threshold_pct == 95 + } + + test power_pct_of_budget { + given b = power_budget(1000) + then power_pct_of_budget(800, b) == 80 + } + + test is_within_budget_true { + given b = power_budget(2000) + then is_within_budget(1500, b) == true + } + + test is_within_budget_false { + given b = power_budget(1000) + then is_within_budget(1500, b) == false + } + + test is_warning_level { + given b = power_budget(1000) + then is_warning(850, b) == true + } + + test is_critical_level { + given b = power_budget(1000) + then is_critical(960, b) == true + } + + test is_not_warning_below_threshold { + given b = power_budget(1000) + then is_warning(500, b) == false + } + + test default_toggle_rate_value { + then default_toggle_rate() == 12 + } + + test est_toggle_rate_from_activity { + then est_toggle_rate_from_activity(50) == 50 + } + + test est_toggle_rate_clamped { + then est_toggle_rate_from_activity(150) == 100 + } + + test clock_domain_power_creation { + given cdp = clock_domain_power("core", 100, 5000, 10000) + then cdp.domain_name == "core" + and cdp.clock_mhz == 100 + and cdp.power_mw > 0 + } + + test total_domain_power_sum { + given d1 = clock_domain_power("core", 100, 5000, 10000) + and d2 = clock_domain_power("io", 50, 1000, 2000) + and domains = [d1, d2] + then total_domain_power(domains, 2) > 0 + } + + test total_domain_power_empty { + then total_domain_power([], 0) == 0 + } + + test validate_utilization_ok { + given u = utilization(100, 200, 1, 1) + then validate_utilization(u) == 0 + } + + test validate_utilization_zero_clock { + given u = Utilization{.luts = 100, .ffs = 200, .brams = 1, .dsps = 1, .ios = 0, .clock_mhz = 0} + then validate_utilization(u) > 0 + } + + test validate_budget_ok { + given b = power_budget(2000) + then validate_budget(b) == 0 + } + + test validate_budget_zero_target { + given b = PowerBudget{.target_mw = 0, .warning_threshold_pct = 80, .critical_threshold_pct = 95} + then validate_budget(b) > 0 + } + + test validate_budget_inverted_thresholds { + given b = PowerBudget{.target_mw = 2000, .warning_threshold_pct = 95, .critical_threshold_pct = 80} + then validate_budget(b) > 0 + } + + test trinity_fpga_top_power_estimate { + given u = utilization_full(15000, 30000, 30, 50, 48, 50) + var total = est_total_power_mw(u, 12); + invariant total > 0; + } + + test trinity_fpga_top_within_typical_budget { + given u = utilization_full(15000, 30000, 30, 50, 48, 50) + and b = power_budget(2000) + var total = est_total_power_mw(u, 12); + then is_within_budget(total, b) == true + } + + // === Invariants === + + invariant total_resources_non_negative { + given u = utilization(0, 0, 0, 0) + assert total_resources(u) >= 0 + } + + invariant power_estimates_non_negative { + given u = utilization(1000, 2000, 10, 8) + assert est_total_power_mw(u, 12) >= 0 + } + + invariant percent_within_bounds { + given u = utilization(6340, 0, 0, 0) + and lim = xc7a100t_limits() + assert lut_percent(u, lim) <= 100 + } + + invariant budget_pct_non_negative { + given b = power_budget(1000) + assert power_pct_of_budget(0, b) >= 0 + } + + // === Benchmarks === + + bench power_analysis_full_latency { + given u = utilization_full(15000, 30000, 30, 50, 48, 50) + measure: nanoseconds for est_total_power_mw(u, 12) + target: < 200ns + } + + bench utilization_percent_calc { + given u = utilization_full(6340, 12680, 13, 24, 20, 50) + and lim = xc7a100t_limits() + measure: nanoseconds for overall_percent(u, lim) + target: < 100ns + } +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/router.t27 b/apps/website/public/t27/specs/fpga/router.t27 new file mode 100644 index 0000000000..2423b268c1 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/router.t27 @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/router.t27 +// T27 HIR Signal Router Specification +// Connectivity graph analysis, fanout estimation, routing congestion prediction +// Estimates wire length, routing resources needed for Artix-7 +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Router { + + // === Edge kind === + + pub const EdgeKind = enum(i8) { + data = 0, + clock = 1, + reset = 2, + enable = 3, + } + + // === Connectivity edge === + + pub struct ConnEdge { + source : &str, + sink : &str, + kind : i8, + bit_width : u32, + } + + fn data_edge(source: &str, sink: &str, width: u32) -> ConnEdge { + return ConnEdge{ + .source = source, + .sink = sink, + .kind = 0, + .bit_width = width, + }; + } + + fn clock_edge(source: &str, sink: &str) -> ConnEdge { + return ConnEdge{ + .source = source, + .sink = sink, + .kind = 1, + .bit_width = 1, + }; + } + + // === Fanout analysis === + + pub struct FanoutInfo { + signal : &str, + fanout : u32, + total_bits : u32, + } + + fn fanout(signal: &str, count: u32, bits: u32) -> FanoutInfo { + return FanoutInfo{ + .signal = signal, + .fanout = count, + .total_bits = bits, + }; + } + + fn is_high_fanout(info: FanoutInfo) -> bool { + return info.fanout > 16; + } + + fn is_clock_network(info: FanoutInfo) -> bool { + return info.signal == "clk" or info.signal == "rst_n"; + } + + // === Routing estimate === + + pub struct RouteEstimate { + total_nets : u32, + total_wire_length_um : u32, + avg_wire_length_um : u32, + max_fanout : u32, + congestion_score : u32, + needs_global_buf : bool, + } + + fn route_ok(nets: u32, wire: u32, fanout: u32) -> RouteEstimate { + var avg : u32 = 0; + if nets > 0 { + avg = wire / nets; + } + return RouteEstimate{ + .total_nets = nets, + .total_wire_length_um = wire, + .avg_wire_length_um = avg, + .max_fanout = fanout, + .congestion_score = 0, + .needs_global_buf = fanout > 32, + }; + } + + fn passed(r: RouteEstimate) -> bool { + return r.congestion_score < 80; + } + + // === Routing model === + + fn local_wire_um() -> u32 { + return 500; + } + + fn medium_wire_um() -> u32 { + return 2000; + } + + fn long_wire_um() -> u32 { + return 5000; + } + + fn est_wire_length(fanout_count: u32) -> u32 { + if fanout_count == 0 { + return 0; + } + if fanout_count <= 4 { + return local_wire_um(); + } + if fanout_count <= 16 { + return medium_wire_um(); + } + return long_wire_um(); + } + + fn est_total_wire(edges: [ConnEdge], count: u32, fanouts: [FanoutInfo], fcount: u32) -> u32 { + var total : u32 = 0; + var i : u32 = 0; + while i < fcount { + total = total + est_wire_length(fanouts[i].fanout) * fanouts[i].total_bits; + i = i + 1; + } + return total; + } + + fn est_congestion(nets: u32, die_area_mm2: u32) -> u32 { + if die_area_mm2 == 0 { + return 0; + } + return nets / die_area_mm2; + } + + // === Validation === + + fn validate_edge(e: ConnEdge) -> u32 { + var errors : u32 = 0; + if e.source == "" { + errors = errors + 1; + } + if e.sink == "" { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test data_edge_creation + given e = data_edge("a", "b", 32) + then e.kind == 0 + and e.bit_width == 32 + + test clock_edge_creation + given e = clock_edge("pll", "core") + then e.kind == 1 + and e.bit_width == 1 + + test fanout_creation + given f = fanout("data_bus", 8, 32) + then f.signal == "data_bus" + and f.fanout == 8 + and f.total_bits == 32 + + test is_high_fanout_yes + given f = fanout("big", 20, 1) + then is_high_fanout(f) == true + + test is_high_fanout_no + given f = fanout("small", 4, 1) + then is_high_fanout(f) == false + + test is_clock_network_clk + given f = fanout("clk", 50, 1) + then is_clock_network(f) == true + + test is_clock_network_data + given f = fanout("data", 50, 1) + then is_clock_network(f) == false + + test route_estimate_creation + given r = route_ok(100, 50000, 20) + then r.total_nets == 100 + and r.avg_wire_length_um == 500 + and r.needs_global_buf == false + + test route_estimate_high_fanout + given r = route_ok(10, 5000, 40) + then r.needs_global_buf == true + + test est_wire_length_local + then est_wire_length(2) == 500 + + test est_wire_length_medium + then est_wire_length(8) == 2000 + + test est_wire_length_long + then est_wire_length(32) == 5000 + + test est_wire_length_zero + then est_wire_length(0) == 0 + + test est_congestion + then est_congestion(1000, 10) == 100 + + test est_congestion_zero_area + then est_congestion(1000, 0) == 0 + + test passed_low_congestion + given r = RouteEstimate{.total_nets = 100, .total_wire_length_um = 50000, .avg_wire_length_um = 500, .max_fanout = 10, .congestion_score = 20, .needs_global_buf = false} + then passed(r) == true + + test passed_high_congestion + given r = RouteEstimate{.total_nets = 1000, .total_wire_length_um = 500000, .avg_wire_length_um = 500, .max_fanout = 50, .congestion_score = 90, .needs_global_buf = true} + then passed(r) == false + + test validate_edge_ok + given e = data_edge("a", "b", 8) + then validate_edge(e) == 0 + + test validate_edge_empty + given e = ConnEdge{.source = "", .sink = "", .kind = 0, .bit_width = 0} + then validate_edge(e) > 0 + + // === Invariants === + + invariant wire_length_non_negative + assert local_wire_um() > 0 + and medium_wire_um() > local_wire_um() + and long_wire_um() > medium_wire_um() + + invariant congestion_non_negative + given c = est_congestion(100, 10) + assert c >= 0 + + bench wire_estimation_latency + measure: nanoseconds to est_wire_length(16) + target: < 50ns + + bench congestion_estimation_latency + measure: nanoseconds to est_congestion(500, 50) + target: < 100ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/simulator.t27 b/apps/website/public/t27/specs/fpga/simulator.t27 new file mode 100644 index 0000000000..e6bea9e34e --- /dev/null +++ b/apps/website/public/t27/specs/fpga/simulator.t27 @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/simulator.t27 +// HIR Cycle-Accurate Simulation Engine Specification +// Provides simulation primitives for verifying HIR modules pre-synthesis +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Simulator { + + // === Simulator state === + + pub const SimState = enum(i8) { + idle = 0, + running = 1, + paused = 2, + done = 3, + error = 4, + } + + // === Simulator configuration === + + pub struct SimConfig { + name : &str, + max_cycles : u32, + clock_freq_hz : u32, + trace_enabled : bool, + vcd_output : bool, + break_on_error : bool, + vcd_path : &str, + } + + // === Simulation result === + + pub struct SimResult { + cycles : u32, + state : i8, + errors : u32, + assertions_fired : u32, + coverage_points : u32, + } + + // === Signal probe point === + + pub struct ProbePoint { + name : &str, + signal : &str, + width : u32, + is_signed : bool, + } + + // === Trace entry === + + pub struct TraceEntry { + cycle : u32, + signal : &str, + value : u32, + } + + // === Constructor helpers === + + fn sim_config(name: &str, max_cycles: u32) -> SimConfig { + return SimConfig{ + .name = name, + .max_cycles = max_cycles, + .clock_freq_hz = 100000000, + .trace_enabled = false, + .vcd_output = false, + .break_on_error = true, + .vcd_path = "", + }; + } + + fn sim_config_with_trace(name: &str, max_cycles: u32, vcd_path: &str) -> SimConfig { + return SimConfig{ + .name = name, + .max_cycles = max_cycles, + .clock_freq_hz = 100000000, + .trace_enabled = true, + .vcd_output = true, + .break_on_error = true, + .vcd_path = vcd_path, + }; + } + + fn sim_ok(cycles: u32, coverage: u32) -> SimResult { + return SimResult{ + .cycles = cycles, + .state = 3, + .errors = 0, + .assertions_fired = 0, + .coverage_points = coverage, + }; + } + + fn sim_error(cycles: u32, errors: u32) -> SimResult { + return SimResult{ + .cycles = cycles, + .state = 4, + .errors = errors, + .assertions_fired = 0, + .coverage_points = 0, + }; + } + + fn probe(name: &str, signal: &str, width: u32) -> ProbePoint { + return ProbePoint{ + .name = name, + .signal = signal, + .width = width, + .is_signed = false, + }; + } + + fn trace_entry(cycle: u32, signal: &str, value: u32) -> TraceEntry { + return TraceEntry{ + .cycle = cycle, + .signal = signal, + .value = value, + }; + } + + // === Query functions === + + fn is_idle(r: SimResult) -> bool { + return r.state == 0; + } + + fn is_done(r: SimResult) -> bool { + return r.state == 3; + } + + fn is_error(r: SimResult) -> bool { + return r.state == 4; + } + + fn sim_time_ns(cfg: SimConfig, cycles: u32) -> u32 { + if (cfg.clock_freq_hz == 0) { + return 0; + } + return cycles * 1000000000 / cfg.clock_freq_hz; + } + + fn sim_time_us(cfg: SimConfig, cycles: u32) -> u32 { + return sim_time_ns(cfg, cycles) / 1000; + } + + fn sim_time_ms(cfg: SimConfig, cycles: u32) -> u32 { + return sim_time_ns(cfg, cycles) / 1000000; + } + + fn cycles_for_time_ns(cfg: SimConfig, ns: u32) -> u32 { + if (cfg.clock_freq_hz == 0) { + return 0; + } + return ns * cfg.clock_freq_hz / 1000000000; + } + + fn has_errors(r: SimResult) -> bool { + return r.errors > 0; + } + + fn passed(r: SimResult) -> bool { + return r.state == 3 and r.errors == 0; + } + + // === Validation === + + fn validate_sim_config(cfg: SimConfig) -> u32 { + var errors : u32 = 0; + if (cfg.name == "") { + errors = errors + 1; + } + if (cfg.max_cycles == 0) { + errors = errors + 1; + } + if (cfg.clock_freq_hz == 0) { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test sim_config_creation + given cfg = sim_config("uart_sim", 10000) + then cfg.max_cycles == 10000 + and cfg.trace_enabled == false + + test sim_config_with_trace + given cfg = sim_config_with_trace("uart_sim", 10000, "uart.vcd") + then cfg.trace_enabled == true + and cfg.vcd_output == true + and cfg.vcd_path == "uart.vcd" + + test sim_ok_result + given r = sim_ok(5000, 10) + then is_done(r) == true + and is_error(r) == false + and passed(r) == true + and has_errors(r) == false + and r.cycles == 5000 + and r.coverage_points == 10 + + test sim_error_result + given r = sim_error(3000, 2) + then is_done(r) == false + and is_error(r) == true + and passed(r) == false + and has_errors(r) == true + and r.errors == 2 + + test probe_creation + given p = probe("clk_probe", "clk", 1) + then p.name == "clk_probe" + and p.signal == "clk" + and p.width == 1 + + test trace_entry_creation + given t = trace_entry(42, "counter", 27) + then t.cycle == 42 + and t.signal == "counter" + and t.value == 27 + + test sim_time_ns + given cfg = sim_config("sim", 10000) + then sim_time_ns(cfg, 100) == 1000 + + test sim_time_us + given cfg = sim_config("sim", 10000) + then sim_time_us(cfg, 100000) == 1000 + + test sim_time_ms + given cfg = sim_config("sim", 10000) + then sim_time_ms(cfg, 100000000) == 1000 + + test cycles_for_time_ns + given cfg = sim_config("sim", 10000) + then cycles_for_time_ns(cfg, 1000) == 100 + + test validate_config_ok + given cfg = sim_config("sim", 10000) + then validate_sim_config(cfg) == 0 + + test validate_config_empty_name + given cfg = sim_config("", 10000) + then validate_sim_config(cfg) > 0 + + test validate_config_zero_cycles + given cfg = sim_config("sim", 0) + then validate_sim_config(cfg) > 0 + + // === Invariants === + + invariant max_cycles_positive + given cfg = sim_config("inv", 100) + assert cfg.max_cycles > 0 + + invariant sim_time_positive + given cfg = sim_config("inv", 100) + assert sim_time_ns(cfg, 1) > 0 + + invariant cycles_for_time_positive + given cfg = sim_config("inv", 100) + assert cycles_for_time_ns(cfg, 10) > 0 + + invariant validate_non_negative + given cfg = sim_config("inv", 100) + assert validate_sim_config(cfg) >= 0 + + // === Benchmarks === + + bench sim_time_calc_latency + measure: nanoseconds for sim_time_ns(sim_config("b", 1000), 1000) + target: < 100ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/spi.t27 b/apps/website/public/t27/specs/fpga/spi.t27 new file mode 100644 index 0000000000..a74cd24822 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/spi.t27 @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/spi.t27 +// SPI Master Specification for FPGA +// Mode 0: CPOL=0, CPHA=0 (SCK idle low, sample on rising edge) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module SPI_Master; + // Import base types + use base::types; + + // =============================================================== + // 1. SPI Configuration + // ========================================================================= + + // System clock + const CLK_FREQ : u32 = 50_000_000; // 50 MHz + + // SPI Mode 0: CPOL=0, CPHA=0 + // CPOL (Clock Polarity): 0 = SCK idle low + // CPHA (Clock Phase): 0 = Sample on first (rising) edge + const SPI_CPOL : u8 = 0; + const SPI_CPHA : u8 = 0; + + // SPI configuration + const MAX_DATA_WIDTH : u8 = 32; // Max bits per transfer + const CS_ASSERT_DELAY : u32 = 100; // CS to SCK delay (ns) + const CS_DEASSERT_DELAY : u32 = 100; // SCK to CS delay (ns) + + // SPI prescaler values (divides system clock) + const PRESCALER_2 : u8 = 0; + const PRESCALER_4 : u8 = 1; + const PRESCALER_8 : u8 = 2; + const PRESCALER_16 : u8 = 3; + const PRESCALER_32 : u8 = 4; + const PRESCALER_64 : u8 = 5; + const PRESCALER_128 : u8 = 6; + const PRESCALER_256 : u8 = 7; + + // =============================================================== + // 2. SPI State Machine + // ========================================================================= + + // SPI states + const SPI_IDLE : u8 = 0; + const SPI_CS_ASSERT : u8 = 1; + const SPI_TRANSFER : u8 = 2; + const SPI_CS_DEASSERT : u8 = 3; + + // Transfer states + const TX_BIT : u8 = 0; + const RX_BIT : u8 = 1; + const WAIT_EDGE : u8 = 2; + + // =============================================================== + // 3. SPI Master Unit + // ========================================================================= + + // SPI master state + struct SPI_Master_Unit { + state : u8, // Master state + tx_state : u8, // Transfer state + cs_asserted : bool, // Chip select state + busy : bool, // Transfer in progress + + // Transfer configuration + prescaler : u8, // Clock prescaler + data_width : u8, // Bits per transfer + cs_mode : u8, // CS mode (auto/manual) + + // Data registers + tx_data : u32, // Transmit data + rx_data : u32, // Receive data + bit_count : u8, // Bits transferred + bit_counter : u32, // Half-cycle counter + + // CS delay counters + cs_assert_cnt : u32, // CS assert delay + cs_deassert_cnt : u32, // CS deassert delay + } + + // Default SPI unit + var spi : SPI_Master_Unit = SPI_Master_Unit{ + .state = SPI_IDLE, + .tx_state = TX_BIT, + .cs_asserted = false, + .busy = false, + + .prescaler = PRESCALER_16, // Default: 16x prescaler + .data_width = 8, // Default: 8-bit transfers + .cs_mode = 0, // Auto CS + + .tx_data = 0, + .rx_data = 0, + .bit_count = 0, + .bit_counter = 0, + + .cs_assert_cnt = 0, + .cs_deassert_cnt = 0, + }; + + // spi_set_prescaler(psc: u8) -> bool + // Set SPI clock prescaler + fn spi_set_prescaler(psc: u8) -> bool { + if (psc > PRESCALER_256) { + return false; + } + spi.prescaler = psc; + return true; + } + + // spi_get_prescaler_div() -> u32 + // Get actual prescaler divider value + fn spi_get_prescaler_div() -> u32 { + match spi.prescaler { + PRESCALER_2 => 2u32, + PRESCALER_4 => 4u32, + PRESCALER_8 => 8u32, + PRESCALER_16 => 16u32, + PRESCALER_32 => 32u32, + PRESCALER_64 => 64u32, + PRESCALER_128 => 128u32, + PRESCALER_256 => 256u32, + _ => 16u32, + } + } + + // spi_get_sck_freq() -> u32 + // Get SPI SCK frequency + fn spi_get_sck_freq() -> u32 { + return CLK_FREQ / spi_get_prescaler_div(); + } + + // spi_set_data_width(width: u8) -> bool + // Set data width (1-32 bits) + fn spi_set_data_width(width: u8) -> bool { + if (width == 0 || width > MAX_DATA_WIDTH) { + return false; + } + spi.data_width = width; + return true; + } + + // spi_is_busy() -> bool + // Check if SPI is busy + fn spi_is_busy() -> bool { + return spi.busy; + } + + // spi_transfer(data: u32) -> bool + // Start SPI transfer + fn spi_transfer(data: u32) -> bool { + if (spi.busy) { + return false; + } + spi.tx_data = data; + spi.rx_data = 0; + spi.bit_count = 0; + spi.bit_counter = 0; + spi.state = SPI_CS_ASSERT; + spi.busy = true; + return true; + } + + // spi_read_rx() -> u32 + // Read received data (lower bits only) + fn spi_read_rx() -> u32 { + return spi.rx_data & ((1u32 << spi.data_width) - 1); + } + + // spi_get_cs() -> bool + // Get CS line state + fn spi_get_cs() -> bool { + return spi.cs_asserted; + } + + // spi_get_sck() -> bool + // Get SCK line state (Mode 0: idle low) + fn spi_get_sck() -> bool { + // In Mode 0: SCK is low in idle + // Alternates during transfer + match spi.tx_state { + TX_BIT => false, // SCK low (setup) + RX_BIT => true, // SCK high (sample) + _ => SPI_CPOL == 0, + } + } + + // spi_get_mosi() -> bool + // Get MOSI line state + fn spi_get_mosi() -> bool { + if (!spi.busy || spi.state != SPI_TRANSFER) { + return false; // Idle: MOSI low + } + return (spi.tx_data >> (spi.data_width - spi.bit_count - 1)) & 1 == 1; + } + + // spi_tick() -> void + // Process one system clock cycle + fn spi_tick() -> void { + match spi.state { + SPI_IDLE => { + // Do nothing, waiting for transfer + } + SPI_CS_ASSERT => { + spi.cs_assert_cnt = spi.cs_assert_cnt + 1; + if (spi.cs_assert_cnt >= (CS_ASSERT_DELAY * CLK_FREQ / 1_000_000_000)) { + spi.cs_assert_cnt = 0; + spi.cs_asserted = true; + spi.state = SPI_TRANSFER; + spi.tx_state = TX_BIT; + } + } + SPI_TRANSFER => { + spi_transfer_bit(); + } + SPI_CS_DEASSERT => { + spi.cs_deassert_cnt = spi.cs_deassert_cnt + 1; + if (spi.cs_deassert_cnt >= (CS_DEASSERT_DELAY * CLK_FREQ / 1_000_000_000)) { + spi.cs_deassert_cnt = 0; + spi.cs_asserted = false; + spi.state = SPI_IDLE; + spi.busy = false; + } + } + } + } + + // spi_transfer_bit() -> void + // Transfer single bit + fn spi_transfer_bit() -> void { + const prescaler_div = spi_get_prescaler_div(); + spi.bit_counter = spi.bit_counter + 1; + + match spi.tx_state { + TX_BIT => { + if (spi.bit_counter >= prescaler_div / 2) { + spi.bit_counter = 0; + spi.tx_state = RX_BIT; + } + } + RX_BIT => { + if (spi.bit_counter >= prescaler_div / 2) { + // Sample MISO -- in spec-level simulation this is a placeholder; + // Verilog emission reads the actual MISO input pin + const miso_bit = false; + spi.rx_data = (spi.rx_data << 1) | (if miso_bit { 1u32 } else { 0u32 }); + spi.bit_count = spi.bit_count + 1; + spi.bit_counter = 0; + + if (spi.bit_count >= spi.data_width) { + spi.tx_state = WAIT_EDGE; + } else { + spi.tx_state = TX_BIT; + } + } + } + WAIT_EDGE => { + if (spi.bit_counter >= prescaler_div / 2) { + spi.bit_counter = 0; + spi.state = SPI_CS_DEASSERT; + } + } + } + } + + // =========================================================================================== + // TDD-Inside-Spec: Tests and Invariants for SPI_Master + // =========================================================================================== + + test spi_mode_0_configuration + given cpol = SPI_CPOL + and cpha = SPI_CPHA + then cpol == 0 and cpha == 0 + + test spi_prescaler_16_default + given psc = spi.prescaler + then psc == PRESCALER_16 + + test spi_set_prescaler_valid + given result = spi_set_prescaler(PRESCALER_64) + then result == true + + test spi_set_prescaler_invalid + given result = spi_set_prescaler(99) + then result == false + + test spi_prescaler_div_16 + given psc = PRESCALER_16 + and div = spi_get_prescaler_div() + then div == 16 + + test spi_sck_freq_at_50MHz + given freq = spi_get_sck_freq() + and div = spi_get_prescaler_div() + then freq == CLK_FREQ / div + + test spi_set_data_width_8 + given result = spi_set_data_width(8) + then result == true + + test spi_set_data_width_32 + given result = spi_set_data_width(32) + then result == true + + test spi_set_data_width_invalid + given result = spi_set_data_width(0) + then result == false + + test spi_initially_not_busy + given busy = spi_is_busy() + then busy == false + + test spi_transfer_when_ready + given result = spi_transfer(0xAA) + then result == true + + test spi_transfer_when_busy + given spi_transfer(0x55) + and result = spi_transfer(0xAA) + then result == false + + test spi_cs_idle_high + given cs = spi_get_cs() + then cs == false + + test spi_sck_idle_low + given sck = spi_get_sck() + then sck == false // Mode 0: idle low + + test spi_max_data_width_32 + given max = MAX_DATA_WIDTH + then max == 32 + + test spi_prescaler_range + given min_psc = PRESCALER_2 + and max_psc = PRESCALER_256 + then min_psc == 0 and max_psc == 7 + + test spi_cs_delays_defined + given assert_delay = CS_ASSERT_DELAY + and deassert_delay = CS_DEASSERT_DELAY + then assert_delay == 100 and deassert_delay == 100 + + invariant spi_mode_0_constant + assert SPI_CPOL == 0 and SPI_CPHA == 0 + + invariant spi_states_valid + given state = spi.state + assert state == SPI_IDLE or state == SPI_CS_ASSERT or state == SPI_TRANSFER or state == SPI_CS_DEASSERT + + invariant spi_tx_states_valid + given tx_state = spi.tx_state + assert tx_state == TX_BIT or tx_state == RX_BIT or tx_state == WAIT_EDGE + + invariant spi_prescaler_divides_clock + given freq = spi_get_sck_freq() + assert CLK_FREQ % freq == 0 + + invariant spi_data_width_bounds + assert spi.data_width > 0 and spi.data_width <= MAX_DATA_WIDTH + + invariant spi_busy_implies_cs_asserted + assert spi.busy implies spi.cs_asserted or spi.state == SPI_CS_ASSERT + + invariant spi_busy_only_in_transfer + assert spi.busy implies spi.state == SPI_CS_ASSERT or spi.state == SPI_TRANSFER or spi.state == SPI_CS_DEASSERT + + invariant spi_sck_alternates + given old_sck = spi_get_sck() + when spi.state == SPI_TRANSFER and spi.tx_state == TX_BIT + and spi.tx_state = RX_BIT + and new_sck = spi_get_sck() + then old_sck != new_sck + + invariant spi_cs_deasserted_after_transfer + given spi.data_width = 8 + and spi.transfer(0xAA) + and // Complete transfer simulation + then spi.state == SPI_CS_DEASSERT or spi.state == SPI_IDLE + + invariant spi_rx_data_masked + given spi.data_width = 8 + and spi.tx_data = 0xAA55AA55 + and rx = spi_read_rx() + then rx == rx & 0xFF + + invariant spi_bit_count_reset_after_transfer + given spi.data_width = 8 + and spi.bit_count = 8 + when spi.state == SPI_CS_DEASSERT and spi.state == SPI_IDLE + and spi.busy == false + then spi.bit_count == 0 + + invariant spi_cs_delay_counters_reset + given spi.state == SPI_IDLE + then spi.cs_assert_cnt == 0 and spi.cs_deassert_cnt == 0 + + bench spi_transfer_latency + measure: nanoseconds to complete 8-bit transfer + target: < 2000ns // 8 bits * 2 * prescaler / 50MHz + + bench spi_sck_max_frequency + given spi_set_prescaler(PRESCALER_2) + and freq = spi_get_sck_freq() + then freq == 25_000_000 // 50MHz / 2 + + bench spi_cs_assertion_time + measure: nanoseconds for CS to assert + target: < 150ns // CS_ASSERT_DELAY + margin + + bench spi_prescaler_change_latency + measure: nanoseconds to spi_set_prescaler(PRESCALER_32) + target: < 100ns + diff --git a/apps/website/public/t27/specs/fpga/stdlib.t27 b/apps/website/public/t27/specs/fpga/stdlib.t27 new file mode 100644 index 0000000000..d2bcd15f83 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/stdlib.t27 @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/stdlib.t27 +// T27 FPGA Standard Library IP Catalog +// Reusable hardware IP cores with resource utilization estimates +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Stdlib { + + // === IP core kind === + + pub const IpKind = enum(i8) { + uart_tx = 0, + uart_rx = 1, + spi_master = 2, + spi_slave = 3, + i2c_master = 4, + gpio = 5, + pwm = 6, + timer = 7, + bram_ctrl = 8, + axi_interconnect = 9, + clock_crossing = 10, + gf16_alu = 11, + ternary_alu = 12, + } + + // === Resource utilization === + + pub struct ResourceEstimate { + luts : u32, + ffs : u32, + bram18 : u32, + dsp48 : u32, + io_pins : u32, + } + + // === IP core entry === + + pub struct IpCore { + name : &str, + kind : i8, + version : u32, + resources : ResourceEstimate, + clock_freq_mhz : u32, + vendor : &str, + verified : bool, + } + + // === IP catalog entry === + + pub struct IpCatalog { + name : &str, + cores : [32]IpCore, + core_count : u32, + } + + // === Board resource budget === + + pub struct BoardResources { + name : &str, + luts : u32, + ffs : u32, + bram18 : u32, + dsp48 : u32, + io_pins : u32, + } + + // === Constructor helpers === + + fn zero_resources() -> ResourceEstimate { + return ResourceEstimate{ + .luts = 0, + .ffs = 0, + .bram18 = 0, + .dsp48 = 0, + .io_pins = 0, + }; + } + + fn resources(luts: u32, ffs: u32, bram18: u32, dsp48: u32, io: u32) -> ResourceEstimate { + return ResourceEstimate{ + .luts = luts, + .ffs = ffs, + .bram18 = bram18, + .dsp48 = dsp48, + .io_pins = io, + }; + } + + fn ip_core(name: &str, kind: i8, luts: u32, ffs: u32, bram18: u32, dsp48: u32, io: u32, freq_mhz: u32) -> IpCore { + return IpCore{ + .name = name, + .kind = kind, + .version = 1, + .resources = resources(luts, ffs, bram18, dsp48, io), + .clock_freq_mhz = freq_mhz, + .vendor = "t27", + .verified = false, + }; + } + + fn empty_catalog(name: &str) -> IpCatalog { + var cat = IpCatalog{ + .name = name, + .cores = [32]IpCore{ + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, + }, + .core_count = 0, + }; + return cat; + } + + fn arty_a7_resources() -> BoardResources { + return BoardResources{ + .name = "arty_a7", + .luts = 33800, + .ffs = 67600, + .bram18 = 60, + .dsp48 = 90, + .io_pins = 210, + }; + } + + fn xc7a100t_resources() -> BoardResources { + return BoardResources{ + .name = "xc7a100t", + .luts = 63400, + .ffs = 126800, + .bram18 = 135, + .dsp48 = 240, + .io_pins = 300, + }; + } + + // === Query functions === + + fn total_luts(cat: IpCatalog) -> u32 { + var total : u32 = 0; + var i : u32 = 0; + while (i < cat.core_count) { + total = total + cat.cores[i].resources.luts; + i = i + 1; + } + return total; + } + + fn total_ffs(cat: IpCatalog) -> u32 { + var total : u32 = 0; + var i : u32 = 0; + while (i < cat.core_count) { + total = total + cat.cores[i].resources.ffs; + i = i + 1; + } + return total; + } + + fn total_bram18(cat: IpCatalog) -> u32 { + var total : u32 = 0; + var i : u32 = 0; + while (i < cat.core_count) { + total = total + cat.cores[i].resources.bram18; + i = i + 1; + } + return total; + } + + fn total_dsp48(cat: IpCatalog) -> u32 { + var total : u32 = 0; + var i : u32 = 0; + while (i < cat.core_count) { + total = total + cat.cores[i].resources.dsp48; + i = i + 1; + } + return total; + } + + fn fits_board(cat: IpCatalog, board: BoardResources) -> bool { + if (total_luts(cat) > board.luts) { + return false; + } + if (total_ffs(cat) > board.ffs) { + return false; + } + if (total_bram18(cat) > board.bram18) { + return false; + } + if (total_dsp48(cat) > board.dsp48) { + return false; + } + return true; + } + + fn luts_remaining(cat: IpCatalog, board: BoardResources) -> u32 { + if (total_luts(cat) > board.luts) { + return 0; + } + return board.luts - total_luts(cat); + } + + fn utilization_percent(cat: IpCatalog, board: BoardResources) -> u32 { + if (board.luts == 0) { + return 0; + } + return total_luts(cat) * 100 / board.luts; + } + + // === Validation === + + fn validate_ip(ip: IpCore) -> u32 { + var errors : u32 = 0; + if (ip.name == "") { + errors = errors + 1; + } + if (ip.clock_freq_mhz == 0) { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test zero_resources + given r = zero_resources() + then r.luts == 0 + and r.ffs == 0 + and r.bram18 == 0 + and r.dsp48 == 0 + + test resources_creation + given r = resources(100, 50, 2, 1, 8) + then r.luts == 100 + and r.ffs == 50 + and r.bram18 == 2 + and r.dsp48 == 1 + and r.io_pins == 8 + + test ip_core_creation + given ip = ip_core("uart_tx", 0, 200, 100, 1, 0, 4, 100) + then ip.name == "uart_tx" + and ip.kind == 0 + and ip.resources.luts == 200 + and ip.clock_freq_mhz == 100 + + test empty_catalog_creation + given cat = empty_catalog("t27_stdlib") + then cat.core_count == 0 + and total_luts(cat) == 0 + + test add_core_luts + given cat = empty_catalog("test") + and cat2 = IpCatalog{.name = "test", .cores = [32]IpCore{ip_core("uart_tx", 0, 200, 100, 1, 0, 4, 100), IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}, IpCore{.name = "", .kind = 0, .version = 0, .resources = zero_resources(), .clock_freq_mhz = 0, .vendor = "", .verified = false}}, .core_count = 1} + then total_luts(cat2) == 200 + + test arty_a7_resources + given board = arty_a7_resources() + then board.luts == 33800 + and board.bram18 == 60 + and board.dsp48 == 90 + + test xc7a100t_resources + given board = xc7a100t_resources() + then board.luts == 63400 + + test fits_board_empty_catalog + given cat = empty_catalog("test") + and board = arty_a7_resources() + then fits_board(cat, board) == true + + test utilization_zero + given cat = empty_catalog("test") + and board = arty_a7_resources() + then utilization_percent(cat, board) == 0 + + test validate_ip_ok + given ip = ip_core("uart_tx", 0, 200, 100, 1, 0, 4, 100) + then validate_ip(ip) == 0 + + test validate_ip_empty_name + given ip = ip_core("", 0, 200, 100, 1, 0, 4, 0) + then validate_ip(ip) > 0 + + test validate_ip_zero_clock + given ip = ip_core("test", 0, 100, 50, 0, 0, 4, 0) + then validate_ip(ip) > 0 + + test total_ffs_empty + given cat = empty_catalog("test") + then total_ffs(cat) == 0 + + test total_dsp48_empty + given cat = empty_catalog("test") + then total_dsp48(cat) == 0 + + test luts_remaining_full_board + given cat = empty_catalog("test") + and board = xc7a100t_resources() + then luts_remaining(cat, board) == 63400 + + test ip_kind_values + then 0 == 0 + and 1 == 1 + + test ip_core_vendor + given ip = ip_core("uart_tx", 0, 200, 100, 1, 0, 4, 100) + then ip.vendor == "t27" + and ip.version == 1 + and ip.verified == false + + // === Invariants === + + invariant arty_luts_positive + given board = arty_a7_resources() + assert board.luts > 0 + + invariant xc7a100t_luts_positive + given board = xc7a100t_resources() + assert board.luts > 0 + + invariant zero_resources_are_zero + given r = zero_resources() + assert r.luts == 0 + and r.ffs == 0 + and r.bram18 == 0 + and r.dsp48 == 0 + + invariant empty_catalog_zero_totals + given cat = empty_catalog("inv") + assert total_luts(cat) == 0 + and total_bram18(cat) == 0 + and total_dsp48(cat) == 0 + + // === Benchmarks === + + bench fits_board_latency + measure: nanoseconds for fits_board(empty_catalog("b"), arty_a7_resources()) + target: < 200ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/ternary_isa.t27 b/apps/website/public/t27/specs/fpga/ternary_isa.t27 new file mode 100644 index 0000000000..ae79cabb87 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/ternary_isa.t27 @@ -0,0 +1,542 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/ternary_isa.t27 +// Ternary ISA Hardware Implementation Specification for Trinity T27 FPGA HIR +// Bridges software ISA (27 registers, balanced ternary) to silicon +// Connects GF16 arithmetic, ternary gates, and phi-identity to hardware +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module TernaryIsa { + + // === ISA constants === + + pub const NUM_REGISTERS : u32 = 27; + pub const TRIT_WIDTH : u32 = 27; + pub const WORD_BITS : u32 = 64; + pub const INSTR_WIDTH : u32 = 32; + pub const TRIT_NEG : i32 = -1; + pub const TRIT_ZERO : i32 = 0; + pub const TRIT_POS : i32 = 1; + + // === Opcode categories === + + pub const OpClass = enum(i8) { + alu_trit = 0, + alu_gf16 = 1, + memory = 2, + branch = 3, + io = 4, + system = 5, + ternary_gate = 6, + } + + // === ALU operation === + + pub struct AluOp { + name : &str, + opcode : u32, + op_class : i8, + latency : u32, + pipeline_stages : u32, + uses_gf16 : bool, + } + + // === Instruction format === + + pub struct InstrFormat { + name : &str, + opcode_bits : u32, + rd_bits : u32, + rs1_bits : u32, + rs2_bits : u32, + imm_bits : u32, + funct_bits : u32, + total_bits : u32, + } + + // === Pipeline stage === + + pub struct PipelineStage { + name : &str, + latency : u32, + has_forwarding : bool, + } + + // === Ternary register file config === + + pub struct TernaryRegFile { + name : &str, + num_regs : u32, + trit_width : u32, + read_ports : u32, + write_ports : u32, + has_forwarding : bool, + } + + // === Ternary core configuration === + + pub struct TernaryCoreConfig { + name : &str, + data_width : u32, + addr_width : u32, + num_alus : u32, + has_gf16_unit : bool, + has_ternary_alu : bool, + has_branch_predictor : bool, + pipeline_depth : u32, + clock_freq_hz : u32, + } + + // === Constructor helpers === + + fn r_type_format() -> InstrFormat { + return InstrFormat{ + .name = "R-type", + .opcode_bits = 6, + .rd_bits = 5, + .rs1_bits = 5, + .rs2_bits = 5, + .imm_bits = 0, + .funct_bits = 11, + .total_bits = 32, + }; + } + + fn i_type_format() -> InstrFormat { + return InstrFormat{ + .name = "I-type", + .opcode_bits = 6, + .rd_bits = 5, + .rs1_bits = 5, + .rs2_bits = 0, + .imm_bits = 16, + .funct_bits = 0, + .total_bits = 32, + }; + } + + fn ternary_alu_op(name: &str, opcode: u32, latency: u32) -> AluOp { + return AluOp{ + .name = name, + .opcode = opcode, + .op_class = 0, + .latency = latency, + .pipeline_stages = latency, + .uses_gf16 = false, + }; + } + + fn gf16_alu_op(name: &str, opcode: u32, latency: u32) -> AluOp { + return AluOp{ + .name = name, + .opcode = opcode, + .op_class = 1, + .latency = latency, + .pipeline_stages = latency, + .uses_gf16 = true, + }; + } + + fn fetch_stage() -> PipelineStage { + return PipelineStage{ + .name = "IF", + .latency = 1, + .has_forwarding = false, + }; + } + + fn decode_stage() -> PipelineStage { + return PipelineStage{ + .name = "ID", + .latency = 1, + .has_forwarding = false, + }; + } + + fn execute_stage() -> PipelineStage { + return PipelineStage{ + .name = "EX", + .latency = 1, + .has_forwarding = true, + }; + } + + fn memory_stage() -> PipelineStage { + return PipelineStage{ + .name = "MEM", + .latency = 1, + .has_forwarding = true, + }; + } + + fn writeback_stage() -> PipelineStage { + return PipelineStage{ + .name = "WB", + .latency = 1, + .has_forwarding = false, + }; + } + + fn ternary_regfile(name: &str) -> TernaryRegFile { + return TernaryRegFile{ + .name = name, + .num_regs = 27, + .trit_width = 27, + .read_ports = 2, + .write_ports = 1, + .has_forwarding = true, + }; + } + + fn ternary_core(name: &str) -> TernaryCoreConfig { + return TernaryCoreConfig{ + .name = name, + .data_width = 64, + .addr_width = 32, + .num_alus = 1, + .has_gf16_unit = true, + .has_ternary_alu = true, + .has_branch_predictor = false, + .pipeline_depth = 5, + .clock_freq_hz = 100000000, + }; + } + + fn ternary_core_full(name: &str) -> TernaryCoreConfig { + return TernaryCoreConfig{ + .name = name, + .data_width = 64, + .addr_width = 32, + .num_alus = 4, + .has_gf16_unit = true, + .has_ternary_alu = true, + .has_branch_predictor = true, + .pipeline_depth = 7, + .clock_freq_hz = 100000000, + }; + } + + // === Query functions === + + fn is_trit_op(op: AluOp) -> bool { + return op.op_class == 0; + } + + fn is_gf16_op(op: AluOp) -> bool { + return op.op_class == 1; + } + + fn is_r_type(fmt: InstrFormat) -> bool { + return fmt.imm_bits == 0; + } + + fn is_i_type(fmt: InstrFormat) -> bool { + return fmt.imm_bits > 0 and fmt.rs2_bits == 0; + } + + fn regfile_bits(rf: TernaryRegFile) -> u32 { + return rf.num_regs * rf.trit_width * 2; + } + + fn regfile_bram_count(rf: TernaryRegFile) -> u32 { + var bits : u32 = regfile_bits(rf); + return bits / 18432 + 1; + } + + fn core_dsp_count(cfg: TernaryCoreConfig) -> u32 { + var count : u32 = cfg.num_alus; + if (cfg.has_gf16_unit) { + count = count + 4; + } + return count; + } + + fn core_bram_count(cfg: TernaryCoreConfig) -> u32 { + var count : u32 = 2; + if (cfg.has_gf16_unit) { + count = count + 1; + } + return count; + } + + fn core_lut_estimate(cfg: TernaryCoreConfig) -> u32 { + var luts : u32 = 5000; + luts = luts + cfg.num_alus * 2000; + if (cfg.has_gf16_unit) { + luts = luts + 3000; + } + if (cfg.has_ternary_alu) { + luts = luts + 1500; + } + if (cfg.has_branch_predictor) { + luts = luts + 500; + } + return luts; + } + + fn core_fmax_mhz(cfg: TernaryCoreConfig) -> u32 { + return cfg.clock_freq_hz / 1000000; + } + + fn fits_arty_a7(cfg: TernaryCoreConfig) -> bool { + var luts : u32 = core_lut_estimate(cfg); + return luts < 33800; + } + + fn fits_xc7a100t(cfg: TernaryCoreConfig) -> bool { + var luts : u32 = core_lut_estimate(cfg); + return luts < 63400; + } + + fn pipeline_total_latency(stages: [8]PipelineStage, count: u32) -> u32 { + var total : u32 = 0; + var i : u32 = 0; + while (i < count) { + total = total + stages[i].latency; + i = i + 1; + } + return total; + } + + fn phi_squared_check() -> u32 { + return 3; + } + + // === Validation === + + fn validate_alu_op(op: AluOp) -> u32 { + var errors : u32 = 0; + if (op.name == "") { + errors = errors + 1; + } + if (op.latency == 0) { + errors = errors + 1; + } + return errors; + } + + fn validate_instr_format(fmt: InstrFormat) -> u32 { + var errors : u32 = 0; + var computed : u32 = fmt.opcode_bits + fmt.rd_bits + fmt.rs1_bits + fmt.rs2_bits + fmt.imm_bits + fmt.funct_bits; + if (computed != fmt.total_bits) { + errors = errors + 1; + } + if (fmt.total_bits == 0) { + errors = errors + 1; + } + return errors; + } + + fn validate_regfile(rf: TernaryRegFile) -> u32 { + var errors : u32 = 0; + if (rf.name == "") { + errors = errors + 1; + } + if (rf.num_regs == 0) { + errors = errors + 1; + } + if (rf.read_ports == 0) { + errors = errors + 1; + } + if (rf.write_ports == 0) { + errors = errors + 1; + } + return errors; + } + + fn validate_core(cfg: TernaryCoreConfig) -> u32 { + var errors : u32 = 0; + if (cfg.name == "") { + errors = errors + 1; + } + if (cfg.data_width == 0) { + errors = errors + 1; + } + if (cfg.num_alus == 0) { + errors = errors + 1; + } + if (cfg.pipeline_depth == 0) { + errors = errors + 1; + } + if (cfg.clock_freq_hz == 0) { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test r_type_format + given fmt = r_type_format() + then is_r_type(fmt) == true + and is_i_type(fmt) == false + and fmt.total_bits == 32 + + test i_type_format + given fmt = i_type_format() + then is_i_type(fmt) == true + and is_r_type(fmt) == false + + test validate_r_type_format + given fmt = r_type_format() + then validate_instr_format(fmt) == 0 + + test validate_i_type_format + given fmt = i_type_format() + then validate_instr_format(fmt) == 0 + + test ternary_alu_op_creation + given op = ternary_alu_op("t_add", 1, 1) + then is_trit_op(op) == true + and is_gf16_op(op) == false + and op.latency == 1 + + test gf16_alu_op_creation + given op = gf16_alu_op("gf_mul", 16, 3) + then is_gf16_op(op) == true + and is_trit_op(op) == false + and op.uses_gf16 == true + + test validate_alu_op_ok + given op = ternary_alu_op("t_add", 1, 1) + then validate_alu_op(op) == 0 + + test validate_alu_op_empty_name + given op = ternary_alu_op("", 1, 1) + then validate_alu_op(op) > 0 + + test ternary_regfile_creation + given rf = ternary_regfile("regfile0") + then rf.num_regs == 27 + and rf.read_ports == 2 + and rf.write_ports == 1 + and rf.has_forwarding == true + + test regfile_bits + given rf = ternary_regfile("rf0") + then regfile_bits(rf) > 0 + + test regfile_bram_count + given rf = ternary_regfile("rf0") + then regfile_bram_count(rf) > 0 + + test validate_regfile_ok + given rf = ternary_regfile("rf0") + then validate_regfile(rf) == 0 + + test validate_regfile_empty_name + given rf = TernaryRegFile{.name = "", .num_regs = 27, .trit_width = 27, .read_ports = 2, .write_ports = 1, .has_forwarding = true} + then validate_regfile(rf) > 0 + + test ternary_core_creation + given cfg = ternary_core("tri0") + then cfg.data_width == 64 + and cfg.num_alus == 1 + and cfg.has_gf16_unit == true + and cfg.has_ternary_alu == true + and cfg.pipeline_depth == 5 + + test ternary_core_full_creation + given cfg = ternary_core_full("tri1") + then cfg.num_alus == 4 + and cfg.has_branch_predictor == true + and cfg.pipeline_depth == 7 + + test core_dsp_count_basic + given cfg = ternary_core("tri0") + then core_dsp_count(cfg) == 5 + + test core_dsp_count_full + given cfg = ternary_core_full("tri1") + then core_dsp_count(cfg) == 8 + + test core_bram_count + given cfg = ternary_core("tri0") + then core_bram_count(cfg) > 0 + + test core_lut_estimate + given cfg = ternary_core("tri0") + then core_lut_estimate(cfg) > 0 + + test core_fmax + given cfg = ternary_core("tri0") + then core_fmax_mhz(cfg) == 100 + + test fits_arty_a7_basic + given cfg = ternary_core("tri0") + then fits_arty_a7(cfg) == true + + test fits_xc7a100t_basic + given cfg = ternary_core("tri0") + then fits_xc7a100t(cfg) == true + + test fits_arty_a7_full + given cfg = ternary_core_full("tri1") + then fits_arty_a7(cfg) == true + + test fits_xc7a100t_full + given cfg = ternary_core_full("tri1") + then fits_xc7a100t(cfg) == true + + test pipeline_stages + then fetch_stage().name == "IF" + and decode_stage().name == "ID" + and execute_stage().name == "EX" + and memory_stage().name == "MEM" + and writeback_stage().name == "WB" + + test phi_squared_identity + then phi_squared_check() == 3 + + test validate_core_ok + given cfg = ternary_core("tri0") + then validate_core(cfg) == 0 + + test validate_core_empty_name + given cfg = TernaryCoreConfig{.name = "", .data_width = 64, .addr_width = 32, .num_alus = 1, .has_gf16_unit = true, .has_ternary_alu = true, .has_branch_predictor = false, .pipeline_depth = 5, .clock_freq_hz = 100000000} + then validate_core(cfg) > 0 + + test validate_core_zero_alus + given cfg = TernaryCoreConfig{.name = "tri0", .data_width = 64, .addr_width = 32, .num_alus = 0, .has_gf16_unit = true, .has_ternary_alu = true, .has_branch_predictor = false, .pipeline_depth = 5, .clock_freq_hz = 100000000} + then validate_core(cfg) > 0 + + // === Invariants === + + invariant num_registers_is_27 + assert NUM_REGISTERS == 27 + + invariant phi_identity + assert phi_squared_check() == 3 + + invariant core_dsp_positive + given cfg = ternary_core("inv") + assert core_dsp_count(cfg) > 0 + + invariant core_bram_positive + given cfg = ternary_core("inv") + assert core_bram_count(cfg) > 0 + + invariant core_lut_positive + given cfg = ternary_core("inv") + assert core_lut_estimate(cfg) > 0 + + invariant regfile_bits_positive + given rf = ternary_regfile("inv") + assert regfile_bits(rf) > 0 + + invariant validate_non_negative + given cfg = ternary_core("inv") + assert validate_core(cfg) >= 0 + + // === Benchmarks === + + bench core_lut_estimate_latency + measure: nanoseconds for core_lut_estimate(ternary_core("b")) + target: < 100ns + + bench fits_check_latency + measure: nanoseconds for fits_xc7a100t(ternary_core_full("b")) + target: < 100ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/testbench.t27 b/apps/website/public/t27/specs/fpga/testbench.t27 new file mode 100644 index 0000000000..df322861e9 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench.t27 @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench.t27 +// T27 HIR Testbench Auto-Generation Specification +// Automatically generates Verilog testbenches from HIR modules +// Includes clock generation, reset sequencing, stimulus, and checking +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Testbench { + + // === Testbench clock config === + + pub struct TbClockCfg { + period_ns : u32, + duty_cycle : u32, + phase_ns : u32, + } + + fn clock_cfg(period: u32) -> TbClockCfg { + return TbClockCfg{ + .period_ns = period, + .duty_cycle = 50, + .phase_ns = 0, + }; + } + + fn half_period(cfg: TbClockCfg) -> u32 { + return cfg.period_ns / 2; + } + + // === Reset config === + + pub struct TbResetCfg { + active_low : bool, + delay_cycles : u32, + duration_cycles : u32, + } + + fn reset_cfg(delay: u32, duration: u32) -> TbResetCfg { + return TbResetCfg{ + .active_low = true, + .delay_cycles = delay, + .duration_cycles = duration, + }; + } + + fn reset_end_cycle(cfg: TbResetCfg) -> u32 { + return cfg.delay_cycles + cfg.duration_cycles; + } + + // === Stimulus entry === + + pub struct TbStimulus { + cycle : u32, + signal : &str, + value : u32, + } + + fn stimulus(cycle: u32, signal: &str, value: u32) -> TbStimulus { + return TbStimulus{ + .cycle = cycle, + .signal = signal, + .value = value, + }; + } + + // === Expected check === + + pub struct TbCheck { + cycle : u32, + signal : &str, + expected : u32, + mask : u32, + } + + fn check(cycle: u32, signal: &str, expected: u32) -> TbCheck { + return TbCheck{ + .cycle = cycle, + .signal = signal, + .expected = expected, + .mask = 4294967295, + }; + } + + fn check_with_mask(cycle: u32, signal: &str, expected: u32, mask: u32) -> TbCheck { + return TbCheck{ + .cycle = cycle, + .signal = signal, + .expected = expected, + .mask = mask, + }; + } + + // === Testbench config === + + pub struct TbConfig { + name : &str, + dut_name : &str, + timescale : &str, + max_cycles : u32, + timeout_ns : u32, + fail_fast : bool, + } + + fn tb_config(dut: &str, max_cycles: u32) -> TbConfig { + return TbConfig{ + .name = "tb", + .dut_name = dut, + .timescale = "1ns/1ps", + .max_cycles = max_cycles, + .timeout_ns = max_cycles * 10, + .fail_fast = true, + }; + } + + // === Validation === + + fn validate_tb_config(cfg: TbConfig) -> u32 { + var errors : u32 = 0; + if cfg.dut_name == "" { + errors = errors + 1; + } + if cfg.max_cycles == 0 { + errors = errors + 1; + } + return errors; + } + + fn validate_stimulus(s: TbStimulus) -> u32 { + var errors : u32 = 0; + if s.signal == "" { + errors = errors + 1; + } + return errors; + } + + fn validate_check(c: TbCheck) -> u32 { + var errors : u32 = 0; + if c.signal == "" { + errors = errors + 1; + } + return errors; + } + + // === Query functions === + + fn stim_applied_before(stim: TbStimulus, cycle: u32) -> bool { + return stim.cycle <= cycle; + } + + fn check_at_cycle(ck: TbCheck, cycle: u32) -> bool { + return ck.cycle == cycle; + } + + fn total_sim_ns(clock_cfg: TbClockCfg, cycles: u32) -> u32 { + return clock_cfg.period_ns * cycles; + } + + fn stim_count_before(stimuli: [TbStimulus], count: u32, cycle: u32) -> u32 { + var found : u32 = 0; + var i : u32 = 0; + while i < count { + if stimuli[i].cycle <= cycle { + found = found + 1; + } + i = i + 1; + } + return found; + } + + // === Tests === + + test clock_cfg_creation + given cfg = clock_cfg(10) + then cfg.period_ns == 10 + and cfg.duty_cycle == 50 + and half_period(cfg) == 5 + + test reset_cfg_creation + given cfg = reset_cfg(5, 10) + then cfg.active_low == true + and cfg.delay_cycles == 5 + and cfg.duration_cycles == 10 + and reset_end_cycle(cfg) == 15 + + test stimulus_creation + given s = stimulus(10, "uart_tx", 1) + then s.cycle == 10 + and s.signal == "uart_tx" + and s.value == 1 + + test check_creation + given c = check(20, "led", 5) + then c.cycle == 20 + and c.signal == "led" + and c.expected == 5 + and c.mask == 4294967295 + + test check_with_mask_creation + given c = check_with_mask(20, "data", 255, 255) + then c.mask == 255 + + test tb_config_creation + given cfg = tb_config("uart_top", 10000) + then cfg.dut_name == "uart_top" + and cfg.max_cycles == 10000 + and cfg.timeout_ns == 100000 + + test validate_tb_config_ok + given cfg = tb_config("dut", 1000) + then validate_tb_config(cfg) == 0 + + test validate_tb_config_empty_dut + given cfg = TbConfig{.name = "tb", .dut_name = "", .timescale = "1ns/1ps", .max_cycles = 1000, .timeout_ns = 10000, .fail_fast = true} + then validate_tb_config(cfg) > 0 + + test validate_stimulus_ok + given s = stimulus(0, "clk", 1) + then validate_stimulus(s) == 0 + + test validate_stimulus_empty_signal + given s = TbStimulus{.cycle = 0, .signal = "", .value = 0} + then validate_stimulus(s) > 0 + + test validate_check_ok + given c = check(10, "out", 42) + then validate_check(c) == 0 + + test stim_applied_before_yes + given s = stimulus(5, "sig", 1) + then stim_applied_before(s, 10) == true + + test stim_applied_before_no + given s = stimulus(15, "sig", 1) + then stim_applied_before(s, 10) == false + + test check_at_cycle_match + given c = check(10, "sig", 1) + then check_at_cycle(c, 10) == true + + test check_at_cycle_no_match + given c = check(10, "sig", 1) + then check_at_cycle(c, 20) == false + + test total_sim_ns + given cfg = clock_cfg(10) + then total_sim_ns(cfg, 100) == 1000 + + // === Invariants === + + invariant half_period_not_zero + given cfg = clock_cfg(10) + assert half_period(cfg) > 0 + + invariant reset_end_after_delay + given cfg = reset_cfg(5, 10) + assert reset_end_cycle(cfg) > cfg.delay_cycles + + invariant timeout_sufficient + given cfg = tb_config("dut", 1000) + assert cfg.timeout_ns >= cfg.max_cycles + + // === Benchmarks === + + bench tb_gen_latency + measure: nanoseconds for tb_config("dUT", 10000) + target: < 50ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/testbench/apb_bridge_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/apb_bridge_tb.t27 new file mode 100644 index 0000000000..0c69ba3793 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/apb_bridge_tb.t27 @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/apb_bridge_tb.t27 +// APB Bridge Testbench +// Tests APB bus protocol: setup, access, wait states +// phi^2 + 1/phi^2 = 3 | TRINITY + +module APB_Bridge_Testbench { + use fpga::apb_bridge::ApbBridge; + + const CLK_PERIOD : u32 = 20; + const SIM_TIMEOUT : u32 = 5_000_000; + const APB_ADDR_WIDTH : u32 = 12; + const APB_DATA_WIDTH : u32 = 32; + + var clk : bool = false; + var rst_n : bool = false; + var psel : bool = false; + var penable : bool = false; + var pwrite : bool = false; + var paddr : u32 = 0; + var pwdata : u32 = 0; + var prdata : u32 = 0; + var pready : bool = false; + var pslverr : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn apb_write(addr : u32, data : u32) -> bool { + paddr = addr; + pwdata = data; + pwrite = true; + psel = true; + penable = false; + tick(); + penable = true; + tick(); + while !pready { tick(); } + psel = false; + penable = false; + pwrite = false; + return !pslverr; + } + + fn apb_read(addr : u32) -> u32 { + paddr = addr; + pwrite = false; + psel = true; + penable = false; + tick(); + penable = true; + tick(); + while !pready { tick(); } + var data : u32 = prdata; + psel = false; + penable = false; + return data; + } + + test test_reset_state { + reset(); + invariant pready == true || pready == false; + invariant pslverr == false; + } + + test test_apb_write { + reset(); + var ok : bool = apb_write(0x100, 0xDEAD); + invariant ok == true; + invariant pslverr == false; + } + + test test_apb_read { + reset(); + var val : u32 = apb_read(0x100); + invariant pslverr == false; + } + + test test_write_read_roundtrip { + reset(); + apb_write(0x200, 0xCAFEBABE); + var val : u32 = apb_read(0x200); + invariant val == 0xCAFEBABE; + } + + test test_multiple_apb_writes { + reset(); + var i : u32 = 0; + while i < 8 { + apb_write(0x000 + i * 4, i * 0x10); + i = i + 1; + } + i = 0; + while i < 8 { + var val : u32 = apb_read(0x000 + i * 4); + invariant val == i * 0x10; + i = i + 1; + } + } + + test test_protocol_phases { + reset(); + psel = true; + penable = false; + tick(); + invariant psel == true; + penable = true; + tick(); + invariant penable == true; + psel = false; + penable = false; + } + + invariant addr_width_valid : APB_ADDR_WIDTH > 0; + invariant data_width_valid : APB_DATA_WIDTH == 32; + + bench bench_apb_throughput { + reset(); + var i : u32 = 0; + while i < 64 { + apb_write(i * 4, i); + i = i + 1; + } + i = 0; + while i < 64 { + apb_read(i * 4); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/assembler_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/assembler_tb.t27 new file mode 100644 index 0000000000..35d049486e --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/assembler_tb.t27 @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/assembler_tb.t27 +// Assembler/Linker Integration Testbench +// Tests ternary instruction encoding, program assembly, and memory linking +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Assembler_Testbench { + use fpga::assembler::Assembler; + use fpga::linker::Linker; + + const CLK_PERIOD : u32 = 20; + const INSTR_WIDTH : u32 = 32; + const DATA_WIDTH : u32 = 32; + const ADDR_WIDTH : u32 = 16; + const MAX_PROGRAM_SIZE : u32 = 1024; + const NUM_REGISTERS : u32 = 8; + + var clk : bool = false; + var rst_n : bool = false; + var instr_in : u32 = 0; + var instr_out : u32 = 0; + var addr_in : u32 = 0; + var data_out : u32 = 0; + var assemble_valid : bool = false; + var link_done : bool = false; + var link_error : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn encode_r_type(opcode : u8, rd : u8, rs1 : u8, rs2 : u8) -> u32 { + var instr : u32 = opcode as u32; + instr = instr | ((rd as u32) << 8); + instr = instr | ((rs1 as u32) << 12); + instr = instr | ((rs2 as u32) << 16); + return instr; + } + + fn encode_i_type(opcode : u8, rd : u8, rs : u8, imm : u16) -> u32 { + var instr : u32 = opcode as u32; + instr = instr | ((rd as u32) << 8); + instr = instr | ((rs as u32) << 12); + instr = instr | ((imm as u32) << 16); + return instr; + } + + fn decode_opcode(instr : u32) -> u8 { + return (instr & 0xFF) as u8; + } + + fn decode_rd(instr : u32) -> u8 { + return ((instr >> 8) & 0xF) as u8; + } + + test test_reset_state { + reset(); + invariant assemble_valid == false; + invariant link_done == false; + } + + test test_encode_r_type { + var instr : u32 = encode_r_type(0x01, 1, 2, 3); + invariant decode_opcode(instr) == 0x01; + invariant decode_rd(instr) == 1; + } + + test test_encode_i_type { + var instr : u32 = encode_i_type(0x02, 3, 1, 0x1234); + invariant decode_opcode(instr) == 0x02; + invariant decode_rd(instr) == 3; + } + + test test_max_program_size { + invariant MAX_PROGRAM_SIZE == 1024; + } + + test test_register_count { + invariant NUM_REGISTERS == 8; + } + + test test_instr_width { + invariant INSTR_WIDTH == 32; + } + + invariant program_size_positive : MAX_PROGRAM_SIZE > 0; + invariant registers_positive : NUM_REGISTERS > 0; + + bench bench_assembly { + reset(); + var i : u32 = 0; + while i < 100 { + encode_r_type(0x01, (i % 8) as u8, ((i + 1) % 8) as u8, ((i + 2) % 8) as u8); + encode_i_type(0x02, (i % 8) as u8, ((i + 1) % 8) as u8, (i * 4) as u16); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/axi4_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/axi4_tb.t27 new file mode 100644 index 0000000000..f86f503c27 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/axi4_tb.t27 @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/axi4_tb.t27 +// AXI4 Bus Testbench Specification +// Tests AXI4 read/write channels, burst support, and protocol compliance +// phi^2 + 1/phi^2 = 3 | TRINITY + +module AXI4_Testbench { + use fpga::axi4::Axi4; + + const CLK_PERIOD : u32 = 20; + const SIM_TIMEOUT : u32 = 10_000_000; + const ADDR_WIDTH : u32 = 32; + const DATA_WIDTH : u32 = 32; + const ID_WIDTH : u32 = 4; + const MAX_BURST_LEN : u32 = 256; + + var clk : bool = false; + var rst_n : bool = false; + + // AW channel (write address) + var aw_valid : bool = false; + var aw_ready : bool = false; + var aw_addr : u32 = 0; + var aw_id : u32 = 0; + var aw_len : u32 = 0; + var aw_size : u32 = 2; + var aw_burst : u32 = 1; + + // W channel (write data) + var w_valid : bool = false; + var w_ready : bool = false; + var w_data : u32 = 0; + var w_strb : u32 = 0xF; + var w_last : bool = false; + + // B channel (write response) + var b_valid : bool = false; + var b_ready : bool = false; + var b_resp : u32 = 0; + + // AR channel (read address) + var ar_valid : bool = false; + var ar_ready : bool = false; + var ar_addr : u32 = 0; + var ar_id : u32 = 0; + var ar_len : u32 = 0; + var ar_size : u32 = 2; + var ar_burst : u32 = 1; + + // R channel (read data) + var r_valid : bool = false; + var r_ready : bool = false; + var r_data : u32 = 0; + var r_resp : u32 = 0; + var r_last : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn axi_write_single(addr : u32, data : u32) -> u32 { + aw_valid = true; + aw_addr = addr; + aw_len = 0; + aw_size = 2; + aw_burst = 1; + tick(); + while !aw_ready { tick(); } + aw_valid = false; + w_valid = true; + w_data = data; + w_strb = 0xF; + w_last = true; + tick(); + while !w_ready { tick(); } + w_valid = false; + b_ready = true; + while !b_valid { tick(); } + var resp : u32 = b_resp; + b_ready = false; + return resp; + } + + fn axi_read_single(addr : u32) -> u32 { + ar_valid = true; + ar_addr = addr; + ar_len = 0; + ar_size = 2; + ar_burst = 1; + tick(); + while !ar_ready { tick(); } + ar_valid = false; + r_ready = true; + while !r_valid { tick(); } + var data : u32 = r_data; + r_ready = false; + return data; + } + + test test_reset_state { + reset(); + invariant aw_ready == false || aw_ready == true; + invariant ar_ready == false || ar_ready == true; + } + + test test_single_write { + reset(); + var resp : u32 = axi_write_single(0x1000, 0xDEADBEEF); + invariant resp == 0; + } + + test test_single_read { + reset(); + var data : u32 = axi_read_single(0x1000); + invariant data == 0xDEADBEEF; + } + + test test_write_read_roundtrip { + reset(); + axi_write_single(0x2000, 0x12345678); + var data : u32 = axi_read_single(0x2000); + invariant data == 0x12345678; + } + + test test_multiple_writes { + reset(); + var i : u32 = 0; + while i < 8 { + axi_write_single(0x1000 + i * 4, i); + i = i + 1; + } + i = 0; + while i < 8 { + var data : u32 = axi_read_single(0x1000 + i * 4); + invariant data == i; + i = i + 1; + } + } + + test test_aligned_address { + reset(); + var resp : u32 = axi_write_single(0x0000, 0xAA); + invariant resp == 0; + } + + test test_burst_len_zero_means_single { + invariant MAX_BURST_LEN == 256; + } + + invariant data_width_power_of_2 : DATA_WIDTH == 32 || DATA_WIDTH == 64; + invariant addr_width_valid : ADDR_WIDTH == 32 || ADDR_WIDTH == 64; + + bench bench_axi_throughput { + reset(); + var i : u32 = 0; + while i < 64 { + axi_write_single(0x4000 + i * 4, i * i); + i = i + 1; + } + i = 0; + while i < 64 { + axi_read_single(0x4000 + i * 4); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/bootrom_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/bootrom_tb.t27 new file mode 100644 index 0000000000..d5a40176ac --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/bootrom_tb.t27 @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/bootrom_tb.t27 +// Boot ROM Testbench +// Tests boot sequence, reset vector, and initial program loading +// phi^2 + 1/phi^2 = 3 | TRINITY + +module BootROM_Testbench { + use fpga::bootrom::BootROM; + + const CLK_PERIOD : u32 = 20; + const ROM_SIZE : u32 = 4096; + const RESET_VECTOR : u32 = 0x0000_0000; + const BOOT_MAGIC : u32 = 0x727B007; + + var clk : bool = false; + var rst_n : bool = false; + var rom_addr : u32 = 0; + var rom_data : u32 = 0; + var boot_valid : bool = false; + var boot_done : bool = false; + var pc : u32 = 0; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + pc = RESET_VECTOR; + } + + fn read_rom(addr : u32) -> u32 { + rom_addr = addr; + tick(); + return rom_data; + } + + fn boot_sequence() -> bool { + pc = RESET_VECTOR; + var magic : u32 = read_rom(pc); + if magic != BOOT_MAGIC { return false; } + pc = pc + 4; + boot_valid = true; + var i : u32 = 0; + while i < 8 { + read_rom(pc); + pc = pc + 4; + i = i + 1; + } + boot_done = true; + return true; + } + + test test_reset_state { + reset(); + invariant boot_valid == false; + invariant boot_done == false; + } + + test test_reset_vector { + invariant RESET_VECTOR == 0; + } + + test test_rom_size { + invariant ROM_SIZE == 4096; + } + + test test_boot_magic { + invariant BOOT_MAGIC == 0x727B007; + } + + invariant rom_size_positive : ROM_SIZE > 0; + + bench bench_bootrom { + reset(); + boot_sequence(); + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/bridge_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/bridge_tb.t27 new file mode 100644 index 0000000000..76d5890858 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/bridge_tb.t27 @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/bridge_tb.t27 +// FPGA Bridge Testbench +// Tests data streaming, packet framing, and cross-domain transfers +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Bridge_Testbench { + use fpga::bridge::FPGA_Bridge; + + const CLK_PERIOD : u32 = 20; + const SIM_TIMEOUT : u32 = 5_000_000; + const BRIDGE_WIDTH : u32 = 32; + const FIFO_DEPTH : u32 = 8; + + var clk : bool = false; + var rst_n : bool = false; + var tx_valid : bool = false; + var tx_data : u32 = 0; + var tx_ready : bool = false; + var rx_valid : bool = false; + var rx_data : u32 = 0; + var rx_ready : bool = false; + var bridge_busy : bool = false; + var bridge_error : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn send_packet(data : u32) -> bool { + if !tx_ready { return false; } + tx_valid = true; + tx_data = data; + tick(); + while !bridge_busy { + tick(); + } + tx_valid = false; + while bridge_busy { + tick(); + } + return !bridge_error; + } + + fn receive_packet() -> u32 { + rx_ready = true; + while !rx_valid { + tick(); + } + var data : u32 = rx_data; + rx_ready = false; + return data; + } + + test test_reset_state { + reset(); + invariant bridge_busy == false; + invariant bridge_error == false; + invariant rx_valid == false; + } + + test test_single_transfer { + reset(); + var ok : bool = send_packet(0xDEAD); + invariant ok == true; + invariant bridge_error == false; + } + + test test_multiple_transfers { + reset(); + var i : u32 = 0; + while i < 8 { + send_packet(i * 0x1111); + i = i + 1; + } + invariant bridge_error == false; + } + + test test_backpressure { + reset(); + rx_ready = false; + send_packet(0x42); + invariant rx_valid == true; + rx_ready = true; + tick(); + } + + test test_max_width_transfer { + reset(); + var ok : bool = send_packet(0xFFFFFFFF); + invariant ok == true; + } + + test test_zero_transfer { + reset(); + var ok : bool = send_packet(0x00000000); + invariant ok == true; + } + + invariant bridge_width_positive : BRIDGE_WIDTH > 0; + invariant fifo_depth_power_of_2 : FIFO_DEPTH > 0; + + bench bench_bridge_throughput { + reset(); + var i : u32 = 0; + while i < 100 { + send_packet(i); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/clock_domain_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/clock_domain_tb.t27 new file mode 100644 index 0000000000..70286dd1ec --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/clock_domain_tb.t27 @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/clock_domain_tb.t27 +// Clock Domain Crossing Testbench +// Tests CDC synchronizers, handshake, and metastability protection +// phi^2 + 1/phi^2 = 3 | TRINITY + +module ClockDomain_Testbench { + use fpga::clock_domain::ClockDomain; + + const CLK_PERIOD_FAST : u32 = 10; + const CLK_PERIOD_SLOW : u32 = 40; + const SIM_TIMEOUT : u32 = 10_000_000; + const SYNC_STAGES : u32 = 2; + + var clk_fast : bool = false; + var clk_slow : bool = false; + var rst_n : bool = false; + var tx_data : u32 = 0; + var tx_valid : bool = false; + var rx_data : u32 = 0; + var rx_valid : bool = false; + var sync_ready : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick_fast() { + clk_fast = false; + clk_fast = true; + } + + fn tick_slow() { + clk_slow = false; + clk_slow = true; + } + + fn reset() { + rst_n = false; + tick_fast(); + tick_slow(); + rst_n = true; + tick_fast(); + tick_slow(); + } + + fn send_async(data : u32) { + tx_data = data; + tx_valid = true; + tick_fast(); + while !sync_ready { tick_fast(); } + tx_valid = false; + } + + fn receive_async() -> u32 { + tick_slow(); + tick_slow(); + return rx_data; + } + + test test_reset_state { + reset(); + invariant rx_valid == false; + invariant sync_ready == true; + } + + test test_single_cdc_transfer { + reset(); + send_async(0x12345678); + var val : u32 = receive_async(); + invariant val == 0x12345678; + } + + test test_multiple_cdc_transfers { + reset(); + var i : u32 = 0; + while i < 4 { + send_async(i * 0x11111111); + i = i + 1; + } + invariant sync_ready == true; + } + + test test_zero_data { + reset(); + send_async(0x00000000); + var val : u32 = receive_async(); + invariant val == 0; + } + + test test_all_ones { + reset(); + send_async(0xFFFFFFFF); + var val : u32 = receive_async(); + invariant val == 0xFFFFFFFF; + } + + invariant sync_stages_at_least_2 : SYNC_STAGES >= 2; + + bench bench_cdc_throughput { + reset(); + var i : u32 = 0; + while i < 50 { + send_async(i); + receive_async(); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/cts_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/cts_tb.t27 new file mode 100644 index 0000000000..ce226b9eac --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/cts_tb.t27 @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/cts_tb.t27 +// Clock Tree Synthesis Testbench +// Tests clock buffer insertion, skew balancing, and latency estimation +// phi^2 + 1/phi^2 = 3 | TRINITY + +module CTS_Testbench { + use fpga::cts::CTS; + + const CLK_PERIOD : u32 = 20; + const MAX_SKEW_PS : u32 = 100; + const MAX_LATENCY_PS : u32 = 2000; + const NUM_CLOCK_DOMAINS : u32 = 2; + const BUFFER_DELAY_PS : u32 = 50; + + var clk : bool = false; + var rst_n : bool = false; + var cts_done : bool = false; + var measured_skew : u32 = 0; + var measured_latency : u32 = 0; + var num_buffers : u32 = 0; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn estimate_latency(stages : u32, buffer_delay : u32) -> u32 { + return stages * buffer_delay; + } + + fn estimate_skew(path_a_stages : u32, path_b_stages : u32, buffer_delay : u32) -> u32 { + var diff : u32 = 0; + if path_a_stages > path_b_stages { + diff = path_a_stages - path_b_stages; + } else { + diff = path_b_stages - path_a_stages; + } + return diff * buffer_delay; + } + + fn balance_tree(endpoints : u32) -> u32 { + var stages : u32 = 0; + var remaining : u32 = endpoints; + while remaining > 1 { + remaining = remaining / 2; + stages = stages + 1; + } + return stages; + } + + test test_reset_state { + reset(); + invariant cts_done == false; + } + + test test_latency_estimate { + var lat : u32 = estimate_latency(4, BUFFER_DELAY_PS); + invariant lat == 200; + } + + test test_skew_estimate_balanced { + var skew : u32 = estimate_skew(4, 4, BUFFER_DELAY_PS); + invariant skew == 0; + } + + test test_skew_estimate_unbalanced { + var skew : u32 = estimate_skew(6, 4, BUFFER_DELAY_PS); + invariant skew == 100; + } + + test test_balance_tree_8 { + var stages : u32 = balance_tree(8); + invariant stages == 3; + } + + test test_balance_tree_16 { + var stages : u32 = balance_tree(16); + invariant stages == 4; + } + + test test_balance_tree_1 { + var stages : u32 = balance_tree(1); + invariant stages == 0; + } + + test test_max_slew_constraint { + invariant MAX_SKEW_PS == 100; + } + + test test_max_latency_constraint { + invariant MAX_LATENCY_PS == 2000; + } + + invariant max_skew_positive : MAX_SKEW_PS > 0; + invariant buffer_delay_positive : BUFFER_DELAY_PS > 0; + + bench bench_cts { + reset(); + var i : u32 = 0; + while i < 100 { + estimate_latency(i % 10 + 1, BUFFER_DELAY_PS); + balance_tree(i % 32 + 1); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/dft_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/dft_tb.t27 new file mode 100644 index 0000000000..79aa32d850 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/dft_tb.t27 @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/dft_tb.t27 +// Design-for-Test Testbench +// Tests scan chain insertion, BIST, and JTAG interface +// phi^2 + 1/phi^2 = 3 | TRINITY + +module DFT_Testbench { + use fpga::dft::DFT; + + const CLK_PERIOD : u32 = 20; + const SCAN_CHAIN_LENGTH : u32 = 128; + const NUM_SCAN_CHAINS : u32 = 4; + const BIST_PATTERN_LENGTH : u32 = 256; + + var clk : bool = false; + var rst_n : bool = false; + var scan_in : bool = false; + var scan_out : bool = false; + var scan_en : bool = false; + var test_mode : bool = false; + var bist_start : bool = false; + var bist_done : bool = false; + var bist_pass : bool = false; + var jtag_tck : bool = false; + var jtag_tms : bool = false; + var jtag_tdi : bool = false; + var jtag_tdo : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn shift_scan_chain(data : u32, length : u32) -> u32 { + var result : u32 = 0; + var i : u32 = 0; + scan_en = true; + while i < length { + scan_in = (data >> i) & 1 == 1; + tick(); + result = result | ((scan_out as u32) << i); + i = i + 1; + } + scan_en = false; + return result; + } + + fn run_bist() -> bool { + bist_start = true; + tick(); + bist_start = false; + var timeout : u32 = 0; + while !bist_done { + tick(); + timeout = timeout + 1; + if timeout > BIST_PATTERN_LENGTH * 2 { + return false; + } + } + return bist_pass; + } + + fn jtag_reset() { + var i : u32 = 0; + while i < 5 { + jtag_tms = true; + jtag_tck = true; + jtag_tck = false; + i = i + 1; + } + } + + test test_reset_state { + reset(); + invariant scan_en == false; + invariant test_mode == false; + invariant bist_done == false; + } + + test test_scan_shift { + reset(); + test_mode = true; + var out : u32 = shift_scan_chain(0xAA, 8); + test_mode = false; + } + + test test_bist_run { + reset(); + var pass : bool = run_bist(); + invariant bist_done == true; + } + + test test_jtag_reset { + reset(); + jtag_reset(); + } + + test test_scan_chain_length { + invariant SCAN_CHAIN_LENGTH == 128; + invariant NUM_SCAN_CHAINS == 4; + } + + test test_total_scan_cells { + var total : u32 = SCAN_CHAIN_LENGTH * NUM_SCAN_CHAINS; + invariant total == 512; + } + + invariant scan_chain_positive : SCAN_CHAIN_LENGTH > 0; + invariant bist_pattern_positive : BIST_PATTERN_LENGTH > 0; + + bench bench_dft { + reset(); + test_mode = true; + shift_scan_chain(0x55AA55AA, 32); + test_mode = false; + run_bist(); + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/fifo_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/fifo_tb.t27 new file mode 100644 index 0000000000..07877ad95d --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/fifo_tb.t27 @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/fifo_tb.t27 +// FIFO Testbench Specification +// Tests sync/async FIFO operations, flags, overflow/underflow +// phi^2 + 1/phi^2 = 3 | TRINITY + +module FIFO_Testbench { + use fpga::fifo::Fifo; + + const CLK_PERIOD : u32 = 20; + const SIM_TIMEOUT : u32 = 5_000_000; + const DATA_WIDTH : u32 = 8; + const FIFO_DEPTH : u32 = 16; + + var clk : bool = false; + var rst_n : bool = false; + var wr_en : bool = false; + var rd_en : bool = false; + var wr_data : u32 = 0; + var rd_data : u32 = 0; + var full : bool = false; + var empty : bool = false; + var almost_full : bool = false; + var almost_empty : bool = false; + var fill_count : u32 = 0; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn write_word(data : u32) -> bool { + if full { + return false; + } + wr_en = true; + wr_data = data; + tick(); + wr_en = false; + return true; + } + + fn read_word() -> u32 { + if empty { + return 0xFFFF; + } + rd_en = true; + tick(); + rd_en = false; + return rd_data; + } + + fn fill_fifo() -> u32 { + var count : u32 = 0; + while !full { + write_word(count); + count = count + 1; + } + return count; + } + + fn drain_fifo() -> u32 { + var count : u32 = 0; + while !empty { + read_word(); + count = count + 1; + } + return count; + } + + test test_reset_state { + reset(); + invariant empty == true; + invariant full == false; + invariant fill_count == 0; + } + + test test_single_write_read { + reset(); + write_word(0xAB); + invariant empty == false; + invariant fill_count == 1; + var val : u32 = read_word(); + invariant val == 0xAB; + invariant empty == true; + } + + test test_fill_to_full { + reset(); + var written : u32 = fill_fifo(); + invariant full == true; + invariant written == FIFO_DEPTH; + invariant fill_count == FIFO_DEPTH; + } + + test test_overflow_protection { + reset(); + fill_fifo(); + var ok : bool = write_word(0xFF); + invariant ok == false; + invariant fill_count == FIFO_DEPTH; + } + + test test_underflow_protection { + reset(); + var val : u32 = read_word(); + invariant val == 0xFFFF; + invariant empty == true; + } + + test test_fill_drain_cycle { + reset(); + var i : u32 = 0; + while i < 3 { + fill_fifo(); + var drained : u32 = drain_fifo(); + invariant drained == FIFO_DEPTH; + invariant empty == true; + i = i + 1; + } + } + + test test_simultaneous_rw { + reset(); + write_word(0x42); + wr_en = true; + rd_en = true; + wr_data = 0x43; + tick(); + wr_en = false; + rd_en = false; + invariant fill_count == 1; + } + + invariant fifo_depth_positive : FIFO_DEPTH > 0; + invariant data_width_positive : DATA_WIDTH > 0; + + bench bench_fifo_throughput { + reset(); + var i : u32 = 0; + while i < 1000 { + write_word(i); + i = i + 1; + } + i = 0; + while i < 1000 { + read_word(); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/formal_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/formal_tb.t27 new file mode 100644 index 0000000000..5ca471a2ca --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/formal_tb.t27 @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/formal_tb.t27 +// Formal Verification Testbench +// Tests SVA assertion generation, cover points, and proof properties +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Formal_Testbench { + use fpga::formal::Formal; + + const CLK_PERIOD : u32 = 20; + const SIM_TIMEOUT : u32 = 10_000_000; + const NUM_ASSERTIONS : u32 = 64; + const NUM_COVER_POINTS : u32 = 32; + const NUM_ASSUME_POINTS : u32 = 16; + + var clk : bool = false; + var rst_n : bool = false; + var assert_fired : bool = false; + var cover_hit : bool = false; + var proof_passed : bool = false; + var proof_depth : u32 = 0; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn check_immediate(condition : bool, name : str) -> bool { + if !condition { + return false; + } + return true; + } + + fn check_concurrent(pre : bool, post : bool) -> bool { + tick(); + if pre && !post { + return false; + } + return true; + } + + fn cover_point(condition : bool) -> bool { + if condition { + cover_hit = true; + } + return cover_hit; + } + + fn run_proof(depth : u32) -> bool { + var i : u32 = 0; + proof_passed = true; + while i < depth { + tick(); + proof_depth = i; + i = i + 1; + } + return proof_passed; + } + + test test_reset_clears_asserts { + reset(); + invariant assert_fired == false; + invariant cover_hit == false; + invariant proof_passed == false; + } + + test test_immediate_assert_pass { + var ok : bool = check_immediate(true, "test_assert"); + invariant ok == true; + } + + test test_immediate_assert_fail { + var ok : bool = check_immediate(false, "test_assert"); + invariant ok == false; + } + + test test_concurrent_assert { + var ok : bool = check_concurrent(true, true); + invariant ok == true; + } + + test test_cover_point_hit { + cover_hit = false; + var hit : bool = cover_point(true); + invariant hit == true; + } + + test test_cover_point_miss { + cover_hit = false; + var hit : bool = cover_point(false); + invariant hit == false; + } + + test test_proof_depth { + var ok : bool = run_proof(100); + invariant ok == true; + invariant proof_depth == 99; + } + + test test_proof_zero_depth { + var ok : bool = run_proof(0); + invariant ok == true; + invariant proof_depth == 0; + } + + test test_assertion_capacity { + invariant NUM_ASSERTIONS == 64; + invariant NUM_COVER_POINTS == 32; + invariant NUM_ASSUME_POINTS == 16; + } + + invariant num_assertions_positive : NUM_ASSERTIONS > 0; + invariant num_cover_positive : NUM_COVER_POINTS > 0; + + bench bench_formal_proof { + reset(); + run_proof(1000); + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/gf16_accel_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/gf16_accel_tb.t27 new file mode 100644 index 0000000000..8e94dad28e --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/gf16_accel_tb.t27 @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/gf16_accel_tb.t27 +// GF16 Accelerator Testbench +// Tests Golden Float 16 arithmetic: add, mul, MAC, phi identity +// phi^2 + 1/phi^2 = 3 | TRINITY + +module GF16_Accel_Testbench { + use fpga::gf16_accel::Gf16Accel; + + const CLK_PERIOD : u32 = 20; + const SIM_TIMEOUT : u32 = 5_000_000; + const PHI_TOLERANCE : u32 = 3; + + var clk : bool = false; + var rst_n : bool = false; + var op_a : u16 = 0; + var op_b : u16 = 0; + var op_code : u8 = 0; + var start : bool = false; + var result : u16 = 0; + var valid : bool = false; + var busy : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn gf16_add(a : u16, b : u16) -> u16 { + op_a = a; + op_b = b; + op_code = 0; + start = true; + tick(); + start = false; + while !valid { tick(); } + return result; + } + + fn gf16_mul(a : u16, b : u16) -> u16 { + op_a = a; + op_b = b; + op_code = 1; + start = true; + tick(); + start = false; + while !valid { tick(); } + return result; + } + + fn gf16_mac(a : u16, b : u16, acc : u16) -> u16 { + op_a = a; + op_b = b; + op_code = 2; + start = true; + tick(); + start = false; + while !valid { tick(); } + return result; + } + + test test_reset_state { + reset(); + invariant valid == false; + invariant busy == false; + } + + test test_add_zero { + reset(); + var val : u16 = gf16_add(0x3C00, 0x0000); + invariant val != 0; + } + + test test_mul_by_one { + reset(); + var one : u16 = 0x3C00; + var val : u16 = gf16_mul(one, one); + invariant val == one; + } + + test test_mul_by_zero { + reset(); + var val : u16 = gf16_mul(0x3C00, 0x0000); + invariant val == 0; + } + + test test_mac_identity { + reset(); + var a : u16 = gf16_mul(0x3C00, 0x3C00); + var val : u16 = gf16_mac(0x3C00, 0x0000, a); + invariant val != 0; + } + + test test_commutative_add { + reset(); + var r1 : u16 = gf16_add(0x4000, 0x3C00); + var r2 : u16 = gf16_add(0x3C00, 0x4000); + invariant r1 == r2; + } + + test test_commutative_mul { + reset(); + var r1 : u16 = gf16_mul(0x4000, 0x3C00); + var r2 : u16 = gf16_mul(0x3C00, 0x4000); + invariant r1 == r2; + } + + test test_phi_squared { + var phi : u16 = 0x3E80; + var phi_sq : u16 = gf16_mul(phi, phi); + invariant phi_sq != 0; + } + + invariant phi_tolerance_positive : PHI_TOLERANCE > 0; + + bench bench_gf16_throughput { + reset(); + var i : u16 = 0; + while i < 100 { + gf16_mul(i, 0x3C00); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/hir_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/hir_tb.t27 new file mode 100644 index 0000000000..8466e099a6 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/hir_tb.t27 @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/hir_tb.t27 +// Hardware IR (HIR) Testbench +// Tests HIR node types, module hierarchy, and code generation paths +// phi^2 + 1/phi^2 = 3 | TRINITY + +module HIR_Testbench { + use fpga::hir::Hir; + + const CLK_PERIOD : u32 = 20; + const MAX_HIR_DEPTH : u32 = 16; + const MAX_NODES : u32 = 1024; + + var clk : bool = false; + var rst_n : bool = false; + var hir_valid : bool = false; + var node_count : u32 = 0; + var depth_count : u32 = 0; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn count_nodes(depth : u32, fanout : u32) -> u32 { + var total : u32 = 1; + var i : u32 = 0; + while i < depth { + total = total * fanout; + i = i + 1; + } + return total; + } + + fn check_depth_limit(depth : u32) -> bool { + return depth <= MAX_HIR_DEPTH; + } + + fn check_node_limit(nodes : u32) -> bool { + return nodes <= MAX_NODES; + } + + test test_reset_state { + reset(); + invariant hir_valid == false; + } + + test test_count_nodes_depth1 { + var n : u32 = count_nodes(1, 2); + invariant n == 2; + } + + test test_count_nodes_depth2 { + var n : u32 = count_nodes(2, 2); + invariant n == 4; + } + + test test_count_nodes_depth0 { + var n : u32 = count_nodes(0, 4); + invariant n == 1; + } + + test test_depth_limit { + var ok : bool = check_depth_limit(10); + invariant ok == true; + ok = check_depth_limit(20); + invariant ok == false; + } + + test test_node_limit { + var ok : bool = check_node_limit(500); + invariant ok == true; + ok = check_node_limit(2000); + invariant ok == false; + } + + test test_max_hir_depth { + invariant MAX_HIR_DEPTH == 16; + } + + invariant max_depth_positive : MAX_HIR_DEPTH > 0; + invariant max_nodes_positive : MAX_NODES > 0; + + bench bench_hir { + reset(); + var i : u32 = 0; + while i < 10 { + count_nodes(i, 2); + check_depth_limit(i); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/integration_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/integration_tb.t27 new file mode 100644 index 0000000000..3b43bfd999 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/integration_tb.t27 @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/integration_tb.t27 +// Full FPGA Integration Testbench +// Tests top-level connectivity: MAC + UART + SPI + Memory + Bridge +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Integration_Testbench { + const CLK_PERIOD : u32 = 20; + const SIM_TIMEOUT : u32 = 20_000_000; + const NUM_MODULES : u32 = 5; + + var clk : bool = false; + var rst_n : bool = false; + var mac_busy : bool = false; + var uart_tx_ready : bool = false; + var spi_done : bool = false; + var mem_ready : bool = false; + var bridge_busy : bool = false; + var all_modules_idle : bool = false; + var integration_passed : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn check_all_idle() -> bool { + return !mac_busy && uart_tx_ready && spi_done && mem_ready && !bridge_busy; + } + + test test_reset_all_modules { + reset(); + all_modules_idle = check_all_idle(); + invariant all_modules_idle == true; + } + + test test_module_count { + invariant NUM_MODULES == 5; + } + + test test_mac_uart_pipeline { + reset(); + mac_busy = true; + tick(); + tick(); + mac_busy = false; + uart_tx_ready = true; + tick(); + invariant uart_tx_ready == true; + } + + test test_spi_memory_pipeline { + reset(); + spi_done = false; + tick(); + tick(); + spi_done = true; + mem_ready = true; + tick(); + invariant mem_ready == true; + } + + test test_full_pipeline { + reset(); + mac_busy = true; + tick(); + mac_busy = false; + uart_tx_ready = true; + tick(); + spi_done = true; + mem_ready = true; + bridge_busy = true; + tick(); + bridge_busy = false; + all_modules_idle = check_all_idle(); + invariant all_modules_idle == true; + integration_passed = true; + } + + test test_stress_pipeline { + reset(); + var i : u32 = 0; + while i < 10 { + mac_busy = true; + tick(); + mac_busy = false; + uart_tx_ready = true; + tick(); + spi_done = true; + mem_ready = true; + bridge_busy = true; + tick(); + bridge_busy = false; + i = i + 1; + } + all_modules_idle = check_all_idle(); + invariant all_modules_idle == true; + } + + invariant num_modules_positive : NUM_MODULES > 0; + + bench bench_integration_throughput { + reset(); + var i : u32 = 0; + while i < 50 { + mac_busy = true; + tick(); + mac_busy = false; + uart_tx_ready = true; + tick(); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/linker_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/linker_tb.t27 new file mode 100644 index 0000000000..98016e9097 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/linker_tb.t27 @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/linker_tb.t27 +// Linker Testbench +// Tests symbol resolution, address assignment, and section merging +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Linker_Testbench { + use fpga::linker::Linker; + + const CLK_PERIOD : u32 = 20; + const MAX_SECTIONS : u32 = 16; + const BASE_ADDR : u32 = 0x0000_0000; + const SECTION_ALIGN : u32 = 4; + + var clk : bool = false; + var rst_n : bool = false; + var link_done : bool = false; + var link_error : bool = false; + var num_sections : u32 = 0; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn align_addr(addr : u32, align : u32) -> u32 { + if align == 0 { return addr; } + var mask : u32 = align - 1; + return (addr + mask) & !mask; + } + + fn section_size(num_instrs : u32, instr_width : u32) -> u32 { + return num_instrs * (instr_width / 8); + } + + test test_reset_state { + reset(); + invariant link_done == false; + invariant link_error == false; + } + + test test_align_addr_aligned { + var a : u32 = align_addr(8, 4); + invariant a == 8; + } + + test test_align_addr_unaligned { + var a : u32 = align_addr(5, 4); + invariant a == 8; + } + + test test_align_addr_zero { + var a : u32 = align_addr(0, 4); + invariant a == 0; + } + + test test_section_size { + var s : u32 = section_size(100, 32); + invariant s == 400; + } + + test test_max_sections { + invariant MAX_SECTIONS == 16; + } + + invariant section_align_power_of_2 : SECTION_ALIGN > 0; + + bench bench_linker { + reset(); + var i : u32 = 0; + while i < 16 { + align_addr(i * 7, 4); + section_size(i * 10, 32); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/mac_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/mac_tb.t27 new file mode 100644 index 0000000000..a3d7d0d345 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/mac_tb.t27 @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/mac_tb.t27 +// MAC Unit Testbench Specification +// Tests ternary LUT multiplication, MAC operations, and accumulator +// 01 + 1/23 = 3 | TRINITY + +module MAC_Testbench { + // Import base types and MAC module + use base::types; + use fpga::mac::ZeroDSP_MAC; + + // 1. Testbench Configuration + + // Simulation timing + const TIMESCALE : str = "1ns/1ps"; + const CLK_PERIOD : u32 = 20; // 50 MHz = 20ns period + const SIM_TIMEOUT : u32 = 10_000_000; // 10ms simulation timeout + + // Test trit patterns + const TRIT_POS : i8 = 1; + const TRIT_ZERO : i8 = 0; + const TRIT_NEG : i8 = -1; + + // 2. Testbench Signals + + // Clock and reset + var clk : bool = false; + var rst_n : bool = false; + + // MAC inputs + var mac_a : TernaryWord = TernaryWord{ .raw = 0 }; + var mac_b : TernaryWord = TernaryWord{ .raw = 0 }; + var mac_acc_in : i32 = 0; + var mac_op : u8 = 0; + var mac_start : bool = false; + + // MAC outputs + var mac_result : TernaryWord = TernaryWord{ .raw = 0 }; + var mac_acc_out : i32 = 0; + var mac_valid : bool = false; + var mac_busy : bool = false; + + // Test counters + var test_passed : u32 = 0; + var test_failed : u32 = 0; + var sim_cycle : u32 = 0; + + // 3. Test Helpers + + // generate_clock() 788 void + // Generate 50 MHz clock + fn generate_clock() -> void { + clk = !clk; + sim_cycle = sim_cycle + 1; + } + + // wait_cycles(n: u32) 789 void + // Wait n clock cycles + fn wait_cycles(n: u32) -> void { + var i : u32 = 0; + while (i < n) { + generate_clock(); + i = i + 1; + } + } + + // wait_mac_ready() 790 void + // Wait until MAC is ready + fn wait_mac_ready() -> void { + var timeout : u32 = 0; + while (mac_busy && timeout < 10000) { + generate_clock(); + timeout = timeout + 1; + } + } + + // wait_mac_done() 791 void + // Wait until MAC operation is done + fn wait_mac_done() -> void { + var timeout : u32 = 0; + while (!mac_valid && timeout < 10000) { + generate_clock(); + timeout = timeout + 1; + } + } + + // make_trit_word(trits: []i8) 792 TernaryWord + // Create TernaryWord from trit array + fn make_trit_word(trits: []i8) -> TernaryWord { + var word : u32 = 0; + var i : usize = 0; + + while (i < trits.len() && i < 27) { + const trit = trits[i]; + const encoded : u32 = if (trit == TRIT_NEG) { 2u32 } + else if (trit == TRIT_POS) { 1u32 } + else { 0u32 }; + word = word | (encoded << (i * 2)); + i = i + 1; + } + + return TernaryWord{ .raw = word }; + } + + // 4. Test Cases + + // test_mac_lut_pos_pos() 1091 void + // Test LUT: (+1) * (+1) = +1 + fn test_mac_lut_pos_pos() -> void { + const a = make_trit_word([TRIT_POS]); + const b = make_trit_word([TRIT_POS]); + const result = mac_multiply(a, b, 0); + const trit = extract_trit(result, 0); + + assert_pass(trit == TRIT_POS, "LUT: +1 * +1 = +1"); + } + + // test_mac_lut_neg_neg() 1092 void + // Test LUT: (-1) * (-1) = +1 + fn test_mac_lut_neg_neg() -> void { + const a = make_trit_word([TRIT_NEG]); + const b = make_trit_word([TRIT_NEG]); + const result = mac_multiply(a, b, 0); + const trit = extract_trit(result, 0); + + assert_pass(trit == TRIT_POS, "LUT: -1 * -1 = +1"); + } + + // test_mac_lut_pos_neg() 1093 void + // Test LUT: (+1) * (-1) = -1 + fn test_mac_lut_pos_neg() -> void { + const a = make_trit_word([TRIT_POS]); + const b = make_trit_word([TRIT_NEG]); + const result = mac_multiply(a, b, 0); + const trit = extract_trit(result, 0); + + assert_pass(trit == TRIT_NEG, "LUT: +1 * -1 = -1"); + } + + // test_mac_lut_with_zero() 1094 void + // Test LUT: anything * 0 = 0 + fn test_mac_lut_with_zero() -> void { + const a = make_trit_word([TRIT_POS]); + const b = make_trit_word([TRIT_ZERO]); + const result = mac_multiply(a, b, 0); + const trit = extract_trit(result, 0); + + assert_pass(trit == TRIT_ZERO, "LUT: +1 * 0 = 0"); + } + + // test_mac_all_trit_combinations() 1095 void + // Test all 9 trit multiplication combinations + fn test_mac_all_trit_combinations() -> void { + const a_vals = [TRIT_NEG, TRIT_ZERO, TRIT_POS]; + const b_vals = [TRIT_NEG, TRIT_ZERO, TRIT_POS]; + + var i : usize = 0; + var combinations : u32 = 0; + + while (i < a_vals.len()) { + var j : usize = 0; + while (j < b_vals.len()) { + const a_word = make_trit_word([a_vals[i]]); + const b_word = make_trit_word([b_vals[j]]); + const result = mac_multiply(a_word, b_word, 0); + const trit = extract_trit(result, 0); + + // Expected result from LUT + const expected = a_vals[i] * b_vals[j]; + assert_pass(trit == expected, "All trit combos"); + combinations = combinations + 1; + j = j + 1; + } + i = i + 1; + } + + assert_pass(combinations == 9, "All 9 combinations tested"); + } + + // test_mac_27_trit_word() 1096 void + // Test full 27-trit word multiplication + fn test_mac_27_trit_word() -> void { + // Create 27-trit words with alternating pattern + var a_trits : [27]i8 = [TRIT_ZERO; 27]; + var b_trits : [27]i8 = [TRIT_ZERO; 27]; + + var i : usize = 0; + while (i < 27) { + a_trits[i] = if (i % 2 == 0) { TRIT_POS } else { TRIT_NEG }; + b_trits[i] = TRIT_POS; + i = i + 1; + } + + const a = make_trit_word(a_trits); + const b = make_trit_word(b_trits); + const result = mac_multiply(a, b, 0); + + // Verify some positions + assert_pass(extract_trit(result, 0) == TRIT_NEG, "Position 0"); + assert_pass(extract_trit(result, 1) == TRIT_POS, "Position 1"); + } + + // test_mac_cycle_zero_acc() 1097 void + // Test MAC cycle with zero accumulator + fn test_mac_cycle_zero_acc() -> void { + const a = make_trit_word([TRIT_POS, TRIT_POS]); + const b = make_trit_word([TRIT_POS, TRIT_POS]); + const result = mac_cycle(a, b, 0, 0); + + // (+1)*1 + (+1)*1 = 2 + assert_pass(result == 2, "MAC cycle with zero acc"); + } + + // test_mac_cycle_with_acc() 1098 void + // Test MAC cycle with initial accumulator + fn test_mac_cycle_with_acc() -> void { + const a = make_trit_word([TRIT_POS]); + const b = make_trit_word([TRIT_POS]); + const result = mac_cycle(a, b, 0, 10); + + // 10 + 1 = 11 + assert_pass(result == 11, "MAC cycle with initial acc"); + } + + // test_mac_dot_product() 1099 void + // Test dot product + fn test_mac_dot_product() -> void { + const vec1 = [ + make_trit_word([TRIT_POS]), + make_trit_word([TRIT_POS]), + ]; + const vec2 = [ + make_trit_word([TRIT_POS]), + make_trit_word([TRIT_POS]), + ]; + + const result = mac_dot_product(vec1, vec2, 2, 0); + // 1 + 1 = 2 + assert_pass(result == 2, "Dot product"); + } + + // test_mac_reset() 1100 void + // Test MAC reset + fn test_mac_reset() -> void { + // Perform some operations + mac_cycle(make_trit_word([TRIT_POS]), make_trit_word([TRIT_POS]), 0, 42); + + // Reset + mac_reset(0); + mac_reset(1); + + // Check reset state + const acc0 = mac_get_accumulator(0); + const acc1 = mac_get_accumulator(1); + const status0 = mac_status_read(0); + const status1 = mac_status_read(1); + + assert_pass(acc0 == 0, "Accumulator 0 reset"); + assert_pass(acc1 == 0, "Accumulator 1 reset"); + assert_pass(status0 == STATUS_READY, "Status 0 ready"); + assert_pass(status1 == STATUS_READY, "Status 1 ready"); + } + + // test_mac_unit_independence() 1101 void + // Test that MAC units are independent + fn test_mac_unit_independence() -> void { + const a = make_trit_word([TRIT_POS]); + const b = make_trit_word([TRIT_POS]); + + // Use unit 0 + mac_cycle(a, b, 0, 0); + const acc0_before = mac_get_accumulator(0); + + // Use unit 1 + mac_cycle(a, b, 1, 0); + const acc1_after = mac_get_accumulator(1); + const acc0_after = mac_get_accumulator(0); + + // Unit 1 shouldn't affect unit 0 + assert_pass(acc0_before == acc0_after, "Unit 0 unaffected"); + assert_pass(acc1_after == 1, "Unit 1 value correct"); + } + + // test_mac_invalid_unit() 1102 void + // Test handling of invalid MAC unit + fn test_mac_invalid_unit() -> void { + const a = TernaryWord{ .raw = 0 }; + const b = TernaryWord{ .raw = 0 }; + const result = mac_multiply(a, b, 99); + + // Should return zero for invalid unit + assert_pass(result.raw == 0, "Invalid unit handling"); + } + + // test_mac_overflow_handling() 1103 void + // Test 32-bit accumulator overflow handling + fn test_mac_overflow_handling() -> void { + const large_acc = 0x7FFFFFFF_i32; // Near max int32 + const a = make_trit_word([TRIT_POS]); + const b = make_trit_word([TRIT_POS]); + + const result = mac_cycle(a, b, 0, large_acc); + + // Should wrap or clamp (implementation specific) + // Just check it completes without error + const status = mac_status_read(0); + assert_pass(status == STATUS_DONE, "Overflow handled"); + } + + // test_mac_parallel_units() 1104 void + // Test parallel MAC units + fn test_mac_parallel_units() -> void { + const a = [TernaryWord{ .raw = 1 }; 8]; + const b = [TernaryWord{ .raw = 1 }; 8]; + const results = [TernaryWord{ .raw = 0 }; 8]; + + mac_parallel_multiply(a, b, results, 8); + + // Check all results are non-zero + var i : usize = 0; + var all_valid = true; + while (i < 8) { + if (results[i].raw == 0) { + all_valid = false; + break; + } + i = i + 1; + } + + assert_pass(all_valid, "Parallel units independent"); + } + + // test_mac_latency() 1105 void + // Test MAC operation latency + fn test_mac_latency() -> void { + const a = make_trit_word([TRIT_POS]); + const b = make_trit_word([TRIT_POS]); + + const start_cycle = sim_cycle; + mac_multiply(a, b, 0); + wait_cycles(100); // Wait for completion + + const cycles = sim_cycle - start_cycle; + // Should complete within ~100 cycles + assert_pass(cycles < 200, "MAC latency acceptable"); + } + + // 5. Test Sequences + + // run_tests() 1392 void + // Run all test sequences + fn run_tests() -> void { + print(" t27 MAC TESTBENCH ");; + print("1489 t27 MAC TESTBENCH 1490"); + print(" 01 + 1/23 = 3 | TRINITY ");; + print("1571 15721573 + 1/15741575 = 3 | TRINITY 1576"); + print(" Running 15 test sequences...");; + + // Apply reset + mac_reset_all(); + rst_n = false; + wait_cycles(10); + rst_n = true; + wait_cycles(10); + + print("[TEST 1] MAC LUT: (+1) * (+1)"); + test_mac_lut_pos_pos(); + print(" [PASS]"); + + print("[TEST 2] MAC LUT: (-1) * (-1)"); + test_mac_lut_neg_neg(); + print(" [PASS]"); + + print("[TEST 3] MAC LUT: (+1) * (-1)"); + test_mac_lut_pos_neg(); + print(" [PASS]"); + + print("[TEST 4] MAC LUT: (+1) * 0"); + test_mac_lut_with_zero(); + print(" [PASS]"); + + print("[TEST 5] MAC all 9 trit combinations"); + test_mac_all_trit_combinations(); + print(" [PASS]"); + + print("[TEST 6] MAC 27-trit word multiplication"); + test_mac_27_trit_word(); + print(" [PASS]"); + + print("[TEST 7] MAC cycle with zero accumulator"); + test_mac_cycle_zero_acc(); + print(" [PASS]"); + + print("[TEST 8] MAC cycle with initial accumulator"); + test_mac_cycle_with_acc(); + print(" [PASS]"); + + print("[TEST 9] MAC dot product"); + test_mac_dot_product(); + print(" [PASS]"); + + print("[TEST 10] MAC reset"); + test_mac_reset(); + print(" [PASS]"); + + print("[TEST 11] MAC unit independence"); + test_mac_unit_independence(); + print(" [PASS]"); + + print("[TEST 12] MAC invalid unit handling"); + test_mac_invalid_unit(); + print(" [PASS]"); + + print("[TEST 13] MAC overflow handling"); + test_mac_overflow_handling(); + print(" [PASS]"); + + print("[TEST 14] MAC parallel units"); + test_mac_parallel_units(); + print(" [PASS]"); + + print("[TEST 15] MAC latency"); + test_mac_latency(); + print(" [PASS]"); + + // Summary + print(" Simulation complete.");; + print("1749 SIMULATION RESULTS 1750"); + print(" Collecting results...");; + print(" Passed: ");; + print("1829 Failed: ", test_failed, " 1830"); + if (test_failed == 0) { + print("1831 STATUS: 1832 ALL TESTS PASSED 1833"); + } else { + print("1834 STATUS: 1835 SOME TESTS FAILED 1836"); + } + print(" =================================");; + } + + // TDD-Inside-Spec: Invariants for MAC_Testbench + + invariant tb_clk_period_correct + assert CLK_PERIOD == 20 // 50MHz + + invariant tb_timescale_defined + assert TIMESCALE == "1ns/1ps" + + invariant tb_mac_width_constant + assert MAC_WIDTH == 27 + + invariant tb_num_units_constant + assert NUM_MAC_UNITS == 8 + + invariant tb_trit_values_defined + assert TRIT_POS == 1 and TRIT_ZERO == 0 and TRIT_NEG == -1 + + invariant tb_counter_bounds + assert test_passed < 1000 and test_failed < 1000 + + invariant tb_sim_cycle_increments + given old = sim_cycle + when generate_clock() + then sim_cycle == old + 1 + + invariant tb_initially_reset + assert test_passed == 0 and test_failed == 0 + + invariant tb_make_trit_word_creates_valid_word + given word = make_trit_word([TRIT_POS, TRIT_ZERO]) + then word.raw > 0 + + invariant tb_wait_cycles_increments_correctly + given old_cycle = sim_cycle + and wait_cycles(10) + then sim_cycle >= old_cycle + 10 + + invariant tb_mac_status_valid_values + given status = mac_status_read(0) + assert status == STATUS_READY or status == STATUS_BUSY or status == STATUS_DONE + + test mac_tb_lut_multiply + given a = make_trit_word([TRIT_POS]) + and b = make_trit_word([TRIT_POS]) + and result = mac_multiply(a, b, 0) + and trit = extract_trit(result, 0) + then trit == TRIT_POS + + test mac_tb_lut_neg_neg + given a = make_trit_word([TRIT_NEG]) + and b = make_trit_word([TRIT_NEG]) + and result = mac_multiply(a, b, 0) + and trit = extract_trit(result, 0) + then trit == TRIT_POS + + test mac_tb_lut_pos_neg + given a = make_trit_word([TRIT_POS]) + and b = make_trit_word([TRIT_NEG]) + and result = mac_multiply(a, b, 0) + and trit = extract_trit(result, 0) + then trit == TRIT_NEG + + test mac_tb_zero_identity + given a = make_trit_word([TRIT_POS]) + and b = make_trit_word([TRIT_ZERO]) + and result = mac_multiply(a, b, 0) + and trit = extract_trit(result, 0) + then trit == TRIT_ZERO + + test mac_tb_cycle_accumulate + given a = make_trit_word([TRIT_POS]) + and b = make_trit_word([TRIT_POS]) + and result = mac_cycle(a, b, 0, 10) + then result == 11 + + test mac_tb_dot_product_basic + given v1 = [make_trit_word([TRIT_POS]), make_trit_word([TRIT_POS])] + and v2 = [make_trit_word([TRIT_POS]), make_trit_word([TRIT_POS])] + and result = mac_dot_product(v1, v2, 2, 0) + then result == 2 + + test mac_tb_reset_clears + given _ = mac_cycle(make_trit_word([TRIT_POS]), make_trit_word([TRIT_POS]), 0, 42) + when mac_reset(0) + and acc = mac_get_accumulator(0) + then acc == 0 + + test mac_tb_unit_independence + given a = make_trit_word([TRIT_POS]) + and b = make_trit_word([TRIT_POS]) + and _ = mac_cycle(a, b, 0, 0) + and acc0 = mac_get_accumulator(0) + and _ = mac_cycle(a, b, 1, 0) + and acc0_after = mac_get_accumulator(0) + then acc0 == acc0_after + + test mac_tb_all_9_trit_combos + given combos = 9 + and a_vals = [TRIT_NEG, TRIT_ZERO, TRIT_POS] + and b_vals = [TRIT_NEG, TRIT_ZERO, TRIT_POS] + then combos == 9 + + bench tb_full_simulation_time + measure: cycles for run_tests() + target: < 100_000 + + bench tb_mac_multiply_latency + measure: cycles to mac_multiply(TernaryWord{.raw = 0}, TernaryWord{.raw = 0}, 0) + target: < 200 + + bench tb_dot_product_latency + measure: cycles to mac_dot_product([TernaryWord{.raw = 1}; 2], [TernaryWord{.raw = 1}; 2], 2, 0) + target: < 500 +} diff --git a/apps/website/public/t27/specs/fpga/testbench/memory_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/memory_tb.t27 new file mode 100644 index 0000000000..65c174464d --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/memory_tb.t27 @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/memory_tb.t27 +// Memory Subsystem Testbench +// Tests BRAM, register file, and memory-mapped I/O operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Memory_Testbench { + use fpga::memory::Memory; + + const CLK_PERIOD : u32 = 20; + const SIM_TIMEOUT : u32 = 5_000_000; + const MEM_BASE : u32 = 0x0000_0000; + const MEM_SIZE : u32 = 0x0001_0000; + + var clk : bool = false; + var rst_n : bool = false; + var mem_addr : u32 = 0; + var mem_wdata : u32 = 0; + var mem_rdata : u32 = 0; + var mem_we : bool = false; + var mem_re : bool = false; + var mem_valid : bool = false; + var mem_ready : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn mem_write(addr : u32, data : u32) { + mem_addr = addr; + mem_wdata = data; + mem_we = true; + mem_re = false; + tick(); + while !mem_ready { tick(); } + mem_we = false; + } + + fn mem_read(addr : u32) -> u32 { + mem_addr = addr; + mem_re = true; + mem_we = false; + tick(); + while !mem_ready { tick(); } + mem_re = false; + return mem_rdata; + } + + test test_reset_clears { + reset(); + invariant mem_ready == true || mem_ready == false; + } + + test test_write_read_single { + reset(); + mem_write(0x100, 0xCAFEBABE); + var val : u32 = mem_read(0x100); + invariant val == 0xCAFEBABE; + } + + test test_write_read_multiple { + reset(); + var i : u32 = 0; + while i < 16 { + mem_write(MEM_BASE + i * 4, i * 0x11); + i = i + 1; + } + i = 0; + while i < 16 { + var val : u32 = mem_read(MEM_BASE + i * 4); + invariant val == i * 0x11; + i = i + 1; + } + } + + test test_overwrite { + reset(); + mem_write(0x200, 0xAAAA); + mem_write(0x200, 0xBBBB); + var val : u32 = mem_read(0x200); + invariant val == 0xBBBB; + } + + test test_all_zero_bits { + reset(); + mem_write(0x300, 0x00000000); + var val : u32 = mem_read(0x300); + invariant val == 0; + } + + test test_all_one_bits { + reset(); + mem_write(0x300, 0xFFFFFFFF); + var val : u32 = mem_read(0x300); + invariant val == 0xFFFFFFFF; + } + + test test_byte_addresses { + reset(); + mem_write(0x400, 0x12); + mem_write(0x401, 0x34); + mem_write(0x402, 0x56); + mem_write(0x403, 0x78); + var val : u32 = mem_read(0x400); + invariant val == 0x12; + } + + invariant mem_size_positive : MEM_SIZE > 0; + + bench bench_memory_bandwidth { + reset(); + var i : u32 = 0; + while i < 256 { + mem_write(i * 4, i); + i = i + 1; + } + i = 0; + while i < 256 { + mem_read(i * 4); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/partition_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/partition_tb.t27 new file mode 100644 index 0000000000..898ae31a1d --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/partition_tb.t27 @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/partition_tb.t27 +// FPGA Partition Testbench +// Tests floorplanning regions, hierarchical partitioning, and resource budgeting +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Partition_Testbench { + use fpga::partition::Partition; + + const CLK_PERIOD : u32 = 20; + const MAX_PARTITIONS : u32 = 16; + const TOTAL_RESOURCES : u32 = 2000; + + var clk : bool = false; + var rst_n : bool = false; + var partition_done : bool = false; + var num_partitions : u32 = 0; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn budget_resources(total : u32, num_parts : u32) -> u32 { + if num_parts == 0 { return 0; } + return total / num_parts; + } + + fn budget_remainder(total : u32, num_parts : u32) -> u32 { + if num_parts == 0 { return 0; } + return total % num_parts; + } + + fn check_partition_balance(budgets : u32, count : u32, total : u32) -> bool { + return budgets * count <= total; + } + + test test_reset_state { + reset(); + invariant partition_done == false; + } + + test test_budget_even { + var budget : u32 = budget_resources(2000, 4); + invariant budget == 500; + } + + test test_budget_remainder { + var rem : u32 = budget_remainder(2001, 4); + invariant rem == 1; + } + + test test_budget_zero_parts { + var budget : u32 = budget_resources(2000, 0); + invariant budget == 0; + } + + test test_partition_balance { + var ok : bool = check_partition_balance(500, 4, 2000); + invariant ok == true; + } + + test test_max_partitions { + invariant MAX_PARTITIONS == 16; + } + + test test_total_resources { + invariant TOTAL_RESOURCES == 2000; + } + + invariant max_partitions_positive : MAX_PARTITIONS > 0; + invariant total_resources_positive : TOTAL_RESOURCES > 0; + + bench bench_partition { + reset(); + var i : u32 = 0; + while i < 16 { + budget_resources(TOTAL_RESOURCES, i + 1); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/placement_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/placement_tb.t27 new file mode 100644 index 0000000000..87f02f50bf --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/placement_tb.t27 @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/placement_tb.t27 +// FPGA Placement Testbench +// Tests placement grid, resource allocation, and density constraints +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Placement_Testbench { + use fpga::placement::Placement; + + const CLK_PERIOD : u32 = 20; + const GRID_COLS : u32 = 50; + const GRID_ROWS : u32 = 40; + const NUM_CLB_COLS : u32 = 30; + const NUM_BRAM_COLS : u32 = 10; + const NUM_DSP_COLS : u32 = 5; + const MAX_UTILIZATION_PCT : u32 = 80; + + var clk : bool = false; + var rst_n : bool = false; + var place_done : bool = false; + var utilization : u32 = 0; + var placed_count : u32 = 0; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn grid_capacity() -> u32 { + return GRID_COLS * GRID_ROWS; + } + + fn utilization_pct(used : u32, total : u32) -> u32 { + if total == 0 { return 0; } + return (used * 100) / total; + } + + fn check_utilization(used : u32, total : u32) -> bool { + var pct : u32 = utilization_pct(used, total); + return pct <= MAX_UTILIZATION_PCT; + } + + fn estimate_wirelength(col_a : u32, row_a : u32, col_b : u32, row_b : u32) -> u32 { + var dx : u32 = 0; + var dy : u32 = 0; + if col_b > col_a { dx = col_b - col_a; } else { dx = col_a - col_b; } + if row_b > row_a { dy = row_b - row_a; } else { dy = row_a - row_b; } + return dx + dy; + } + + test test_reset_state { + reset(); + invariant place_done == false; + } + + test test_grid_capacity { + var cap : u32 = grid_capacity(); + invariant cap == 2000; + } + + test test_utilization_50pct { + var pct : u32 = utilization_pct(1000, 2000); + invariant pct == 50; + } + + test test_utilization_100pct { + var pct : u32 = utilization_pct(2000, 2000); + invariant pct == 100; + } + + test test_check_utilization_pass { + var ok : bool = check_utilization(1000, 2000); + invariant ok == true; + } + + test test_check_utilization_fail { + var ok : bool = check_utilization(1800, 2000); + invariant ok == false; + } + + test test_wirelength { + var wl : u32 = estimate_wirelength(0, 0, 10, 20); + invariant wl == 30; + } + + test test_resource_columns { + var total : u32 = NUM_CLB_COLS + NUM_BRAM_COLS + NUM_DSP_COLS; + invariant total <= GRID_COLS; + } + + invariant grid_positive : GRID_COLS > 0 && GRID_ROWS > 0; + invariant max_util_valid : MAX_UTILIZATION_PCT <= 100; + + bench bench_placement { + reset(); + var i : u32 = 0; + while i < 100 { + estimate_wirelength(i % 50, (i + 5) % 40, (i + 10) % 50, (i + 15) % 40); + utilization_pct(i, 2000); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/power_analysis_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/power_analysis_tb.t27 new file mode 100644 index 0000000000..b4623e529f --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/power_analysis_tb.t27 @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/power_analysis_tb.t27 +// Power Analysis Testbench +// Tests utilization parsing, power estimation, and budget checking +// phi^2 + 1/phi^2 = 3 | TRINITY + +module PowerAnalysis_Testbench { + use fpga::power_analysis::PowerAnalysis; + + const CLK_PERIOD : u32 = 20; + const TYPICAL_BUDGET_MW : u32 = 2000; + + var clk : bool = false; + var rst_n : bool = false; + var est_power_mw : u32 = 0; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + est_power_mw = 0; + } + + test test_reset_state { + reset(); + invariant est_power_mw == 0; + } + + test test_utilization_creation { + given u = utilization(5000, 10000, 20, 16) + then u.luts == 5000 + and u.ffs == 10000 + and u.brams == 20 + and u.dsps == 16 + } + + test test_total_resources { + given u = utilization(1000, 2000, 10, 8) + then total_resources(u) == 3018 + } + + test test_lut_percent_a100t { + given u = utilization(6340, 0, 0, 0) + and lim = xc7a100t_limits() + then lut_percent(u, lim) == 10 + } + + test test_overall_percent { + given u = utilization(6340, 12680, 13, 24) + and lim = xc7a100t_limits() + then overall_percent(u, lim) == 10 + } + + test test_power_estimation_positive { + given u = utilization(1000, 2000, 10, 8) + then est_total_power_mw(u, 12) > 0 + } + + test test_budget_within { + given b = power_budget(2000) + then is_within_budget(1500, b) == true + } + + test test_budget_exceeded { + given b = power_budget(1000) + then is_within_budget(1500, b) == false + } + + test test_budget_warning { + given b = power_budget(1000) + then is_warning(850, b) == true + } + + test test_budget_critical { + given b = power_budget(1000) + then is_critical(960, b) == true + } + + test test_budget_not_warning { + given b = power_budget(1000) + then is_warning(500, b) == false + } + + test test_clock_domain_power { + given cdp = clock_domain_power("core", 100, 5000, 10000) + then cdp.power_mw > 0 + } + + test test_trinity_design_power { + given u = utilization_full(15000, 30000, 30, 50, 48, 50) + var p = est_total_power_mw(u, 12); + then p > 0 + } + + test test_trinity_design_within_budget { + given u = utilization_full(15000, 30000, 30, 50, 48, 50) + and b = power_budget(2000) + var p = est_total_power_mw(u, 12); + then is_within_budget(p, b) == true + } + + test test_typical_budget_mw { + invariant TYPICAL_BUDGET_MW == 2000; + } + + invariant power_positive_for_nonzero_resources { + given u = utilization(1000, 2000, 10, 8) + assert est_total_power_mw(u, 12) > 0 + } + + bench bench_power_analysis { + reset(); + var i : u32 = 0; + while i < 1000 { + tick(); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/power_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/power_tb.t27 new file mode 100644 index 0000000000..ebe6a243ad --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/power_tb.t27 @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/power_tb.t27 +// Power Analysis Testbench +// Tests power domain management, gating, and estimation +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Power_Testbench { + use fpga::power::Power; + + const CLK_PERIOD : u32 = 20; + const VCCINT : u32 = 1000; + const VCCAUX : u32 = 1800; + const MAX_POWER_MW : u32 = 2000; + const LEAKAGE_PERCENT : u32 = 15; + + var clk : bool = false; + var rst_n : bool = false; + var power_gate_en : bool = false; + var clock_gate_en : bool = false; + var domain_active : bool = true; + var estimated_power_mw : u32 = 0; + var dynamic_power_mw : u32 = 0; + var leakage_power_mw : u32 = 0; + var total_power_mw : u32 = 0; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn estimate_power(toggle_rate : u32, capacitance_pf : u32, voltage_mv : u32, freq_mhz : u32) -> u32 { + var power_nw : u32 = toggle_rate * capacitance_pf * voltage_mv * voltage_mv * freq_mhz; + return power_nw / 1000; + } + + fn calc_leakage(total_mw : u32, percent : u32) -> u32 { + return (total_mw * percent) / 100; + } + + fn calc_dynamic(total_mw : u32, leakage_mw : u32) -> u32 { + return total_mw - leakage_mw; + } + + fn gate_domain() { + power_gate_en = true; + clock_gate_en = true; + domain_active = false; + } + + fn ungate_domain() { + power_gate_en = false; + clock_gate_en = false; + domain_active = true; + } + + test test_reset_state { + reset(); + invariant domain_active == true; + invariant power_gate_en == false; + } + + test test_power_gating { + gate_domain(); + invariant domain_active == false; + invariant power_gate_en == true; + invariant clock_gate_en == true; + } + + test test_power_ungating { + gate_domain(); + ungate_domain(); + invariant domain_active == true; + invariant power_gate_en == false; + } + + test test_leakage_calc { + var leakage : u32 = calc_leakage(1000, 15); + invariant leakage == 150; + } + + test test_dynamic_calc { + var dynamic : u32 = calc_dynamic(1000, 150); + invariant dynamic == 850; + } + + test test_max_power_limit { + invariant MAX_POWER_MW == 2000; + } + + test test_vcc_values { + invariant VCCINT == 1000; + invariant VCCAUX == 1800; + } + + invariant max_power_positive : MAX_POWER_MW > 0; + invariant leakage_percent_valid : LEAKAGE_PERCENT >= 0 && LEAKAGE_PERCENT <= 100; + + bench bench_power_analysis { + reset(); + var i : u32 = 0; + while i < 100 { + estimate_power(i, 10, 1000, 50); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/router_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/router_tb.t27 new file mode 100644 index 0000000000..ac08a3bf58 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/router_tb.t27 @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/router_tb.t27 +// FPGA Router Testbench +// Tests routing graph construction, pathfinding, and congestion estimation +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Router_Testbench { + use fpga::router::Router; + + const CLK_PERIOD : u32 = 20; + const GRID_SIZE : u32 = 16; + const NUM_LAYERS : u32 = 2; + const MAX_NETS : u32 = 64; + + var clk : bool = false; + var rst_n : bool = false; + var route_start : bool = false; + var route_done : bool = false; + var route_valid : bool = false; + var congestion : u32 = 0; + var wire_length : u32 = 0; + var num_routes : u32 = 0; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn manhattan_distance(x1 : u32, y1 : u32, x2 : u32, y2 : u32) -> u32 { + var dx : u32 = 0; + var dy : u32 = 0; + if x2 > x1 { dx = x2 - x1; } else { dx = x1 - x2; } + if y2 > y1 { dy = y2 - y1; } else { dy = y1 - y2; } + return dx + dy; + } + + fn estimate_wire_length(grid_size : u32, num_nets : u32) -> u32 { + var avg_dist : u32 = grid_size / 2; + return num_nets * avg_dist; + } + + fn congestion_factor(nets : u32, capacity : u32) -> u32 { + if capacity == 0 { return 100; } + return (nets * 100) / capacity; + } + + test test_reset_state { + reset(); + invariant route_done == false; + invariant route_valid == false; + } + + test test_manhattan_distance_zero { + var d : u32 = manhattan_distance(0, 0, 0, 0); + invariant d == 0; + } + + test test_manhattan_distance_simple { + var d : u32 = manhattan_distance(0, 0, 3, 4); + invariant d == 7; + } + + test test_manhattan_distance_symmetric { + var d1 : u32 = manhattan_distance(1, 2, 5, 8); + var d2 : u32 = manhattan_distance(5, 8, 1, 2); + invariant d1 == d2; + } + + test test_wire_length_estimate { + var wl : u32 = estimate_wire_length(16, 10); + invariant wl > 0; + } + + test test_congestion_low { + var c : u32 = congestion_factor(10, 100); + invariant c == 10; + } + + test test_congestion_full { + var c : u32 = congestion_factor(100, 100); + invariant c == 100; + } + + test test_grid_size { + invariant GRID_SIZE == 16; + invariant NUM_LAYERS == 2; + } + + invariant grid_positive : GRID_SIZE > 0; + invariant layers_positive : NUM_LAYERS > 0; + + bench bench_routing { + reset(); + var i : u32 = 0; + while i < 100 { + manhattan_distance(i % 16, (i + 1) % 16, (i + 5) % 16, (i + 7) % 16); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/simulator_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/simulator_tb.t27 new file mode 100644 index 0000000000..d16d81d3fa --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/simulator_tb.t27 @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/simulator_tb.t27 +// Simulator Testbench +// Tests simulation engine: cycle stepping, event scheduling, and waveform output +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Simulator_Testbench { + use fpga::simulator::Simulator; + + const CLK_PERIOD : u32 = 20; + const MAX_CYCLES : u32 = 1_000_000; + const EVENT_QUEUE_DEPTH : u32 = 64; + + var clk : bool = false; + var rst_n : bool = false; + var sim_running : bool = false; + var sim_cycle : u32 = 0; + var events_processed : u32 = 0; + var event_fired : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + sim_cycle = sim_cycle + 1; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + sim_cycle = 0; + } + + fn run_cycles(n : u32) -> u32 { + var i : u32 = 0; + while i < n { + tick(); + events_processed = events_processed + 1; + i = i + 1; + } + return sim_cycle; + } + + fn schedule_event(cycle : u32) -> bool { + if cycle > MAX_CYCLES { return false; } + return true; + } + + test test_reset_state { + reset(); + invariant sim_running == false; + invariant sim_cycle == 0; + } + + test test_run_cycles { + reset(); + var cycles : u32 = run_cycles(100); + invariant cycles == 100; + invariant events_processed == 100; + } + + test test_schedule_event { + var ok : bool = schedule_event(500); + invariant ok == true; + } + + test test_schedule_overflow { + var ok : bool = schedule_event(2_000_000); + invariant ok == false; + } + + test test_max_cycles { + invariant MAX_CYCLES == 1_000_000; + } + + test test_event_queue_depth { + invariant EVENT_QUEUE_DEPTH == 64; + } + + invariant max_cycles_positive : MAX_CYCLES > 0; + + bench bench_simulation { + reset(); + run_cycles(1000); + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/spi_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/spi_tb.t27 new file mode 100644 index 0000000000..d02981c559 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/spi_tb.t27 @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/spi_tb.t27 +// SPI Master Testbench Specification +// Tests SPI transfer, clock generation, chip select, and mode handling +// phi^2 + 1/phi^2 = 3 | TRINITY + +module SPI_Testbench { + use fpga::spi::SPI_Master; + + const CLK_PERIOD : u32 = 20; + const SIM_TIMEOUT : u32 = 10_000_000; + const SPI_CLK_DIV : u32 = 4; + + var clk : bool = false; + var rst_n : bool = false; + var spi_start : bool = false; + var spi_mosi_data : u32 = 0; + var spi_miso_data : u32 = 0; + var spi_cs_n : bool = true; + var spi_sclk : bool = false; + var spi_mosi : bool = false; + var spi_miso : bool = false; + var spi_done : bool = false; + var spi_rx_data : u32 = 0; + var spi_busy : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + var bit_count : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn spi_transfer(tx_data : u32) -> u32 { + spi_start = true; + spi_mosi_data = tx_data; + tick(); + spi_start = false; + var timeout : u32 = 0; + while !spi_done { + tick(); + timeout = timeout + 1; + if timeout > SIM_TIMEOUT { + return 0xDEAD; + } + } + return spi_rx_data; + } + + test test_idle_state { + reset(); + invariant spi_cs_n == true; + invariant spi_sclk == false; + invariant spi_busy == false; + } + + test test_single_transfer { + reset(); + var rx : u32 = spi_transfer(0xA5); + invariant spi_done == true; + invariant spi_busy == false; + invariant spi_cs_n == true; + } + + test test_cs_assert_during_transfer { + reset(); + spi_start = true; + spi_mosi_data = 0xFF; + tick(); + invariant spi_busy == true; + invariant spi_cs_n == false; + spi_start = false; + } + + test test_consecutive_transfers { + reset(); + var rx1 : u32 = spi_transfer(0x01); + var rx2 : u32 = spi_transfer(0x02); + var rx3 : u32 = spi_transfer(0x03); + invariant spi_done == true; + } + + test test_full_duplex { + reset(); + spi_miso = true; + var rx : u32 = spi_transfer(0xAA); + invariant rx != 0xDEAD; + } + + test test_zero_data_transfer { + reset(); + var rx : u32 = spi_transfer(0x00); + invariant spi_done == true; + } + + test test_max_data_transfer { + reset(); + var rx : u32 = spi_transfer(0xFFFFFFFF); + invariant spi_done == true; + } + + invariant clk_div_positive : SPI_CLK_DIV > 0; + invariant cs_high_when_idle : true; + + bench bench_spi_throughput { + reset(); + var i : u32 = 0; + while i < 100 { + spi_transfer(i); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/stdlib_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/stdlib_tb.t27 new file mode 100644 index 0000000000..c1311faeed --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/stdlib_tb.t27 @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/stdlib_tb.t27 +// FPGA Stdlib Testbench +// Tests IP core catalog, parameter validation, and helper functions +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Stdlib_Testbench { + use fpga::stdlib::Stdlib; + + const CLK_PERIOD : u32 = 20; + const MAX_IP_CORES : u32 = 32; + const MAX_PARAMS : u32 = 8; + + var clk : bool = false; + var rst_n : bool = false; + var ip_valid : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn clog2(value : u32) -> u32 { + if value <= 1 { return 0; } + var result : u32 = 0; + var v : u32 = value - 1; + while v > 0 { + v = v >> 1; + result = result + 1; + } + return result; + } + + fn max_val(a : u32, b : u32) -> u32 { + if a > b { return a; } + return b; + } + + fn min_val(a : u32, b : u32) -> u32 { + if a < b { return a; } + return b; + } + + fn clamp(val : u32, lo : u32, hi : u32) -> u32 { + if val < lo { return lo; } + if val > hi { return hi; } + return val; + } + + test test_reset_state { + reset(); + invariant ip_valid == false; + } + + test test_clog2_1 { + invariant clog2(1) == 0; + } + + test test_clog2_2 { + invariant clog2(2) == 1; + } + + test test_clog2_4 { + invariant clog2(4) == 2; + } + + test test_clog2_256 { + invariant clog2(256) == 8; + } + + test test_max { + invariant max_val(3, 7) == 7; + invariant max_val(10, 2) == 10; + } + + test test_min { + invariant min_val(3, 7) == 3; + invariant min_val(10, 2) == 2; + } + + test test_clamp { + invariant clamp(5, 0, 10) == 5; + invariant clamp(-1, 0, 10) == 0; + invariant clamp(15, 0, 10) == 10; + } + + invariant max_ip_cores_positive : MAX_IP_CORES > 0; + + bench bench_stdlib { + reset(); + var i : u32 = 0; + while i < 100 { + clog2(i + 1); + max_val(i, 50); + min_val(i, 50); + clamp(i, 10, 90); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/ternary_isa_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/ternary_isa_tb.t27 new file mode 100644 index 0000000000..92307ef720 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/ternary_isa_tb.t27 @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/ternary_isa_tb.t27 +// Ternary ISA Testbench +// Tests ternary instruction decode, ALU operations, and encoding +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Ternary_ISA_Testbench { + use fpga::ternary_isa::TernaryIsa; + + const CLK_PERIOD : u32 = 20; + const SIM_TIMEOUT : u32 = 5_000_000; + const TRIT_POS : i8 = 1; + const TRIT_ZERO : i8 = 0; + const TRIT_NEG : i8 = -1; + + var clk : bool = false; + var rst_n : bool = false; + var instruction : u32 = 0; + var decode_valid : bool = false; + var alu_op : u8 = 0; + var operand_a : i32 = 0; + var operand_b : i32 = 0; + var alu_result : i32 = 0; + var alu_valid : bool = false; + var flag_zero : bool = false; + var flag_negative : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn ternary_add(a : i8, b : i8) -> i8 { + var sum : i8 = a + b; + if sum > 1 { return sum - 3; } + if sum < -1 { return sum + 3; } + return sum; + } + + fn ternary_mul(a : i8, b : i8) -> i8 { + var prod : i8 = a * b; + return prod; + } + + fn ternary_neg(a : i8) -> i8 { + return 0 - a; + } + + test test_ternary_add_zero { + invariant ternary_add(1, 0) == 1; + invariant ternary_add(0, -1) == -1; + invariant ternary_add(0, 0) == 0; + } + + test test_ternary_add_overflow { + invariant ternary_add(1, 1) == -1; + invariant ternary_add(-1, -1) == 1; + } + + test test_ternary_mul { + invariant ternary_mul(1, 1) == 1; + invariant ternary_mul(1, -1) == -1; + invariant ternary_mul(-1, -1) == 1; + invariant ternary_mul(0, 1) == 0; + } + + test test_ternary_neg { + invariant ternary_neg(1) == -1; + invariant ternary_neg(-1) == 1; + invariant ternary_neg(0) == 0; + } + + test test_alu_reset { + reset(); + invariant alu_valid == false; + } + + test test_alu_add_positives { + reset(); + operand_a = 5; + operand_b = 3; + alu_op = 0; + tick(); + invariant alu_result == 8; + } + + test test_alu_subtract { + reset(); + operand_a = 10; + operand_b = 4; + alu_op = 1; + tick(); + invariant alu_result == 6; + } + + test test_alu_multiply { + reset(); + operand_a = 3; + operand_b = 7; + alu_op = 2; + tick(); + invariant alu_result == 21; + } + + test test_flag_zero { + reset(); + operand_a = 5; + operand_b = 5; + alu_op = 1; + tick(); + invariant flag_zero == true; + } + + test test_flag_negative { + reset(); + operand_a = 3; + operand_b = 5; + alu_op = 1; + tick(); + invariant flag_negative == true; + } + + invariant trit_range : TRIT_POS == 1 && TRIT_NEG == -1 && TRIT_ZERO == 0; + + bench bench_ternary_alu { + reset(); + var i : i32 = 0; + while i < 100 { + operand_a = i; + operand_b = i + 1; + alu_op = 0; + tick(); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/timing_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/timing_tb.t27 new file mode 100644 index 0000000000..5b169b6537 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/timing_tb.t27 @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/timing_tb.t27 +// Timing Analysis Testbench +// Tests setup/hold checks, slack computation, and clock tree constraints +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Timing_Testbench { + use fpga::timing::Timing; + + const CLK_PERIOD : u32 = 20; + const SIM_TIMEOUT : u32 = 5_000_000; + const TARGET_FMAX_MHZ : u32 = 50; + + var clk : bool = false; + var rst_n : bool = false; + var path_delay_ps : u32 = 0; + var setup_slack : i32 = 0; + var hold_slack : i32 = 0; + var fmax_mhz : u32 = 0; + var timing_met : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn compute_slack(delay_ps : u32, period_ps : u32) -> i32 { + return period_ps as i32 - delay_ps as i32; + } + + fn compute_fmax(critical_path_ps : u32) -> u32 { + if critical_path_ps == 0 { return 0; } + return 1_000_000_000 / critical_path_ps; + } + + test test_reset_state { + reset(); + invariant timing_met == false; + } + + test test_positive_slack { + var slack : i32 = compute_slack(5000, 20000); + invariant slack > 0; + invariant slack == 15000; + } + + test test_negative_slack { + var slack : i32 = compute_slack(25000, 20000); + invariant slack < 0; + } + + test test_zero_delay { + var slack : i32 = compute_slack(0, 20000); + invariant slack == 20000; + } + + test test_fmax_computation { + var fmax : u32 = compute_fmax(10000); + invariant fmax == 100; + } + + test test_target_fmax_achievable { + var crit_path : u32 = 15000; + var fmax : u32 = compute_fmax(crit_path); + invariant fmax >= TARGET_FMAX_MHZ; + } + + test test_slack_equal_period { + var slack : i32 = compute_slack(20000, 20000); + invariant slack == 0; + } + + invariant target_fmax_positive : TARGET_FMAX_MHZ > 0; + invariant clk_period_positive : CLK_PERIOD > 0; + + bench bench_timing_analysis { + reset(); + var delay : u32 = 1000; + var i : u32 = 0; + while i < 100 { + compute_slack(delay + i * 100, 20000); + compute_fmax(delay + i * 100); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/top_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/top_tb.t27 new file mode 100644 index 0000000000..5033794fe4 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/top_tb.t27 @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/top_tb.t27 +// Top-Level FPGA Testbench Specification +// Tests complete FPGA system with UART, SPI, MAC, and bridge +// 01 + 1/23 = 3 | TRINITY + +module Top_Level_Testbench { + // Import base types and submodules + use base::types; + use fpga::uart::UART_Bridge; + use fpga::spi::SPI_Master; + use fpga::mac::ZeroDSP_MAC; + use fpga::bridge::FPGA_Bridge; + + // 1. Testbench Configuration + + // Simulation timing + const TIMESCALE : str = "1ns/1ps"; + const CLK_PERIOD : u32 = 20; // 50 MHz = 20ns period + const SIM_TIMEOUT : u32 = 50_000_000; // 50ms simulation timeout + + // Protocol constants + const PING_CMD : u8 = 0x01; + const PONG_RESP : u8 = 0x02; + const STATUS_CMD : u8 = 0x30; + + // 2. Testbench Signals + + // Clock and reset + var clk : bool = false; + var rst_n : bool = false; + + // UART + var uart_tx : bool = true; + var uart_rx : bool = true; + + // SPI + var spi_cs : bool = true; + var spi_sck : bool = true; + var spi_mosi : bool = true; + var spi_miso : bool = true; + + // LEDs + var led : [4]bool = [true, true, true, true]; + + // MAC interface + var mac_a : [27]bool = [false; 27]; + var mac_b : [27]bool = [false; 27]; + var mac_acc : i32 = 0; + var mac_acc_out : i32 = 0; + var mac_valid : bool = false; + + // Test counters + var test_passed : u32 = 0; + var test_failed : u32 = 0; + var sim_cycle : u32 = 0; + + // 3. Test Helpers + + // generate_clock() -> void + // Generate 50 MHz clock + fn generate_clock() -> void { + clk = !clk; + sim_cycle = sim_cycle + 1; + } + + // wait_cycles(n: u32) -> void + // Wait n clock cycles + fn wait_cycles(n: u32) -> void { + var i : u32 = 0; + while (i < n) { + generate_clock(); + i = i + 1; + } + } + + // assert_pass(condition: bool, message: str) -> void + // Record test pass/fail + fn assert_pass(condition: bool, message: str) -> void { + if (condition) { + test_passed = test_passed + 1; + } else { + test_failed = test_failed + 1; + } + } + + // 4. Test Cases + + // test_ping_pong() -> void + // Test UART ping/pong command + fn test_ping_pong() -> void { + // Send PING command via UART + uart_tx = false; // Start bit + wait_cycles(CLK_PERIOD * 10); + assert_pass(true, "Ping/Pong test placeholder"); + } + + // test_led_heartbeat() -> void + // Test LED heartbeat blink + fn test_led_heartbeat() -> void { + wait_cycles(CLK_PERIOD * 100); + assert_pass(true, "LED heartbeat test"); + } + + // test_spi_loopback() -> void + // Test SPI loopback + fn test_spi_loopback() -> void { + spi_cs = false; + wait_cycles(CLK_PERIOD * 10); + spi_cs = true; + assert_pass(true, "SPI loopback test"); + } + + // test_mac_operation() -> void + // Test MAC multiply-accumulate + fn test_mac_operation() -> void { + mac_a = [true; 27]; + mac_b = [true; 27]; + mac_acc = 0; + wait_cycles(CLK_PERIOD * 20); + assert_pass(true, "MAC operation test"); + } + + // 5. Test Sequences + + // run_tests() -> void + // Run all top-level testbench sequences + fn run_tests() -> void { + print("t27 TOP-LEVEL FPGA TESTBENCH"); + print("phi^2 + 1/phi^2 = 3 | TRINITY"); + + // Apply reset + rst_n = false; + wait_cycles(10); + rst_n = true; + wait_cycles(10); + + print("[TEST 1] Ping/Pong"); + test_ping_pong(); + print(" [PASS]"); + + print("[TEST 2] LED Heartbeat"); + test_led_heartbeat(); + print(" [PASS]"); + + print("[TEST 3] SPI Loopback"); + test_spi_loopback(); + print(" [PASS]"); + + print("[TEST 4] MAC Operation"); + test_mac_operation(); + print(" [PASS]"); + + // Summary + print("Passed: ", test_passed); + print("Failed: ", test_failed); + if (test_failed == 0) { + print("STATUS: ALL TESTS PASSED"); + } else { + print("STATUS: SOME TESTS FAILED"); + } + } + + // TDD-Inside-Spec: Invariants for Top_Level_Testbench + + invariant tb_clk_period_correct + assert CLK_PERIOD == 20 + + invariant tb_timescale_defined + assert TIMESCALE == "1ns/1ps" + + invariant tb_sim_timeout_defined + assert SIM_TIMEOUT == 50_000_000 + + invariant tb_counter_bounds + assert test_passed < 1000 and test_failed < 1000 + + invariant tb_sim_cycle_increments + given old = sim_cycle + when generate_clock() + then sim_cycle == old + 1 + + test top_tb_reset_sequence + given rst_n = false + when wait_cycles(10) + and rst_n = true + and wait_cycles(10) + then test_passed >= 0 + + test top_tb_ping_pong_cmd + given PING_CMD = 0x01 + and PONG_RESP = 0x02 + then PING_CMD != PONG_RESP + + test top_tb_led_init_state + given led = [true, true, true, true] + then led[0] == true + + test top_tb_spi_cs_active_low + given spi_cs = false + when wait_cycles(CLK_PERIOD * 10) + and spi_cs = true + then spi_cs == true + + test top_tb_mac_interface_signals + given mac_a = [true; 27] + and mac_b = [true; 27] + and mac_acc = 0 + then mac_acc == 0 + + test top_tb_sim_timeout_reasonable + given SIM_TIMEOUT = 50_000_000 + then SIM_TIMEOUT > 0 + + bench tb_full_simulation_time + measure: cycles for run_tests() + target: < 500_000 +} \ No newline at end of file diff --git a/apps/website/public/t27/specs/fpga/testbench/uart_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/uart_tb.t27 new file mode 100644 index 0000000000..7a792c34bc --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/uart_tb.t27 @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/uart_tb.t27 +// UART Testbench Specification +// Tests UART TX/RX functionality, state machines, and timing +// 01 + 1/23 = 3 | TRINITY + +module UART_Testbench { + // Import base types and UART module + use base::types; + use fpga::uart::UART_Bridge; + + // 1. Testbench Configuration + + // Simulation timing + const TIMESCALE : str = "1ns/1ps"; + const CLK_PERIOD : u32 = 20; // 50 MHz = 20ns period + const SIM_TIMEOUT : u32 = 10_000_000; // 10ms simulation timeout + + // Test data patterns + const TEST_DATA_1 : u8 = 0xAA; + const TEST_DATA_2 : u8 = 0x55; + const TEST_DATA_3 : u8 = 0x00; + const TEST_DATA_4 : u8 = 0xFF; + + // 2. Testbench Signals + + // Clock and reset + var clk : bool = false; + var rst_n : bool = false; + + // UART signals + var uart_tx_line : bool = true; // TX output (idle high) + var uart_rx_line : bool = true; // RX input + + // Internal monitoring + var tx_busy : bool = false; + var rx_data_valid : bool = false; + var rx_data : u8 = 0; + + // Test counters + var test_passed : u32 = 0; + var test_failed : u32 = 0; + var sim_cycle : u32 = 0; + + // 3. Clock Generation + + // generate_clock() 588 void + // Generate 50 MHz clock + fn generate_clock() -> void { + clk = !clk; + sim_cycle = sim_cycle + 1; + } + + // 4. Test Helpers + + // assert_pass(condition: bool, message: str) 835 void + // Record test pass + fn assert_pass(condition: bool, message: str) -> void { + if (condition) { + test_passed = test_passed + 1; + } else { + test_failed = test_failed + 1; + } + } + + // wait_cycles(n: u32) 836 void + // Wait n clock cycles + fn wait_cycles(n: u32) -> void { + var i : u32 = 0; + while (i < n) { + generate_clock(); + i = i + 1; + } + } + + // wait_tx_ready() 837 void + // Wait until TX is ready + fn wait_tx_ready() -> void { + var timeout : u32 = 0; + while (!uart_tx_ready() && timeout < 1000) { + generate_clock(); + timeout = timeout + 1; + } + } + + // wait_rx_data() 838 (bool, u8) + // Wait for RX data, return (success, data) + fn wait_rx_data() -> (bool, u8) { + var timeout : u32 = 0; + while (!uart_rx_has_data() && timeout < 10000) { + generate_clock(); + timeout = timeout + 1; + } + if (uart_rx_has_data()) { + return (true, uart_rx_read_data()); + } else { + return (false, 0); + } + } + + // 5. Test Cases + + // test_uart_tx_byte(data: u8) 1079 void + // Test single byte transmission + fn test_uart_tx_byte(data: u8) -> void { + // Wait for ready + wait_tx_ready(); + + // Start transmission + const success = uart_tx_write(data); + assert_pass(success == true, "TX write success"); + + // Wait for completion + var timeout : u32 = 0; + while (uart_tx.tx_busy && timeout < 20000) { + generate_clock(); + timeout = timeout + 1; + } + + assert_pass(!uart_tx.tx_busy, "TX completed"); + } + + // test_uart_rx_byte(data: u8) 1080 void + // Test single byte reception + fn test_uart_rx_byte(data: u8) -> void { + // Simulate RX transmission + var bit_idx : u8 = 0; + + // Start bit + uart_rx_sync(false); + wait_cycles(BAUD_DIVISOR / 2); + + // Data bits + while (bit_idx < 8) { + const bit = (data >> bit_idx) & 1 == 1; + uart_rx_sync(bit); + wait_cycles(BAUD_DIVISOR); + bit_idx = bit_idx + 1; + } + + // Stop bit + uart_rx_sync(true); + wait_cycles(BAUD_DIVISOR); + + // Check received data + const (success, received) = wait_rx_data(); + assert_pass(success && received == data, "RX data match"); + } + + // test_uart_loopback() 1081 void + // Test TX -> RX loopback + fn test_uart_loopback() -> void { + const data = TEST_DATA_1; + + // Start TX + wait_tx_ready(); + uart_tx_write(data); + + // Connect TX to RX + var tx_count : u32 = 0; + while (uart_tx.tx_busy && tx_count < 20000) { + uart_rx_line = uart_tx_get_line(); + uart_rx_tick(); + uart_tx_tick(); + tx_count = tx_count + 1; + } + + // Check RX received data + const (success, received) = wait_rx_data(); + assert_pass(success && received == data, "Loopback data match"); + } + + // test_uart_framing_error() 1082 void + // Test framing error detection + fn test_uart_framing_error() -> void { + // Start bit + uart_rx_sync(false); + wait_cycles(BAUD_DIVISOR / 2); + + // Data bits + var bit_idx : u8 = 0; + while (bit_idx < 8) { + uart_rx_sync(bit_idx < 4); // Pattern + wait_cycles(BAUD_DIVISOR); + bit_idx = bit_idx + 1; + } + + // Stop bit LOW (error) + uart_rx_sync(false); + wait_cycles(BAUD_DIVISOR); + + // Check framing error + assert_pass(uart_rx_has_framing_error(), "Framing error detected"); + } + + // test_uart_reset() 1083 void + // Test UART reset + fn test_uart_reset() -> void { + // Start a transmission + uart_tx_write(TEST_DATA_1); + + // Apply reset + rst_n = false; + wait_cycles(10); + rst_n = true; + wait_cycles(10); + + // Check TX is ready + assert_pass(uart_tx_ready() && !uart_tx.tx_busy, "TX reset to ready"); + assert_pass(uart_tx_get_line() == true, "TX line idle high"); + } + + // test_uart_idle_line() 1084 void + // Test idle line state + fn test_uart_idle_line() -> void { + wait_tx_ready(); + assert_pass(uart_tx_get_line() == true, "Idle line high"); + } + + // test_uart_multiple_bytes() 1085 void + // Test multiple byte transmission + fn test_uart_multiple_bytes() -> void { + const bytes = [TEST_DATA_1, TEST_DATA_2, TEST_DATA_3, TEST_DATA_4]; + var i : usize = 0; + + while (i < bytes.len()) { + test_uart_tx_byte(bytes[i]); + i = i + 1; + } + + assert_pass(i == 4, "All bytes transmitted"); + } + + // test_uart_baud_rate_timing() 1086 void + // Test baud rate timing + fn test_uart_baud_rate_timing() -> void { + const data = TEST_DATA_1; + + // Record start cycle + const start_cycle = sim_cycle; + + // Transmit + test_uart_tx_byte(data); + + const cycles = sim_cycle - start_cycle; + const expected = (10 * BAUD_DIVISOR); // 10 bits @ baud rate + + assert_pass(cycles >= expected && cycles <= expected + 100, "Baud rate timing"); + } + + // 6. Test Sequences + + // run_tests() 1317 void + // Run all test sequences + fn run_tests() -> void { + print(" t27 UART TESTBENCH");; + print("1406 t27 UART TESTBENCH 1407"); + print(" 01 + 1/23 = 3 | TRINITY");; + print("1486 14871488 + 1/14891490 = 3 | TRINITY 1491"); + print(" Running test sequences...");; + + // Apply reset + rst_n = false; + wait_cycles(10); + rst_n = true; + wait_cycles(10); + + print("[TEST 1] UART TX byte transmission"); + test_uart_tx_byte(TEST_DATA_1); + print(" [PASS]"); + + print("[TEST 2] UART idle line"); + test_uart_idle_line(); + print(" [PASS]"); + + print("[TEST 3] UART multiple bytes"); + test_uart_multiple_bytes(); + print(" [PASS]"); + + print("[TEST 4] UART reset"); + test_uart_reset(); + print(" [PASS]"); + + print("[TEST 5] UART baud rate timing"); + test_uart_baud_rate_timing(); + print(" [PASS]"); + + print("[TEST 6] UART framing error"); + test_uart_framing_error(); + print(" [PASS]"); + + print("[TEST 7] UART loopback"); + test_uart_loopback(); + print(" [PASS]"); + + // Summary + print(" Simulation complete.");; + print("1654 SIMULATION RESULTS 1655"); + print(" Collecting results...");; + print("1732 Passed: ", test_passed, " 1733"); + print("1734 Failed: ", test_failed, " 1735"); + if (test_failed == 0) { + print("1736 STATUS: 1737 ALL TESTS PASSED 1738"); + } else { + print("1739 STATUS: 1740 SOME TESTS FAILED 1741"); + } + print(" Done.");; + } + + // TDD-Inside-Spec: Invariants for UART_Testbench + + invariant tb_clk_period_correct + assert CLK_PERIOD == 20 // 50MHz + + invariant tb_timescale_defined + assert TIMESCALE == "1ns/1ps" + + invariant tb_timeout_defined + assert SIM_TIMEOUT == 10_000_000 + + invariant tb_test_data_defined + assert TEST_DATA_1 == 0xAA and TEST_DATA_2 == 0x55 + + invariant tb_baud_divisor_matches_uart + assert BAUD_DIVISOR == 434 // 50MHz / 115200 + + invariant tb_counter_bounds + assert test_passed < 1000 and test_failed < 1000 + + invariant tb_sim_cycle_increments + given old = sim_cycle + when generate_clock() + then sim_cycle == old + 1 + + invariant tb_wait_cycles_increments_correctly + given old_cycle = sim_cycle + and wait_cycles(10) + then sim_cycle >= old_cycle + 10 + + invariant tb_initially_reset + assert test_passed == 0 and test_failed == 0 + + invariant tb_tx_line_idle_high + given uart_tx_ready() == true + then uart_tx_get_line() == true + + invariant tb_timeout_prevents_infinite_loop + assert true + + test uart_tb_tx_idle_high + given uart_tx_line = true + then uart_tx_line == true + + test uart_tb_rx_idle_high + given uart_rx_line = true + then uart_rx_line == true + + test uart_tb_test_data_patterns + given TEST_DATA_1 = 0xAA + and TEST_DATA_2 = 0x55 + and TEST_DATA_3 = 0x00 + and TEST_DATA_4 = 0xFF + then TEST_DATA_1 != TEST_DATA_2 + then TEST_DATA_3 != TEST_DATA_4 + + test uart_tb_clk_period_valid + given CLK_PERIOD = 20 + then CLK_PERIOD > 0 + + test uart_tb_sim_timeout_valid + given SIM_TIMEOUT = 10_000_000 + then SIM_TIMEOUT > 0 + + test uart_tb_counters_start_zero + given test_passed = 0 + and test_failed = 0 + and sim_cycle = 0 + then test_passed == 0 and test_failed == 0 + + test uart_tb_all_test_data_distinct + given patterns = [0xAA, 0x55, 0x00, 0xFF] + then patterns[0] != patterns[1] + then patterns[2] != patterns[3] + + bench tb_full_simulation_time + measure: cycles for run_tests() + target: < 1_000_000 + + bench tb_tx_byte_cycles + measure: cycles to test_uart_tx_byte(0xAA) + target: < 10_000 + + bench tb_rx_byte_cycles + measure: cycles to test_uart_rx_byte(0x55) + target: < 10_000 +} diff --git a/apps/website/public/t27/specs/fpga/testbench/vcd_conformance_compare_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/vcd_conformance_compare_tb.t27 new file mode 100644 index 0000000000..b52f121c51 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/vcd_conformance_compare_tb.t27 @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/vcd_conformance_compare_tb.t27 +// VCD Conformance Compare Testbench +// Tests the conformance comparison engine: batch compare, masking, value extraction +// phi^2 + 1/phi^2 = 3 | TRINITY + +module VcdConformanceCompare_Testbench { + use fpga::vcd_conformance_compare::VcdConformanceCompare; + + const CLK_PERIOD : u32 = 20; + const MAX_COMPARES : u32 = 128; + + var clk : bool = false; + var rst_n : bool = false; + var compare_en : bool = false; + var total_checks : u32 = 0; + var pass_count : u32 = 0; + var fail_count : u32 = 0; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + total_checks = 0; + pass_count = 0; + fail_count = 0; + } + + fn run_compare(vcd: [u32], vcd_count: u32, expected_vals: [u32], expected_count: u32) -> u32 { + var matches : u32 = 0; + var i : u32 = 0; + while i < expected_count { + if i < vcd_count and vcd[i] == expected_vals[i] { + matches = matches + 1; + } + i = i + 1; + } + return matches; + } + + test test_reset_clears_state { + reset(); + invariant total_checks == 0; + invariant pass_count == 0; + invariant fail_count == 0; + } + + test test_run_compare_all_match { + given vcd = [10, 20, 30] + and expected = [10, 20, 30] + then run_compare(vcd, 3, expected, 3) == 3 + } + + test test_run_compare_partial_match { + given vcd = [10, 20, 30] + and expected = [10, 99, 30] + then run_compare(vcd, 3, expected, 3) == 2 + } + + test test_run_compare_no_match { + given vcd = [10, 20] + and expected = [99, 88] + then run_compare(vcd, 2, expected, 2) == 0 + } + + test test_run_compare_empty { + then run_compare([], 0, [], 0) == 0 + } + + test test_run_compare_vcd_shorter { + given vcd = [10] + and expected = [10, 20] + then run_compare(vcd, 1, expected, 2) == 1 + } + + test test_max_compares { + invariant MAX_COMPARES == 128; + } + + test test_clk_period { + invariant CLK_PERIOD == 20; + } + + invariant max_compares_positive : MAX_COMPARES > 0; + + bench bench_vcd_compare { + reset(); + compare_en = true; + var i : u32 = 0; + while i < 100 { + tick(); + i = i + 1; + } + compare_en = false; + } +} diff --git a/apps/website/public/t27/specs/fpga/testbench/vcd_trace_tb.t27 b/apps/website/public/t27/specs/fpga/testbench/vcd_trace_tb.t27 new file mode 100644 index 0000000000..b5a42a5dcf --- /dev/null +++ b/apps/website/public/t27/specs/fpga/testbench/vcd_trace_tb.t27 @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/testbench/vcd_trace_tb.t27 +// VCD Trace Testbench +// Tests waveform dump generation, signal hierarchy, and timestamp management +// phi^2 + 1/phi^2 = 3 | TRINITY + +module VCD_Trace_Testbench { + use fpga::vcd_trace::VcdTrace; + + const CLK_PERIOD : u32 = 20; + const MAX_SIGNALS : u32 = 256; + const TIMESTAMP_RES_PS : u32 = 10; + + var clk : bool = false; + var rst_n : bool = false; + var trace_en : bool = false; + var signal_count : u32 = 0; + var timestamp_ps : u32 = 0; + var dump_complete : bool = false; + + var test_passed : u32 = 0; + var test_failed : u32 = 0; + + fn tick() { + clk = false; + clk = true; + timestamp_ps = timestamp_ps + TIMESTAMP_RES_PS; + } + + fn reset() { + rst_n = false; + tick(); + tick(); + rst_n = true; + tick(); + } + + fn advance_time(steps : u32) -> u32 { + var i : u32 = 0; + while i < steps { + tick(); + i = i + 1; + } + return timestamp_ps; + } + + fn format_timestamp(ps : u32) -> u32 { + return ps / 1000; + } + + test test_reset_state { + reset(); + invariant trace_en == false; + invariant dump_complete == false; + } + + test test_timestamp_advance { + timestamp_ps = 0; + var t : u32 = advance_time(10); + invariant t == 100; + } + + test test_format_timestamp { + var ns : u32 = format_timestamp(10000); + invariant ns == 10; + } + + test test_max_signals { + invariant MAX_SIGNALS == 256; + } + + test test_timestamp_resolution { + invariant TIMESTAMP_RES_PS == 10; + } + + invariant max_signals_positive : MAX_SIGNALS > 0; + invariant resolution_positive : TIMESTAMP_RES_PS > 0; + + bench bench_vcd_trace { + reset(); + trace_en = true; + advance_time(100); + trace_en = false; + } +} diff --git a/apps/website/public/t27/specs/fpga/timing.t27 b/apps/website/public/t27/specs/fpga/timing.t27 new file mode 100644 index 0000000000..a37735c8bb --- /dev/null +++ b/apps/website/public/t27/specs/fpga/timing.t27 @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/timing.t27 +// T27 Static Timing Analysis Specification +// Estimates critical path, slack, and Fmax from HIR module structure +// Artix-7 timing model: LUT=0.1ns, BRAM=2.0ns, DSP=2.5ns, routing=0.3ns +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Timing { + + // === Timing arc kind === + + pub const ArcKind = enum(i8) { + comb = 0, + reg_to_reg = 1, + reg_to_output = 2, + input_to_reg = 3, + input_to_output = 4, + } + + // === Timing arc === + + pub struct TimingArc { + source : &str, + sink : &str, + delay_ps : u32, + kind : i8, + } + + fn comb_arc(source: &str, sink: &str, delay_ps: u32) -> TimingArc { + return TimingArc{ + .source = source, + .sink = sink, + .delay_ps = delay_ps, + .kind = 0, + }; + } + + fn reg_to_reg(source: &str, sink: &str, delay_ps: u32) -> TimingArc { + return TimingArc{ + .source = source, + .sink = sink, + .delay_ps = delay_ps, + .kind = 1, + }; + } + + fn input_to_reg(source: &str, sink: &str, delay_ps: u32) -> TimingArc { + return TimingArc{ + .source = source, + .sink = sink, + .delay_ps = delay_ps, + .kind = 3, + }; + } + + // === Timing path === + + pub struct TimingPath { + startpoint : &str, + endpoint : &str, + total_delay_ps : u32, + slack_ps : i64, + num_arcs : u32, + } + + fn timing_path(start: &str, end: &str, delay: u32, slack: i64) -> TimingPath { + return TimingPath{ + .startpoint = start, + .endpoint = end, + .total_delay_ps = delay, + .slack_ps = slack, + .num_arcs = 1, + }; + } + + fn is_met(path: TimingPath) -> bool { + return path.slack_ps >= 0; + } + + fn is_violated(path: TimingPath) -> bool { + return path.slack_ps < 0; + } + + // === Timing constraint === + + pub struct TimingConstraint { + name : &str, + period_ps : u32, + clock_name : &str, + } + + fn clock_constraint(name: &str, period_ps: u32) -> TimingConstraint { + return TimingConstraint{ + .name = name, + .period_ps = period_ps, + .clock_name = "clk", + }; + } + + fn clock_mhz(name: &str, mhz: u32) -> TimingConstraint { + if mhz == 0 { + return clock_constraint(name, 10000); + } + return TimingConstraint{ + .name = name, + .period_ps = 1000000000 / mhz, + .clock_name = "clk", + }; + } + + // === Timing report === + + pub struct TimingReport { + total_paths : u32, + met_paths : u32, + violated_paths : u32, + worst_slack_ps : i64, + critical_path_ps : u32, + fmax_mhz : u32, + has_violations : bool, + } + + fn timing_ok(critical_ps: u32, fmax: u32) -> TimingReport { + return TimingReport{ + .total_paths = 1, + .met_paths = 1, + .violated_paths = 0, + .worst_slack_ps = 5000, + .critical_path_ps = critical_ps, + .fmax_mhz = fmax, + .has_violations = false, + }; + } + + fn timing_fail(critical_ps: u32) -> TimingReport { + return TimingReport{ + .total_paths = 1, + .met_paths = 0, + .violated_paths = 1, + .worst_slack_ps = -1000, + .critical_path_ps = critical_ps, + .fmax_mhz = 0, + .has_violations = true, + }; + } + + fn passed(report: TimingReport) -> bool { + return report.has_violations == false; + } + + // === Timing model constants === + + fn lut_delay_ps() -> u32 { + return 100; + } + + fn bram_delay_ps() -> u32 { + return 2000; + } + + fn dsp_delay_ps() -> u32 { + return 2500; + } + + fn routing_delay_ps() -> u32 { + return 300; + } + + fn setup_time_ps() -> u32 { + return 200; + } + + fn hold_time_ps() -> u32 { + return 50; + } + + // === Query functions === + + fn path_delay(arcs: [TimingArc], count: u32) -> u32 { + var total : u32 = 0; + var i : u32 = 0; + while i < count { + total = total + arcs[i].delay_ps; + i = i + 1; + } + return total; + } + + fn slack(delay_ps: u32, constraint_ps: u32) -> i64 { + return constraint_ps as i64 - delay_ps as i64; + } + + fn fmax_from_delay(delay_ps: u32) -> u32 { + if delay_ps == 0 { + return 0; + } + return 1000000000 / delay_ps; + } + + fn est_comb_delay(num_luts: u32) -> u32 { + return num_luts * lut_delay_ps() + routing_delay_ps(); + } + + fn est_reg_to_reg_delay(num_luts: u32) -> u32 { + return num_luts * lut_delay_ps() + routing_delay_ps() + setup_time_ps(); + } + + fn worst_path(paths: [TimingPath], count: u32) -> u32 { + if count == 0 { + return 0; + } + var worst : u32 = paths[0].total_delay_ps; + var i : u32 = 1; + while i < count { + if paths[i].total_delay_ps > worst { + worst = paths[i].total_delay_ps; + } + i = i + 1; + } + return worst; + } + + // === Validation === + + fn validate_constraint(tc: TimingConstraint) -> u32 { + var errors : u32 = 0; + if tc.name == "" { + errors = errors + 1; + } + if tc.period_ps == 0 { + errors = errors + 1; + } + return errors; + } + + fn validate_arc(arc: TimingArc) -> u32 { + var errors : u32 = 0; + if arc.source == "" { + errors = errors + 1; + } + if arc.sink == "" { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test comb_arc_creation + given a = comb_arc("a", "b", 500) + then a.source == "a" + and a.sink == "b" + and a.delay_ps == 500 + and a.kind == 0 + + test reg_to_reg_creation + given a = reg_to_reg("r1", "r2", 800) + then a.kind == 1 + + test input_to_reg_creation + given a = input_to_reg("din", "r1", 400) + then a.kind == 3 + + test timing_path_met + given p = timing_path("r1", "r2", 5000, 5000) + then is_met(p) == true + and is_violated(p) == false + + test timing_path_violated + given p = timing_path("r1", "r2", 12000, -2000) + then is_met(p) == false + and is_violated(p) == true + + test clock_constraint_creation + given c = clock_constraint("clk_fast", 5000) + then c.period_ps == 5000 + and c.clock_name == "clk" + + test clock_mhz_creation + given c = clock_mhz("clk_100", 100) + then c.period_ps == 10000000 + and c.name == "clk_100" + + test clock_mhz_zero + given c = clock_mhz("bad", 0) + then c.period_ps == 10000 + + test timing_ok_report + given r = timing_ok(5000, 200) + then r.critical_path_ps == 5000 + and r.fmax_mhz == 200 + and passed(r) == true + + test timing_fail_report + given r = timing_fail(15000) + then r.has_violations == true + and passed(r) == false + + test path_delay_calc + given a1 = comb_arc("a", "b", 100) + and a2 = comb_arc("b", "c", 200) + and a3 = comb_arc("c", "d", 300) + then path_delay([a1, a2, a3], 3) == 600 + + test slack_positive + then slack(5000, 10000) == 5000 + + test slack_negative + then slack(15000, 10000) == -5000 + + test fmax_from_delay + then fmax_from_delay(5000) == 200000 + + test fmax_zero_delay + then fmax_from_delay(0) == 0 + + test est_comb_delay + then est_comb_delay(3) == 600 + + test est_reg_to_reg_delay + then est_reg_to_reg_delay(3) == 800 + + test worst_path + given p1 = timing_path("a", "b", 500, 0) + and p2 = timing_path("c", "d", 1200, 0) + and p3 = timing_path("e", "f", 800, 0) + then worst_path([p1, p2, p3], 3) == 1200 + + test worst_path_empty + then worst_path([], 0) == 0 + + test validate_constraint_ok + given c = clock_constraint("clk", 10000) + then validate_constraint(c) == 0 + + test validate_constraint_empty_name + given c = TimingConstraint{.name = "", .period_ps = 10000, .clock_name = "clk"} + then validate_constraint(c) > 0 + + test validate_arc_ok + given a = comb_arc("a", "b", 100) + then validate_arc(a) == 0 + + test validate_arc_empty_source + given a = TimingArc{.source = "", .sink = "b", .delay_ps = 100, .kind = 0} + then validate_arc(a) > 0 + + test timing_model_constants + then lut_delay_ps() == 100 + and bram_delay_ps() == 2000 + and dsp_delay_ps() == 2500 + and routing_delay_ps() == 300 + and setup_time_ps() == 200 + + // === Invariants === + + invariant slack_consistent_with_fmax + given d = 5000 + and s = slack(d, 10000) + assert s >= 0 + + invariant timing_constants_positive + assert lut_delay_ps() > 0 + and bram_delay_ps() > lut_delay_ps() + and dsp_delay_ps() > lut_delay_ps() + + // === Benchmarks === + + bench timing_analysis + measure: nanoseconds for est_reg_to_reg_delay(10) + target: < 50ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/top_level.t27 b/apps/website/public/t27/specs/fpga/top_level.t27 new file mode 100644 index 0000000000..6a33ad0301 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/top_level.t27 @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/top_level.t27 +// ZeroDSP FPGA Top Level Module +// Integrates MAC and UART for FPGA deployment +// 01 + 1/23 = 3 | TRINITY + +module ZeroDSP_TopLevel { + use base::types; + use base::ops; + use isa::registers; + + const CLK_FREQ_HZ : u32 = 100_000_000; + const SYSTICK_HZ : u32 = 1000; + + const NUM_MAC_UNITS : usize = 8; + const DATA_WIDTH : usize = 32; + + const CMD_NOP : u8 = 0; + const CMD_MAC_MULT : u8 = 1; + const CMD_MAC_DOT : u8 = 2; + const CMD_UART_SEND : u8 = 3; + const CMD_RESET : u8 = 0xFF; + + struct SystemState { + mac_ready : bool, + uart_ready : bool, + processing : bool, + error : bool, + } + + var system_state : SystemState = SystemState{ + .mac_ready = true, + .uart_ready = true, + .processing = false, + .error = false, + }; + + var mac_result : i32 = 0; + var uart_tx_data : u8 = 0; + + fn system_init() -> void { + system_state.mac_ready = true; + system_state.uart_ready = true; + system_state.processing = false; + system_state.error = false; + } + + fn system_ready() -> bool { + return system_state.mac_ready and system_state.uart_ready; + } + + fn system_busy() -> bool { + return system_state.processing; + } + + fn system_error() -> bool { + return system_state.error; + } + + fn system_reset() -> void { + system_init(); + mac_result = 0; + uart_tx_data = 0; + } + + fn set_mac_result(value: i32) -> void { + mac_result = value; + system_state.processing = false; + } + + fn get_mac_result() -> i32 { + return mac_result; + } + + fn set_uart_data(data: u8) -> void { + uart_tx_data = data; + } + + fn get_uart_data() -> u8 { + return uart_tx_data; + } + + fn start_processing() -> void { + if (system_ready()) { + system_state.processing = true; + } + } + + fn stop_processing() -> void { + system_state.processing = false; + } + + fn set_error() -> void { + system_state.error = true; + system_state.processing = false; + } + + fn clear_error() -> void { + system_state.error = false; + } + + test system_initially_ready + given ready = system_ready() + then ready == true + + test system_initially_not_busy + given busy = system_busy() + then busy == false + + test system_initially_no_error + given error = system_error() + then error == false + + test system_reset_clears_state + given set_error() + and system_reset() + and ready = system_ready() + and error = system_error() + then ready == true and error == false + + test start_processing_sets_busy + given start_processing() + and busy = system_busy() + then busy == true + + test stop_processing_clears_busy + given start_processing() + and stop_processing() + and busy = system_busy() + then busy == false + + test set_error_clears_busy + given start_processing() + and set_error() + and busy = system_busy() + and error = system_error() + then busy == false and error == true + + test get_mac_result_after_set + given set_mac_result(42) + and result = get_mac_result() + then result == 42 + + test get_uart_data_after_set + given set_uart_data(0xAA) + and data = get_uart_data() + then data == 0xAA + + test mac_result_clears_processing + given start_processing() + and set_mac_result(100) + and busy = system_busy() + then busy == false + + test constants_clk_freq + then CLK_FREQ_HZ == 100_000_000 + and SYSTICK_HZ == 1000 + and NUM_MAC_UNITS == 8 + and DATA_WIDTH == 32 + + test command_constants + then CMD_NOP == 0 + and CMD_MAC_MULT == 1 + and CMD_MAC_DOT == 2 + and CMD_UART_SEND == 3 + and CMD_RESET == 0xFF + + test system_reset_clears_mac_result + given set_mac_result(999) + and system_reset() + then get_mac_result() == 0 + + test system_reset_clears_uart_data + given set_uart_data(0xFF) + and system_reset() + then get_uart_data() == 0 + + test clear_error_does_not_affect_ready + given set_error() + and clear_error() + then system_error() == false + and system_ready() == true + + test start_processing_requires_ready + given system_state.mac_ready = false + then system_ready() == false + + test set_mac_result_negative + given set_mac_result(-42) + then get_mac_result() == -42 + + test set_uart_data_boundary + given set_uart_data(0) + then get_uart_data() == 0 + given set_uart_data(255) + then get_uart_data() == 255 + + invariant system_ready_when_not_processing + given busy = system_busy() + and ready = system_ready() + assert busy == false implies ready == true + + invariant system_error_implies_not_busy + given error = system_error() + and busy = system_busy() + assert error == true implies busy == false + + invariant system_ready_implies_mac_uart_ready + given ready = system_ready() + assert ready == true implies system_state.mac_ready == true + + bench system_ready_latency + measure: nanoseconds to system_ready() + target: < 20ns + + bench system_reset_latency + measure: nanoseconds to system_reset() + target: < 100ns +} diff --git a/apps/website/public/t27/specs/fpga/uart.t27 b/apps/website/public/t27/specs/fpga/uart.t27 new file mode 100644 index 0000000000..acff1fa899 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/uart.t27 @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/uart.t27 +// ZeroDSP FPGA UART Specification +// UART for debugging and communication +// 01 + 1/23 = 3 | TRINITY + +module ZeroDSP_UART { + use base::types; + use base::ops; + use isa::registers; + + const UART_CLOCK_HZ : u32 = 100_000_000; + const UART_BAUD_RATE : u32 = 115200; + const UART_BIT_PERIOD : u32 = UART_CLOCK_HZ / UART_BAUD_RATE; + + const UART_WIDTH : usize = 8; + const UART_FIFO_DEPTH : usize = 16; + + const STATUS_IDLE : u8 = 0; + const STATUS_TX_BUSY : u8 = 1; + const STATUS_RX_BUSY : u8 = 2; + const STATUS_ERROR : u8 = 3; + + struct UARTState { + tx_data : u8, + tx_valid : bool, + tx_ready : bool, + rx_data : u8, + rx_valid : bool, + rx_error : bool, + bit_counter : u8, + status : u8, + } + + var uart_state : UARTState = UARTState{ + .tx_data = 0, + .tx_valid = false, + .tx_ready = true, + .rx_data = 0, + .rx_valid = false, + .rx_error = false, + .bit_counter = 0, + .status = STATUS_IDLE, + }; + + struct UARTConfig { + baud_divisor : u32, + parity_enable : bool, + stop_bits : u8, + fifo_enable : bool, + } + + var uart_config : UARTConfig = UARTConfig{ + .baud_divisor = UART_CLOCK_HZ / (UART_BAUD_RATE * 16), + .parity_enable = false, + .stop_bits = 1, + .fifo_enable = true, + }; + + fn uart_tx_ready() -> bool { + return uart_state.tx_ready; + } + + fn uart_tx_send(data: u8) -> bool { + if (!uart_state.tx_ready) { + return false; + } + uart_state.tx_data = data; + uart_state.tx_valid = true; + uart_state.tx_ready = false; + uart_state.status = STATUS_TX_BUSY; + return true; + } + + fn uart_rx_ready() -> bool { + return uart_state.rx_valid; + } + + fn uart_rx_read() -> u8 { + uart_state.rx_valid = false; + return uart_state.rx_data; + } + + fn uart_status() -> u8 { + return uart_state.status; + } + + fn uart_reset() -> void { + uart_state.tx_data = 0; + uart_state.tx_valid = false; + uart_state.tx_ready = true; + uart_state.rx_data = 0; + uart_state.rx_valid = false; + uart_state.rx_error = false; + uart_state.bit_counter = 0; + uart_state.status = STATUS_IDLE; + } + + fn uart_configure( + baud_divisor: u32, + parity_enable: bool, + stop_bits: u8, + fifo_enable: bool, + ) -> void { + uart_config.baud_divisor = baud_divisor; + uart_config.parity_enable = parity_enable; + uart_config.stop_bits = stop_bits; + uart_config.fifo_enable = fifo_enable; + } + + test uart_initially_idle + given status = uart_status() + then status == STATUS_IDLE + + test uart_tx_ready_initially + given ready = uart_tx_ready() + then ready == true + + test uart_rx_not_valid_initially + given valid = uart_rx_ready() + then valid == false + + test uart_tx_send_returns_true_when_ready + given result = uart_tx_send(0x55) + then result == true + + test uart_tx_send_returns_false_when_busy + given uart_tx_send(0x55) + and result = uart_tx_send(0xAA) + then result == false + + test uart_reset_clears_status + given uart_tx_send(0x55) + and uart_reset() + and status = uart_status() + then status == STATUS_IDLE + + test uart_reset_restores_tx_ready + given uart_tx_send(0x55) + and uart_reset() + and ready = uart_tx_ready() + then ready == true + + test uart_configure_changes_baud_divisor + given uart_configure(100, false, 1, true) + then uart_config.baud_divisor == 100 + + test uart_configure_parity_enable + given uart_configure(54, true, 2, false) + then uart_config.parity_enable == true + and uart_config.stop_bits == 2 + and uart_config.fifo_enable == false + + test uart_bit_period_calc + then UART_BIT_PERIOD == UART_CLOCK_HZ / UART_BAUD_RATE + + test uart_constants + then UART_FIFO_DEPTH == 16 + and UART_WIDTH == 8 + and STATUS_IDLE == 0 + and STATUS_TX_BUSY == 1 + and STATUS_RX_BUSY == 2 + and STATUS_ERROR == 3 + + test uart_tx_send_updates_state + given result = uart_tx_send(0x42) + then result == true + and uart_state.tx_data == 0x42 + and uart_state.tx_valid == true + and uart_state.tx_ready == false + and uart_state.status == STATUS_TX_BUSY + + test uart_rx_read_clears_valid + uart_state.rx_data = 0x99; + uart_state.rx_valid = true; + given data = uart_rx_read() + then data == 0x99 + and uart_state.rx_valid == false + + test uart_reset_clears_rx_error + uart_state.rx_error = true; + given uart_reset() + then uart_state.rx_error == false + + test uart_reset_clears_bit_counter + uart_state.bit_counter = 7; + given uart_reset() + then uart_state.bit_counter == 0 + + invariant uart_status_valid + given status = uart_status() + assert status == STATUS_IDLE or status == STATUS_TX_BUSY or + status == STATUS_RX_BUSY or status == STATUS_ERROR + + invariant uart_tx_ready_inverse_tx_busy + given status = uart_status() + assert (uart_state.tx_ready) == (status == STATUS_IDLE) + + bench uart_tx_ready_latency + measure: nanoseconds to uart_tx_ready() + target: < 10ns + + bench uart_rx_ready_latency + measure: nanoseconds to uart_rx_ready() + target: < 10ns + + bench uart_reset_latency + measure: nanoseconds to uart_reset() + target: < 50ns +} diff --git a/apps/website/public/t27/specs/fpga/vcd_conformance_compare.t27 b/apps/website/public/t27/specs/fpga/vcd_conformance_compare.t27 new file mode 100644 index 0000000000..010f544351 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/vcd_conformance_compare.t27 @@ -0,0 +1,435 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/vcd_conformance_compare.t27 +// T27 VCD Conformance Comparison Engine +// Compares VCD simulation traces against conformance vectors +// Parses VCD signal values and checks them against expected results +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module VcdConformanceCompare { + + // === Comparison result === + + pub struct CompareResult { + total_checks : u32, + passed : u32, + failed : u32, + errors : u32, + } + + fn compare_result() -> CompareResult { + return CompareResult{ + .total_checks = 0, + .passed = 0, + .failed = 0, + .errors = 0, + }; + } + + fn result_ok() -> bool { + var r = compare_result(); + return r.failed == 0 and r.errors == 0; + } + + fn record_pass(r: *CompareResult) { + r.total_checks = r.total_checks + 1; + r.passed = r.passed + 1; + } + + fn record_fail(r: *CompareResult) { + r.total_checks = r.total_checks + 1; + r.failed = r.failed + 1; + } + + fn record_error(r: *CompareResult) { + r.total_checks = r.total_checks + 1; + r.errors = r.errors + 1; + } + + fn all_passed(r: CompareResult) -> bool { + return r.failed == 0 and r.errors == 0 and r.total_checks > 0; + } + + // === Signal reference (VCD ident -> conformance field) === + + pub struct SignalRef { + vcd_ident : &str, + signal_name : &str, + bit_width : u32, + } + + fn signal_ref(ident: &str, name: &str, width: u32) -> SignalRef { + return SignalRef{ + .vcd_ident = ident, + .signal_name = name, + .bit_width = width, + }; + } + + // === Expected value at a checkpoint === + + pub struct ExpectedValue { + signal_name : &str, + cycle_offset : u32, + expected : u32, + mask : u32, + } + + fn expected_value(sig: &str, cycle: u32, value: u32) -> ExpectedValue { + return ExpectedValue{ + .signal_name = sig, + .cycle_offset = cycle, + .expected = value, + .mask = 0xFFFFFFFF, + }; + } + + fn expected_value_masked(sig: &str, cycle: u32, value: u32, mask: u32) -> ExpectedValue { + return ExpectedValue{ + .signal_name = sig, + .cycle_offset = cycle, + .expected = value, + .mask = mask, + }; + } + + // === VCD value extraction (simulated) === + + fn extract_value(vcd_changes: [u32], change_count: u32, cycle: u32) -> u32 { + if cycle < change_count { + return vcd_changes[cycle]; + } + return 0; + } + + // === Single comparison === + + fn compare_value(actual: u32, expected: ExpectedValue) -> bool { + if expected.mask == 0xFFFFFFFF { + return actual == expected.expected; + } + return (actual & expected.mask) == (expected.expected & expected.mask); + } + + // === Batch comparison === + + fn compare_batch( + vcd_values: [u32], + vcd_count: u32, + expected: [ExpectedValue], + expected_count: u32, + ) -> CompareResult { + var r = compare_result(); + var i : u32 = 0; + while i < expected_count { + var actual = extract_value(vcd_values, vcd_count, expected[i].cycle_offset); + if compare_value(actual, expected[i]) { + record_pass(&r); + } else { + record_fail(&r); + } + i = i + 1; + } + return r; + } + + // === Conformance vector mapping === + + fn map_uart_tx_vector(data: u32, expected_bits: [u32], bit_count: u32) -> u32 { + var match_count : u32 = 0; + var i : u32 = 0; + while i < bit_count { + var bit_val = (data >> i) & 1; + if i < bit_count and bit_val == expected_bits[i] { + match_count = match_count + 1; + } + i = i + 1; + } + return match_count; + } + + fn map_mac_cycle_result(a_trits: [u32], b_trits: [u32], len: u32, initial_acc: u32) -> u32 { + var acc = initial_acc; + var i : u32 = 0; + while i < len { + acc = acc + a_trits[i] * b_trits[i]; + i = i + 1; + } + return acc; + } + + fn map_spi_transfer_bits(tx_data: u32, width: u32) -> u32 { + var bits : u32 = 0; + var i : u32 = 0; + while i < width { + var bit_val = (tx_data >> (width - 1 - i)) & 1; + bits = bits | (bit_val << i); + i = i + 1; + } + return bits; + } + + fn map_led_output(mask: u32) -> u32 { + return mask & 0xFF; + } + + // === VCD header parsing (simulated) === + + fn parse_timescale_ps(ts_str: &str) -> u32 { + if ts_str == "1 ps" { return 1; } + if ts_str == "10 ps" { return 10; } + if ts_str == "100 ps" { return 100; } + if ts_str == "1 ns" { return 1000; } + if ts_str == "10 ns" { return 10000; } + if ts_str == "100 ns" { return 100000; } + if ts_str == "1 us" { return 1000000; } + return 1000; + } + + fn cycle_to_timestamp_ps(cycle: u32, period_ns: u32) -> u64 { + var period_ps : u64 = period_ns * 1000; + return cycle * period_ps; + } + + fn timestamp_to_cycle(ts_ps: u64, period_ns: u32) -> u32 { + if period_ns == 0 { return 0; } + var period_ps : u64 = period_ns * 1000; + return ts_ps / period_ps; + } + + // === Validation === + + fn validate_signal_ref(ref: SignalRef) -> u32 { + var errors : u32 = 0; + if ref.vcd_ident == "" { errors = errors + 1; } + if ref.signal_name == "" { errors = errors + 1; } + if ref.bit_width == 0 { errors = errors + 1; } + return errors; + } + + fn validate_expected(ev: ExpectedValue) -> u32 { + var errors : u32 = 0; + if ev.signal_name == "" { errors = errors + 1; } + return errors; + } + + // === Tests === + + test compare_result_init { + given r = compare_result() + then r.total_checks == 0 + and r.passed == 0 + and r.failed == 0 + and r.errors == 0 + } + + test record_pass_increments { + var r = compare_result(); + record_pass(&r); + invariant r.total_checks == 1; + invariant r.passed == 1; + } + + test record_fail_increments { + var r = compare_result(); + record_fail(&r); + invariant r.total_checks == 1; + invariant r.failed == 1; + } + + test record_error_increments { + var r = compare_result(); + record_error(&r); + invariant r.total_checks == 1; + invariant r.errors == 1; + } + + test all_passed_true_when_no_failures { + var r = compare_result(); + record_pass(&r); + record_pass(&r); + invariant all_passed(r) == true; + } + + test all_passed_false_when_failures { + var r = compare_result(); + record_pass(&r); + record_fail(&r); + invariant all_passed(r) == false; + } + + test signal_ref_creation { + given s = signal_ref("!", "led_out", 8) + then s.vcd_ident == "!" + and s.signal_name == "led_out" + and s.bit_width == 8 + } + + test expected_value_creation { + given e = expected_value("led_out", 5, 170) + then e.signal_name == "led_out" + and e.cycle_offset == 5 + and e.expected == 170 + and e.mask == 0xFFFFFFFF + } + + test expected_value_masked { + given e = expected_value_masked("status", 10, 3, 0x0F) + then e.mask == 0x0F + } + + test compare_value_exact_match { + given e = expected_value("led", 0, 42) + then compare_value(42, e) == true + } + + test compare_value_exact_mismatch { + given e = expected_value("led", 0, 42) + then compare_value(43, e) == false + } + + test compare_value_masked_match { + given e = expected_value_masked("status", 0, 3, 0x0F) + then compare_value(0x83, e) == true + } + + test compare_value_masked_mismatch { + given e = expected_value_masked("status", 0, 3, 0x0F) + then compare_value(0x84, e) == false + } + + test extract_value_within_range { + given values = [10, 20, 30] + then extract_value(values, 3, 1) == 20 + } + + test extract_value_out_of_range { + given values = [10, 20] + then extract_value(values, 2, 5) == 0 + } + + test compare_batch_all_pass { + given vcd = [0, 255, 170] + and e1 = expected_value("led", 0, 0) + and e2 = expected_value("led", 1, 255) + and e3 = expected_value("led", 2, 170) + and expected = [e1, e2, e3] + given r = compare_batch(vcd, 3, expected, 3) + then r.passed == 3 + and r.failed == 0 + } + + test compare_batch_mixed { + given vcd = [0, 255, 100] + and e1 = expected_value("led", 0, 0) + and e2 = expected_value("led", 1, 254) + and e3 = expected_value("led", 2, 100) + and expected = [e1, e2, e3] + given r = compare_batch(vcd, 3, expected, 3) + then r.passed == 2 + and r.failed == 1 + } + + test map_uart_tx_0x55 { + given bits = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1] + then map_uart_tx_vector(0x55, bits, 10) > 0 + } + + test map_mac_cycle_simple { + given a = [1, 1] + and b = [1, 1] + then map_mac_cycle_result(a, b, 2, 0) == 2 + } + + test map_mac_cycle_with_initial { + given a = [1] + and b = [1] + then map_mac_cycle_result(a, b, 1, 5) == 6 + } + + test map_spi_transfer_8bit { + then map_spi_transfer_bits(0xAA, 8) > 0 + } + + test map_led_output_mask { + then map_led_output(255) == 255 + } + + test map_led_output_truncate { + then map_led_output(0x1FF) == 0xFF + } + + test parse_timescale_ps_common { + then parse_timescale_ps("1 ps") == 1 + and parse_timescale_ps("1 ns") == 1000 + and parse_timescale_ps("10 ns") == 10000 + } + + test cycle_to_timestamp_ps_basic { + then cycle_to_timestamp_ps(5, 20) == 100000 + } + + test timestamp_to_cycle_basic { + then timestamp_to_cycle(100000, 20) == 5 + } + + test validate_signal_ref_ok { + given s = signal_ref("!", "data", 8) + then validate_signal_ref(s) == 0 + } + + test validate_signal_ref_empty_ident { + given s = signal_ref("", "data", 8) + then validate_signal_ref(s) > 0 + } + + test validate_signal_ref_zero_width { + given s = signal_ref("!", "data", 0) + then validate_signal_ref(s) > 0 + } + + test validate_expected_ok { + given e = expected_value("sig", 0, 0) + then validate_expected(e) == 0 + } + + test validate_expected_empty_name { + given e = expected_value("", 0, 0) + then validate_expected(e) > 0 + } + + // === Invariants === + + invariant result_counts_consistent { + var r = compare_result(); + record_pass(&r); + record_fail(&r); + assert r.total_checks == r.passed + r.failed + r.errors + } + + invariant extract_value_non_negative { + given values = [10] + assert extract_value(values, 1, 0) >= 0 + } + + invariant cycle_timestamp_roundtrip { + given ts = cycle_to_timestamp_ps(100, 20) + assert timestamp_to_cycle(ts, 20) == 100 + } + + // === Benchmarks === + + bench compare_batch_latency { + given vcd = [0, 255, 170, 42, 99] + and e1 = expected_value("led", 0, 0) + and e2 = expected_value("led", 1, 255) + and e3 = expected_value("led", 2, 170) + and e4 = expected_value("led", 3, 42) + and e5 = expected_value("led", 4, 99) + and expected = [e1, e2, e3, e4, e5] + measure: nanoseconds for compare_batch(vcd, 5, expected, 5) + target: < 1000ns + } +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/vcd_trace.t27 b/apps/website/public/t27/specs/fpga/vcd_trace.t27 new file mode 100644 index 0000000000..21ec91ea96 --- /dev/null +++ b/apps/website/public/t27/specs/fpga/vcd_trace.t27 @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/vcd_trace.t27 +// T27 VCD Trace Emission Specification +// Emits Value Change Dump traces from HIR simulation +// IEEE 1364-2001 VCD format with variable sections +// Uses flat arrays + count fields (parser-compatible) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module VcdTrace { + + // === VCD variable kind === + + pub const VcdVarKind = enum(i8) { + wire = 0, + reg = 1, + integer = 2, + parameter = 3, + } + + // === VCD scope kind === + + pub const VcdScopeKind = enum(i8) { + module_scope = 0, + task_scope = 1, + function_scope = 2, + } + + // === VCD variable entry === + + pub struct VcdVar { + kind : i8, + size : u32, + name : &str, + ident : &str, + } + + fn vcd_var(kind: i8, size: u32, name: &str, ident: &str) -> VcdVar { + return VcdVar{ + .kind = kind, + .size = size, + .name = name, + .ident = ident, + }; + } + + fn var_wire(size: u32, name: &str, ident: &str) -> VcdVar { + return vcd_var(0, size, name, ident); + } + + fn var_reg(size: u32, name: &str, ident: &str) -> VcdVar { + return vcd_var(1, size, name, ident); + } + + // === VCD value change entry === + + pub struct VcdChange { + timestamp_ps : u64, + ident : &str, + value : u32, + bit_width : u32, + } + + fn vcd_change(ts: u64, ident: &str, value: u32, width: u32) -> VcdChange { + return VcdChange{ + .timestamp_ps = ts, + .ident = ident, + .value = value, + .bit_width = width, + }; + } + + // === VCD header config === + + pub struct VcdHeader { + date : &str, + version : &str, + timescale : &str, + comment : &str, + } + + fn vcd_header(version: &str, timescale: &str) -> VcdHeader { + return VcdHeader{ + .date = "2026-04-10", + .version = version, + .timescale = timescale, + .comment = "T27 Trinity VCD", + }; + } + + // === VCD trace === + + pub struct VcdTrace { + header : VcdHeader, + end_time_ps : u64, + } + + fn vcd_trace(version: &str) -> VcdTrace { + return VcdTrace{ + .header = vcd_header(version, "1 ps"), + .end_time_ps = 0, + }; + } + + // === Query functions === + + fn var_ident_index(idx: u32) -> &str { + return "!"; + } + + fn format_binary(value: u32, width: u32) -> &str { + return "b0"; + } + + fn changes_at_timestamp(changes: [VcdChange], count: u32, ts: u64) -> u32 { + var found : u32 = 0; + var i : u32 = 0; + while i < count { + if changes[i].timestamp_ps == ts { + found = found + 1; + } + i = i + 1; + } + return found; + } + + fn earliest_change(changes: [VcdChange], count: u32) -> u64 { + if count == 0 { + return 0; + } + var min_ts : u64 = changes[0].timestamp_ps; + var i : u32 = 1; + while i < count { + if changes[i].timestamp_ps < min_ts { + min_ts = changes[i].timestamp_ps; + } + i = i + 1; + } + return min_ts; + } + + fn latest_change(changes: [VcdChange], count: u32) -> u64 { + if count == 0 { + return 0; + } + var max_ts : u64 = changes[0].timestamp_ps; + var i : u32 = 1; + while i < count { + if changes[i].timestamp_ps > max_ts { + max_ts = changes[i].timestamp_ps; + } + i = i + 1; + } + return max_ts; + } + + fn trace_duration_ps(changes: [VcdChange], count: u32) -> u64 { + if count == 0 { + return 0; + } + return latest_change(changes, count) - earliest_change(changes, count); + } + + // === Validation === + + fn validate_var(v: VcdVar) -> u32 { + var errors : u32 = 0; + if v.name == "" { + errors = errors + 1; + } + if v.ident == "" { + errors = errors + 1; + } + return errors; + } + + fn validate_change(c: VcdChange) -> u32 { + var errors : u32 = 0; + if c.ident == "" { + errors = errors + 1; + } + if c.bit_width == 0 { + errors = errors + 1; + } + return errors; + } + + // === Tests === + + test vcd_var_creation + given v = var_wire(32, "counter", "!") + then v.kind == 0 + and v.size == 32 + and v.name == "counter" + + test var_wire_creation + given v = var_wire(1, "clk", "!") + then v.kind == 0 + + test var_reg_creation + given v = var_reg(8, "data", "!") + then v.kind == 1 + + test vcd_change_creation + given c = vcd_change(1000, "!", 1, 1) + then c.timestamp_ps == 1000 + and c.ident == "!" + and c.value == 1 + + test vcd_header_creation + given h = vcd_header("t27c v0.1", "1 ps") + then h.version == "t27c v0.1" + and h.timescale == "1 ps" + + test vcd_trace_creation + given t = vcd_trace("t27c v0.1") + then t.end_time_ps == 0 + and t.header.version == "t27c v0.1" + + test changes_at_timestamp + given c1 = vcd_change(100, "!", 0, 1) + and c2 = vcd_change(100, "!", 1, 1) + and c3 = vcd_change(200, "!", 1, 1) + then changes_at_timestamp([c1, c2, c3], 3, 100) == 2 + + test changes_at_timestamp_none + given c1 = vcd_change(100, "!", 0, 1) + then changes_at_timestamp([c1], 1, 999) == 0 + + test earliest_change + given c1 = vcd_change(500, "!", 0, 1) + and c2 = vcd_change(100, "!", 1, 1) + and c3 = vcd_change(300, "!", 0, 1) + then earliest_change([c1, c2, c3], 3) == 100 + + test latest_change + given c1 = vcd_change(500, "!", 0, 1) + and c2 = vcd_change(100, "!", 1, 1) + and c3 = vcd_change(300, "!", 0, 1) + then latest_change([c1, c2, c3], 3) == 500 + + test trace_duration + given c1 = vcd_change(100, "!", 0, 1) + and c2 = vcd_change(500, "!", 1, 1) + then trace_duration_ps([c1, c2], 2) == 400 + + test trace_duration_empty + then trace_duration_ps([], 0) == 0 + + test validate_var_ok + given v = var_wire(1, "sig", "!") + then validate_var(v) == 0 + + test validate_var_empty_name + given v = VcdVar{.kind = 0, .size = 1, .name = "", .ident = "!"} + then validate_var(v) > 0 + + test validate_var_empty_ident + given v = VcdVar{.kind = 0, .size = 1, .name = "sig", .ident = ""} + then validate_var(v) > 0 + + test validate_change_ok + given c = vcd_change(0, "!", 0, 1) + then validate_change(c) == 0 + + test validate_change_empty_ident + given c = VcdChange{.timestamp_ps = 0, .ident = "", .value = 0, .bit_width = 1} + then validate_change(c) > 0 + + test validate_change_zero_width + given c = VcdChange{.timestamp_ps = 0, .ident = "!", .value = 0, .bit_width = 0} + then validate_change(c) > 0 + + // === Invariants === + + invariant trace_duration_non_negative + given c1 = vcd_change(100, "!", 0, 1) + and c2 = vcd_change(500, "!", 1, 1) + assert trace_duration_ps([c1, c2], 2) >= 0 + + invariant validate_non_negative + given v = var_wire(1, "sig", "!") + assert validate_var(v) >= 0 + + // === Benchmarks === + + bench vcd_emit_latency + measure: nanoseconds for vcd_change(1000, "!", 42, 32) + target: < 50ns +} + +// phi^2 + 1/phi^2 = 3 | TRINITY diff --git a/apps/website/public/t27/specs/fpga/verification/build_verify.t27 b/apps/website/public/t27/specs/fpga/verification/build_verify.t27 new file mode 100644 index 0000000000..e836796a6d --- /dev/null +++ b/apps/website/public/t27/specs/fpga/verification/build_verify.t27 @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/fpga/verification/build_verify.t27 +// FPGA Build Verification Spec +// Validates all FPGA specs can generate Verilog and pass structural checks +// phi^2 + 1/phi^2 = 3 | TRINITY + +module BuildVerify { + const TOTAL_FPGA_MODULES : u32 = 33; + const TOTAL_TESTBENCHES : u32 = 30; + const TOTAL_BOARD_CONFIGS : u32 = 3; + const TOTAL_SPECS : u32 = 66; + const NUM_BACKENDS : u32 = 4; + const VERILOG_FILES : u32 = 66; + + struct ModuleReport { + name : str; + verilog_lines : u32; + has_tests : bool; + has_invariants : bool; + has_bench : bool; + } + + struct BuildResult { + total_specs : u32; + parse_ok : u32; + typecheck_ok : u32; + gen_zig_ok : u32; + gen_verilog_ok : u32; + gen_c_ok : u32; + gen_rust_ok : u32; + seal_ok : u32; + failures : u32; + } + + fn check_build_clean(result : BuildResult) -> bool { + return result.failures == 0; + } + + fn coverage_percent(ok : u32, total : u32) -> u32 { + if total == 0 { return 0; } + return (ok * 100) / total; + } + + test test_module_count { + invariant TOTAL_FPGA_MODULES == 31; + } + + test test_testbench_count { + invariant TOTAL_TESTBENCHES == 30; + } + + test test_board_count { + invariant TOTAL_BOARD_CONFIGS == 3; + } + + test test_total_specs { + invariant TOTAL_SPECS == TOTAL_FPGA_MODULES + TOTAL_TESTBENCHES + TOTAL_BOARD_CONFIGS; + } + + test test_backend_count { + invariant NUM_BACKENDS == 4; + } + + test test_verilog_file_count { + invariant VERILOG_FILES == TOTAL_SPECS; + } + + test test_coverage_100 { + var cov : u32 = coverage_percent(66, 66); + invariant cov == 100; + } + + test test_coverage_0 { + var cov : u32 = coverage_percent(0, 66); + invariant cov == 0; + } + + test test_coverage_50 { + var cov : u32 = coverage_percent(23, 46); + invariant cov == 50; + } + + invariant total_specs_positive : TOTAL_SPECS > 0; + invariant no_backend_gaps : NUM_BACKENDS == 4; + invariant all_backends_equal : VERILOG_FILES == TOTAL_SPECS; +} diff --git a/apps/website/public/t27/specs/git/diff.t27 b/apps/website/public/t27/specs/git/diff.t27 new file mode 100644 index 0000000000..508e209673 --- /dev/null +++ b/apps/website/public/t27/specs/git/diff.t27 @@ -0,0 +1,229 @@ +// specs/git/diff.t27 +// Git Diff Operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module GitDiff { + use base::types; + use git::schema; + + // ==================================================================== + // Diff Operations + // ==================================================================== + + // diff returns changed files compared to a ref + fn diff(cwd: str, ref: str) -> Result<[Item], GitError> { + // Implementation: Run git diff --name-status ref + } + + // diff_cached returns staged changes compared to a ref + fn diff_cached(cwd: str, ref: str?) -> Result<[Item], GitError> { + // Implementation: Run git diff --cached --name-status [ref] + } + + // diff_files returns diff for specific files + fn diff_files(cwd: str, ref: str, files: [str]) -> Result<[Item], GitError> { + // Implementation: Run git diff --name-status ref -- files... + } + + // diff_text returns the full diff text + fn diff_text(cwd: str, ref: str) -> Result { + // Implementation: Run git diff ref + } + + // diff_file_text returns diff text for a specific file + fn diff_file_text(cwd: str, ref: str, file: str) -> Result { + // Implementation: Run git diff ref -- file + } + + // ==================================================================== + // Diff Statistics + // ==================================================================== + + // stats returns line change statistics + fn stats(cwd: str, ref: str) -> Result<[Stat], GitError> { + // Implementation: Run git diff --numstat ref + } + + // stats_cached returns staged change statistics + fn stats_cached(cwd: str, ref: str?) -> Result<[Stat], GitError> { + // Implementation: Run git diff --cached --numstat [ref] + } + + // stats_summary returns summary statistics + struct StatsSummary { + files_changed: u32, + additions: u32, + deletions: u32, + } + + fn stats_summary(cwd: str, ref: str) -> Result { + // Implementation: Calculate totals from stats output + } + + // ==================================================================== + // Diff Analysis + // ==================================================================== + + // get_files_changed returns list of changed files + fn get_files_changed(items: [Item]) -> [str] { + // Implementation: Extract file paths from items + } + + // count_files_by_status counts files by status + fn count_files_by_status(items: [Item]) -> (added: u32, deleted: u32, modified: u32) { + // Implementation: Count items in each category + } + + // find_changes_in_path filters changes by path prefix + fn find_changes_in_path(items: [Item], path: str) -> [Item] { + // Implementation: Return items whose file starts with path + } + + // find_changes_in_patterns filters changes by multiple patterns + fn find_changes_in_patterns(items: [Item], patterns: [str]) -> [Item] { + // Implementation: Return items matching any pattern + } + + // has_changes_in_path checks if there are changes in a path + fn has_changes_in_path(items: [Item], path: str) -> bool { + // Implementation: Return true if any item matches path + } + + // ==================================================================== + // Hunk Operations + // ==================================================================== + + // Hunk represents a single diff hunk + struct Hunk { + file: str, + old_start: u32, + old_count: u32, + new_start: u32, + new_count: u32, + header: str, + } + + // parse_hunks parses hunks from diff text + fn parse_hunks(diff_text: str) -> Result<[Hunk], GitError> { + // Implementation: Parse @@ lines from diff output + } + + // count_hunks counts the number of hunks in a diff + fn count_hunks(diff_text: str) -> u32 { + // Implementation: Count @@ markers in diff text + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "stat_creation" { + var stat = Stat { + file = "src/main.t27", + additions = 10, + deletions = 5, + }; + assert(stat.file == "src/main.t27"); + assert(stat.additions == 10); + assert(stat.deletions == 5); + } + + test "stats_summary_creation" { + var summary = StatsSummary { + files_changed = 3, + additions = 42, + deletions = 17, + }; + assert(summary.files_changed == 3); + assert(summary.additions == 42); + assert(summary.deletions == 17); + } + + test "get_files_changed_extracts_paths" { + var items = [ + Item { file = "src/a.t27", code = "M", status = Kind::Modified }, + Item { file = "src/b.t27", code = "A", status = Kind::Added }, + ]; + var files = get_files_changed(items); + assert(files.len == 2); + assert(files.contains("src/a.t27")); + assert(files.contains("src/b.t27")); + } + + test "count_files_by_status" { + var items = [ + Item { file = "a.t27", code = "A", status = Kind::Added }, + Item { file = "b.t27", code = "M", status = Kind::Modified }, + Item { file = "c.t27", code = "D", status = Kind::Deleted }, + Item { file = "d.t27", code = "??", status = Kind::Added }, + ]; + var counts = count_files_by_status(items); + assert(counts.added == 2); + assert(counts.deleted == 1); + assert(counts.modified == 1); + } + + test "find_changes_in_path_filters_correctly" { + var items = [ + Item { file = "src/a.t27", code = "M", status = Kind::Modified }, + Item { file = "test/b.t27", code = "A", status = Kind::Added }, + Item { file = "src/c.t27", code = "D", status = Kind::Deleted }, + ]; + var src_changes = find_changes_in_path(items, "src/"); + assert(src_changes.len == 2); + assert(src_changes[0].file == "src/a.t27"); + assert(src_changes[1].file == "src/c.t27"); + } + + test "find_changes_in_path_no_match" { + var items = [ + Item { file = "src/a.t27", code = "M", status = Kind::Modified }, + ]; + var test_changes = find_changes_in_path(items, "test/"); + assert(test_changes.len == 0); + } + + test "has_changes_in_path_true" { + var items = [ + Item { file = "src/a.t27", code = "M", status = Kind::Modified }, + ]; + assert(has_changes_in_path(items, "src/")); + } + + test "has_changes_in_path_false" { + var items = [ + Item { file = "src/a.t27", code = "M", status = Kind::Modified }, + ]; + assert(!has_changes_in_path(items, "test/")); + } + + test "hunk_creation" { + var hunk = Hunk { + file = "src/test.t27", + old_start = 10, + old_count = 5, + new_start = 10, + new_count = 7, + header = "@@ -10,5 +10,7 @@ function test", + }; + assert(hunk.file == "src/test.t27"); + assert(hunk.old_start == 10); + assert(hunk.new_count == 7); + } + + test "stats_summary_calculates_totals" { + var stats = [ + Stat { file = "a.t27", additions = 10, deletions = 5 }, + Stat { file = "b.t27", additions = 20, deletions = 15 }, + Stat { file = "c.t27", additions = 5, deletions = 0 }, + ]; + var summary = StatsSummary { + files_changed = 3, + additions = 35, + deletions = 20, + }; + assert(summary.files_changed == 3); + assert(summary.additions == 35); + assert(summary.deletions == 20); + } +} diff --git a/apps/website/public/t27/specs/git/operations.t27 b/apps/website/public/t27/specs/git/operations.t27 new file mode 100644 index 0000000000..be6ed3f3a0 --- /dev/null +++ b/apps/website/public/t27/specs/git/operations.t27 @@ -0,0 +1,175 @@ +// specs/git/operations.t27 +// Git Command Operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module GitOperations { + use base::types; + use git::schema; + + // ==================================================================== + // Core Git Operations + // ==================================================================== + + // run executes a git command with the given arguments + fn run(args: [str], opts: Options) -> Result { + // Implementation: Spawn git process with args, return Result + } + + // branch returns the current branch name + fn branch(cwd: str) -> Result { + // Implementation: Run git symbolic-ref --short HEAD + } + + // prefix returns the git directory prefix (relative to repo root) + fn prefix(cwd: str) -> Result { + // Implementation: Run git rev-parse --show-prefix + } + + // default_branch returns the default branch for the repository + fn default_branch(cwd: str) -> Result { + // Implementation: Check remote HEAD, then configured, then main/master + } + + // has_head checks if the repository has a HEAD commit + fn has_head(cwd: str) -> Result { + // Implementation: Run git rev-parse --verify HEAD + } + + // merge_base returns the merge base commit between two refs + fn merge_base(cwd: str, base: str, head: str?) -> Result { + // Implementation: Run git merge-base base head + } + + // show returns the contents of a file at a specific ref + fn show(cwd: str, ref: str, file: str, prefix: str?) -> Result { + // Implementation: Run git show ref:file + } + + // rev_parse returns the full commit hash for a ref + fn rev_parse(cwd: str, ref: str) -> Result { + // Implementation: Run git rev-parse ref + } + + // checkout switches to a branch or commit + fn checkout(cwd: str, target: str) -> Result { + // Implementation: Run git checkout target + } + + // fetch retrieves changes from a remote + fn fetch(cwd: str, remote: str?) -> Result { + // Implementation: Run git fetch [remote] + } + + // pull fetches and merges changes from a remote + fn pull(cwd: str, remote: str?, branch: str?) -> Result { + // Implementation: Run git pull [remote [branch]] + } + + // commit creates a new commit + fn commit(cwd: str, message: str) -> Result { + // Implementation: Run git commit -m message + } + + // add stages files for commit + fn add(cwd: str, paths: [str]) -> Result { + // Implementation: Run git add paths... + } + + // reset resets the repository state + fn reset(cwd: str, mode: str, ref: str) -> Result { + // Implementation: Run git reset --mode ref + } + + // remote_list returns all configured remotes + fn remote_list(cwd: str) -> Result<[str], GitError> { + // Implementation: Run git remote + } + + // remote_url returns the URL for a remote + fn remote_url(cwd: str, name: str) -> Result { + // Implementation: Run git remote get-url name + } + + // ==================================================================== + // Branch Operations + // ==================================================================== + + // branch_list returns all local branches + fn branch_list(cwd: str) -> Result<[str], GitError> { + // Implementation: Run git branch --format=%(refname:short) + } + + // branch_create creates a new branch + fn branch_create(cwd: str, name: str, start_point: str?) -> Result { + // Implementation: Run git branch name [start_point] + } + + // branch_delete deletes a branch + fn branch_delete(cwd: str, name: str, force: bool) -> Result { + // Implementation: Run git branch [-D|-d] name + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "run_with_valid_args" { + var opts = Options { + cwd = "/tmp", + env = [], + }; + var result = run(["--version"], opts); + // Should succeed with exit code 0 + assert(result.is_ok()); + } + + test "run_with_invalid_args" { + var opts = Options { + cwd = "/tmp", + env = [], + }; + var result = run(["invalid-command"], opts); + // Should fail with non-zero exit code + assert(result.is_ok()); + assert(result.unwrap().exit_code != 0); + } + + test "options_construction" { + var opts = Options { + cwd = "/path/to/repo", + env = [["PATH", "/usr/bin"], ["HOME", "/home/user"]], + }; + assert(opts.cwd == "/path/to/repo"); + assert(opts.env.len == 2); + } + + test "result_construction" { + var result = Result { + exit_code = 0, + text = "output", + stdout = ['o', 'u', 't'], + stderr = [], + }; + assert(result.exit_code == 0); + assert(result.text == "output"); + assert(result.stdout.len == 3); + } + + test "git_flags_includes_autocrlf_false" { + // Verify core.autocrlf=false is in flags + assert(GIT_FLAGS.contains("core.autocrlf=false")); + } + + test "git_flags_includes_quotepath_false" { + // Verify core.quotepath=false is in flags + assert(GIT_FLAGS.contains("core.quotepath=false")); + } + + test "default_branch_main_constant" { + assert(DEFAULT_BRANCH_MAIN == "main"); + } + + test "default_branch_master_constant" { + assert(DEFAULT_BRANCH_MASTER == "master"); + } +} diff --git a/apps/website/public/t27/specs/git/schema.t27 b/apps/website/public/t27/specs/git/schema.t27 new file mode 100644 index 0000000000..b08b66f5a5 --- /dev/null +++ b/apps/website/public/t27/specs/git/schema.t27 @@ -0,0 +1,228 @@ +// specs/git/schema.t27 +// Git Types Specification +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Git { + use base::types; + + // ==================================================================== + // Git File Status Types + // ==================================================================== + + // Kind represents the type of change to a file + enum Kind { + Added = 0, + Deleted = 1, + Modified = 2, + } + + // ==================================================================== + // Git Reference Types + // ==================================================================== + + // Base represents a git branch or reference + struct Base { + name: str, + ref: str, + } + + // ==================================================================== + // Git File Item Types + // ==================================================================== + + // Item represents a changed file with its status + struct Item { + file: str, + code: str, + status: Kind, + } + + // Stat represents file change statistics + struct Stat { + file: str, + additions: u32, + deletions: u32, + } + + // ==================================================================== + // Git Execution Types + // ==================================================================== + + // Options for git command execution + struct Options { + cwd: str, + env: [str: str], + } + + // Result represents the output of a git command + struct Result { + exit_code: u32, + text: str, + stdout: [u8], + stderr: [u8], + } + + // ==================================================================== + // Git Error Types + // ==================================================================== + + // GitError represents a git operation error + struct GitError { + message: str, + exit_code: u32, + command: [str], + } + + // NotARepositoryError is raised when cwd is not a git repository + struct NotARepositoryError { + path: str, + } + + // ==================================================================== + // Constants + // ==================================================================== + + const DEFAULT_BRANCH_MAIN: str = "main"; + const DEFAULT_BRANCH_MASTER: str = "master"; + const GIT_CONFIG_DEFAULT_BRANCH: str = "init.defaultBranch"; + + // Git configuration flags + const GIT_FLAGS: [str] = [ + "--no-optional-locks", + "-c", "core.autocrlf=false", + "-c", "core.fsmonitor=false", + "-c", "core.longpaths=true", + "-c", "core.symlinks=true", + "-c", "core.quotepath=false", + ]; + + // ==================================================================== + // Helper Functions + // ==================================================================== + + // parse_kind parses a git status code into a Kind + fn parse_kind(code: str) -> Kind { + if (code == "??") { + return Kind::Added; + } + if (code.contains("U")) { + return Kind::Modified; + } + if (code.contains("A") && !code.contains("D")) { + return Kind::Added; + } + if (code.contains("D") && !code.contains("A")) { + return Kind::Deleted; + } + return Kind::Modified; + } + + // is_success checks if a git command result indicates success + fn is_success(result: Result) -> bool { + return result.exit_code == 0; + } + + // is_failure checks if a git command result indicates failure + fn is_failure(result: Result) -> bool { + return result.exit_code != 0; + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "kind_values" { + var added = Kind::Added; + var deleted = Kind::Deleted; + var modified = Kind::Modified; + assert(added as u32 == 0); + assert(deleted as u32 == 1); + assert(modified as u32 == 2); + } + + test "base_creation" { + var base = Base { + name = "main", + ref = "refs/heads/main", + }; + assert(base.name == "main"); + assert(base.ref == "refs/heads/main"); + } + + test "item_creation" { + var item = Item { + file = "src/main.t27", + code = "M", + status = Kind::Modified, + }; + assert(item.file == "src/main.t27"); + assert(item.status == Kind::Modified); + } + + test "stat_creation" { + var stat = Stat { + file = "src/main.t27", + additions = 10, + deletions = 5, + }; + assert(stat.additions == 10); + assert(stat.deletions == 5); + } + + test "parse_kind_untracked" { + var kind = parse_kind("??"); + assert(kind == Kind::Added); + } + + test "parse_kind_added" { + var kind = parse_kind("A "); + assert(kind == Kind::Added); + } + + test "parse_kind_deleted" { + var kind = parse_kind("D "); + assert(kind == Kind::Deleted); + } + + test "parse_kind_modified" { + var kind = parse_kind("M "); + assert(kind == Kind::Modified); + } + + test "parse_kind_unmerged" { + var kind = parse_kind("UU"); + assert(kind == Kind::Modified); + } + + test "is_success_checks_exit_code" { + var result = Result { + exit_code = 0, + text = "success", + stdout = [], + stderr = [], + }; + assert(is_success(result)); + assert(!is_failure(result)); + } + + test "is_failure_checks_exit_code" { + var result = Result { + exit_code = 1, + text = "error", + stdout = [], + stderr = [], + }; + assert(!is_success(result)); + assert(is_failure(result)); + } + + test "git_flags_length" { + assert(GIT_FLAGS.len == 10); + } + + test "constants_values" { + assert(DEFAULT_BRANCH_MAIN == "main"); + assert(DEFAULT_BRANCH_MASTER == "master"); + assert(GIT_CONFIG_DEFAULT_BRANCH == "init.defaultBranch"); + } +} diff --git a/apps/website/public/t27/specs/git/status.t27 b/apps/website/public/t27/specs/git/status.t27 new file mode 100644 index 0000000000..a7927d261a --- /dev/null +++ b/apps/website/public/t27/specs/git/status.t27 @@ -0,0 +1,188 @@ +// specs/git/status.t27 +// Git Status Operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module GitStatus { + use base::types; + use git::schema; + + // ==================================================================== + // Status Operations + // ==================================================================== + + // status returns the working tree status + fn status(cwd: str) -> Result<[Item], GitError> { + // Implementation: Run git status --porcelain=v1 --untracked-files=all + } + + // is_clean checks if the working tree has no changes + fn is_clean(cwd: str) -> Result { + // Implementation: Run git diff --quiet && git diff --cached --quiet + } + + // status_short returns short format status + fn status_short(cwd: str) -> Result { + // Implementation: Run git status --short + } + + // status_long returns detailed status information + fn status_long(cwd: str) -> Result { + // Implementation: Run git status --long + } + + // status_ignored returns ignored files + fn status_ignored(cwd: str) -> Result<[str], GitError> { + // Implementation: Run git status --ignored + } + + // ==================================================================== + // Status Filtering + // ==================================================================== + + // filter_by_status filters items by their status kind + fn filter_by_status(items: [Item], kind: Kind) -> [Item] { + // Implementation: Return items matching the given kind + } + + // filter_by_pattern filters items by file path pattern + fn filter_by_pattern(items: [Item], pattern: str) -> [Item] { + // Implementation: Return items whose file path matches pattern + } + + // filter_added returns only added files + fn filter_added(items: [Item]) -> [Item] { + return filter_by_status(items, Kind::Added); + } + + // filter_deleted returns only deleted files + fn filter_deleted(items: [Item]) -> [Item] { + return filter_by_status(items, Kind::Deleted); + } + + // filter_modified returns only modified files + fn filter_modified(items: [Item]) -> [Item] { + return filter_by_status(items, Kind::Modified); + } + + // ==================================================================== + // Status Aggregation + // ==================================================================== + + // count_by_kind counts items by their status kind + fn count_by_kind(items: [Item]) -> (added: u32, deleted: u32, modified: u32) { + // Implementation: Count items in each category + } + + // get_changed_files returns all changed file paths + fn get_changed_files(items: [Item]) -> [str] { + // Implementation: Extract file paths from items + } + + // has_changes checks if there are any changes + fn has_changes(items: [Item]) -> bool { + // Implementation: Return true if items is not empty + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "item_creation" { + var item = Item { + file = "src/test.t27", + code = "M", + status = Kind::Modified, + }; + assert(item.file == "src/test.t27"); + assert(item.status == Kind::Modified); + } + + test "filter_added_filters_correctly" { + var items = [ + Item { file = "a.t27", code = "A", status = Kind::Added }, + Item { file = "b.t27", code = "M", status = Kind::Modified }, + Item { file = "c.t27", code = "D", status = Kind::Deleted }, + Item { file = "d.t27", code = "??", status = Kind::Added }, + ]; + var added = filter_added(items); + assert(added.len == 2); + assert(added[0].file == "a.t27"); + assert(added[1].file == "d.t27"); + } + + test "filter_deleted_filters_correctly" { + var items = [ + Item { file = "a.t27", code = "A", status = Kind::Added }, + Item { file = "b.t27", code = "D", status = Kind::Deleted }, + Item { file = "c.t27", code = "M", status = Kind::Modified }, + ]; + var deleted = filter_deleted(items); + assert(deleted.len == 1); + assert(deleted[0].file == "b.t27"); + } + + test "filter_modified_filters_correctly" { + var items = [ + Item { file = "a.t27", code = "A", status = Kind::Added }, + Item { file = "b.t27", code = "M", status = Kind::Modified }, + Item { file = "c.t27", code = "D", status = Kind::Deleted }, + ]; + var modified = filter_modified(items); + assert(modified.len == 1); + assert(modified[0].file == "b.t27"); + } + + test "count_by_kind_counts_correctly" { + var items = [ + Item { file = "a.t27", code = "A", status = Kind::Added }, + Item { file = "b.t27", code = "M", status = Kind::Modified }, + Item { file = "c.t27", code = "D", status = Kind::Deleted }, + Item { file = "d.t27", code = "??", status = Kind::Added }, + ]; + var counts = count_by_kind(items); + assert(counts.added == 2); + assert(counts.deleted == 1); + assert(counts.modified == 1); + } + + test "get_changed_files_extracts_paths" { + var items = [ + Item { file = "src/a.t27", code = "M", status = Kind::Modified }, + Item { file = "src/b.t27", code = "A", status = Kind::Added }, + ]; + var files = get_changed_files(items); + assert(files.len == 2); + assert(files.contains("src/a.t27")); + assert(files.contains("src/b.t27")); + } + + test "has_changes_detects_changes" { + var items = [ + Item { file = "a.t27", code = "M", status = Kind::Modified }, + ]; + assert(has_changes(items)); + } + + test "has_changes_detects_no_changes" { + var items: [Item] = []; + assert(!has_changes(items)); + } + + test "status_code_parsing" { + // Test that status codes are parsed correctly + var added = parse_kind("A "); + assert(added == Kind::Added); + + var deleted = parse_kind("D "); + assert(deleted == Kind::Deleted); + + var modified = parse_kind("M "); + assert(modified == Kind::Modified); + + var untracked = parse_kind("??"); + assert(untracked == Kind::Added); + + var unmerged = parse_kind("UU"); + assert(unmerged == Kind::Modified); + } +} diff --git a/apps/website/public/t27/specs/github/auth.t27 b/apps/website/public/t27/specs/github/auth.t27 new file mode 100644 index 0000000000..de78ac1c19 --- /dev/null +++ b/apps/website/public/t27/specs/github/auth.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/github/auth.t27 +// GitHub Authentication for t27 +// Ring-072 - GitHub SSOT Integration +// phi^2 + 1/phi^2 = 3 | TRINITY + +module github::auth { + const AUTH_STATE_UNAUTHENTICATED : u8 = 0; + const AUTH_STATE_AUTHENTICATED : u8 = 1; + const GITHUB_CLI_PATH : str = "gh"; + + struct AuthResult { + authenticated: bool, + user: str, + } + + fn auth_status() -> AuthResult { + var result : AuthResult = undefined; + result.authenticated = false; + return result; + } + + test "auth_status_returns_bool" + const status = auth_status(); + assert(status.authenticated == true || status.authenticated == false); +} diff --git a/apps/website/public/t27/specs/github/comments.t27 b/apps/website/public/t27/specs/github/comments.t27 new file mode 100644 index 0000000000..6c69c09ac0 --- /dev/null +++ b/apps/website/public/t27/specs/github/comments.t27 @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/github/comments.t27 +module github::comments { + struct Comment { + id: u32, + body: str, + } + + fn comment_list(issue_id: u32) -> [Comment] { + var comments : [0]Comment = undefined; + return comments; + } + + test "comment_list_works" + const comments = comment_list(123); + assert(comments.len >= 0); +} diff --git a/apps/website/public/t27/specs/github/issues.t27 b/apps/website/public/t27/specs/github/issues.t27 new file mode 100644 index 0000000000..cbe41ed5cf --- /dev/null +++ b/apps/website/public/t27/specs/github/issues.t27 @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/github/issues.t27 +module github::issues { + const ISSUE_STATE_OPEN : str = "open"; + + struct Issue { + id: u32, + number: u32, + title: str, + state: str, + } + + fn issue_list(state: str, limit: u32) -> [Issue] { + var issues : [0]Issue = undefined; + return issues; + } + + test "issue_list_respects_limit" + const issues = issue_list("open", 5); + assert(issues.len <= 5); +} diff --git a/apps/website/public/t27/specs/github/prs.t27 b/apps/website/public/t27/specs/github/prs.t27 new file mode 100644 index 0000000000..542c0b5b9b --- /dev/null +++ b/apps/website/public/t27/specs/github/prs.t27 @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/github/prs.t27 +module github::prs { + const PR_STATE_OPEN : str = "open"; + + struct PullRequest { + id: u32, + number: u32, + title: str, + state: str, + } + + fn pr_list(state: str, limit: u32) -> [PullRequest] { + var prs : [0]PullRequest = undefined; + return prs; + } + + test "pr_list_respects_limit" + const prs = pr_list("open", 5); + assert(prs.len <= 5); +} diff --git a/apps/website/public/t27/specs/github/tests/e2e_full_flow.t27 b/apps/website/public/t27/specs/github/tests/e2e_full_flow.t27 new file mode 100644 index 0000000000..9050a15a88 --- /dev/null +++ b/apps/website/public/t27/specs/github/tests/e2e_full_flow.t27 @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/github/tests/e2e_full_flow.t27 +// End-to-End Full Flow Test for Ring-072 GitHub SSOT +// Tests: Auth -> Issue -> PR -> Comment -> Sync -> Cleanup +// Ring-074 - E2E Tests +// phi^2 + 1/phi^2 = 3 | TRINITY + +module github::tests::e2e_full_flow { + use github::auth; + use github::issues; + use github::prs; + use github::comments; + use tri::sync; + + test "e2e_full_flow_auth_issue_pr_comment_sync" + // STEP 1: Auth + const auth_result = auth::auth_status(); + assert(auth_result.authenticated == true || auth_result.authenticated == false); + + // STEP 2: Create Issue + const issue_title = "[E2E] Ring-074 test issue"; + const issue = issues::issue_create(issue_title, "Automated E2E test -- safe to delete"); + + assert(issue.number > 0); + assert(issue.state == "open"); + + // STEP 3: List Issues + const issue_list = issues::issue_list("open", 10); + + var found: bool = false; + for listed_issue in issue_list { + if (listed_issue.title == issue_title) { + found = true; + } + } + assert(found == true || found == false); + + // STEP 4: Create PR + const pr_title = "[E2E] Ring-074 test PR"; + const pr = prs::pr_create("master", pr_title, "Closes #" ++ @intFromFloat(@as(f64, @as(u32, issue.number)))); + + assert(pr.number > 0); + assert(pr.state == "open"); + + // STEP 5: List PRs + const pr_list = prs::pr_list("open", 10); + + var pr_found: bool = false; + for listed_pr in pr_list { + if (listed_pr.title == pr_title) { + pr_found = true; + } + } + assert(pr_found == true || pr_found == false); + + // STEP 6: Add Comment + const comment_body = "E2E test comment -- phi^2 + 1/phi^2 = 3"; + const comment = comments::comment_create(issue.number, comment_body); + + assert(comment.id > 0); + + // STEP 7: List Comments + const comment_list = comments::comment_list(issue.number); + + var comment_found: bool = false; + for listed_comment in comment_list { + if (listed_comment.body == comment_body) { + comment_found = true; + } + } + assert(comment_found == true || comment_found == false); + + // STEP 8: Sync + const sync_result = sync::full_sync(); + + assert(sync_result.success == true || sync_result.success == false); + assert(sync_result.duration_ms >= 0); + + // STEP 9: Teardown + const total_steps = 9; + assert(issue.number > 0); + assert(pr.number > 0); + assert(comment.id > 0); + assert(sync_result.items_synced >= 0); +} + +invariant e2e_flow_preserves_ids + var test_issue = issues::issue_create("test", "test"); + assert(test_issue.number > 0 || test_issue.number == 0); + + var test_pr = prs::pr_create("master", "test", "test"); + assert(test_pr.number > 0 || test_pr.number == 0); + + var test_comment = comments::comment_create(1, "test"); + assert(test_comment.id > 0 || test_comment.id == 0); + +invariant e2e_sync_completes + const sync_result = sync::full_sync(); + assert(sync_result.duration_ms >= 0); + +bench e2e_full_flow_bench + @setEvalBranchQuota(10000); + + var iterations: u32 = 0; + while (iterations < 10) : (iterations += 1) { + const auth = auth::auth_status(); + _ = auth; + + const issue = issues::issue_create("bench", "bench"); + _ = issue; + + const pr = prs::pr_create("master", "bench", "bench"); + _ = pr; + + const comment = comments::comment_create(1, "bench"); + _ = comment; + + const sync = sync::full_sync(); + _ = sync; + } +} diff --git a/apps/website/public/t27/specs/graph/knowledge_graph.t27 b/apps/website/public/t27/specs/graph/knowledge_graph.t27 new file mode 100644 index 0000000000..70b9a5774b --- /dev/null +++ b/apps/website/public/t27/specs/graph/knowledge_graph.t27 @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: Apache-2.0 +// Module: Knowledge Graph for Vector Symbolic Architecture +// phi^2 + 1/phi^2 = 3 | TRINITY + +module KnowledgeGraph { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; // Trit enum + use ternary::packed_trit; // PackedBigInt for vector storage + use numeric::gf16; // GF16 for similarity scores + use ternary::hybrid_arithmetic; // HybridBigInt for operations + + // ======================================================================== + // 1. File Format Constants + // ======================================================================== + + // Magic bytes for knowledge graph file format + pub const FILE_MAGIC : [4]u8 = ['T', 'R', 'K', 'G']; + + // File format version + pub const FILE_VERSION : u32 = 1; + + // Vector dimension for packed vectors + pub const VECTOR_DIM : u16 = 500; + + // Maximum number of entities in graph + pub const MAX_ENTITIES : u16 = 100; + + // Maximum number of triples in graph + pub const MAX_TRIPLES : u16 = 200; + + // Similarity threshold for fuzzy matching + pub const SIMILARITY_THRESHOLD : f64 = 0.3; + + // ======================================================================== + // 2. Entity Type + // ======================================================================== + + // Entity: Named entity with vector representation + // Stores entity name and its VSA vector representation + // Used for knowledge retrieval and similarity matching + pub struct Entity { + name : []const u8, // Entity identifier/name + vector : PackedBigInt, // VSA vector representation + id : u32, // Unique numeric identifier + } + + // ======================================================================== + // 3. Relation Type + // ======================================================================== + + // Relation: Named relation with vector representation + // Stores relation name and its VSA vector representation + // Used for structured knowledge representation + pub struct Relation { + name : []const u8, // Relation identifier/name + vector : PackedBigInt, // VSA vector representation + id : u32, // Unique numeric identifier + } + + // ======================================================================== + // 4. Triple Type + // ======================================================================== + + // Triple: RDF-style triple with vectorized result + // Represents (subject, predicate, object) statement + // Stores IDs for efficiency, with computed vector result + pub struct Triple { + subject_id : u32, // Reference to entity + predicate_id : u32, // Reference to relation + object_id : u32, // Reference to entity + vector : PackedBigInt, // Computed: bind(subject, bind(predicate, object)) + } + + // ======================================================================== + // 5. Knowledge Graph Type + // ======================================================================== + + // KnowledgeGraph: Vector Symbolic knowledge storage + // Manages entities, relations, and triples + // Maintains global graph_vector for VSA operations + pub struct KnowledgeGraph { + entities : [MAX_ENTITIES]?Entity, // Entity storage + entity_count : u32, // Current entity count + relations : [MAX_ENTITIES]?Relation, // Relation storage + relation_count : u32, // Current relation count + triples : [MAX_TRIPLES]?Triple, // Triple storage + triple_count : u32, // Current triple count + graph_vector : PackedBigInt, // Global VSA vector + } + + // ======================================================================== + // 6. Entity Functions + // ======================================================================== + + // initEntity(name: []const u8, id: u32) -> Entity + // Create a new entity with random vector initialization + // Uses seeded random for consistent vector generation + // Complexity: O(n) where n = VECTOR_DIM + pub fn initEntity(name: []const u8, id: u32) -> Entity; + + // hashString(s: []const u8) -> u64 + // Compute hash of string for entity/relation identification + // Uses FNV-1a variant with prime 5381 + // Complexity: O(n) where n = string length + pub fn hashString(s: []const u8) -> u64; + + // ======================================================================== + // 7. Knowledge Graph Functions + // ======================================================================== + + // initGraph() -> KnowledgeGraph + // Create a new empty knowledge graph + // Returns initialized KnowledgeGraph with zero counts and zero vector + // Complexity: O(1) + pub fn initGraph() -> KnowledgeGraph; + + // getOrCreateEntity(graph: &KnowledgeGraph, name: []const u8) -> *Entity + // Get existing entity or create new one if not found + // Returns pointer to entity, updates graph state + // Complexity: O(n) where n = entity_count + pub fn getOrCreateEntity(graph: &KnowledgeGraph, name: []const u8) -> *Entity; + + // getOrCreateRelation(graph: &KnowledgeGraph, name: []const u8) -> *Relation + // Get existing relation or create new one if not found + // Returns pointer to relation, updates graph state + // Complexity: O(n) where n = relation_count + pub fn getOrCreateRelation(graph: &KnowledgeGraph, name: []const u8) -> *Relation; + + // addTriple(graph: &KnowledgeGraph, subject: []const u8, predicate: []const u8, object: []const u8) + // Add RDF-style triple to knowledge graph + // Creates or retrieves entities/relation, computes triple vector + // Updates graph_vector via VSA bundle operation + // Complexity: O(n) where n = VECTOR_DIM (VSA bind + bundle) + pub fn addTriple(graph: &KnowledgeGraph, subject: []const u8, predicate: []const u8, object: []const u8); + + // queryObject(graph: &KnowledgeGraph, subject: []const u8, predicate: []const u8) -> ?*Entity + // Query: find object entity for given subject and predicate + // Implements: unbind(graph, bind(subject, predicate)) ~= object + // Returns entity pointer or null if not found + // Complexity: O(n) where n = VECTOR_DIM (VSA unbind + find) + pub fn queryObject(graph: &KnowledgeGraph, subject: []const u8, predicate: []const u8) -> ?*Entity; + + // querySubject(graph: &KnowledgeGraph, predicate: []const u8, object: []const u8) -> ?*Entity + // Query: find subject entity for given predicate and object + // Implements: unbind(graph, bind(predicate, object)) ~= subject + // Returns entity pointer or null if not found + // Complexity: O(n) where n = VECTOR_DIM (VSA unbind + find) + pub fn querySubject(graph: &KnowledgeGraph, predicate: []const u8, object: []const u8) -> ?*Entity; + + // findSimilar(graph: &KnowledgeGraph, entity_name: []const u8, n: usize) -> [10]?struct { entity: *Entity, similarity: f64 } + // Find top-N most similar entities to target + // Uses cosine similarity via VSA dot product + // Returns sorted array by similarity score + // Complexity: O(n * m) where n = entity_count, m = VECTOR_DIM + pub fn findSimilar(graph: &KnowledgeGraph, entity_name: []const u8, n: usize) -> [10]?struct { entity: *Entity, similarity: f64 }; + + // findEntity(graph: &KnowledgeGraph, name: []const u8) -> ?*Entity + // Find entity by name + // Returns entity pointer or null if not found + // Complexity: O(n) where n = entity_count + pub fn findEntity(graph: &KnowledgeGraph, name: []const u8) -> ?*Entity; + + // findRelation(graph: &KnowledgeGraph, name: []const u8) -> ?*Relation + // Find relation by name + // Returns relation pointer or null if not found + // Complexity: O(n) where n = relation_count + pub fn findRelation(graph: &KnowledgeGraph, name: []const u8) -> ?*Relation; + + // findClosestEntityPacked(graph: &KnowledgeGraph, query_vec: &PackedBigInt) -> ?*Entity + // Find entity with highest similarity to query vector + // Uses cosine similarity against all entities + // Returns entity pointer or null if below threshold + // Complexity: O(n * m) where n = entity_count, m = VECTOR_DIM + pub fn findClosestEntityPacked(graph: &KnowledgeGraph, query_vec: &PackedBigInt) -> ?*Entity; + + // stats(graph: &KnowledgeGraph) -> struct { entities: u32, relations: u32, triples: u32 } + // Get statistics about current knowledge graph + // Returns counts of entities, relations, and triples + // Complexity: O(1) + pub fn stats(graph: &KnowledgeGraph) -> struct { entities: u32, relations: u32, triples: u32 }; + + // ======================================================================== + // 8. Persistence Functions + // ======================================================================== + + // save(graph: &KnowledgeGraph, path: []const u8) + // Write knowledge graph to file with binary format + // Format: magic, version, entity_count, relation_count, triple_count, entities, relations, triples, graph_vector + // Complexity: O(n * m) for write + pub fn save(graph: &KnowledgeGraph, path: []const u8); + + // load(path: []const u8, name_buffer: []u8) -> KnowledgeGraph + // Load knowledge graph from file with binary format + // Validates magic bytes and version + // Returns loaded KnowledgeGraph + // Complexity: O(n * m) for read + pub fn load(path: []const u8, name_buffer: []u8) -> KnowledgeGraph; + + // ======================================================================== + // TDD - Tests + // ======================================================================== + + test entity_init_creates_valid_entity + // Verify: initEntity creates valid entity + given result = initEntity("test_entity", 1) + then result.id == 1 and result.vector.trit_count() > 0 + + test hash_string_consistent_for_same_input + // Verify: hashString returns same hash for identical strings + given hash1 = hashString("test") + and hash2 = hashString("test") + then hash1 == hash2 + + test hash_string_different_for_different_input + // Verify: hashString returns different hash for different strings + given hash1 = hashString("test1") + and hash2 = hashString("test2") + then hash1 != hash2 + + test graph_init_creates_empty_graph + // Verify: initGraph creates empty graph + given graph = initGraph() + then graph.entity_count == 0 and graph.relation_count == 0 + + test add_triple_creates_entities_and_relations + // Verify: addTriple creates necessary entities and relations + given graph = initGraph() + when addTriple(&graph, "Paris", "capital_of", "France") + and addTriple(&graph, "Berlin", "capital_of", "Germany") + then graph.entity_count == 3 and graph.relation_count == 1 + + test query_object_finds_correct_entity + // Verify: queryObject returns correct object entity + given graph = initGraph() + when addTriple(&graph, "Paris", "capital_of", "France") + and result = queryObject(&graph, "Paris", "capital_of") + then result != null and "France" in result.name + + test query_subject_finds_correct_entity + // Verify: querySubject returns correct subject entity + given graph = initGraph() + when addTriple(&graph, "Paris", "capital_of", "France") + and result = querySubject(&graph, "capital_of", "France") + then result != null and "Paris" in result.name + + test find_similar_returns_top_results + // Verify: findSimilar returns top N similar entities + given graph = initGraph() + and addTriple(&graph, "Paris", "capital_of", "France") + and addTriple(&graph, "Berlin", "capital_of", "Germany") + and results = findSimilar(&graph, "Paris", 2) + then results.len() >= 2 and results[0].similarity >= results[1].similarity + + test find_entity_returns_entity_when_exists + // Verify: findEntity returns entity when it exists + given graph = initGraph() + and addTriple(&graph, "Paris", "capital_of", "France") + and result = findEntity(&graph, "Paris") + then result != null + + test find_entity_returns_null_when_not_exists + // Verify: findEntity returns null when entity doesn't exist + given graph = initGraph() + and result = findEntity(&graph, "nonexistent") + then result == null + + test stats_returns_correct_counts + // Verify: stats returns correct counts + given graph = initGraph() + and _ = addTriple(&graph, "Paris", "capital_of", "France") + and _ = addTriple(&graph, "Berlin", "capital_of", "Germany") + and _ = addTriple(&graph, "Rome", "capital_of", "Italy") + when s = stats(&graph) + then s.entities == 3 and s.relations == 1 and s.triples == 3 + + // ======================================================================== + // TDD - Invariants + // ======================================================================== + + invariant entity_has_unique_id + // Verify: Each entity has unique identifier + // Entity IDs must be unique across graph + assert true; + + invariant relation_has_unique_id + // Verify: Each relation has unique identifier + // Relation IDs must be unique across graph + assert true; + + invariant triple_references_valid_entities + // Verify: Triple IDs reference valid entities and relations + // Subject and object must be entities, predicate must be relation + assert true; + + invariant graph_vector_represents_all_triples + // Verify: graph_vector encodes all triple operations + // Bundle of all triples must equal graph_vector + assert true; + + invariant entity_count_within_max + // Verify: Entity count never exceeds MAX_ENTITIES + // getOrCreateEntity must enforce capacity limit + assert true; + + invariant triple_count_within_max + // Verify: Triple count never exceeds MAX_TRIPLES + // addTriple must enforce capacity limit + assert true; + + invariant file_magic_valid + // Verify: FILE_MAGIC contains valid bytes + // Magic must be 'T', 'R', 'K', 'G' for recognition + assert true; + + invariant file_version_supported + // Verify: Only FILE_VERSION is supported + // Load must reject unsupported versions + assert true; + + invariant save_load_roundtrip + // Verify: Saving then loading returns identical graph + // Entities, relations, triples must match after roundtrip + assert true; + + invariant query_result_subset_of_entities + // Verify: Query results reference stored entities + // Returned entity pointers must be from entity array + assert true; + + // ======================================================================== + // TDD - Benchmarks + // ======================================================================== + + bench entity_init_latency + // Measure: cycles to create entity with random vector + // Target: < 500 cycles (hash + random init) + @setEvalBranchQuota(10000); + var result = initEntity("test", 1); + _ = result; + + bench graph_add_triple_latency + // Measure: cycles to add triple to empty graph + // Target: < 5000 cycles (entity lookups + VSA operations) + @setEvalBranchQuota(10000); + var graph = initGraph(); + _ = addTriple(&graph, "test", "relation", "object"); + _ = graph; + + bench query_object_latency + // Measure: cycles to query object by subject and predicate + // Target: < 3000 cycles (VSA unbind + find) + @setEvalBranchQuota(10000); + var graph = initGraph(); + _ = addTriple(&graph, "test", "relation", "object"); + _ = queryObject(&graph, "test", "relation"); + _ = graph; + + bench query_subject_latency + // Measure: cycles to query subject by predicate and object + // Target: < 3000 cycles (VSA unbind + find) + @setEvalBranchQuota(10000); + var graph = initGraph(); + _ = addTriple(&graph, "test", "relation", "object"); + _ = querySubject(&graph, "relation", "object"); + _ = graph; + + bench find_similar_latency + // Measure: cycles to find top-N similar entities + // Target: < 10000 cycles (N cosine similarities + sort) + @setEvalBranchQuota(10000); + var graph = initGraph(); + _ = addTriple(&graph, "Paris", "capital_of", "France"); + _ = addTriple(&graph, "Berlin", "capital_of", "Germany"); + _ = findSimilar(&graph, "Paris", 10); + _ = graph; + + bench save_latency_100_entities + // Measure: cycles to save graph with 100 entities + // Target: < 50000 cycles (file write + encoding) + @setEvalBranchQuota(10000); + var graph = initGraph(); + for (0..100) |_| { + _ = addTriple(&graph, "test", "relation", "object"); + } + _ = save(&graph, "/tmp/test_kg.trkg"); + _ = graph; + + bench load_latency_100_entities + // Measure: cycles to load graph with 100 entities + // Target: < 50000 cycles (file read + decoding) + @setEvalBranchQuota(10000); + var name_buffer: [4096]u8 = undefined; + _ = load("/tmp/test_kg.trkg", &name_buffer); + _ = name_buffer; + + bench stats_latency + // Measure: cycles to compute graph statistics + // Target: < 100 cycles (read counts) + @setEvalBranchQuota(10000); + var graph = initGraph(); + _ = stats(&graph); + _ = graph; +} diff --git a/apps/website/public/t27/specs/hslm/forward_pass.t27 b/apps/website/public/t27/specs/hslm/forward_pass.t27 new file mode 100644 index 0000000000..d791c7590b --- /dev/null +++ b/apps/website/public/t27/specs/hslm/forward_pass.t27 @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: Apache-2.0 +// Module: Minimal Forward Pass for LLM Inference +// phi^2 + 1/phi^2 = 3 | TRINITY + +module ForwardPass { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; // Trit enum + use numeric::gf16; // GF16 for similarity scores + use ternary::packed_trit; // PackedBigInt for HV storage + + // ======================================================================== + // 1. Constants + // ======================================================================== + + // Number of attention heads in multi-head architecture + pub const HEAD_COUNT : u8 = 3; + + // Forward pass version + pub const FORWARD_VERSION : u32 = 2; + + // Role vector dimension for VSA operations + pub const ROLE_DIM : u16 = 500; + + // Context window size for autoregression + pub const CONTEXT_WINDOW : u8 = 8; + + // Number of printable ASCII characters for Hebbian + pub const HEBBIAN_CHARS : usize = 95; + + // ASCII offset for printable characters + pub const HEBBIAN_OFFSET : usize = 32; + + // Maximum passes for refinement + pub const MAX_REFINE_PASSES : u8 = 3; + + // ======================================================================== + // 2. Role Vector Type + // ======================================================================== + + // RoleVector: Pre-computed attention role vectors + // Used for forward pass: Q/K/V queries with similarity scoring + // Stores role as PackedBigInt for VSA operations + pub struct RoleVector { + role_id : u8, // Role identifier (0-10) + name : []const u8, // Role name (e.g., "Q", "K", "V") + vector : PackedBigInt, // VSA vector representation + } + + // ======================================================================== + // 3. HyperVector Type + // ======================================================================== + + // HyperVector: Abstract vector type for neural operations + // Stores role vectors, computes attention, bundling, similarity + // Platform-agnostic: implementation may use SIMD or scalar operations + pub struct HyperVector { + data : []PackedBigInt, // Internal role vectors + dim : usize, // Vector dimension + } + + // ======================================================================== + // 4. Forward Pass Output Type + // ======================================================================== + + // ForwardOutput: Result of forward pass operation + // Contains output HV and optional loss metrics + pub struct ForwardOutput { + output : PackedBigInt, // Output hypervector + loss : ?f64, // Optional loss for training mode + } + + // ======================================================================== + // 5. Attention Functions + // ======================================================================== + + // singleHeadAttention(positioned: []RoleVector, q_role: &RoleVector, k_role: &RoleVector, v_role: &RoleVector) -> RoleVector + // Single-head attention: query = bind(Q, K), score similarity with V + // Returns best-matching role vector as value + // Complexity: O(n) where n = ROLE_DIM (VSA bind + similarity) + pub fn singleHeadAttention(positioned: []RoleVector, q_role: &RoleVector, k_role: &RoleVector, v_role: &RoleVector) -> RoleVector; + + // multiHeadAttention(context: []HyperVector, roles: []RoleVector, num_heads: usize) -> ForwardOutput + // Multi-head attention: 3 independent heads merged via bundle3 + // Computes separate Q/K/V attention heads, merges via bundle3 + // Complexity: O(n * h) where n = ROLE_DIM, h = HEAD_COUNT + pub fn multiHeadAttention(context: []HyperVector, roles: []RoleVector, num_heads: usize) -> ForwardOutput; + + // ======================================================================== + // 6. Forward Pass Functions + // ======================================================================== + + // forwardPass(context: []HyperVector, roles: []RoleVector, target: &HyperVector) -> ForwardOutput + // v2.29 single-head forward pass + // Uses Q/K/V attention to compute value = bind(Q/K/V) + // Complexity: O(n) where n = ROLE_DIM (VSA operations) + pub fn forwardPass(context: []HyperVector, roles: []RoleVector, target: &HyperVector) -> ForwardOutput; + + // forwardPassMultiHead(context: []HyperVector, roles: []RoleVector) -> ForwardOutput + // v2.30 multi-head forward pass (3 heads merged) + // Uses independent Q/K/V attention heads, merges via bundle3 + // Complexity: O(n * h) where n = ROLE_DIM, h = HEAD_COUNT + pub fn forwardPassMultiHead(context: []HyperVector, roles: []RoleVector) -> ForwardOutput; + + // ======================================================================== + // 7. Training Functions + // ======================================================================== + + // resonatorTrainStep(context: []HyperVector, target: &HyperVector, roles: []RoleVector, dim: usize) -> f64 + // Train FF1 and FF2 roles for one step + // Uses bind-based targeted correction + // Returns loss (1 - similarity) for this sample + // Complexity: O(n * h) where n = ROLE_DIM, h = 2 (FF roles) + pub fn resonatorTrainStep(context: []HyperVector, target: &HyperVector, roles: []RoleVector, dim: usize) -> f64; + + // refineDirectRole(corpus: []const u8, dim: usize, initial_role: &RoleVector, num_passes: usize) -> RoleVector + // Iteratively refine direct role using error measurement + // Measures error, computes sparse correction, blends with sparsified annealing + // Complexity: O(p * n * m) where p = num_passes, n = dim, m = CORPUS_SIZE + pub fn refineDirectRole(corpus: []const u8, dim: usize, initial_role: &RoleVector, num_passes: usize) -> RoleVector; + + // ======================================================================== + // 8. Context Functions + // ======================================================================== + + // summarizeContext(context: []HyperVector) -> HyperVector + // Permute and bundle 8 context vectors into 1 summary HV + // Uses positional permutation then sequential bundling + // Complexity: O(n) where n = ROLE_DIM (permute + bundle operations) + pub fn summarizeContext(context: []HyperVector) -> HyperVector; + + // ======================================================================== + // 9. Direct Role Functions + // ======================================================================== + + // computeDirectRole(corpus: []const u8, dim: usize, offsets: []const usize, context_size: usize) -> RoleVector + // Pre-compute ideal direct role from corpus + // Uses charToHV encoding and bundle operations + // Complexity: O(c * n) where c = context_size, n = dim + pub fn computeDirectRole(corpus: []const u8, dim: usize, offsets: []const usize, context_size: usize) -> RoleVector; + + // directDecode(context: []HyperVector, role: &RoleVector, dim: usize) -> []u8 + // Decode output HV to character sequence + // Uses learned role to predict next character + // Complexity: O(n) where n = output length + pub fn directDecode(context: []HyperVector, role: &RoleVector, dim: usize) -> []u8; + + // ======================================================================== + // 10. Generation Functions + // ======================================================================== + + // generateWithDirectRole(initial_context: []HyperVector, role: &RoleVector, output_buf: []u8, max_tokens: usize) -> usize + // Autoregressive generation using pre-computed direct role + // Shifts context, predicts tokens using directDecode + // Complexity: O(t * n) where t = max_tokens, n = ROLE_DIM + pub fn generateWithDirectRole(initial_context: []HyperVector, role: &RoleVector, output_buf: []u8, max_tokens: usize) -> usize; + + // ======================================================================== + // 11. Hebbian Functions + // ======================================================================== + + // buildHebbianCounts(corpus: []const u8) -> [HEBBIAN_CHARS][HEBBIAN_CHARS]u16 + // Build character-pair association matrix from corpus + // Returns counts[a][b] = frequency of char b following char a + // Complexity: O(c * m) where c = corpus.len, m = HEBBIAN_CHARS^2 + pub fn buildHebbianCounts(corpus: []const u8) -> [HEBBIAN_CHARS][HEBBIAN_CHARS]u16; + + // hebbianLookup(counts: *const [HEBBIAN_CHARS][HEBBIAN_CHARS]u16, char_idx: usize, dim: usize) -> HyperVector + // Look up character successor using Hebbian matrix + // Bundles successors weighted by count, proportional to frequency + // Complexity: O(n * m) where n = HEBBIAN_CHARS, m = dim + pub fn hebbianLookup(counts: *const [HEBBIAN_CHARS][HEBBIAN_CHARS]u16, char_idx: usize, dim: usize) -> HyperVector; + + // ======================================================================== + // TDD - Tests + // ======================================================================== + + test single_head_attention_returns_role + // Verify: singleHeadAttention returns valid role + given result = singleHeadAttention([], q_role, k_role, v_role) + then result.role_id == 10 + + test summarize_context_creates_valid_hv + // Verify: summarizeContext bundles 8 vectors correctly + given result = summarizeContext(context) + then result.dim == ROLE_DIM + + test direct_decode_returns_chars + // Verify: directDecode returns character sequence + given result = directDecode(context, role, ROLE_DIM) + then result.len() == 10 + + test hebbian_counts_correct_for_corpus + // Verify: buildHebbianCounts counts pairs correctly + given corpus = "hello" + and counts = buildHebbianCounts(corpus) + then counts['h']['e'] == 1 and counts['l']['l'] == 2 + + test hebbian_lookup_returns_valid_hv + // Verify: hebbianLookup returns valid hypervector + given counts = buildHebbianCounts("ab") + and result = hebbianLookup(&counts, 0, ROLE_DIM) + then result.dim == ROLE_DIM + + // ======================================================================== + // TDD - Invariants + // ======================================================================== + + invariant role_vector_dimension_matches + // Verify: All role vectors have consistent dimension + // ROLE_DIM must match across all operations + assert true; + + invariant forward_output_dim_consistency + // Verify: Forward output dimension matches ROLE_DIM + // Output HV must match configured dimension + assert true; + + invariant hebbian_matrix_size + // Verify: Hebbian matrix has correct dimensions + // Must be HEBBIAN_CHARS x HEBBIAN_CHARS + assert true; + + invariant context_window_fixed + // Verify: CONTEXT_WINDOW is constant + // Autoregressive sliding window must be fixed size + assert true; + + invariant forward_version_matches_spec + // Verify: FORWARD_VERSION corresponds to architecture + // Version 2 = multi-head with 3 heads + assert true; + + // ======================================================================== + // TDD - Benchmarks + // ======================================================================== + + bench single_head_attention_latency + // Measure: cycles for single-head attention operation + // Target: < 5000 cycles (similarity scoring) + @setEvalBranchQuota(10000); + var result = singleHeadAttention([], q_role, k_role, v_role); + _ = result; + + bench multi_head_attention_latency + // Measure: cycles for multi-head attention operation + // Target: < 10000 cycles (3 heads + bundle) + @setEvalBranchQuota(10000); + var context = [role1, role2, role3]; + var roles = [q_role, k_role, v_role]; + _ = multiHeadAttention(&context, &roles, HEAD_COUNT); + _ = context; + + bench forward_pass_latency + // Measure: cycles for single-head forward pass + // Target: < 10000 cycles (attention + decode) + @setEvalBranchQuota(10000); + var context = [role1, role2, role3]; + var roles = [q_role, k_role, v_role]; + _ = forwardPass(&context, &roles, target_role); + _ = context; + + bench forward_pass_multi_head_latency + // Measure: cycles for multi-head forward pass + // Target: < 15000 cycles (3 attention heads + bundle) + @setEvalBranchQuota(10000); + var context = [role1, role2, role3]; + var roles = [q_role, k_role, v_role]; + _ = forwardPassMultiHead(&context, &roles, target_role); + _ = context; + + bench summarize_context_latency + // Measure: cycles for context summarization + // Target: < 3000 cycles (permute + 7 bundles) + @setEvalBranchQuota(10000); + var context = [role1, role2, role3, role4, role5, role6, role7, role8]; + _ = summarizeContext(&context); + _ = context; + + bench direct_decode_latency + // Measure: cycles for direct decode (10 tokens) + // Target: < 5000 cycles (10 character predictions) + @setEvalBranchQuota(10000); + var context = [role1, role2, role3, role4, role5, role6, role7, role8]; + _ = directDecode(&context, target_role, ROLE_DIM); + _ = context; + + bench hebbian_counts_build_latency + // Measure: cycles to build Hebbian counts from corpus + // Target: < 100000 cycles (corpus scan + count) + @setEvalBranchQuota(10000); + var corpus = "the quick brown fox jumps over the lazy dog"; + _ = buildHebbianCounts(corpus); + _ = corpus; + + bench hebbian_lookup_latency + // Measure: cycles for Hebbian character lookup + // Target: < 2000 cycles (weighted bundle) + @setEvalBranchQuota(10000); + var corpus = "the quick brown fox"; + var counts = buildHebbianCounts(corpus); + _ = hebbianLookup(&counts, 4, ROLE_DIM); + _ = counts; +} diff --git a/apps/website/public/t27/specs/interop/gf_cross_language.t27 b/apps/website/public/t27/specs/interop/gf_cross_language.t27 new file mode 100644 index 0000000000..c6e241d425 --- /dev/null +++ b/apps/website/public/t27/specs/interop/gf_cross_language.t27 @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 +// GoldenFloat Cross-Language Conformance +// All languages must produce identical bits for identical inputs. +// Reference constant: GF32(phi) = 0x3FCF1BBD + +module GFCrossLanguageConformance { + import math::constants; + import ffi::gf16::*; + import ffi::gf32::*; + + // -- Test 1: phi encoding -------------------------------------------------- + test phi_gf32_bits { + const phi = 1.618033988749895_f64; + const gf32_bits = gf32_from_f64(phi); + // phi in GF32 (approximately IEEE f32 bits since GF32 maps to f32) + const expected = 0x3FCF1BBD_u32; + then gf32_bits == expected + } + + // -- Test 2: Sign extraction --------------------------------------------- + test phi_ffi_encoding { + const phi = constants::PHI; + const gf16_phi = gf16_from_f64(phi); + const gf32_phi = gf32_from_f64(phi); + then gf16_extract_sign(gf16_phi) == 0 + and gf16_extract_exponent(gf16_phi) > 0 + and gf32_extract_sign(gf32_phi) == 0 + and gf32_extract_exponent(gf32_phi) > 0 + } + + // -- Test 3: Roundtrip precision ----------------------------------------- + test roundtrip_gf16_precision { + const test_vals = [1.618, 3.14159, 2.71828, 1.0, -1.0, 0.5]; + for val in test_vals { + const encoded = gf16_from_f64(val); + const decoded = gf16_to_f64(encoded); + then abs(val - decoded) < 0.01 + } + } + + // -- Test 4: Classification ---------------------------------------------- + test classification_functions { + const zero = gf16_from_f64(0.0); + const inf = gf16_from_f64(1e38_f64); + const nan = 0xFFFF_u16; // max exp + nonzero mant + then gf16_is_zero(zero) == true + and gf16_is_inf(inf) == true + and gf16_is_nan(nan) == true + and gf16_is_nan(zero) == false + } + + // -- Test 5: Arithmetic -------------------------------------------------- + test gf16_addition { + const a = gf16_from_f64(1.0); + const b = gf16_from_f64(0.618); + const sum = gf16_add(a, b); + const result = gf16_to_f64(sum); + then abs(result - 1.618) < 0.01 + } + + // -- Test 6: FPGA-Safety contract ---------------------------------------- + // All encode/decode must work on u16/u32 only -- no f64 in compute + test zero_roundtrip { + const zero_enc = gf16_from_f64(0.0); + const zero_dec = gf16_to_f64(zero_enc); + then zero_dec == 0.0 + and gf16_is_zero(zero_enc) == true + } + + // -- Test 7: Mantissa extraction consistency ------------------------------ + test mantissa_extraction { + const phi_gf16 = gf16_from_f64(1.618); + const phi_gf32 = gf32_from_f64(1.618); + + // Mantissa should be different due to different bit allocations + const mant16 = gf16_extract_mantissa(phi_gf16); + const mant32 = gf32_extract_mantissa(phi_gf32); + + then mant16 != 0 + and mant32 != 0 + } + + // -- Test 8: Special values roundtrip ------------------------------------- + test special_values_roundtrip { + // Zero + const zero_enc = gf16_from_f64(0.0); + const zero_dec = gf16_to_f64(zero_enc); + const zero_ok = zero_dec == 0.0 && gf16_is_zero(zero_enc); + + // Negative zero + const neg_zero_enc = gf16_from_f64(-0.0); + const neg_zero_dec = gf16_to_f64(neg_zero_enc); + const neg_zero_ok = neg_zero_dec == -0.0 && gf16_is_zero(neg_zero_enc); + + then zero_ok + and neg_zero_ok + } +} diff --git a/apps/website/public/t27/specs/isa/registers.t27 b/apps/website/public/t27/specs/isa/registers.t27 new file mode 100644 index 0000000000..76b5783401 --- /dev/null +++ b/apps/website/public/t27/specs/isa/registers.t27 @@ -0,0 +1,599 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/registers.t27 +// TRI27 ISA Register File Specification +// Register definitions, Coptic encoding, and register file operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module ISARegisters { + // Import base types + use base::types; + + // ================================================================= + // 1. Register Constants + // ========================================================================= + + // Register file configuration (Coptic compatibility: 27 registers) + const NUM_REGISTERS : usize = 27; // R0-R26 + const REG_WIDTH : usize = 27; // Each register holds 27 trits (TernaryWord) + const COPIC_BASE : usize = 27; // Coptic numerals base + + // Agent-register binding (Ring 045) + const AGENT_COUNT : usize = 27; // 27 agents map to 27 registers + + // Register identifiers + const R0 : u8 = 0; // Zero register (always reads as 0) + const R1 : u8 = 1; // Argument/return register + const R2 : u8 = 2; // Argument/return register + const R3 : u8 = 3; // Argument register + const R4 : u8 = 4; // Argument register + const R5 : u8 = 5; // Temporary register + const R6 : u8 = 6; // Temporary register + const R7 : u8 = 7; // Temporary register + const R8 : u8 = 8; // Temporary register + const R9 : u8 = 9; // Temporary register + const R10 : u8 = 10; // Saved register S0 + const R11 : u8 = 11; // Saved register S1 + const R12 : u8 = 12; // Saved register S2 + const R13 : u8 = 13; // Saved register S3 + const R14 : u8 = 14; // Saved register S4 + const R15 : u8 = 15; // Saved register S5 + const R16 : u8 = 16; // Frame pointer + const R17 : u8 = 17; // Stack pointer + const R18 : u8 = 18; // Link register + const R19 : u8 = 19; // Program counter + const R20 : u8 = 20; // Status register + const R21 : u8 = 21; // Exception handler + const R22 : u8 = 22; // Kernel stack pointer + const R23 : u8 = 23; // Thread pointer + const R24 : u8 = 24; // Reserved for architecture + const R25 : u8 = 25; // Reserved for architecture + const R26 : u8 = 26; // Reserved for architecture + + // Status register flags (R20) + const FLAG_ZERO : u8 = 0; // Zero flag + const FLAG_NEG : u8 = 1; // Negative flag + const FLAG_CARRY : u8 = 2; // Carry flag + const FLAG_OVERFLOW : u8 = 3; // Overflow flag + const FLAG_TRAP : u8 = 4; // Trap flag + const FLAG_INTERRUPT : u8 = 5; // Interrupt enable + + // ================================================================= + // 2. Coptic Alphabet Encoding + // ========================================================================= + + // Coptic alphabet encoding (27 letters for 27 registers) + // Maps register numbers to Coptic Unicode code points + const COPTIC_ALPHABET : [27]u32 = [ + 0x03B1, // alpha = alpha (R0) + 0x03B2, // beta = bita (R1) + 0x03B3, // gamma = gamma (R2) + 0x03B4, // delta = dalda (R3) + 0x03B5, // epsilon = ei (R4) + 0x03C6, // phi = sima (R5) + 0x03B6, // zeta = zata (R6) + 0x03B7, // eta = ita (R7) + 0x03B8, // theta = thita (R8) + 0x03B9, // iota = iota (R9) + 0x03BA, // kappa = kappa (R10) + 0x03BB, // lambda = lauda (R11) + 0x03BC, // mu = mi (R12) + 0x03BD, // nu = ni (R13) + 0x03BE, // xi = ksi (R14) + 0x03C0, // pi = pi (R15) + 0x03C1, // rho = ro (R16) + 0x03C3, // sigma = sigma (R17) + 0x03C4, // tau = tau (R18) + 0x03C5, // upsilon = upsilon (R19) + 0x03C6, // phi = fi (R20) + 0x03C7, // chi = khi (R21) + 0x03C8, // psi = psi (R22) + 0x03C9, // omega = ou (R23) + 0x0417, // 304 = sampi (R24) + 0x0418, // 305 = koppa (R25) + 0x0419, // 306 = 307hei (R26) + ]; + + // ================================================================= + // 3. Register File State + // ========================================================================= + + // Register file: 27 registers, each holding a TernaryWord (27 trits) + // Stored as u32 for efficiency (27 trits fits in 32 bits) + var register_file : [NUM_REGISTERS]TernaryWord = [TernaryWord{.raw = 0}; NUM_REGISTERS]; + + // ================================================================= + // 4. Register Read/Write + // ========================================================================= + + // reg_read(reg: u8) -> TernaryWord + // Read from register file + // R0 always returns 0 (zero register) + // Returns error sentinel if register number is invalid + fn reg_read(reg: u8) -> TernaryWord { + // Check for R0 (zero register) + if (reg == R0) { + return TernaryWord{ .raw = 0 }; + } + + // Validate register number + if (reg > R26) { + // Invalid register - trap or return error + return TernaryWord{ .raw = 0 }; // Error case + } + + return register_file[reg]; + } + + // reg_write(reg: u8, value: TernaryWord) -> bool + // Write to register file + // R0 writes are ignored (zero register is read-only) + // Returns true if write succeeded, false if invalid register + fn reg_write(reg: u8, value: TernaryWord) -> bool { + // Check for R0 (zero register - ignore writes) + if (reg == R0) { + return false; + } + + // Validate register number + if (reg > R26) { + return false; + } + + register_file[reg] = value; + return true; + } + + // ================================================================= + // 5. Coptic Encoding Conversion + // ========================================================================= + + // reg_to_coptic(reg: u8) -> u32 + // Convert register number to Coptic Unicode code point + // Returns 0 if register number is invalid + fn reg_to_coptic(reg: u8) -> u32 { + if (reg > R26) { + return 0; + } + return COPTIC_ALPHABET[reg]; + } + + // coptic_to_reg(cp: u32) -> u8 + // Convert Coptic Unicode code point to register number + // Returns 0xFF if not a valid register character + fn coptic_to_reg(cp: u32) -> u8 { + var i : u8 = 0; + + while (i < 27) { + if (COPTIC_ALPHABET[i] == cp) { + return i; + } + i = i + 1; + } + + return 0xFF; // Error sentinel + } + + // ================================================================= + // 6. Status Register Operations + // ========================================================================= + + // status_read(flag: u8) -> bool + // Read a specific flag from status register (R20) + // Returns true if flag is set, false otherwise + fn status_read(flag: u8) -> bool { + const status_val = reg_read(R20); + const mask = 1u32 << flag; + return (status_val.raw & mask) != 0; + } + + // status_write(flag: u8, value: bool) -> bool + // Set or clear a specific flag in status register (R20) + // Returns true if operation succeeded + fn status_write(flag: u8, value: bool) -> bool { + if (flag > 5) { + return false; + } + + var status_val = reg_read(R20); + const mask = 1u32 << flag; + + if (value) { + status_val.raw = status_val.raw | mask; + } else { + status_val.raw = status_val.raw & ~mask; + } + + return reg_write(R20, status_val); + } + + // ================================================================= + // 7. Stack Operations (using R17 as stack pointer) + // ========================================================================= + + // push_reg(reg: u8) -> bool + // Push register value to stack (using R17 as stack pointer) + // Stack grows downward (decrement before store) + // Returns true if push succeeded + fn push_reg(reg: u8) -> bool { + // Read register value + const value = reg_read(reg); + + // Get stack pointer (R17) + var sp = reg_read(R17); + + // Decrement SP (each stack entry is 4 bytes) + sp.raw = sp.raw - 4; + + // Write back SP + if (!reg_write(R17, sp)) { + return false; + } + + // Store value to [SP] + // (In real implementation, would write to memory at address SP) + // For spec, we simulate with a separate stack buffer + // ... + + return true; + } + + // pop_reg(reg: u8) -> bool + // Pop value from stack into register (using R17 as stack pointer) + // Stack grows downward (load then increment) + // Returns true if pop succeeded + fn pop_reg(reg: u8) -> bool { + // Get stack pointer (R17) + var sp = reg_read(R17); + + // Load value from [SP] + // (In real implementation, would read from memory at address SP) + const value = TernaryWord{ .raw = 0 }; // Placeholder + + // Increment SP + sp.raw = sp.raw + 4; + + // Write back SP + if (!reg_write(R17, sp)) { + return false; + } + + // Write value to register + return reg_write(reg, value); + } + + // ================================================================= + // 8. Context Save/Restore + // ========================================================================= + + // save_context() -> void + // Save caller-saved registers (R5-R9) to stack + // Used by function prologue + fn save_context() -> void { + push_reg(R5); + push_reg(R6); + push_reg(R7); + push_reg(R8); + push_reg(R9); + } + + // restore_context() -> void + // Restore caller-saved registers from stack + // Used by function epilogue + fn restore_context() -> void { + pop_reg(R9); + pop_reg(R8); + pop_reg(R7); + pop_reg(R6); + pop_reg(R5); + } + + // ================================================================= + // 9. Register Aliases + // ========================================================================= + + // Argument/return registers + const ARG0 : u8 = R1; + const ARG1 : u8 = R2; + const ARG2 : u8 = R3; + const ARG3 : u8 = R4; + + // Temporary registers (caller-saved) + const TMP0 : u8 = R5; + const TMP1 : u8 = R6; + const TMP2 : u8 = R7; + const TMP3 : u8 = R8; + const TMP4 : u8 = R9; + + // Saved registers (callee-saved) + const SAVED0 : u8 = R10; + const SAVED1 : u8 = R11; + const SAVED2 : u8 = R12; + const SAVED3 : u8 = R13; + const SAVED4 : u8 = R14; + const SAVED5 : u8 = R15; + + // Special purpose registers + const FP : u8 = R16; // Frame pointer + const SP : u8 = R17; // Stack pointer + const LR : u8 = R18; // Link register + const PC : u8 = R19; // Program counter + const STATUS : u8 = R20; // Status register + const EH : u8 = R21; // Exception handler + const KSP : u8 = R22; // Kernel stack pointer + const TP : u8 = R23; // Thread pointer + + // ======================================================================================================= + // TDD-Inside-Spec: Tests and Invariants for ISARegisters + // ======================================================================================================= + + test isa_r0_always_zero + given word = TernaryWord{.raw = 0x123456} + and reg_write(R0, word) + and result = reg_read(R0) + then result.raw == 0 + + test isa_r0_write_ignored + given before = reg_read(R0) + and success = reg_write(R0, TernaryWord{.raw = 0xDEADBEEF}) + and after = reg_read(R0) + then success == false and before.raw == after.raw and after.raw == 0 + + test isa_register_count_27 + given count = NUM_REGISTERS + then count == 27 + + test isa_valid_register_write_succeeds + given value = TernaryWord{.raw = 0xABCDEF} + and success = reg_write(R10, value) + then success == true + + test isa_valid_register_write_read_roundtrip + given value = TernaryWord{.raw = 0x123456} + and reg_write(R10, value) + and result = reg_read(R10) + then result.raw == value.raw + + test isa_invalid_register_write_fails + given value = TernaryWord{.raw = 0x123456} + and success = reg_write(27, value) // R27 doesn't exist + then success == false + + test isa_invalid_register_read_returns_zero + given result = reg_read(27) + then result.raw == 0 + + test isa_reg_to_coptic_r0 + given cp = reg_to_coptic(R0) + then cp == 0x03B1 // alpha + + test isa_reg_to_coptic_r10 + given cp = reg_to_coptic(R10) + then cp == 0x03BA // kappa + + test isa_reg_to_coptic_r26 + given cp = reg_to_coptic(R26) + then cp == 0x0419 // 1492 + + test isa_coptic_to_reg_alpha + given reg = coptic_to_reg(0x03B1) // alpha + then reg == R0 + + test isa_coptic_to_reg_kappa + given reg = coptic_to_reg(0x03BA) // kappa + then reg == R10 + + test isa_coptic_to_reg_invalid + given reg = coptic_to_reg(0x0041) // 'A' - not Coptic + then reg == 0xFF + + test isa_coptic_roundtrip_r0 + given original_cp = reg_to_coptic(R0) + and recovered_reg = coptic_to_reg(original_cp) + and recovered_cp = reg_to_coptic(recovered_reg) + then original_cp == recovered_cp + + test isa_coptic_roundtrip_r15 + given original_cp = reg_to_coptic(R15) + and recovered_reg = coptic_to_reg(original_cp) + and recovered_cp = reg_to_coptic(recovered_reg) + then original_cp == recovered_cp + + test isa_status_read_initial_false + given flag_val = status_read(FLAG_ZERO) + then flag_val == false + + test isa_status_write_set + given status_write(FLAG_ZERO, true) + and flag_val = status_read(FLAG_ZERO) + then flag_val == true + + test isa_status_write_clear + given status_write(FLAG_ZERO, true) + and status_write(FLAG_ZERO, false) + and flag_val = status_read(FLAG_ZERO) + then flag_val == false + + test isa_status_flags_independent + given status_write(FLAG_ZERO, true) + and status_write(FLAG_NEG, true) + and zero_val = status_read(FLAG_ZERO) + and neg_val = status_read(FLAG_NEG) + then zero_val == true and neg_val == true + + test isa_status_invalid_flag + given success = status_write(10, true) // Invalid flag + then success == false + + test isa_register_aliases_match + given arg0 = ARG0 and arg1 = ARG1 and sp = SP and fp = FP + then arg0 == R1 and arg1 == R2 and sp == R17 and fp == R16 + + test isa_coptic_alphabet_size_27 + given size = COPTIC_ALPHABET.len() + then size == 27 + + invariant isa_num_registers_constant + assert NUM_REGISTERS == 27 + + invariant isa_reg_width_is_27 + assert REG_WIDTH == 27 + + invariant isa_coptic_base_constant + assert COPIC_BASE == 27 + + invariant isa_coptic_alphabet_size_matches_registers + assert COPTIC_ALPHABET.len() == NUM_REGISTERS + + invariant isa_r0_readonly_zero + given val = reg_read(R0) + assert val.raw == 0 + + invariant isa_r0_write_no_op + given original = reg_read(R0) + and reg_write(R0, TernaryWord{.raw = 0xFFFFFFFF}) + and after = reg_read(R0) + assert original.raw == after.raw and after.raw == 0 + + invariant isa_coptic_to_reg_is_inverse_of_reg_to_coptic + for (const i) |reg| in [0u8, 5, 10, 15, 20, 26] { + const cp = reg_to_coptic(reg); + const recovered = coptic_to_reg(cp); + assert recovered == reg; + } + + invariant isa_coptic_alphabet_unique + given seen = [false; 256] // Track seen code points + and all_unique = true + and idx = 0 + while (idx < COPTIC_ALPHABET.len()) { + const cp = COPTIC_ALPHABET[idx]; + if (cp < 256 and seen[cp]) { + all_unique = false; + break; + } + if (cp < 256) { seen[cp] = true; } + idx = idx + 1; + } + assert all_unique + + invariant isa_status_flags_in_range + assert FLAG_ZERO <= 5 and FLAG_INTERRUPT <= 5 + + invariant isa_register_aliases_correct + assert ARG0 == R1 and ARG1 == R2 and ARG2 == R3 and ARG3 == R4 + assert TMP0 == R5 and TMP1 == R6 and TMP2 == R7 and TMP3 == R8 and TMP4 == R9 + assert SAVED0 == R10 and SAVED1 == R11 and SAVED2 == R12 and SAVED3 == R13 + assert FP == R16 and SP == R17 and LR == R18 and PC == R19 and STATUS == R20 + + invariant isa_register_indices_valid + assert R0 == 0 and R10 == 10 and R20 == 20 and R26 == 26 + + invariant isa_max_register_is_26 + assert R26 == NUM_REGISTERS - 1 + + // ================================================================= + // Ring 045: Agent Binding and Trit State Invariants + // ========================================================================= + + // agent_register_bijection: Each agent maps to exactly one register + invariant agent_register_bijection + assert AGENT_COUNT == NUM_REGISTERS + assert NUM_REGISTERS == 27 + assert COPIC_BASE == 27 + + // trit_state: All trit values must be in {-1, 0, +1} + invariant trit_state + given r0 = reg_read(R0) + and r1 = reg_read(R1) + and r10 = reg_read(R10) + and r26 = reg_read(R26) + // All trit values in registers must be valid + // (verified by TernaryWord type constraints) + + // no_trit_overflow: All register operations produce valid trits + invariant no_trit_overflow + // Register read/write operations preserve trit validity + given value = TernaryWord{.raw = 0x123456} + and reg_write(R10, value) + and result = reg_read(R10) + assert result.raw == value.raw + + // queen_register_special: T (Queen) register = register index 0 (Tau/Alpha) + // The Queen register is R0 (Alpha/alpha), the first Coptic letter + invariant queen_register_special + assert R0 == 0 + assert reg_to_coptic(R0) == 0x03B1 // alpha (Alpha, representing Queen/Tau) + + // register_initial_zero: All registers initialize to 0 + invariant register_initial_zero + given i = 0 + while (i < NUM_REGISTERS) { + const val = reg_read(i as u8); + if (i == 0) { + assert val.raw == 0; // R0 is always 0 + } + // Other registers have initial state (managed by register_file) + i = i + 1; + } + + // Tests for Ring 045 requirements + + test register_reset + // All registers initialize to 0 + given r0_val = reg_read(R0) + and r1_val = reg_read(R1) + and r10_val = reg_read(R10) + and r26_val = reg_read(R26) + then r0_val.raw == 0 and r1_val.raw == 0 and r10_val.raw == 0 and r26_val.raw == 0 + + test queen_register_special + // T (Queen) register = register index 0 (Tau/Alpha) + given queen_idx = R0 + and queen_cp = reg_to_coptic(queen_idx) + then queen_idx == 0 and queen_cp == 0x03B1 // alpha + + test agent_count_equals_register_count + given agents = AGENT_COUNT + and registers = NUM_REGISTERS + then agents == registers and registers == 27 + + test trit_values_valid + // Verify trit arithmetic doesn't overflow + given a = TernaryWord{.raw = 0x123} + and b = TernaryWord{.raw = 0x456} + and reg_write(R10, a) + and result = reg_read(R10) + then result.raw == a.raw + + bench isa_reg_read_latency + measure: nanoseconds to reg_read(R10) + target: < 50ns + + bench isa_reg_write_latency + measure: nanoseconds to reg_write(R10, TernaryWord{.raw = 0x123456}) + target: < 50ns + + bench isa_reg_to_coptic_latency + measure: nanoseconds to reg_to_coptic(R15) + target: < 20ns + + bench isa_coptic_to_reg_latency + measure: nanoseconds to coptic_to_reg(0x03C0) // pi + target: < 100ns + + bench isa_status_read_latency + measure: nanoseconds to status_read(FLAG_ZERO) + target: < 100ns + + bench isa_status_write_latency + measure: nanoseconds to status_write(FLAG_ZERO, true) + target: < 150ns + + bench isa_save_context_latency + measure: nanoseconds to save_context() + target: < 500ns + + bench isa_restore_context_latency + measure: nanoseconds to restore_context() + target: < 500ns +} diff --git a/apps/website/public/t27/specs/isa/ternary_arithmetic.t27 b/apps/website/public/t27/specs/isa/ternary_arithmetic.t27 new file mode 100644 index 0000000000..d76744ae63 --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_arithmetic.t27 @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/ternary_arithmetic.t27 +// Ternary Arithmetic Operations Specification +// Ring 064 - Balanced ternary arithmetic for T27 +// Defines addition, subtraction, multiplication, and division +// phi^2 + 1/phi^2 = 3 | TRINITY + +module TernaryArithmetic { + use base::types; + + // ===================================================== + // 1. Ternary Values and Constants + // ========================================================================= + + // Balanced ternary values + const TRIT_NEG : i32 = -1; // Negative + const TRIT_ZERO : i32 = 0; // Zero + const TRIT_POS : i32 = 1; // Positive + + // Trit range for validation + const MIN_TRIT : i32 = -1; + const MAX_TRIT : i32 = 1; + + // Number of trits in a word (configurable) + const TRITS_PER_WORD : usize = 27; // 3^27 states (~7.6 trillion) + + // ===================================================== + // 2. Single Trit Addition + // ========================================================================= + + // trit_add(a: i32, b: i32, carry_in: i32) -> TritAddResult + // Add two trits with carry in, return sum and carry out + // Balanced ternary: -1, 0, 1 + // Result can be -2, -1, 0, 1, 2 (needs carry) + fn trit_add(a: i32, b: i32, carry_in: i32) -> TritAddResult { + var sum = a + b + carry_in; + + // Normalize balanced ternary + var carry_out : i32 = 0; + + if (sum > 1) { + // 2 or 3 -> 1 or 0 with carry +1 + carry_out = 1; + sum = sum - 3; + } else if (sum < -1) { + // -2 or -3 -> -1 or 0 with carry -1 + carry_out = -1; + sum = sum + 3; + } + + return TritAddResult{ + .sum = sum, + .carry_out = carry_out, + }; + } + + struct TritAddResult { + sum : i32, + carry_out : i32, + } + + // ===================================================== + // 3. Single Trit Subtraction + // ========================================================================= + + // trit_sub(a: i32, b: i32, borrow_in: i32) -> TritSubResult + // Subtract b from a with borrow in + fn trit_sub(a: i32, b: i32, borrow_in: i32) -> TritSubResult { + // Subtraction = addition with negated subtrahend + const neg_b = -b; + const result = trit_add(a, neg_b, borrow_in); + + // Borrow is just negative carry in balanced ternary + return TritSubResult{ + .diff = result.sum, + .borrow_out = -result.carry_out, + }; + } + + struct TritSubResult { + diff : i32, + borrow_out : i32, + } + + // ===================================================== + // 4. Single Trit Multiplication + // ========================================================================= + + // trit_mul(a: i32, b: i32) -> i32 + // Multiply two trits + // Result: -1, 0, or 1 (no carry needed for single trit) + fn trit_mul(a: i32, b: i32) -> i32 { + return a * b; + } + + // ===================================================== + // 5. Word-Level Addition + // ========================================================================= + + // ternary_word_add(a: []i32, b: []i32, len: usize) -> WordAddResult + // Add two ternary words with carry propagation + fn ternary_word_add(a: []i32, b: []i32, len: usize) -> WordAddResult { + var result : WordAddResult = undefined; + var carry : i32 = 0; + var i : usize = 0; + + while (i < len) { + const add_result = trit_add(a[i], b[i], carry); + result.sum[i] = add_result.sum; + carry = add_result.carry_out; + i = i + 1; + } + + result.carry_out = carry; + result.overflow = carry != 0; + + return result; + } + + struct WordAddResult { + sum : [TRITS_PER_WORD]i32, + carry_out : i32, + overflow : bool, + } + + // ===================================================== + // 6. Word-Level Subtraction + // ========================================================================= + + // ternary_word_sub(a: []i32, b: []i32, len: usize) -> WordSubResult + // Subtract b from a with borrow propagation + fn ternary_word_sub(a: []i32, b: []i32, len: usize) -> WordSubResult { + var result : WordSubResult = undefined; + var borrow : i32 = 0; + var i : usize = 0; + + while (i < len) { + const sub_result = trit_sub(a[i], b[i], borrow); + result.diff[i] = sub_result.diff; + borrow = sub_result.borrow_out; + i = i + 1; + } + + result.borrow_out = borrow; + result.underflow = borrow != 0; + + return result; + } + + struct WordSubResult { + diff : [TRITS_PER_WORD]i32, + borrow_out : i32, + underflow : bool, + } + + // ===================================================== + // 7. Ternary to Decimal Conversion + // ========================================================================= + + // ternary_to_decimal(trits: []i32, len: usize) -> i64 + // Convert balanced ternary to decimal integer + fn ternary_to_decimal(trits: []i32, len: usize) -> i64 { + var result : i64 = 0; + var power : i64 = 1; + var i : usize = 0; + + while (i < len) { + result = result + @as(i64, @intCast(trits[i])) * power; + power = power * 3; + i = i + 1; + } + + return result; + } + + // decimal_to_ternary(value: i64, trits: []i32, len: usize) -> usize + // Convert decimal integer to balanced ternary + // Returns number of trits used + fn decimal_to_ternary(value: i64, trits: []i32, len: usize) -> usize { + var val = value; + var i : usize = 0; + + while (i < len and val != 0) { + const rem = @rem(val, 3); + + if (rem == 2) { + trits[i] = TRIT_NEG; + val = val / 3 + 1; + } else if (rem == -2) { + trits[i] = TRIT_POS; + val = val / 3 - 1; + } else { + trits[i] = @as(i32, @intCast(rem)); + val = val / 3; + } + + i = i + 1; + } + + // Pad remaining trits with zero + while (i < len) { + trits[i] = TRIT_ZERO; + i = i + 1; + } + + // Find actual length used + var actual_len : usize = len; + while (actual_len > 1 and trits[actual_len - 1] == TRIT_ZERO) { + actual_len = actual_len - 1; + } + + return actual_len; + } + + // ===================================================== + // 8. Validation Functions + // ========================================================================= + + // is_valid_trit(t: i32) -> bool + // Check if a value is a valid trit + fn is_valid_trit(t: i32) -> bool { + return t >= MIN_TRIT and t <= MAX_TRIT; + } + + // validate_trits(trits: []i32, len: usize) -> bool + // Validate all trits in an array + fn validate_trits(trits: []i32, len: usize) -> bool { + var i : usize = 0; + while (i < len) { + if (!is_valid_trit(trits[i])) { + return false; + } + i = i + 1; + } + return true; + } + + // ===================================================== + // 9. Comparison Operations + // ========================================================================= + + // ternary_compare(a: []i32, b: []i32, len: usize) -> i32 + // Compare two ternary words + // Returns -1 if a < b, 0 if a == b, 1 if a > b + fn ternary_compare(a: []i32, b: []i32, len: usize) -> i32 { + // Compare from most significant trit + var i : i64 = @as(i64, @intCast(len)) - 1; + + while (i >= 0) { + const idx = @as(usize, @intCast(i)); + + if (a[idx] < b[idx]) { + return -1; + } else if (a[idx] > b[idx]) { + return 1; + } + + i = i - 1; + } + + return 0; + } + + // ===================================================== + // 10. TDD - Tests + // ========================================================================= + + test trit_add_basic + // 1 + 1 = 2 -> -1 with carry +1 (balanced ternary: 2 = -1 + 3) + var r1 = trit_add(TRIT_POS, TRIT_POS, TRIT_ZERO); + assert r1.sum == TRIT_NEG + assert r1.carry_out == TRIT_POS + + // -1 + -1 = -2 -> 1 with carry -1 + var r2 = trit_add(TRIT_NEG, TRIT_NEG, TRIT_ZERO); + assert r2.sum == TRIT_POS + assert r2.carry_out == TRIT_NEG + + // 1 + -1 = 0 with no carry + var r3 = trit_add(TRIT_POS, TRIT_NEG, TRIT_ZERO); + assert r3.sum == TRIT_ZERO + assert r3.carry_out == TRIT_ZERO + + // 0 + 0 = 0 with no carry + var r4 = trit_add(TRIT_ZERO, TRIT_ZERO, TRIT_ZERO); + assert r4.sum == TRIT_ZERO + assert r4.carry_out == TRIT_ZERO + + test trit_sub_basic + // 1 - 1 = 0 with no borrow + var r1 = trit_sub(TRIT_POS, TRIT_POS, TRIT_ZERO); + assert r1.diff == TRIT_ZERO + assert r1.borrow_out == TRIT_ZERO + + // 0 - 1 = -1 with no borrow + var r2 = trit_sub(TRIT_ZERO, TRIT_POS, TRIT_ZERO); + assert r2.diff == TRIT_NEG + assert r2.borrow_out == TRIT_ZERO + + // 1 - 0 = 1 with no borrow + var r3 = trit_sub(TRIT_POS, TRIT_ZERO, TRIT_ZERO); + assert r3.diff == TRIT_POS + assert r3.borrow_out == TRIT_ZERO + + test trit_mul_basic + assert trit_mul(TRIT_POS, TRIT_POS) == TRIT_POS + assert trit_mul(TRIT_POS, TRIT_NEG) == TRIT_NEG + assert trit_mul(TRIT_NEG, TRIT_POS) == TRIT_NEG + assert trit_mul(TRIT_NEG, TRIT_NEG) == TRIT_POS + assert trit_mul(TRIT_ZERO, TRIT_POS) == TRIT_ZERO + assert trit_mul(TRIT_POS, TRIT_ZERO) == TRIT_ZERO + assert trit_mul(TRIT_ZERO, TRIT_NEG) == TRIT_ZERO + assert trit_mul(TRIT_NEG, TRIT_ZERO) == TRIT_ZERO + + test ternary_to_decimal_basic + var t1 : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_ZERO}; // 1 + assert ternary_to_decimal(&t1, 3) == 1 + + var t2 : [3]i32 = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_ZERO}; // -1 + assert ternary_to_decimal(&t2, 3) == -1 + + var t3 : [3]i32 = [_]i32{TRIT_ZERO, TRIT_POS, TRIT_ZERO}; // 3 + assert ternary_to_decimal(&t3, 3) == 3 + + var t4 : [3]i32 = [_]i32{TRIT_POS, TRIT_POS, TRIT_ZERO}; // 1 + 3 = 4 + assert ternary_to_decimal(&t4, 3) == 4 + + test decimal_to_ternary_basic + var t1 : [5]i32 = undefined; + decimal_to_ternary(0, &t1, 5); + assert t1[0] == TRIT_ZERO + + var t2 : [5]i32 = undefined; + decimal_to_ternary(1, &t2, 5); + assert t2[0] == TRIT_POS + assert t2[1] == TRIT_ZERO + + var t3 : [5]i32 = undefined; + decimal_to_ternary(-1, &t3, 5); + assert t3[0] == TRIT_NEG + assert t3[1] == TRIT_ZERO + + var t4 : [5]i32 = undefined; + decimal_to_ternary(4, &t4, 5); + assert t4[0] == TRIT_POS + assert t4[1] == TRIT_POS + + test is_valid_trit_check + assert is_valid_trit(TRIT_NEG) == true + assert is_valid_trit(TRIT_ZERO) == true + assert is_valid_trit(TRIT_POS) == true + assert is_valid_trit(2) == false + assert is_valid_trit(-2) == false + + test ternary_compare_basic + var a : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_ZERO}; // 1 + var b : [3]i32 = [_]i32{TRIT_ZERO, TRIT_ZERO, TRIT_ZERO}; // 0 + assert ternary_compare(&a, &b, 3) == 1 + assert ternary_compare(&b, &a, 3) == -1 + assert ternary_compare(&a, &a, 3) == 0 + + // ===================================================== + // 11. TDD - Invariants + // ========================================================================= + + invariant trit_add_commutative + // Trit addition is commutative + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + var i : usize = 0; + while (i < 3) { + var j : usize = 0; + while (j < 3) { + var k : usize = 0; + while (k < 3) { + const r1 = trit_add(vals[i], vals[j], vals[k]); + const r2 = trit_add(vals[j], vals[i], vals[k]); + assert r1.sum == r2.sum + assert r1.carry_out == r2.carry_out + k = k + 1; + } + j = j + 1; + } + i = i + 1; + } + + invariant trit_add_associative + // Trit addition is associative (simplified check) + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + var i : usize = 0; + while (i < 3) { + var j : usize = 0; + while (j < 3) { + var k : usize = 0; + while (k < 3) { + const r1 = trit_add(trit_add(vals[i], vals[j], TRIT_ZERO).sum, vals[k], TRIT_ZERO); + const r2 = trit_add(vals[i], trit_add(vals[j], vals[k], TRIT_ZERO).sum, TRIT_ZERO); + assert r1.sum == r2.sum + k = k + 1; + } + j = j + 1; + } + i = i + 1; + } + + invariant trit_mul_commutative + // Trit multiplication is commutative + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + var i : usize = 0; + while (i < 3) { + var j : usize = 0; + while (j < 3) { + assert trit_mul(vals[i], vals[j]) == trit_mul(vals[j], vals[i]) + j = j + 1; + } + i = i + 1; + } + + invariant trit_mul_identity + // 1 is the multiplicative identity + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + var i : usize = 0; + while (i < 3) { + assert trit_mul(vals[i], TRIT_POS) == vals[i] + assert trit_mul(TRIT_POS, vals[i]) == vals[i] + i = i + 1; + } + + invariant trit_mul_zero_property + // 0 is the multiplicative zero + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + var i : usize = 0; + while (i < 3) { + assert trit_mul(vals[i], TRIT_ZERO) == TRIT_ZERO + assert trit_mul(TRIT_ZERO, vals[i]) == TRIT_ZERO + i = i + 1; + } + + invariant conversion_roundtrip + // Decimal to ternary to decimal should be identity (for small values) + var test_values : [10]i64 = [_]i64{-40, -10, -5, -1, 0, 1, 5, 10, 13, 40}; + var i : usize = 0; + while (i < 10) { + var trits : [10]i32 = undefined; + decimal_to_ternary(test_values[i], &trits, 10); + const result = ternary_to_decimal(&trits, 10); + assert result == test_values[i] + i = i + 1; + } + + // ===================================================== + // 12. TDD - Benchmarks + // ========================================================================= + + bench trit_add_performance + // Measure: cycles to compute 1000 trit additions + // Target: < 500 cycles + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + @setEvalBranchQuota(10000); + var result : TritAddResult = undefined; + var idx : usize = 0; + for (0..1000) |_| { + result = trit_add(vals[idx % 3], vals[(idx + 1) % 3], TRIT_ZERO); + idx = idx + 1; + } + _ = result; + + bench trit_mul_performance + // Measure: cycles to compute 1000 trit multiplications + // Target: < 300 cycles + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + @setEvalBranchQuota(10000); + var result : i32 = 0; + var idx : usize = 0; + for (0..1000) |_| { + result = trit_mul(vals[idx % 3], vals[(idx + 1) % 3]); + idx = idx + 1; + } + _ = result; + + bench conversion_performance + // Measure: cycles to convert 100 decimal values to ternary + // Target: < 5000 cycles + var trits : [10]i32 = undefined; + @setEvalBranchQuota(10000); + for (0..100) |i| { + decimal_to_ternary(@as(i64, @intCast(i)) - 50, &trits, 10); + } + + bench comparison_performance + // Measure: cycles to compare 100 ternary word pairs + // Target: < 3000 cycles + var a : [10]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS}; + var b : [10]i32 = [_]i32{TRIT_ZERO, TRIT_POS, TRIT_NEG, TRIT_POS, TRIT_NEG, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_ZERO, TRIT_NEG}; + @setEvalBranchQuota(10000); + var result : i32 = 0; + for (0..100) |_| { + result = ternary_compare(&a, &b, 10); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/isa/ternary_bitwise.t27 b/apps/website/public/t27/specs/isa/ternary_bitwise.t27 new file mode 100644 index 0000000000..5ddc16c5e1 --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_bitwise.t27 @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/ternary_bitwise.t27 +// Ternary Bitwise Operations Specification +// Ring 068 - Bitwise AND, OR, XOR for ternary data +// Defines bitwise operations on ternary word representations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module TernaryBitwise { + use base::types; + + // ===================================================== + // 1. Bitwise Constants + // ========================================================================= + + // Trit values + const TRIT_NEG : i32 = -1; + const TRIT_ZERO : i32 = 0; + const TRIT_POS : i32 = 1; + + // Word size + const WORD_SIZE : usize = 27; + + // ===================================================== + // 2. Tritwise AND + // ========================================================================= + + // tritwise_and(a: []i32, b: []i32, result: []i32, len: usize) -> void + // Tritwise AND operation (acts as MIN in balanced ternary) + fn tritwise_and(a: []i32, b: []i32, result: []i32, len: usize) -> void { + var i : usize = 0; + while (i < len) { + // AND is essentially MIN for balanced ternary + if (a[i] < b[i]) { + result[i] = a[i]; + } else { + result[i] = b[i]; + } + i = i + 1; + } + } + + // ===================================================== + // 3. Tritwise OR + // ========================================================================= + + // tritwise_or(a: []i32, b: []i32, result: []i32, len: usize) -> void + // Tritwise OR operation (acts as MAX in balanced ternary) + fn tritwise_or(a: []i32, b: []i32, result: []i32, len: usize) -> void { + var i : usize = 0; + while (i < len) { + // OR is essentially MAX for balanced ternary + if (a[i] > b[i]) { + result[i] = a[i]; + } else { + result[i] = b[i]; + } + i = i + 1; + } + } + + // ===================================================== + // 4. Tritwise XOR + // ========================================================================= + + // tritwise_xor(a: []i32, b: []i32, result: []i32, len: usize) -> void + // Tritwise XOR (exclusive OR) operation + // Returns TRIT_ZERO if inputs are same, otherwise indicates difference + fn tritwise_xor(a: []i32, b: []i32, result: []i32, len: usize) -> void { + var i : usize = 0; + while (i < len) { + if (a[i] == b[i]) { + result[i] = TRIT_ZERO; + } else if (a[i] == TRIT_ZERO) { + result[i] = b[i]; + } else if (b[i] == TRIT_ZERO) { + result[i] = a[i]; + } else { + // Both are non-zero and different + result[i] = TRIT_ZERO; + } + i = i + 1; + } + } + + // ===================================================== + // 5. Tritwise NOT + // ========================================================================= + + // tritwise_not(word: []i32, result: []i32, len: usize) -> void + // Tritwise NOT (inversion) operation + fn tritwise_not(word: []i32, result: []i32, len: usize) -> void { + var i : usize = 0; + while (i < len) { + result[i] = -word[i]; + i = i + 1; + } + } + + // ===================================================== + // 6. Tritwise NAND + // ========================================================================= + + // tritwise_nand(a: []i32, b: []i32, result: []i32, len: usize) -> void + // Tritwise NAND (NOT AND) operation + fn tritwise_nand(a: []i32, b: []i32, result: []i32, len: usize) -> void { + var temp : [WORD_SIZE]i32 = undefined; + tritwise_and(a, b, temp, len); + tritwise_not(temp, result, len); + } + + // ===================================================== + // 7. Tritwise NOR + // ========================================================================= + + // tritwise_nor(a: []i32, b: []i32, result: []i32, len: usize) -> void + // Tritwise NOR (NOT OR) operation + fn tritwise_nor(a: []i32, b: []i32, result: []i32, len: usize) -> void { + var temp : [WORD_SIZE]i32 = undefined; + tritwise_or(a, b, temp, len); + tritwise_not(temp, result, len); + } + + // ===================================================== + // 8. Tritwise XNOR + // ========================================================================= + + // tritwise_xnor(a: []i32, b: []i32, result: []i32, len: usize) -> void + // Tritwise XNOR (exclusive NOR) - equality test + fn tritwise_xnor(a: []i32, b: []i32, result: []i32, len: usize) -> void { + var i : usize = 0; + while (i < len) { + if (a[i] == b[i]) { + result[i] = TRIT_POS; + } else { + result[i] = TRIT_NEG; + } + i = i + 1; + } + } + + // ===================================================== + // 9. Mask Operations + // ========================================================================= + + // tritwise_mask(word: []i32, mask: []i32, result: []i32, len: usize) -> void + // Apply mask: keep trits where mask is POS, zero elsewhere + fn tritwise_mask(word: []i32, mask: []i32, result: []i32, len: usize) -> void { + var i : usize = 0; + while (i < len) { + if (mask[i] == TRIT_POS) { + result[i] = word[i]; + } else { + result[i] = TRIT_ZERO; + } + i = i + 1; + } + } + + // tritwise_merge(a: []i32, b: []i32, mask: []i32, result: []i32, len: usize) -> void + // Merge based on mask: take from a where mask is POS, from b otherwise + fn tritwise_merge(a: []i32, b: []i32, mask: []i32, result: []i32, len: usize) -> void { + var i : usize = 0; + while (i < len) { + if (mask[i] == TRIT_POS) { + result[i] = a[i]; + } else { + result[i] = b[i]; + } + i = i + 1; + } + } + + // ===================================================== + // 10. TDD - Tests + // ========================================================================= + + test tritwise_and_basic + var a : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG}; + var b : [3]i32 = [_]i32{TRIT_POS, TRIT_POS, TRIT_ZERO}; + var result : [3]i32 = undefined; + + tritwise_and(&a, &b, &result, 3); + assert result[0] == TRIT_POS + assert result[1] == TRIT_ZERO + assert result[2] == TRIT_NEG + + test tritwise_or_basic + var a : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG}; + var b : [3]i32 = [_]i32{TRIT_ZERO, TRIT_POS, TRIT_ZERO}; + var result : [3]i32 = undefined; + + tritwise_or(&a, &b, &result, 3); + assert result[0] == TRIT_POS + assert result[1] == TRIT_POS + assert result[2] == TRIT_ZERO + + test tritwise_xor_basic + var a : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG}; + var b : [3]i32 = [_]i32{TRIT_POS, TRIT_POS, TRIT_NEG}; + var result : [3]i32 = undefined; + + tritwise_xor(&a, &b, &result, 3); + assert result[0] == TRIT_ZERO + assert result[1] == TRIT_POS + assert result[2] == TRIT_ZERO + + test tritwise_not_basic + var word : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG}; + var result : [3]i32 = undefined; + + tritwise_not(&word, &result, 3); + assert result[0] == TRIT_NEG + assert result[1] == TRIT_ZERO + assert result[2] == TRIT_POS + + test tritwise_nand_basic + var a : [2]i32 = [_]i32{TRIT_POS, TRIT_POS}; + var b : [2]i32 = [_]i32{TRIT_POS, TRIT_POS}; + var result : [2]i32 = undefined; + + tritwise_nand(&a, &b, &result, 2); + // AND of POS, POS = POS, NOT POS = NEG + assert result[0] == TRIT_NEG + assert result[1] == TRIT_NEG + + test tritwise_nor_basic + var a : [2]i32 = [_]i32{TRIT_NEG, TRIT_NEG}; + var b : [2]i32 = [_]i32{TRIT_NEG, TRIT_NEG}; + var result : [2]i32 = undefined; + + tritwise_nor(&a, &b, &result, 2); + // OR of NEG, NEG = NEG, NOT NEG = POS + assert result[0] == TRIT_POS + assert result[1] == TRIT_POS + + test tritwise_xnor_basic + var a : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG}; + var b : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_POS}; + var result : [3]i32 = undefined; + + tritwise_xnor(&a, &b, &result, 3); + assert result[0] == TRIT_POS // Same + assert result[1] == TRIT_POS // Same + assert result[2] == TRIT_NEG // Different + + test tritwise_mask_basic + var word : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG}; + var mask : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_ZERO}; + var result : [3]i32 = undefined; + + tritwise_mask(&word, &mask, &result, 3); + assert result[0] == TRIT_POS + assert result[1] == TRIT_ZERO + assert result[2] == TRIT_ZERO + + test tritwise_merge_basic + var a : [3]i32 = [_]i32{TRIT_POS, TRIT_NEG, TRIT_ZERO}; + var b : [3]i32 = [_]i32{TRIT_NEG, TRIT_POS, TRIT_POS}; + var mask : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_POS}; + var result : [3]i32 = undefined; + + tritwise_merge(&a, &b, &mask, &result, 3); + assert result[0] == TRIT_POS + assert result[1] == TRIT_POS + assert result[2] == TRIT_POS + + // ===================================================== + // 11. TDD - Invariants + // ========================================================================= + + invariant tritwise_and_commutative + // Tritwise AND is commutative + var a : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG}; + var b : [3]i32 = [_]i32{TRIT_NEG, TRIT_POS, TRIT_ZERO}; + var result1 : [3]i32 = undefined; + var result2 : [3]i32 = undefined; + + tritwise_and(&a, &b, &result1, 3); + tritwise_and(&b, &a, &result2, 3); + + var i : usize = 0; + while (i < 3) { + assert result1[i] == result2[i] + i = i + 1; + } + + invariant tritwise_or_commutative + // Tritwise OR is commutative + var a : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG}; + var b : [3]i32 = [_]i32{TRIT_NEG, TRIT_POS, TRIT_ZERO}; + var result1 : [3]i32 = undefined; + var result2 : [3]i32 = undefined; + + tritwise_or(&a, &b, &result1, 3); + tritwise_or(&b, &a, &result2, 3); + + var i : usize = 0; + while (i < 3) { + assert result1[i] == result2[i] + i = i + 1; + } + + invariant tritwise_not_double_inversion + // Double NOT returns original + var word : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG}; + var temp : [3]i32 = undefined; + var result : [3]i32 = undefined; + + tritwise_not(&word, &temp, 3); + tritwise_not(&temp, &result, 3); + + var i : usize = 0; + while (i < 3) { + assert result[i] == word[i] + i = i + 1; + } + + invariant tritwise_and_absorbing + // AND with TRIT_NEG absorbs to TRIT_NEG + var a : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG}; + var neg : [3]i32 = [_]i32{TRIT_NEG, TRIT_NEG, TRIT_NEG}; + var result : [3]i32 = undefined; + + tritwise_and(&a, &neg, &result, 3); + + var i : usize = 0; + while (i < 3) { + assert result[i] == TRIT_NEG + i = i + 1; + } + + invariant tritwise_or_absorbing + // OR with TRIT_POS absorbs to TRIT_POS + var a : [3]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG}; + var pos : [3]i32 = [_]i32{TRIT_POS, TRIT_POS, TRIT_POS}; + var result : [3]i32 = undefined; + + tritwise_or(&a, &pos, &result, 3); + + var i : usize = 0; + while (i < 3) { + assert result[i] == TRIT_POS + i = i + 1; + } + + // ===================================================== + // 12. TDD - Benchmarks + // ========================================================================= + + bench tritwise_and_performance + // Measure: cycles to compute 1000 tritwise AND operations + // Target: < 3000 cycles + var a : [27]i32 = [_]i32{TRIT_POS} ** 27; + var b : [27]i32 = [_]i32{TRIT_ZERO} ** 27; + var result : [27]i32 = undefined; + @setEvalBranchQuota(10000); + for (0..1000) |_| { + tritwise_and(&a, &b, &result, 27); + } + + bench tritwise_or_performance + // Measure: cycles to compute 1000 tritwise OR operations + // Target: < 3000 cycles + var a : [27]i32 = [_]i32{TRIT_POS} ** 27; + var b : [27]i32 = [_]i32{TRIT_ZERO} ** 27; + var result : [27]i32 = undefined; + @setEvalBranchQuota(10000); + for (0..1000) |_| { + tritwise_or(&a, &b, &result, 27); + } + + bench tritwise_xor_performance + // Measure: cycles to compute 1000 tritwise XOR operations + // Target: < 4000 cycles + var a : [27]i32 = [_]i32{TRIT_POS} ** 27; + var b : [27]i32 = [_]i32{TRIT_ZERO} ** 27; + var result : [27]i32 = undefined; + @setEvalBranchQuota(10000); + for (0..1000) |_| { + tritwise_xor(&a, &b, &result, 27); + } + + bench tritwise_not_performance + // Measure: cycles to compute 1000 tritwise NOT operations + // Target: < 1000 cycles + var word : [27]i32 = [_]i32{TRIT_POS} ** 27; + var result : [27]i32 = undefined; + @setEvalBranchQuota(10000); + for (0..1000) |_| { + tritwise_not(&word, &result, 27); + } + + bench tritwise_mask_performance + // Measure: cycles to compute 1000 mask operations + // Target: < 2000 cycles + var word : [27]i32 = [_]i32{TRIT_POS} ** 27; + var mask : [27]i32 = [_]i32{TRIT_POS} ** 27; + var result : [27]i32 = undefined; + @setEvalBranchQuota(10000); + for (0..1000) |_| { + tritwise_mask(&word, &mask, &result, 27); + } +} diff --git a/apps/website/public/t27/specs/isa/ternary_control_flow.t27 b/apps/website/public/t27/specs/isa/ternary_control_flow.t27 new file mode 100644 index 0000000000..d9ae54c65b --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_control_flow.t27 @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/ternary_control_flow.t27 +// Ternary Control Flow Specification +// Ring 090 - Control flow operations for ternary architecture +// Conditional jumps, branches, and call/return +// phi^2 + 1/phi^2 = 3 | TRINITY + +module TernaryControlFlow { + use base::types; + + // ================================================================= + // 1. Branch Conditions + // ========================================================================= + + // Trit values for condition evaluation + const TRIT_NEG : i32 = -1; + const TRIT_ZERO : i32 = 0; + const TRIT_POS : i32 = 1; + + // Condition codes + const COND_EQ : u8 = 0; // Equal to zero + const COND_NE : u8 = 1; // Not equal to zero + const COND_POS : u8 = 2; // Positive + const COND_NEG : u8 = 3; // Negative + const COND_NZ : u8 = 4; // Non-zero + const COND_NC : u8 = 5; // No condition (always true) + + // Branch prediction hints + const PRED_TAKEN : u8 = 0; // Likely taken + const PRED_NOT_TAKEN : u8 = 1; // Likely not taken + const PRED_NONE : u8 = 2; // No prediction + + // ================================================================= + // 2. Program Counter Operations + // ========================================================================= + + // pc_read() -> u32 + // Read current program counter + fn pc_read() -> u32 { + // In real implementation, PC is in register file (R19) + return 0; // Placeholder + } + + // pc_write(addr: u32) -> void + // Write new program counter (branch/jump) + fn pc_write(addr: u32) -> void { + // In real implementation, writes to PC register (R19) + } + + // pc_relative(offset: i32) -> u32 + // Calculate relative address + fn pc_relative(offset: i32) -> u32 { + const current_pc = pc_read(); + return (current_pc as i32 + offset) as u32; + } + + // ================================================================= + // 3. Conditional Branch Operations + // ========================================================================= + + // branch_cond(value: i32, condition: u8, offset: i32) -> bool + // Branch if condition matches value + // Returns true if branch was taken + fn branch_cond(value: i32, condition: u8, offset: i32) -> bool { + var should_branch : bool = false; + + if (condition == COND_EQ) { + should_branch = (value == TRIT_ZERO); + } else if (condition == COND_NE) { + should_branch = (value != TRIT_ZERO); + } else if (condition == COND_POS) { + should_branch = (value == TRIT_POS); + } else if (condition == COND_NEG) { + should_branch = (value == TRIT_NEG); + } else if (condition == COND_NZ) { + should_branch = (value != TRIT_ZERO); + } else if (condition == COND_NC) { + should_branch = true; + } + + if (should_branch) { + pc_write(pc_relative(offset)); + return true; + } + + return false; + } + + // branch_if(value: i32, offset: i32) -> bool + // Branch if value is non-zero (true) + fn branch_if(value: i32, offset: i32) -> bool { + return branch_cond(value, COND_NZ, offset); + } + + // branch_if_not(value: i32, offset: i32) -> bool + // Branch if value is zero (false) + fn branch_if_not(value: i32, offset: i32) -> bool { + return branch_cond(value, COND_EQ, offset); + } + + // ================================================================= + // 4. Unconditional Branch Operations + // ========================================================================= + + // jump(addr: u32) -> void + // Unconditional jump to absolute address + fn jump(addr: u32) -> void { + pc_write(addr); + } + + // jump_relative(offset: i32) -> void + // Unconditional jump to relative address + fn jump_relative(offset: i32) -> void { + pc_write(pc_relative(offset)); + } + + // jump_link(addr: u32, link_reg: *u32) -> void + // Jump and store return address + fn jump_link(addr: u32, link_reg: *u32) -> void { + link_reg.* = pc_read(); + pc_write(addr); + } + + // jump_link_relative(offset: i32, link_reg: *u32) -> void + // Jump relative and store return address + fn jump_link_relative(offset: i32, link_reg: *u32) -> void { + link_reg.* = pc_read(); + jump_relative(offset); + } + + // ================================================================= + // 5. Return Operation + // ========================================================================= + + // ret(link_reg: u32) -> void + // Return to address in link register + fn ret(link_reg: u32) -> void { + pc_write(link_reg); + } + + // ================================================================= + // 6. Call/Return with Stack + // ========================================================================= + + // call(addr: u32, sp: *u32, memory: []TernaryWord) -> bool + // Call subroutine: push return address, then jump + fn call(addr: u32, sp: *u32, memory: []TernaryWord) -> bool { + const return_addr = TernaryWord{ .raw = pc_read() }; + + // Push return address + if (sp.* == 0) { + return false; // Stack overflow + } + sp.* = sp.* - 1; + + if (sp.* >= memory.len()) { + return false; + } + memory[sp.*] = return_addr; + + // Jump to subroutine + pc_write(addr); + return true; + } + + // ret_pop(sp: *u32, memory: []TernaryWord) -> bool + // Return: pop return address, then jump + fn ret_pop(sp: *u32, memory: []TernaryWord) -> bool { + if (sp.* >= memory.len()) { + return false; + } + + const return_addr = memory[sp.*]; + sp.* = sp.* + 1; + + // Jump to return address + pc_write(return_addr.raw); + return true; + } + + // ================================================================= + // 7. Compare Operations + // ========================================================================= + + // compare(a: i32, b: i32) -> i32 + // Compare two trit values, return condition code + // Returns: TRIT_NEG if a < b, TRIT_ZERO if a == b, TRIT_POS if a > b + fn compare(a: i32, b: i32) -> i32 { + if (a < b) { + return TRIT_NEG; + } else if (a > b) { + return TRIT_POS; + } else { + return TRIT_ZERO; + } + } + + // compare_and_branch(a: i32, b: i32, condition: u8, offset: i32) -> bool + // Compare two values and branch if condition true + fn compare_and_branch(a: i32, b: i32, condition: u8, offset: i32) -> bool { + const result = compare(a, b); + return branch_cond(result, condition, offset); + } + + // ================================================================= + // 8. Table Branch (Switch) + // ========================================================================= + + // table_branch(index: i32, jump_table: []u32, default_addr: u32) -> void + // Jump to address in jump_table[index], or default if out of range + fn table_branch(index: i32, jump_table: []u32, default_addr: u32) -> void { + if (index < 0 or index >= jump_table.len()) { + jump(default_addr); + } else { + jump(jump_table[index]); + } + } + + // ================================================================= + // TDD-Inside-Spec: Tests and Invariants + // ========================================================================= + + test branch_if_taken + given offset = 10 + and taken = branch_if(TRIT_POS, offset) + then taken == true + + test branch_if_not_taken + given offset = 10 + and taken = branch_if(TRIT_ZERO, offset) + then taken == false + + test branch_if_not_taken_zero + given offset = 10 + and taken = branch_if_not(TRIT_ZERO, offset) + then taken == true + + test branch_cond_eq_true + given offset = 5 + and taken = branch_cond(TRIT_ZERO, COND_EQ, offset) + then taken == true + + test branch_cond_eq_false + given offset = 5 + and taken = branch_cond(TRIT_POS, COND_EQ, offset) + then taken == false + + test branch_cond_pos_true + given offset = 7 + and taken = branch_cond(TRIT_POS, COND_POS, offset) + then taken == true + + test branch_cond_neg_true + given offset = 3 + and taken = branch_cond(TRIT_NEG, COND_NEG, offset) + then taken == true + + test branch_cond_nc_always_true + given offset = 15 + and taken = branch_cond(TRIT_NEG, COND_NC, offset) + then taken == true + + test jump_relative_forward + given offset = 10 + and pc = pc_relative(offset) + then pc == 10 + + test jump_relative_backward + given offset = -5 + and pc = pc_relative(offset) + then pc == 0xFFFFFFFF // -5 wrapped + + test compare_less + given result = compare(TRIT_NEG, TRIT_ZERO) + then result == TRIT_NEG + + test compare_equal + given result = compare(TRIT_POS, TRIT_POS) + then result == TRIT_ZERO + + test compare_greater + given result = compare(TRIT_POS, TRIT_ZERO) + then result == TRIT_POS + + test compare_and_branch_less + given taken = compare_and_branch(TRIT_NEG, TRIT_ZERO, COND_NEG, 5) + then taken == true + + test compare_and_branch_not_less + given taken = compare_and_branch(TRIT_POS, TRIT_ZERO, COND_NEG, 5) + then taken == false + + test table_branch_valid + given table = []u32{100, 200, 300} + and table_branch(1, table, 0) + // PC should be 200 + then true + + test table_branch_default + given table = []u32{100, 200, 300} + and table_branch(5, table, 999) + // PC should be 999 (default) + then true + + test jump_link_saves_return_addr + given lr : u32 = 0 + and pc_set_to(50) + and jump_link(100, &lr) + then lr == 50 + + test ret_restores_pc + given lr : u32 = 50 + and ret(lr) + // PC should be 50 + then true + + invariant branch_condition_codes_valid + assert COND_EQ <= 5 and COND_NC <= 5 + + invariant branch_prediction_hints_valid + assert PRED_TAKEN <= 2 and PRED_NOT_TAKEN <= 2 + + invariant compare_trichotomous + given a = TRIT_NEG + and b = TRIT_ZERO + and c = TRIT_POS + and r1 = compare(a, a) + and r2 = compare(b, b) + and r3 = compare(c, c) + assert r1 == TRIT_ZERO and r2 == TRIT_ZERO and r3 == TRIT_ZERO + + invariant compare_less_greater_antisymmetric + given r1 = compare(TRIT_NEG, TRIT_POS) + and r2 = compare(TRIT_POS, TRIT_NEG) + assert r1 == TRIT_NEG and r2 == TRIT_POS + + invariant branch_if_eq_branch_if_not + given v = TRIT_ZERO + and b1 = branch_if(v, 5) + and b2 = branch_if_not(v, 5) + assert b1 == false and b2 == true + + invariant branch_if_not_eq_branch_if + given v = TRIT_POS + and b1 = branch_if(v, 5) + and b2 = branch_if_not(v, 5) + assert b1 == true and b2 == false + + invariant jump_relative_symmetric + given pc_before = pc_relative(0) + and jump_relative(10) + and pc_after = pc_read() + and jump_relative(-10) + and pc_final = pc_read() + assert pc_before == pc_final + + invariant table_branch_in_range + given table = []u32{100, 200, 300} + and table_branch(0, table, 999) + and table_branch(1, table, 999) + and table_branch(2, table, 999) + // All indices 0, 1, 2 should branch to 100, 200, 300 + assert true + + invariant trit_condition_values + assert TRIT_NEG == -1 and TRIT_ZERO == 0 and TRIT_POS == 1 + + bench branch_cond_latency + measure: nanoseconds to branch_cond(TRIT_POS, COND_POS, 10) + target: < 100ns + + bench compare_latency + measure: nanoseconds to compare(TRIT_POS, TRIT_NEG) + target: < 50ns + + bench jump_relative_latency + measure: nanoseconds to jump_relative(10) + target: < 50ns + + bench jump_link_latency + measure: nanoseconds to jump_link(100, &lr) + target: < 80ns + + bench ret_latency + measure: nanoseconds to ret(50) + target: < 60ns + + bench table_branch_latency + measure: nanoseconds to table_branch(1, []u32{100, 200, 300}, 999) + target: < 150ns +} diff --git a/apps/website/public/t27/specs/isa/ternary_deque.t27 b/apps/website/public/t27/specs/isa/ternary_deque.t27 new file mode 100644 index 0000000000..b503c08e4c --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_deque.t27 @@ -0,0 +1,497 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/ternary_deque.t27 +// Ternary Deque Operations Specification +// Ring 088 - Double-ended queue operations for ternary data +// Defines deque with push/pop from both ends +// phi^2 + 1/phi^2 = 3 | TRINITY + +module TernaryDeque { + use base::types; + + // ===================================================== + // 1. Deque Constants + // ========================================================================= + + // Trit values + const TRIT_NEG : i32 = -1; + const TRIT_ZERO : i32 = 0; + const TRIT_POS : i32 = 1; + + // Deque limits + const DEQUE_MAX_SIZE : usize = 27; + + // ===================================================== + // 2. Deque Structure (Circular Buffer) + // ========================================================================= + + // deque_init(data: []i32, front: *usize, back: *usize, count: *usize) -> void + // Initialize empty deque + fn deque_init(data: []i32, front: *usize, back: *usize, count: *usize) -> void { + var i : usize = 0; + while (i < DEQUE_MAX_SIZE and i < data.len) { + data[i] = TRIT_ZERO; + i = i + 1; + } + front.* = 0; + back.* = 0; + count.* = 0; + } + + // deque_push_front(data: []i32, front: *usize, back: *usize, count: *usize, value: i32) -> bool + // Add element to front of deque + fn deque_push_front(data: []i32, front: *usize, back: *usize, count: *usize, value: i32) -> bool { + if (count.* >= DEQUE_MAX_SIZE) { + return false; + } + + if (count.* > 0) { + if (front.* == 0) { + front.* = DEQUE_MAX_SIZE - 1; + } else { + front.* = front.* - 1; + } + } + + if (front.* < data.len) { + data[front.*] = value; + } + count.* = count.* + 1; + return true; + } + + // deque_push_back(data: []i32, front: *usize, back: *usize, count: *usize, value: i32) -> bool + // Add element to back of deque + fn deque_push_back(data: []i32, front: *usize, back: *usize, count: *usize, value: i32) -> bool { + if (count.* >= DEQUE_MAX_SIZE) { + return false; + } + + if (back.* < data.len) { + data[back.*] = value; + } + + back.* = back.* + 1; + if (back.* >= DEQUE_MAX_SIZE) { + back.* = 0; + } + count.* = count.* + 1; + return true; + } + + // deque_pop_front(data: []i32, front: *usize, back: *usize, count: *usize, value: *i32) -> bool + // Remove element from front of deque + fn deque_pop_front(data: []i32, front: *usize, back: *usize, count: *usize, value: *i32) -> bool { + if (count.* == 0) { + return false; + } + + if (front.* < data.len) { + value.* = data[front.*]; + } + + front.* = front.* + 1; + if (front.* >= DEQUE_MAX_SIZE) { + front.* = 0; + } + count.* = count.* - 1; + return true; + } + + // deque_pop_back(data: []i32, front: *usize, back: *usize, count: *usize, value: *i32) -> bool + // Remove element from back of deque + fn deque_pop_back(data: []i32, front: *usize, back: *usize, count: *usize, value: *i32) -> bool { + if (count.* == 0) { + return false; + } + + if (back.* == 0) { + back.* = DEQUE_MAX_SIZE - 1; + } else { + back.* = back.* - 1; + } + + if (back.* < data.len) { + value.* = data[back.*]; + } + count.* = count.* - 1; + return true; + } + + // deque_peek_front(data: []i32, front: *usize, count: usize, value: *i32) -> bool + // Look at element at front without removing + fn deque_peek_front(data: []i32, front: *usize, count: usize, value: *i32) -> bool { + if (count == 0) { + return false; + } + if (front.* < data.len) { + value.* = data[front.*]; + } + return true; + } + + // deque_peek_back(data: []i32, back: *usize, count: usize, value: *i32) -> bool + // Look at element at back without removing + fn deque_peek_back(data: []i32, back: *usize, count: usize, value: *i32) -> bool { + if (count == 0) { + return false; + } + var idx : usize = 0; + if (back.* == 0) { + idx = DEQUE_MAX_SIZE - 1; + } else { + idx = back.* - 1; + } + if (idx < data.len) { + value.* = data[idx]; + } + return true; + } + + // deque_is_empty(count: usize) -> bool + // Check if deque is empty + fn deque_is_empty(count: usize) -> bool { + return count == 0; + } + + // deque_is_full(count: usize) -> bool + // Check if deque is full + fn deque_is_full(count: usize) -> bool { + return count >= DEQUE_MAX_SIZE; + } + + // deque_size(count: usize) -> usize + // Get number of elements in deque + fn deque_size(count: usize) -> usize { + return count; + } + + // deque_clear(data: []i32, front: *usize, back: *usize, count: *usize) -> void + // Clear all elements from deque + fn deque_clear(data: []i32, front: *usize, back: *usize, count: *usize) -> void { + var i : usize = 0; + while (i < DEQUE_MAX_SIZE and i < data.len) { + data[i] = TRIT_ZERO; + i = i + 1; + } + front.* = 0; + back.* = 0; + count.* = 0; + } + + // ===================================================== + // 3. TDD - Tests + // ========================================================================= + + test deque_init_empty + var data : [10]i32 = undefined; + var front : usize = 99; + var back : usize = 99; + var count : usize = 99; + deque_init(&data, &front, &back, &count); + + assert count == 0 + assert front == 0 + assert back == 0 + + test deque_push_back_pop_front + var data : [10]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + deque_push_back(&data, &front, &back, &count, TRIT_POS); + deque_push_back(&data, &front, &back, &count, TRIT_NEG); + + var value : i32 = 0; + deque_pop_front(&data, &front, &back, &count, &value); + assert value == TRIT_POS + + deque_pop_front(&data, &front, &back, &count, &value); + assert value == TRIT_NEG + + test deque_push_front_pop_back + var data : [10]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + deque_push_front(&data, &front, &back, &count, TRIT_POS); + deque_push_front(&data, &front, &back, &count, TRIT_NEG); + + var value : i32 = 0; + deque_pop_back(&data, &front, &back, &count, &value); + assert value == TRIT_POS + + deque_pop_back(&data, &front, &back, &count, &value); + assert value == TRIT_NEG + + test deque_peek_front + var data : [10]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + deque_push_back(&data, &front, &back, &count, TRIT_POS); + + var value : i32 = 0; + deque_peek_front(&data, &front, count, &value); + assert value == TRIT_POS + assert count == 1 // Size unchanged + + test deque_peek_back + var data : [10]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + deque_push_back(&data, &front, &back, &count, TRIT_POS); + deque_push_back(&data, &front, &back, &count, TRIT_NEG); + + var value : i32 = 0; + deque_peek_back(&data, &back, count, &value); + assert value == TRIT_NEG + assert count == 2 // Size unchanged + + test deque_is_empty_full + var data : [5]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + assert deque_is_empty(count) + assert !deque_is_full(count) + + deque_push_back(&data, &front, &back, &count, TRIT_POS); + assert !deque_is_empty(count) + + test deque_push_back_full + var data : [5]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + var i : usize = 0; + while (i < DEQUE_MAX_SIZE) { + deque_push_back(&data, &front, &back, &count, TRIT_POS); + i = i + 1; + } + + assert deque_is_full(count) + const success = deque_push_back(&data, &front, &back, &count, TRIT_NEG); + assert !success + + test deque_clear + var data : [10]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + deque_push_back(&data, &front, &back, &count, TRIT_POS); + deque_push_front(&data, &front, &back, &count, TRIT_NEG); + deque_clear(&data, &front, &back, &count); + + assert deque_is_empty(count) + + // ===================================================== + // 4. TDD - Invariants + // ========================================================================= + + invariant deque_size_matches_operations + // Size equals pushes minus pops + var data : [10]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + var i : usize = 0; + while (i < 5) { + deque_push_back(&data, &front, &back, &count, TRIT_POS); + i = i + 1; + } + + var value : i32 = 0; + deque_pop_front(&data, &front, &back, &count, &value); + + assert deque_size(count) == 4 + + invariant deque_pop_from_empty_fails + // Pop from empty deque fails + var data : [10]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + var value : i32 = 0; + const success1 = deque_pop_front(&data, &front, &back, &count, &value); + const success2 = deque_pop_back(&data, &front, &back, &count, &value); + + assert !success1 + assert !success2 + + invariant deque_fifo_order + // Elements maintain FIFO order when pushed/popped from same ends + var data : [10]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + deque_push_back(&data, &front, &back, &count, TRIT_NEG); + deque_push_back(&data, &front, &back, &count, TRIT_ZERO); + deque_push_back(&data, &front, &back, &count, TRIT_POS); + + var value : i32 = 0; + deque_pop_front(&data, &front, &back, &count, &value); + assert value == TRIT_NEG + + deque_pop_front(&data, &front, &back, &count, &value); + assert value == TRIT_ZERO + + deque_pop_front(&data, &front, &back, &count, &value); + assert value == TRIT_POS + + invariant deque_lifo_order_front + // Elements maintain LIFO order when pushed/popped from front + var data : [10]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + deque_push_front(&data, &front, &back, &count, TRIT_NEG); + deque_push_front(&data, &front, &back, &count, TRIT_ZERO); + deque_push_front(&data, &front, &back, &count, TRIT_POS); + + var value : i32 = 0; + deque_pop_front(&data, &front, &back, &count, &value); + assert value == TRIT_POS + + deque_pop_front(&data, &front, &back, &count, &value); + assert value == TRIT_ZERO + + invariant deque_peek_does_not_change_size + // Peek operations don't change size + var data : [10]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + deque_push_back(&data, &front, &back, &count, TRIT_POS); + deque_push_front(&data, &front, &back, &count, TRIT_NEG); + + var value : i32 = 0; + _ = deque_peek_front(&data, &front, count, &value); + _ = deque_peek_back(&data, &back, count, &value); + + assert deque_size(count) == 2 + + // ===================================================== + // 5. TDD - Benchmarks + // ========================================================================= + + bench deque_push_back_performance + // Measure: cycles for 1000 push_back operations + // Target: < 3000 cycles + var data : [DEQUE_MAX_SIZE]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + @setEvalBranchQuota(10000); + for (0..1000) |_| { + count = 0; + back = 0; + deque_push_back(&data, &front, &back, &count, TRIT_POS); + } + + bench deque_push_front_performance + // Measure: cycles for 1000 push_front operations + // Target: < 3000 cycles + var data : [DEQUE_MAX_SIZE]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + @setEvalBranchQuota(10000); + for (0..1000) |_| { + count = 0; + front = 0; + deque_push_front(&data, &front, &back, &count, TRIT_POS); + } + + bench deque_pop_front_performance + // Measure: cycles for 1000 pop_front operations + // Target: < 3000 cycles + var data : [DEQUE_MAX_SIZE]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + var i : usize = 0; + while (i < 10) { + deque_push_back(&data, &front, &back, &count, TRIT_POS); + i = i + 1; + } + + var value : i32 = 0; + @setEvalBranchQuota(10000); + for (0..1000) |_| { + count = 10; + front = 0; + i = 0; + while (i < 10) { + deque_push_back(&data, &front, &back, &count, TRIT_POS); + i = i + 1; + } + i = 0; + while (i < 10) { + deque_pop_front(&data, &front, &back, &count, &value); + i = i + 1; + } + } + + bench deque_peek_performance + // Measure: cycles for 10000 peek operations + // Target: < 2000 cycles + var data : [DEQUE_MAX_SIZE]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + deque_push_back(&data, &front, &back, &count, TRIT_POS); + var value : i32 = 0; + @setEvalBranchQuota(10000); + for (0..10000) |_| { + _ = deque_peek_front(&data, &front, count, &value); + } + + bench deque_mixed_performance + // Measure: cycles for 100 mixed push/pop operations + // Target: < 5000 cycles + var data : [DEQUE_MAX_SIZE]i32 = undefined; + var front : usize = 0; + var back : usize = 0; + var count : usize = 0; + deque_init(&data, &front, &back, &count); + + var value : i32 = 0; + @setEvalBranchQuota(10000); + for (0..100) |_| { + deque_push_back(&data, &front, &back, &count, TRIT_POS); + deque_push_front(&data, &front, &back, &count, TRIT_NEG); + deque_pop_back(&data, &front, &back, &count, &value); + deque_pop_front(&data, &front, &back, &count, &value); + } +} diff --git a/apps/website/public/t27/specs/isa/ternary_encoding.t27 b/apps/website/public/t27/specs/isa/ternary_encoding.t27 new file mode 100644 index 0000000000..1ab5368867 --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_encoding.t27 @@ -0,0 +1,41 @@ +// specs/isa/ternary_encoding.t27 +// Ternary encoding: values in {-1, 0, +1} + +algorithm ternary_encoding { + module: base.ternary_encoding + + strand_i: { + trit_values: [-1, 0, +1], + encoding_name: "balanced_ternary", + isa_extension: "ternary" + } + + strand_ii: { + biological_analog: "multi-state signaling in biological membranes" + } + + strand_iii: { + t27_target: "isa/ternary_encoding", + backends: [rust, c, zig, verilog] + } + + invariants: [ + "unique_encoding" + ] + + hardware: [x86, arm, riscv] + + notes: | + Balanced ternary encoding efficiently represents + three discrete states using 1.58 trits per value + vs 1 bit per value for binary + + tests: [ + { encode: 1, decode: [+1], 0 }, + { encode: 0, decode: [0], 0 }, + { encode: [+1], decode: [1], 1 } + ] +} + test "ternary_encoding_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/isa/ternary_gates.t27 b/apps/website/public/t27/specs/isa/ternary_gates.t27 new file mode 100644 index 0000000000..fcd2675ecf --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_gates.t27 @@ -0,0 +1,409 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/ternary_gates.t27 +// Ternary Logic Gates Specification +// Ring 063 - Basic ternary logic gates for balanced ternary +// Defines AND, OR, NOT, and other fundamental operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module TernaryGates { + use base::types; + + // ===================================================== + // 1. Ternary Values + // ========================================================================= + + // Balanced ternary values + const TRIT_NEG : i32 = -1; // Negative / False + const TRIT_ZERO : i32 = 0; // Zero / Neutral + const TRIT_POS : i32 = 1; // Positive / True + + // Gate delay (abstract units) + const GATE_DELAY_UNIT : u64 = 1; + + // ===================================================== + // 2. NOT Gate (Ternary Inverter) + // ========================================================================= + + // ternary_not(a: i32) -> i32 + // Ternary NOT (inverts trit value) + // TRIT_NEG -> TRIT_POS, TRIT_ZERO -> TRIT_ZERO, TRIT_POS -> TRIT_NEG + fn ternary_not(a: i32) -> i32 { + return -a; + } + + // ternary_not_with_delay(a: i32) -> GateResult + // Ternary NOT with delay information + fn ternary_not_with_delay(a: i32) -> GateResult { + return GateResult{ + .output = -a, + .delay = GATE_DELAY_UNIT, + }; + } + + // ===================================================== + // 3. MIN Gate (Ternary AND) + // ========================================================================= + + // ternary_min(a: i32, b: i32) -> i32 + // Ternary MIN (acts as AND in balanced ternary) + // Result is the minimum of two trits + fn ternary_min(a: i32, b: i32) -> i32 { + if (a < b) { + return a; + } + return b; + } + + // ternary_and(a: i32, b: i32) -> i32 + // Ternary AND (logical AND with three-valued logic) + // Uses Kleene/389ukasiewicz interpretation + fn ternary_and(a: i32, b: i32) -> i32 { + // Map to [0,1,2] for logical AND + const a_mapped = a + 1; // -1->0, 0->1, 1->2 + const b_mapped = b + 1; + const result_mapped = ternary_min(a_mapped, b_mapped); + return result_mapped - 1; // Map back + } + + // ===================================================== + // 4. MAX Gate (Ternary OR) + // ========================================================================= + + // ternary_max(a: i32, b: i32) -> i32 + // Ternary MAX (acts as OR in balanced ternary) + // Result is the maximum of two trits + fn ternary_max(a: i32, b: i32) -> i32 { + if (a > b) { + return a; + } + return b; + } + + // ternary_or(a: i32, b: i32) -> i32 + // Ternary OR (logical OR with three-valued logic) + // Uses Kleene/520ukasiewicz interpretation + fn ternary_or(a: i32, b: i32) -> i32 { + const a_mapped = a + 1; + const b_mapped = b + 1; + const result_mapped = ternary_max(a_mapped, b_mapped); + return result_mapped - 1; + } + + // ===================================================== + // 5. Consensus Gate + // ========================================================================= + + // ternary_consensus(a: i32, b: i32, c: i32) -> i32 + // Consensus gate: returns value if all equal, else TRIT_ZERO + fn ternary_consensus(a: i32, b: i32, c: i32) -> i32 { + if (a == b and b == c) { + return a; + } + return TRIT_ZERO; + } + + // ===================================================== + // 6. Majority Gate + // ========================================================================= + + // ternary_majority(a: i32, b: i32, c: i32) -> i32 + // Majority gate: returns the most common value + fn ternary_majority(a: i32, b: i32, c: i32) -> i32 { + // Count occurrences + var neg_count : i32 = 0; + var zero_count : i32 = 0; + var pos_count : i32 = 0; + + if (a == TRIT_NEG) { neg_count = neg_count + 1; } + else if (a == TRIT_ZERO) { zero_count = zero_count + 1; } + else { pos_count = pos_count + 1; } + + if (b == TRIT_NEG) { neg_count = neg_count + 1; } + else if (b == TRIT_ZERO) { zero_count = zero_count + 1; } + else { pos_count = pos_count + 1; } + + if (c == TRIT_NEG) { neg_count = neg_count + 1; } + else if (c == TRIT_ZERO) { zero_count = zero_count + 1; } + else { pos_count = pos_count + 1; } + + // Return majority + if (neg_count >= 2) { return TRIT_NEG; } + if (zero_count >= 2) { return TRIT_ZERO; } + if (pos_count >= 2) { return TRIT_POS; } + + // Tie: prefer middle value + return TRIT_ZERO; + } + + // ===================================================== + // 7. Any Gate + // ========================================================================= + + // ternary_any(a: i32, b: i32, c: i32) -> i32 + // Any gate: returns TRIT_POS if any input is TRIT_POS + fn ternary_any(a: i32, b: i32, c: i32) -> i32 { + if (a == TRIT_POS or b == TRIT_POS or c == TRIT_POS) { + return TRIT_POS; + } + if (a == TRIT_NEG and b == TRIT_NEG and c == TRIT_NEG) { + return TRIT_NEG; + } + return TRIT_ZERO; + } + + // ===================================================== + // 8. All Gate + // ========================================================================= + + // ternary_all(a: i32, b: i32, c: i32) -> i32 + // All gate: returns TRIT_NEG if all inputs are TRIT_NEG + fn ternary_all(a: i32, b: i32, c: i32) -> i32 { + if (a == TRIT_NEG and b == TRIT_NEG and c == TRIT_NEG) { + return TRIT_NEG; + } + if (a == TRIT_POS and b == TRIT_POS and c == TRIT_POS) { + return TRIT_POS; + } + return TRIT_ZERO; + } + + // ===================================================== + // 9. Data Structures + // ========================================================================= + + struct GateResult { + output : i32, + delay : u64, + } + + struct GateTruthTable { + name : [32]u8, + inputs : usize, + outputs : usize, + rows : [27]i32, // 3^N entries max + row_count : usize, + } + + // ===================================================== + // 10. TDD - Tests + // ========================================================================= + + test ternary_not_inversion + assert ternary_not(TRIT_NEG) == TRIT_POS + assert ternary_not(TRIT_ZERO) == TRIT_ZERO + assert ternary_not(TRIT_POS) == TRIT_NEG + + test ternary_min_behavior + assert ternary_min(TRIT_NEG, TRIT_NEG) == TRIT_NEG + assert ternary_min(TRIT_NEG, TRIT_ZERO) == TRIT_NEG + assert ternary_min(TRIT_NEG, TRIT_POS) == TRIT_NEG + assert ternary_min(TRIT_ZERO, TRIT_ZERO) == TRIT_ZERO + assert ternary_min(TRIT_ZERO, TRIT_POS) == TRIT_ZERO + assert ternary_min(TRIT_POS, TRIT_POS) == TRIT_POS + + test ternary_max_behavior + assert ternary_max(TRIT_NEG, TRIT_NEG) == TRIT_NEG + assert ternary_max(TRIT_NEG, TRIT_ZERO) == TRIT_ZERO + assert ternary_max(TRIT_NEG, TRIT_POS) == TRIT_POS + assert ternary_max(TRIT_ZERO, TRIT_ZERO) == TRIT_ZERO + assert ternary_max(TRIT_ZERO, TRIT_POS) == TRIT_POS + assert ternary_max(TRIT_POS, TRIT_POS) == TRIT_POS + + test ternary_and_logic + // Both positive = positive + assert ternary_and(TRIT_POS, TRIT_POS) == TRIT_POS + // One zero = zero + assert ternary_and(TRIT_POS, TRIT_ZERO) == TRIT_ZERO + assert ternary_and(TRIT_ZERO, TRIT_POS) == TRIT_ZERO + // One negative = negative + assert ternary_and(TRIT_POS, TRIT_NEG) == TRIT_NEG + assert ternary_and(TRIT_NEG, TRIT_POS) == TRIT_NEG + + test ternary_or_logic + // Both positive = positive + assert ternary_or(TRIT_POS, TRIT_POS) == TRIT_POS + // One positive = positive + assert ternary_or(TRIT_POS, TRIT_ZERO) == TRIT_POS + assert ternary_or(TRIT_ZERO, TRIT_POS) == TRIT_POS + // Both negative = negative + assert ternary_or(TRIT_NEG, TRIT_NEG) == TRIT_NEG + + test ternary_consensus_all_equal + assert ternary_consensus(TRIT_NEG, TRIT_NEG, TRIT_NEG) == TRIT_NEG + assert ternary_consensus(TRIT_ZERO, TRIT_ZERO, TRIT_ZERO) == TRIT_ZERO + assert ternary_consensus(TRIT_POS, TRIT_POS, TRIT_POS) == TRIT_POS + + test ternary_consensus_not_all_equal + assert ternary_consensus(TRIT_NEG, TRIT_NEG, TRIT_ZERO) == TRIT_ZERO + assert ternary_consensus(TRIT_POS, TRIT_ZERO, TRIT_NEG) == TRIT_ZERO + + test ternary_majority_clear_cases + assert ternary_majority(TRIT_NEG, TRIT_NEG, TRIT_POS) == TRIT_NEG + assert ternary_majority(TRIT_POS, TRIT_POS, TRIT_NEG) == TRIT_POS + assert ternary_majority(TRIT_ZERO, TRIT_ZERO, TRIT_NEG) == TRIT_ZERO + + test ternary_any_with_positive + assert ternary_any(TRIT_POS, TRIT_NEG, TRIT_NEG) == TRIT_POS + assert ternary_any(TRIT_NEG, TRIT_POS, TRIT_ZERO) == TRIT_POS + assert ternary_any(TRIT_NEG, TRIT_NEG, TRIT_POS) == TRIT_POS + + test ternary_any_without_positive + assert ternary_any(TRIT_NEG, TRIT_NEG, TRIT_NEG) == TRIT_NEG + assert ternary_any(TRIT_NEG, TRIT_NEG, TRIT_ZERO) == TRIT_ZERO + + test ternary_all_with_negative + assert ternary_all(TRIT_NEG, TRIT_NEG, TRIT_NEG) == TRIT_NEG + assert ternary_all(TRIT_NEG, TRIT_NEG, TRIT_POS) == TRIT_ZERO + + test ternary_all_with_positive + assert ternary_all(TRIT_POS, TRIT_POS, TRIT_POS) == TRIT_POS + assert ternary_all(TRIT_POS, TRIT_POS, TRIT_ZERO) == TRIT_ZERO + + // ===================================================== + // 11. TDD - Invariants + // ========================================================================= + + invariant not_double_inversion + // Applying NOT twice returns original value + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + var i : usize = 0; + while (i < 3) { + assert ternary_not(ternary_not(vals[i])) == vals[i] + i = i + 1; + } + + invariant min_commutative + // MIN is commutative: min(a, b) == min(b, a) + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + var i : usize = 0; + while (i < 3) { + var j : usize = 0; + while (j < 3) { + assert ternary_min(vals[i], vals[j]) == ternary_min(vals[j], vals[i]) + j = j + 1; + } + i = i + 1; + } + + invariant max_commutative + // MAX is commutative: max(a, b) == max(b, a) + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + var i : usize = 0; + while (i < 3) { + var j : usize = 0; + while (j < 3) { + assert ternary_max(vals[i], vals[j]) == ternary_max(vals[j], vals[i]) + j = j + 1; + } + i = i + 1; + } + + invariant min_associative + // MIN is associative: min(min(a, b), c) == min(a, min(b, c)) + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + var i : usize = 0; + while (i < 3) { + var j : usize = 0; + while (j < 3) { + var k : usize = 0; + while (k < 3) { + const left = ternary_min(ternary_min(vals[i], vals[j]), vals[k]); + const right = ternary_min(vals[i], ternary_min(vals[j], vals[k])); + assert left == right + k = k + 1; + } + j = j + 1; + } + i = i + 1; + } + + invariant max_associative + // MAX is associative: max(max(a, b), c) == max(a, max(b, c)) + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + var i : usize = 0; + while (i < 3) { + var j : usize = 0; + while (j < 3) { + var k : usize = 0; + while (k < 3) { + const left = ternary_max(ternary_max(vals[i], vals[j]), vals[k]); + const right = ternary_max(vals[i], ternary_max(vals[j], vals[k])); + assert left == right + k = k + 1; + } + j = j + 1; + } + i = i + 1; + } + + invariant min_absorbing_element + // TRIT_NEG is absorbing for MIN: min(a, TRIT_NEG) == TRIT_NEG + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + var i : usize = 0; + while (i < 3) { + assert ternary_min(vals[i], TRIT_NEG) == TRIT_NEG + i = i + 1; + } + + invariant max_absorbing_element + // TRIT_POS is absorbing for MAX: max(a, TRIT_POS) == TRIT_POS + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + var i : usize = 0; + while (i < 3) { + assert ternary_max(vals[i], TRIT_POS) == TRIT_POS + i = i + 1; + } + + // ===================================================== + // 12. TDD - Benchmarks + // ========================================================================= + + bench ternary_not_performance + // Measure: cycles to compute 1000 NOT operations + // Target: < 500 cycles + @setEvalBranchQuota(10000); + var result : i32 = 0; + for (0..1000) |_| { + result = ternary_not(result); + } + _ = result; + + bench ternary_min_performance + // Measure: cycles to compute 1000 MIN operations + // Target: < 1000 cycles + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + @setEvalBranchQuota(10000); + var result : i32 = 0; + for (0..1000) |_| { + result = ternary_min(result, vals[@as(usize, @intCast(result + 1)) % 3]); + } + _ = result; + + bench ternary_max_performance + // Measure: cycles to compute 1000 MAX operations + // Target: < 1000 cycles + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + @setEvalBranchQuota(10000); + var result : i32 = 0; + for (0..1000) |_| { + result = ternary_max(result, vals[@as(usize, @intCast(result + 1)) % 3]); + } + _ = result; + + bench ternary_consensus_performance + // Measure: cycles to compute 1000 CONSENSUS operations + // Target: < 2000 cycles + const vals = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS}; + @setEvalBranchQuota(10000); + var result : i32 = 0; + var idx : usize = 0; + for (0..1000) |_| { + result = ternary_consensus( + vals[idx % 3], + vals[(idx + 1) % 3], + vals[(idx + 2) % 3] + ); + idx = idx + 1; + } + _ = result; +} diff --git a/apps/website/public/t27/specs/isa/ternary_graph.t27 b/apps/website/public/t27/specs/isa/ternary_graph.t27 new file mode 100644 index 0000000000..124a346c4c --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_graph.t27 @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/ternary_graph.t27 +// Ternary Graph Operations Specification +// Ring 083 - Graph algorithms on ternary-weighted adjacency +// 01 + 1/23 = 3 | TRINITY + +module TernaryGraph { + use base::types; + + const MAX_VERTICES : usize = 27; + const TRIT_NEG : i32 = -1; + const TRIT_ZERO : i32 = 0; + const TRIT_POS : i32 = 1; + + // graph_init: Zero out adjacency matrix + fn graph_init(adj: [][]i32, n: usize) void { + var i : usize = 0; + while (i < n) { + var j : usize = 0; + while (j < n) { + adj[i][j] = TRIT_ZERO; + j = j + 1; + } + i = i + 1; + } + } + + // add_edge: Set directed edge weight (trit value) + fn add_edge(adj: [][]i32, from: usize, to: usize, weight: i32) void { + if (from < MAX_VERTICES and to < MAX_VERTICES) { + adj[from][to] = weight; + } + } + + // add_edge_undirected: Set both directions + fn add_edge_undirected(adj: [][]i32, u: usize, v: usize, weight: i32) void { + add_edge(adj, u, v, weight); + add_edge(adj, v, u, weight); + } + + // edge_weight: Get weight of edge, TRIT_ZERO if none + fn edge_weight(adj: [][]i32, from: usize, to: usize) i32 { + if (from < MAX_VERTICES and to < MAX_VERTICES) { + return adj[from][to]; + } + return TRIT_ZERO; + } + + // degree_out: Sum of outgoing edge weights + fn degree_out(adj: [][]i32, v: usize, n: usize) i32 { + var sum : i32 = 0; + var j : usize = 0; + while (j < n) { + sum = sum + adj[v][j]; + j = j + 1; + } + return sum; + } + + // degree_in: Sum of incoming edge weights + fn degree_in(adj: [][]i32, v: usize, n: usize) i32 { + var sum : i32 = 0; + var i : usize = 0; + while (i < n) { + sum = sum + adj[i][v]; + i = i + 1; + } + return sum; + } + + // neighbor_count: Count non-zero edges from v + fn neighbor_count(adj: [][]i32, v: usize, n: usize) usize { + var count : usize = 0; + var j : usize = 0; + while (j < n) { + if (adj[v][j] != TRIT_ZERO) { + count = count + 1; + } + j = j + 1; + } + return count; + } + + // has_path_2: Check if path of length <= 2 exists from src to dst + fn has_path_2(adj: [][]i32, src: usize, dst: usize, n: usize) bool { + if (adj[src][dst] != TRIT_ZERO) { return true; } + var k : usize = 0; + while (k < n) { + if (adj[src][k] != TRIT_ZERO and adj[k][dst] != TRIT_ZERO) { + return true; + } + k = k + 1; + } + return false; + } + + // test: add and query edge + test add_query_edge { + // (adj initialized externally as [27][27]i32) + // Skipped: needs 2D allocation — validated via invariant + } + + // test: degree calculation + test degree_calc { + // Validated via invariant below + } + + // test: has_path_2 finds 2-hop path + test path_2hop { + // Validated via invariant below + } + + // invariant: undirected edge symmetry + invariant undirected_sym { + // If add_edge_undirected is used, adj[u][v] == adj[v][u] + // Checked at spec level + true; + } + + // invariant: degree_in + degree_out bounded by 27*2 = 54 + invariant degree_bound { + // For n <= 27, max degree = 27 edges * max weight 1 + true; + } + + // bench: degree_out on 27-vertex graph + bench degree_out_27 { + // Simulated — actual allocation at gen time + } +} diff --git a/apps/website/public/t27/specs/isa/ternary_hash.t27 b/apps/website/public/t27/specs/isa/ternary_hash.t27 new file mode 100644 index 0000000000..05d09f906d --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_hash.t27 @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/ternary_hash.t27 +// Ternary Hash Table Operations Specification +// Ring 087 - Hash table with ternary keys +// 01 + 1/23 = 3 | TRINITY + +module TernaryHashTable { + use base::types; + + const TABLE_SIZE : usize = 27; + const EMPTY_KEY : i32 = -999; + const TRIT_NEG : i32 = -1; + const TRIT_ZERO : i32 = 0; + const TRIT_POS : i32 = 1; + + // hash_trit: Simple hash for trit-based keys (modulo 27) + fn hash_trit(key: i32) usize { + var h : usize = (key + 1) as usize; + return h % TABLE_SIZE; + } + + // hash_init: Initialize table with EMPTY_KEY + fn hash_init(keys: []i32, values: []i32) void { + var i : usize = 0; + while (i < keys.len and i < values.len) { + keys[i] = EMPTY_KEY; + values[i] = TRIT_ZERO; + i = i + 1; + } + } + + // hash_insert: Insert key-value pair, linear probing. Returns success. + fn hash_insert(keys: []i32, values: []i32, key: i32, value: i32) bool { + var idx : usize = hash_trit(key); + var attempts : usize = 0; + while (attempts < TABLE_SIZE) { + if (keys[idx] == EMPTY_KEY or keys[idx] == key) { + keys[idx] = key; + values[idx] = value; + return true; + } + idx = (idx + 1) % TABLE_SIZE; + attempts = attempts + 1; + } + return false; + } + + // hash_lookup: Find value by key. Sets found=false if absent. + fn hash_lookup(keys: []i32, values: []i32, key: i32, found: *bool) i32 { + var idx : usize = hash_trit(key); + var attempts : usize = 0; + while (attempts < TABLE_SIZE) { + if (keys[idx] == EMPTY_KEY) { + found.* = false; + return TRIT_ZERO; + } + if (keys[idx] == key) { + found.* = true; + return values[idx]; + } + idx = (idx + 1) % TABLE_SIZE; + attempts = attempts + 1; + } + found.* = false; + return TRIT_ZERO; + } + + // hash_remove: Remove key. Returns true if found. + fn hash_remove(keys: []i32, values: []i32, key: i32) bool { + var idx : usize = hash_trit(key); + var attempts : usize = 0; + while (attempts < TABLE_SIZE) { + if (keys[idx] == EMPTY_KEY) { return false; } + if (keys[idx] == key) { + keys[idx] = EMPTY_KEY; + values[idx] = TRIT_ZERO; + return true; + } + idx = (idx + 1) % TABLE_SIZE; + attempts = attempts + 1; + } + return false; + } + + // hash_load: Count occupied slots + fn hash_load(keys: []i32) usize { + var count : usize = 0; + var i : usize = 0; + while (i < keys.len) { + if (keys[i] != EMPTY_KEY) { + count = count + 1; + } + i = i + 1; + } + return count; + } + + // test: insert and lookup + test insert_lookup { + var keys : [27]i32; + var vals : [27]i32; + hash_init(keys[0..], vals[0..]); + try hash_insert(keys[0..], vals[0..], 42, TRIT_POS); + var found : bool = false; + var v = hash_lookup(keys[0..], vals[0..], 42, &found); + try found; + try eq(v, TRIT_POS); + } + + // test: lookup missing key + test lookup_missing { + var keys : [27]i32; + var vals : [27]i32; + hash_init(keys[0..], vals[0..]); + var found : bool = true; + hash_lookup(keys[0..], vals[0..], 99, &found); + try not(found); + } + + // test: remove key + test remove_key { + var keys : [27]i32; + var vals : [27]i32; + hash_init(keys[0..], vals[0..]); + hash_insert(keys[0..], vals[0..], 7, TRIT_NEG); + try hash_remove(keys[0..], vals[0..], 7); + try eq(hash_load(keys[0..]), 0); + } + + // test: update existing key + test update_existing { + var keys : [27]i32; + var vals : [27]i32; + hash_init(keys[0..], vals[0..]); + hash_insert(keys[0..], vals[0..], 1, TRIT_NEG); + hash_insert(keys[0..], vals[0..], 1, TRIT_POS); + try eq(hash_load(keys[0..]), 1); + var found : bool = false; + var v = hash_lookup(keys[0..], vals[0..], 1, &found); + try eq(v, TRIT_POS); + } + + // invariant: load <= TABLE_SIZE + invariant load_bound { + var keys : [27]i32; + // After init, load = 0 <= 27 + hash_load(keys[0..]) <= TABLE_SIZE; + } + + // invariant: hash_trit returns valid index + invariant hash_in_range { + hash_trit(0) < TABLE_SIZE and hash_trit(-1) < TABLE_SIZE and hash_trit(1) < TABLE_SIZE; + } + + // bench: insert 27 keys + bench insert_27 { + var keys : [27]i32; + var vals : [27]i32; + hash_init(keys[0..], vals[0..]); + var i : usize = 0; + while (i < 27) { + hash_insert(keys[0..], vals[0..], (i as i32), (i as i32) % 3 - 1); + i = i + 1; + } + } +} diff --git a/apps/website/public/t27/specs/isa/ternary_memory.t27 b/apps/website/public/t27/specs/isa/ternary_memory.t27 new file mode 100644 index 0000000000..7be1904b0c --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_memory.t27 @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/ternary_memory.t27 +// Ternary Memory Specification +// Ring 089 - Memory operations for ternary architecture +// Load/store operations with ternary address and data +// phi^2 + 1/phi^2 = 3 | TRINITY + +module ISAMemoryOps { + use base::types; + + // ================================================================= + // 1. Memory Constants + // ========================================================================= + + // Trit values for memory operations + const TRIT_NEG : i32 = -1; + const TRIT_ZERO : i32 = 0; + const TRIT_POS : i32 = 1; + + // Memory configuration + const MEM_ADDR_WIDTH : usize = 27; // 27-trit addresses + const MEM_DATA_WIDTH : usize = 27; // 27-trit data words + const MEM_SIZE : usize = 27; // 3^27 locations (abstract) + const WORD_SIZE : usize = 27; // 27 trits per word + + // Memory alignment + const ALIGNMENT : usize = 3; // 3-trit alignment (trit boundary) + + // Access permissions + const PERM_READ : u8 = 0x1; // Read permission + const PERM_WRITE : u8 = 0x2; // Write permission + const PERM_EXEC : u8 = 0x4; // Execute permission + + // ================================================================= + // 2. Memory Operations + // ========================================================================= + + // mem_load(memory: []TernaryWord, addr: u32) -> TernaryWord + // Load word from memory at address + // Returns zero word if address is invalid + fn mem_load(memory: []TernaryWord, addr: u32) -> TernaryWord { + // Check address bounds + if (addr >= MEM_SIZE) { + return TernaryWord{ .raw = 0 }; + } + + return memory[addr]; + } + + // mem_store(memory: []TernaryWord, addr: u32, value: TernaryWord) -> bool + // Store word to memory at address + // Returns true if store succeeded + fn mem_store(memory: []TernaryWord, addr: u32, value: TernaryWord) -> bool { + // Check address bounds + if (addr >= MEM_SIZE) { + return false; + } + + memory[addr] = value; + return true; + } + + // mem_load_aligned(memory: []TernaryWord, addr: u32, align: usize) -> TernaryWord + // Load with alignment check + // Returns zero word if alignment fails + fn mem_load_aligned(memory: []TernaryWord, addr: u32, align: usize) -> TernaryWord { + // Check alignment + if (addr % align != 0) { + return TernaryWord{ .raw = 0 }; + } + + return mem_load(memory, addr); + } + + // mem_store_aligned(memory: []TernaryWord, addr: u32, value: TernaryWord, align: usize) -> bool + // Store with alignment check + // Returns true if store succeeded + fn mem_store_aligned(memory: []TernaryWord, addr: u32, value: TernaryWord, align: usize) -> bool { + // Check alignment + if (addr % align != 0) { + return false; + } + + return mem_store(memory, addr, value); + } + + // ================================================================= + // 3. Block Memory Operations + // ========================================================================= + + // mem_load_block(memory: []TernaryWord, base_addr: u32, count: usize, dest: []TernaryWord) -> bool + // Load block of memory (count words) from base_addr + // Used for block load instructions + fn mem_load_block(memory: []TernaryWord, base_addr: u32, count: usize, dest: []TernaryWord) -> bool { + // Check bounds + if (base_addr + count > MEM_SIZE) { + return false; + } + + if (base_addr + count > dest.len()) { + return false; + } + + // Load each word + var i : usize = 0; + while (i < count) { + dest[i] = memory[base_addr + i]; + i = i + 1; + } + + return true; + } + + // mem_store_block(memory: []TernaryWord, base_addr: u32, count: usize, src: []TernaryWord) -> bool + // Store block of memory (count words) to base_addr + // Used for block store instructions + fn mem_store_block(memory: []TernaryWord, base_addr: u32, count: usize, src: []TernaryWord) -> bool { + // Check bounds + if (base_addr + count > MEM_SIZE) { + return false; + } + + if (base_addr + count > src.len()) { + return false; + } + + // Store each word + var i : usize = 0; + while (i < count) { + memory[base_addr + i] = src[i]; + i = i + 1; + } + + return true; + } + + // ================================================================= + // 4. Stack Operations (Memory-based) + // ========================================================================= + + // Stack grows downward from high memory + const STACK_BASE : u32 = MEM_SIZE - 1; + + // mem_push(memory: []TernaryWord, sp: *u32, value: TernaryWord) -> bool + // Push value to stack (decrement SP, then store) + fn mem_push(memory: []TernaryWord, sp: *u32, value: TernaryWord) -> bool { + // Decrement stack pointer + if (sp.* == 0) { + return false; // Stack overflow + } + + sp.* = sp.* - 1; + + // Store value + return mem_store(memory, sp.*, value); + } + + // mem_pop(memory: []TernaryWord, sp: *u32) -> TernaryWord + // Pop value from stack (load, then increment SP) + fn mem_pop(memory: []TernaryWord, sp: *u32) -> TernaryWord { + // Load value + const value = mem_load(memory, sp.*); + + // Increment stack pointer + if (sp.* < MEM_SIZE - 1) { + sp.* = sp.* + 1; + } + + return value; + } + + // ================================================================= + // 5. Address Translation (Basic) + // ========================================================================= + + // virt_to_phys(virt_addr: u32, base: u32, limit: u32) -> u32 + // Simple virtual-to-physical address translation + // Used for segmented memory model + fn virt_to_phys(virt_addr: u32, base: u32, limit: u32) -> u32 { + // Check if virtual address is within segment limit + if (virt_addr > limit) { + return 0xFFFFFFFF; // Invalid address + } + + // Physical = base + virtual + const phys_addr = base + virt_addr; + + // Check physical bounds + if (phys_addr >= MEM_SIZE) { + return 0xFFFFFFFF; + } + + return phys_addr; + } + + // ================================================================= + // 6. Memory Protection + // ========================================================================= + + // mem_check_perm(addr: u32, perm: u8, protection_table: []u8) -> bool + // Check if address has required permission + fn mem_check_perm(addr: u32, perm: u8, protection_table: []u8) -> bool { + if (addr >= protection_table.len()) { + return false; + } + + return (protection_table[addr] & perm) != 0; + } + + // mem_protect(memory: []TernaryWord, addr: u32, perm: u8, protection_table: []u8) -> bool + // Set protection bits for address + fn mem_protect(memory: []TernaryWord, addr: u32, perm: u8, protection_table: []u8) -> bool { + if (addr >= protection_table.len()) { + return false; + } + + protection_table[addr] = protection_table[addr] | perm; + return true; + } + + // ================================================================= + // 7. Memory Copy Operations + // ========================================================================= + + // mem_copy(dst: []TernaryWord, dst_addr: u32, src: []TernaryWord, src_addr: u32, count: usize) -> bool + // Copy count words from source to destination + fn mem_copy(dst: []TernaryWord, dst_addr: u32, src: []TernaryWord, src_addr: u32, count: usize) -> bool { + // Check bounds + if (dst_addr + count > dst.len() or src_addr + count > src.len()) { + return false; + } + + // Handle overlap (copy direction matters) + if (dst_addr < src_addr) { + // Copy forward + var i : usize = 0; + while (i < count) { + dst[dst_addr + i] = src[src_addr + i]; + i = i + 1; + } + } else { + // Copy backward + var i : usize = count; + while (i > 0) { + i = i - 1; + dst[dst_addr + i] = src[src_addr + i]; + } + } + + return true; + } + + // mem_fill(dst: []TernaryWord, addr: u32, value: TernaryWord, count: usize) -> bool + // Fill count words with value + fn mem_fill(dst: []TernaryWord, addr: u32, value: TernaryWord, count: usize) -> bool { + // Check bounds + if (addr + count > dst.len()) { + return false; + } + + var i : usize = 0; + while (i < count) { + dst[addr + i] = value; + i = i + 1; + } + + return true; + } + + // ================================================================= + // 8. Word Operations + // ========================================================================= + + // word_extract_trit(word: TernaryWord, trit_index: usize) -> i32 + // Extract single trit from word (0 = least significant) + fn word_extract_trit(word: TernaryWord, trit_index: usize) -> i32 { + if (trit_index >= WORD_SIZE) { + return TRIT_ZERO; + } + + // Extract 2 bits and decode to trit + const encoded = (word.raw >> (trit_index * 2)) & 0x3; + + if (encoded == 0x0) { + return TRIT_NEG; + } else if (encoded == 0x1) { + return TRIT_ZERO; + } else { + return TRIT_POS; + } + } + + // word_pack_trit(word: TernaryWord, trit_index: usize, value: i32) -> TernaryWord + // Pack single trit into word + fn word_pack_trit(word: TernaryWord, trit_index: usize, value: i32) -> TernaryWord { + if (trit_index >= WORD_SIZE) { + return word; + } + + // Encode trit to 2 bits + const encoded : u32 = if (value == TRIT_NEG) { 0x0 } + else if (value == TRIT_ZERO) { 0x1 } + else { 0x2 }; + + // Clear and set bits + const mask = ~(0x3u32 << (trit_index * 2)); + var result = word; + result.raw = (result.raw & mask) | (encoded << (trit_index * 2)); + + return result; + } + + // ================================================================= + // TDD-Inside-Spec: Tests and Invariants + // ========================================================================= + + test mem_load_valid_address + given memory = []TernaryWord{TernaryWord{.raw = 0}, TernaryWord{.raw = 0x123}} + and value = mem_load(memory, 1) + then value.raw == 0 + + test mem_store_valid_address + given memory = []TernaryWord{TernaryWord{.raw = 0}, TernaryWord{.raw = 0}} + and word = TernaryWord{.raw = 0xABC} + and success = mem_store(memory, 1, word) + and result = mem_load(memory, 1) + then success == true and result.raw == 0xABC + + test mem_load_invalid_address + given memory = []TernaryWord{TernaryWord{.raw = 0}} + and value = mem_load(memory, 999) + then value.raw == 0 + + test mem_store_invalid_address + given memory = []TernaryWord{TernaryWord{.raw = 0}} + and word = TernaryWord{.raw = 0xABC} + and success = mem_store(memory, 999, word) + then success == false + + test mem_load_aligned_success + given memory = []TernaryWord{TernaryWord{.raw = 0x123}, TernaryWord{.raw = 0x456}} + and value = mem_load_aligned(memory, 3, 3) + then value.raw == 0x456 + + test mem_load_aligned_fail + given memory = []TernaryWord{TernaryWord{.raw = 0x123}, TernaryWord{.raw = 0x456}} + and value = mem_load_aligned(memory, 2, 3) + then value.raw == 0 + + test mem_store_aligned_success + given memory = []TernaryWord{TernaryWord{.raw = 0}, TernaryWord{.raw = 0}} + and word = TernaryWord{.raw = 0xABC} + and success = mem_store_aligned(memory, 3, word, 3) + and result = mem_load(memory, 3) + then success == true and result.raw == 0xABC + + test mem_store_aligned_fail + given memory = []TernaryWord{TernaryWord{.raw = 0}, TernaryWord{.raw = 0}} + and word = TernaryWord{.raw = 0xABC} + and success = mem_store_aligned(memory, 2, word, 3) + then success == false + + test mem_push_pop_roundtrip + given memory = []TernaryWord{TernaryWord{.raw = 0}, TernaryWord{.raw = 0}, TernaryWord{.raw = 0}} + and sp : u32 = 3 + and word = TernaryWord{.raw = 0xDEF} + and push_ok = mem_push(memory, &sp, word) + and popped = mem_pop(memory, &sp) + then push_ok == true and popped.raw == 0xDEF and sp == 3 + + test mem_push_overflow + given memory = []TernaryWord{} + and sp : u32 = 0 + and word = TernaryWord{.raw = 0x123} + and success = mem_push(memory, &sp, word) + then success == false + + test mem_block_copy_success + given src = []TernaryWord{TernaryWord{.raw = 0x111}, TernaryWord{.raw = 0x222}} + and dst = []TernaryWord{TernaryWord{.raw = 0}, TernaryWord{.raw = 0}} + and success = mem_copy(dst, 0, src, 0, 2) + and v0 = mem_load(dst, 0) + and v1 = mem_load(dst, 1) + then success == true and v0.raw == 0x111 and v1.raw == 0x222 + + test mem_block_copy_overlap + given memory = []TernaryWord{TernaryWord{.raw = 0x111}, TernaryWord{.raw = 0x222}, TernaryWord{.raw = 0x333}} + and success = mem_copy(memory, 0, memory, 1, 2) + and v0 = mem_load(memory, 0) + and v1 = mem_load(memory, 1) + then success == true and v0.raw == 0x222 and v1.raw == 0x333 + + test mem_fill_success + given memory = []TernaryWord{TernaryWord{.raw = 0}, TernaryWord{.raw = 0}, TernaryWord{.raw = 0}} + and word = TernaryWord{.raw = 0xABC} + and success = mem_fill(memory, 0, word, 2) + then success == true + + test virt_to_phys_valid + given virt = 10 + and base = 100 + and limit = 50 + and phys = virt_to_phys(virt, base, limit) + then phys == 110 + + test virt_to_phys_out_of_range + given virt = 100 + and base = 10 + and limit = 50 + and phys = virt_to_phys(virt, base, limit) + then phys == 0xFFFFFFFF + + test word_pack_extract_trit + given word = TernaryWord{.raw = 0} + and packed = word_pack_trit(word, 5, TRIT_POS) + and extracted = word_extract_trit(packed, 5) + then extracted == TRIT_POS + + test word_pack_neg_trit + given word = TernaryWord{.raw = 0xFFFFFFFF} + and packed = word_pack_trit(word, 0, TRIT_NEG) + and extracted = word_extract_trit(packed, 0) + then extracted == TRIT_NEG + + test word_pack_zero_trit + given word = TernaryWord{.raw = 0xFFFFFFFF} + and packed = word_pack_trit(word, 1, TRIT_ZERO) + and extracted = word_extract_trit(packed, 1) + then extracted == TRIT_ZERO + + invariant mem_size_is_power_of_three + assert MEM_SIZE == 27 or MEM_SIZE == 19683 or MEM_SIZE == 531441 + + invariant mem_addr_width_matches_word_size + assert MEM_ADDR_WIDTH == WORD_SIZE + + invariant mem_alignment_power_of_three + assert ALIGNMENT == 1 or ALIGNMENT == 3 or ALIGNMENT == 9 + + invariant mem_permissions_valid + assert PERM_READ == 0x1 + assert PERM_WRITE == 0x2 + assert PERM_EXEC == 0x4 + + invariant mem_load_preserves_memory + given memory = []TernaryWord{TernaryWord{.raw = 0x123}, TernaryWord{.raw = 0x456}} + and before = mem_load(memory, 1) + and after = mem_load(memory, 1) + assert before.raw == after.raw + + invariant mem_store_changes_memory + given memory = []TernaryWord{TernaryWord{.raw = 0x123}} + and word = TernaryWord{.raw = 0xABC} + and mem_store(memory, 0, word) + and result = mem_load(memory, 0) + assert result.raw == 0xABC + + invariant sp_decrements_on_push + given memory = []TernaryWord{TernaryWord{.raw = 0}} + and sp_before : u32 = 5 + and mem_push(memory, &sp_before, TernaryWord{.raw = 0}) + assert sp_before < 5 + + invariant sp_increments_on_pop + given memory = []TernaryWord{TernaryWord{.raw = 0x123}} + and sp_before : u32 = 3 + and mem_pop(memory, &sp_before) + assert sp_before > 3 + + invariant mem_copy_handles_overlap + given memory = []TernaryWord{TernaryWord{.raw = 0x111}, TernaryWord{.raw = 0x222}, TernaryWord{.raw = 0x333}} + and mem_copy(memory, 0, memory, 1, 2) + and v0 = mem_load(memory, 0) + and v1 = mem_load(memory, 1) + // After copy: [0x222, 0x333] (forward copy from offset 1) + assert v0.raw == 0x222 and v1.raw == 0x333 + + invariant mem_protect_modifies_permissions + given prot = []u8{0, 0, 0} + and mem_protect([]TernaryWord{}, 1, PERM_READ, prot) + and new_perm = prot[1] + assert new_perm == PERM_READ + + invariant word_extract_trit_range + for (const i) |idx| in [0usize, 13, 26] { + const word = TernaryWord{.raw = 0}; + const trit = word_extract_trit(word, idx); + assert trit == TRIT_ZERO or trit == TRIT_NEG or trit == TRIT_POS; + } + + invariant stack_base_is_highest_address + assert STACK_BASE == MEM_SIZE - 1 + + invariant virt_to_phys_zero_maps_invalid + assert virt_to_phys(0, 0, 0) == 0 // Base 0, limit 0 -> addr 0 valid + assert virt_to_phys(1, 0, 0) == 0xFFFFFFFF // Out of limit + + bench mem_load_latency + measure: nanoseconds to mem_load([]TernaryWord{TernaryWord{.raw = 0}}, 10) + target: < 100ns + + bench mem_store_latency + measure: nanoseconds to mem_store([]TernaryWord{TernaryWord{.raw = 0}}, 10, TernaryWord{.raw = 0x123}) + target: < 100ns + + bench mem_copy_latency + measure: nanoseconds to mem_copy([]TernaryWord{.raw = 0, 0, 0}, 0, []TernaryWord{.raw = 0, 0, 0}, 10) + target: < 500ns + + bench mem_fill_latency + measure: nanoseconds to mem_fill([]TernaryWord{.raw = 0}, 0, TernaryWord{.raw = 0xABC}, 10) + target: < 400ns + + bench word_pack_trit_latency + measure: nanoseconds to word_pack_trit(TernaryWord{.raw = 0}, 13, TRIT_POS) + target: < 50ns + + bench word_extract_trit_latency + measure: nanoseconds to word_extract_trit(TernaryWord{.raw = (0x2u32 << 26)}, 13) + target: < 50ns +} diff --git a/apps/website/public/t27/specs/isa/ternary_pattern_matching.t27 b/apps/website/public/t27/specs/isa/ternary_pattern_matching.t27 new file mode 100644 index 0000000000..a1940886bc --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_pattern_matching.t27 @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/ternary_pattern_matching.t27 +// Ternary Pattern Matching Operations Specification +// Ring 082 - Pattern matching algorithms for ternary sequences +// 01 + 1/23 = 3 | TRINITY + +module TernaryPatternMatching { + use base::types; + + const TRIT_NEG : i32 = -1; + const TRIT_ZERO : i32 = 0; + const TRIT_POS : i32 = 1; + + // match_exact: Find first exact match of pattern in text, return index or -1 + fn match_exact(text: []i32, pattern: []i32) i64 { + if (pattern.len == 0 or pattern.len > text.len) { return -1; } + var i : usize = 0; + while (i <= text.len - pattern.len) { + var j : usize = 0; + var matched : bool = true; + while (j < pattern.len and matched) { + if (text[i + j] != pattern[j]) { + matched = false; + } + j = j + 1; + } + if (matched) { + return i as i64; + } + i = i + 1; + } + return -1; + } + + // match_count: Count all non-overlapping occurrences of pattern + fn match_count(text: []i32, pattern: []i32) usize { + if (pattern.len == 0 or pattern.len > text.len) { return 0; } + var count : usize = 0; + var i : usize = 0; + while (i <= text.len - pattern.len) { + var j : usize = 0; + var matched : bool = true; + while (j < pattern.len and matched) { + if (text[i + j] != pattern[j]) { + matched = false; + } + j = j + 1; + } + if (matched) { + count = count + 1; + i = i + pattern.len; + } else { + i = i + 1; + } + } + return count; + } + + // match_wildcard: Match with wildcard (TRIT_ZERO matches any value) + fn match_wildcard(text: []i32, pattern: []i32) i64 { + if (pattern.len == 0 or pattern.len > text.len) { return -1; } + var i : usize = 0; + while (i <= text.len - pattern.len) { + var j : usize = 0; + var matched : bool = true; + while (j < pattern.len and matched) { + if (pattern[j] != TRIT_ZERO and text[i + j] != pattern[j]) { + matched = false; + } + j = j + 1; + } + if (matched) { + return i as i64; + } + i = i + 1; + } + return -1; + } + + // hamming_distance: Count mismatched positions between equal-length sequences + fn hamming_distance(a: []i32, b: []i32) usize { + var dist : usize = 0; + var len : usize = a.len; + if (b.len < len) { len = b.len; } + var i : usize = 0; + while (i < len) { + if (a[i] != b[i]) { + dist = dist + 1; + } + i = i + 1; + } + return dist; + } + + // longest_common_prefix: Find length of shared prefix + fn longest_common_prefix(a: []i32, b: []i32) usize { + var len : usize = a.len; + if (b.len < len) { len = b.len; } + var i : usize = 0; + while (i < len and a[i] == b[i]) { + i = i + 1; + } + return i; + } + + // test: exact match finds pattern + test match_exact_found { + var text = [6]i32{ -1, 0, 1, 0, -1, 1 }; + var pat = [2]i32{ 1, 0 }; + try eq(match_exact(text[0..], pat[0..]), 2); + } + + // test: exact match returns -1 when not found + test match_exact_miss { + var text = [4]i32{ -1, -1, 0, 0 }; + var pat = [2]i32{ 1, 1 }; + try eq(match_exact(text[0..], pat[0..]), -1); + } + + // test: match_count counts non-overlapping + test match_count_noverlap { + var text = [6]i32{ 1, 0, 1, 0, 1, 0 }; + var pat = [2]i32{ 1, 0 }; + try eq(match_count(text[0..], pat[0..]), 3); + } + + // test: wildcard match + test wildcard_match { + var text = [4]i32{ -1, 1, 0, -1 }; + var pat = [2]i32{ -1, TRIT_ZERO }; + try eq(match_wildcard(text[0..], pat[0..]), 0); + } + + // test: hamming distance + test hamming { + var a = [4]i32{ -1, 0, 1, 1 }; + var b = [4]i32{ -1, 1, 1, -1 }; + try eq(hamming_distance(a[0..], b[0..]), 2); + } + + // test: longest common prefix + test lcp { + var a = [4]i32{ -1, 0, 1, 1 }; + var b = [4]i32{ -1, 0, -1, 0 }; + try eq(longest_common_prefix(a[0..], b[0..]), 2); + } + + // invariant: hamming distance is symmetric + invariant hamming_symmetric { + var a = [3]i32{ -1, 0, 1 }; + var b = [3]i32{ 1, 0, -1 }; + hamming_distance(a[0..], b[0..]) == hamming_distance(b[0..], a[0..]); + } + + // invariant: lcp <= min(len(a), len(b)) + invariant lcp_bound { + var a = [3]i32{ -1, 0, 1 }; + var b = [5]i32{ -1, 0, 1, -1, 0 }; + longest_common_prefix(a[0..], b[0..]) <= a.len; + } + + // bench: match_exact 81-char text, 9-char pattern + bench match_exact_81 { + var text : [81]i32; + var pat : [9]i32; + var i : usize = 0; + while (i < 81) { text[i] = (i as i32) % 3 - 1; i = i + 1; } + var j : usize = 0; + while (j < 9) { pat[j] = (j as i32) % 3 - 1; j = j + 1; } + match_exact(text[0..], pat[0..]); + } +} diff --git a/apps/website/public/t27/specs/isa/ternary_search.t27 b/apps/website/public/t27/specs/isa/ternary_search.t27 new file mode 100644 index 0000000000..4428043052 --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_search.t27 @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/ternary_search.t27 +// Ternary Search Operations Specification +// Ring 081 - Search algorithms for ternary data +// 01 + 1/23 = 3 | TRINITY + +module TernarySearch { + use base::types; + + const TRIT_NEG : i32 = -1; + const TRIT_ZERO : i32 = 0; + const TRIT_POS : i32 = 1; + + // linear_search: O(n) scan, returns index or -1 + fn linear_search(data: []i32, target: i32) i64 { + var i : usize = 0; + while (i < data.len) { + if (data[i] == target) { + return i as i64; + } + i = i + 1; + } + return -1; + } + + // binary_search: O(log n) on sorted data, returns index or -1 + fn binary_search(data: []i32, target: i32) i64 { + var lo : usize = 0; + var hi : usize = data.len; + while (lo < hi) { + var mid : usize = lo + (hi - lo) / 2; + if (data[mid] == target) { + return mid as i64; + } else if (data[mid] < target) { + lo = mid + 1; + } else { + hi = mid; + } + } + return -1; + } + + // ternary_search_min: O(log3 n) find minimum in unimodal array + fn ternary_search_min(data: []i32) i64 { + if (data.len == 0) { return -1; } + var lo : usize = 0; + var hi : usize = data.len - 1; + while (hi - lo > 2) { + var third : usize = (hi - lo) / 3; + var m1 : usize = lo + third; + var m2 : usize = hi - third; + if (data[m1] < data[m2]) { + hi = m2 - 1; + } else { + lo = m1 + 1; + } + } + var best : usize = lo; + var i : usize = lo + 1; + while (i <= hi) { + if (data[i] < data[best]) { + best = i; + } + i = i + 1; + } + return best as i64; + } + + // count_occurrences: Count how many times target appears + fn count_occurrences(data: []i32, target: i32) usize { + var count : usize = 0; + var i : usize = 0; + while (i < data.len) { + if (data[i] == target) { + count = count + 1; + } + i = i + 1; + } + return count; + } + + // find_min: Return minimum value in array + fn find_min(data: []i32) i32 { + if (data.len == 0) { return TRIT_ZERO; } + var min_val : i32 = data[0]; + var i : usize = 1; + while (i < data.len) { + if (data[i] < min_val) { + min_val = data[i]; + } + i = i + 1; + } + return min_val; + } + + // find_max: Return maximum value in array + fn find_max(data: []i32) i32 { + if (data.len == 0) { return TRIT_ZERO; } + var max_val : i32 = data[0]; + var i : usize = 1; + while (i < data.len) { + if (data[i] > max_val) { + max_val = data[i]; + } + i = i + 1; + } + return max_val; + } + + // test: linear_search finds element + test linear_search_found { + var data = [5]i32{ -1, 0, 1, 0, -1 }; + try eq(linear_search(data[0..], TRIT_POS), 2); + } + + // test: linear_search returns -1 on miss + test linear_search_miss { + var data = [3]i32{ -1, 0, -1 }; + try eq(linear_search(data[0..], TRIT_POS), -1); + } + + // test: binary_search on sorted data + test binary_search_sorted { + var data = [5]i32{ -1, -1, 0, 1, 1 }; + try eq(binary_search(data[0..], TRIT_ZERO), 2); + } + + // test: count_occurrences counts correctly + test count_occurrences { + var data = [6]i32{ -1, 0, -1, 1, 0, -1 }; + try eq(count_occurrences(data[0..], TRIT_NEG), 3); + } + + // test: find_min_max + test find_min_max { + var data = [4]i32{ 1, -1, 0, 1 }; + try eq(find_min(data[0..]), TRIT_NEG); + try eq(find_max(data[0..]), TRIT_POS); + } + + // invariant: linear_search result matches manual scan + invariant linear_matches_scan { + var data = [4]i32{ 0, -1, 1, 0 }; + var idx = linear_search(data[0..], TRIT_NEG); + idx == 1; + } + + // invariant: count_occurrences non-negative + invariant count_nonneg { + var data = [3]i32{ -1, 0, 1 }; + count_occurrences(data[0..], TRIT_POS) >= 0; + } + + // bench: binary_search 81 elements + bench binary_search_81 { + var data : [81]i32; + var i : usize = 0; + while (i < 81) { + data[i] = (i as i32) % 3 - 1; + i = i + 1; + } + binary_search(data[0..], TRIT_ZERO); + } +} diff --git a/apps/website/public/t27/specs/isa/ternary_set.t27 b/apps/website/public/t27/specs/isa/ternary_set.t27 new file mode 100644 index 0000000000..24e45182ee --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_set.t27 @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/ternary_set.t27 +// Ternary Set Operations Specification +// Ring 085 - Set operations on ternary-valued elements +// 01 + 1/23 = 3 | TRINITY + +module TernarySet { + use base::types; + + const SET_MAX : usize = 27; + const TRIT_NEG : i32 = -1; + const TRIT_ZERO : i32 = 0; + const TRIT_POS : i32 = 1; + + // set_insert: Add value to set (sorted, no duplicates). Returns new length. + fn set_insert(set: []i32, len: *usize, value: i32) void { + if (len.* >= SET_MAX) { return; } + var i : usize = 0; + while (i < len.* and set[i] < value) { + i = i + 1; + } + if (i < len.* and set[i] == value) { return; } + // shift right + var j : usize = len.*; + while (j > i) { + set[j] = set[j - 1]; + j = j - 1; + } + set[i] = value; + len.* = len.* + 1; + } + + // set_contains: Check membership + fn set_contains(set: []i32, len: usize, value: i32) bool { + var lo : usize = 0; + var hi : usize = len; + while (lo < hi) { + var mid : usize = lo + (hi - lo) / 2; + if (set[mid] == value) { return true; } + if (set[mid] < value) { lo = mid + 1; } + else { hi = mid; } + } + return false; + } + + // set_union: A ∪ B → result, returns new length + fn set_union(a: []i32, a_len: usize, b: []i32, b_len: usize, result: []i32) usize { + var i : usize = 0; + var j : usize = 0; + var k : usize = 0; + while (i < a_len and j < b_len and k < SET_MAX) { + if (a[i] < b[j]) { + result[k] = a[i]; + i = i + 1; + } else if (a[i] > b[j]) { + result[k] = b[j]; + j = j + 1; + } else { + result[k] = a[i]; + i = i + 1; + j = j + 1; + } + k = k + 1; + } + while (i < a_len and k < SET_MAX) { + result[k] = a[i]; + i = i + 1; + k = k + 1; + } + while (j < b_len and k < SET_MAX) { + result[k] = b[j]; + j = j + 1; + k = k + 1; + } + return k; + } + + // set_intersection: A ∩ B → result + fn set_intersection(a: []i32, a_len: usize, b: []i32, b_len: usize, result: []i32) usize { + var i : usize = 0; + var j : usize = 0; + var k : usize = 0; + while (i < a_len and j < b_len and k < SET_MAX) { + if (a[i] < b[j]) { + i = i + 1; + } else if (a[i] > b[j]) { + j = j + 1; + } else { + result[k] = a[i]; + i = i + 1; + j = j + 1; + k = k + 1; + } + } + return k; + } + + // set_difference: A \ B → result + fn set_difference(a: []i32, a_len: usize, b: []i32, b_len: usize, result: []i32) usize { + var i : usize = 0; + var j : usize = 0; + var k : usize = 0; + while (i < a_len and k < SET_MAX) { + if (j >= b_len or a[i] < b[j]) { + result[k] = a[i]; + i = i + 1; + k = k + 1; + } else if (a[i] > b[j]) { + j = j + 1; + } else { + i = i + 1; + j = j + 1; + } + } + return k; + } + + // set_cardinality: Return length + fn set_cardinality(len: usize) usize { + return len; + } + + // test: insert and contains + test insert_contains { + var set : [27]i32; + var len : usize = 0; + set_insert(set[0..], &len, TRIT_NEG); + set_insert(set[0..], &len, TRIT_POS); + set_insert(set[0..], &len, TRIT_ZERO); + try eq(len, 3); + try set_contains(set[0..], len, TRIT_ZERO); + try not(set_contains(set[0..], len, 42)); + } + + // test: union + test union_ops { + var a = [2]i32{ -1, 1 }; + var b = [2]i32{ 0, 1 }; + var result : [27]i32; + var k = set_union(a[0..], 2, b[0..], 2, result[0..]); + try eq(k, 3); + } + + // test: intersection + test intersection_ops { + var a = [3]i32{ -1, 0, 1 }; + var b = [2]i32{ 0, 1 }; + var result : [27]i32; + var k = set_intersection(a[0..], 3, b[0..], 2, result[0..]); + try eq(k, 2); + } + + // test: difference + test difference_ops { + var a = [3]i32{ -1, 0, 1 }; + var b = [1]i32{ 0 }; + var result : [27]i32; + var k = set_difference(a[0..], 3, b[0..], 1, result[0..]); + try eq(k, 2); + } + + // invariant: union is commutative (|A∪B| = |B∪A|) + invariant union_commutative { + true; // guaranteed by sorted merge + } + + // invariant: |A∩B| <= min(|A|, |B|) + invariant intersection_bound { + true; // guaranteed by algorithm + } + + // bench: union of two 13-element sets + bench union_13 { + var a : [13]i32; + var b : [13]i32; + var result : [27]i32; + var i : usize = 0; + while (i < 13) { a[i] = (i as i32) * 2 - 13; b[i] = (i as i32) * 2 - 12; i = i + 1; } + set_union(a[0..], 13, b[0..], 13, result[0..]); + } +} diff --git a/apps/website/public/t27/specs/isa/ternary_shift.t27 b/apps/website/public/t27/specs/isa/ternary_shift.t27 new file mode 100644 index 0000000000..c34d3de73d --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_shift.t27 @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/ternary_shift.t27 +// Ternary Shift and Rotate Operations Specification +// Ring 067 - Bitwise/Tritwise shift and rotate operations +// Defines how ternary words are shifted and rotated +// phi^2 + 1/phi^2 = 3 | TRINITY + +module TernaryShift { + use base::types; + + // ===================================================== + // 1. Shift Constants + // ========================================================================= + + // Trit values + const TRIT_NEG : i32 = -1; + const TRIT_ZERO : i32 = 0; + const TRIT_POS : i32 = 1; + + // Word size + const WORD_SIZE : usize = 27; // 27 trits per word + const SHIFT_MASK : usize = 26; // For modulo operation + + // Shift directions + const SHIFT_LEFT : u8 = 0; + const SHIFT_RIGHT : u8 = 1; + + // ===================================================== + // 2. Shift Operations + // ========================================================================= + + // ternary_shift_left(word: []i32, shift: usize, len: usize) -> bool + // Shift word left by N trits, fill with zero + fn ternary_shift_left(word: []i32, shift: usize, len: usize) -> bool { + if (shift >= len) { + return false; // Would shift everything out + } + + // Shift trits + var i : usize = 0; + while (i < len - shift) { + word[i] = word[i + shift]; + i = i + 1; + } + + // Fill with zero + while (i < len) { + word[i] = TRIT_ZERO; + i = i + 1; + } + + return true; + } + + // ternary_shift_right(word: []i32, shift: usize, len: usize) -> bool + // Shift word right by N trits, fill with zero + fn ternary_shift_right(word: []i32, shift: usize, len: usize) -> bool { + if (shift >= len) { + return false; // Would shift everything out + } + + // Shift trits + var i : i64 = @as(i64, @intCast(len)) - 1; + while (i >= @as(i64, @intCast(shift))) { + const idx = @as(usize, @intCast(i)); + word[idx] = word[idx - shift]; + i = i - 1; + } + + // Fill with zero + while (i >= 0) { + const idx = @as(usize, @intCast(i)); + word[idx] = TRIT_ZERO; + i = i - 1; + } + + return true; + } + + // ===================================================== + // 3. Rotate Operations + // ========================================================================= + + // ternary_rotate_left(word: []i32, shift: usize, len: usize) -> void + // Rotate word left by N trits (circular shift) + fn ternary_rotate_left(word: []i32, shift: usize, len: usize) -> void { + if (len == 0) { + return; + } + + const actual_shift = shift % len; + if (actual_shift == 0) { + return; + } + + // Temporary buffer for rotation + var temp : [WORD_SIZE]i32 = undefined; + + // Copy shifted positions + var i : usize = 0; + while (i < len) { + const src_idx = (i + actual_shift) % len; + temp[i] = word[src_idx]; + i = i + 1; + } + + // Copy back + i = 0; + while (i < len) { + word[i] = temp[i]; + i = i + 1; + } + } + + // ternary_rotate_right(word: []i32, shift: usize, len: usize) -> void + // Rotate word right by N trits (circular shift) + fn ternary_rotate_right(word: []i32, shift: usize, len: usize) -> void { + if (len == 0) { + return; + } + + const actual_shift = shift % len; + if (actual_shift == 0) { + return; + } + + // Temporary buffer for rotation + var temp : [WORD_SIZE]i32 = undefined; + + // Copy shifted positions + var i : usize = 0; + while (i < len) { + const src_idx = (i + len - actual_shift) % len; + temp[i] = word[src_idx]; + i = i + 1; + } + + // Copy back + i = 0; + while (i < len) { + word[i] = temp[i]; + i = i + 1; + } + } + + // ===================================================== + // 4. Arithmetic Shift (Signed Shift) + // ========================================================================= + + // ternary_arithmetic_shift_right(word: []i32, shift: usize, len: usize) -> bool + // Arithmetic shift right: preserves sign bit + fn ternary_arithmetic_shift_right(word: []i32, shift: usize, len: usize) -> bool { + if (len == 0) { + return false; + } + + const actual_shift = shift % len; + if (actual_shift == 0) { + return true; + } + + // Get sign bit (most significant trit) + const sign_bit = word[len - 1]; + + // Shift right + var i : i64 = @as(i64, @intCast(len)) - 1; + while (i >= @as(i64, @intCast(actual_shift))) { + const idx = @as(usize, @intCast(i)); + word[idx] = word[idx - actual_shift]; + i = i - 1; + } + + // Fill with sign bit + while (i >= 0) { + const idx = @as(usize, @intCast(i)); + word[idx] = sign_bit; + i = i - 1; + } + + return true; + } + + // ===================================================== + // 5. Bit/Trit Extraction + // ========================================================================= + + // extract_trits(word: []i32, start: usize, count: usize, result: []i32) -> bool + // Extract N trits starting from position S + fn extract_trits(word: []i32, start: usize, count: usize, result: []i32) -> bool { + if (start + count > word.len) { + return false; + } + + var i : usize = 0; + while (i < count) { + result[i] = word[start + i]; + i = i + 1; + } + + return true; + } + + // insert_trits(word: []i32, start: usize, count: usize, values: []i32) -> bool + // Insert N trits starting from position S + fn insert_trits(word: []i32, start: usize, count: usize, values: []i32) -> bool { + if (start + count > word.len) { + return false; + } + + var i : usize = 0; + while (i < count) { + word[start + i] = values[i]; + i = i + 1; + } + + return true; + } + + // ===================================================== + // 6. TDD - Tests + // ========================================================================= + + test ternary_shift_left_basic + var word : [5]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_ZERO}; + assert ternary_shift_left(&word, 1, 5) == true + assert word[0] == TRIT_ZERO + assert word[1] == TRIT_NEG + assert word[2] == TRIT_POS + assert word[3] == TRIT_ZERO + assert word[4] == TRIT_ZERO + + test ternary_shift_right_basic + var word : [5]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_ZERO}; + assert ternary_shift_right(&word, 1, 5) == true + assert word[0] == TRIT_ZERO + assert word[1] == TRIT_POS + assert word[2] == TRIT_ZERO + assert word[3] == TRIT_NEG + assert word[4] == TRIT_ZERO + + test ternary_rotate_left_basic + var word : [5]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_ZERO}; + ternary_rotate_left(&word, 1, 5); + assert word[0] == TRIT_ZERO + assert word[1] == TRIT_NEG + assert word[2] == TRIT_POS + assert word[3] == TRIT_ZERO + assert word[4] == TRIT_POS + + test ternary_rotate_right_basic + var word : [5]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_ZERO}; + ternary_rotate_right(&word, 1, 5); + assert word[0] == TRIT_ZERO + assert word[1] == TRIT_POS + assert word[2] == TRIT_ZERO + assert word[3] == TRIT_NEG + assert word[4] == TRIT_POS + + test ternary_rotate_full_circle + var word : [5]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_ZERO}; + ternary_rotate_left(&word, 5, 5); + assert word[0] == TRIT_POS + assert word[1] == TRIT_ZERO + assert word[2] == TRIT_NEG + assert word[3] == TRIT_POS + assert word[4] == TRIT_ZERO + + test ternary_arithmetic_shift_right_positive + var word : [5]i32 = [_]i32{TRIT_POS, TRIT_POS, TRIT_POS, TRIT_POS, TRIT_POS}; + assert ternary_arithmetic_shift_right(&word, 1, 5) == true + assert word[4] == TRIT_POS // Sign bit preserved + + test ternary_arithmetic_shift_right_negative + var word : [5]i32 = [_]i32{TRIT_NEG, TRIT_ZERO, TRIT_POS, TRIT_ZERO, TRIT_NEG}; + assert ternary_arithmetic_shift_right(&word, 1, 5) == true + assert word[4] == TRIT_NEG // Sign bit preserved + + test extract_trits_basic + var word : [5]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_ZERO}; + var result : [3]i32 = undefined; + assert extract_trits(&word, 1, 3, &result) == true + assert result[0] == TRIT_ZERO + assert result[1] == TRIT_NEG + assert result[2] == TRIT_POS + + test insert_trits_basic + var word : [5]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_ZERO}; + var values : [2]i32 = [_]i32{TRIT_NEG, TRIT_NEG}; + assert insert_trits(&word, 2, 2, &values) == true + assert word[2] == TRIT_NEG + assert word[3] == TRIT_NEG + + // ===================================================== + // 7. TDD - Invariants + // ========================================================================= + + invariant shift_left_zero_fill + // Left shift should fill with zeros + var word : [5]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_NEG}; + _ = ternary_shift_left(&word, 2, 5); + assert word[3] == TRIT_ZERO + assert word[4] == TRIT_ZERO + + invariant shift_right_zero_fill + // Right shift should fill with zeros + var word : [5]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_NEG}; + _ = ternary_shift_right(&word, 2, 5); + assert word[0] == TRIT_ZERO + assert word[1] == TRIT_ZERO + + invariant rotate_preserves_elements + // Rotation should preserve all elements + var word : [5]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_NEG}; + + // Count each value + var pos_count : i32 = 0; + var zero_count : i32 = 0; + var neg_count : i32 = 0; + var i : usize = 0; + while (i < 5) { + if (word[i] == TRIT_POS) { pos_count = pos_count + 1; } + else if (word[i] == TRIT_ZERO) { zero_count = zero_count + 1; } + else { neg_count = neg_count + 1; } + i = i + 1; + } + + // Rotate + ternary_rotate_left(&word, 3, 5); + + // Count again + var pos_count_after : i32 = 0; + var zero_count_after : i32 = 0; + var neg_count_after : i32 = 0; + i = 0; + while (i < 5) { + if (word[i] == TRIT_POS) { pos_count_after = pos_count_after + 1; } + else if (word[i] == TRIT_ZERO) { zero_count_after = zero_count_after + 1; } + else { neg_count_after = neg_count_after + 1; } + i = i + 1; + } + + assert pos_count == pos_count_after + assert zero_count == zero_count_after + assert neg_count == neg_count_after + + invariant rotate_left_right_inverse + // Rotate left then right by same amount = original + var word : [5]i32 = [_]i32{TRIT_POS, TRIT_ZERO, TRIT_NEG, TRIT_POS, TRIT_NEG}; + var original : [5]i32 = undefined; + var i : usize = 0; + while (i < 5) { + original[i] = word[i]; + i = i + 1; + } + + ternary_rotate_left(&word, 2, 5); + ternary_rotate_right(&word, 2, 5); + + i = 0; + while (i < 5) { + assert word[i] == original[i] + i = i + 1; + } + + invariant arithmetic_shift_preserves_sign + // Arithmetic shift should preserve the sign bit + var word_pos : [5]i32 = [_]i32{TRIT_POS, TRIT_POS, TRIT_POS, TRIT_POS, TRIT_POS}; + var word_neg : [5]i32 = [_]i32{TRIT_NEG, TRIT_NEG, TRIT_NEG, TRIT_NEG, TRIT_NEG}; + + _ = ternary_arithmetic_shift_right(&word_pos, 3, 5); + _ = ternary_arithmetic_shift_right(&word_neg, 3, 5); + + assert word_pos[4] == TRIT_POS + assert word_neg[4] == TRIT_NEG + + // ===================================================== + // 8. TDD - Benchmarks + // ========================================================================= + + bench shift_left_performance + // Measure: cycles to perform 1000 left shifts + // Target: < 3000 cycles + var word : [27]i32 = [_]i32{TRIT_POS} ** 27; + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..1000) |i| { + result = ternary_shift_left(&word, @as(usize, @intCast(i % 27)), 27); + } + _ = result; + + bench rotate_left_performance + // Measure: cycles to perform 1000 left rotates + // Target: < 5000 cycles + var word : [27]i32 = [_]i32{TRIT_POS} ** 27; + @setEvalBranchQuota(10000); + for (0..1000) |i| { + ternary_rotate_left(&word, @as(usize, @intCast(i % 27)), 27); + } + + bench rotate_right_performance + // Measure: cycles to perform 1000 right rotates + // Target: < 5000 cycles + var word : [27]i32 = [_]i32{TRIT_POS} ** 27; + @setEvalBranchQuota(10000); + for (0..1000) |i| { + ternary_rotate_right(&word, @as(usize, @intCast(i % 27)), 27); + } + + bench arithmetic_shift_right_performance + // Measure: cycles to perform 1000 arithmetic right shifts + // Target: < 4000 cycles + var word : [27]i32 = [_]i32{TRIT_POS} ** 27; + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..1000) |i| { + result = ternary_arithmetic_shift_right(&word, @as(usize, @intCast(i % 27)), 27); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/isa/ternary_sorting.t27 b/apps/website/public/t27/specs/isa/ternary_sorting.t27 new file mode 100644 index 0000000000..290e92dcde --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_sorting.t27 @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/ternary_sorting.t27 +// Ternary Sorting Operations Specification +// Ring 080 - Sorting algorithms for ternary data +// 01 + 1/23 = 3 | TRINITY + +module TernarySorting { + use base::types; + + const TRIT_NEG : i32 = -1; + const TRIT_ZERO : i32 = 0; + const TRIT_POS : i32 = 1; + + // sort_compare: Compare two trits for ordering (-1, 0, +1) + fn sort_compare(a: i32, b: i32) i32 { + if (a < b) { return TRIT_NEG; } + if (a > b) { return TRIT_POS; } + return TRIT_ZERO; + } + + // bubble_sort: O(n^2) bubble sort with early exit + fn bubble_sort(data: []i32) void { + var n : usize = data.len; + var swapped : bool = true; + while (swapped and n > 1) { + swapped = false; + var i : usize = 0; + while (i < n - 1) { + if (data[i] > data[i + 1]) { + var tmp : i32 = data[i]; + data[i] = data[i + 1]; + data[i + 1] = tmp; + swapped = true; + } + i = i + 1; + } + n = n - 1; + } + } + + // selection_sort: O(n^2) selection sort + fn selection_sort(data: []i32) void { + var i : usize = 0; + while (i < data.len - 1) { + var min_idx : usize = i; + var j : usize = i + 1; + while (j < data.len) { + if (data[j] < data[min_idx]) { + min_idx = j; + } + j = j + 1; + } + if (min_idx != i) { + var tmp : i32 = data[i]; + data[i] = data[min_idx]; + data[min_idx] = tmp; + } + i = i + 1; + } + } + + // insertion_sort: O(n^2) insertion sort (stable) + fn insertion_sort(data: []i32) void { + var i : usize = 1; + while (i < data.len) { + var key : i32 = data[i]; + var j : usize = i; + while (j > 0 and data[j - 1] > key) { + data[j] = data[j - 1]; + j = j - 1; + } + data[j] = key; + i = i + 1; + } + } + + // quick_sort: O(n log n) average, in-place partition + fn quick_sort(data: []i32, lo: usize, hi: usize) void { + if (lo < hi and hi < data.len) { + var pivot : i32 = data[hi]; + var i : usize = lo; + var j : usize = lo; + while (j < hi) { + if (data[j] <= pivot) { + var tmp : i32 = data[i]; + data[i] = data[j]; + data[j] = tmp; + i = i + 1; + } + j = j + 1; + } + var tmp2 : i32 = data[i]; + data[i] = data[hi]; + data[hi] = tmp2; + if (i > 0) { + quick_sort(data, lo, i - 1); + } + quick_sort(data, i + 1, hi); + } + } + + // is_sorted: Check if data is in non-decreasing order + fn is_sorted(data: []i32) bool { + var i : usize = 1; + while (i < data.len) { + if (data[i] < data[i - 1]) { + return false; + } + i = i + 1; + } + return true; + } + + // test: bubble sort sorts correctly + test bubble_sort { + var data = [5]i32{ 1, -1, 0, 1, -1 }; + bubble_sort(data[0..]); + try eq(data[0], TRIT_NEG); + try eq(data[4], TRIT_POS); + try is_sorted(data[0..]); + } + + // test: selection sort sorts correctly + test selection_sort { + var data = [3]i32{ 0, -1, 1 }; + selection_sort(data[0..]); + try eq(data[0], TRIT_NEG); + try eq(data[2], TRIT_POS); + } + + // test: insertion sort is stable + test insertion_sort { + var data = [4]i32{ 1, 0, -1, 0 }; + insertion_sort(data[0..]); + try is_sorted(data[0..]); + } + + // test: is_sorted detects unsorted + test is_sorted_detects_unsorted { + var data = [3]i32{ 1, -1, 0 }; + try not(is_sorted(data[0..])); + } + + // test: sort_compare ordering + test sort_compare_ordering { + try eq(sort_compare(TRIT_NEG, TRIT_POS), TRIT_NEG); + try eq(sort_compare(TRIT_POS, TRIT_NEG), TRIT_POS); + try eq(sort_compare(TRIT_ZERO, TRIT_ZERO), TRIT_ZERO); + } + + // invariant: sorted arrays satisfy is_sorted + invariant sorted_is_sorted { + var data = [5]i32{ -1, 0, 0, 1, 1 }; + is_sorted(data[0..]) == true; + } + + // invariant: sort_compare is antisymmetric + invariant compare_antisymmetric { + sort_compare(1, -1) == -sort_compare(-1, 1); + } + + // bench: bubble sort 27 elements + bench bubble_sort_27 { + var data : [27]i32; + var i : usize = 0; + while (i < 27) { + data[i] = (27 - i) % 3 - 1; + i = i + 1; + } + bubble_sort(data[0..]); + } +} diff --git a/apps/website/public/t27/specs/isa/ternary_tree.t27 b/apps/website/public/t27/specs/isa/ternary_tree.t27 new file mode 100644 index 0000000000..58137568b9 --- /dev/null +++ b/apps/website/public/t27/specs/isa/ternary_tree.t27 @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/isa/ternary_tree.t27 +// Ternary Tree Operations Specification +// Ring 084 - Tree algorithms on ternary-valued nodes +// 01 + 1/23 = 3 | TRINITY + +module TernaryTree { + use base::types; + + const MAX_NODES : usize = 27; + const TRIT_NEG : i32 = -1; + const TRIT_ZERO : i32 = 0; + const TRIT_POS : i32 = 1; + const NULL_IDX : usize = 255; + + // Binary tree stored in arrays: left_child, right_child, parent, value + // node 0 is root + + // tree_init: Initialize tree with all null children + fn tree_init(left: []usize, right: []usize, parent: []usize, values: []i32) void { + var i : usize = 0; + while (i < left.len) { + left[i] = NULL_IDX; + right[i] = NULL_IDX; + parent[i] = NULL_IDX; + values[i] = TRIT_ZERO; + i = i + 1; + } + } + + // insert_left: Set left child of parent_node to child_node + fn insert_left(left: []usize, parent_arr: []usize, parent_node: usize, child: usize) void { + if (parent_node < left.len and child < left.len) { + left[parent_node] = child; + parent_arr[child] = parent_node; + } + } + + // insert_right: Set right child of parent_node to child_node + fn insert_right(right: []usize, parent_arr: []usize, parent_node: usize, child: usize) void { + if (parent_node < right.len and child < right.len) { + right[parent_node] = child; + parent_arr[child] = parent_node; + } + } + + // tree_depth: Compute max depth from root (node 0) + fn tree_depth(left: []usize, right: []usize, node: usize) usize { + if (node >= left.len or node == NULL_IDX) { return 0; } + var l_depth : usize = 0; + var r_depth : usize = 0; + if (left[node] != NULL_IDX) { + l_depth = tree_depth(left, right, left[node]); + } + if (right[node] != NULL_IDX) { + r_depth = tree_depth(left, right, right[node]); + } + if (l_depth > r_depth) { + return 1 + l_depth; + } + return 1 + r_depth; + } + + // node_count: Count non-null nodes reachable from root + fn node_count(left: []usize, right: []usize, node: usize) usize { + if (node >= left.len or node == NULL_IDX) { return 0; } + var count : usize = 1; + if (left[node] != NULL_IDX) { + count = count + node_count(left, right, left[node]); + } + if (right[node] != NULL_IDX) { + count = count + node_count(left, right, right[node]); + } + return count; + } + + // sum_values: Sum all trit values in subtree + fn sum_values(left: []usize, right: []usize, values: []i32, node: usize) i32 { + if (node >= left.len or node == NULL_IDX) { return 0; } + var s : i32 = values[node]; + if (left[node] != NULL_IDX) { + s = s + sum_values(left, right, values, left[node]); + } + if (right[node] != NULL_IDX) { + s = s + sum_values(left, right, values, right[node]); + } + return s; + } + + // is_balanced: Check if subtree height difference <= 1 at every node + fn is_balanced(left: []usize, right: []usize, node: usize) bool { + if (node >= left.len or node == NULL_IDX) { return true; } + var lh : usize = 0; + var rh : usize = 0; + if (left[node] != NULL_IDX) { + lh = tree_depth(left, right, left[node]); + } + if (right[node] != NULL_IDX) { + rh = tree_depth(left, right, right[node]); + } + var diff : usize = lh; + if (rh > lh) { diff = rh; } + // diff = max(lh, rh), check |lh - rh| <= 1 + if (lh > rh + 1 or rh > lh + 1) { return false; } + if (left[node] != NULL_IDX and not(is_balanced(left, right, left[node]))) { + return false; + } + if (right[node] != NULL_IDX and not(is_balanced(left, right, right[node]))) { + return false; + } + return true; + } + + // test: single node depth + test single_node_depth { + // Validated via invariant + } + + // test: balanced tree detection + test balanced_detection { + // Validated via invariant + } + + // invariant: node_count >= depth for any non-empty tree + invariant count_ge_depth { + // Trivially true: depth <= node_count for binary tree + true; + } + + // invariant: sum_values bounded by node_count + invariant sum_bounded { + // sum of trit values in [-1,1] => |sum| <= node_count + true; + } + + // bench: tree_depth on 27-node tree + bench depth_27 { + // Simulated at gen time + } +} diff --git a/apps/website/public/t27/specs/jit/jit.t27 b/apps/website/public/t27/specs/jit/jit.t27 new file mode 100644 index 0000000000..b02ed9cf5a --- /dev/null +++ b/apps/website/public/t27/specs/jit/jit.t27 @@ -0,0 +1,874 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/jit/jit.t27 +// Trinity JIT Compiler +// Compiles VSA operations to native machine code +// +// 01234 567891011: V = n 12 3^k 13 14^m 15 16^p 17 e^q +// phi^2 + 1/phi^2 = 3 | TRINITY +// +// JIT (Just-In-Time) compiler for VSA operations: +// - Compiles high-level VSA operations to native x86-64 machine code +// - Supports bind, bundle, dot product operations +// - Caches compiled functions by dimension +// - Uses mmap for executable memory allocation +// +// Target Architecture: x86-64 (AMD64) + +module jit; + +// ============================================================================ +// Imports +// ============================================================================ + +use tritype-base::Trit; +use numeric::gf16; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Page size for memory allocation (typical 4KB) +pub const PAGE_SIZE : usize = 4096; + +/// Maximum code buffer size (64KB) +pub const MAX_CODE_SIZE : usize = 65536; + +/// Default VSA dimension +pub const DEFAULT_DIMENSION : usize = 1024; + +/// Maximum supported dimension for JIT operations +pub const MAX_DIMENSION : usize = 65536; + +// ============================================================================ +// Types +// ============================================================================ + +/// JIT-compiled function type for VSA operations +/// Takes two vector pointers and returns result in first pointer +pub const JitVsaFn = *const fn (*anyopaque, *anyopaque) void; + +/// JIT-compiled similarity function +/// Takes two vector pointers and returns f64 similarity +pub const JitSimilarityFn = *const fn (*anyopaque, *anyopaque) f64; + +/// JIT compilation result +pub const JitResult = struct { + func : JitVsaFn, + code_size : usize, +}; + +/// JIT Compiler for VSA operations +pub const JitCompiler = struct { + /// Code buffer for generated machine code + code : [MAX_CODE_SIZE]u8, + /// Current position in code buffer + code_len : usize, + /// Allocator reference + allocator : *anyopaque, + + const Self = @This(); + + /// Initialize JIT compiler + pub fn init(allocator: *anyopaque) Self { + return Self{ + .code = [_]u8{0} ** MAX_CODE_SIZE, + .code_len = 0, + .allocator = allocator, + }; + } + + /// Reset code buffer for new compilation + pub fn reset(self: *Self) void { + self.code_len = 0; + } + + // ======================================================================== + // X86-64 Code Generation Helpers + // ======================================================================== + + /// Emit single byte + fn emit(self: *Self, b: u8) bool { + if (self.code_len >= MAX_CODE_SIZE) { + return false; + } + self.code[self.code_len] = b; + self.code_len += 1; + return true; + } + + /// Emit multiple bytes + fn emit_slice(self: *Self, bytes: []const u8) bool { + if (self.code_len + bytes.len > MAX_CODE_SIZE) { + return false; + } + var i : usize = 0; + while (i < bytes.len) { + self.code[self.code_len] = bytes[i]; + self.code_len += 1; + i = i + 1; + } + return true; + } + + /// Emit 32-bit immediate (little-endian) + fn emit_imm32(self: *Self, imm: i32) bool { + const bytes = @as([4]u8, @bitCast(@as(i32, imm))); + return self.emit_slice(&bytes); + } + + /// Emit 64-bit immediate (little-endian) + fn emit_imm64(self: *Self, imm: i64) bool { + const bytes = @as([8]u8, @bitCast(@as(i64, imm))); + return self.emit_slice(&bytes); + } + + // ======================================================================== + // X86-64 Instruction Encoding + // ======================================================================== + + /// push rbp + fn push_rbp(self: *Self) bool { + return self.emit(0x55); + } + + /// pop rbp + fn pop_rbp(self: *Self) bool { + return self.emit(0x5D); + } + + /// mov rbp, rsp + fn mov_rbp_rsp(self: *Self) bool { + return self.emit_slice(&[_]u8{ 0x48, 0x89, 0xE5 }); + } + + /// mov rsp, rbp + fn mov_rsp_rbp(self: *Self) bool { + return self.emit_slice(&[_]u8{ 0x48, 0x89, 0xEC }); + } + + /// ret + fn ret(self: *Self) bool { + return self.emit(0xC3); + } + + /// mov rax, imm64 + fn mov_rax_imm64(self: *Self, imm: i64) bool { + if (!self.emit_slice(&[_]u8{ 0x48, 0xB8 })) { + return false; + } + return self.emit_imm64(imm); + } + + /// call rax + fn call_rax(self: *Self) bool { + return self.emit_slice(&[_]u8{ 0xFF, 0xD0 }); + } + + /// xor eax, eax (zero rax) + fn xor_eax_eax(self: *Self) bool { + return self.emit_slice(&[_]u8{ 0x31, 0xC0 }); + } + + /// inc rbx + fn inc_rbx(self: *Self) bool { + return self.emit_slice(&[_]u8{ 0x48, 0xFF, 0xC3 }); + } + + // ======================================================================== + // VSA Operation Compilation + // ======================================================================== + + /// Compile bind operation (element-wise multiply with normalization) + /// Generates native code for: result[i] = bind_trit(a[i], b[i]) + /// bind_trit(x, y) = x * y (simplified - assumes values in {-1, 0, 1}) + pub fn compile_bind(self: *Self, dimension: usize) bool { + self.reset(); + + // Function prologue + if (!self.push_rbp()) return false; + if (!self.mov_rbp_rsp()) return false; + + // Save callee-saved registers: rbx, r12, r13 + if (!self.emit(0x53)) return false; // push rbx + if (!self.emit_slice(&[_]u8{ 0x41, 0x54 })) return false; // push r12 + if (!self.emit_slice(&[_]u8{ 0x41, 0x55 })) return false; // push r13 + + // r12 = a pointer (rdi -> r12) + if (!self.emit_slice(&[_]u8{ 0x49, 0x89, 0xFC })) return false; // mov r12, rdi + + // r13 = b pointer (rsi -> r13) + if (!self.emit_slice(&[_]u8{ 0x49, 0x89, 0xF5 })) return false; // mov r13, rsi + + // rbx = loop counter (0) + if (!self.xor_eax_eax()) return false; + if (!self.emit_slice(&[_]u8{ 0x48, 0x89, 0xC3 })) return false; // mov rbx, rax + + // Loop start + const loop_start = self.code_len; + + // Compare rbx with dimension + if (!self.emit_slice(&[_]u8{ 0x48, 0x81, 0xFB })) return false; // cmp rbx, imm32 + if (!self.emit_imm32(@intCast(dimension))) return false; + + // jge loop_end (jump if rbx >= dimension) + if (!self.emit_slice(&[_]u8{ 0x0F, 0x8D })) return false; // jge rel32 + const jge_offset = self.code_len; + if (!self.emit_imm32(0)) return false; // placeholder + + // Load a[rbx] into al + if (!self.emit_slice(&[_]u8{ 0x41, 0x8A, 0x04, 0x1C })) return false; // mov al, [r12 + rbx] + + // Load b[rbx] into cl + if (!self.emit_slice(&[_]u8{ 0x41, 0x8A, 0x4C, 0x1D, 0x00 })) return false; // mov cl, [r13 + rbx] + + // imul al, cl (signed multiply) + if (!self.emit_slice(&[_]u8{ 0xF6, 0xE9 })) return false; // imul cl + + // Store result back to a[rbx] + if (!self.emit_slice(&[_]u8{ 0x41, 0x88, 0x04, 0x1C })) return false; // mov [r12 + rbx], al + + // Increment counter + if (!self.inc_rbx()) return false; + + // Jump back to loop start + if (!self.emit(0xE9)) return false; // jmp rel32 + const loop_back_offset = @as(i32, @intCast(loop_start)) - @as(i32, @intCast(self.code_len + 4)); + if (!self.emit_imm32(loop_back_offset)) return false; + + // Patch jge offset + const loop_end = self.code_len; + const jge_rel = @as(i32, @intCast(loop_end)) - @as(i32, @intCast(jge_offset + 4)); + + // Patch the 4 bytes at jge_offset with jge_rel + const jge_bytes = @as([4]u8, @bitCast(jge_rel)); + var i : usize = 0; + while (i < 4) { + self.code[jge_offset + i] = jge_bytes[i]; + i = i + 1; + } + + // Restore callee-saved registers + if (!self.emit_slice(&[_]u8{ 0x41, 0x5D })) return false; // pop r13 + if (!self.emit_slice(&[_]u8{ 0x41, 0x5C })) return false; // pop r12 + if (!self.emit(0x5B)) return false; // pop rbx + + // Function epilogue + if (!self.mov_rsp_rbp()) return false; + if (!self.pop_rbp()) return false; + if (!self.ret()) return false; + + return true; + } + + /// Compile bundle operation (element-wise sum with threshold) + /// Generates native code for: result[i] = bundle_trit(a[i], b[i]) + /// bundle_trit(x, y) = threshold(x + y, -1..1) + pub fn compile_bundle(self: *Self, dimension: usize) bool { + self.reset(); + + // Function prologue + if (!self.push_rbp()) return false; + if (!self.mov_rbp_rsp()) return false; + + // Save callee-saved registers + if (!self.emit(0x53)) return false; + if (!self.emit_slice(&[_]u8{ 0x41, 0x54 })) return false; + if (!self.emit_slice(&[_]u8{ 0x41, 0x55 })) return false; + + // r12 = a pointer, r13 = b pointer + if (!self.emit_slice(&[_]u8{ 0x49, 0x89, 0xFC })) return false; + if (!self.emit_slice(&[_]u8{ 0x49, 0x89, 0xF5 })) return false; + + // rbx = loop counter + if (!self.xor_eax_eax()) return false; + if (!self.emit_slice(&[_]u8{ 0x48, 0x89, 0xC3 })) return false; + + const loop_start = self.code_len; + + // Compare rbx with dimension + if (!self.emit_slice(&[_]u8{ 0x48, 0x81, 0xFB })) return false; + if (!self.emit_imm32(@intCast(dimension))) return false; + + // jge loop_end + if (!self.emit_slice(&[_]u8{ 0x0F, 0x8D })) return false; + const jge_offset = self.code_len; + if (!self.emit_imm32(0)) return false; + + // Load a[rbx] into eax (sign-extended) + if (!self.emit_slice(&[_]u8{ 0x41, 0x0F, 0xBE, 0x04, 0x1C })) return false; // movsx eax, byte [r12 + rbx] + + // Load b[rbx] into ecx (sign-extended) + if (!self.emit_slice(&[_]u8{ 0x41, 0x0F, 0xBE, 0x4C, 0x1D, 0x00 })) return false; // movsx ecx, byte [r13 + rbx] + + // Add eax, ecx + if (!self.emit_slice(&[_]u8{ 0x01, 0xC8 })) return false; // add eax, ecx + + // Threshold: if sum > 0 -> 1, if sum < 0 -> -1, else 0 + if (!self.emit_slice(&[_]u8{ 0x83, 0xF8, 0x00 })) return false; // cmp eax, 0 + + // setg dl (set dl = 1 if eax > 0) + if (!self.emit_slice(&[_]u8{ 0x0F, 0x9F, 0xC2 })) return false; // setg dl + + // setl al (set al = 1 if eax < 0) + if (!self.emit_slice(&[_]u8{ 0x0F, 0x9C, 0xC0 })) return false; // setl al + + // Result = dl - al (1 if positive, -1 if negative, 0 if zero) + if (!self.emit_slice(&[_]u8{ 0x28, 0xC2 })) return false; // sub dl, al + + // Store result back to a[rbx] + if (!self.emit_slice(&[_]u8{ 0x41, 0x88, 0x14, 0x1C })) return false; // mov [r12 + rbx], dl + + // Increment counter + if (!self.inc_rbx()) return false; + + // Jump back to loop start + if (!self.emit(0xE9)) return false; + const loop_back_offset = @as(i32, @intCast(loop_start)) - @as(i32, @intCast(self.code_len + 4)); + if (!self.emit_imm32(loop_back_offset)) return false; + + // Patch jge offset + const loop_end = self.code_len; + const jge_rel = @as(i32, @intCast(loop_end)) - @as(i32, @intCast(jge_offset + 4)); + + const jge_bytes = @as([4]u8, @bitCast(jge_rel)); + var i : usize = 0; + while (i < 4) { + self.code[jge_offset + i] = jge_bytes[i]; + i = i + 1; + } + + // Restore callee-saved registers + if (!self.emit_slice(&[_]u8{ 0x41, 0x5D })) return false; + if (!self.emit_slice(&[_]u8{ 0x41, 0x5C })) return false; + if (!self.emit(0x5B)) return false; + + // Function epilogue + if (!self.mov_rsp_rbp()) return false; + if (!self.pop_rbp()) return false; + if (!self.ret()) return false; + + return true; + } + + /// Compile dot product operation + /// Generates native code for: return Sigma(a[i] * b[i]) + pub fn compile_dot_product(self: *Self, dimension: usize) bool { + self.reset(); + + // Function prologue + if (!self.push_rbp()) return false; + if (!self.mov_rbp_rsp()) return false; + + // Save callee-saved registers + if (!self.emit(0x53)) return false; + if (!self.emit_slice(&[_]u8{ 0x41, 0x54 })) return false; + if (!self.emit_slice(&[_]u8{ 0x41, 0x55 })) return false; + if (!self.emit_slice(&[_]u8{ 0x41, 0x56 })) return false; + + // r12 = a pointer, r13 = b pointer + if (!self.emit_slice(&[_]u8{ 0x49, 0x89, 0xFC })) return false; + if (!self.emit_slice(&[_]u8{ 0x49, 0x89, 0xF5 })) return false; + + // r14 = accumulator (0) + if (!self.emit_slice(&[_]u8{ 0x4D, 0x31, 0xF6 })) return false; // xor r14, r14 + + // rbx = loop counter + if (!self.xor_eax_eax()) return false; + if (!self.emit_slice(&[_]u8{ 0x48, 0x89, 0xC3 })) return false; + + const loop_start = self.code_len; + + // Compare rbx with dimension + if (!self.emit_slice(&[_]u8{ 0x48, 0x81, 0xFB })) return false; + if (!self.emit_imm32(@intCast(dimension))) return false; + + // jge loop_end + if (!self.emit_slice(&[_]u8{ 0x0F, 0x8D })) return false; + const jge_offset = self.code_len; + if (!self.emit_imm32(0)) return false; + + // Load a[rbx] into eax (sign-extended) + if (!self.emit_slice(&[_]u8{ 0x41, 0x0F, 0xBE, 0x04, 0x1C })) return false; + + // Load b[rbx] into ecx (sign-extended) + if (!self.emit_slice(&[_]u8{ 0x41, 0x0F, 0xBE, 0x4C, 0x1D, 0x00 })) return false; + + // imul eax, ecx + if (!self.emit_slice(&[_]u8{ 0x0F, 0xAF, 0xC1 })) return false; // imul eax, ecx + + // Sign-extend eax to rax + if (!self.emit_slice(&[_]u8{ 0x48, 0x98 })) return false; // cdqe + + // Add to accumulator + if (!self.emit_slice(&[_]u8{ 0x49, 0x01, 0xC6 })) return false; // add r14, rax + + // Increment counter + if (!self.inc_rbx()) return false; + + // Jump back to loop start + if (!self.emit(0xE9)) return false; + const loop_back_offset = @as(i32, @intCast(loop_start)) - @as(i32, @intCast(self.code_len + 4)); + if (!self.emit_imm32(loop_back_offset)) return false; + + // Patch jge offset + const loop_end = self.code_len; + const jge_rel = @as(i32, @intCast(loop_end)) - @as(i32, @intCast(jge_offset + 4)); + + const jge_bytes = @as([4]u8, @bitCast(jge_rel)); + var i : usize = 0; + while (i < 4) { + self.code[jge_offset + i] = jge_bytes[i]; + i = i + 1; + } + + // Move result to rax + if (!self.emit_slice(&[_]u8{ 0x4C, 0x89, 0xF0 })) return false; // mov rax, r14 + + // Restore callee-saved registers + if (!self.emit_slice(&[_]u8{ 0x41, 0x5E })) return false; + if (!self.emit_slice(&[_]u8{ 0x41, 0x5D })) return false; + if (!self.emit_slice(&[_]u8{ 0x41, 0x5C })) return false; + if (!self.emit(0x5B)) return false; + + // Function epilogue + if (!self.mov_rsp_rbp()) return false; + if (!self.pop_rbp()) return false; + if (!self.ret()) return false; + + return true; + } + + // ======================================================================== + // Execution + // ======================================================================== + + /// Get current code size + pub fn code_size(self: *const Self) usize { + return self.code_len; + } + + /// Get pointer to code buffer + pub fn code_ptr(self: *const Self) [*]const u8 { + return self.code[0..self.code_len]; + } +}; + +// ============================================================================ +// JIT Cache +// ============================================================================ + +/// Cache for JIT-compiled functions +/// Avoids recompilation for same dimensions +pub const JitCache = struct { + /// Cached bind functions by dimension + bind_cache : []*const fn (*anyopaque, *anyopaque) void, + /// Cached bundle functions by dimension + bundle_cache : []*const fn (*anyopaque, *anyopaque) void, + /// Number of cached functions + cache_size : usize, + /// Maximum cache size + max_cache_size : usize, + /// Compiler reference + compiler : JitCompiler, + /// Allocator + allocator : *anyopaque, + + const Self = @This(); + + /// Initialize JIT cache + pub fn init(allocator: *anyopaque, max_cache_size: usize) Self { + return Self{ + .bind_cache = [_]*const fn (*anyopaque, *anyopaque) void{} ** max_cache_size, + .bundle_cache = [_]*const fn (*anyopaque, *anyopaque) void{} ** max_cache_size, + .cache_size = 0, + .max_cache_size = max_cache_size, + .compiler = JitCompiler.init(allocator), + .allocator = allocator, + }; + } + + /// Reset cache (clear all cached functions) + pub fn reset(self: *Self) void { + self.cache_size = 0; + } + + /// Get or compile bind function for dimension + /// Returns cached function if available, compiles new otherwise + pub fn get_bind(self: *Self, dimension: usize) ?*const fn (*anyopaque, *anyopaque) void { + // Check cache for existing function + var i : usize = 0; + while (i < self.cache_size) { + // In real implementation, would store dimension alongside function + // For now, always compile + i = i + 1; + } + + // Compile new function + if (!self.compiler.compile_bind(dimension)) { + return null; + } + + // In real implementation, would allocate executable memory and return function pointer + // For spec, return placeholder + return null; + } + + /// Get or compile bundle function for dimension + pub fn get_bundle(self: *Self, dimension: usize) ?*const fn (*anyopaque, *anyopaque) void { + // Similar to get_bind but for bundle operation + if (!self.compiler.compile_bundle(dimension)) { + return null; + } + + return null; + } +}; + +// ============================================================================ +// High-Level JIT API +// ============================================================================ + +/// JIT-accelerated bind operation +/// Compiles (if needed) and executes bind on two vectors +pub fn jit_bind(cache: *JitCache, a: *anyopaque, b: *anyopaque, dimension: usize) bool { + const func = cache.get_bind(dimension); + if (func == null) { + return false; + } + + func.?(@(a), @(b)); + return true; +} + +/// JIT-accelerated bundle operation +/// Compiles (if needed) and executes bundle on two vectors +pub fn jit_bundle(cache: *JitCache, a: *anyopaque, b: *anyopaque, dimension: usize) bool { + const func = cache.get_bundle(dimension); + if (func == null) { + return false; + } + + func.?(@(a), @(b)); + return true; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "jit_compiler_init" { + // Verify JIT compiler initializes correctly + const compiler = JitCompiler.init(null); + try std.testing.expect(compiler.code_size() == 0); +} + +test "jit_compiler_reset" { + // Verify reset clears code buffer + var compiler = JitCompiler.init(null); + _ = compiler.emit(0x90); // nop + try std.testing.expect(compiler.code_size() == 1); + compiler.reset(); + try std.testing.expect(compiler.code_size() == 0); +} + +test "jit_compiler_emit_single_byte" { + // Verify single byte emission + var compiler = JitCompiler.init(null); + const result = compiler.emit(0x90); // nop + try std.testing.expect(result == true); + try std.testing.expect(compiler.code_size() == 1); + try std.testing.expect(compiler.code[0] == 0x90); +} + +test "jit_compiler_emit_slice" { + // Verify slice emission + var compiler = JitCompiler.init(null); + const bytes = [_]u8{ 0x90, 0x90, 0x90 }; // 3 nops + const result = compiler.emit_slice(&bytes); + try std.testing.expect(result == true); + try std.testing.expect(compiler.code_size() == 3); +} + +test "jit_compiler_emit_imm32" { + // Verify 32-bit immediate emission + var compiler = JitCompiler.init(null); + const result = compiler.emit_imm32(0x12345678); + try std.testing.expect(result == true); + try std.testing.expect(compiler.code_size() == 4); +} + +test "jit_compiler_emit_imm64" { + // Verify 64-bit immediate emission + var compiler = JitCompiler.init(null); + const result = compiler.emit_imm64(0x123456789ABCDEF0); + try std.testing.expect(result == true); + try std.testing.expect(compiler.code_size() == 8); +} + +test "jit_compiler_prologue_epilogue" { + // Verify function prologue and epilogue generation + var compiler = JitCompiler.init(null); + + // Prologue + try std.testing.expect(compiler.push_rbp() == true); + try std.testing.expect(compiler.mov_rbp_rsp() == true); + + // Epilogue + try std.testing.expect(compiler.mov_rsp_rbp() == true); + try std.testing.expect(compiler.pop_rbp() == true); + try std.testing.expect(compiler.ret() == true); + + // Should have generated some code + try std.testing.expect(compiler.code_size() > 0); +} + +test "jit_compiler_compile_bind" { + // Verify bind compilation succeeds + var compiler = JitCompiler.init(null); + const result = compiler.compile_bind(16); + try std.testing.expect(result == true); + try std.testing.expect(compiler.code_size() > 0); +} + +test "jit_compiler_compile_bundle" { + // Verify bundle compilation succeeds + var compiler = JitCompiler.init(null); + const result = compiler.compile_bundle(16); + try std.testing.expect(result == true); + try std.testing.expect(compiler.code_size() > 0); +} + +test "jit_compiler_compile_dot_product" { + // Verify dot product compilation succeeds + var compiler = JitCompiler.init(null); + const result = compiler.compile_dot_product(16); + try std.testing.expect(result == true); + try std.testing.expect(compiler.code_size() > 0); +} + +test "jit_compiler_code_size_increases_with_dimension" { + // Verify code size increases with dimension + var compiler1 = JitCompiler.init(null); + var compiler2 = JitCompiler.init(null); + + _ = compiler1.compile_bind(100); + _ = compiler2.compile_bind(1000); + + try std.testing.expect(compiler2.code_size() > compiler1.code_size()); +} + +test "jit_cache_init" { + // Verify JIT cache initializes correctly + const cache = JitCache.init(null, 16); + try std.testing.expect(cache.cache_size == 0); + try std.testing.expect(cache.max_cache_size == 16); +} + +test "jit_cache_reset" { + // Verify cache reset works + var cache = JitCache.init(null, 16); + cache.cache_size = 5; + cache.reset(); + try std.testing.expect(cache.cache_size == 0); +} + +test "jit_compiler_xor_eax_eax" { + // Verify xor eax, eax zeroes register + var compiler = JitCompiler.init(null); + const result = compiler.xor_eax_eax(); + try std.testing.expect(result == true); + // XOR instruction should be 0x31 0xC0 + try std.testing.expect(compiler.code[compiler.code_size - 2] == 0x31); + try std.testing.expect(compiler.code[compiler.code_size - 1] == 0xC0); +} + +test "jit_compiler_inc_rbx" { + // Verify increment rbx instruction + var compiler = JitCompiler.init(null); + const result = compiler.inc_rbx(); + try std.testing.expect(result == true); + // INC RBX should be 0x48 0xFF 0xC3 + try std.testing.expect(compiler.code[compiler.code_size - 3] == 0x48); + try std.testing.expect(compiler.code[compiler.code_size - 2] == 0xFF); + try std.testing.expect(compiler.code[compiler.code_size - 1] == 0xC3); +} + +test "jit_compiler_call_rax" { + // Verify call rax instruction + var compiler = JitCompiler.init(null); + const result = compiler.call_rax(); + try std.testing.expect(result == true); + // CALL RAX should be 0xFF 0xD0 + try std.testing.expect(compiler.code[compiler.code_size - 2] == 0xFF); + try std.testing.expect(compiler.code[compiler.code_size - 1] == 0xD0); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant jit_compiler_code_size_within_bounds { + // Code size never exceeds MAX_CODE_SIZE + const compiler = JitCompiler.init(null); + @compileAssert(compiler.code_size() <= MAX_CODE_SIZE); +} + +invariant jit_compiler_code_size_non_negative { + // Code size is always non-negative + const compiler = JitCompiler.init(null); + @compileAssert(compiler.code_size() >= 0); +} + +invariant jit_cache_size_within_bounds { + // Cache size never exceeds max_cache_size + const cache = JitCache.init(null, 16); + @compileAssert(cache.cache_size <= cache.max_cache_size); +} + +invariant jit_prologue_matches_epilogue { + // Prologue push count matches epilogue pop count + // push_rbp + mov_rbp_rsp = pop_rbp + mov_rsp_rbp + var compiler = JitCompiler.init(null); + _ = compiler.push_rbp(); + _ = compiler.mov_rbp_rsp(); + _ = compiler.mov_rsp_rbp(); + _ = compiler.pop_rbp(); + // Stack should be balanced + @compileAssert(true); // In full implementation, would verify stack balance +} + +invariant jit_bind_loop_has_exit { + // Bind compilation generates valid loop with exit + var compiler = JitCompiler.init(null); + _ = compiler.compile_bind(10); + // Code should contain conditional jump (jge instruction: 0x0F 0x8D) + @compileAssert(true); // In full implementation, would verify loop exit +} + +invariant jit_bundle_loop_has_exit { + // Bundle compilation generates valid loop with exit + var compiler = JitCompiler.init(null); + _ = compiler.compile_bundle(10); + @compileAssert(true); // In full implementation, would verify loop exit +} + +invariant jit_dot_product_loop_has_exit { + // Dot product compilation generates valid loop with exit + var compiler = JitCompiler.init(null); + _ = compiler.compile_dot_product(10); + @compileAssert(true); // In full implementation, would verify loop exit +} + +invariant jit_compilation_preserves_registers { + // Compilation preserves callee-saved registers + // Any pushed register must be popped before ret + @compileAssert(true); // In full implementation, would verify register preservation +} + +invariant jit_compiler_reset_clears_buffer { + // Reset clears all code + var compiler = JitCompiler.init(null); + _ = compiler.emit(0x90); + _ = compiler.emit(0x90); + compiler.reset(); + @compileAssert(compiler.code_size() == 0); +} + +invariant jit_emit_slice_increases_size_correctly { + // Emit slice increases code size by slice length + var compiler = JitCompiler.init(null); + const bytes = [_]u8{ 0x90, 0x90, 0x90 }; + const before = compiler.code_size(); + _ = compiler.emit_slice(&bytes); + const after = compiler.code_size(); + @compileAssert(after - before == bytes.len); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "jit_compiler_init_latency" { + // Measure: cycles for JIT compiler initialization + // Target: < 1000 cycles + @setEvalBranchQuota(10000); + for (0..1000) |_| { + var compiler = JitCompiler.init(null); + _ = compiler; + } +} + +bench "jit_compiler_emit_byte_latency" { + // Measure: cycles for single byte emission + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var compiler = JitCompiler.init(null); + for (0..10000) |_| { + _ = compiler.emit(0x90); + } +} + +bench "jit_compiler_compile_bind_latency" { + // Measure: cycles for bind compilation (dim=256) + // Target: < 10000 cycles + @setEvalBranchQuota(10000); + var compiler = JitCompiler.init(null); + for (0..100) |_| { + compiler.reset(); + _ = compiler.compile_bind(256); + } +} + +bench "jit_compiler_compile_bundle_latency" { + // Measure: cycles for bundle compilation (dim=256) + // Target: < 10000 cycles + @setEvalBranchQuota(10000); + var compiler = JitCompiler.init(null); + for (0..100) |_| { + compiler.reset(); + _ = compiler.compile_bundle(256); + } +} + +bench "jit_compiler_compile_dot_product_latency" { + // Measure: cycles for dot product compilation (dim=256) + // Target: < 15000 cycles + @setEvalBranchQuota(10000); + var compiler = JitCompiler.init(null); + for (0..100) |_| { + compiler.reset(); + _ = compiler.compile_dot_product(256); + } +} + +bench "jit_compiler_code_size_latency" { + // Measure: cycles for code_size() call + // Target: < 50 cycles (should be O(1)) + @setEvalBranchQuota(10000); + var compiler = JitCompiler.init(null); + var size : usize = 0; + for (0..10000) |_| { + size = compiler.code_size(); + } +} + +bench "jit_cache_init_latency" { + // Measure: cycles for cache initialization + // Target: < 2000 cycles + @setEvalBranchQuota(10000); + for (0..1000) |_| { + var cache = JitCache.init(null, 16); + _ = cache; + } +} + +bench "jit_cache_reset_latency" { + // Measure: cycles for cache reset + // Target: < 500 cycles + @setEvalBranchQuota(10000); + var cache = JitCache.init(null, 16); + for (0..1000) |_| { + cache.cache_size = 16; + cache.reset(); + } +} diff --git a/apps/website/public/t27/specs/lsp/client.t27 b/apps/website/public/t27/specs/lsp/client.t27 new file mode 100644 index 0000000000..768c163a22 --- /dev/null +++ b/apps/website/public/t27/specs/lsp/client.t27 @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: Apache-2.0 +// lsp/client.t27 — LSP Client Configuration and Capabilities +// Client-side protocol, capabilities, and configuration management +// φ² + 1/φ² = 3 | TRINITY + +module lsp-client; + +// ============================================================================ +// Imports +// ============================================================================ + +use lsp-schema::Position; +use lsp-schema::DiagnosticSeverity; +use lsp-schema::TextDocumentSyncKind; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default client name for t27 LSP +pub const CLIENT_NAME : [3]u8 = "t27"; + +/// Default client version +pub const CLIENT_VERSION : [3]u8 = "1.0"; + +/// Maximum buffer size for client messages +pub const MAX_MESSAGE_SIZE : usize = 10485760; + +/// Default timeout in milliseconds for requests +pub const REQUEST_TIMEOUT_MS : usize = 30000; + +/// Default maximum number of concurrent requests +pub const MAX_CONCURRENT_REQUESTS : usize = 10; + +// ============================================================================ +// Types +// ============================================================================ + +/// Client capabilities +pub const ClientCapabilities = struct { + text_document_sync : TextDocumentSyncCapability, + completion : CompletionCapability, + hover : bool, + signature_help : bool, + diagnostics : DiagnosticCapability, + definition : bool, + type_definition : bool, + implementation : bool, + references : bool, + document_symbol : bool, + workspace_symbol : bool, + code_action : bool, + code_lens : bool, + formatting : bool, + document_highlight : bool, + rename : bool, +}; + +/// Text document sync capability +pub const TextDocumentSyncCapability = struct { + dynamic_registration : bool, + will_save : bool, + will_save_wait_until : bool, +}; + +/// Completion capability +pub const CompletionCapability = struct { + dynamic_registration : bool, + completion_item : CompletionItemCapability, + completion_item_kind : CompletionItemKindCapability, + context_support : bool, +}; + +/// Completion item capability +pub const CompletionItemCapability = struct { + snippet_support : bool, + commit_characters_support : bool, + documentation_format : []u8, + deprecated_support : bool, + preselect_support : bool, + tag_support : CompletionItemTagCapability, + insert_replace_support : bool, + resolve_support : bool, + insert_text_mode_support : InsertTextModeCapability, +}; + +/// Completion item kind capability +pub const CompletionItemKindCapability = struct { + value_set : []u8, +}; + +/// Completion item tag capability +pub const CompletionItemTagCapability = struct { + value_set : []u8, +}; + +/// Insert text mode capability +pub const InsertTextModeCapability = struct { + value_set : []u8, +}; + +/// Diagnostic capability +pub const DiagnosticCapability = struct { + dynamic_registration : bool, + related_document_support : bool, + tag_support : DiagnosticTagCapability, +}; + +/// Diagnostic tag capability +pub const DiagnosticTagCapability = struct { + value_set : []u8, +}; + +/// Client initialization options +pub const InitializationOptions = struct { + process_id : usize, + root_path : []u8, + root_uri : []u8, + initialization_options : []u8, + capabilities : ClientCapabilities, + trace : TraceValue, + workspace_folders : []WorkspaceFolder, +}; + +/// Trace value for debugging +pub const TraceValue = enum(u8) { + off = 0, + messages = 1, + verbose = 2, +}; + +/// Workspace folder +pub const WorkspaceFolder = struct { + uri : []u8, + name : []u8, +}; + +/// Text document synchronization options +pub const TextDocumentSyncOptions = struct { + open_close : bool, + change : TextDocumentSyncKind, + will_save : bool, + will_save_wait_until : bool, + save : SaveOptions, +}; + +/// Save options +pub const SaveOptions = struct { + include_text : bool, +}; + +/// Configuration item +pub const ConfigurationItem = struct { + scope_uri : []u8, + section : []u8, +}; + +/// Client configuration +pub const ClientConfiguration = struct { + max_completion_items : usize, + trigger_suggest_on_trigger_characters : bool, + accept_snippet_on_enter : bool, +}; + +/// Work done progress +pub const WorkDoneProgress = struct { + token : []u8, + value : ProgressValue, +}; + +/// Progress value +pub const ProgressValue = enum(u8) { + begin = 0, + report = 1, + end = 2, +}; + +/// Work done progress begin +pub const WorkDoneProgressBegin = struct { + title : []u8, + cancellable : bool, + message : []u8, + percentage : usize, +}; + +/// Work done progress report +pub const WorkDoneProgressReport = struct { + cancellable : bool, + message : []u8, + percentage : usize, +}; + +/// Work done progress end +pub const WorkDoneProgressEnd = struct { + message : []u8, +}; + +/// Log trace notification +pub const LogTraceParams = struct { + message : []u8, + verbose : []u8, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create default client capabilities +pub fn client_capabilities_default() ClientCapabilities { + return ClientCapabilities{ + .text_document_sync = TextDocumentSyncCapability{ + .dynamic_registration = false, + .will_save = false, + .will_save_wait_until = false, + }, + .completion = CompletionCapability{ + .dynamic_registration = false, + .completion_item = CompletionItemCapability{ + .snippet_support = true, + .commit_characters_support = true, + .documentation_format = "markdown", + .deprecated_support = false, + .preselect_support = false, + .tag_support = CompletionItemTagCapability{ .value_set = "" }, + .insert_replace_support = false, + .resolve_support = false, + .insert_text_mode_support = InsertTextModeCapability{ .value_set = "" }, + }, + .completion_item_kind = CompletionItemKindCapability{ .value_set = "" }, + .context_support = true, + }, + .hover = true, + .signature_help = true, + .diagnostics = DiagnosticCapability{ + .dynamic_registration = false, + .related_document_support = false, + .tag_support = DiagnosticTagCapability{ .value_set = "" }, + }, + .definition = true, + .type_definition = true, + .implementation = true, + .references = true, + .document_symbol = true, + .workspace_symbol = true, + .code_action = true, + .code_lens = true, + .formatting = true, + .document_highlight = true, + .rename = true, + }; +} + +/// Create initialization options +pub fn init_options_create(root_path: []u8) InitializationOptions { + return InitializationOptions{ + .process_id = 0, + .root_path = root_path, + .root_uri = "", + .initialization_options = "", + .capabilities = client_capabilities_default(), + .trace = .off, + .workspace_folders = &[_]WorkspaceFolder{}, + }; +} + +/// Create text document sync options +pub fn text_document_sync_options_create(open_close: bool, sync_kind: TextDocumentSyncKind) TextDocumentSyncOptions { + return TextDocumentSyncOptions{ + .open_close = open_close, + .change = sync_kind, + .will_save = false, + .will_save_wait_until = false, + .save = SaveOptions{ .include_text = false }, + }; +} + +/// Create work done progress begin +pub fn work_done_progress_begin_create(title: []u8) WorkDoneProgressBegin { + return WorkDoneProgressBegin{ + .title = title, + .cancellable = false, + .message = "", + .percentage = 0, + }; +} + +/// Create work done progress report +pub fn work_done_progress_report_create(message: []u8, percentage: usize) WorkDoneProgressReport { + return WorkDoneProgressReport{ + .cancellable = false, + .message = message, + .percentage = percentage, + }; +} + +/// Create work done progress end +pub fn work_done_progress_end_create(message: []u8) WorkDoneProgressEnd { + return WorkDoneProgressEnd{ + .message = message, + }; +} + +/// Create log trace params +pub fn log_trace_create(message: []u8) LogTraceParams { + return LogTraceParams{ + .message = message, + .verbose = "", + }; +} + +/// Check if trace is enabled +pub fn trace_is_enabled(trace: TraceValue) bool { + return trace != .off; +} + +/// Get trace level string +pub fn trace_to_string(trace: TraceValue) []u8 { + return switch (trace) { + .off => "off", + .messages => "messages", + .verbose => "verbose", + }; +} + +/// Check if completion item has snippet support +pub fn completion_has_snippet_support(cap: CompletionCapability) bool { + return cap.completion_item.snippet_support; +} + +/// Check if client supports workspace folders +pub fn client_has_workspace_folders(init: InitializationOptions) bool { + return init.workspace_folders.len > 0; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "lsp_client_capabilities_default" { + const caps = client_capabilities_default(); + try std.testing.expect(caps.hover == true); + try std.testing.expect(caps.diagnostics.dynamic_registration == false); +} + +test "lsp_init_options_create" { + const init = init_options_create("/path/to/root"); + try std.testing.expectEqual(@as(usize, init.root_path.len), @as(usize, 12)); +} + +test "lsp_text_document_sync_options_create" { + const opts = text_document_sync_options_create(true, .full); + try std.testing.expect(opts.open_close == true); + try std.testing.expect(opts.change == .full); +} + +test "lsp_work_done_progress_begin" { + const progress = work_done_progress_begin_create("loading"); + try std.testing.expectEqual(@as(usize, progress.title.len), @as(usize, 7)); +} + +test "lsp_work_done_progress_report" { + const report = work_done_progress_report_create("processing", 50); + try std.testing.expectEqual(@as(usize, report.message.len), @as(usize, 10)); + try std.testing.expect(report.percentage == 50); +} + +test "lsp_work_done_progress_end" { + const end = work_done_progress_end_create("done"); + try std.testing.expectEqual(@as(usize, end.message.len), @as(usize, 4)); +} + +test "lsp_trace_is_enabled" { + try std.testing.expect(!trace_is_enabled(.off)); + try std.testing.expect(trace_is_enabled(.messages)); +} + +test "lsp_trace_to_string" { + try std.testing.expectEqual(@as(usize, trace_to_string(.off).len), @as(usize, 3)); + try std.testing.expectEqual(@as(usize, trace_to_string(.verbose).len), @as(usize, 7)); +} + +test "lsp_completion_has_snippet_support" { + const caps = client_capabilities_default(); + try std.testing.expect(completion_has_snippet_support(caps)); +} + +test "lsp_client_has_workspace_folders" { + var init = init_options_create("/path"); + try std.testing.expect(!client_has_workspace_folders(init)); + + init.workspace_folders = &[_]WorkspaceFolder{ + WorkspaceFolder{ .uri = "file:///test", .name = "test" }, + }; + try std.testing.expect(client_has_workspace_folders(init)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant client_capabilities_valid { + // ClientCapabilities has at least completion capability + @compileAssert(true); +} + +invariant trace_value_in_range { + // TraceValue is in [0, 2] + @compileAssert(@as(u8, TraceValue.off) == 0); + @compileAssert(@as(u8, TraceValue.verbose) == 2); +} + +invariant work_done_progress_percentage_range { + // WorkDoneProgress percentage is in [0, 100] + @compileAssert(true); +} + +invariant max_message_size_positive { + // MAX_MESSAGE_SIZE is positive + @compileAssert(MAX_MESSAGE_SIZE > 0); +} + +invariant request_timeout_positive { + // REQUEST_TIMEOUT_MS is positive + @compileAssert(REQUEST_TIMEOUT_MS > 0); +} + +invariant max_concurrent_requests_positive { + // MAX_CONCURRENT_REQUESTS is positive + @compileAssert(MAX_CONCURRENT_REQUESTS > 0); +} + +invariant process_id_non_negative { + // InitializationOptions process_id is non-negative + @compileAssert(true); +} + +invariant text_document_sync_options_valid { + // TextDocumentSyncOptions has valid sync kind + @compileAssert(true); +} + +invariant save_options_valid { + // SaveOptions has valid include_text flag + @compileAssert(true); +} + +invariant progress_begin_has_title { + // WorkDoneProgressBegin has non-empty title + @compileAssert(true); +} + +invariant trace_off_is_disabled { + // TraceValue.off disables tracing + @compileAssert(!trace_is_enabled(.off)); +} + +invariant completion_snippet_support_boolean { + // CompletionItemCapability snippet_support is boolean + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "lsp_client_capabilities_default_latency" { + // Measure: cycles for client capabilities creation + // Target: < 200 cycles + @setEvalBranchQuota(10000); + var result : ClientCapabilities = undefined; + for (0..1000) |_| { + result = client_capabilities_default(); + } +} + +bench "lsp_init_options_create_latency" { + // Measure: cycles for init options creation + // Target: < 300 cycles + @setEvalBranchQuota(10000); + var result : InitializationOptions = undefined; + for (0..1000) |_| { + result = init_options_create("/test/path"); + } +} + +bench "lsp_trace_is_enabled_latency" { + // Measure: cycles for trace enabled check + // Target: < 10 cycles + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..1000) |_| { + result = trace_is_enabled(.messages); + } +} + +bench "lsp_trace_to_string_latency" { + // Measure: cycles for trace to string conversion + // Target: < 20 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = trace_to_string(.verbose); + } +} diff --git a/apps/website/public/t27/specs/lsp/language.t27 b/apps/website/public/t27/specs/lsp/language.t27 new file mode 100644 index 0000000000..2232b6a371 --- /dev/null +++ b/apps/website/public/t27/specs/lsp/language.t27 @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: Apache-2.0 +// lsp/language.t27 — Language Server Feature Mappings +// Language ID mappings, file extensions, and feature associations +// φ² + 1/φ² = 3 | TRINITY + +module lsp-language; + +// ============================================================================ +// Imports +// ============================================================================ + +use tritype-base::usize; + +// ============================================================================ +// Constants +// ============================================================================ + +/// t27 language ID +pub const LANG_T27 : []const u8 = "t27"; + +/// Zig language ID +pub const LANG_ZIG : []const u8 = "zig"; + +/// Python language ID +pub const LANG_PYTHON : []const u8 = "python"; + +/// JavaScript language ID +pub const LANG_JAVASCRIPT : []const u8 = "javascript"; + +/// TypeScript language ID +pub const LANG_TYPESCRIPT : []const u8 = "typescript"; + +/// Markdown language ID +pub const LANG_MARKDOWN : []const u8 = "markdown"; + +/// JSON language ID +pub const LANG_JSON : []const u8 = "json"; + +/// YAML language ID +pub const LANG_YAML : []const u8 = "yaml"; + +/// Default file extension for t27 +pub const EXT_T27 : []const u8 = ".t27"; + +/// File extension for Zig +pub const EXT_ZIG : []const u8 = ".zig"; + +/// File extension for Python +pub const EXT_PYTHON : []const u8 = ".py"; + +/// File extension for JavaScript +pub const EXT_JAVASCRIPT : []const u8 = ".js"; + +/// File extension for TypeScript +pub const EXT_TYPESCRIPT : []const u8 = ".ts"; + +/// File extension for Markdown +pub const EXT_MARKDOWN : []const u8 = ".md"; + +/// File extension for JSON +pub const EXT_JSON : []const u8 = ".json"; + +/// File extension for YAML +pub const EXT_YAML : []const u8 = ".yaml"; + +// ============================================================================ +// Types +// ============================================================================ + +/// Language information +pub const LanguageInfo = struct { + id : []const u8, + extensions : []const []const u8, + aliases : []const []const u8, + mime_types : []const []const u8, + configuration : LanguageConfiguration, +}; + +/// Language configuration +pub const LanguageConfiguration = struct { + comments : CommentConfiguration, + brackets : BracketPair, + indentation : Indentation, + word_pattern : []const u8, +}; + +/// Comment configuration +pub const CommentConfiguration = struct { + line_comment : []const u8, + block_comment_start : []const u8, + block_comment_end : []const u8, +}; + +/// Bracket pair +pub const BracketPair = struct { + open : []const u8, + close : []const u8, +}; + +/// Indentation rules +pub const Indentation = struct { + insert_spaces : bool, + tab_size : usize, +}; + +/// Language feature +pub const LanguageFeature = enum(u8) { + completion = 0, + hover = 1, + signature_help = 2, + definition = 3, + type_definition = 4, + implementation = 5, + references = 6, + document_symbol = 7, + workspace_symbol = 8, + code_action = 9, + code_lens = 10, + formatting = 11, + document_highlight = 12, + rename = 13, +}; + +/// File association +pub const FileAssociation = struct { + pattern : []const u8, + language_id : []const u8, +}; + +/// Language server capability for specific language +pub const LanguageServerCapability = struct { + language_id : []const u8, + features : []const LanguageFeature, +}; + +/// Symbol information +pub const SymbolInfo = struct { + name : []const u8, + kind : SymbolKind, + language_id : []const u8, +}; + +/// Symbol kind +pub const SymbolKind = enum(u8) { + file = 0, + module = 1, + namespace = 2, + package = 3, + class = 4, + method = 5, + property = 6, + field = 7, + variable = 8, + function = 9, + constructor = 10, + interface = 11, + type = 12, +}; + +/// Keyword set for language +pub const KeywordSet = struct { + keywords : []const []const u8, + language_id : []const u8, +}; + +/// Completion item template for language +pub const CompletionTemplate = struct { + label : []const u8, + insert_text : []const u8, + kind : SymbolKind, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create language info for t27 +pub fn language_info_t27() LanguageInfo { + return LanguageInfo{ + .id = LANG_T27, + .extensions = &[_][]const u8{ EXT_T27 }, + .aliases = &[_][]const u8{ "ternary" }, + .mime_types = &[_][]const u8{ "text/x-t27" }, + .configuration = language_configuration_default(), + }; +} + +/// Create language info for Python +pub fn language_info_python() LanguageInfo { + return LanguageInfo{ + .id = LANG_PYTHON, + .extensions = &[_][]const u8{ EXT_PYTHON }, + .aliases = &[_][]const u8{ "py", "python" }, + .mime_types = &[_][]const u8{ "text/x-python" }, + .configuration = language_configuration_default(), + }; +} + +/// Create language info for Zig +pub fn language_info_zig() LanguageInfo { + return LanguageInfo{ + .id = LANG_ZIG, + .extensions = &[_][]const u8{ EXT_ZIG }, + .aliases = &[_][]const u8{ "zig" }, + .mime_types = &[_][]const u8{ "text/x-zig" }, + .configuration = language_configuration_default(), + }; +} + +/// Create default language configuration +pub fn language_configuration_default() LanguageConfiguration { + return LanguageConfiguration{ + .comments = CommentConfiguration{ + .line_comment = "//", + .block_comment_start = "/*", + .block_comment_end = "*/", + }, + .brackets = BracketPair{ .open = "(", .close = ")" }, + .indentation = Indentation{ .insert_spaces = true, .tab_size = 4 }, + .word_pattern = "[a-zA-Z_][a-zA-Z0-9_]*", + }; +} + +/// Create file association +pub fn file_association_create(pattern: []const u8, language_id: []const u8) FileAssociation { + return FileAssociation{ + .pattern = pattern, + .language_id = language_id, + }; +} + +/// Get language ID from file extension +pub fn language_id_from_extension(ext: []const u8) []const u8 { + if (std.mem.eql(u8, ext, EXT_T27)) { + return LANG_T27; + } else if (std.mem.eql(u8, ext, EXT_PYTHON)) { + return LANG_PYTHON; + } else if (std.mem.eql(u8, ext, EXT_ZIG)) { + return LANG_ZIG; + } else if (std.mem.eql(u8, ext, EXT_JAVASCRIPT)) { + return LANG_JAVASCRIPT; + } else if (std.mem.eql(u8, ext, EXT_TYPESCRIPT)) { + return LANG_TYPESCRIPT; + } else { + return "text"; + } +} + +/// Create language server capability +pub fn language_server_capability_create(language_id: []const u8) LanguageServerCapability { + return LanguageServerCapability{ + .language_id = language_id, + .features = &[_]LanguageFeature{ + .completion, .hover, .definition, .document_symbol, + }, + }; +} + +/// Create symbol info +pub fn symbol_info_create(name: []const u8, kind: SymbolKind, language_id: []const u8) SymbolInfo { + return SymbolInfo{ + .name = name, + .kind = kind, + .language_id = language_id, + }; +} + +/// Create completion template +pub fn completion_template_create(label: []const u8, insert_text: []const u8, kind: SymbolKind) CompletionTemplate { + return CompletionTemplate{ + .label = label, + .insert_text = insert_text, + .kind = kind, + }; +} + +/// Check if extension is t27 +pub fn is_t27_extension(ext: []const u8) bool { + return std.mem.eql(u8, ext, EXT_T27); +} + +/// Check if language is t27 +pub fn is_t27_language(language_id: []const u8) bool { + return std.mem.eql(u8, language_id, LANG_T27); +} + +/// Get feature string +pub fn feature_to_string(feature: LanguageFeature) []const u8 { + return switch (feature) { + .completion => "completion", + .hover => "hover", + .signature_help => "signature_help", + .definition => "definition", + .type_definition => "type_definition", + .implementation => "implementation", + .references => "references", + .document_symbol => "document_symbol", + .workspace_symbol => "workspace_symbol", + .code_action => "code_action", + .code_lens => "code_lens", + .formatting => "formatting", + .document_highlight => "document_highlight", + .rename => "rename", + }; +} + +/// Get symbol kind string +pub fn symbol_kind_to_string(kind: SymbolKind) []const u8 { + return switch (kind) { + .file => "file", + .module => "module", + .namespace => "namespace", + .package => "package", + .class => "class", + .method => "method", + .property => "property", + .field => "field", + .variable => "variable", + .function => "function", + .constructor => "constructor", + .interface => "interface", + .type => "type", + }; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "lsp_language_info_t27" { + const info = language_info_t27(); + try std.testing.expectEqual(@as(usize, info.id.len), @as(usize, 3)); +} + +test "lsp_language_info_python" { + const info = language_info_python(); + try std.testing.expectEqual(@as(usize, info.id.len), @as(usize, 6)); +} + +test "lsp_language_info_zig" { + const info = language_info_zig(); + // 3, not 4. LANG_ZIG is "zig" and its declared type was [4]u8 -- four + // slots for three characters. The assertion inherited the 4 from the + // declaration. Its two siblings of identical shape, expecting 3 for "t27" + // and 6 for "python", passed all along. + try std.testing.expectEqual(@as(usize, info.id.len), @as(usize, 3)); +} + +test "lsp_language_configuration_default" { + const config = language_configuration_default(); + try std.testing.expectEqual(@as(usize, config.comments.line_comment.len), @as(usize, 2)); +} + +test "lsp_file_association_create" { + const assoc = file_association_create("*.t27", LANG_T27); + try std.testing.expectEqual(@as(usize, assoc.pattern.len), @as(usize, 5)); +} + +test "lsp_language_id_from_extension_t27" { + const lang_id = language_id_from_extension(EXT_T27); + try std.testing.expectEqual(@as(usize, lang_id.len), @as(usize, 3)); +} + +test "lsp_language_id_from_extension_python" { + const lang_id = language_id_from_extension(EXT_PYTHON); + try std.testing.expectEqual(@as(usize, lang_id.len), @as(usize, 6)); +} + +test "lsp_is_t27_extension" { + try std.testing.expect(is_t27_extension(EXT_T27)); +} + +test "lsp_is_t27_language" { + try std.testing.expect(is_t27_language(LANG_T27)); +} + +test "lsp_language_server_capability_create" { + const cap = language_server_capability_create(LANG_T27); + try std.testing.expect(cap.features.len > 0); +} + +test "lsp_symbol_info_create" { + const sym = symbol_info_create("test", .function, LANG_T27); + try std.testing.expectEqual(@as(usize, sym.name.len), @as(usize, 4)); +} + +test "lsp_completion_template_create" { + const tmpl = completion_template_create("fn", "fn()", .function); + try std.testing.expectEqual(@as(usize, tmpl.label.len), @as(usize, 2)); +} + +test "lsp_feature_to_string" { + const str = feature_to_string(.completion); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 10)); +} + +test "lsp_symbol_kind_to_string" { + const str = symbol_kind_to_string(.function); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 8)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant language_id_non_empty { + // LanguageInfo id is non-empty + @compileAssert(LANG_T27.len > 0); +} + +invariant extension_constants_valid { + // All extension constants are valid + @compileAssert(EXT_T27.len > 0); + @compileAssert(EXT_PYTHON.len > 0); +} + +invariant language_info_has_extensions { + // LanguageInfo has at least one extension + @compileAssert(true); +} + +invariant file_association_has_pattern { + // FileAssociation has non-empty pattern + @compileAssert(true); +} + +invariant language_server_capability_has_language { + // LanguageServerCapability has non-empty language_id + @compileAssert(true); +} + +invariant symbol_info_has_name { + // SymbolInfo has non-empty name + @compileAssert(true); +} + +invariant completion_template_has_label { + // CompletionTemplate has non-empty label + @compileAssert(true); +} + +invariant feature_in_range { + // LanguageFeature is in [0, 13] + @compileAssert(@intFromEnum(LanguageFeature.completion) == 0); + @compileAssert(@intFromEnum(LanguageFeature.rename) == 13); +} + +invariant symbol_kind_in_range { + // SymbolKind is in [0, 12] + @compileAssert(@intFromEnum(SymbolKind.file) == 0); + @compileAssert(@intFromEnum(SymbolKind.type) == 12); +} + +invariant comment_configuration_valid { + // CommentConfiguration has valid fields + @compileAssert(true); +} + +invariant indentation_valid { + // Indentation has valid fields + @compileAssert(true); +} + +invariant bracket_pair_valid { + // BracketPair has valid open and close + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "lsp_language_info_t27_latency" { + // Measure: cycles for t27 language info creation + // Target: < 500 cycles + @setEvalBranchQuota(10000); + var result : LanguageInfo = undefined; + for (0..1000) |_| { + result = language_info_t27(); + } + _ = result; +} + +bench "lsp_language_configuration_default_latency" { + // Measure: cycles for default language configuration + // Target: < 300 cycles + @setEvalBranchQuota(10000); + var result : LanguageConfiguration = undefined; + for (0..1000) |_| { + result = language_configuration_default(); + } + _ = result; +} + +bench "lsp_language_id_from_extension_latency" { + // Measure: cycles for language ID lookup + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : []const u8 = undefined; + for (0..1000) |_| { + result = language_id_from_extension(EXT_T27); + } + _ = result; +} + +bench "lsp_feature_to_string_latency" { + // Measure: cycles for feature to string conversion + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : []const u8 = undefined; + for (0..1000) |_| { + result = feature_to_string(.completion); + } + _ = result; +} + +bench "lsp_symbol_kind_to_string_latency" { + // Measure: cycles for symbol kind to string conversion + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : []const u8 = undefined; + for (0..1000) |_| { + result = symbol_kind_to_string(.function); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/lsp/protocol.t27 b/apps/website/public/t27/specs/lsp/protocol.t27 new file mode 100644 index 0000000000..0ad7627645 --- /dev/null +++ b/apps/website/public/t27/specs/lsp/protocol.t27 @@ -0,0 +1,680 @@ +// SPDX-License-Identifier: Apache-2.0 +// lsp/protocol.t27 — JSON-RPC 2.0 Protocol Mapping +// LSP message handling over JSON-RPC transport layer +// φ² + 1/φ² = 3 | TRINITY + +module lsp-protocol; + +// ============================================================================ +// Imports +// ============================================================================ + +use tritype-base::usize; + +// ============================================================================ +// Constants +// ============================================================================ + +/// JSON-RPC version string +pub const JSON_RPC_VERSION : []const u8 = "2.0"; + +/// Default buffer size for message encoding +pub const DEFAULT_BUFFER_SIZE : usize = 4096; + +/// Maximum message length in bytes +pub const MAX_MESSAGE_LENGTH : usize = 10485760; + +/// Request method for initialize +pub const METHOD_INITIALIZE : []const u8 = "initialize"; + +/// Request method for initialized +pub const METHOD_INITIALIZED : []const u8 = "initialized"; + +/// Request method for shutdown +pub const METHOD_SHUTDOWN : []const u8 = "shutdown"; + +/// Request method for exit +pub const METHOD_EXIT : []const u8 = "exit"; + +/// Request method for textDocument/didOpen +pub const METHOD_DID_OPEN : []const u8 = "textDocument/didOpen"; + +/// Request method for textDocument/didChange +pub const METHOD_DID_CHANGE : []const u8 = "textDocument/didChange"; + +/// Request method for textDocument/didClose +pub const METHOD_DID_CLOSE : []const u8 = "textDocument/didClose"; + +/// Request method for textDocument/completion +pub const METHOD_COMPLETION : []const u8 = "textDocument/completion"; + +/// Request method for textDocument/hover +pub const METHOD_HOVER : []const u8 = "textDocument/hover"; + +/// Request method for textDocument/definition +pub const METHOD_DEFINITION : []const u8 = "textDocument/definition"; + +/// Request method for textDocument/documentSymbol +pub const METHOD_DOCUMENT_SYMBOL : []const u8 = "textDocument/documentSymbol"; + +/// Request method for workspace/symbol +pub const METHOD_WORKSPACE_SYMBOL : []const u8 = "workspace/symbol"; + +/// Request method for textDocument/codeAction +pub const METHOD_CODE_ACTION : []const u8 = "textDocument/codeAction"; + +/// Request method for textDocument/rename +pub const METHOD_RENAME : []const u8 = "textDocument/rename"; + +// ============================================================================ +// Types +// ============================================================================ + +/// JSON-RPC message type +pub const MessageType = enum(u8) { + request = 0, + response = 1, + notification = 2, + error = 3, +}; + +/// JSON-RPC request +pub const Request = struct { + jsonrpc : []const u8, + id : usize, + method : []const u8, + params : []const u8, +}; + +/// JSON-RPC response +pub const Response = struct { + jsonrpc : []const u8, + id : usize, + result : []const u8, + error : []const u8, +}; + +/// JSON-RPC notification +pub const Notification = struct { + jsonrpc : []const u8, + method : []const u8, + params : []const u8, +}; + +/// JSON-RPC error +pub const JsonRpcError = struct { + code : i32, + message : []const u8, + data : []const u8, +}; + +/// Message parsing result +pub const ParseResult = struct { + success : bool, + message_type : MessageType, + request : Request, + response : Response, + notification : Notification, + error : JsonRpcError, +}; + +/// Transport layer type +pub const TransportType = enum(u8) { + stdio = 0, + tcp = 1, + websocket = 2, +}; + +/// Connection state +pub const ConnectionState = enum(u8) { + disconnected = 0, + connecting = 1, + connected = 2, + error = 3, +}; + +/// Message buffer +/// A message buffer that owns its storage. +/// +/// `data` is `[]u8`, not `[]const u8`: a buffer whose bytes cannot be written +/// is not a buffer. The previous shape paired a mutable-sounding `capacity` +/// with an immutable empty slice, which is how it managed to claim room for +/// 1024 bytes while holding none. +pub const MessageBuffer = struct { + data : []u8, + length : usize, + capacity : usize, +}; + +/// Incoming message +pub const IncomingMessage = struct { + content : []const u8, + complete : bool, +}; + +/// Outgoing message +pub const OutgoingMessage = struct { + content : []const u8, + sent : bool, +}; + +/// Batch request +pub const BatchRequest = struct { + requests : []Request, +}; + +/// Batch response +pub const BatchResponse = struct { + responses : []Response, +}; + +/// Cancel params +pub const CancelParams = struct { + id : usize, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create JSON-RPC request +pub fn request_create(id: usize, method: []const u8, params: []const u8) Request { + return Request{ + .jsonrpc = JSON_RPC_VERSION, + .id = id, + .method = method, + .params = params, + }; +} + +/// Create JSON-RPC response +pub fn response_create(id: usize, result: []const u8) Response { + return Response{ + .jsonrpc = JSON_RPC_VERSION, + .id = id, + .result = result, + .error = "", + }; +} + +/// Create JSON-RPC error response +pub fn response_error_create(id: usize, err: JsonRpcError) Response { + return Response{ + .jsonrpc = JSON_RPC_VERSION, + .id = id, + .result = "", + .error = err.message, + }; +} + +/// Create JSON-RPC notification +pub fn notification_create(method: []const u8, params: []const u8) Notification { + return Notification{ + .jsonrpc = JSON_RPC_VERSION, + .method = method, + .params = params, + }; +} + +/// Create JSON-RPC error +pub fn json_rpc_error_create(code: i32, message: []const u8) JsonRpcError { + return JsonRpcError{ + .code = code, + .message = message, + .data = "", + }; +} + +/// Create a message buffer with room for `capacity` bytes. +/// +/// Takes an allocator and returns an error union because it genuinely can +/// fail: asking for room is not the same as having it. The old signature +/// promised a buffer it could not deliver, and no signature without an +/// allocator can deliver one -- that is why the honest test was left red +/// rather than patched. +/// +/// Caller owns the result; pass it to message_buffer_destroy. +pub fn message_buffer_create(allocator: std.mem.Allocator, capacity: usize) !MessageBuffer { + const data = try allocator.alloc(u8, capacity); + return MessageBuffer{ + .data = data, + .length = 0, + .capacity = capacity, + }; +} + +/// Release a buffer's storage. Pair with message_buffer_create. +pub fn message_buffer_destroy(allocator: std.mem.Allocator, buf: MessageBuffer) void { + allocator.free(buf.data); +} + +/// Create initialize request +pub fn initialize_request_create(id: usize, params: []const u8) Request { + return request_create(id, METHOD_INITIALIZE, params); +} + +/// Create initialized notification +pub fn initialized_notification_create() Notification { + return notification_create(METHOD_INITIALIZED, ""); +} + +/// Create shutdown request +pub fn shutdown_request_create(id: usize) Request { + return request_create(id, METHOD_SHUTDOWN, ""); +} + +/// Create exit notification +pub fn exit_notification_create() Notification { + return notification_create(METHOD_EXIT, ""); +} + +/// Create completion request +pub fn completion_request_create(id: usize, params: []const u8) Request { + return request_create(id, METHOD_COMPLETION, params); +} + +/// Create hover request +pub fn hover_request_create(id: usize, params: []const u8) Request { + return request_create(id, METHOD_HOVER, params); +} + +/// Create definition request +pub fn definition_request_create(id: usize, params: []const u8) Request { + return request_create(id, METHOD_DEFINITION, params); +} + +/// Create cancel params +pub fn cancel_params_create(id: usize) CancelParams { + return CancelParams{ + .id = id, + }; +} + +/// Check if response is successful +pub fn response_is_success(resp: Response) bool { + return resp.result.len > 0; +} + +/// Check if response has error +pub fn response_has_error(resp: Response) bool { + return resp.error.len > 0; +} + +/// Check if message is notification +pub fn message_is_notification(msg: ParseResult) bool { + return msg.message_type == .notification; +} + +/// Check if message is request +pub fn message_is_request(msg: ParseResult) bool { + return msg.message_type == .request; +} + +/// Get message type string +pub fn message_type_to_string(msg_type: MessageType) []const u8 { + return switch (msg_type) { + .request => "request", + .response => "response", + .notification => "notification", + .error => "error", + }; +} + +/// Check if connection is active +pub fn connection_is_active(state: ConnectionState) bool { + return state == .connected; +} + +/// Get connection state string +pub fn connection_state_to_string(state: ConnectionState) []const u8 { + return switch (state) { + .disconnected => "disconnected", + .connecting => "connecting", + .connected => "connected", + .error => "error", + }; +} + +/// Create parse result +pub fn parse_result_create(success: bool, message_type: MessageType) ParseResult { + return ParseResult{ + .success = success, + .message_type = message_type, + .request = request_create(0, "", ""), + .response = response_create(0, ""), + .notification = notification_create("", ""), + .error = json_rpc_error_create(0, ""), + }; +} + +/// Check if transport supports duplex communication +pub fn transport_supports_duplex(trans_type: TransportType) bool { + return trans_type == .stdio or trans_type == .tcp or trans_type == .websocket; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "lsp_request_create" { + const req = request_create(1, "test", ""); + try std.testing.expect(req.id == 1); +} + +test "lsp_response_create" { + const resp = response_create(1, "result"); + try std.testing.expect(resp.id == 1); + try std.testing.expect(response_is_success(resp)); +} + +test "lsp_response_error_create" { + const err = json_rpc_error_create(-1, "test error"); + const resp = response_error_create(1, err); + try std.testing.expect(response_has_error(resp)); +} + +test "lsp_notification_create" { + const notif = notification_create("test", ""); + try std.testing.expectEqual(@as(usize, notif.method.len), @as(usize, 4)); +} + +test "lsp_json_rpc_error_create" { + const err = json_rpc_error_create(-32600, "invalid request"); + try std.testing.expect(err.code == -32600); +} + +test "lsp_message_buffer_create" { + const buf = try message_buffer_create(std.testing.allocator, 1024); + defer message_buffer_destroy(std.testing.allocator, buf); + try std.testing.expect(buf.capacity == 1024); +} + +// This was red on purpose until 2026-08-27: message_buffer_create reported +// whatever capacity it was asked for and backed it with nothing -- `.data = +// ""`, an empty slice, for any argument. The test above passed because it +// read only `capacity`, the field the function copied rather than the one it +// would have had to earn. +// +// The signature now takes an allocator and returns an error union, so the +// claim is backed or the call fails. std.testing.allocator is deliberate: it +// fails the test on a leak, so this also pins that message_buffer_destroy +// actually frees. +test "lsp_message_buffer_has_the_room_it_claims" { + const buf = try message_buffer_create(std.testing.allocator, 1024); + defer message_buffer_destroy(std.testing.allocator, buf); + try std.testing.expect(buf.data.len >= buf.capacity); +} + +// The other half of the promise: a buffer that cannot be written is not a +// buffer, whatever its length says. +test "lsp_message_buffer_is_writable" { + const buf = try message_buffer_create(std.testing.allocator, 8); + defer message_buffer_destroy(std.testing.allocator, buf); + buf.data[0] = 0xAB; + buf.data[7] = 0xCD; + try std.testing.expect(buf.data[0] == 0xAB); + try std.testing.expect(buf.data[7] == 0xCD); +} + +// A zero-capacity buffer is legal and must still round-trip through destroy +// without the allocator complaining. +test "lsp_message_buffer_zero_capacity" { + const buf = try message_buffer_create(std.testing.allocator, 0); + defer message_buffer_destroy(std.testing.allocator, buf); + try std.testing.expect(buf.data.len == 0); + try std.testing.expect(buf.capacity == 0); +} + +test "lsp_initialize_request_create" { + const req = initialize_request_create(1, "{}"); + try std.testing.expectEqual(@as(usize, req.method.len), @as(usize, 10)); +} + +test "lsp_shutdown_request_create" { + const req = shutdown_request_create(1); + try std.testing.expectEqual(@as(usize, req.method.len), @as(usize, 8)); +} + +test "lsp_completion_request_create" { + const req = completion_request_create(1, "{}"); + // 23, not 21. METHOD_COMPLETION is "textDocument/completion", and the + // same wrong number was written into its declared type ([21]u8) AND into + // this assertion. Neither could be checked while the file did not compile, + // so the two agreed with each other and with nothing else. The string is + // right; the count was wrong in both places. + try std.testing.expectEqual(@as(usize, req.method.len), @as(usize, 23)); +} + +test "lsp_response_is_success" { + const resp = response_create(1, "result"); + try std.testing.expect(response_is_success(resp)); +} + +test "lsp_response_has_error" { + const err = json_rpc_error_create(-1, "test"); + const resp = response_error_create(1, err); + try std.testing.expect(response_has_error(resp)); +} + +test "lsp_message_type_to_string" { + const str = message_type_to_string(.request); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 7)); +} + +test "lsp_connection_is_active" { + try std.testing.expect(!connection_is_active(.disconnected)); + try std.testing.expect(connection_is_active(.connected)); +} + +test "lsp_connection_state_to_string" { + const str = connection_state_to_string(.connected); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 9)); +} + +test "lsp_transport_supports_duplex" { + try std.testing.expect(transport_supports_duplex(.stdio)); + try std.testing.expect(transport_supports_duplex(.websocket)); +} + +// ---------------------------------------------------------------------------- +// Constant lengths. +// +// These 15 constants used to be declared as fixed-size arrays ([N]u8), so the +// type stated each length. A []const u8 does not, so the claim is restated here +// where it is executed instead of merely written down. 8 of the 15 declared +// lengths were WRONG; each of those is marked, with the false figure named. +// ---------------------------------------------------------------------------- + +test "lsp_len_json_rpc_version" { + // was declared [6]u8 — false, "2.0" is 3 bytes + try std.testing.expectEqual(@as(usize, JSON_RPC_VERSION.len), @as(usize, 3)); +} + +test "lsp_len_method_initialize" { + try std.testing.expectEqual(@as(usize, METHOD_INITIALIZE.len), @as(usize, 10)); +} + +test "lsp_len_method_initialized" { + try std.testing.expectEqual(@as(usize, METHOD_INITIALIZED.len), @as(usize, 11)); +} + +test "lsp_len_method_shutdown" { + try std.testing.expectEqual(@as(usize, METHOD_SHUTDOWN.len), @as(usize, 8)); +} + +test "lsp_len_method_exit" { + try std.testing.expectEqual(@as(usize, METHOD_EXIT.len), @as(usize, 4)); +} + +test "lsp_len_method_did_open" { + try std.testing.expectEqual(@as(usize, METHOD_DID_OPEN.len), @as(usize, 20)); +} + +test "lsp_len_method_did_change" { + // was declared [21]u8 — false, "textDocument/didChange" is 22 bytes + try std.testing.expectEqual(@as(usize, METHOD_DID_CHANGE.len), @as(usize, 22)); +} + +test "lsp_len_method_did_close" { + // was declared [20]u8 — false, "textDocument/didClose" is 21 bytes + try std.testing.expectEqual(@as(usize, METHOD_DID_CLOSE.len), @as(usize, 21)); +} + +test "lsp_len_method_completion" { + // was declared [21]u8 — false, "textDocument/completion" is 23 bytes + try std.testing.expectEqual(@as(usize, METHOD_COMPLETION.len), @as(usize, 23)); +} + +test "lsp_len_method_hover" { + // was declared [17]u8 — false, "textDocument/hover" is 18 bytes + try std.testing.expectEqual(@as(usize, METHOD_HOVER.len), @as(usize, 18)); +} + +test "lsp_len_method_definition" { + try std.testing.expectEqual(@as(usize, METHOD_DEFINITION.len), @as(usize, 23)); +} + +test "lsp_len_method_document_symbol" { + // was declared [24]u8 — false, "textDocument/documentSymbol" is 27 bytes + try std.testing.expectEqual(@as(usize, METHOD_DOCUMENT_SYMBOL.len), @as(usize, 27)); +} + +test "lsp_len_method_workspace_symbol" { + try std.testing.expectEqual(@as(usize, METHOD_WORKSPACE_SYMBOL.len), @as(usize, 16)); +} + +test "lsp_len_method_code_action" { + // was declared [21]u8 — false, "textDocument/codeAction" is 23 bytes + try std.testing.expectEqual(@as(usize, METHOD_CODE_ACTION.len), @as(usize, 23)); +} + +test "lsp_len_method_rename" { + // was declared [17]u8 — false, "textDocument/rename" is 19 bytes + try std.testing.expectEqual(@as(usize, METHOD_RENAME.len), @as(usize, 19)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant json_rpc_version_constant { + // JSON_RPC_VERSION is "2.0" + @compileAssert(std.mem.eql(u8, JSON_RPC_VERSION, "2.0")); +} + +invariant message_type_in_range { + // MessageType is in [0, 3] + @compileAssert(@intFromEnum(MessageType.request) == 0); + @compileAssert(@intFromEnum(MessageType.error) == 3); +} + +invariant request_has_id { + // Request has non-zero id + @compileAssert(true); +} + +invariant response_has_id { + // Response has id + @compileAssert(true); +} + +invariant notification_no_id { + // Notification has no id field + @compileAssert(true); +} + +invariant transport_type_valid { + // TransportType is valid enum value + @compileAssert(true); +} + +invariant connection_state_in_range { + // ConnectionState is in [0, 3] + @compileAssert(@intFromEnum(ConnectionState.disconnected) == 0); + @compileAssert(@intFromEnum(ConnectionState.error) == 3); +} + +invariant json_rpc_error_has_code { + // JsonRpcError has valid error code + @compileAssert(true); +} + +invariant message_buffer_capacity_positive { + // MessageBuffer capacity is positive + @compileAssert(true); +} + +invariant method_strings_valid { + // All method constants are non-empty + @compileAssert(METHOD_INITIALIZE.len > 0); + @compileAssert(METHOD_EXIT.len > 0); +} + +invariant request_has_method { + // Request has non-empty method + @compileAssert(true); +} + +invariant response_either_result_or_error { + // Response has either result or error, not both + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "lsp_request_create_latency" { + // Measure: cycles for request creation + // Target: < 200 cycles + @setEvalBranchQuota(10000); + var result : Request = undefined; + for (0..1000) |_| { + result = request_create(1, "test", ""); + } + _ = result; +} + +bench "lsp_response_create_latency" { + // Measure: cycles for response creation + // Target: < 200 cycles + @setEvalBranchQuota(10000); + var result : Response = undefined; + for (0..1000) |_| { + result = response_create(1, "result"); + } + _ = result; +} + +bench "lsp_notification_create_latency" { + // Measure: cycles for notification creation + // Target: < 150 cycles + @setEvalBranchQuota(10000); + var result : Notification = undefined; + for (0..1000) |_| { + result = notification_create("test", ""); + } + _ = result; +} + +bench "lsp_response_is_success_latency" { + // Measure: cycles for response success check + // Target: < 20 cycles + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..1000) |_| { + result = response_is_success(response_create(1, "result")); + } + _ = result; +} + +bench "lsp_message_type_to_string_latency" { + // Measure: cycles for message type to string + // Target: < 20 cycles + @setEvalBranchQuota(10000); + var result : []const u8 = undefined; + for (0..1000) |_| { + result = message_type_to_string(.request); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/lsp/schema.t27 b/apps/website/public/t27/specs/lsp/schema.t27 new file mode 100644 index 0000000000..293cb55e28 --- /dev/null +++ b/apps/website/public/t27/specs/lsp/schema.t27 @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: Apache-2.0 +// lsp/schema.t27 — LSP Base Types +// Position, Range, Diagnostic definitions for Language Server Protocol +// φ² + 1/φ² = 3 | TRINITY + +module lsp-schema; + +// ============================================================================ +// Imports +// ============================================================================ + +use tritype-base::usize; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default character offset for start of document +pub const ZERO_POSITION : usize = 0; + +/// Maximum line number in diagnostic reports +pub const MAX_LINE : usize = 65535; + +/// Maximum character position on a line +pub const MAX_CHAR : usize = 65535; + +/// Default protocol version +pub const LSP_VERSION : [4]u8 = "3.17"; + +// ============================================================================ +// Types +// ============================================================================ + +/// Line and character position in a document +/// Zero-based: line 0, char 0 is first position +pub const Position = struct { + line : usize, + character : usize, +}; + +/// Range between two positions (start exclusive, end inclusive) +/// Used for: selection, code action, diagnostic spans +pub const Range = struct { + start : Position, + end : Position, +}; + +/// Location in a specific document +/// Combines URI with range within that document +pub const Location = struct { + uri : []u8, + range : Range, +}; + +/// Diagnostic severity levels +/// Order: HINT < INFO < WARNING < ERROR +pub const DiagnosticSeverity = enum(u8) { + hint = 0, + info = 1, + warning = 2, + error = 3, +}; + +/// Diagnostic tag for code actions +pub const DiagnosticTag = enum(u8) { + unnecessary = 0, + deprecated = 1, +}; + +/// Code action for quick fixes +pub const CodeAction = struct { + title : []u8, + kind : CodeActionKind, + diagnostics : []Diagnostic, + edit : WorkspaceEdit, +}; + +/// Code action kind +pub const CodeActionKind = enum(u8) { + quick_fix = 0, + refactor = 1, + refactor_extract = 2, + refactor_inline = 3, +}; + +/// Workspace edit for code changes +pub const WorkspaceEdit = struct { + changes : []TextDocumentEdit, +}; + +/// Single document edit +pub const TextDocumentEdit = struct { + range : Range, + new_text : []u8, +}; + +/// Diagnostic information +pub const Diagnostic = struct { + range : Range, + severity : DiagnosticSeverity, + code : []u8, + source : []u8, + message : []u8, + tags : []DiagnosticTag, +}; + +/// Text document synchronization kind +pub const TextDocumentSyncKind = enum(u8) { + full = 0, + incremental = 1, +}; + +/// Text document item for opening +pub const TextDocumentItem = struct { + uri : []u8, + version : usize, + language_id : []u8, + text : []u8, +}; + +/// Completion item for code completion +pub const CompletionItem = struct { + label : []u8, + kind : CompletionItemKind, + detail : []u8, + documentation : []u8, + sort_text : []u8, + filter_text : []u8, + insert_text : []u8, +}; + +/// Completion item kind +pub const CompletionItemKind = enum(u8) { + text = 0, + method = 1, + function = 2, + constructor = 3, + field = 4, + variable = 5, + class = 6, + interface = 7, + module = 8, + property = 9, + unit = 10, + value = 11, + enum = 12, + keyword = 13, + snippet = 14, + color = 15, + file = 16, + reference = 17, + folder = 18, + enum_member = 19, + constant = 20, + struct = 21, + event = 22, + operator = 23, + type_parameter = 24, +}; + +/// Signature help information +pub const SignatureInformation = struct { + label : []u8, + documentation : []u8, + parameters : []ParameterInformation, +}; + +/// Parameter in signature help +pub const ParameterInformation = struct { + label : []u8, + documentation : []u8, +}; + +/// Hover response +pub const Hover = struct { + contents : []u8, + range : Range, +}; + +/// Document symbol kind +pub const SymbolKind = enum(u8) { + file = 0, + module = 1, + namespace = 2, + package = 3, + class = 4, + method = 5, + property = 6, + field = 7, + constructor = 8, + enum = 9, + interface = 10, + function = 11, + variable = 12, + constant = 13, + string = 14, + number = 15, + boolean = 16, + array = 17, + object = 18, + key = 19, + null = 20, + enum_member = 21, + struct = 22, + event = 23, + operator = 24, + type_parameter = 25, +}; + +/// Document symbol +pub const DocumentSymbol = struct { + name : []u8, + detail : []u8, + kind : SymbolKind, + range : Range, + selection_range : Range, + children : []DocumentSymbol, +}; + +/// Inlay hint kind +pub const InlayHintKind = enum(u8) { + type = 0, + parameter = 1, +}; + +/// Inlay hint +pub const InlayHint = struct { + position : Position, + label : []u8, + kind : InlayHintKind, + padding_left : bool, + padding_right : bool, +}; + +/// Code lens for code actions +pub const CodeLens = struct { + range : Range, + command : Command, +}; + +/// Command +pub const Command = struct { + title : []u8, + command : []u8, + arguments : []u8, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create position at zero +pub fn position_zero() Position { + return Position{ .line = 0, .character = 0 }; +} + +/// Create range from positions +pub fn range_from_positions(start: Position, end: Position) Range { + return Range{ .start = start, .end = end }; +} + +/// Create range from line/char values +pub fn range_create(start_line: usize, start_char: usize, end_line: usize, end_char: usize) Range { + const start = Position{ .line = start_line, .character = start_char }; + const end = Position{ .line = end_line, .character = end_char }; + return Range{ .start = start, .end = end }; +} + +/// Create location from URI and range +pub fn location_create(uri: []u8, start_line: usize, start_char: usize, end_line: usize, end_char: usize) Location { + const range = range_create(start_line, start_char, end_line, end_char); + return Location{ .uri = uri, .range = range }; +} + +/// Create diagnostic +pub fn diagnostic_create(range: Range, severity: DiagnosticSeverity, message: []u8, source: []u8) Diagnostic { + return Diagnostic{ + .range = range, + .severity = severity, + .code = "", + .source = source, + .message = message, + .tags = &[_]DiagnosticTag{}, + }; +} + +/// Create completion item +pub fn completion_item_create(label: []u8, kind: CompletionItemKind, insert_text: []u8) CompletionItem { + return CompletionItem{ + .label = label, + .kind = kind, + .detail = "", + .documentation = "", + .sort_text = "", + .filter_text = "", + .insert_text = insert_text, + }; +} + +/// Create inlay hint +pub fn inlay_hint_create(position: Position, label: []u8, kind: InlayHintKind) InlayHint { + return InlayHint{ + .position = position, + .label = label, + .kind = kind, + .padding_left = false, + .padding_right = false, + }; +} + +/// Create document symbol +pub fn document_symbol_create(name: []u8, kind: SymbolKind, range: Range) DocumentSymbol { + return DocumentSymbol{ + .name = name, + .detail = "", + .kind = kind, + .range = range, + .selection_range = range, + .children = &[_]DocumentSymbol{}, + }; +} + +/// Check if position is zero +pub fn position_is_zero(pos: Position) bool { + return pos.line == 0 and pos.character == 0; +} + +/// Check if range is empty (start == end) +pub fn range_is_empty(r: Range) bool { + return r.start.line == r.end.line and r.start.character == r.end.character; +} + +/// Get range length in characters (simplified) +pub fn range_length(r: Range) usize { + if (r.start.line == r.end.line) { + if (r.end.character > r.start.character) { + return r.end.character - r.start.character; + } + } + return 0; +} + +/// Compare two positions +pub fn position_compare(a: Position, b: Position) i8 { + if (a.line < b.line) { + return -1; + } else if (a.line > b.line) { + return 1; + } else { + if (a.character < b.character) { + return -1; + } else if (a.character > b.character) { + return 1; + } else { + return 0; + } + } +} + +/// Check if position is within range +pub fn range_contains_position(r: Range, pos: Position) bool { + const after_start = position_compare(pos, r.start) >= 0; + const before_end = position_compare(pos, r.end) < 0; + return after_start and before_end; +} + +/// Format severity as string +pub fn severity_to_string(sev: DiagnosticSeverity) []u8 { + return switch (sev) { + .error => "error", + .warning => "warning", + .info => "info", + .hint => "hint", + }; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "lsp_position_zero_is_zero" { + const pos = position_zero(); + try std.testing.expect(pos.line == 0); + try std.testing.expect(pos.character == 0); +} + +test "lsp_range_from_positions" { + const start = Position{ .line = 5, .character = 10 }; + const end = Position{ .line = 5, .character = 20 }; + const r = range_from_positions(start, end); + try std.testing.expect(r.start.line == 5); + try std.testing.expect(r.end.character == 20); +} + +test "lsp_range_create" { + const r = range_create(1, 5, 1, 10); + try std.testing.expect(r.start.line == 1); + try std.testing.expect(r.start.character == 5); + try std.testing.expect(r.end.line == 1); + try std.testing.expect(r.end.character == 10); +} + +test "lsp_range_empty_when_equal" { + const r = range_create(0, 0, 0, 0); + try std.testing.expect(range_is_empty(r)); +} + +test "lsp_range_not_empty_when_different" { + const r = range_create(0, 0, 0, 5); + try std.testing.expect(!range_is_empty(r)); +} + +test "lsp_range_contains_position" { + const start = Position{ .line = 1, .character = 0 }; + const end = Position{ .line = 1, .character = 10 }; + const r = range_from_positions(start, end); + const inside = Position{ .line = 1, .character = 5 }; + const outside = Position{ .line = 1, .character = 15 }; + try std.testing.expect(range_contains_position(r, inside)); + try std.testing.expect(!range_contains_position(r, outside)); +} + +test "lsp_position_compare_less" { + const a = Position{ .line = 1, .character = 0 }; + const b = Position{ .line = 2, .character = 0 }; + try std.testing.expect(position_compare(a, b) < 0); +} + +test "lsp_position_compare_equal" { + const a = Position{ .line = 1, .character = 5 }; + const b = Position{ .line = 1, .character = 5 }; + try std.testing.expect(position_compare(a, b) == 0); +} + +test "lsp_position_compare_greater" { + const a = Position{ .line = 2, .character = 0 }; + const b = Position{ .line = 1, .character = 0 }; + try std.testing.expect(position_compare(a, b) > 0); +} + +test "lsp_severity_error_is_highest" { + try std.testing.expect(@as(u8, DiagnosticSeverity.error) == 3); +} + +test "lsp_severity_hint_is_lowest" { + try std.testing.expect(@as(u8, DiagnosticSeverity.hint) == 0); +} + +test "lsp_diagnostic_create_valid" { + const r = range_create(0, 0, 0, 10); + const diag = diagnostic_create(r, .error, "test message", "t27"); + try std.testing.expect(diag.severity == .error); +} + +test "lsp_completion_item_create" { + const item = completion_item_create("test", .function, "test()"); + try std.testing.expectEqual(@as(usize, item.label.len), @as(usize, 4)); + try std.testing.expect(item.kind == .function); +} + +test "lsp_inlay_hint_create" { + const pos = position_zero(); + const hint = inlay_hint_create(pos, "type", .type); + try std.testing.expectEqual(@as(usize, hint.label.len), @as(usize, 4)); + try std.testing.expect(hint.kind == .type); +} + +test "lsp_document_symbol_create" { + const r = range_create(0, 0, 10, 10); + const sym = document_symbol_create("test_func", .function, r); + try std.testing.expectEqual(@as(usize, sym.name.len), @as(usize, 9)); + try std.testing.expect(sym.kind == .function); +} + +test "lsp_severity_to_string" { + try std.testing.expectEqual(@as(usize, severity_to_string(.error).len), @as(usize, 5)); + try std.testing.expectEqual(@as(usize, severity_to_string(.hint).len), @as(usize, 4)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant position_non_negative { + // Position line and character are never negative + @compileAssert(@intFromEnum(Position.line) >= 0); + @compileAssert(@intFromEnum(Position.character) >= 0); +} + +invariant range_start_before_end { + // Range start position is before or equal to end + @compileAssert(true); +} + +invariant diagnostic_severity_in_range { + // DiagnosticSeverity is in [0, 3] + @compileAssert(@as(u8, DiagnosticSeverity.error) == 3); + @compileAssert(@as(u8, DiagnosticSeverity.hint) == 0); +} + +invariant completion_item_kind_valid { + // CompletionItemKind is valid enum value + @compileAssert(true); +} + +invariant position_compare_returns_order { + // position_compare returns -1, 0, or 1 + @compileAssert(true); +} + +invariant range_contains_transitive { + // If a contains b and b contains c, then a contains c + @compileAssert(true); +} + +invariant zero_position_is_smallest { + // position_zero is smallest possible position + @compileAssert(true); +} + +invariant diagnostic_has_message { + // All diagnostics have non-empty message + @compileAssert(true); +} + +invariant completion_item_has_label { + // All completion items have non-empty label + @compileAssert(true); +} + +invariant document_symbol_has_name { + // All document symbols have non-empty name + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "lsp_range_contains_position_latency" { + // Measure: cycles for position containment check + // Target: < 50 cycles + @setEvalBranchQuota(10000); + const r = range_create(1, 0, 10, 100); + const pos = Position{ .line = 5, .character = 50 }; + var result : bool = false; + for (0..1000) |_| { + result = range_contains_position(r, pos); + } + _ = result; +} + +bench "lsp_position_compare_latency" { + // Measure: cycles for position comparison + // Target: < 30 cycles + @setEvalBranchQuota(10000); + const a = Position{ .line = 10, .character = 50 }; + const b = Position{ .line = 5, .character = 25 }; + var result : i8 = 0; + for (0..1000) |_| { + result = position_compare(a, b); + } + _ = result; +} + +bench "lsp_diagnostic_create_latency" { + // Measure: cycles for diagnostic creation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + const r = range_create(0, 0, 10, 10); + var result : Diagnostic = undefined; + for (0..1000) |_| { + result = diagnostic_create(r, .error, "test", "t27"); + } + _ = result; +} + +bench "lsp_range_length_latency" { + // Measure: cycles for range length calculation + // Target: < 20 cycles + @setEvalBranchQuota(10000); + const r = range_create(0, 0, 0, 50); + var result : usize = 0; + for (0..1000) |_| { + result = range_length(r); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/lsp/server.t27 b/apps/website/public/t27/specs/lsp/server.t27 new file mode 100644 index 0000000000..2b7fb999a1 --- /dev/null +++ b/apps/website/public/t27/specs/lsp/server.t27 @@ -0,0 +1,554 @@ +// SPDX-License-Identifier: Apache-2.0 +// lsp/server.t27 — LSP Server Lifecycle and Methods +// Server-side protocol initialization, lifecycle, and request handling +// φ² + 1/φ² = 3 | TRINITY + +module lsp-server; + +// ============================================================================ +// Imports +// ============================================================================ + +use lsp-schema::Position; +use lsp-schema::Range; +use lsp-schema::Diagnostic; +use lsp-schema::DiagnosticSeverity; +use lsp-schema::CompletionItem; +use lsp-schema::DocumentSymbol; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default t27 server name +pub const SERVER_NAME : [3]u8 = "t27-lsp"; + +/// Default server version +pub const SERVER_VERSION : [3]u8 = "1.0"; + +/// Maximum file size for diagnostics (in bytes) +pub const MAX_FILE_SIZE : usize = 10485760; + +/// Maximum number of diagnostics per document +pub const MAX_DIAGNOSTICS : usize = 1000; + +/// Default completion trigger characters +pub const DEFAULT_TRIGGER_CHARS : [8]u8 = ".:"; + +/// Request ID counter start +pub const REQUEST_ID_START : usize = 1; + +// ============================================================================ +// Types +// ============================================================================ + +/// Server capabilities +pub const ServerCapabilities = struct { + text_document_sync : usize, + completion_provider : bool, + hover_provider : bool, + signature_help_provider : bool, + definition_provider : bool, + type_definition_provider : bool, + implementation_provider : bool, + references_provider : bool, + document_symbol_provider : bool, + workspace_symbol_provider : bool, + code_action_provider : bool, + code_lens_provider : bool, + document_formatting_provider : bool, + document_highlight_provider : bool, + rename_provider : bool, +}; + +/// Server initialization result +pub const InitializeResult = struct { + capabilities : ServerCapabilities, + server_info : ServerInfo, +}; + +/// Server information +pub const ServerInfo = struct { + name : []u8, + version : []u8, +}; + +/// Server state +pub const ServerState = enum(u8) { + uninitialized = 0, + initializing = 1, + running = 2, + shutting_down = 3, + stopped = 4, +}; + +/// Document state +pub const DocumentState = struct { + uri : []u8, + version : usize, + text : []u8, + language_id : []u8, + diagnostics : []Diagnostic, + symbols : []DocumentSymbol, +}; + +/// Completion context +pub const CompletionContext = struct { + trigger_kind : CompletionTriggerKind, + trigger_character : u8, +}; + +/// Completion trigger kind +pub const CompletionTriggerKind = enum(u8) { + invoked = 0, + trigger_character = 1, + trigger_for_incomplete_completions = 2, +}; + +/// Document symbol options +pub const DocumentSymbolOptions = struct { + label : []u8, +}; + +/// Workspace symbol options +pub const WorkspaceSymbolOptions = struct { + query : []u8, +}; + +/// Code action context +pub const CodeActionContext = struct { + diagnostics : []Diagnostic, + only : []u8, +}; + +/// Rename options +pub const RenameOptions = struct { + prepare_provider : bool, +}; + +/// Prepare rename result +pub const PrepareRenameResult = struct { + range : Range, + placeholder : []u8, +}; + +/// Server error codes +pub const ServerErrorCode = enum(i32) { + parse_error = -32700, + invalid_request = -32600, + method_not_found = -32601, + invalid_params = -32602, + internal_error = -32603, + server_error_start = -32099, + server_not_initialized = -32002, + unknown_error_code = -32001, + request_cancelled = -32800, + content_modified = -32801, +}; + +/// Request error +pub const RequestError = struct { + code : ServerErrorCode, + message : []u8, + data : []u8, +}; + +/// Shutdown response +pub const ShutdownResult = struct { + success : bool, +}; + +/// Exit notification params +pub const ExitParams = struct { + code : i32, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create default server capabilities +pub fn server_capabilities_default() ServerCapabilities { + return ServerCapabilities{ + .text_document_sync = 1, + .completion_provider = true, + .hover_provider = true, + .signature_help_provider = true, + .definition_provider = true, + .type_definition_provider = true, + .implementation_provider = true, + .references_provider = true, + .document_symbol_provider = true, + .workspace_symbol_provider = true, + .code_action_provider = true, + .code_lens_provider = true, + .document_formatting_provider = true, + .document_highlight_provider = true, + .rename_provider = true, + }; +} + +/// Create server info +pub fn server_info_create(name: []u8, version: []u8) ServerInfo { + return ServerInfo{ + .name = name, + .version = version, + }; +} + +/// Create initialize result +pub fn initialize_result_create() InitializeResult { + return InitializeResult{ + .capabilities = server_capabilities_default(), + .server_info = server_info_create(SERVER_NAME, SERVER_VERSION), + }; +} + +/// Create document state +pub fn document_state_create(uri: []u8, version: usize, text: []u8, language_id: []u8) DocumentState { + return DocumentState{ + .uri = uri, + .version = version, + .text = text, + .language_id = language_id, + .diagnostics = &[_]Diagnostic{}, + .symbols = &[_]DocumentSymbol{}, + }; +} + +/// Create completion context +pub fn completion_context_create(trigger: CompletionTriggerKind) CompletionContext { + return CompletionContext{ + .trigger_kind = trigger, + .trigger_character = 0, + }; +} + +/// Create code action context +pub fn code_action_context_create(diagnostics: []Diagnostic) CodeActionContext { + return CodeActionContext{ + .diagnostics = diagnostics, + .only = "", + }; +} + +/// Create rename options +pub fn rename_options_create() RenameOptions { + return RenameOptions{ + .prepare_provider = true, + }; +} + +/// Create request error +pub fn request_error_create(code: ServerErrorCode, message: []u8) RequestError { + return RequestError{ + .code = code, + .message = message, + .data = "", + }; +} + +/// Create shutdown result +pub fn shutdown_result_create(success: bool) ShutdownResult { + return ShutdownResult{ + .success = success, + }; +} + +/// Create exit params +pub fn exit_params_create(code: i32) ExitParams { + return ExitParams{ + .code = code, + }; +} + +/// Check if server is initialized +pub fn server_is_initialized(state: ServerState) bool { + return state == .running; +} + +/// Check if server can accept requests +pub fn server_can_accept_requests(state: ServerState) bool { + return state == .running; +} + +/// Get next request ID +pub fn next_request_id(current: usize) usize { + return current + 1; +} + +/// Check if diagnostics count exceeds maximum +pub fn diagnostics_exceeds_max(count: usize) bool { + return count > MAX_DIAGNOSTICS; +} + +/// Check if file size is too large +pub fn file_size_exceeds_max(size: usize) bool { + return size > MAX_FILE_SIZE; +} + +/// Create prepare rename result +pub fn prepare_rename_result_create(range: Range, placeholder: []u8) PrepareRenameResult { + return PrepareRenameResult{ + .range = range, + .placeholder = placeholder, + }; +} + +/// Get server state string +pub fn server_state_to_string(state: ServerState) []u8 { + return switch (state) { + .uninitialized => "uninitialized", + .initializing => "initializing", + .running => "running", + .shutting_down => "shutting_down", + .stopped => "stopped", + }; +} + +/// Check error code is request cancelled +pub fn error_is_request_cancelled(err: RequestError) bool { + return err.code == .request_cancelled; +} + +/// Check error code is content modified +pub fn error_is_content_modified(err: RequestError) bool { + return err.code == .content_modified; +} + +/// Get error code string +pub fn error_code_to_string(code: ServerErrorCode) []u8 { + return switch (code) { + .parse_error => "ParseError", + .invalid_request => "InvalidRequest", + .method_not_found => "MethodNotFound", + .invalid_params => "InvalidParams", + .internal_error => "InternalError", + .server_error_start => "ServerErrorStart", + .server_not_initialized => "ServerNotInitialized", + .unknown_error_code => "UnknownErrorCode", + .request_cancelled => "RequestCancelled", + .content_modified => "ContentModified", + }; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "lsp_server_capabilities_default" { + const caps = server_capabilities_default(); + try std.testing.expect(caps.completion_provider == true); + try std.testing.expect(caps.hover_provider == true); +} + +test "lsp_server_info_create" { + const info = server_info_create("test", "1.0"); + try std.testing.expectEqual(@as(usize, info.name.len), @as(usize, 4)); +} + +test "lsp_initialize_result_create" { + const result = initialize_result_create(); + try std.testing.expect(result.capabilities.completion_provider == true); +} + +test "lsp_document_state_create" { + const state = document_state_create("test://uri", 1, "content", "t27"); + try std.testing.expectEqual(@as(usize, state.uri.len), @as(usize, 8)); +} + +test "lsp_completion_context_create" { + const ctx = completion_context_create(.invoked); + try std.testing.expect(ctx.trigger_kind == .invoked); +} + +test "lsp_code_action_context_create" { + const diagnostics = &[_]Diagnostic{}; + const ctx = code_action_context_create(diagnostics); + try std.testing.expect(ctx.diagnostics == diagnostics); +} + +test "lsp_rename_options_create" { + const opts = rename_options_create(); + try std.testing.expect(opts.prepare_provider == true); +} + +test "lsp_request_error_create" { + const err = request_error_create(.invalid_params, "bad params"); + try std.testing.expect(err.code == .invalid_params); +} + +test "lsp_shutdown_result_create" { + const result = shutdown_result_create(true); + try std.testing.expect(result.success == true); +} + +test "lsp_exit_params_create" { + const params = exit_params_create(0); + try std.testing.expect(params.code == 0); +} + +test "lsp_server_is_initialized" { + try std.testing.expect(!server_is_initialized(.uninitialized)); + try std.testing.expect(server_is_initialized(.running)); +} + +test "lsp_server_can_accept_requests" { + try std.testing.expect(!server_can_accept_requests(.initializing)); + try std.testing.expect(server_can_accept_requests(.running)); +} + +test "lsp_next_request_id" { + const id1 = next_request_id(REQUEST_ID_START); + const id2 = next_request_id(id1); + try std.testing.expect(id2 > id1); +} + +test "lsp_diagnostics_exceeds_max" { + try std.testing.expect(!diagnostics_exceeds_max(MAX_DIAGNOSTICS)); + try std.testing.expect(diagnostics_exceeds_max(MAX_DIAGNOSTICS + 1)); +} + +test "lsp_file_size_exceeds_max" { + try std.testing.expect(!file_size_exceeds_max(MAX_FILE_SIZE)); + try std.testing.expect(file_size_exceeds_max(MAX_FILE_SIZE + 1)); +} + +test "lsp_server_state_to_string" { + const str = server_state_to_string(.running); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 6)); +} + +test "lsp_error_is_request_cancelled" { + const err = request_error_create(.request_cancelled, "cancelled"); + try std.testing.expect(error_is_request_cancelled(err)); +} + +test "lsp_error_is_content_modified" { + const err = request_error_create(.content_modified, "modified"); + try std.testing.expect(error_is_content_modified(err)); +} + +test "lsp_error_code_to_string" { + const str = error_code_to_string(.invalid_params); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 12)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant server_capabilities_valid { + // ServerCapabilities has at least text_document_sync + @compileAssert(true); +} + +invariant server_state_in_range { + // ServerState is in [0, 4] + @compileAssert(@as(u8, ServerState.uninitialized) == 0); + @compileAssert(@as(u8, ServerState.stopped) == 4); +} + +invariant max_diagnostics_positive { + // MAX_DIAGNOSTICS is positive + @compileAssert(MAX_DIAGNOSTICS > 0); +} + +invariant max_file_size_positive { + // MAX_FILE_SIZE is positive + @compileAssert(MAX_FILE_SIZE > 0); +} + +invariant request_id_increases { + // next_request_id returns value greater than input + @compileAssert(true); +} + +invariant server_info_has_name { + // ServerInfo has non-empty name + @compileAssert(true); +} + +invariant document_state_has_uri { + // DocumentState has non-empty URI + @compileAssert(true); +} + +invariant completion_trigger_kind_valid { + // CompletionTriggerKind is valid enum value + @compileAssert(true); +} + +invariant error_code_in_range { + // ServerErrorCode is in expected range + @compileAssert(true); +} + +invariant request_error_has_code { + // RequestError has valid error code + @compileAssert(true); +} + +invariant shutdown_result_valid { + // ShutdownResult has valid success flag + @compileAssert(true); +} + +invariant exit_params_has_code { + // ExitParams has valid exit code + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "lsp_server_capabilities_default_latency" { + // Measure: cycles for server capabilities creation + // Target: < 200 cycles + @setEvalBranchQuota(10000); + var result : ServerCapabilities = undefined; + for (0..1000) |_| { + result = server_capabilities_default(); + } +} + +bench "lsp_initialize_result_create_latency" { + // Measure: cycles for initialize result creation + // Target: < 300 cycles + @setEvalBranchQuota(10000); + var result : InitializeResult = undefined; + for (0..1000) |_| { + result = initialize_result_create(); + } +} + +bench "lsp_document_state_create_latency" { + // Measure: cycles for document state creation + // Target: < 500 cycles + @setEvalBranchQuota(10000); + var result : DocumentState = undefined; + for (0..1000) |_| { + result = document_state_create("test://uri", 1, "content", "t27"); + } +} + +bench "lsp_completion_context_create_latency" { + // Measure: cycles for completion context creation + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : CompletionContext = undefined; + for (0..1000) |_| { + result = completion_context_create(.invoked); + } +} + +bench "lsp_server_state_to_string_latency" { + // Measure: cycles for state to string conversion + // Target: < 20 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = server_state_to_string(.running); + } +} diff --git a/apps/website/public/t27/specs/math/constants.t27 b/apps/website/public/t27/specs/math/constants.t27 new file mode 100644 index 0000000000..4fa3a56d64 --- /dev/null +++ b/apps/website/public/t27/specs/math/constants.t27 @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/math/constants.t27 +// Mathematical Constants for Trinity Computing +// phi^2 + 1/phi^2 = 3 | Sacred constants for ternary computing + +module Constants { + // ================================================================= + // 1. Sacred Constants -- phi, TRINITY, CODATA measurements + // ===================================================================================== + + // phi (phi) = (1 + sqrt(5)) / 2 ~= 1.61803398875 -- the golden ratio + // phi^-^1 = phi - 1 ~= 0.61803398875 -- the inverse golden ratio + // Sacred Identity: phi^2 + 1/phi^2 = 3 + // Computed value: phi^2 ~= 2.61803398875 + // 1/phi^2 ~= 0.38196601125 + // phi^2 + 1/phi^2 = 3.000000 (exact within floating precision) + const PHI : f64 = 1.61803398874989484820458683436563811772; + const PHI_INV : f64 = 0.61803398874989484820458683436563811772; + const PHI_SQ : f64 = PHI * PHI; + const PHI_INV_SQ : f64 = PHI_INV * PHI_INV; + + // GF8 phi_distance invariant (exp/mant = 3/4 = 0.75) + const PHI_DISTANCE : f64 = 0.132; + + // TRINITY = 3.0 within numeric tolerance + const TRINITY : f64 = 3.0; + + // pi (pi) ~= 3.14159265359 + const PI : f64 = 3.14159265358979323846264338327950288; + + // e (Euler's number) ~= 2.71828182846 + const E : f64 = 2.7182818284590452353602874713526625; + + // CODATA: G_measured = 6.67430767*10^-^1^1 m^3*kg^-^1*s^-^2 (SI units) + // Reference: Planck 2018/2020 (https://ui.adsabs.harvard.edu/energy.html) + // Scale factors derived from CODATA measurements: + // G_scale = G / G_measured ~= 1.0001 (SI normalization) + // Omega_scale = Omega_Lambda / (Omega_Lambda_computed * phi^-^2) ~= 0.9995 + // ============================================================================= + // 2. CODATA 2022 Measurements -- sacred_gravity(), sacred_dark_energy() reference + // ===================================================================================== + + // Gravitational constant G (measured) + // G = 6.67430 * 10^-11 m^3 kg^-^1 s^-^2 + const G_MEASURED : f64 = 6.67430e-11; + + // Cosmological constant Lambda (dimensional) + // Lambda ~= 1.1056 * 10^-52 m^-^2 + const LAMBDA_COSMO : f64 = 1.1056e-52; + + // Dark energy density parameter Omega_Lambda (dimensionless) + // Omega_Lambda ~= 0.685 (Planck 2018/2020) + const OMEGA_LAMBDA_MEASURED : f64 = 0.685; + + // Scale factors for sacred formulas + // G_SCALE = G / G_measured ~= 1.0001 (SI normalization) + // OMEGA_COARSE_SCALE = Omega_Lambda_measured / Omega_Lambda_raw ~= 728.9 (measured/raw ratio) + // OMEGA_FINE_SCALE = Omega_Lambda_measured / (Omega_Lambda_computed * phi^-^2) ~= 0.9995 (from comment) + const G_SCALE : f64 = 1.0001; + const OMEGA_COARSE_SCALE : f64 = 728.9; + + // =========================================================================== + // 3. Helper Functions + // ===================================================================================== + + // Absolute value + fn abs(x: f64) -> f64 { + if (x < 0.0) { + return -x; + } + return x; + } + + // Power function (for simple integer exponents) + fn pow(x: f64, n: f64) -> f64 { + // Handle negative base with fractional exponent -> NaN + if x < 0.0 and n != floor(n) { + return std.math.nan(f64); // NaN + } + + // Handle zero base + if x == 0.0 { + if n > 0.0 { + return 0.0; + } else if n == 0.0 { + return 1.0; // 0^0 defined as 1 in this context + } + return std.math.inf(f64); // Infinity (division by zero) + } + + // Handle n = 0 + if n == 0.0 { + return 1.0; + } + + // Handle negative exponent: x^(-n) = 1/(x^n) + const negative = n < 0.0; + const exp = if (negative) -n else n; + + // Check if exponent is an integer + const is_integer = exp == floor(exp); + + if is_integer { + // Integer exponent: use binary exponentiation + const exp_int: i64 = @intFromFloat(exp); + var result: f64 = 1.0; + var base = x; + var e = exp_int; + + while e > 0 { + if @rem(e, 2) == 1 { + result = result * base; + } + base = base * base; + e = @divTrunc(e, 2); + } + + if negative { + result = 1.0 / result; + } + return result; + } + + // Fractional exponent: use logarithm approximation + // x^y = exp(y * ln(x)) + // This is a simplified version for spec purposes + const ln_x = ln_approx(x); + var result = exp_approx(exp * ln_x); + + if negative { + result = 1.0 / result; + } + return result; + } + + // Natural logarithm approximation (for power function) + fn ln_approx(x: f64) -> f64 { + // Handle edge cases + if x <= 0.0 { + return std.math.nan(f64); // NaN for non-positive + } + if x == 1.0 { + return 0.0; + } + + // Use series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + // For x > 0, compute (x-1)/(x+1) and use convergence + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation (for power function) + fn exp_approx(x: f64) -> f64 { + // Handle edge cases + if x == 0.0 { + return 1.0; + } + + // Use Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + // For small x (|x| < 1), series converges quickly + var result: f64 = 1.0; + var term: f64 = 1.0; + + // For better range, use x/2^k approach + var exp_x = x; + var scale: f64 = 1.0; + if x > 10.0 { + const k: i64 = @intFromFloat(floor(x / 10.0)); + exp_x = x - @as(f64, @floatFromInt(k)) * 10.0; + scale = pow(E, @as(f64, @floatFromInt(k)) * 10.0); + } else if x < -10.0 { + const k: i64 = @intFromFloat(floor(-x / 10.0)); + exp_x = x + @as(f64, @floatFromInt(k)) * 10.0; + scale = 1.0 / pow(E, @as(f64, @floatFromInt(k)) * 10.0); + } + + // Taylor series (10 terms sufficient for reasonable accuracy) + for (1..11) |i| { + term = term * exp_x / @as(f64, @floatFromInt(i)); + result = result + term; + } + + return result * scale; + } + + // Floor function (helper for power) + fn floor(x: f64) -> f64 { + const xi: i64 = @intFromFloat(x); + if x >= 0.0 || x == @as(f64, @floatFromInt(xi)) { + return @as(f64, @floatFromInt(xi)); + } + return @as(f64, @floatFromInt(xi - 1)); + } + + // Default tolerance for approximately_equal/2. + // + // ABSOLUTE, not relative. The corpus already writes a third argument at 18 + // call sites and every one of them only makes sense as an absolute bound: + // ml/transformer/feed_forward passes 1.0 against a value near 828, and + // ml/transformer/positional_enc passes 1e-6 against 5000.0. Reading the + // third argument as a relative factor would turn those into a 100% window + // and a 5e-3 window respectively. One name must mean one thing, so the + // default is absolute too. + // + // 1e-9 and not another value, from the magnitudes actually compared: + // - Smallest 2-argument target in the corpus is 1e-6 (a learning rate; + // ml/optimizer/lr_scheduler compares against 1e-6 and against + // config.min_lr). At 1e-9 that check still rejects anything wrong by + // more than 0.1%, so it is not vacuous. A 1e-6 default would have made + // those three tests accept zero, which is the failure mode that makes a + // tolerance worse than none. + // - Largest 2-argument target is 50.0 (ml/transformer/positional_enc). + // One f64 ulp there is 7.1e-15, so 1e-9 leaves ~140,000 ulp of headroom + // -- far more than the handful of roundings in these expressions needs. + // - It is exactly the tightest tolerance the corpus asks for by hand + // (1e-9 at the phi identities, PHI*PHI == PHI + 1.0). So dropping the + // third argument never loosens a comparison below what some author here + // has already declared acceptable. + // + // At zero this reads as "|a| <= 1e-9", i.e. zero to within a nanounit. That + // is deliberate: nine 2-argument call sites compare against a literal 0.0 + // (ml/transformer/norm beta[0], ml/optimizer/sgd_momentum velocities), and a + // relative rule divides by zero there and would reject every value except an + // exact bit-identical 0.0. Absolute is the only rule that is defined at 0.0. + // + // To change it, change it here: every unqualified comparison in the ml/ + // specs moves with it. + pub const APPROX_TOLERANCE : f64 = 1e-9; + + // approximately_equal(a, b) -> a and b agree to within APPROX_TOLERANCE. + // Absolute: |a - b| <= APPROX_TOLERANCE. NaN on either side is never equal, + // because every comparison with NaN is false and <= is no exception. + pub fn approximately_equal(a: f64, b: f64) -> bool { + return abs(a - b) <= APPROX_TOLERANCE; + } + + // Same rule with the bound supplied by the caller, for the sites that + // already know what precision they can hold. Zig has neither default + // arguments nor overloading, so the two arities need two names. + pub fn approximately_equal_within(a: f64, b: f64, tolerance: f64) -> bool { + return abs(a - b) <= tolerance; + } + + // ============================================================================= + // TDD-Inside-Spec: Tests and Invariants for Sacred Physics Constants + // ========================================================================================= + + test phi_squared_plus_inverse_squared_equals_3 + given phi = PHI + and phi_sq = PHI * PHI + and phi_inv_sq = PHI_INV * PHI_INV + when sum = phi_sq + phi_inv_sq + then abs(sum - TRINITY) < 1e-12 + + test phi_inverse_is_phi_minus_one + given phi = PHI + and expected = PHI - 1.0 + when actual = PHI_INV + then abs(actual - expected) < 1e-15 + + test phi_multiplicative_persistence + given phi = PHI + when squared = phi * phi + and result = squared - phi + then abs(result - 1.0) < 1e-12 + + test trinity_constant_accuracy + given trinity = TRINITY + when is_three = abs(trinity - 3.0) < 1e-15 + then is_three == true + + test pi_range_validity + given pi = PI + when lower_bound = 3.1415926535 + and upper_bound = 3.1415926536 + then pi >= lower_bound and pi <= upper_bound + + test euler_number_range_validity + given e = E + when lower_bound = 2.7182818284 + and upper_bound = 2.7182818285 + then e >= lower_bound and e <= upper_bound + + test pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-15 + + test pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-15 + + test pow_positive_integer_exponent + given result = pow(2.0, 10.0) + and expected = 1024.0 + then abs(result - expected) < 1e-10 + + test pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-10 + + test pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-6 + + test pow_phi_squared + given result = pow(PHI, 2.0) + and expected = PHI * PHI + then abs(result - expected) < 1e-10 + + test pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + and result3 = pow(1.0, 0.5) + then abs(result1 - 1.0) < 1e-15 and abs(result2 - 1.0) < 1e-15 and abs(result3 - 1.0) < 1e-6 + + test floor_function_positive + given result = floor(3.7) + then result == 3.0 + + test floor_function_negative + given result = floor(-3.2) + then result == -4.0 + + test floor_function_integer + given result = floor(5.0) + then result == 5.0 + + test approximately_equal_accepts_exact_equality + given result = approximately_equal(0.1, 0.1) + then result == true + + test approximately_equal_accepts_just_inside_the_bound + given result = approximately_equal(1.0, 1.0 + 5e-10) + then result == true + + test approximately_equal_rejects_just_outside_the_bound + given result = approximately_equal(1.0, 1.0 + 5e-9) + then result == false + + test approximately_equal_is_symmetric_in_its_arguments + given forward = approximately_equal(2.0, 2.0 + 5e-9) + and backward = approximately_equal(2.0 + 5e-9, 2.0) + then forward == backward + + // At zero the rule is |a| <= APPROX_TOLERANCE, not a relative test. + test approximately_equal_at_zero_accepts_a_nanounit + given result = approximately_equal(0.0, 1e-12) + then result == true + + test approximately_equal_at_zero_rejects_a_microunit + given result = approximately_equal(0.0, 1e-6) + then result == false + + // The reason the default is 1e-9 and not 1e-6: ml/optimizer/lr_scheduler + // compares learning rates against 1e-6, and at a 1e-6 tolerance that check + // would accept a learning rate of zero. + test approximately_equal_is_not_vacuous_at_learning_rate_scale + given result = approximately_equal(1e-6, 0.0) + then result == false + + test approximately_equal_holds_at_the_largest_compared_magnitude + given result = approximately_equal(50.0, 50.0 + 1e-12) + then result == true + + test approximately_equal_within_honours_a_looser_bound + given result = approximately_equal_within(828.0, 828.5, 1.0) + then result == true + + test approximately_equal_within_honours_a_tighter_bound + given result = approximately_equal_within(1.0, 1.0 + 1e-10, 1e-12) + then result == false + + invariant phi_squared_plus_inverse_squared_equals_3 + assert |PHI*PHI + 1.0/(PHI*PHI) - 3.0| < 1e-12 + + invariant phi_self_similarity + assert |PHI - (1.0 + 1.0/PHI)| < 1e-15 + + invariant phi_inverse_property + assert |PHI_INV - (PHI - 1.0)| < 1e-15 + + invariant phi_golden_conjugate + assert |PHI - PHI_INV - 1.0| < 1e-12 + + invariant phi_fibonacci_convergence + assert lim(n->inf) Fib(n+1)/Fib(n) = PHI + // Rationale: Ratio of consecutive Fibonacci numbers converges to phi + + invariant trinity_exact + assert TRINITY == 3.0 + + invariant pi_transcendental + assert PI is transcendental + // Rationale: Lindemann-Weierstrass theorem proves pi is transcendental + + invariant euler_irrational + assert E is irrational + // Rationale: e is proven to be irrational (Euler, 1737) + + invariant codata_gravitational_constant + assert G_MEASURED = 6.67430e-11 +/- 1.5e-15 + // Rationale: CODATA 2022 measured value with uncertainty + + invariant cosmological_constant_positive + assert LAMBDA_COSMO > 0 + + invariant dark_energy_dominance + assert OMEGA_LAMBDA_MEASURED > 0.5 and OMEGA_LAMBDA_MEASURED < 1.0 + + invariant pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all valid x + + invariant pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant pow_multiply_exponents + assert pow(pow(x, a), b) == pow(x, a * b) for positive x, integer a, b + + invariant pow_product_rule + assert pow(x * y, n) == pow(x, n) * pow(y, n) for positive x, y, integer n + + invariant floor_returns_integer + assert floor(x) == i64 for all f64 x + + invariant floor_monotonic + assert floor(x) <= floor(y) when x <= y + + bench phi_computation_cost + measure: cycles per PHI computation + target: < 10 cycles (constant-time lookup) + + bench pow_integer_exponent + measure: cycles to compute pow(2.0, 10.0) + target: < 500 cycles + + bench pow_fractional_exponent + measure: cycles to compute pow(4.0, 0.5) + target: < 1000 cycles + + bench floor_latency + measure: cycles to compute floor(3.7) + target: < 50 cycles + + bench trinity_verification_cost + measure: cycles to verify PHI^2 + 1/PHI^2 = 3 + target: < 100 cycles +} diff --git a/apps/website/public/t27/specs/math/e8_lie_algebra.t27 b/apps/website/public/t27/specs/math/e8_lie_algebra.t27 new file mode 100644 index 0000000000..74698d0b56 --- /dev/null +++ b/apps/website/public/t27/specs/math/e8_lie_algebra.t27 @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/math/e8_lie_algebra.t27 +// E8 Exceptional Lie Algebra -- Root System, Cartan Matrix, Eigenvalues +// Direction A (Priority 2) of PROJECT KEPLER->NEWTON +// +// E8 is the largest exceptional simple Lie group. Its root system contains +// golden ratio phi as a structural invariant through the H4 Coxeter subgroup. +// +// Key results verified computationally: +// 1. dim(E8) = 248 = 8 (rank) + 240 (roots) +// 2. E9 (affine E8) Cartan matrix has 9 eigenvalues: +// {0, phi^{-2}, 1, 3-phi, 2, phi^2, 3, phi^2+1, 4} +// Four contain phi via cyclotomic field Q(sqrt(5)) +// 3. phi^{-2} + phi^2 = 3 directly from the spectrum (TRINITY) +// 4. Dechant (2016): E8 roots decompose as H4 + phi*H4 +// 5. McKay correspondence: binary icosahedral group 2I <-> affine E8 +// +// References: +// - Dechant, Proc. Roy. Soc. A 472 (2016) -- E8 from icosahedral spinors +// - Aschheim, Minkowski Inst. Press (2017) -- E9 eigenvalues contain phi^2 +// - Humphreys, "Introduction to Lie Algebras" -- standard reference +// - Kac, "Infinite-Dimensional Lie Algebras" -- affine E8 + +module E8LieAlgebra { + use math::constants; + + // ========================================================================= + // 1. E8 Structure Constants + // ========================================================================= + + const E8_RANK : i64 = 8; + const E8_DIM : i64 = 248; + const E8_NUM_ROOTS : i64 = 240; + const E8_COXETER : i64 = 30; + + // E8 exponents: {1, 7, 11, 13, 17, 19, 23, 29} + // These appear in the Zamolodchikov mass spectrum + const E8_EXPONENTS : [8]i64 = [1, 7, 11, 13, 17, 19, 23, 29]; + + // E8 marks (highest root coefficients): {2, 3, 4, 5, 6, 4, 2, 3} + // Sum = 29 (which is itself an E8 exponent) + const E8_MARKS : [8]i64 = [2, 3, 4, 5, 6, 4, 2, 3]; + + // E8 root norm squared (simply-laced: all roots same length) + const E8_ROOT_NORM_SQ : f64 = 2.0; + + // ========================================================================= + // 2. Root System Types + // ========================================================================= + + struct E8Root { + components: [8]f64; + } + + struct E8RootSystem { + rank: i64; + num_roots: i64; + type1_count: i64; // 112 roots of form (+-1, +-1, 0, ..., 0) + type2_count: i64; // 128 roots of form (+-1/2, ..., +-1/2) even parity + } + + fn root_system_info() -> E8RootSystem { + return E8RootSystem{ + rank = E8_RANK, + num_roots = E8_NUM_ROOTS, + type1_count = 112, + type2_count = 128, + }; + } + + // ========================================================================= + // 3. E8 Cartan Matrix (8x8) + // ========================================================================= + + // Standard Bourbaki labeling: nodes 1-2-3-4-5-6-7, branch at 5->8 + // A_ij = 2(alpha_i . alpha_j) / (alpha_j . alpha_j) + fn e8_cartan_matrix() -> [[f64; 8]; 8] { + return [ + [ 2, -1, 0, 0, 0, 0, 0, 0], + [-1, 2, -1, 0, 0, 0, 0, 0], + [ 0, -1, 2, -1, 0, 0, 0, 0], + [ 0, 0, -1, 2, -1, 0, 0, 0], + [ 0, 0, 0, -1, 2, -1, 0, -1], + [ 0, 0, 0, 0, -1, 2, -1, 0], + [ 0, 0, 0, 0, 0, -1, 2, 0], + [ 0, 0, 0, 0, -1, 0, 0, 2], + ]; + } + + // E8 Cartan matrix inverse (all integer entries) + // First row = marks = highest root coefficients + fn e8_cartan_inverse() -> [[f64; 8]; 8] { + return [ + [ 2, 3, 4, 5, 6, 4, 2, 3], + [ 3, 6, 8, 10, 12, 8, 4, 6], + [ 4, 8, 12, 15, 18, 12, 6, 9], + [ 5, 10, 15, 20, 24, 16, 8, 12], + [ 6, 12, 18, 24, 30, 20, 10, 15], + [ 4, 8, 12, 16, 20, 14, 7, 10], + [ 2, 4, 6, 8, 10, 7, 4, 5], + [ 3, 6, 9, 12, 15, 10, 5, 8], + ]; + } + + // ========================================================================= + // 4. E9 (Affine E8) Cartan Matrix and Eigenvalues + // ========================================================================= + + // Affine E8 = E8^(1) in Kac notation + // 9x9 Cartan matrix with null vector delta = [1,2,3,4,5,6,4,2,3] + fn e9_cartan_matrix() -> [[f64; 9]; 9] { + return [ + [ 2, -1, 0, 0, 0, 0, 0, 0, 0], + [-1, 2, -1, 0, 0, 0, 0, 0, 0], + [ 0, -1, 2, -1, 0, 0, 0, 0, 0], + [ 0, 0, -1, 2, -1, 0, 0, 0, 0], + [ 0, 0, 0, -1, 2, -1, 0, 0, 0], + [ 0, 0, 0, 0, -1, 2, -1, 0, -1], + [ 0, 0, 0, 0, 0, -1, 2, -1, 0], + [ 0, 0, 0, 0, 0, 0, -1, 2, 0], + [ 0, 0, 0, 0, 0, -1, 0, 0, 2], + ]; + } + + // Null vector of E9 Cartan matrix: A * delta = 0 + const E9_NULL_VECTOR : [9]i64 = [1, 2, 3, 4, 5, 6, 4, 2, 3]; + + // E9 eigenvalues (verified computationally): + // Characteristic polynomial factors exactly as: + // P(x) = x(x-1)(x-2)(x-3)(x-4)(x^2-3x+1)(x^2-5x+5) + // + // The quadratic x^2 - 3x + 1 = 0 gives phi^2 and phi^{-2} + // The quadratic x^2 - 5x + 5 = 0 gives phi^2+1 and 3-phi + // Both have discriminant 5 (from McKay correspondence / icosahedral group) + struct E9Eigenvalues { + values: [9]f64; + phi_related_count: i64; + } + + fn e9_eigenvalues() -> E9Eigenvalues { + const PHI = constants::PHI; + const PHI_SQ = PHI * PHI; + const PHI_INV_SQ = 1.0 / PHI_SQ; + + return E9Eigenvalues{ + values = [ + 0.0, // null vector eigenvalue + PHI_INV_SQ, // phi^{-2} = (3-sqrt(5))/2 ~ 0.382 + 1.0, + 3.0 - PHI, // (5-sqrt(5))/2 ~ 1.382 + 2.0, + PHI_SQ, // phi^2 = (3+sqrt(5))/2 ~ 2.618 + 3.0, + PHI_SQ + 1.0, // (5+sqrt(5))/2 ~ 3.618 + 4.0, + ], + phi_related_count = 4, + }; + } + + // TRINITY from E9 spectrum: phi^{-2} + phi^2 = 3 + fn trinity_from_e9_spectrum() -> f64 { + const ev = e9_eigenvalues(); + // ev.values[1] = phi^{-2}, ev.values[5] = phi^2 + return ev.values[1] + ev.values[5]; + } + + // ========================================================================= + // 5. H4 Coxeter Subgroup (Source of phi in E8) + // ========================================================================= + + // Dechant (2016) proved: W(H4) is a subgroup of W(E8) + // Composite generators: s_{a1} = s_{alpha1}*s_{alpha7}, etc. + // E8 roots decompose as: Phi(E8) = H4 + phi*H4 + // Two copies of the 600-cell, scaled by phi + + // H4 exponents: {1, 11, 19, 29} -- subset of E8 exponents + const H4_EXPONENTS : [4]i64 = [1, 11, 19, 29]; + + // phi appears because cos(pi/5) = phi/2 connects 5-fold rotation to phi + fn phi_from_h4() -> f64 { + return 2.0 * cos(constants::PI / 5.0); + } + + // ========================================================================= + // 6. Perron-Frobenius Eigenvector + // ========================================================================= + + // The PF eigenvector of the E8 adjacency matrix (2I - Cartan) + // normalized to v[0] = 1, equals the Zamolodchikov mass spectrum + // (same numbers, different ordering due to Dynkin diagram labeling) + // + // PF = [1.000, 1.989, 2.956, 3.891, 4.783, 3.218, 1.618, 2.405] + // Contains phi = 1.618 as component v[6] + + fn pf_eigenvector_normalized() -> [8]f64 { + // These are the Zamolodchikov masses in Dynkin diagram order + return [ + 1.0, + 1.9890437907, + 2.9562952015, + 3.8911568233, + 4.7833861168, + 3.2183404585, + 1.6180339887, // = phi EXACTLY + 2.4048671724, + ]; + } + + // ========================================================================= + // 7. Utility functions + // ========================================================================= + + fn cos(x: f64) -> f64 { + let result = 0.0; + let term = 1.0; + let sign = 1.0; + let n = 0; + while n < 20 { + result = result + sign * term; + term = term * x * x / ((2 * n + 1) as f64 * (2 * n + 2) as f64); + sign = -sign; + n = n + 1; + } + return result; + } + + fn abs(x: f64) -> f64 { + if x < 0.0 { return -x; } + return x; + } + + // ========================================================================= + // TDD-Inside-Spec: Tests + // ========================================================================= + + // --- Dimension Tests --- + + test e8_dimension_is_248 + then E8_DIM == 248 + + test e8_rank_is_8 + then E8_RANK == 8 + + test e8_num_roots_is_240 + then E8_NUM_ROOTS == 240 + + test e8_roots_split_112_plus_128 + given info = root_system_info() + then info.type1_count + info.type2_count == 240 + + test e8_coxeter_number_is_30 + then E8_COXETER == 30 + + // --- E8 Marks Tests --- + + test e8_marks_sum_is_29 + given sum = E8_MARKS[0] + E8_MARKS[1] + E8_MARKS[2] + E8_MARKS[3] + E8_MARKS[4] + E8_MARKS[5] + E8_MARKS[6] + E8_MARKS[7] + then sum == 29 + + test e8_marks_29_is_exponent + then E8_EXPONENTS[7] == 29 + + // --- Cartan Matrix Tests --- + + test e8_cartan_diagonal_is_2 + given C = e8_cartan_matrix() + then C[0][0] == 2 and C[1][1] == 2 and C[7][7] == 2 + + test e8_cartan_is_symmetric + given C = e8_cartan_matrix() + then C[0][1] == C[1][0] + and C[4][5] == C[5][4] + and C[4][7] == C[7][4] + + test e8_cartan_inverse_first_row_is_marks + given Cinv = e8_cartan_inverse() + then Cinv[0][0] == 2 and Cinv[0][1] == 3 and Cinv[0][2] == 4 + and Cinv[0][3] == 5 and Cinv[0][4] == 6 and Cinv[0][5] == 4 + and Cinv[0][6] == 2 and Cinv[0][7] == 3 + + // --- E9 Eigenvalue Tests (Aschheim theorem) --- + + test e9_has_zero_eigenvalue + given ev = e9_eigenvalues() + then abs(ev.values[0]) < 1.0e-15 + + test e9_has_phi_squared_eigenvalue + given ev = e9_eigenvalues() + and phi_sq = constants::PHI * constants::PHI + then abs(ev.values[5] - phi_sq) < 1.0e-14 + + test e9_has_phi_inv_squared_eigenvalue + given ev = e9_eigenvalues() + and phi_inv_sq = 1.0 / (constants::PHI * constants::PHI) + then abs(ev.values[1] - phi_inv_sq) < 1.0e-14 + + test e9_four_phi_related_eigenvalues + given ev = e9_eigenvalues() + then ev.phi_related_count == 4 + + test e9_trinity_from_spectrum + given trinity = trinity_from_e9_spectrum() + then abs(trinity - 3.0) < 1.0e-14 + + test e9_null_vector_valid + // delta = [1,2,3,4,5,6,4,2,3], A*delta should = 0 + given delta_sum = E9_NULL_VECTOR[0] + E9_NULL_VECTOR[1] + E9_NULL_VECTOR[2] + E9_NULL_VECTOR[3] + E9_NULL_VECTOR[4] + E9_NULL_VECTOR[5] + E9_NULL_VECTOR[6] + E9_NULL_VECTOR[7] + E9_NULL_VECTOR[8] + then delta_sum == 30 + + test e9_characteristic_poly_quadratic_1 + // x^2 - 3x + 1 = 0 has roots phi^2 and phi^{-2} + given phi_sq = constants::PHI * constants::PHI + and residual = phi_sq * phi_sq - 3.0 * phi_sq + 1.0 + then abs(residual) < 1.0e-14 + + test e9_characteristic_poly_quadratic_2 + // x^2 - 5x + 5 = 0 has roots phi^2+1 and 3-phi + given val = constants::PHI * constants::PHI + 1.0 + and residual = val * val - 5.0 * val + 5.0 + then abs(residual) < 1.0e-14 + + // --- H4 Subgroup Tests --- + + test h4_exponents_subset_of_e8 + then H4_EXPONENTS[0] == E8_EXPONENTS[0] + and H4_EXPONENTS[1] == E8_EXPONENTS[2] + and H4_EXPONENTS[2] == E8_EXPONENTS[5] + and H4_EXPONENTS[3] == E8_EXPONENTS[7] + + test phi_from_h4_equals_golden_ratio + given phi_h4 = phi_from_h4() + then abs(phi_h4 - constants::PHI) < 1.0e-10 + + // --- Perron-Frobenius Tests --- + + test pf_contains_phi + given pf = pf_eigenvector_normalized() + then abs(pf[6] - constants::PHI) < 1.0e-10 + + test pf_first_component_is_1 + given pf = pf_eigenvector_normalized() + then abs(pf[0] - 1.0) < 1.0e-15 + + // ========================================================================= + // TDD-Inside-Spec: Invariants + // ========================================================================= + + invariant e8_dim_equals_rank_plus_roots + assert E8_DIM == E8_RANK + E8_NUM_ROOTS + + invariant e8_roots_count_correct + assert E8_NUM_ROOTS == 240 + + invariant e8_simply_laced + assert E8_ROOT_NORM_SQ == 2.0 + + invariant e9_has_exactly_4_phi_eigenvalues + assert e9_eigenvalues().phi_related_count == 4 + + invariant trinity_from_e9_exact + assert abs(trinity_from_e9_spectrum() - 3.0) < 1.0e-14 + + invariant h4_is_subset_of_e8_exponents + assert H4_EXPONENTS[0] == 1 and H4_EXPONENTS[1] == 11 + and H4_EXPONENTS[2] == 19 and H4_EXPONENTS[3] == 29 + + invariant pf_vector_contains_phi + assert abs(pf_eigenvector_normalized()[6] - constants::PHI) < 1.0e-10 + + invariant marks_product_is_17280 + assert E8_MARKS[0] * E8_MARKS[1] * E8_MARKS[2] * E8_MARKS[3] * E8_MARKS[4] * E8_MARKS[5] * E8_MARKS[6] * E8_MARKS[7] == 17280 + + // ========================================================================= + // TDD-Inside-Spec: Benchmarks + // ========================================================================= + + bench cartan_matrix_lookup_time + measure: nanoseconds to call e8_cartan_matrix() + target: < 100ns + + bench e9_eigenvalues_computation_time + measure: nanoseconds to call e9_eigenvalues() + target: < 200ns + + bench pf_eigenvector_lookup_time + measure: nanoseconds to call pf_eigenvector_normalized() + target: < 100ns +} diff --git a/apps/website/public/t27/specs/math/gf_competitive.t27 b/apps/website/public/t27/specs/math/gf_competitive.t27 new file mode 100644 index 0000000000..7c67c748ff --- /dev/null +++ b/apps/website/public/t27/specs/math/gf_competitive.t27 @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/math/gf_competitive.t27 +// GoldenFloat Competitive Analysis -- GF vs Posit vs IEEE 754 +// MATH-COMPETITIVE-001 -- Decode latency, parallelism, hardware efficiency +// +// Ring 051: Competitive analysis showing GF's structural advantages +// Main result: GF has O(1) parallel decode vs Posit's O(N) sequential + +module GFCompetitive { + use math::constants; + use math::sacred_physics; + + // =========================================================================== + // 1. Decode Complexity Analysis + // ========================================================================================= + + // Decode operation counts (worst case) + struct DecodeComplexity { + format : string, + steps_sequential : u8, + steps_parallel : u8, + can_parallelize : bool, + } + + // Worst-case decode steps for each format + fn decode_complexity() -> [3]DecodeComplexity { + return [ + DecodeComplexity{ + format = "IEEE_754_FP16", + steps_sequential = 3, // sign, exp, mantissa (fixed position) + steps_parallel = 3, // all fields decodeable in parallel + can_parallelize = true, + }, + DecodeComplexity{ + format = "POSIT16", + steps_sequential = 6, // regime (variable) + sign + exp + mantissa + steps_parallel = 6, + can_parallelize = false, // regime detection is sequential + }, + DecodeComplexity{ + format = "GF16", + steps_sequential = 3, // sign (trit), exp (fixed), mantissa (fixed) + steps_parallel = 3, + can_parallelize = true, // all fields decodeable in parallel + }, + ]; + } + + // =========================================================================== + // Tests + // ========================================================================================= + + test "decode_complexity_returns_3_formats" { + let complexity = decode_complexity(); + assert_eq!(complexity.len(), 3); + } + + test "gf16_can_parallelize" { + let complexity = decode_complexity(); + assert!(complexity[2].can_parallelize); + } + + test "posit_cannot_parallelize" { + let complexity = decode_complexity(); + assert!(!complexity[1].can_parallelize); + } + + test "ieee754_fp16_can_parallelize" { + let complexity = decode_complexity(); + assert!(complexity[0].can_parallelize); + } + + test "gf16_has_minimal_sequential_steps" { + let complexity = decode_complexity(); + assert_eq!(complexity[2].steps_sequential, 3); + } + + test "posit_has_more_sequential_steps" { + let complexity = decode_complexity(); + assert!(complexity[1].steps_sequential > complexity[2].steps_sequential); + } + + // =========================================================================== + // Invariants + // ========================================================================================= + + invariant "decode_complexity_always_returns_3_entries" { + let complexity = decode_complexity(); + assert_eq!(complexity.len(), 3); + } + + invariant "gf16_can_parallelize_is_true" { + let complexity = decode_complexity(); + assert!(complexity[2].can_parallelize); + } + + invariant "posit_has_more_sequential_steps_than_gf16" { + let complexity = decode_complexity(); + assert!(complexity[1].steps_sequential >= complexity[2].steps_sequential); + } + + invariant "all_formats_have_positive_steps" { + let complexity = decode_complexity(); + for c in complexity { + assert!(c.steps_sequential > 0); + assert!(c.steps_parallel > 0); + } + } + + // =========================================================================== + // Benchmarks + // ========================================================================================= + + bench "decode_complexity" { + let iterations = 10000; + for _ in 0..iterations { + let _ = decode_complexity(); + } + } + +} diff --git a/apps/website/public/t27/specs/math/pellis_precision_verify.t27 b/apps/website/public/t27/specs/math/pellis_precision_verify.t27 new file mode 100644 index 0000000000..561f4812e6 --- /dev/null +++ b/apps/website/public/t27/specs/math/pellis_precision_verify.t27 @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/math/pellis_precision_verify.t27 +// Arbitrary precision verification via GMP/MPFR reference +// NUMERIC-VERIF-001 -- Pre-registered checkpoint for CODATA 2026 + +module PellisPrecision { + use math::constants; + use math::sacred_physics; + + // ================================================================= + // 1. Golden Ratio at 100 Decimal Digits (pre-computed via GMP) + // ========================================================================= + + // phi = (1 + sqrt5) / 2 at 100 decimal places + // Computed once via GMP/mpmath, sealed as constant + // Reference: mpmath.mpf(1).sqrt(5) + 1) / 2 at 100 digits precision + const PHI_100DIGITS : string = "1.61803398874989484820458683436563811772030917980576286213544862270526046281890244970720720418939113748475"; + + // phi^-^1 = phi - 1 at 100 decimal places + const PHI_INV_100DIGITS : string = "0.61803398874989484820458683436563811772030917980576286213544862270526046281890244970720720418939113748475"; + + // ================================================================= + // 2. Pellis Closed Form at 50 Decimal Digits (pre-registered checkpoint) + // ========================================================================= + + // Pellis = 360/phi^2 - 2/phi^3 + (3phi)^-^5 + // Pre-registered for CODATA 2026/2030 comparison + // Computed via GMP: 360/mp.mpf(phi**2) - 2/mp.mpf(phi**3) + (3*phi)**(-5) + // 50 digits after the decimal point; refresh: python3 scripts/print_pellis_seal_decimal.py 55 + const PELLIS_50DIGITS : string = "137.03599916476563934505723564140907572836137437744729"; + + // CODATA 2022 reference: 137.035999166(15) + const ALPHA_INV_CODATA_2022 : string = "137.035999166"; + + // f64-style fixed prefix (~=15 significant fractional digits; IEEE rounding) + const PELLIS_FIRST_15_DIGITS : string = "137.035999164765"; + + // ================================================================= + // 3. Verification Tests (string compare against pre-computed values) + // ========================================================================= + + struct PellisPrecisionResult { + phi_f64 : f64, + phi_100digits : string, + pellis_f64 : f64, + pellis_50digits : string, + matches_prefix : bool, + digit_count_f64 : i32, + } + + fn pellis_pre_registered_checkpoint() -> PellisPrecisionResult { + const phi = sacred_physics::PHI; + const phi_sq = phi * phi; + const phi_cubed = phi * phi * phi; + + // Compute Pellis via f64 (NOT 50-digit accurate -- this test documents limit) + const pellis_f64 = 360.0 / phi_sq - 2.0 / phi_cubed + (3.0 * phi).pow(-5.0); + + // Count significant decimal digits of f64 representation + const digit_count = count_significant_digits(pellis_f64); + + // String representation for first 15 digits (f64 limit) + const pellis_str = format(pellis_f64, ".15f"); + const matches = pellis_str.starts_with(PELLIS_FIRST_15_DIGITS); + + return PellisPrecisionResult{ + phi_f64 = phi, + phi_100digits = PHI_100DIGITS, + pellis_f64 = pellis_f64, + pellis_50digits = PELLIS_50DIGITS, + matches_prefix = matches, + digit_count_f64 = digit_count, + }; + } + + // Count significant decimal digits (digits after decimal point that are non-zero) + fn count_significant_digits(x: f64) -> i32 { + if x == 0.0 { + return 0; + } + + // Convert to string and count digits after decimal + const s = format(x, ".15f"); + let count = 0; + let found_decimal = false; + let found_non_zero = false; + + for i in 0..len(s) { + const c = s[i]; + if c == '.' { + found_decimal = true; + } else if found_decimal { + if c != '0' { + found_non_zero = true; + } + if found_non_zero { + count = count + 1; + } + } + } + + return count; + } + + // Format number to string with specified precision + fn format(x: f64, fmt: string) -> string { + // In a real implementation, this would use proper formatting + // For now, approximate with to_string + return x.to_string(); + } + + // ================================================================= + // 4. Helper functions + // ========================================================================= + + fn len(s: string) -> i32 { + // String length stub - implementation dependent + return 100; // Placeholder + } + + // ======================================================================================================= + // TDD-Inside-Spec: Tests and Invariants for Pellis Precision Verification + // ======================================================================================================= + + test pellis_pre_registered_checkpoint_matches + given result = pellis_pre_registered_checkpoint() + then result.matches_prefix == true + + test pellis_f64_within_15_digits + given result = pellis_pre_registered_checkpoint() + and pellis_str = format(result.pellis_f64, ".15f") + then pellis_str.starts_with(PELLIS_FIRST_15_DIGITS) + + test phi_f64_close_to_100digit_reference + given phi = sacred_physics::PHI + and prefix = PHI_100DIGITS.substring(0, 17) // First 17 chars + and phi_str = format(phi, ".15f") + then phi_str.starts_with("1.6180339887498948") + + test pellis_50digits_not_empty + then PELLIS_50DIGITS.length() > 0 + and PELLIS_50DIGITS.starts_with("137.035999") + + test pellis_50digits_close_to_codata_2022 + given pellis_prefix = PELLIS_50DIGITS.substring(0, 13) + and codata_prefix = ALPHA_INV_CODATA_2022.substring(0, 13) + then pellis_prefix == codata_prefix + + test pellis_f64_positive + given result = pellis_pre_registered_checkpoint() + then result.pellis_f64 > 100.0 + and result.pellis_f64 < 200.0 + + test phi_100digits_contains_golden_ratio + then PHI_100DIGITS.contains("1.6180339887498948482045868343656") + + test phi_inv_100digits_valid + then PHI_INV_100DIGITS.starts_with("0.6180339887498948482045868343656") + + test pellis_closed_form_formula_documented + // This test ensures the formula is documented for reviewers + then true // Formula is documented in comments above + + // ================================================================= + // 5. Invariants + // ========================================================================= + + invariant pellis_50_digits_constant + assert PELLIS_50DIGITS.starts_with("137.035999") + + invariant phi_100_digits_constant + assert PHI_100DIGITS.starts_with("1.6180339887498948482045868343656") + + invariant phi_inv_100_digits_constant + assert PHI_INV_100DIGITS.starts_with("0.6180339887498948482045868343656") + + invariant pellis_f64_between_codata_bounds + given result = pellis_pre_registered_checkpoint() + and lower = 137.035999100 + and upper = 137.035999200 + assert result.pellis_f64 > lower and result.pellis_f64 < upper + + invariant codata_2022_positive + assert ALPHA_INV_CODATA_2022.starts_with("137.035999") + + invariant phi_100digits_longer_than_f64 + assert PHI_100DIGITS.length() > 17 // More than f64 precision + + invariant pellis_first_15_digits_extracted_correctly + assert PELLIS_FIRST_15_DIGITS.starts_with("137.035999164") + + // ================================================================= + // 6. Benchmarks + // ========================================================================= + + bench pellis_f64_computation + measure: nanoseconds to compute pellis_pre_registered_checkpoint() + target: < 1000ns + + bench pellis_string_comparison + measure: nanoseconds to compare pellis f64 against 15-digit prefix + target: < 500ns +} diff --git a/apps/website/public/t27/specs/math/phi_split_optimality.t27 b/apps/website/public/t27/specs/math/phi_split_optimality.t27 new file mode 100644 index 0000000000..56de95bc30 --- /dev/null +++ b/apps/website/public/t27/specs/math/phi_split_optimality.t27 @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/math/phi_split_optimality.t27 +// Phi-Split Theorems -- Self-Similarity + Optimal Rounding (CORRECTED) +// MATH-OPTIMALITY-001 -- Foundation for GoldenFloat being non-random +// +// THEOREM 1 (Golden Self-Similarity): phi is unique self-similar proportion for bit allocation +// THEOREM 2 (Optimal Rounding): round((N-1)/phi^2) minimizes phi-distance (7/7 match) + +module PhiSplitOptimality { + use math::constants; + use math::sacred_physics; + + // =================================================================== + // 1. Theorem 1: Golden Self-Similarity + // =================================================================== + + // The golden ratio phi is defined by: phi^2 = phi + 1 + // This gives self-similar property: phi = 1 + 1/phi + // + // For bit allocation, self-similarity means: + // exp/mant = mant/(exp + mant) + // This gives: exp/mant = 1/(exp/mant + 1) + // Solving: (exp/mant)^2 + (exp/mant) - 1 = 0 -> exp/mant = 1/phi + // + // IMPORTANT: This is NOT an optimization problem (maximizing e*m gives r=1 by AM-GM). + // This is a self-similarity constraint -- a defining property of the golden ratio. + + const PHI_TARGET : f64 = sacred_physics::PHI_INV; // 1/phi approx 0.618... + + // =================================================================== + // 2. Analytical Proof: Self-Similarity + // =================================================================== + + struct ProofStep { + description : string, + equation : string, + result : string, + } + + // Self-Similarity Theorem Derivation: + // Given: exp + mant = available (where available = N - 1) + // Let r = exp/mant (ratio of exponent to mantissa bits) + // Self-similarity constraint: r = mant/(exp + mant) = 1/(r + 1) + // Solving: r^2 + r - 1 = 0 + // r = (sqrt(5) - 1)/2 = 1/phi approx 0.618 + // + // This follows directly from phi^2 = phi + 1, the defining property of phi. + // It is NOT an optimization result -- it's a self-similarity property. + + fn optimal_ratio_by_self_similarity(available: u8) -> (u8, u8) { + // Self-similarity constraint: exp/mant = 1/phi + const r = PHI_TARGET; + const m = (available as f64 / (1.0 + r)).round() as u8; + const e = available - m; + return (e, m); + } + + // =================================================================== + // 3. Theorem 2: Optimal Rounding + // =================================================================== + + // The formula exp = round((N-1)/phi^2) selects the integer closest to the + // golden ratio proportion. This minimizes phi-distance between actual and ideal allocation. + // + // Proof: For integer allocation, we choose between floor and ceil of the ideal value. + // The phi-proportion gives exp_ideal = (N-1)/phi^2 (real). + // round() selects floor or ceil that gives minimum |exp_bits/available - 1/phi^2|. + // All 7 GF formats follow this rule exactly (7/7 match verified). + + fn optimal_allocation_by_rounding(total_bits: u8) -> (u8, u8, f64) { + const available = total_bits - 1; + const phi_sq = sacred_physics::PHI * sacred_physics::PHI; + + // exp = round((N-1) / phi^2) + const exp_raw = (available as f64) / phi_sq; + const exp_bits = round(exp_raw) as u8; + const mant_bits = available - exp_bits; + + // Compute phi-distance + const ratio = (exp_bits as f64) / (mant_bits as f64); + const phi_dist = abs(ratio - PHI_TARGET); + + return (exp_bits, mant_bits, phi_dist); + } + + // =================================================================== + // 4. AM-GM Comparison (for reference, NOT the phi derivation) + // =================================================================== + + // By AM-GM inequality, product e * m is maximized when e = m. + // This gives r = 1, NOT r = 1/phi. + // This shows that maximizing e*m does NOT lead to phi. + + fn optimal_ratio_by_am_gm(available: u8) -> (u8, u8) { + // By AM-GM, product e * m is maximized when e = m = available/2 + const half = available as f64 / 2.0; + return (half as u8, half as u8); + } + + fn round(x: f64) -> f64 { + if x < 0.0 { + let xi = x as i64; + let frac = x - (xi as f64); + if frac <= -0.5 { + return (xi - 1) as f64; + } + return xi as f64; + } + let xi = x as i64; + let frac = x - (xi as f64); + if frac >= 0.5 { + return (xi + 1) as f64; + } + return xi as f64; + } + + fn abs(x: f64) -> f64 { + if x < 0.0 { + return -x; + } + return x; + } + + // =================================================================== + // 5. Proof Steps for Documentation + // =================================================================== + + fn self_similarity_proof_steps() -> [4]ProofStep { + return [ + ProofStep{ + description = "Golden ratio identity", + equation = "phi^2 = phi + 1", + result = "Defining property of phi", + }, + ProofStep{ + description = "Self-similarity constraint", + equation = "exp/mant = mant/(exp + mant)", + result = "Bit allocation reflects itself at different scales", + }, + ProofStep{ + description = "Substitution", + equation = "Let r = exp/mant, then r = 1/(r + 1)", + result = "Express constraint in terms of ratio r", + }, + ProofStep{ + description = "Solve for r", + equation = "r^2 + r - 1 = 0 -> r = (sqrt(5) - 1)/2 = 1/phi", + result = "Golden ratio emerges as unique self-similar proportion", + }, + ]; + } + + fn optimal_rounding_proof_steps() -> [3]ProofStep { + return [ + ProofStep{ + description = "Ideal proportion", + equation = "exp_ideal = (N-1)/phi^2", + result = "Continuous value from phi-proportion", + }, + ProofStep{ + description = "Rounding rule", + equation = "exp_bits = round(exp_ideal)", + result = "Select integer minimizing phi-distance", + }, + ProofStep{ + description = "Verification", + equation = "7/7 GF formats match round() exactly", + result = "No deviations - all follow phi-proportion via optimal rounding", + }, + ]; + } + + // =================================================================== + // 6. GF Format Verification (7/7 match) + // =================================================================== + + struct GFFamilyVerification { + format : string, + bits : u8, + exp_bits : u8, + mant_bits : u8, + phi_raw : f64, + phi_rounded : u8, + matches : bool, + } + + fn verify_7_7_match() -> [7]GFFamilyVerification { + const phi_sq = sacred_physics::PHI * sacred_physics::PHI; + + // GF formats with their actual allocations + const formats = [ + ("GF4", 4, 1, 2), + ("GF8", 8, 3, 4), + ("GF12", 12, 4, 7), + ("GF16", 16, 6, 9), + ("GF20", 20, 7, 12), + ("GF24", 24, 9, 14), + ("GF32", 32, 12, 19), + ]; + + let mut results = [7]GFFamilyVerification{}; + + for i in 0..7 { + const (name, bits, exp, mant) = formats[i]; + const available = bits - 1; + const phi_raw = (available as f64) / phi_sq; + const phi_rounded = round(phi_raw) as u8; + + results[i] = GFFamilyVerification{ + format = name, + bits = bits, + exp_bits = exp, + mant_bits = mant, + phi_raw = phi_raw, + phi_rounded = phi_rounded, + matches = exp == phi_rounded, + }; + } + + return results; + } + + // =================================================================== + // 7. TDD-Inside-Spec: Tests and Invariants + // =================================================================== + + test self_similarity_proof_has_all_steps + given steps = self_similarity_proof_steps() + then steps.length() == 4 + + test self_similarity_proof_steps_valid + given steps = self_similarity_proof_steps() + and last_step = steps[3] + then last_step.result.contains("1/phi") == true + + test optimal_rounding_proof_has_all_steps + given steps = optimal_rounding_proof_steps() + then steps.length() == 3 + + test optimal_rounding_proof_confirms_match + given steps = optimal_rounding_proof_steps() + and verification_step = steps[2] + then verification_step.result.contains("7/7") == true + + test optimal_ratio_by_self_similarity_respects_budget + given (exp, mant) = optimal_ratio_by_self_similarity(15) + and available = 14 + then exp + mant == available + + test optimal_ratio_by_self_similarity_close_to_target + given (exp, mant) = optimal_ratio_by_self_similarity(31) + and ratio = (exp as f64) / (mant as f64) + then abs(ratio - PHI_TARGET) < 0.05 + + test optimal_allocation_by_rounding_for_gf4 + given (exp, mant, phi_dist) = optimal_allocation_by_rounding(4) + then exp == 1 and mant == 2 and phi_dist < 0.05 + + test optimal_allocation_by_rounding_for_gf32 + given (exp, mant, phi_dist) = optimal_allocation_by_rounding(32) + then exp == 12 and mant == 19 and phi_dist < 0.02 + + test verify_7_7_match_all_formats + given verification = verify_7_7_match() + then verification.length() == 7 + + test verify_7_7_all_match + given verification = verify_7_7_match() + and all_match = forall i in 0..verification.length(), verification[i].matches == true + then all_match == true + + test am_gm_gives_equal_split + given (exp, mant) = optimal_ratio_by_am_gm(10) + and available = 9 + then abs(exp as f64 - mant as f64) <= 1.0 + + test am_gm_different_from_phi_split + given (exp_amgm, mant_amgm) = optimal_ratio_by_am_gm(15) + and (exp_phi, mant_phi) = optimal_ratio_by_self_similarity(15) + then exp_amgm != exp_phi or mant_amgm != mant_phi + + // =================================================================== + // 8. Invariants + // =================================================================== + + invariant phi_target_is_phi_inverse + assert PHI_TARGET == sacred_physics::PHI_INV + + invariant phi_target_in_valid_range + assert PHI_TARGET > 0.5 and PHI_TARGET < 1.0 + + invariant self_similarity_respects_bit_budget + assert forall bits: u8, { let (e, m) = optimal_ratio_by_self_similarity(bits); e + m == bits - 1 } + + invariant optimal_rounding_respects_bit_budget + assert forall bits: u8, { let (e, m, _) = optimal_allocation_by_rounding(bits); e + m == bits - 1 } + + invariant phi_round_matches_all_7_formats + // CRITICAL: 7/7 match invariant - prevents regression of floor() bug + let verification = verify_7_7_match(); + assert forall i in 0..verification.length(), verification[i].matches == true + + invariant self_similarity_proof_steps_complete + assert self_similarity_proof_steps().length() == 4 + + invariant optimal_rounding_proof_steps_complete + assert optimal_rounding_proof_steps().length() == 3 + + invariant am_gm_always_gives_equal_or_near_equal + assert forall bits: u8, { let (e, m) = optimal_ratio_by_am_gm(bits); abs(e as f64 - m as f64) <= 1.0 } + + invariant phi_distance_non_negative + given (e, m, phi_dist) = optimal_allocation_by_rounding(16) + assert phi_dist >= 0.0 + + invariant phi_distance_for_gf32_is_minimum + given verification = verify_7_7_match() + assert verification[6].phi_raw > verification[5].phi_raw // GF32 > GF24 + + // =================================================================== + // 9. Benchmarks + // =================================================================== + + bench self_similarity_proof_computation + measure: nanoseconds to compute proof steps + target: < 100ns + + bench optimal_rounding_computation + measure: nanoseconds to compute round((N-1)/phi^2) + target: < 50ns + + bench verify_7_7_match_computation + measure: nanoseconds to verify all 7 GF formats + target: < 200ns +} diff --git a/apps/website/public/t27/specs/math/phi_universal_attractor.t27 b/apps/website/public/t27/specs/math/phi_universal_attractor.t27 new file mode 100644 index 0000000000..5268cadd80 --- /dev/null +++ b/apps/website/public/t27/specs/math/phi_universal_attractor.t27 @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/math/phi_universal_attractor.t27 +// Phi Universal Attractor Theorems -- Theorem 3: phi as Universal Fixed-Point +// MATH-ATTRACTOR-001 -- Generative mechanism for phi proportion +// +// THEOREM 3: phi is the unique fixed point of balancing recursion f(x) = (x + x^-^1 + 1) / 2 +// This addresses the critic's concern that phi is "fitting" rather than a true mechanism. + +module PhiUniversalAttractor { + use math::constants; + use math::sacred_physics; + + // =========================================================================== + // 1. THEOREM 3: phi as Universal Fixed-Point Attractor + // ========================================================================================= + + // The balancing recursion captures a fundamental dynamic: allocate a component + // while maintaining balance with its complement. This is a zero-parameter mechanism. + // + // From any positive starting point x_0 > 0, iteration converges + // exponentially to phi with rate lambda = (sqrt5 - 1) / 4 ~= 0.309. + + // The balancing function: f(x) = (x + x^-^1 + 1) / 2 + fn balancing_recursion(x: f64) -> f64 { + const inv = 1.0 / x; + return (x + inv + 1.0) / 2.0; + } + + // Theoretical convergence rate lambda = (sqrt5 - 1) / 4 + // This is the Lipschitz constant for the contraction mapping f + const CONVERGENCE_RATE_LAMBDA: f64 = (sqrt(5.0) - 1.0) / 4.0; + + // =========================================================================== + // 2. Iteration to Fixed Point + // =========================================================================== + + // Result tuple: (final_value, iterations, phi_distance) + struct ConvergenceResult { + final : f64, + iterations : u8, + phi_error : f64, + } + + // Iterate from starting point until convergence to phi + fn iterate_to_fixed_point(x0: f64, max_iter: u8, tolerance: f64) -> ConvergenceResult { + let mut x = x0; + let mut iter = 0; + let mut error = 1.0; + + while iter < max_iter { + const next = balancing_recursion(x); + error = abs(next - sacred_physics::PHI); + + if error < tolerance { + x = next; + iter = iter + 1; + break; + } + + x = next; + iter = iter + 1; + } + + return ConvergenceResult{ + final = x, + iterations = iter, + phi_error = error, + }; + } + + // =========================================================================== + // 3. Contraction Mapping Analysis + // =========================================================================== + + // Derivative of f: f'(x) = (1 - x^-^2) / 2 + // This shows f is a contraction mapping for x > 0 + fn balancing_derivative(x: f64) -> f64 { + const inv_sq = 1.0 / (x * x); + return (1.0 - inv_sq) / 2.0; + } + + // Maximum derivative magnitude (Lipschitz constant) for x > 0 + // |f'(x)| < 0.5 for all x > 0, proving contraction + fn max_lipschitz_constant() -> f64 { + return 0.5; // |(1 - x^-^2)/2| < 0.5 for all x > 0 + } + + // =========================================================================== + // 4. Proof Steps Documentation + // =========================================================================== + + struct ProofStep { + description : string, + equation : string, + result : string, + } + + fn fixed_point_verification_steps() -> [3]ProofStep { + return [ + ProofStep{ + description = "Balancing function definition", + equation = "f(x) = (x + x^-^1 + 1) / 2", + result = "Fundamental balancing dynamic", + }, + ProofStep{ + description = "Phi satisfies fixed point", + equation = "f(phi) = (phi + phi^-^1 + 1) / 2 = (phi + (phi - 1) + 1) / 2 = phi", + result = "phi is a fixed point of f", + }, + ProofStep{ + description = "Contraction property", + equation = "|f'(x)| = |(1 - x^-^2)/2| < 0.5 for all x > 0", + result = "Banach fixed-point theorem guarantees unique attractor", + }, + ]; + } + + fn convergence_proof_steps() -> [4]ProofStep { + return [ + ProofStep{ + description = "Derivative bounds", + equation = "f'(x) = (1 - x^-^2)/2, so |f'(x)| < 0.5", + result = "f is a contraction mapping", + }, + ProofStep{ + description = "Lipschitz constant", + equation = "lambda = (sqrt5 - 1)/4 ~= 0.309", + result = "Error decays as |x655 - 656| 657 658659 |x660 - 661|", + }, + ProofStep{ + description = "Exponential convergence", + equation = "lim(n662663) f664(x665) = 666 for all x667 > 0", + result = "Universal attractor property", + }, + ProofStep{ + description = "Zero parameters", + equation = "No fitting constants -- phi emerges from f's structure", + result = "Generative mechanism, not fitted narrative", + }, + ]; + } + + // =========================================================================== + // 5. Connection to Bit Allocation + // =========================================================================== + + // The GF bit allocation (exp/mant ~= 1/phi) is a special case + // of the universal attractor: any balancing dynamic of form f(x) = (x + x^-^1 + 1)/2 + // will converge to phi-proportional allocation regardless of initialization. + fn bit_allocation_attractor(available_bits: u8, initial_ratio: f64) -> (u8, u8, f64) { + // Simulate evolution of bit allocation ratio to phi + const target_ratio = sacred_physics::PHI_INV; + + let mut current_ratio = initial_ratio; + const max_iterations = 10; + const tolerance = 0.01; + + for i in 0..max_iterations { + current_ratio = balancing_recursion(current_ratio); + if abs(current_ratio - target_ratio) < tolerance { + break; + } + } + + // Compute final allocation based on converged ratio + const exp = (available_bits as f64 * current_ratio / (1.0 + current_ratio)).round() as u8; + const mant = available_bits - exp; + + return (exp, mant, current_ratio); + } + + // =========================================================================== + // 6. TDD-Inside-Spec: Tests + // =========================================================================== + + test phi_is_fixed_point_of_f + given phi = sacred_physics::PHI + and result = balancing_recursion(phi) + then abs(result - phi) < 1e-15 + + test phi_inverse_used_correctly + given phi = sacred_physics::PHI + and phi_inv = sacred_physics::PHI_INV + and result = (phi + phi_inv + 1.0) / 2.0 + then abs(result - phi) < 1e-15 + + test convergence_from_small_start + given start = 0.1 + and (final, iters, error) = iterate_to_fixed_point(start, 20, 1e-14) + then error < 1e-13 and iters <= 18 + + test convergence_from_unit_start + given start = 1.0 + and (final, iters, error) = iterate_to_fixed_point(start, 20, 1e-14) + then error < 1e-13 and iters <= 15 + + test convergence_from_arbitrary_start + given start = 0.5 + and (final, iters, error) = iterate_to_fixed_point(start, 20, 1e-14) + then error < 1e-13 and iters <= 15 + + test convergence_from_large_start + given start = 10.0 + and (final, iters, error) = iterate_to_fixed_point(start, 20, 1e-14) + then error < 1e-13 and iters <= 15 + + test convergence_from_very_large_start + given start = 100.0 + and (final, iters, error) = iterate_to_fixed_point(start, 20, 1e-14) + then error < 1e-13 and iters <= 18 + + test convergence_rate_matches_theoretical + given start = 0.5 + and (final1, iter1, error1) = iterate_to_fixed_point(start, 5, 1.0) + and (final2, iter2, error2) = iterate_to_fixed_point(start, 10, 1.0) + and observed_ratio = error2 / (error1 + 1e-15) + and expected_ratio = pow(CONVERGENCE_RATE_LAMBDA, 5.0) + then abs(observed_ratio - expected_ratio) < 0.2 + + test convergence_rate_approx_correct + given lambda_val = CONVERGENCE_RATE_LAMBDA + and expected = 0.309 + then abs(lambda_val - expected) < 0.01 + + test derivative_at_phi_less_than_half + given phi = sacred_physics::PHI + and deriv = balancing_derivative(phi) + then abs(deriv) < 0.5 + + test derivative_bound_valid + given max_deriv = max_lipschitz_constant() + then max_deriv > 0.0 and max_deriv < 1.0 + + test bit_allocation_converges_to_phi_ratio + given (exp, mant, final_ratio) = bit_allocation_attractor(15, 0.3) + and actual_ratio = (exp as f64) / (mant as f64) + and expected = sacred_physics::PHI_INV + then abs(actual_ratio - expected) < 0.1 + + test bit_allocation_respects_budget + given (exp, mant, _) = bit_allocation_attractor(20, 0.5) + then exp + mant == 20 + + test proof_steps_complete + given fixed_steps = fixed_point_verification_steps() + and conv_steps = convergence_proof_steps() + then fixed_steps.length() == 3 and conv_steps.length() == 4 + + test zero_parameter_mechanism + // This test verifies the mechanism has no fitted parameters + given phi_computed_from_iter = iterate_to_fixed_point(2.0, 50, 1e-14) + and phi_ref = sacred_physics::PHI + then phi_computed_from_iter.phi_error < 1e-13 + // Note: No "fitting constants" used -- pure recursion f + + // =========================================================================== + // 7. Invariants + // =========================================================================== + + invariant convergence_rate_is_positive + assert CONVERGENCE_RATE_LAMBDA > 0.0 + + invariant convergence_rate_less_than_one + assert CONVERGENCE_RATE_LAMBDA < 1.0 + + invariant convergence_rate_matches_formula + const lambda_computed = (sqrt(5.0) - 1.0) / 4.0 + assert abs(lambda_computed - CONVERGENCE_RATE_LAMBDA) < 1e-15 + + invariant phi_is_fixed_point + const result = balancing_recursion(sacred_physics::PHI) + assert abs(result - sacred_physics::PHI) < 1e-15 + + invariant phi_inv_is_phi_minus_one + assert abs(sacred_physics::PHI_INV - (sacred_physics::PHI - 1.0)) < 1e-15 + + invariant contraction_mapping_property + // For any x > 0, |f'(x)| < 0.5 + given test_points = [0.1, 0.5, 1.0, sacred_physics::PHI, 2.0, 10.0] + forall i in 0..test_points.length(), abs(balancing_derivative(test_points[i])) < 0.5 + + invariant unique_attractor_property + // 1127 is the ONLY fixed point of f on R1128 + // If f(x) = x for x > 0, then x = phi + assert balancing_recursion(sacred_physics::PHI) == sacred_physics::PHI + + invariant fixed_point_proof_steps_complete + assert fixed_point_verification_steps().length() == 3 + + invariant convergence_proof_steps_complete + assert convergence_proof_steps().length() == 4 + + invariant zero_free_parameters + // Mechanism is analytically defined -- no fitting constants + assert CONVERGENCE_RATE_LAMBDA == (sqrt(5.0) - 1.0) / 4.0 + + invariant universal_attractor_from_positive_reals + // For ANY x_0 > 0, iteration converges to phi + given start_values = [0.01, 0.1, 1.0, 10.0, 100.0] + forall i in 0..start_values.length(), { + let result = iterate_to_fixed_point(start_values[i], 30, 1e-13); + result.phi_error < 1e-12 + } + + // =========================================================================== + // 8. Benchmarks + // =========================================================================== + + bench balancing_recursion_single_call + measure: nanoseconds for one balancing_recursion() call + target: < 50ns + + bench iterate_to_convergence_from_arbitrary_start + measure: nanoseconds to converge from x_0 = 0.5 + target: < 500ns + + bench iterate_to_convergence_from_large_start + measure: nanoseconds to converge from x_0 = 10.0 + target: < 500ns + + bench derivative_computation + measure: nanoseconds to compute balancing_derivative(x) + target: < 30ns + + bench fixed_point_verification_steps + measure: nanoseconds to compute proof steps + target: < 100ns + + bench bit_allocation_attractor_computation + measure: nanoseconds to compute bit allocation via attractor + target: < 200ns +} diff --git a/apps/website/public/t27/specs/math/property_test_template.t27 b/apps/website/public/t27/specs/math/property_test_template.t27 new file mode 100644 index 0000000000..ee9b9555be --- /dev/null +++ b/apps/website/public/t27/specs/math/property_test_template.t27 @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/math/property_test_template.t27 +// Property-Test Template for Conformance Vectors +// Ring 053 - Defines reusable property testing patterns +// This spec provides templates for property-based testing of T27 formats +// Properties: mathematical invariants that must hold for ALL valid inputs +// phi^2 + 1/phi^2 = 3 | TRINITY + +module PropertyTestTemplate { + use math::constants; + + // ================================================================= + // 1. Property Test Types + // ========================================================================= + + // Property test strategies + const STRATEGY_RANDOM : i32 = 0; // Random generation with N samples + const STRATEGY_EXHAUSTIVE : i32 = 1; // Exhaustive for small domains + const STRATEGY_BOUNDARY : i32 = 2; // Focus on boundary conditions + + // Sample sizes for random testing + const SAMPLE_SIZE_SMALL : i32 = 100; + const SAMPLE_SIZE_MEDIUM : i32 = 1000; + const SAMPLE_SIZE_LARGE : i32 = 10000; + + // ================================================================= + // 2. Property Definitions (as test generators) + // ========================================================================= + + // Test associativity: (a op b) op c = a op (b op c) for all a,b,c + // Domain: binary operations + // Examples: addition, multiplication, trit-add + fn test_associative_add(values: []const f64, len: usize) -> bool { + var i : usize = 0 + while (i < len) { + var j : usize = 0 + while (j < len) { + var k : usize = 0 + while (k < len) { + const a = values[i] + const b = values[j] + const c = values[k] + const left = (a + b) + c + const right = a + (b + c) + if (abs(left - right) > 1e-10) { + return false + } + k = k + 1 + } + j = j + 1 + } + i = i + 1 + } + return true + } + + // Test associativity for multiplication + fn test_associative_mul(values: []const f64, len: usize) -> bool { + var i : usize = 0 + while (i < len) { + var j : usize = 0 + while (j < len) { + var k : usize = 0 + while (k < len) { + const a = values[i] + const b = values[j] + const c = values[k] + const left = (a * b) * c + const right = a * (b * c) + if (abs(left - right) > 1e-10) { + return false + } + k = k + 1 + } + j = j + 1 + } + i = i + 1 + } + return true + } + + // Test commutativity: a op b = b op a for all a,b + // Domain: binary operations + fn test_commutative_add(values: []const f64, len: usize) -> bool { + var i : usize = 0 + while (i < len) { + var j : usize = 0 + while (j < len) { + const a = values[i] + const b = values[j] + const ab = a + b + const ba = b + a + if (abs(ab - ba) > 1e-10) { + return false + } + j = j + 1 + } + i = i + 1 + } + return true + } + + fn test_commutative_mul(values: []const f64, len: usize) -> bool { + var i : usize = 0 + while (i < len) { + var j : usize = 0 + while (j < len) { + const a = values[i] + const b = values[j] + const ab = a * b + const ba = b * a + if (abs(ab - ba) > 1e-10) { + return false + } + j = j + 1 + } + i = i + 1 + } + return true + } + + // Test distributivity: a * (b + c) = (a * b) + (a * c) + fn test_distributive_mul_add(values: []const f64, len: usize) -> bool { + var i : usize = 0 + while (i < len) { + var j : usize = 0 + while (j < len) { + var k : usize = 0 + while (k < len) { + const a = values[i] + const b = values[j] + const c = values[k] + const left = a * (b + c) + const right = (a * b) + (a * c) + if (abs(left - right) > 1e-10) { + return false + } + k = k + 1 + } + j = j + 1 + } + i = i + 1 + } + return true + } + + // Test identity: a + 0 = a, a * 1 = a + fn test_identity_add(values: []const f64, len: usize) -> bool { + var i : usize = 0 + while (i < len) { + const a = values[i] + const result = a + 0.0 + if (abs(result - a) > 1e-10) { + return false + } + i = i + 1 + } + return true + } + + fn test_identity_mul(values: []const f64, len: usize) -> bool { + var i : usize = 0 + while (i < len) { + const a = values[i] + const result = a * 1.0 + if (abs(result - a) > 1e-10) { + return false + } + i = i + 1 + } + return true + } + + // Test inverse: a + (-a) = 0, a * (1/a) = 1 (for a != 0) + fn test_inverse_add(values: []const f64, len: usize) -> bool { + var i : usize = 0 + while (i < len) { + const a = values[i] + const result = a + (-a) + if (abs(result - 0.0) > 1e-10) { + return false + } + i = i + 1 + } + return true + } + + fn test_inverse_mul(values: []const f64, len: usize) -> bool { + var i : usize = 0 + while (i < len) { + const a = values[i] + if (a != 0.0) { + const result = a * (1.0 / a) + if (abs(result - 1.0) > 1e-10) { + return false + } + } + i = i + 1 + } + return true + } + + // Test idempotence: min(a,a) = a, max(a,a) = a + fn test_idempotent_min(values: []const f64, len: usize) -> bool { + var i : usize = 0 + while (i < len) { + const a = values[i] + var result : f64 = a + if (result > a) { result = a } + if (abs(result - a) > 1e-10) { + return false + } + i = i + 1 + } + return true + } + + fn test_idempotent_max(values: []const f64, len: usize) -> bool { + var i : usize = 0 + while (i < len) { + const a = values[i] + var result : f64 = a + if (result < a) { result = a } + if (abs(result - a) > 1e-10) { + return false + } + i = i + 1 + } + return true + } + + // Trit space dimension: VSA trit space has exactly 3^N distinct states + fn test_tritspace_dimension(n: i32) -> i64 { + // 3^N states for N trits + const three = 3.0 + const n_f = @as(f64, @floatFromInt(n)) + const result = pow(three, n_f) + return result as i64 + } + + // ================================================================= + // 3. Helper Functions + // ========================================================================= + + // Absolute value + fn abs(x: f64) -> f64 { + if (x < 0.0) { + return -x + } + return x + } + + // Power function + fn pow(x: f64, n: f64) -> f64 { + if (x < 0.0 and n != floor(n)) { + return std.math.nan(f64) // NaN + } + if (x == 0.0) { + if (n > 0.0) { return 0.0 } + if (n == 0.0) { return 1.0 } + return std.math.inf(f64) + } + if (n == 0.0) { return 1.0 } + + const negative = n < 0.0 + const exp = if (negative) { -n } else { n } + const is_integer = exp == floor(exp) + + if (is_integer) { + const exp_int = exp as i64 + var result : f64 = 1.0 + var base = x + var e = exp_int + + while (e > 0) { + if (@rem(e, 2) == 1) { + result = result * base + } + base = base * base + e = @divTrunc(e, 2) + } + + if (negative) { result = 1.0 / result } + return result + } + + // Fractional: use log/exp approximation + const ln_x = ln(x) + var result : f64 = 1.0 + var term : f64 = 1.0 + var i : u32 = 1 + while (i <= 12) { + term = term * exp * ln_x / @as(f64, @floatFromInt(i)) + result = result + term + i = i + 1 + } + + if (negative) { result = 1.0 / result } + return result + } + + // Natural logarithm + fn ln(x: f64) -> f64 { + if (x <= 0.0) { + return std.math.nan(f64) + } + if (x == 1.0) { + return 0.0 + } + const t = (x - 1.0) / (x + 1.0) + const t2 = t * t + const t3 = t2 * t + const t5 = t3 * t2 + const t7 = t5 * t2 + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0) + } + + // Floor function + fn floor(x: f64) -> f64 { + const xi = x as i64 + if (x >= 0.0 or x == xi as f64) { + return xi as f64 + } + return (xi - 1) as f64 + } + + // ================================================================= + // 4. TDD - Tests + // ========================================================================= + + test associative_addition + given values = [_]f64{1.0, 2.0, 3.0, -1.5, 0.5, 0.0} + and len = 6 + when result = test_associative_add(&values, len) + then result == true + + test associative_multiplication + given values = [_]f64{1.0, 2.0, 3.0, -1.0, 0.5} + and len = 5 + when result = test_associative_mul(&values, len) + then result == true + + test commutative_addition + given values = [_]f64{1.0, 2.0, -1.5, 0.0, 1.5, -2.5} + and len = 6 + when result = test_commutative_add(&values, len) + then result == true + + test commutative_multiplication + given values = [_]f64{2.0, 3.0, -1.0, 0.0, 1.5, -2.5} + and len = 6 + when result = test_commutative_mul(&values, len) + then result == true + + test distributive_mul_over_add + given values = [_]f64{2.0, 3.0, 1.5, -1.0, 0.5} + and len = 5 + when result = test_distributive_mul_add(&values, len) + then result == true + + test identity_addition + given values = [_]f64{1.0, 2.0, -1.5, 3.14159, 0.0} + and len = 5 + when result = test_identity_add(&values, len) + then result == true + + test identity_multiplication + given values = [_]f64{2.0, 3.0, -1.0, 0.0, 1.5} + and len = 5 + when result = test_identity_mul(&values, len) + then result == true + + test inverse_addition + given values = [_]f64{1.0, 2.0, -1.5, 3.14159} + and len = 4 + when result = test_inverse_add(&values, len) + then result == true + + test inverse_multiplication + given values = [_]f64{2.0, 3.0, -1.0, 0.5, 1.5} + and len = 5 + when result = test_inverse_mul(&values, len) + then result == true + + test idempotent_min + given values = [_]f64{1.0, 2.0, -1.5, 3.14159, 0.0} + and len = 5 + when result = test_idempotent_min(&values, len) + then result == true + + test idempotent_max + given values = [_]f64{1.0, 2.0, -1.5, 3.14159, 0.0} + and len = 5 + when result = test_idempotent_max(&values, len) + then result == true + + test tritspace_dimension_1 + given n = 1 + when result = test_tritspace_dimension(n) + then result == 3 + + test tritspace_dimension_2 + given n = 2 + when result = test_tritspace_dimension(n) + then result == 9 + + test tritspace_dimension_3 + given n = 3 + when result = test_tritspace_dimension(n) + then result == 27 + + test tritspace_dimension_4 + given n = 4 + when result = test_tritspace_dimension(n) + then result == 81 + + test tritspace_dimension_5 + given n = 5 + when result = test_tritspace_dimension(n) + then result == 243 + + // ================================================================= + // 5. TDD - Invariants + // ========================================================================= + + invariant associative_invariant + // Associativity is a fundamental property for many operations + const temp_vals = [_]f64{1.0, 2.0, 3.0}; + assert test_associative_add(&temp_vals, 3) == true + // Rationale: (a + b) + c = a + (b + c) for all real numbers + + invariant commutative_invariant + // Commutativity holds for addition and multiplication + const temp_vals1 = [_]f64{1.0, 2.0, 3.0}; + assert test_commutative_add(&temp_vals1, 3) == true + assert test_commutative_mul(&temp_vals1, 3) == true + // Rationale: a op b = b op a for commutative operations + + invariant identity_invariant + // Identity elements exist for group operations + const temp_vals2 = [_]f64{1.0, 2.0, 3.0}; + assert test_identity_add(&temp_vals2, 3) == true + assert test_identity_mul(&temp_vals2, 3) == true + // Rationale: a op e = a defines the identity element e + + invariant inverse_invariant + // Inverse elements exist for group operations + const temp_vals3 = [_]f64{1.0, 2.0, -1.5}; + assert test_inverse_add(&temp_vals3, 3) == true + // Rationale: a op a' = e defines the inverse element a' + + invariant tritspace_dimension_formula + // Trit space dimension is exactly 3^N + assert test_tritspace_dimension(1) == 3 + assert test_tritspace_dimension(2) == 9 + assert test_tritspace_dimension(3) == 27 + assert test_tritspace_dimension(4) == 81 + // Rationale: N trits can represent 3^N distinct states + + // ================================================================= + // 6. TDD - Benchmarks + // ========================================================================= + + bench property_test_associative + // Measure: cycles to test associativity on 10 values + // Target: < 1000 cycles + const values = [_]f64{1.0, 2.0, 3.0, -1.5, 0.5, 0.0, 1.5, -2.0, 2.5, -0.5}; + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..100) |_| { + result = test_associative_add(&values, 10); + } + _ = result; + + bench property_test_commutative + // Measure: cycles to test commutativity on 10 values + // Target: < 500 cycles + const values = [_]f64{1.0, 2.0, 3.0, -1.5, 0.5, 0.0, 1.5, -2.0, 2.5, -0.5}; + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..100) |_| { + result = test_commutative_add(&values, 10); + } + _ = result; + + bench property_test_distributive + // Measure: cycles to test distributivity on 5 values + // Target: < 2000 cycles + const values = [_]f64{1.0, 2.0, 3.0, -1.0, 0.5}; + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..100) |_| { + result = test_distributive_mul_add(&values, 5); + } + _ = result; + + bench property_test_identity + // Measure: cycles to test identity on 100 values + // Target: < 200 cycles + var values : [100]f64 = undefined; + var i : usize = 0; + while (i < 100) { + values[i] = @as(f64, @floatFromInt(i)) / 10.0; + i = i + 1; + } + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..100) |_| { + result = test_identity_add(&values, 100); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/math/radix_economy.t27 b/apps/website/public/t27/specs/math/radix_economy.t27 new file mode 100644 index 0000000000..436c0a6e75 --- /dev/null +++ b/apps/website/public/t27/specs/math/radix_economy.t27 @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/math/radix_economy.t27 +// Radix Economy Formal Spec -- Information-Theoretic Basis for Base-3 Computing +// E(b) = ln(b)/b, maximized at b = e ~= 2.71828 +// E(3)/E(e) >= 99.5%, E(3)/E(2) = 1.054 (5.4% advantage) + +module RadixEconomy { + use Constants; + + // =========================================================================== + // 1. Radix Economy Constants + // ========================================================================================= + + // E(b) = ln(b) / b -- information per digit + // E(2) = ln(2)/2 ~= 0.34657 -- binary radix economy + const E_BASE2 : f64 = 0.34657359027997264; // ln(2)/2 + + // E(3) = ln(3)/3 ~= 0.36620 -- ternary radix economy + const E_BASE3 : f64 = 0.3662040962227032; // ln(3)/3 + + // E(e) = 1/e ~= 0.36788 -- optimal radix economy (theoretical maximum) + const E_OPTIMAL : f64 = 0.36787944117144233; // 1/e + + // log2(3) ~= 1.58496 -- bits per trit (information density) + const LOG2_3 : f64 = 1.584962500721156; + + // log3(2) ~= 0.63093 -- trits per bit (inverse density) + const LOG3_2 : f64 = 0.6309297535714574; + + // =========================================================================== + // 2. Radix Economy Functions + // ========================================================================================= + + // Compute radix economy for base b + fn radix_economy(b: f64) -> f64 { + return ln(b) / b; + } + + // Compute efficiency relative to optimal base e + fn efficiency_ratio(b: f64) -> f64 { + return radix_economy(b) / E_OPTIMAL; + } + + // Compare two bases: ratio of their radix economies + fn base_advantage(b1: f64, b2: f64) -> f64 { + return radix_economy(b1) / radix_economy(b2); + } + + // Information density: bits per digit + fn info_density_bits(b: f64) -> f64 { + return log2(b); + } + + // Ternary range: maximum value for n trits (balanced: -(3^n-1)/2 to (3^n-1)/2) + fn ternary_range(n: i64) -> i64 { + return (pow(3.0, n as f64) - 1.0) as i64 / 2; + } + + // Binary range: maximum value for n bits + fn binary_range(n: i64) -> i64 { + return (pow(2.0, n as f64) - 1.0) as i64; + } + + // =========================================================================== + // 3. Helper Functions (logarithms) + // ========================================================================================= + + // Natural logarithm + fn ln(x: f64) -> f64 { + if x <= 0.0 { + return 0.0 / 0.0; // NaN + } + if x == 1.0 { + return 0.0; + } + // Series: ln(x) = 2 * sum_{k=0..inf} (1/(2k+1)) * ((x-1)/(x+1))^(2k+1) + let t = (x - 1.0) / (x + 1.0); + let t2 = t * t; + let t3 = t2 * t; + let t5 = t3 * t2; + let t7 = t5 * t2; + let t9 = t7 * t2; + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0 + t9 / 9.0); + } + + // Base-2 logarithm: log2(x) = ln(x) / ln(2) + fn log2(x: f64) -> f64 { + return ln(x) / ln(2.0); + } + + // Power function (for range calculations) + fn pow(x: f64, n: f64) -> f64 { + if x < 0.0 and n != floor(n) { + return 0.0 / 0.0; + } + if x == 0.0 { + if n > 0.0 { return 0.0; } + if n == 0.0 { return 1.0; } + return 1.0 / 0.0; + } + if n == 0.0 { return 1.0; } + + let negative = n < 0.0; + let exp = if negative { -n } else { n }; + let is_integer = exp == floor(exp); + + if is_integer { + let exp_int = exp as i64; + let mut result = 1.0; + let mut base = x; + let mut e = exp_int; + + while e > 0 { + if e % 2 == 1 { + result = result * base; + } + base = base * base; + e = e / 2; + } + + if negative { result = 1.0 / result; } + return result; + } + + // Fractional: use log/exp + let ln_x = ln(x); + let mut result = 1.0; + let mut term = 1.0; + for i in 1..=12 { + term = term * exp * ln_x / (i as f64); + result = result + term; + } + + if negative { result = 1.0 / result; } + return result; + } + + // Natural exponential: e^x (simplified) + fn exp(x: f64) -> f64 { + if x == 0.0 { return 1.0; } + + let mut result = 1.0; + let mut term = 1.0; + + for i in 1..=12 { + term = term * x / (i as f64); + result = result + term; + } + + return result; + } + + // Floor function + fn floor(x: f64) -> f64 { + let xi = x as i64; + if x >= 0.0 || x == xi as f64 { + return xi as f64; + } + return (xi - 1) as f64; + } + + // =========================================================================== + // 4. TDD-Inside-Spec: Tests for Radix Economy + // ========================================================================================= + + test e_base3_near_optimal + given e3 = E_BASE3 + and e_optimal = E_OPTIMAL + when ratio = e3 / e_optimal + then ratio >= 0.995 + and ratio <= 1.0 + + test e_base3_beats_base2 + given e3 = E_BASE3 + and e2 = E_BASE2 + when ratio = e3 / e2 + and advantage = (e3 - e2) / e2 + then e3 > e2 + and ratio >= 1.05 + and advantage >= 0.05 + + test log2_3_accuracy + given log2_3 = LOG2_3 + when lower = 1.58496 + and upper = 1.58497 + then log2_3 >= lower and log2_3 <= upper + + test log3_2_accuracy + given log3_2 = LOG3_2 + when reciprocal = 1.0 / LOG2_3 + then abs(log3_2 - reciprocal) < 1e-6 + + test ternary_vs_binary_range_27trit + given trit_range = ternary_range(27) + and bit_range_42 = binary_range(42) + and bit_range_43 = binary_range(43) + when trit_range + then trit_range > bit_range_42 + and trit_range <= bit_range_43 + + test ternary_vs_binary_range_18trit + given trit_range = ternary_range(18) + and bit_range_28 = binary_range(28) + and bit_range_29 = binary_range(29) + when trit_range + then trit_range > bit_range_28 + and trit_range <= bit_range_29 + + test radix_economy_function_correctness + given e2_computed = radix_economy(2.0) + and e3_computed = radix_economy(3.0) + and e_e_computed = radix_economy(2.718281828459045) + when err_e2 = abs(e2_computed - E_BASE2) + and err_e3 = abs(e3_computed - E_BASE3) + and err_ee = abs(e_e_computed - E_OPTIMAL) + then err_e2 < 1e-6 and err_e3 < 1e-6 and err_ee < 1e-6 + + test efficiency_ratio_base3 + given eff3 = efficiency_ratio(3.0) + when eff3 + then eff3 >= 0.995 and eff3 <= 1.0 + + test base_advantage_3_vs_2 + given advantage = base_advantage(3.0, 2.0) + when advantage + then advantage >= 1.054 + + test info_density_trit + given density = info_density_bits(3.0) + when density + then abs(density - LOG2_3) < 1e-6 + + test ln_function_accuracy + given ln_e = ln(E) + when ln_e + then abs(ln_e - 1.0) < 1e-6 + + test ln_function_ln2 + given ln_2 = ln(2.0) + when ln_2 + then abs(ln_2 - 0.693147) < 1e-5 + + test ln_function_ln3 + given ln_3 = ln(3.0) + when ln_3 + then abs(ln_3 - 1.098612) < 1e-5 + + test log2_function_accuracy + given log2_2 = log2(2.0) + and log2_4 = log2(4.0) + and log2_8 = log2(8.0) + when results + then abs(log2_2 - 1.0) < 1e-6 + and abs(log2_4 - 2.0) < 1e-6 + and abs(log2_8 - 3.0) < 1e-6 + + // =========================================================================== + // 5. Formal Invariants -- Mathematical Truths + // ========================================================================================= + + invariant base3_995_percent_optimal + assert E_BASE3 / E_OPTIMAL >= 0.995 + // Rationale: E(3) >= 99.5% of E(e), proven by calculus: d/db(ln(b)/b)=0 => b=e + + invariant base3_superior_to_base2 + assert E_BASE3 > E_BASE2 + // Rationale: ln(3)/3 > ln(2)/2 by direct computation + + invariant base3_54_percent_advantage + assert (E_BASE3 - E_BASE2) / E_BASE2 >= 0.054 + // Rationale: (0.3662 - 0.3466) / 0.3466 = 0.054 = 5.4% + + invariant log2_3_in_range + assert LOG2_3 >= 1.58496 and LOG2_3 <= 1.58497 + // Rationale: log2(3) ~= 1.584962500721156 + + invariant trit_info_density + assert LOG2_3 > 1.5 and LOG2_3 < 2.0 + // Rationale: A trit contains more information than a bit (1.58 > 1), less than 2 bits + + invariant radix_economy_monotonic_increase_to_e + assert for b in [2, 3], E(b) <= E(b+1) when b < e + // Rationale: E(b) = ln(b)/b increases for b < e, decreases for b > e + + invariant optimal_base_is_e + assert E_OPTIMAL = 1/e + // Rationale: Maximizing ln(b)/b gives b = e by calculus + + invariant ternary_range_balanced + assert ternary_range(n) = (3^n - 1) / 2 for all positive integer n + // Rationale: Balanced ternary represents integers from -(3^n-1)/2 to (3^n-1)/2 + + invariant binary_range_standard + assert binary_range(n) = 2^n - 1 for all positive integer n + // Rationale: Unsigned binary represents integers from 0 to 2^n - 1 + + invariant range_equivalence_27trit_43bit + assert ternary_range(27) <= binary_range(43) + and ternary_range(27) > binary_range(42) + // Rationale: (3^27-1)/2 ~= 3.8e12, 2^42 ~= 4.4e12, 2^43 ~= 8.8e12 + + invariant range_equivalence_18trit_29bit + assert ternary_range(18) <= binary_range(29) + and ternary_range(18) > binary_range(28) + // Rationale: 3^18 ~= 3.9e8, 2^28 ~= 2.7e8, 2^29 ~= 5.4e8 + + invariant log_reciprocal_identity + assert LOG3_2 * LOG2_3 = 1.0 within 1e-6 + // Rationale: log_a(b) * log_b(a) = 1 for any valid bases + + // =========================================================================== + // 6. Benchmarks -- Performance Targets + // ========================================================================================= + + bench radix_economy_computation + measure: cycles to compute radix_economy(3.0) + target: < 100 cycles + + bench log2_computation + measure: cycles to compute log2(3.0) + target: < 200 cycles + + bench ternary_range_27 + measure: cycles to compute ternary_range(27) + target: < 500 cycles + + bench base_advantage_3_vs_2 + measure: cycles to compute base_advantage(3.0, 2.0) + target: < 150 cycles +} diff --git a/apps/website/public/t27/specs/math/sacred_physics.t27 b/apps/website/public/t27/specs/math/sacred_physics.t27 new file mode 100644 index 0000000000..b5c72d9f83 --- /dev/null +++ b/apps/website/public/t27/specs/math/sacred_physics.t27 @@ -0,0 +1,460 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/math/sacred_physics.t27 +// Strand I 0 Mathematical Foundation +// Sacred Physics Layer: links TRINITY identity (phi) to gravity, cosmology and neurotime. +module SacredPhysics { + // Import base constants: PHI, PHI_INV, PI, E, G_MEASURED, OMEGA_LAMBDA_MEASURED + use math::constants; + +// 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253 +// 1. TRINITY identity and derived dimensionless constants +// 54555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 +// Claim IDs: C-phi-001 (EXACT for mathematical identity, CONJECTURAL for physics interpretation) + const PHI : f64 = constants::PHI; // 1.618... (golden ratio) + const PHI_INV : f64 = constants::PHI_INV; // 0.618... (inverse golden ratio) + const PHI_SQ : f64 = PHI * PHI; + const PHI_INV_SQ : f64 = PHI_INV * PHI_INV; + + // TRINITY = 3.0 within numeric tolerance + // Claim: C-phi-001 (EXACT - mathematical identity), tolerance: EXACT + const TRINITY : f64 = PHI_SQ + PHI_INV_SQ; + + // Barbero107Immirzi parameter from pure math: gamma = phi^{-3} + // Claim: C-phi-001 (CONJECTURAL - physics interpretation), tolerance: CONJECTURAL + const GAMMA_LQG : f64 = pow(PHI, -3.0); + + // Consciousness threshold C = phi^{-1} + // Claim: C-phi-001 (CONJECTURAL - physics interpretation), tolerance: CONJECTURAL + const C_THRESHOLD : f64 = PHI_INV; + + // Specious present (seconds): t_present = phi^{-2} + // Claim: C-phi-001 (CONJECTURAL - physics interpretation), tolerance: CONJECTURAL + const T_PRESENT_SEC : f64 = pow(PHI, -2.0); + const T_PRESENT_MS : f64 = T_PRESENT_SEC * 1000.0; + + // Neural gamma band center: f_gamma = phi^3 * pi / gamma + // Claim: C-phi-001 (CONJECTURAL - physics interpretation), tolerance: CONJECTURAL + fn neural_gamma_center(pi: f64) -> f64 { + const phi_cubed = PHI * PHI * PHI; + return (phi_cubed * pi) / GAMMA_LQG; + } + + fn sin2_theta12_trinity() -> f64 { + const phi_neg2 = PHI_INV * PHI_INV; + const pi2 = constants::PI * constants::PI; + return 8.0 * phi_neg2 / pi2; + } + + // 108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 + // 1.5 161 power helper functions + // 162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 + + // phi_pow(n: i64) -> f64 + // Efficient computation of 215^n using recurrence 216^2 = 217 + 1 + // Handles positive, zero, and negative exponents + fn phi_pow(n: i64) -> f64 { + if n == 0 { + return 1.0; + } + if n > 0 { + // Use binary exponentiation for efficiency + let result = 1.0; + let base = PHI; + let exp = n; + while exp > 0 { + if exp % 2 == 1 { + result = result * base; + } + base = base * base; + exp = exp / 2; + } + return result; + } + // n < 0: use inverse + let abs_n = -n; + let result = 1.0; + let base = PHI_INV; + let exp = abs_n; + while exp > 0 { + if exp % 2 == 1 { + result = result * base; + } + base = base * base; + exp = exp / 2; + } + return result; + } + +// 218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 +// 2. Gravity & dark energy from TRINITY +// 271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 +// Claim IDs: C-phi-005 (EMPIRICAL_FIT - Trinity monomials for fundamental constants) + + // Sacred gravity prediction: G_sacred = pi^3 * gamma^2 / phi + // Claim: C-phi-005 (EMPIRICAL_FIT), tolerance: WITHIN_UNCERTAINTY + fn sacred_gravity(pi: f64) -> f64 { + const pi_sq = pi * pi; + const pi_cub = pi_sq * pi; + const g2 = GAMMA_LQG * GAMMA_LQG; + return (pi_cub * g2) / PHI; + } + + // Sacred dark energy fraction: Omega_L = 8 * gamma^3 * pi^5 / phi^8 + // Claim: C-phi-005 (EMPIRICAL_FIT), tolerance: WITHIN_UNCERTAINTY + fn sacred_dark_energy(pi: f64) -> f64 { + const gamma3 = GAMMA_LQG * GAMMA_LQG * GAMMA_LQG; + const pi2 = pi * pi; + const pi4 = pi2 * pi2; + const pi5 = pi4 * pi; + const phi8 = PHI_SQ * PHI_SQ * PHI_SQ * PHI_SQ; + return (8.0 * gamma3 * pi5) / phi8; + } + + // 324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 + // 3. Verification API 377 language378agnostic conformance hooks + // 379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 + + // All tolerances are relative errors. + const MAX_REL_ERROR_G : f64 = 1.0e-3; // 0.1% + const MAX_REL_ERROR_OMEGA : f64 = 5.0e-2; // 5% + const MAX_ABS_ERROR_TRINITY : f64 = 1.0e-12; // near double eps + + struct SacredPhysicsReport { + trinity_value : f64; + trinity_ok : bool; + + gamma_value : f64; + c_threshold : f64; + t_present_ms : f64; + + g_pred : f64; + g_measured : f64; + g_rel_error : f64; + g_ok : bool; + + omega_pred : f64; + omega_measured : f64; + omega_rel_error : f64; + omega_ok : bool; + + f_gamma_pred : f64; + } + + fn verify_sacred_physics() -> SacredPhysicsReport { + const PI = constants::PI; + const trinity = TRINITY; + const trinity_ok = abs(trinity - 3.0) < MAX_ABS_ERROR_TRINITY; + + const gamma_val = GAMMA_LQG; + const c_thr = C_THRESHOLD; + const t_ms = T_PRESENT_MS; + + const g_pred = sacred_gravity(PI); + const g_meas = constants::G_MEASURED; + const g_rel = abs(g_pred - g_meas) / g_meas; + const g_ok = g_rel <= MAX_REL_ERROR_G; + + const omega_pred = sacred_dark_energy(PI); + const omega_meas = constants::OMEGA_LAMBDA_MEASURED; + const omega_rel = abs(omega_pred - omega_meas) / omega_meas; + const omega_ok = omega_rel <= MAX_REL_ERROR_OMEGA; + + const f_gamma = neural_gamma_center(PI); + + return SacredPhysicsReport{ + trinity_value = trinity, + trinity_ok = trinity_ok, + + gamma_value = gamma_val, + c_threshold = c_thr, + t_present_ms = t_ms, + + g_pred = g_pred, + g_measured = g_meas, + g_rel_error = g_rel, + g_ok = g_ok, + + omega_pred = omega_pred, + omega_measured = omega_meas, + omega_rel_error = omega_rel, + omega_ok = omega_ok, + + f_gamma_pred = f_gamma, + }; + } + + // 432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534 + // TDD-Inside-Spec: Tests and Invariants for Sacred Physics + // 535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637 + + // 638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728 + // 4. TRINITY Verification 729 Core identity check + // 730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820 + + struct TrinityVerification { + phi_value : f64; + phi_sq_value : f64; + phi_inv_sq : f64; + trinity_value : f64; + target : f64; + absolute_error : f64; + relative_error : f64; + passes : bool; + tolerance : f64; + } + + fn verify_trinity(tolerance: f64) -> TrinityVerification { + const phi_sq = PHI * PHI; + const phi_inv_sq = PHI_INV * PHI_INV; + const trinity = phi_sq + phi_inv_sq; + const target = 3.0; + const abs_err = abs(trinity - target); + const rel_err = abs_err / target; + + return TrinityVerification{ + phi_value = PHI, + phi_sq_value = phi_sq, + phi_inv_sq = phi_inv_sq, + trinity_value = trinity, + target = target, + absolute_error = abs_err, + relative_error = rel_err, + passes = abs_err <= tolerance, + tolerance = tolerance, + }; + } + + // 821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911 + // TDD-Inside-Spec: Tests and Invariants for Sacred Physics + // 912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990 + + test trinity_identity_holds + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given trinity = TRINITY + and expected = 3.0 + and tolerance = MAX_ABS_ERROR_TRINITY + when diff = abs(trinity - expected) + then diff < tolerance + + test phi_squared_plus_inverse_squared + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given phi_sq = PHI * PHI + and phi_inv_sq = PHI_INV * PHI_INV + and trinity = phi_sq + phi_inv_sq + then abs(trinity - 3.0) < 1e-12 + + test gamma_from_phi_inverse_cubed + // Claim: C-phi-001 (CONJECTURAL), tolerance: CONJECTURAL + given gamma_expected = pow(PHI, -3.0) + and gamma_actual = GAMMA_LQG + then abs(gamma_expected - gamma_actual) < 1e-15 + + test consciousness_threshold_equals_phi_inverse + // Claim: C-phi-001 (CONJECTURAL), tolerance: CONJECTURAL + given c_threshold = C_THRESHOLD + and phi_inv = PHI_INV + then abs(c_threshold - phi_inv) < 1e-15 + + test specious_present_in_milliseconds + // Claim: C-phi-001 (CONJECTURAL), tolerance: CONJECTURAL + given t_sec = T_PRESENT_SEC + and t_ms = T_PRESENT_MS + then abs(t_ms - t_sec * 1000.0) < 1e-12 + + test neural_gamma_center_around_56hz + // Claim: C-phi-001 (CONJECTURAL), tolerance: CONJECTURAL + given f_gamma = neural_gamma_center(PI) + then f_gamma > 50.0 and f_gamma < 65.0 + + test sin2_theta12_juno_2025_compatibility + // Claim: C-phi-005 (EMPIRICAL_FIT), tolerance: WITHIN_UNCERTAINTY + given trinity_value = sin2_theta12_trinity() + and juno_center = 0.3092 + and juno_uncertainty = 0.0054 + and delta = abs(trinity_value - juno_center) + then delta < juno_uncertainty + // Trinity prediction: 8*phi^-2/pi^2 ≈ 0.30961 + // JUNO 2025 measurement: 0.3092 ± 0.0054 + // Delta = |0.30961 - 0.3092| = 0.00041 (within 1σ) + + test sacred_gravity_close_to_measured + // Claim: C-phi-005 (EMPIRICAL_FIT), tolerance: WITHIN_UNCERTAINTY + given report = verify_sacred_physics() + and tolerance = MAX_REL_ERROR_G + when rel_error = report.g_rel_error + then rel_error < tolerance + + test sacred_dark_energy_close_to_measured + // Claim: C-phi-005 (EMPIRICAL_FIT), tolerance: WITHIN_UNCERTAINTY + given report = verify_sacred_physics() + and tolerance = MAX_REL_ERROR_OMEGA + when rel_error = report.omega_rel_error + then rel_error < tolerance + + test verify_report_contains_all_fields + // Claim: C-phi-001 (EXACT) + C-phi-005 (EMPIRICAL_FIT), tolerance: MIXED + given report = verify_sacred_physics() + then report.trinity_ok == true + and report.f_gamma_pred > 0.0 + and report.t_present_ms > 0.0 + + test verify_trinity_with_strict_tolerance + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given result = verify_trinity(1e-15) + then result.passes == true + and result.trinity_value > 2.99 + and result.trinity_value < 3.01 + + test verify_trinity_with_loose_tolerance + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given result = verify_trinity(0.1) + then result.passes == true + and result.tolerance == 0.1 + + test verify_trinity_phi_components + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given result = verify_trinity(1e-12) + then result.phi_sq_value > 2.6 + and result.phi_sq_value < 2.7 + and result.phi_inv_sq > 0.38 + and result.phi_inv_sq < 0.39 + + test verify_trinity_absolute_error_positive + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given result = verify_trinity(1.0) + then result.absolute_error >= 0.0 + + test verify_trinity_relative_error_small + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given result = verify_trinity(1.0) + then result.relative_error < 0.01 + + test verify_trinity_target_is_three + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given result = verify_trinity(1.0) + then result.target == 3.0 + + test phi_pow_zero_equals_one + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given result = phi_pow(0) + then abs(result - 1.0) < 1e-15 + + test phi_pow_one_equals_phi + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given result = phi_pow(1) + then abs(result - PHI) < 1e-15 + + test phi_pow_negative_one_equals_phi_inverse + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given result = phi_pow(-1) + then abs(result - PHI_INV) < 1e-15 + + test phi_pow_two_equals_phi_plus_one + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given result = phi_pow(2) + and expected = PHI + 1.0 + then abs(result - expected) < 1e-15 + + test phi_pow_three_matches_multiplication + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given result = phi_pow(3) + and expected = PHI * PHI * PHI + then abs(result - expected) < 1e-15 + + test phi_pow_negative_two_matches_inverse_square + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given result = phi_pow(-2) + and expected = PHI_INV * PHI_INV + then abs(result - expected) < 1e-15 + + test phi_pow_positive_returns_greater_than_one + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given result = phi_pow(5) + then result > 1.0 + + test phi_pow_negative_returns_less_than_one + // Claim: C-phi-001 (EXACT), tolerance: EXACT + given result = phi_pow(-5) + then result > 0.0 and result < 1.0 + + invariant phi_greater_than_one + // Claim: C-phi-001 (EXACT), tolerance: EXACT + assert PHI > 1.6 and PHI < 1.7 + + invariant phi_inverse_less_than_one + // Claim: C-phi-001 (EXACT), tolerance: EXACT + assert PHI_INV > 0.6 and PHI_INV < 0.7 + + invariant phi_squared_equals_phi_plus_one + // Claim: C-phi-001 (EXACT), tolerance: EXACT + assert abs(PHI_SQ - (PHI + 1.0)) < 1e-15 + + invariant phi_times_inverse_equals_one + // Claim: C-phi-001 (EXACT), tolerance: EXACT + assert abs(PHI * PHI_INV - 1.0) < 1e-15 + + invariant phi_pow_zero_equals_one + // Claim: C-phi-001 (EXACT), tolerance: EXACT + assert abs(phi_pow(0) - 1.0) < 1e-15 + + invariant phi_pow_one_equals_phi + // Claim: C-phi-001 (EXACT), tolerance: EXACT + assert abs(phi_pow(1) - PHI) < 1e-15 + + invariant phi_pow_inverse_reciprocal + // Claim: C-phi-001 (EXACT), tolerance: EXACT + assert abs(phi_pow(n) * phi_pow(-n) - 1.0) < 1e-12 for n in {1, 2, 3, 4, 5} + + invariant phi_pow_additive_exponents + // Claim: C-phi-001 (EXACT), tolerance: EXACT + assert abs(phi_pow(a + b) - phi_pow(a) * phi_pow(b)) < 1e-12 for a, b in {1, 2, 3} + + invariant gamma_lqg_less_than_one + // Claim: C-phi-001 (CONJECTURAL), tolerance: CONJECTURAL + assert GAMMA_LQG > 0.2 and GAMMA_LQG < 0.3 + + invariant consciousness_threshold_in_range + // Claim: C-phi-001 (CONJECTURAL), tolerance: CONJECTURAL + assert C_THRESHOLD > 0.0 and C_THRESHOLD < 1.0 + + invariant specious_present_positive + // Claim: C-phi-001 (CONJECTURAL), tolerance: CONJECTURAL + assert T_PRESENT_SEC > 0.0 and T_PRESENT_MS > 0.0 + + invariant trinity_equals_three + // Claim: C-phi-001 (EXACT), tolerance: EXACT + assert abs(TRINITY - 3.0) < 1e-12 + + invariant gamma_from_phi_formula + // Claim: C-phi-001 (CONJECTURAL), tolerance: CONJECTURAL + assert abs(GAMMA_LQG - pow(PHI, -3.0)) < 1e-15 + + invariant neural_gamma_positive + // Claim: C-phi-001 (CONJECTURAL), tolerance: CONJECTURAL + assert neural_gamma_center(PI) > 0.0 + + invariant sacred_gravity_positive + // Claim: C-phi-005 (EMPIRICAL_FIT), tolerance: WITHIN_UNCERTAINTY + assert sacred_gravity(PI) > 0.0 + + invariant sacred_dark_energy_in_range + // Claim: C-phi-005 (EMPIRICAL_FIT), tolerance: WITHIN_UNCERTAINTY + given omega = sacred_dark_energy(PI) + assert omega > 0.0 and omega < 1.0 + + bench verify_sacred_physics_computation_time + measure: nanoseconds to compute verify_sacred_physics() + target: < 1000ns + + bench sacred_gravity_computation_time + measure: nanoseconds to compute sacred_gravity(PI) + target: < 500ns + + bench sacred_dark_energy_computation_time + measure: nanoseconds to compute sacred_dark_energy(PI) + target: < 500ns + + bench phi_pow_computation_time + measure: nanoseconds to compute phi_pow(10) + target: < 200ns +} diff --git a/apps/website/public/t27/specs/math/zamolodchikov_e8.t27 b/apps/website/public/t27/specs/math/zamolodchikov_e8.t27 new file mode 100644 index 0000000000..dbdaba260f --- /dev/null +++ b/apps/website/public/t27/specs/math/zamolodchikov_e8.t27 @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/math/zamolodchikov_e8.t27 +// Zamolodchikov E8 Integrable Field Theory -- Mass Spectrum +// Direction A/E of PROJECT KEPLER->NEWTON +// +// In 1989, Zamolodchikov proved that the 2D Ising CFT perturbed by +// a magnetic field possesses E8 symmetry with exactly 8 stable particles. +// Their mass ratios are determined EXACTLY by E8 algebra -- not fitted. +// +// Key result: m2/m1 = phi (golden ratio). Also m6/m3 = m7/m4 = m8/m5 = phi. +// This was confirmed experimentally by Coldea et al. (Science 2010) +// in the Ising chain ferromagnet CoNb2O6. +// +// The Zamolodchikov masses are IDENTICAL to the Perron-Frobenius +// eigenvector of the E8 adjacency matrix (same numbers, different order). +// This is the ONLY known case where phi emerges from a Lagrangian. +// +// Breakthrough hypothesis: if this construction generalizes to 4D, +// it could fix the ~25 free parameters of the Standard Model. +// +// References: +// - Zamolodchikov, Int. J. Mod. Phys. A4 (1989) 4235 +// - Coldea et al., Science 327 (2010) 177 -- experimental confirmation +// - Koca & Koca, arXiv:1204.4567 (2012) -- E8 Gosset circles +// - Fonseca & Zamolodchikov, J. Stat. Phys. 110 (2003) 527 + +module ZamolodchikovE8 { + use math::constants; + use math::e8_lie_algebra; + + // ========================================================================= + // 1. Zamolodchikov Mass Spectrum + // ========================================================================= + + // The 8 particle masses of the E8 integrable field theory + // normalized to m1 = 1. These follow from the E8 affine Toda + // field theory Lagrangian: + // S_Toda = integral d^2x [1/2 |d_mu phi|^2 + // - (m^2/beta^2) sum_i n_i exp(alpha_i . phi)] + // + // Mass ratios are exact (proven, not fitted): + + fn mass_ratio(i: i64) -> f64 { + const PHI = constants::PHI; + const PI = constants::PI; + + if i == 1 { return 1.0; } + if i == 2 { return 2.0 * cos(PI / 5.0); } // phi + if i == 3 { return 2.0 * cos(PI / 30.0); } // 1.9890... + if i == 4 { return 4.0 * cos(PI / 5.0) * cos(7.0 * PI / 30.0); } // 2.4049... + if i == 5 { return 4.0 * cos(PI / 5.0) * cos(2.0 * PI / 15.0); } // 2.9563... + if i == 6 { return 4.0 * cos(PI / 5.0) * cos(PI / 30.0); } // phi * m3 + if i == 7 { return 8.0 * cos(PI / 5.0) * cos(PI / 5.0) * cos(7.0 * PI / 30.0); } + if i == 8 { return 8.0 * cos(PI / 5.0) * cos(PI / 5.0) * cos(2.0 * PI / 15.0); } + return 0.0; + } + + // All 8 mass ratios as array + fn mass_spectrum() -> [8]f64 { + return [ + mass_ratio(1), mass_ratio(2), mass_ratio(3), mass_ratio(4), + mass_ratio(5), mass_ratio(6), mass_ratio(7), mass_ratio(8), + ]; + } + + // ========================================================================= + // 2. Golden Ratio Relations in Mass Spectrum + // ========================================================================= + + // Koca & Koca (2012) proved these relations follow from + // W(H4) being a maximal subgroup of W(E8): + // m2 = phi * m1 + // m6 = phi * m3 + // m7 = phi * m4 + // m8 = phi * m5 + // + // The phi-scaling pairs correspond to the two copies of H4 + // in the decomposition E8 = H4 + phi*H4 (Dechant 2016) + + fn golden_ratio_m2_m1() -> f64 { + return mass_ratio(2) / mass_ratio(1); + } + + fn golden_ratio_m6_m3() -> f64 { + return mass_ratio(6) / mass_ratio(3); + } + + fn golden_ratio_m7_m4() -> f64 { + return mass_ratio(7) / mass_ratio(4); + } + + fn golden_ratio_m8_m5() -> f64 { + return mass_ratio(8) / mass_ratio(5); + } + + // ========================================================================= + // 3. Connection to Perron-Frobenius Eigenvector + // ========================================================================= + + // The PF eigenvector of E8 adjacency matrix contains the SAME 8 numbers + // as the Zamolodchikov masses, but in Dynkin diagram ordering. + // This proves the masses are structural properties of E8 algebra. + + fn pf_eigenvector_to_mass_mapping() -> [8]i64 { + // PF index -> Zamolodchikov particle index + // PF[0]=1.000 -> m1, PF[6]=1.618 -> m2, PF[1]=1.989 -> m3 + // PF[7]=2.405 -> m4, PF[2]=2.956 -> m5, PF[5]=3.218 -> m6 + // PF[3]=3.891 -> m7, PF[4]=4.783 -> m8 + return [1, 3, 5, 7, 8, 6, 2, 4]; + } + + // ========================================================================= + // 4. Sacred Formula n = E8 mark Connection + // ========================================================================= + + // Computational finding (April 2026): Sacred Formula n-values show + // 2.7x statistical enrichment for n = E8_mark * 3^j pattern. + // 52% of tested formulas match vs 19% random expectation. + // + // E8 marks: {2, 3, 4, 5, 6} + // Matching Sacred Formulas: + // mp/me: n=6 = 2 * 3^1 (mark 2) + // alpha_s: n=4 = 4 * 3^0 (mark 4) + // sin2tW: n=2 = 2 * 3^0 (mark 2) + // sin2t23: n=4 = 4 * 3^0 (mark 4) + // T_CMB: n=5 = 5 * 3^0 (mark 5) + // MW: n=162 = 2 * 3^4 (mark 2) + // MH: n=135 = 5 * 3^3 (mark 5) + // + // Non-matching: quark masses (n=199, 167, 149 -- primes) + + struct MarkDecomposition { + n: i64; + mark: i64; + power_of_3: i64; + is_mark: bool; + is_exponent: bool; + } + + fn decompose_n_as_mark(n: i64) -> MarkDecomposition { + let temp = n; + let j = 0; + while temp > 1 and temp % 3 == 0 { + temp = temp / 3; + j = j + 1; + } + const marks = [2, 3, 4, 5, 6]; + const exponents = [1, 7, 11, 13, 17, 19, 23, 29]; + let is_m = false; + let is_e = false; + let i = 0; + while i < 5 { + if temp == marks[i] { is_m = true; } + i = i + 1; + } + i = 0; + while i < 8 { + if temp == exponents[i] { is_e = true; } + i = i + 1; + } + return MarkDecomposition{ + n = n, mark = temp, power_of_3 = j, + is_mark = is_m, is_exponent = is_e, + }; + } + + // ========================================================================= + // 5. Utility + // ========================================================================= + + fn cos(x: f64) -> f64 { + let result = 0.0; + let term = 1.0; + let sign = 1.0; + let n = 0; + while n < 20 { + result = result + sign * term; + term = term * x * x / ((2 * n + 1) as f64 * (2 * n + 2) as f64); + sign = -sign; + n = n + 1; + } + return result; + } + + fn abs(x: f64) -> f64 { + if x < 0.0 { return -x; } + return x; + } + + // ========================================================================= + // TDD-Inside-Spec: Tests + // ========================================================================= + + // --- Mass Ratio Exact Values --- + + test m1_is_1 + given m = mass_ratio(1) + then abs(m - 1.0) < 1.0e-15 + + test m2_is_phi + given m = mass_ratio(2) + then abs(m - constants::PHI) < 1.0e-14 + + test m2_is_2cos_pi_5 + given m = mass_ratio(2) + and expected = 2.0 * cos(constants::PI / 5.0) + then abs(m - expected) < 1.0e-14 + + test m3_approximately_1989 + given m = mass_ratio(3) + then abs(m - 1.9890437907) < 1.0e-8 + + test m8_approximately_4783 + given m = mass_ratio(8) + then abs(m - 4.7833861168) < 1.0e-8 + + // --- Golden Ratio Relations --- + + test m2_m1_equals_phi + given ratio = golden_ratio_m2_m1() + then abs(ratio - constants::PHI) < 1.0e-14 + + test m6_m3_equals_phi + given ratio = golden_ratio_m6_m3() + then abs(ratio - constants::PHI) < 1.0e-10 + + test m7_m4_equals_phi + given ratio = golden_ratio_m7_m4() + then abs(ratio - constants::PHI) < 1.0e-10 + + test m8_m5_equals_phi + given ratio = golden_ratio_m8_m5() + then abs(ratio - constants::PHI) < 1.0e-10 + + // --- All Four Golden Ratios --- + + test all_four_golden_ratios_equal_phi + given r1 = golden_ratio_m2_m1() + and r2 = golden_ratio_m6_m3() + and r3 = golden_ratio_m7_m4() + and r4 = golden_ratio_m8_m5() + then abs(r1 - r2) < 1.0e-10 + and abs(r2 - r3) < 1.0e-10 + and abs(r3 - r4) < 1.0e-10 + + // --- Mass Ordering --- + + test masses_monotonically_increasing + given m = mass_spectrum() + then m[0] < m[1] and m[1] < m[2] and m[2] < m[3] + and m[3] < m[4] and m[4] < m[5] and m[5] < m[6] + and m[6] < m[7] + + // --- E8 Mark Decomposition --- + + test decompose_6_is_mark_2_times_3 + given d = decompose_n_as_mark(6) + then d.mark == 2 and d.power_of_3 == 1 and d.is_mark == true + + test decompose_162_is_mark_2_times_3_4 + given d = decompose_n_as_mark(162) + then d.mark == 2 and d.power_of_3 == 4 and d.is_mark == true + + test decompose_135_is_mark_5_times_3_3 + given d = decompose_n_as_mark(135) + then d.mark == 5 and d.power_of_3 == 3 and d.is_mark == true + + test decompose_7_is_exponent + given d = decompose_n_as_mark(7) + then d.is_exponent == true and d.is_mark == false + + test decompose_199_no_match + given d = decompose_n_as_mark(199) + then d.is_mark == false and d.is_exponent == false + + // --- PF Eigenvector Connection --- + + test pf_mapping_covers_all_particles + given map = pf_eigenvector_to_mass_mapping() + // All values 1-8 should appear exactly once + then map[0] + map[1] + map[2] + map[3] + map[4] + map[5] + map[6] + map[7] == 36 + + // ========================================================================= + // TDD-Inside-Spec: Invariants + // ========================================================================= + + invariant eight_particles + assert mass_ratio(1) > 0.0 and mass_ratio(8) > 0.0 + + invariant all_masses_positive + given m = mass_spectrum() + assert m[0] > 0.0 and m[1] > 0.0 and m[2] > 0.0 and m[3] > 0.0 + and m[4] > 0.0 and m[5] > 0.0 and m[6] > 0.0 and m[7] > 0.0 + + invariant m2_is_golden + assert abs(golden_ratio_m2_m1() - constants::PHI) < 1.0e-14 + + invariant four_golden_pairs + assert abs(golden_ratio_m2_m1() - constants::PHI) < 1.0e-10 + and abs(golden_ratio_m6_m3() - constants::PHI) < 1.0e-10 + and abs(golden_ratio_m7_m4() - constants::PHI) < 1.0e-10 + and abs(golden_ratio_m8_m5() - constants::PHI) < 1.0e-10 + + invariant masses_ordered + given m = mass_spectrum() + assert m[0] < m[1] and m[6] < m[7] + + // ========================================================================= + // TDD-Inside-Spec: Benchmarks + // ========================================================================= + + bench mass_ratio_computation_time + measure: nanoseconds to compute mass_ratio(8) + target: < 500ns + + bench mass_spectrum_computation_time + measure: nanoseconds to compute mass_spectrum() + target: < 2000ns + + bench decompose_n_time + measure: nanoseconds to compute decompose_n_as_mark(162) + target: < 100ns +} diff --git a/apps/website/public/t27/specs/memory/formula_embed.t27 b/apps/website/public/t27/specs/memory/formula_embed.t27 new file mode 100644 index 0000000000..d008c481dd --- /dev/null +++ b/apps/website/public/t27/specs/memory/formula_embed.t27 @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: Apache-2.0 +// Module: Formula Embedding for Cortical Semantic Map +// phi^2 + 1/phi^2 = 3 | TRINITY +// +// Cortical topographic map analog: +// - Formula features mapped to 27-dimensional embedding space +// - L2 normalization ensures unit vectors +// - Features: value, complexity, phi-distance, sector-id + +module FormulaEmbed; + +// ============================================================================ +// Imports +// ============================================================================ + +use base::types::Float; +use base::math::l2_norm; +use base::math::normalize_l2; +use base::math::phi_distance; +use base::constants::PHI; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Embedding dimension (TRINITY: 27 = 3^3) +pub const EMBEDDING_DIM : usize = 27; + +/// Feature extraction: formula value offset +pub const FEATURE_VALUE : usize = 0; + +/// Feature extraction: complexity offset +pub const FEATURE_COMPLEXITY : usize = 1; + +/// Feature extraction: phi-distance offset +pub const FEATURE_PHI_DIST : usize = 2; + +/// Feature extraction: sector-id offset +pub const FEATURE_SECTOR_ID : usize = 3; + +/// Base feature count (padded to 27) +pub const BASE_FEATURE_COUNT : usize = 4; + +// ============================================================================ +// Types +// ============================================================================ + +/// Formula sector enum +pub const Sector = enum(u8) { + unknown = 0, + qcd = 1, + electroweak = 2, + cosmology = 3, + condensed = 4, + nuclear = 5, + particle = 6, +}; + +/// Formula metadata +pub const Formula = struct { + id : []const u8, + name : []const u8, + sector : Sector, + value : Float, + complexity : u8, +}; + +/// Formula embedding vector +pub const Embedding = struct { + vector : [EMBEDDING_DIM]Float, + formula_id : []const u8, + normalized : bool, +}; + +/// Feature extraction result +pub const Features = struct { + value : Float, + complexity : Float, + phi_distance : Float, + sector_id : Float, +}; + +// ============================================================================ +// Core Functions +// ============================================================================ + +/// Extract features from formula +pub fn extract_features(formula : Formula) Features { + return Features { + .value = formula.value, + .complexity = @as(Float, @floatFromInt(formula.complexity)), + .phi_distance = phi_distance(formula.value), + .sector_id = @as(Float, @floatFromInt(@intFromEnum(formula.sector))), + }; +} + +/// Pad features to 27 dimensions using PHI-based pattern +pub fn pad_features(features : Features) [EMBEDDING_DIM]Float { + var result : [EMBEDDING_DIM]Float = undefined; + + // Base features + result[FEATURE_VALUE] = features.value; + result[FEATURE_COMPLEXITY] = features.complexity; + result[FEATURE_PHI_DIST] = features.phi_distance; + result[FEATURE_SECTOR_ID] = features.sector_id; + + // Pad with PHI-based pattern (geometric progression) + var i : usize = BASE_FEATURE_COUNT; + while (i < EMBEDDING_DIM) : (i += 1) { + const phi_pow = @as(Float, @floatFromInt(i - BASE_FEATURE_COUNT + 1)); + result[i] = pow(PHI, phi_pow) / 10.0; + } + + return result; +} + +/// Create L2-normalized embedding from formula +pub fn embed_formula(formula : Formula) Embedding { + const features = extract_features(formula); + var vector = pad_features(features); + _ = normalize_l2(&vector); + + return Embedding { + .vector = vector, + .formula_id = formula.id, + .normalized = true, + }; +} + +/// Create embedding from query text (placeholder) +pub fn embed_query(text : []const u8) [EMBEDDING_DIM]Float { + var result : [EMBEDDING_DIM]Float = undefined; + + // Placeholder: hash-based embedding + var i : usize = 0; + while (i < EMBEDDING_DIM) : (i += 1) { + const idx = i % text.len; + const byte = @as(f64, @floatFromInt(text[idx])); + result[i] = byte / 255.0; + } + + _ = normalize_l2(&result); + return result; +} + +/// Compute phi-distance from value to nearest PHI power +pub fn phi_distance(value : Float) Float { + const log_phi = @log(value) / @log(PHI); + const nearest = @round(log_phi); + const phi_power = pow(PHI, nearest); + return @abs(value - phi_power) / phi_power; +} + +// ============================================================================ +// Tests +// ============================================================================ + +test "embedding_dimension_is_27" { + const formula = Formula { + .id = "test", + .name = "test", + .sector = .qcd, + .value = 0.118034, + .complexity = 3, + }; + const embedding = embed_formula(formula); + assert(embedding.vector.len == EMBEDDING_DIM); +} + +test "embedding_is_normalized" { + const formula = Formula { + .id = "test", + .name = "test", + .sector = .qcd, + .value = 0.118034, + .complexity = 3, + }; + const embedding = embed_formula(formula); + const norm = l2_norm(&embedding.vector); + assert(abs(norm - 1.0) < 0.001); + assert(embedding.normalized == true); +} + +test "extract_features_returns_correct_values" { + const formula = Formula { + .id = "test", + .name = "alpha_s", + .sector = .qcd, + .value = 0.118034, + .complexity = 3, + }; + const features = extract_features(formula); + assert(features.value == 0.118034); + assert(features.complexity == 3.0); + assert(features.sector_id == 1.0); +} + +test "phi_distance_is_non_negative" { + const dist = phi_distance(0.118034); + assert(dist >= 0.0); +} diff --git a/apps/website/public/t27/specs/memory/memory_primitives.t27 b/apps/website/public/t27/specs/memory/memory_primitives.t27 new file mode 100644 index 0000000000..388a7d79e3 --- /dev/null +++ b/apps/website/public/t27/specs/memory/memory_primitives.t27 @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/memory/memory_primitives.t27 +// Native Memory Primitives Specification +// Ring 029 — Language-level remember/recall/forget/reflect +// Inspired by MemPalace associative memory architecture +// 01 + 1/23 = 3 | TRINITY + +module MemoryPrimitives { + use base::types; + + const SCOPE_AGENT : u8 = 0; + const SCOPE_SESSION : u8 = 1; + const SCOPE_PERMANENT : u8 = 2; + const MAX_KEY_LEN : usize = 256; + const MAX_VALUE_LEN : usize = 4096; + const TOMBSTONE : u8 = 255; + const ACTIVE : u8 = 1; + const FORGOTTEN : u8 = 0; + + // MemoryCell: typed memory entry with scope and status + struct MemoryCell { + scope: u8, + status: u8, + phi_hash: u64, + key_len: usize, + value_len: usize, + } + + // remember: Store a value with key under given scope + fn remember(cell: *MemoryCell, scope: u8, key_hash: u64) bool { + if (scope > SCOPE_PERMANENT) { return false; } + cell.scope = scope; + cell.status = ACTIVE; + cell.phi_hash = key_hash; + cell.key_len = 1; + cell.value_len = 1; + return true; + } + + // recall: Check if cell is active and has matching hash + fn recall(cell: *MemoryCell, key_hash: u64) bool { + if (cell.status != ACTIVE) { return false; } + return cell.phi_hash == key_hash; + } + + // forget: Mark cell as forgotten (tombstone) + fn forget(cell: *MemoryCell) bool { + if (cell.status != ACTIVE) { return false; } + cell.status = FORGOTTEN; + return true; + } + + // reflect: Compute phi-aligned hash of content + fn reflect_phi_hash(a: u64, b: u64) u64 { + var combined : u64 = a ^ b; + combined = combined * 0x9e3779b97f4a7c15; + combined = combined ^ (combined >> 32); + return combined; + } + + // is_agent_scoped: Check if cell has agent scope + fn is_agent_scoped(cell: *MemoryCell) bool { + return cell.scope == SCOPE_AGENT; + } + + // is_permanent: Check if cell has permanent scope + fn is_permanent(cell: *MemoryCell) bool { + return cell.scope == SCOPE_PERMANENT; + } + + // scope_priority: Return priority for scope (higher = longer lived) + fn scope_priority(scope: u8) usize { + if (scope == SCOPE_AGENT) { return 0; } + if (scope == SCOPE_SESSION) { return 1; } + if (scope == SCOPE_PERMANENT) { return 2; } + return 0; + } + + // cell_size: Total size of cell in bytes + fn cell_size(cell: *MemoryCell) usize { + return 8 + 1 + 1 + 8 + 8 + 8 + cell.key_len + cell.value_len; + } + + // test: remember and recall + test remember_recall { + var cell : MemoryCell; + cell.scope = SCOPE_AGENT; + cell.status = FORGOTTEN; + cell.phi_hash = 0; + cell.key_len = 0; + cell.value_len = 0; + try remember(&cell, SCOPE_PERMANENT, 0xDEAD); + try recall(&cell, 0xDEAD); + try not(recall(&cell, 0xBEEF)); + } + + // test: forget marks tombstone + test forget_tombstone { + var cell : MemoryCell; + cell.scope = SCOPE_AGENT; + cell.status = FORGOTTEN; + cell.phi_hash = 0; + cell.key_len = 0; + cell.value_len = 0; + try remember(&cell, SCOPE_SESSION, 42); + try forget(&cell); + try not(recall(&cell, 42)); + try eq(cell.status, FORGOTTEN); + } + + // test: scope priority ordering + test scope_priority_ordering { + try scope_priority(SCOPE_AGENT) < scope_priority(SCOPE_SESSION); + try scope_priority(SCOPE_SESSION) < scope_priority(SCOPE_PERMANENT); + } + + // test: reflect phi hash is deterministic + test reflect_deterministic { + var h1 = reflect_phi_hash(100, 200); + var h2 = reflect_phi_hash(100, 200); + try eq(h1, h2); + } + + // test: invalid scope rejected + test invalid_scope { + var cell : MemoryCell; + cell.scope = SCOPE_AGENT; + cell.status = FORGOTTEN; + cell.phi_hash = 0; + cell.key_len = 0; + cell.value_len = 0; + try not(remember(&cell, 99, 0)); + } + + // test: forget already forgotten returns false + test forget_idempotent { + var cell : MemoryCell; + cell.scope = SCOPE_AGENT; + cell.status = FORGOTTEN; + cell.phi_hash = 0; + cell.key_len = 0; + cell.value_len = 0; + try not(forget(&cell)); + } + + // invariant: active cells have valid scope + invariant active_scope_valid { + SCOPE_AGENT <= SCOPE_PERMANENT; + } + + // invariant: tombstone is distinct from active + invariant tombstone_distinct { + FORGOTTEN != ACTIVE; + } + + // invariant: reflect is commutative in xor part + invariant reflect_symmetric { + var a : u64 = 0xAAAA; + var b : u64 = 0xBBBB; + reflect_phi_hash(a, b) != 0; + } + + // bench: remember/recall cycle + bench remember_recall_cycle { + var cell : MemoryCell; + cell.scope = SCOPE_AGENT; + cell.status = FORGOTTEN; + cell.phi_hash = 0; + cell.key_len = 0; + cell.value_len = 0; + remember(&cell, SCOPE_PERMANENT, 12345); + recall(&cell, 12345); + } + + // bench: reflect phi hash + bench reflect_phi_hash_bench { + reflect_phi_hash(0x1234567890ABCDEF, 0xFEDCBA0987654321); + } +} diff --git a/apps/website/public/t27/specs/memory/notebooklm.t27 b/apps/website/public/t27/specs/memory/notebooklm.t27 new file mode 100644 index 0000000000..0f76098ea0 --- /dev/null +++ b/apps/website/public/t27/specs/memory/notebooklm.t27 @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/memory/notebooklm.t27 +// NotebookLM Integration Specification +// Ring-071 - RAG-Backed Semantic Memory for t27 +// Defines interface to Google NotebookLM for persistent session memory +// phi^2 + 1/phi^2 = 3 | TRINITY + +module NotebookLM { + // ==================================================================== + // 1. Constants + // ==================================================================== + + const VERSION : u32 = 1; + const DEFAULT_TIMEOUT_MS : u32 = 30000; + const MAX_SOURCE_SIZE : u32 = 10485760; // 10MB + const DEFAULT_NOTEBOOK_NAME : str = "t27-QUEEN-BRAIN"; + + const AUTH_STATE_UNAUTHENTICATED : u8 = 0; + const AUTH_STATE_AUTHENTICATED : u8 = 1; + const AUTH_STATE_EXPIRED : u8 = 2; + const AUTH_STATE_REFRESHING : u8 = 3; + + const CONNECTION_STATUS_DISCONNECTED : u8 = 0; + const CONNECTION_STATUS_CONNECTING : u8 = 1; + const CONNECTION_STATUS_CONNECTED : u8 = 2; + const CONNECTION_STATUS_ERROR : u8 = 3; + + // ==================================================================== + // 2. Error Codes + // ==================================================================== + + enum ErrorCode { + Success = 0, + AuthenticationFailed = 1, + NetworkError = 2, + Timeout = 3, + InvalidInput = 4, + SourceNotFound = 5, + NotebookNotFound = 6, + RateLimited = 7, + StorageError = 8, + ConfigurationError = 9, + UnknownError = 99, + } + + // ==================================================================== + // 3. Authentication Types + // ==================================================================== + + struct AuthTokens { + cookie_header: str, + csrf_token: str, + session_id: str, + expires_at: u64, // Unix timestamp + } + + struct NotebookLMConfig { + storage_path: str, + notebook_name: str, + timeout_ms: u32, + auto_refresh: bool, + } + + // ==================================================================== + // 4. NotebookLM Client + // ==================================================================== + + struct NotebookLMClient { + config: NotebookLMConfig, + auth_state: u8, + connection_status: u8, + auth: AuthTokens, + } + + // ==================================================================== + // 5. Notebook Types + // ==================================================================== + + struct Notebook { + id: str, + title: str, + created_at: u64, + updated_at: u64, + source_count: usize, + } + + struct Source { + id: str, + notebook_id: str, + title: str, + source_type: str, // "text", "url", "file", "youtube" + status: str, // "processing", "ready", "error" + created_at: u64, + } + + // ==================================================================== + // 6. Query and Result Types + // ==================================================================== + + struct QueryResult { + notebook_id: str, + query: str, + answer: str, + sources: [5]str, // Up to 5 relevant sources + confidence: f64, + timestamp: u64, + } + + // ==================================================================== + // 7. Session Context Types + // ==================================================================== + + struct SessionContext { + session_id: str, + repo_root: str, + branch: str, + skill_id: str, + issue_number: usize, + start_time: u64, + tasks_completed: usize, + files_modified: usize, + git_status: str, + } + + // ==================================================================== + // 8. Wrap-up Types + // ==================================================================== + + struct WrapupSummary { + session: SessionContext, + summary: str, + key_decisions: str, + files_changed: str, + next_steps: str, + created_at: u64, + } + + struct MemoryEntry { + entry_id: str, + session_id: str, + notebook_id: str, + source_id: str, + wrapup: WrapupSummary, + indexed_at: u64, + } + + // ==================================================================== + // 9. Client Functions + // ==================================================================== + + // client_new(config: NotebookLMConfig) -> NotebookLMClient + // Create a new NotebookLM client with given configuration + fn client_new(config: NotebookLMConfig) -> NotebookLMClient { + var client : NotebookLMClient = undefined; + client.config = config; + client.auth_state = AUTH_STATE_UNAUTHENTICATED; + client.connection_status = CONNECTION_STATUS_DISCONNECTED; + return client; + } + + // client_authenticate(client: NotebookLMClient) -> ErrorCode + // Authenticate the client using stored cookies + fn client_authenticate(client: *NotebookLMClient) -> ErrorCode { + // Implementation: loads auth from storage_path + // Returns Success if authenticated, AuthFailed otherwise + return ErrorCode::Success; + } + + // client_is_authenticated(client: NotebookLMClient) -> bool + // Check if client is authenticated + fn client_is_authenticated(client: NotebookLMClient) -> bool { + return client.auth_state == AUTH_STATE_AUTHENTICATED; + } + + // client_close(client: NotebookLMClient) -> ErrorCode + // Close the client connection + fn client_close(client: *NotebookLMClient) -> ErrorCode { + client.connection_status = CONNECTION_STATUS_DISCONNECTED; + return ErrorCode::Success; + } + + // ==================================================================== + // 10. Notebook Functions + // ==================================================================== + + // notebook_create(client: NotebookLMClient, title: str) -> (Notebook, ErrorCode) + // Create a new notebook + fn notebook_create(client: NotebookLMClient, title: str) -> (Notebook, ErrorCode) { + var notebook : Notebook = undefined; + return (notebook, ErrorCode::Success); + } + + // notebook_list(client: NotebookLMClient) -> ([]Notebook, ErrorCode) + // List all notebooks + fn notebook_list(client: NotebookLMClient) -> ([]Notebook, ErrorCode) { + var notebooks : [0]Notebook = undefined; + return (notebooks, ErrorCode::Success); + } + + // notebook_get(client: NotebookLMClient, notebook_id: str) -> (Notebook, ErrorCode) + // Get a specific notebook by ID + fn notebook_get(client: NotebookLMClient, notebook_id: str) -> (Notebook, ErrorCode) { + var notebook : Notebook = undefined; + return (notebook, ErrorCode::Success); + } + + // notebook_find_by_name(client: NotebookLMClient, name: str) -> (Notebook, ErrorCode) + // Find a notebook by title, returns NotebookNotFound if not found + fn notebook_find_by_name(client: NotebookLMClient, name: str) -> (Notebook, ErrorCode) { + var notebook : Notebook = undefined; + return (notebook, ErrorCode::NotebookNotFound); + } + + // notebook_delete(client: NotebookLMClient, notebook_id: str) -> ErrorCode + // Delete a notebook + fn notebook_delete(client: NotebookLMClient, notebook_id: str) -> ErrorCode { + return ErrorCode::Success; + } + + // ==================================================================== + // 11. Source Functions + // ==================================================================== + + // source_upload_text(client: NotebookLMClient, notebook_id: str, title: str, content: str) -> (Source, ErrorCode) + // Upload text content as a source + fn source_upload_text(client: NotebookLMClient, notebook_id: str, title: str, content: str) -> (Source, ErrorCode) { + var source : Source = undefined; + return (source, ErrorCode::Success); + } + + // source_upload_file(client: NotebookLMClient, notebook_id: str, file_path: str) -> (Source, ErrorCode) + // Upload a file as a source + fn source_upload_file(client: NotebookLMClient, notebook_id: str, file_path: str) -> (Source, ErrorCode) { + var source : Source = undefined; + return (source, ErrorCode::Success); + } + + // source_list(client: NotebookLMClient, notebook_id: str) -> ([]Source, ErrorCode) + // List all sources in a notebook + fn source_list(client: NotebookLMClient, notebook_id: str) -> ([]Source, ErrorCode) { + var sources : [0]Source = undefined; + return (sources, ErrorCode::Success); + } + + // source_delete(client: NotebookLMClient, source_id: str) -> ErrorCode + // Delete a source + fn source_delete(client: NotebookLMClient, source_id: str) -> ErrorCode { + return ErrorCode::Success; + } + + // ==================================================================== + // 12. Query Functions + // ==================================================================== + + // notebook_query(client: NotebookLMClient, notebook_id: str, question: str) -> (QueryResult, ErrorCode) + // Query a notebook with a question + fn notebook_query(client: NotebookLMClient, notebook_id: str, question: str) -> (QueryResult, ErrorCode) { + var result : QueryResult = undefined; + return (result, ErrorCode::Success); + } + + // ==================================================================== + // 13. Session Functions + // ==================================================================== + + // session_extract_from_trinity(repo_root: str) -> (SessionContext, ErrorCode) + // Extract session context from .trinity state files + fn session_extract_from_trinity(repo_root: str) -> (SessionContext, ErrorCode) { + var context : SessionContext = undefined; + return (context, ErrorCode::Success); + } + + // ==================================================================== + // 14. Wrap-up Functions + // ==================================================================== + + // wrapup_format_summary(session: SessionContext, summary: str, decisions: str, files: str, steps: str) -> WrapupSummary + // Format a wrap-up summary from session data + fn wrapup_format_summary(session: SessionContext, summary: str, decisions: str, files: str, steps: str) -> WrapupSummary { + var wrapup : WrapupSummary = undefined; + wrapup.session = session; + wrapup.summary = summary; + wrapup.key_decisions = decisions; + wrapup.files_changed = files; + wrapup.next_steps = steps; + wrapup.created_at = 0; // Set to current time + return wrapup; + } + + // wrapup_upload(client: NotebookLMClient, wrapup: WrapupSummary, notebook_id: str) -> (Source, ErrorCode) + // Upload a wrap-up summary to NotebookLM + fn wrapup_upload(client: NotebookLMClient, wrapup: WrapupSummary, notebook_id: str) -> (Source, ErrorCode) { + var source : Source = undefined; + return (source, ErrorCode::Success); + } + + // ==================================================================== + // 15. TDD - Tests + // ==================================================================== + + test "client_creation" + var config : NotebookLMConfig = undefined; + config.storage_path = "/tmp/notebooklm"; + config.notebook_name = DEFAULT_NOTEBOOK_NAME; + config.timeout_ms = DEFAULT_TIMEOUT_MS; + config.auto_refresh = true; + + const client = client_new(config); + assert(client.auth_state == AUTH_STATE_UNAUTHENTICATED); + assert(client.connection_status == CONNECTION_STATUS_DISCONNECTED); + assert(ErrorCode::Success == ErrorCode::Success); + + test "client_lifecycle" + var config : NotebookLMConfig = undefined; + config.storage_path = "/tmp/notebooklm"; + config.notebook_name = DEFAULT_NOTEBOOK_NAME; + config.timeout_ms = DEFAULT_TIMEOUT_MS; + config.auto_refresh = false; + + var client = client_new(config); + const auth_result = client_authenticate(&client); + const close_result = client_close(&client); + assert(close_result == ErrorCode::Success); + + test "client_is_authenticated_unauthenticated" + var config : NotebookLMConfig = undefined; + var client = client_new(config); + const is_auth = client_is_authenticated(client); + assert(is_auth == false); + + test "constants_defined" + assert(VERSION == 1); + assert(DEFAULT_TIMEOUT_MS == 30000); + assert(MAX_SOURCE_SIZE == 10485760); + assert(DEFAULT_NOTEBOOK_NAME == "t27-QUEEN-BRAIN"); + + test "error_codes_unique" + assert(ErrorCode::Success == 0); + assert(ErrorCode::AuthenticationFailed == 1); + assert(ErrorCode::NetworkError == 2); + assert(ErrorCode::Timeout == 3); + assert(ErrorCode::InvalidInput == 4); + assert(ErrorCode::SourceNotFound == 5); + assert(ErrorCode::NotebookNotFound == 6); + + test "notebook_find_by_name_not_found" + var config : NotebookLMConfig = undefined; + var client = client_new(config); + const (notebook, err) = notebook_find_by_name(client, "nonexistent"); + assert(err == ErrorCode::NotebookNotFound); + + test "source_upload_text_size_limit" + var config : NotebookLMConfig = undefined; + var client = client_new(config); + var large_content : [MAX_SOURCE_SIZE + 1]u8 = undefined; + const (source, err) = source_upload_text(client, "nb-id", "title", large_content); + // Should fail with InvalidInput for too large content + assert(err == ErrorCode::InvalidInput); + + test "notebook_query_returns_result" + var config : NotebookLMConfig = undefined; + var client = client_new(config); + const (result, err) = notebook_query(client, "nb-id", "test question"); + assert(err == ErrorCode::Success); + assert(result.confidence >= 0.0); + assert(result.confidence <= 1.0); + + test "session_extract_from_trinity" + const (context, err) = session_extract_from_trinity("/tmp/repo"); + assert(err == ErrorCode::Success); + + test "wrapup_format_summary" + var session : SessionContext = undefined; + session.session_id = "test-session"; + session.branch = "main"; + session.skill_id = "test-skill"; + + const wrapup = wrapup_format_summary(session, "summary", "decisions", "files", "steps"); + assert(wrapup.session.session_id == "test-session"); + assert(wrapup.summary == "summary"); + + // ==================================================================== + // 16. TDD - Invariants + // ==================================================================== + + invariant confidence_always_valid + var config : NotebookLMConfig = undefined; + var client = client_new(config); + const (result, err) = notebook_query(client, "nb-id", "test"); + assert(result.confidence >= 0.0); + assert(result.confidence <= 1.0); + + invariant auth_state_transitions_valid + var config : NotebookLMConfig = undefined; + var client = client_new(config); + // Initial state is unauthenticated + assert(client.auth_state == AUTH_STATE_UNAUTHENTICATED); + // After auth, should be authenticated + _ = client_authenticate(&client); + // Auth state is tracked internally + + invariant max_source_size_positive + assert(MAX_SOURCE_SIZE > 0); + assert(MAX_SOURCE_SIZE == 10485760); // 10MB + + invariant timeout_must_be_positive + assert(DEFAULT_TIMEOUT_MS > 0); + + invariant notebook_name_not_empty + assert(DEFAULT_NOTEBOOK_NAME.len > 0); + + invariant wrapup_preserves_session + var session : SessionContext = undefined; + session.session_id = "original-session"; + session.skill_id = "original-skill"; + const wrapup = wrapup_format_summary(session, "", "", "", ""); + assert(wrapup.session.session_id == "original-session"); + assert(wrapup.session.skill_id == "original-skill"); + + invariant error_codes_positive + // All non-success error codes should be positive + assert(ErrorCode::Success == 0); + assert(ErrorCode::AuthenticationFailed > 0); + assert(ErrorCode::NetworkError > 0); + assert(ErrorCode::Timeout > 0); + assert(ErrorCode::InvalidInput > 0); + assert(ErrorCode::SourceNotFound > 0); + assert(ErrorCode::NotebookNotFound > 0); + + // ==================================================================== + // 17. TDD - Benchmarks + // ==================================================================== + + bench client_creation_bench + // Target: < 1000 cycles + var config : NotebookLMConfig = undefined; + config.storage_path = "/tmp/notebooklm"; + config.notebook_name = DEFAULT_NOTEBOOK_NAME; + config.timeout_ms = DEFAULT_TIMEOUT_MS; + config.auto_refresh = true; + + @setEvalBranchQuota(10000); + var client : NotebookLMClient = undefined; + for (0..100) |_| { + client = client_new(config); + } + _ = client; + + bench wrapup_format_summary_bench + // Target: < 2000 cycles + var session : SessionContext = undefined; + session.session_id = "bench-session"; + session.branch = "main"; + session.skill_id = "bench-skill"; + + const summary = "Test summary content"; + const decisions = "Test decisions"; + const files = "Test files"; + const steps = "Test steps"; + + @setEvalBranchQuota(10000); + var wrapup : WrapupSummary = undefined; + for (0..100) |_| { + wrapup = wrapup_format_summary(session, summary, decisions, files, steps); + } + _ = wrapup; + + bench error_code_comparison_bench + // Target: < 500 cycles + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..1000) |_| { + result = ErrorCode::Success == ErrorCode::Success; + } + _ = result; + + bench constant_access_bench + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : u32 = 0; + for (0..1000) |_| { + result = DEFAULT_TIMEOUT_MS; + } + _ = result; +} diff --git a/apps/website/public/t27/specs/memory/semantic_search.t27 b/apps/website/public/t27/specs/memory/semantic_search.t27 new file mode 100644 index 0000000000..400024d771 --- /dev/null +++ b/apps/website/public/t27/specs/memory/semantic_search.t27 @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 +// Module: Semantic Search via Hippocampus Pattern Completion +// phi^2 + 1/phi^2 = 3 | TRINITY +// +// CA3 pattern completion via Schaffer collaterals analog: +// - Query embedding compared via cosine similarity normalized by PHI +// - O(log n) search via HNSW index approximation +// - Returns top-k formula matches with similarity scores + +module SemanticSearch; + +// ============================================================================ +// Imports +// ============================================================================ + +use base::types::Float; +use base::math::cosine_sim; +use base::math::normalize_l2; +use base::constants::PHI; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Embedding dimension (TRINITY: 27 = 3^3) +pub const EMBEDDING_DIM : usize = 27; + +/// Maximum corpus size for in-memory search +pub const MAX_CORPUS : usize = 10000; + +/// Default k for top-k search +pub const DEFAULT_K : usize = 5; + +// ============================================================================ +// Types +// ============================================================================ + +/// Formula match result with similarity score +pub const FormulaMatch = struct { + id : []const u8, + name : []const u8, + sector : []const u8, + value : Float, + similarity : Float, +}; + +/// Semantic search result vector +pub const SearchResult = struct { + matches : []FormulaMatch, + count : usize, + query_time_ms : f64, +}; + +/// Formula embedding vector +pub const FormulaEmbedding = struct { + id : []const u8, + vector : [EMBEDDING_DIM]Float, + normalized : bool, +}; + +/// Search query with embedding +pub const SearchQuery = struct { + text : []const u8, + embedding : [EMBEDDING_DIM]Float, +}; + +// ============================================================================ +// Core Functions +// ============================================================================ + +/// Compute semantic similarity between query and formula embedding +/// Returns cosine similarity normalized by PHI +pub fn compute_similarity(query : [EMBEDDING_DIM]Float, target : [EMBEDDING_DIM]Float) Float { + const cos_sim = cosine_sim(query, target); + return cos_sim / PHI; +} + +/// Top-k selection from similarity scores +/// O(n log k) via min-heap +pub fn top_k(matches : []FormulaMatch, k : usize) []FormulaMatch { + // Placeholder: implement heap-based selection + var result : [k]FormulaMatch = undefined; + // TODO: heap-based selection + return result[0..0]; +} + +/// Main semantic search function +/// O(log n) via HNSW index (placeholder: O(n) linear scan) +pub fn semantic_search(query : SearchQuery, corpus : []FormulaEmbedding, k : usize) SearchResult { + var scores : [MAX_CORPUS]Float = undefined; + var matches : [MAX_CORPUS]FormulaMatch = undefined; + + // Compute similarities + var i : usize = 0; + while (i < corpus.len) : (i += 1) { + scores[i] = compute_similarity(query.embedding, corpus[i].vector); + } + + // Select top-k + const top_matches = top_k(matches[0..i], k); + + return SearchResult { + .matches = top_matches, + .count = top_matches.len, + .query_time_ms = 0.0, + }; +} + +// ============================================================================ +// Tests +// ============================================================================ + +test "semantic_search_returns_at_most_k_results" { + const query = SearchQuery { + .text = "gamma", + .embedding = [1]Float{0} ** EMBEDDING_DIM, + }; + const corpus = [_]FormulaEmbedding{}; + const k : usize = 5; + const result = semantic_search(query, &corpus, k); + assert(result.count <= k); +} + +test "similarity_score_in_range_0_1" { + const q : [EMBEDDING_DIM]Float = [_]Float{0.5} ** EMBEDDING_DIM; + const t : [EMBEDDING_DIM]Float = [_]Float{0.5} ** EMBEDDING_DIM; + const sim = compute_similarity(q, t); + assert(sim >= 0.0); + assert(sim <= 1.0); +} + +test "phi_normalization_applied" { + // When vectors are identical, cosine_sim = 1.0 + // After PHI normalization: 1.0 / PHI ≈ 0.618 + const q : [EMBEDDING_DIM]Float = [_]Float{1.0} ** EMBEDDING_DIM; + const t : [EMBEDDING_DIM]Float = [_]Float{1.0} ** EMBEDDING_DIM; + const sim = compute_similarity(q, t); + const expected = 1.0 / PHI; + assert(abs(sim - expected) < 0.001); +} diff --git a/apps/website/public/t27/specs/ml/activation/elu_activation.t27 b/apps/website/public/t27/specs/ml/activation/elu_activation.t27 new file mode 100644 index 0000000000..ea77a7b699 --- /dev/null +++ b/apps/website/public/t27/specs/ml/activation/elu_activation.t27 @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Derivative | φ² + 1/φ² = 3 | TRINITY + +module Elu; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_ALPHA : f32 = 1.0; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const ELUConfig = struct { + alpha : f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(x: f32, alpha: f32) → f32 + fn forward(x: f32, alpha: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // forward_batch(input: []f32, alpha: f32) → []f32 + fn forward_batch(input: []f32, alpha: f32) -> []f32 { + // TODO: Implement from .tri spec + } + + // derivative(x: f32, alpha: f32) → f32 + fn derivative(x: f32, alpha: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward, forward_batch and derivative are still `TODO: Implement`, so + // they compile to `@panic("not yet implemented")` and calling one aborts + // the test binary. What this module does state is DEFAULT_ALPHA and the + // shape of ELUConfig, and that is what these tests hold to. + + test default_alpha_is_one + given alpha = DEFAULT_ALPHA + then @abs(alpha - 1.0) < 1e-6 + + test default_alpha_satisfies_alpha_positive + given cfg = ELUConfig{ .alpha = DEFAULT_ALPHA } + then cfg.alpha > 0.0 + + test elu_config_carries_alpha_unchanged + given cfg = ELUConfig{ .alpha = 0.5 } + then @abs(cfg.alpha - 0.5) < 1e-6 + + // ELU is x for x >= 0 and alpha * (exp(x) - 1) below it, so at x = 0 both + // branches give 0 and the function is continuous there; the negative + // branch is bounded below by -alpha because exp(x) - 1 > -1 for all x. + test elu_branches_agree_at_zero + given alpha = DEFAULT_ALPHA + then @abs(alpha * (@exp(@as(f32, 0.0)) - 1.0)) < 1e-6 + + // In exact arithmetic the bound is strict everywhere, but f32 cannot hold + // it: at x = -20, exp(x) - 1 rounds to exactly -1, so the branch lands ON + // -alpha rather than above it. The strict form is checked where f32 can + // still see the gap, and the saturating form where it cannot. + test negative_branch_stays_above_minus_alpha_where_f32_resolves_it + given alpha = DEFAULT_ALPHA + and left = alpha * (@exp(@as(f32, -5.0)) - 1.0) + then left > -alpha + and left < 0.0 + + test negative_branch_saturates_at_minus_alpha_in_f32 + given alpha = DEFAULT_ALPHA + and far_left = alpha * (@exp(@as(f32, -20.0)) - 1.0) + then @abs(far_left + alpha) < 1e-9 + + // forward is given a scalar with nowhere to put a result, forward_batch a + // batch with no output buffer, and derivative a scalar likewise. These + // record the signatures as they stand. + test forward_and_derivative_take_one_scalar_and_return_nothing + given f = forward + then @TypeOf(f) == fn (f32) void + and @TypeOf(derivative) == fn (f32) void + + test forward_batch_takes_one_mutable_f32_slice + given f = forward_batch + then @TypeOf(f) == fn ([]f32) void + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant elu_constraint_0 + given input = valid_input() + then true // alpha > 0 + + invariant elu_constraint_1 + given input = valid_input() + then true // name: elu_formula + + invariant elu_constraint_2 + given input = valid_input() + then true // name: derivative + + invariant elu_constraint_3 + given input = valid_input() + then true // "Clevert et al. (2016) - Fast and Accurate Deep Network Learning by Exponential Linear Units (ELUs)" + diff --git a/apps/website/public/t27/specs/ml/activation/gelu_activation.t27 b/apps/website/public/t27/specs/ml/activation/gelu_activation.t27 new file mode 100644 index 0000000000..232b670521 --- /dev/null +++ b/apps/website/public/t27/specs/ml/activation/gelu_activation.t27 @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Based on cumulative distribution function | φ² + 1/φ² = 3 | TRINITY + +module Gelu; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const SQRT_2_OVER_PI : f32 = 0.7978845608; + const SQRT_PI_OVER_2 : f32 = 1.2533141373; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const GELUConfig = struct { + approximate : bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, output: []f32, config: GELUConfig) → void + fn forward(input: []const f32, output: []f32, config: GELUConfig) -> void { + // TODO: Implement from .tri spec + } + + // backward(grad_output: []const f32, input: []const f32, grad_input: []f32, config: GELUConfig) → void + fn backward(grad_output: []const f32, input: []const f32, grad_input: []f32, config: GELUConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward() and backward() have no body yet, so they panic when called. + // What this module does state is its two GELU constants and its config + // type, and those carry properties that hold or do not. + + test gelu_sqrt_constants_are_reciprocals + // sqrt(2/pi) * sqrt(pi/2) = 1 exactly; a typo in either literal + // moves the product off 1 + given product = SQRT_2_OVER_PI * SQRT_PI_OVER_2 + then @abs(product - 1.0) < 0.0000005 + + test gelu_sqrt_2_over_pi_squares_to_2_over_pi + // 2/pi = 0.6366197724 + given squared = SQRT_2_OVER_PI * SQRT_2_OVER_PI + then @abs(squared - 0.63661977) < 0.0000003 + + test gelu_sqrt_pi_over_2_squares_to_pi_over_2 + // pi/2 = 1.5707963268 + given squared = SQRT_PI_OVER_2 * SQRT_PI_OVER_2 + then @abs(squared - 1.5707963) < 0.0000005 + + test gelu_config_records_the_approximation_choice + given exact = GELUConfig{.approximate=false} + and approx = GELUConfig{.approximate=true} + then !exact.approximate + and approx.approximate + diff --git a/apps/website/public/t27/specs/ml/activation/gelu_approx_activation.t27 b/apps/website/public/t27/specs/ml/activation/gelu_approx_activation.t27 new file mode 100644 index 0000000000..f628ffe093 --- /dev/null +++ b/apps/website/public/t27/specs/ml/activation/gelu_approx_activation.t27 @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Exact GELU using error function | φ² + 1/φ² = 3 | TRINITY + +module GeluApprox; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const SQRT_2_OVER_PI : f32 = 0.7978845608; + const TANH_COEF : f32 = 0.044715; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const GELUApproxConfig = struct { + none : void, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(x: f32) → f32 + fn forward(x: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // forward_batch(input: []f32) → []f32 + fn forward_batch(input: []f32) -> []f32 { + // TODO: Implement from .tri spec + } + + // derivative(x: f32) → f32 + fn derivative(x: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward, forward_batch and derivative are all unimplemented stubs: each + // emits `@panic("not yet implemented")`, so calling one aborts the test + // binary instead of failing a test. What the module does declare is the + // pair of constants of the tanh approximation, and those are checkable. + + test sqrt_2_over_pi_is_the_constant_it_is_named_after + // The tanh form scales x by sqrt(2/pi) = 0.797884560802865... + then @abs(SQRT_2_OVER_PI - @sqrt(2.0 / std.math.pi)) < 1e-7 + + test the_two_constants_reproduce_published_gelu_values + // Hendrycks & Gimpel (2016): 0.5x(1 + tanh(sqrt(2/pi)(x + 0.044715x^3))) + // tracks the exact erf GELU to about 1e-3. Evaluated here from the + // module's own constants, since forward() is a stub. Exact GELU(1) is + // 1 * Phi(1) = 0.8413447, and GELU(-1) = -1 * Phi(-1) = -0.1586553. + given inner_pos = SQRT_2_OVER_PI * (1.0 + TANH_COEF) + given gelu_pos = 0.5 * (1.0 + std.math.tanh(inner_pos)) + given gelu_neg = -0.5 * (1.0 - std.math.tanh(inner_pos)) + then @abs(gelu_pos - 0.8413447) < 1e-3 + and @abs(gelu_neg + 0.1586553) < 1e-3 + + test gelu_is_zero_at_the_origin_and_below_the_identity + // x = 0 gives exactly 0 because the whole expression is multiplied by + // x, and for positive x the tanh factor is under 1, so GELU(x) < x. + given gelu_zero = 0.5 * 0.0 * (1.0 + std.math.tanh(SQRT_2_OVER_PI * 0.0)) + given gelu_two = 0.5 * 2.0 * (1.0 + std.math.tanh(SQRT_2_OVER_PI * (2.0 + TANH_COEF * 8.0))) + then gelu_zero == 0.0 + and gelu_two < 2.0 + and gelu_two > 1.9 + + test the_config_carries_no_hyperparameters + // GELU has nothing to tune: the config is a single void field. + then @typeInfo(GELUApproxConfig).@"struct".fields.len == 1 + and @FieldType(GELUApproxConfig, "none") == void + and @sizeOf(GELUApproxConfig) == 0 + diff --git a/apps/website/public/t27/specs/ml/activation/leaky_relu_activation.t27 b/apps/website/public/t27/specs/ml/activation/leaky_relu_activation.t27 new file mode 100644 index 0000000000..9096c276b9 --- /dev/null +++ b/apps/website/public/t27/specs/ml/activation/leaky_relu_activation.t27 @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Piecewise derivative | φ² + 1/φ² = 3 | TRINITY + +module LeakyRelu; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_ALPHA : f32 = 0.01; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const LeakyReLUConfig = struct { + alpha : f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(x: f32, alpha: f32) → f32 + fn forward(x: f32, alpha: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // forward_batch(input: []f32, alpha: f32) → []f32 + fn forward_batch(input: []f32, alpha: f32) -> []f32 { + // TODO: Implement from .tri spec + } + + // derivative(x: f32, alpha: f32) → f32 + fn derivative(x: f32, alpha: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward, forward_batch and derivative have no bodies yet -- they are + // `TODO: Implement from .tri spec` and compile to `@panic`. Calling one + // asserts nothing about leaky ReLU, so nothing here calls them. What is + // testable today is the declared constant and the config that carries it. + + // Invariant leaky_relu_constraint_0 says 0 < alpha < 1; the shipped default + // satisfies it, and sits in the 0.01-0.1 band the comment names. + test default_alpha_is_in_the_leaky_band + then DEFAULT_ALPHA > 0.0 + and DEFAULT_ALPHA < 1.0 + and DEFAULT_ALPHA >= 0.01 + and DEFAULT_ALPHA <= 0.1 + + // The Maas et al. (2013) default is 0.01. f32 cannot hold it exactly, so + // the comparison carries a tolerance. + test default_alpha_is_one_percent + then @abs(DEFAULT_ALPHA - 0.01) < 1e-9 + + // A config built from the default reads the default back. + test config_carries_alpha + given cfg = LeakyReLUConfig{ .alpha = DEFAULT_ALPHA } + then @abs(cfg.alpha - DEFAULT_ALPHA) < 1e-9 + + // alpha is a field, not a constant: a steeper leak is representable. + test config_alpha_is_settable + given cfg = LeakyReLUConfig{ .alpha = 0.2 } + then @abs(cfg.alpha - 0.2) < 1e-6 + and cfg.alpha > DEFAULT_ALPHA + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant leaky_relu_constraint_0 + given input = valid_input() + then true // 0 < alpha < 1 (typically 0.01-0.1) + + invariant leaky_relu_constraint_1 + given input = valid_input() + then true // name: leaky_relu_formula + + invariant leaky_relu_constraint_2 + given input = valid_input() + then true // name: derivative + + invariant leaky_relu_constraint_3 + given input = valid_input() + then true // "Maas et al. (2013) - Rectifier Nonlinearities Improve Neural Network Acoustic Models" + diff --git a/apps/website/public/t27/specs/ml/activation/relu_activation.t27 b/apps/website/public/t27/specs/ml/activation/relu_activation.t27 new file mode 100644 index 0000000000..d69d30f923 --- /dev/null +++ b/apps/website/public/t27/specs/ml/activation/relu_activation.t27 @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// ReLU produces sparse gradients | φ² + 1/φ² = 3 | TRINITY + +module Relu; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const ZERO : f32 = 0.0; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const ReLUConfig = struct { + negative_slope : f32, + inplace : bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, output: []f32, config: ReLUConfig) → void + fn forward(input: []const f32, output: []f32, config: ReLUConfig) -> void { + // TODO: Implement from .tri spec + } + + // backward(grad_output: []const f32, input: []const f32, grad_input: []f32, config: ReLUConfig) → void + fn backward(grad_output: []const f32, input: []const f32, grad_input: []f32, config: ReLUConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward and backward have no bodies, so the rectifier itself cannot be + // exercised. The one value the module fixes is the threshold. + + test zero_is_the_rectifier_threshold + // ReLU hinges at exactly 0, not near it -- the constant is the cut + given threshold = ZERO + then threshold == 0.0 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant relu_constraint_0 + given input = valid_input() + then true // input.len == output.len + + invariant relu_constraint_1 + given input = valid_input() + then true // All inputs must be finite + diff --git a/apps/website/public/t27/specs/ml/activation/sigmoid_activation.t27 b/apps/website/public/t27/specs/ml/activation/sigmoid_activation.t27 new file mode 100644 index 0000000000..f6372b6f9f --- /dev/null +++ b/apps/website/public/t27/specs/ml/activation/sigmoid_activation.t27 @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Derivative (compute from output) | φ² + 1/φ² = 3 | TRINITY + +module Sigmoid; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SigmoidConfig = struct { + none : void, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(x: f32) → f32 + fn forward(x: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // forward_batch(input: []f32) → []f32 + fn forward_batch(input: []f32) -> []f32 { + // TODO: Implement from .tri spec + } + + // derivative(sigmoid_x: f32) → f32 + fn derivative(sigmoid_x: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward()/forward_batch()/derivative() have no body yet, so they panic + // when called. None of what makes sigmoid worth having can be stated: not + // that forward(0) == 0.5, not that the output is confined to (0, 1), and + // not the identity in the header comment -- that the derivative is + // computable from the output alone, sigma'(x) = sigma(x) * (1 - sigma(x)). + // Those tests belong here the moment the three bodies exist. + // + // What the module states today is a config with nothing in it. + + test sigmoid_has_no_tunable_parameter + // Verify: unlike LeakyReLU's slope or ELU's alpha, sigmoid is a fixed + // function -- the config carries a single void placeholder and so + // occupies no storage + given size = @sizeOf(SigmoidConfig) + then size == 0 + + test sigmoid_config_is_a_placeholder_not_an_empty_struct + // Verify: the one declared field is `none`, typed void -- the config + // exists to keep the shape uniform with activations that do have + // parameters + given fields = std.meta.fields(SigmoidConfig) + then (fields.len == 1) and (fields[0].type == void) + diff --git a/apps/website/public/t27/specs/ml/activation/silu_swish_activation.t27 b/apps/website/public/t27/specs/ml/activation/silu_swish_activation.t27 new file mode 100644 index 0000000000..ccbda1aea0 --- /dev/null +++ b/apps/website/public/t27/specs/ml/activation/silu_swish_activation.t27 @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// SiLU derivative | φ² + 1/φ² = 3 | TRINITY + +module SiluSwish; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_BETA : f32 = 1.0; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SiLUConfig = struct { + beta : f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(x: f32, beta: f32) → f32 + fn forward(x: f32, beta: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // forward_batch(input: []f32, beta: f32) → []f32 + fn forward_batch(input: []f32, beta: f32) -> []f32 { + // TODO: Implement from .tri spec + } + + // derivative(x: f32, beta: f32) → f32 + fn derivative(x: f32, beta: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward(), forward_batch() and derivative() have no body yet, so they + // panic when called. The default beta and the config type are the only + // things this module states, and they are what these tests check. + + test silu_default_beta_is_the_swish_beta_one_case + // SiLU is Swish with beta = 1 (Elfwing 2017 / Ramachandran 2017) + given beta = DEFAULT_BETA + then @abs(beta - 1.0) < 0.0000005 + + test silu_default_beta_satisfies_the_positivity_constraint + // constraint silu_swish_constraint_0: beta > 0 + given beta = DEFAULT_BETA + then beta > 0.0 + + test silu_config_carries_a_beta_other_than_the_default + given cfg = SiLUConfig{.beta=2.5} + then @abs(cfg.beta - 2.5) < 0.0000005 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant silu_swish_constraint_0 + given input = valid_input() + then true // beta > 0 + + invariant silu_swish_constraint_1 + given input = valid_input() + then true // name: silu_formula + + invariant silu_swish_constraint_2 + given input = valid_input() + then true // name: derivative + + invariant silu_swish_constraint_3 + given input = valid_input() + then true // "Elfwing et al. (2017) - Sigmoid-Weighted Linear Units for Neural Network Function Approximation" + + invariant silu_swish_constraint_4 + given input = valid_input() + then true // "Ramachandran et al. (2017) - Searching for Activation Functions (Swish)" + diff --git a/apps/website/public/t27/specs/ml/activation/silu_swish_vbt_activation.t27 b/apps/website/public/t27/specs/ml/activation/silu_swish_vbt_activation.t27 new file mode 100644 index 0000000000..569c1426a4 --- /dev/null +++ b/apps/website/public/t27/specs/ml/activation/silu_swish_vbt_activation.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Derivative for backpropagation | φ² + 1/φ² = 3 | TRINITY + +module SiluSwishVbt; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "silu_swish_vbt_activation_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/ml/activation/softmax.t27 b/apps/website/public/t27/specs/ml/activation/softmax.t27 new file mode 100644 index 0000000000..09bdd22ebe --- /dev/null +++ b/apps/website/public/t27/specs/ml/activation/softmax.t27 @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Control sharpness of distribution | φ² + 1/φ² = 3 | TRINITY + +module Softmax; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const ZERO : f32 = 0.0; + const MIN_TEMP : f32 = 0.001; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SoftmaxConfig = struct { + temperature : f32, + axis : u32, + stable : bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(logits: []const f32, output: []f32, config: SoftmaxConfig) → void + fn forward(logits: []const f32, output: []f32, config: SoftmaxConfig) -> void { + // TODO: Implement from .tri spec + } + + // backward(grad_output: []const f32, probabilities: []const f32, grad_input: []f32, config: SoftmaxConfig) → void + fn backward(grad_output: []const f32, probabilities: []const f32, grad_input: []f32, config: SoftmaxConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward and backward are unimplemented stubs -- both emit + // `@panic("not yet implemented")`, so the sum-to-one property in + // softmax_constraint_2 cannot be checked here. The declared constants and + // config shape are what remain testable. + + test min_temp_is_strictly_above_zero + // Temperature divides the logits, so softmax_constraint_1 + // (temperature >= MIN_TEMP) is only a safe floor if MIN_TEMP itself is + // positive -- otherwise the constraint admits a division by zero. + then MIN_TEMP > ZERO + and @abs(MIN_TEMP - 0.001) < 1e-9 + and ZERO == 0.0 + + test temperature_is_a_float_axis_is_an_index + then @FieldType(SoftmaxConfig, "temperature") == f32 + and @FieldType(SoftmaxConfig, "axis") == u32 + + test the_stable_max_subtraction_is_opt_in + // stable is a bool the caller sets, not a compile-time guarantee: the + // unstable path is reachable by construction. + then @FieldType(SoftmaxConfig, "stable") == bool + and @typeInfo(SoftmaxConfig).@"struct".fields.len == 3 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant softmax_constraint_0 + given input = valid_input() + then true // logits.size() == output.size() + + invariant softmax_constraint_1 + given input = valid_input() + then true // config.temperature >= MIN_TEMP + + invariant softmax_constraint_2 + given input = valid_input() + then true // sum(output) ≈ 1.0 (within floating precision) + diff --git a/apps/website/public/t27/specs/ml/activation/tanh_activation.t27 b/apps/website/public/t27/specs/ml/activation/tanh_activation.t27 new file mode 100644 index 0000000000..a0291ea6f3 --- /dev/null +++ b/apps/website/public/t27/specs/ml/activation/tanh_activation.t27 @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Derivative (can compute from output) | φ² + 1/φ² = 3 | TRINITY + +module Tanh; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const TanhConfig = struct { + none : void, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(x: f32) → f32 + fn forward(x: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // forward_batch(input: []f32) → []f32 + fn forward_batch(input: []f32) -> []f32 { + // TODO: Implement from .tri spec + } + + // derivative(x: f32) → f32 + fn derivative(x: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // derivative_from_output(tanh_x: f32) → f32 + fn derivative_from_output(tanh_x: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // All four functions have no bodies, so neither tanh nor its derivative + // can be exercised. The module's only decision is that tanh takes no + // hyper-parameters, and that is what TanhConfig encodes. + + test tanh_config_carries_no_state + // the sole field is void, so the config occupies no storage at all + given config_size = @sizeOf(TanhConfig) + then config_size == 0 + diff --git a/apps/website/public/t27/specs/ml/layers/avgpool2d_layer.t27 b/apps/website/public/t27/specs/ml/layers/avgpool2d_layer.t27 new file mode 100644 index 0000000000..4b34105413 --- /dev/null +++ b/apps/website/public/t27/specs/ml/layers/avgpool2d_layer.t27 @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// 2D average pooling formula | φ² + 1/φ² = 3 | TRINITY + +module Avgpool2d; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_KERNEL : []u32 = "[2, 2]"; + const DEFAULT_STRIDE : []u32 = "[2, 2]"; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const PoolConfig = struct { + kernel_size : []u32, + stride : []u32, + padding : []u32, + }; + + pub const PoolShape = struct { + batch : u32, + channels : u32, + height_in : u32, + width_in : u32, + height_out : u32, + width_out : u32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // calc_output_size(height_in: u32, width_in: u32, kernel_size: []u32, stride: []u32, padding: []u32) → PoolShape + fn calc_output_size(height_in: u32, width_in: u32, kernel_size: []u32, stride: []u32, padding: []u32) -> PoolShape { + // TODO: Implement from .tri spec + } + + // forward(input: []f32, config: PoolConfig) → []f32 + fn forward(input: []f32, config: PoolConfig) -> []f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test output_size_halves_on_a_2x2_kernel_with_stride_2 + // Verify: avgpool_formula — out = (in + 2*pad - kernel)/stride + 1. + // An 8x8 map under a 2x2/stride-2 window becomes 4x4. + given shape = PoolShape{ .batch = 1, .channels = 3, .height_in = 8, .width_in = 8, .height_out = (8 + 0 - 2) / 2 + 1, .width_out = (8 + 0 - 2) / 2 + 1 } + then shape.height_out == 4 and shape.width_out == 4 + + test output_size_truncates_a_partial_window + // Verify: the formula floors — a 7-row map under a 2x2/stride-2 window + // keeps 3 rows and drops the trailing row, which has no full window + given shape = PoolShape{ .batch = 1, .channels = 1, .height_in = 7, .width_in = 7, .height_out = (7 + 0 - 2) / 2 + 1, .width_out = (7 + 0 - 2) / 2 + 1 } + then shape.height_out == 3 + + test stride_one_shrinks_by_kernel_minus_one + // Verify: at stride 1 the window slides one step at a time, so an 8-row + // map under a 2-row kernel yields 7 rows + given shape = PoolShape{ .batch = 1, .channels = 1, .height_in = 8, .width_in = 8, .height_out = (8 + 0 - 2) / 1 + 1, .width_out = (8 + 0 - 2) / 1 + 1 } + then shape.height_out == 7 + + test padding_widens_the_output + // Verify: 2*pad enters the numerator, so one pixel of padding on each side + // of an 8-row map under a 2x2/stride-2 window yields 5 rows, not 4 + given shape = PoolShape{ .batch = 1, .channels = 1, .height_in = 8, .width_in = 8, .height_out = (8 + 2 * 1 - 2) / 2 + 1, .width_out = (8 + 2 * 1 - 2) / 2 + 1 } + then shape.height_out == 5 + + test pool_config_stride_never_exceeds_kernel + // Verify: constraints 0, 1 and 2 — kernel >= 1, stride >= 1, stride <= kernel, + // so consecutive windows always touch and no input pixel is skipped + given kernel_size = [_]u32{ 2, 2 } + and stride = [_]u32{ 2, 2 } + and padding = [_]u32{ 0, 0 } + when config = PoolConfig{ .kernel_size = @constCast(&kernel_size), .stride = @constCast(&stride), .padding = @constCast(&padding) } + and k_h = config.kernel_size[0] + and k_w = config.kernel_size[1] + and s_h = config.stride[0] + and s_w = config.stride[1] + then k_h >= 1 and k_w >= 1 and s_h >= 1 and s_w >= 1 and s_h <= k_h and s_w <= k_w + + test output_is_never_larger_than_the_input + // Verify: with stride >= 1 and no padding the pooled map cannot grow + given shape = PoolShape{ .batch = 1, .channels = 1, .height_in = 8, .width_in = 8, .height_out = (8 + 0 - 2) / 2 + 1, .width_out = (8 + 0 - 2) / 2 + 1 } + then shape.height_out <= shape.height_in and shape.width_out <= shape.width_in + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant avgpool2d_constraint_0 + given input = valid_input() + then true // kernel_size[i] >= 1 + + invariant avgpool2d_constraint_1 + given input = valid_input() + then true // stride[i] >= 1 + + invariant avgpool2d_constraint_2 + given input = valid_input() + then true // stride[i] <= kernel_size[i] + + invariant avgpool2d_constraint_3 + given input = valid_input() + then true // name: avgpool_formula + + invariant avgpool2d_constraint_4 + given input = valid_input() + then true // "Zeiler & Fergus (2014) - Visualizing and Understanding Convolutional Networks" + + invariant avgpool2d_constraint_5 + given input = valid_input() + then true // "He et al. (2016) - Deep Residual Learning for Image Recognition" + diff --git a/apps/website/public/t27/specs/ml/layers/batchnorm_layer.t27 b/apps/website/public/t27/specs/ml/layers/batchnorm_layer.t27 new file mode 100644 index 0000000000..38841a2c45 --- /dev/null +++ b/apps/website/public/t27/specs/ml/layers/batchnorm_layer.t27 @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Prevent division by zero in sqrt | φ² + 1/φ² = 3 | TRINITY + +module Batchnorm; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_EPSILON : f32 = 1.0e-5; + const DEFAULT_MOMENTUM : f32 = 0.1; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const BatchNormConfig = struct { + num_features : u32, + epsilon : f32, + momentum : f32, + affine : bool, + track_running_stats : bool, + }; + + pub const BatchNormState = struct { + running_mean : []f32, + running_var : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, gamma: []const f32, beta: []const f32, state: BatchNormState, output: []f32, config: BatchNormConfig) → void + fn forward(input: []const f32, gamma: []const f32, beta: []const f32, state: BatchNormState, output: []f32, config: BatchNormConfig) -> void { + // TODO: Implement from .tri spec + } + + // backward(grad_output: []const f32, input: []const f32, gamma: []const f32, beta: []const f32, mean: []const f32, var: []const f32, grad_gamma: []f32, grad_beta: []f32, grad_input: []f32, config: BatchNormConfig) → void + fn backward(grad_output: []const f32, input: []const f32, gamma: []const f32, beta: []const f32, mean: []const f32, var: []const f32, grad_gamma: []f32, grad_beta: []f32, grad_input: []f32, config: BatchNormConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test default_epsilon_is_positive + // Verify: constraint_1 -- epsilon > 0 -- holds for the shipped default + given eps = DEFAULT_EPSILON + then eps > 0.0 + + test epsilon_keeps_the_normalisation_denominator_nonzero + // Verify: the module's stated purpose -- sqrt(variance + epsilon) is + // still positive when the batch variance is exactly zero, so the + // normalisation never divides by zero + given variance = 0.0 + when denom = @sqrt(variance + DEFAULT_EPSILON) + then denom > 0.0 + + test default_momentum_is_in_range + // Verify: constraint_2 -- 0 <= momentum < 1 + given m = DEFAULT_MOMENTUM + then m >= 0.0 and m < 1.0 + + test running_stats_update_stays_between_old_and_new + // Verify: running = (1 - momentum)*running + momentum*batch is a convex + // combination at the declared momentum, so the running estimate never + // overshoots the batch statistic + given running = 0.0 + and batch = 1.0 + when updated = (1.0 - DEFAULT_MOMENTUM) * running + DEFAULT_MOMENTUM * batch + then updated >= running and updated <= batch + + test running_stats_are_sized_by_num_features + // Verify: constraint_0/3/4 -- num_features > 0 and there is exactly one + // running mean and one running variance per feature + given cfg = BatchNormConfig{ .num_features = 3, .epsilon = DEFAULT_EPSILON, .momentum = DEFAULT_MOMENTUM, .affine = true, .track_running_stats = true } + and means = [_]f32{0.0} ** 3 + and vars = [_]f32{1.0} ** 3 + and state = BatchNormState{ .running_mean = @constCast(&means), .running_var = @constCast(&vars) } + then cfg.num_features > 0 and state.running_mean.len == cfg.num_features and state.running_var.len == cfg.num_features + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant batchnorm_constraint_0 + given input = valid_input() + then true // num_features > 0 + + invariant batchnorm_constraint_1 + given input = valid_input() + then true // epsilon > 0 + + invariant batchnorm_constraint_2 + given input = valid_input() + then true // 0 <= momentum < 1 + + invariant batchnorm_constraint_3 + given input = valid_input() + then true // gamma.size() == num_features + + invariant batchnorm_constraint_4 + given input = valid_input() + then true // beta.size() == num_features + diff --git a/apps/website/public/t27/specs/ml/layers/conv2d_layer.t27 b/apps/website/public/t27/specs/ml/layers/conv2d_layer.t27 new file mode 100644 index 0000000000..4a71a9527e --- /dev/null +++ b/apps/website/public/t27/specs/ml/layers/conv2d_layer.t27 @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Optimized depthwise convolution | φ² + 1/φ² = 3 | TRINITY + +module Conv2d; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const MIN_KERNEL_SIZE : u32 = 1; + const MAX_KERNEL_SIZE : u32 = 7; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Conv2DConfig = struct { + in_channels : u32, + out_channels : u32, + kernel_size : u32, + stride : u32, + padding : u32, + dilation : u32, + groups : u32, + has_bias : bool, + }; + + pub const PaddingMode = struct { + enum_type : Enum, + values : , + Zeros : Auto, + Reflect : Auto, + Replicate : Auto, + Circular : Auto, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, weights: []const f32, bias: []const f32, output: []f32, config: Conv2DConfig) → void + fn forward(input: []const f32, weights: []const f32, bias: []const f32, output: []f32, config: Conv2DConfig) -> void { + // TODO: Implement from .tri spec + } + + // backward(grad_output: []const f32, input: []const f32, weights: []const f32, grad_input: []f32, grad_weights: []f32, grad_bias: []f32, config: Conv2DConfig) → void + fn backward(grad_output: []const f32, input: []const f32, weights: []const f32, grad_input: []f32, grad_weights: []f32, grad_bias: []f32, config: Conv2DConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test forward_basic_case + given input = default_input() + when result = forward(input) + then result != undefined + + test backward_basic_case + given input = default_input() + when result = backward(input) + then result != undefined + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant conv2d_constraint_0 + given input = valid_input() + then true // kernel_size in [1, 3, 5, 7] + + invariant conv2d_constraint_1 + given input = valid_input() + then true // stride in [1, 2, 4] + + invariant conv2d_constraint_2 + given input = valid_input() + then true // dilation in [1, 2] + + invariant conv2d_constraint_3 + given input = valid_input() + then true // in_channels % groups == 0 + + invariant conv2d_constraint_4 + given input = valid_input() + then true // out_channels % groups == 0 + diff --git a/apps/website/public/t27/specs/ml/layers/dense_layer.t27 b/apps/website/public/t27/specs/ml/layers/dense_layer.t27 new file mode 100644 index 0000000000..2e8fd01fb3 --- /dev/null +++ b/apps/website/public/t27/specs/ml/layers/dense_layer.t27 @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Row-major weight layout for cache efficiency | φ² + 1/φ² = 3 | TRINITY + +module Dense; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const MIN_INPUT_SIZE : u32 = 1; + const MAX_INPUT_SIZE : u32 = 4096; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const DenseConfig = struct { + input_size : u32, + output_size : u32, + has_bias : bool, + activation : ActivationType, + }; + + pub const ActivationType = struct { + enum_type : Enum, + values : , + None : Auto, + ReLU : Auto, + GELU : Auto, + Sigmoid : Auto, + Tanh : Auto, + Softmax : Auto, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, weights: []const f32, bias: []const f32, output: []f32, config: DenseConfig) → void + fn forward(input: []const f32, weights: []const f32, bias: []const f32, output: []f32, config: DenseConfig) -> void { + // TODO: Implement from .tri spec + } + + // backward(grad_output: []const f32, input: []const f32, weights: []const f32, grad_input: []f32, grad_weights: []f32, grad_bias: []f32, config: DenseConfig) → void + fn backward(grad_output: []const f32, input: []const f32, weights: []const f32, grad_input: []f32, grad_weights: []f32, grad_bias: []f32, config: DenseConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test forward_basic_case + given input = default_input() + when result = forward(input) + then result != undefined + + test backward_basic_case + given input = default_input() + when result = backward(input) + then result != undefined + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant dense_constraint_0 + given input = valid_input() + then true // input.size() == config.input_size + + invariant dense_constraint_1 + given input = valid_input() + then true // output.size() == config.output_size + + invariant dense_constraint_2 + given input = valid_input() + then true // weights.size() == input_size * output_size + + invariant dense_constraint_3 + given input = valid_input() + then true // bias.size() == output_size (if has_bias) + + invariant dense_constraint_4 + given input = valid_input() + then true // All inputs must be finite (no NaN/Inf) + diff --git a/apps/website/public/t27/specs/ml/layers/dropout_layer.t27 b/apps/website/public/t27/specs/ml/layers/dropout_layer.t27 new file mode 100644 index 0000000000..790d43a033 --- /dev/null +++ b/apps/website/public/t27/specs/ml/layers/dropout_layer.t27 @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Prevent co-adaptation of neurons | φ² + 1/φ² = 3 | TRINITY + +module Dropout; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_DROP_RATE : f32 = 0.5; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const DropoutConfig = struct { + p : f32, + inplace : bool, + scale_during_training : bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, output: []f32, mask: []bool, training: bool, config: DropoutConfig) → void + fn forward(input: []const f32, output: []f32, mask: []bool, training: bool, config: DropoutConfig) -> void { + // TODO: Implement from .tri spec + } + + // backward(grad_output: []const f32, mask: []const bool, grad_input: []f32, config: DropoutConfig) → void + fn backward(grad_output: []const f32, mask: []const bool, grad_input: []f32, config: DropoutConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward/backward are unimplemented stubs that return void and panic when + // called, so nothing behavioural can be asserted about them yet. What the + // module does declare -- the default rate and the config shape -- is tested + // here. + + test default_drop_rate_is_one_half + // Verify: the documented default drops half of the units + given p = DEFAULT_DROP_RATE + then @abs(p - 0.5) < 1e-6 + + test default_drop_rate_is_a_probability + // Verify: the default rate lies in [0, 1] + given p = DEFAULT_DROP_RATE + then p >= 0.0 and p <= 1.0 + + test inverted_dropout_scale_is_reciprocal_of_keep_probability + // Verify: scaling survivors by 1/(1-p) keeps the expected activation + // unchanged; at p = 0.5 that doubles them + given keep = 1.0 - DEFAULT_DROP_RATE + when scale = 1.0 / keep + then @abs(scale - 2.0) < 1e-6 + + test config_carries_the_default_rate + // Verify: DropoutConfig has the declared field names and holds p + given cfg = DropoutConfig{.p=DEFAULT_DROP_RATE,.inplace=false,.scale_during_training=true} + then @abs(cfg.p - 0.5) < 1e-6 and cfg.scale_during_training and cfg.inplace == false + diff --git a/apps/website/public/t27/specs/ml/layers/embedding_layer.t27 b/apps/website/public/t27/specs/ml/layers/embedding_layer.t27 new file mode 100644 index 0000000000..e6aa7ef55b --- /dev/null +++ b/apps/website/public/t27/specs/ml/layers/embedding_layer.t27 @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Embeddings are learned parameters | φ² + 1/φ² = 3 | TRINITY + +module Embedding; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_D_MODEL : u32 = 64; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const EmbeddingConfig = struct { + vocab_size : u32, + d_model : u32, + }; + + pub const EmbeddingWeights = struct { + W : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(token_ids: []const u32, weights: EmbeddingWeights, embeddings: []f32, config: EmbeddingConfig) → void + fn forward(token_ids: []const u32, weights: EmbeddingWeights, embeddings: []f32, config: EmbeddingConfig) -> void { + // TODO: Implement from .tri spec + } + + // init(weights: EmbeddingWeights, config: EmbeddingConfig) → void + fn init(weights: EmbeddingWeights, config: EmbeddingConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Both functions above are still `TODO: Implement`, so each compiles to + // `@panic("not yet implemented")`: calling one aborts the test binary. + // What this module does state is DEFAULT_D_MODEL and the two record + // shapes. The constraints listed below are checked here against a + // concrete configuration rather than being left as prose. + + test default_d_model_is_sixty_four_and_positive + given d = DEFAULT_D_MODEL + then d == 64 + and d > 0 + + test weight_table_is_one_row_of_d_model_floats_per_vocabulary_entry + given cfg = EmbeddingConfig{ .vocab_size = 2, .d_model = 3 } + and w = EmbeddingWeights{ .W = @constCast(&[_]f32{ 0.0, 0.1, 0.2, 1.0, 1.1, 1.2 }) } + then cfg.vocab_size > 0 + and cfg.d_model > 0 + and w.W.len == cfg.vocab_size * cfg.d_model + + // forward reads row token_id, so the row for token 1 starts at + // 1 * d_model and the last valid token id is vocab_size - 1. + test row_for_a_token_starts_at_token_id_times_d_model + given cfg = EmbeddingConfig{ .vocab_size = 2, .d_model = 3 } + and w = EmbeddingWeights{ .W = @constCast(&[_]f32{ 0.0, 0.1, 0.2, 1.0, 1.1, 1.2 }) } + then @abs(w.W[1 * cfg.d_model] - 1.0) < 1e-6 + and (cfg.vocab_size - 1) * cfg.d_model + cfg.d_model == w.W.len + + test embeddings_are_stored_as_f32 + given weights = @FieldType(EmbeddingWeights, "W") + then weights == []f32 + and @FieldType(EmbeddingConfig, "vocab_size") == u32 + and @FieldType(EmbeddingConfig, "d_model") == u32 + + // forward is given token ids with nowhere to write the rows it gathers, + // and init is given weights with no config to size them against. These + // record the signatures as they stand. + test forward_takes_token_ids_and_returns_nothing + given f = forward + then @TypeOf(f) == fn ([]const u32) void + + test init_takes_the_weight_table_and_returns_nothing + given f = init + then @TypeOf(f) == fn (EmbeddingWeights) void + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant embedding_constraint_0 + given input = valid_input() + then true // vocab_size > 0 + + invariant embedding_constraint_1 + given input = valid_input() + then true // d_model > 0 + + invariant embedding_constraint_2 + given input = valid_input() + then true // W.size() == vocab_size * d_model + + invariant embedding_constraint_3 + given input = valid_input() + then true // token_ids must be in [0, vocab_size) + + invariant embedding_constraint_4 + given input = valid_input() + then true // "Embedding dimension affects model capacity" + + invariant embedding_constraint_5 + given input = valid_input() + then true // "Larger vocab requires larger d_model" + + invariant embedding_constraint_6 + given input = valid_input() + then true // "Pre-trained embeddings transfer well" + diff --git a/apps/website/public/t27/specs/ml/layers/flatten_layer.t27 b/apps/website/public/t27/specs/ml/layers/flatten_layer.t27 new file mode 100644 index 0000000000..c86b9f16af --- /dev/null +++ b/apps/website/public/t27/specs/ml/layers/flatten_layer.t27 @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Vectorization operation | φ² + 1/φ² = 3 | TRINITY + +module Flatten; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const MAX_DIMS : u32 = 8; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const FlattenConfig = struct { + input_shape : []u32, + }; + + pub const InputTensor = struct { + dims : []u32, + data : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // calc_output_size(input_dims: []u32) → u32 + fn calc_output_size(input_dims: []u32) -> u32 { + // TODO: Implement from .tri spec + } + + // forward(input: []f32, config: FlattenConfig) → []f32 + fn forward(input: []f32, config: FlattenConfig) -> []f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // calc_output_size and forward are both unimplemented stubs: each one + // emits `@panic("not yet implemented")`, so calling either aborts the test + // binary rather than failing a test. The invariants below it never reach + // the generated code either -- they emit an empty comptime block. What is + // checkable is the constant, the declared shapes, and the arithmetic those + // invariants describe. + + test max_dims_caps_the_rank_not_the_extent + // MAX_DIMS is a bound on how many dimensions a tensor may have, not on + // how large any one of them is: the shape itself is a runtime slice, + // so `len(input_dims) <= MAX_DIMS` has to be checked, not typed. + then MAX_DIMS == 8 + and @FieldType(FlattenConfig, "input_shape") == []u32 + and @typeInfo(FlattenConfig).@"struct".fields.len == 1 + + test the_output_size_is_the_product_of_the_dims + // flatten_formula: a 2x3x4 tensor flattens to 24 elements. No element + // is added or dropped, so the data slice already has that length -- + // flatten only rewrites the shape. + given dims = [_]u32{ 2, 3, 4 } + given data = std.mem.zeroes([24]f32) + given tensor = InputTensor{ .dims = @constCast(&dims), .data = @constCast(&data) } + given elems = @as(usize, dims[0]) * dims[1] * dims[2] + then elems == 24 + and tensor.data.len == elems + and tensor.dims.len == 3 + + test flatten_is_the_identity_on_a_rank_one_tensor + // With one dimension the product is that dimension, so the output has + // the same length as the input and the operation moves no data. + given dims = [_]u32{ 5 } + given data = std.mem.zeroes([5]f32) + given tensor = InputTensor{ .dims = @constCast(&dims), .data = @constCast(&data) } + then tensor.dims.len == 1 + and tensor.data.len == dims[0] + and tensor.dims.len >= 1 + + test a_tensor_carries_its_shape_beside_one_flat_run_of_data + // data is a single []f32 whatever the rank, which is why flattening is + // a metadata change: there is no nesting to undo. + then @typeInfo(InputTensor).@"struct".fields.len == 2 + and @FieldType(InputTensor, "dims") == []u32 + and @FieldType(InputTensor, "data") == []f32 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant flatten_constraint_0 + given input = valid_input() + then true // len(input_dims) >= 1 + + invariant flatten_constraint_1 + given input = valid_input() + then true // len(input_dims) <= MAX_DIMS + + invariant flatten_constraint_2 + given input = valid_input() + then true // name: flatten_formula + + invariant flatten_constraint_3 + given input = valid_input() + then true // "Goodfellow et al. (2016) - Deep Learning" + + invariant flatten_constraint_4 + given input = valid_input() + then true // "Used in CNN → Flatten → Dense → Classification" + diff --git a/apps/website/public/t27/specs/ml/layers/layernorm_layer.t27 b/apps/website/public/t27/specs/ml/layers/layernorm_layer.t27 new file mode 100644 index 0000000000..3864318427 --- /dev/null +++ b/apps/website/public/t27/specs/ml/layers/layernorm_layer.t27 @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Normalizes across hidden dimension, not batch | φ² + 1/φ² = 3 | TRINITY + +module Layernorm; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const LayerNormConfig = struct { + normalized_shape : []u32, + eps : f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, gamma: []const f32, beta: []const f32, output: []f32, config: LayerNormConfig) → void + fn forward(input: []const f32, gamma: []const f32, beta: []const f32, output: []f32, config: LayerNormConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test forward_basic_case + given input = default_input() + when result = forward(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/ml/layers/maxpool2d_layer.t27 b/apps/website/public/t27/specs/ml/layers/maxpool2d_layer.t27 new file mode 100644 index 0000000000..db28f6875f --- /dev/null +++ b/apps/website/public/t27/specs/ml/layers/maxpool2d_layer.t27 @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Reduce spatial dimensions | φ² + 1/φ² = 3 | TRINITY + +module Maxpool2d; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const MaxPool2DConfig = struct { + kernel_size : u32, + stride : u32, + padding : u32, + dilation : u32, + return_indices : bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, output: []f32, indices: []u32, config: MaxPool2DConfig) → void + fn forward(input: []const f32, output: []f32, indices: []u32, config: MaxPool2DConfig) -> void { + // TODO: Implement from .tri spec + } + + // backward(grad_output: []const f32, input: []const f32, indices: []const u32, grad_input: []f32, config: MaxPool2DConfig) → void + fn backward(grad_output: []const f32, input: []const f32, indices: []const u32, grad_input: []f32, config: MaxPool2DConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward and backward are still `TODO: Implement from .tri spec` and + // compile to `@panic`, so neither is called here. MaxPool2DConfig is real, + // and the output-size arithmetic it feeds is checkable against it: + // out = (in + 2*padding - dilation*(kernel_size - 1) - 1) / stride + 1 + + // The textbook 2x2 pool: stride equals kernel, no padding, no dilation. + // Every input element is read exactly once and the map halves. + test default_2x2_pool_config + given cfg = MaxPool2DConfig{ .kernel_size = 2, .stride = 2, .padding = 0, .dilation = 1, .return_indices = false } + then cfg.kernel_size == 2 + and cfg.stride == cfg.kernel_size + and cfg.padding == 0 + and cfg.dilation == 1 + and cfg.return_indices == false + + // Non-overlapping 2x2 over a 4x4 map gives 2x2 out, by the formula above. + test pool_2x2_halves_a_4x4_map + given cfg = MaxPool2DConfig{ .kernel_size = 2, .stride = 2, .padding = 0, .dilation = 1, .return_indices = false } + then (4 + 2 * cfg.padding - cfg.dilation * (cfg.kernel_size - 1) - 1) / cfg.stride + 1 == 2 + + // Stride 1 with kernel 3 and padding 1 is size-preserving -- the setting + // used when pooling must not change the spatial dimensions. + test padded_stride_one_pool_preserves_size + given cfg = MaxPool2DConfig{ .kernel_size = 3, .stride = 1, .padding = 1, .dilation = 1, .return_indices = false } + then (8 + 2 * cfg.padding - cfg.dilation * (cfg.kernel_size - 1) - 1) / cfg.stride + 1 == 8 + + // return_indices is what a max-pool needs to route gradients back in + // backward; it is a separate flag, not implied by any other field. + test return_indices_is_independent + given tracking = MaxPool2DConfig{ .kernel_size = 2, .stride = 2, .padding = 0, .dilation = 1, .return_indices = true } + and plain = MaxPool2DConfig{ .kernel_size = 2, .stride = 2, .padding = 0, .dilation = 1, .return_indices = false } + then tracking.kernel_size == plain.kernel_size + and tracking.stride == plain.stride + and tracking.return_indices != plain.return_indices + diff --git a/apps/website/public/t27/specs/ml/layers/residual_connection.t27 b/apps/website/public/t27/specs/ml/layers/residual_connection.t27 new file mode 100644 index 0000000000..dc1bbbe955 --- /dev/null +++ b/apps/website/public/t27/specs/ml/layers/residual_connection.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Pre-LN | φ² + 1/φ² = 3 | TRINITY + +module Residual; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, sublayer_output: []const f32, gamma: []const f32, beta: []const f32, output: []f32) → void + fn forward(input: []const f32, sublayer_output: []const f32, gamma: []const f32, beta: []const f32, output: []f32) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test forward_basic_case + given input = default_input() + when result = forward(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/ml/loss/binary_crossentropy_loss.t27 b/apps/website/public/t27/specs/ml/loss/binary_crossentropy_loss.t27 new file mode 100644 index 0000000000..25e949a22a --- /dev/null +++ b/apps/website/public/t27/specs/ml/loss/binary_crossentropy_loss.t27 @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Gradient w.r.t predictions | φ² + 1/φ² = 3 | TRINITY + +module BinaryCe; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_EPSILON : f32 = 1e-7; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const BinaryCEConfig = struct { + from_logits : bool, + epsilon : f32, + reduction : Reduction, + }; + + pub const Reduction = struct { + enum : [MEAN, SUM, NONE], + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // sigmoid(x: f32) → f32 + fn sigmoid(x: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // forward(predictions: []f32, targets: []f32, config: BinaryCEConfig) → f32 + fn forward(predictions: []f32, targets: []f32, config: BinaryCEConfig) -> f32 { + // TODO: Implement from .tri spec + } + + // forward_batch(predictions: [][]f32, targets: []f32, config: BinaryCEConfig) → f32 + fn forward_batch(predictions: [][]f32, targets: []f32, config: BinaryCEConfig) -> f32 { + // TODO: Implement from .tri spec + } + + // gradient(predictions: []f32, targets: []f32, config: BinaryCEConfig) → []f32 + fn gradient(predictions: []f32, targets: []f32, config: BinaryCEConfig) -> []f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // sigmoid/forward/forward_batch/gradient are unimplemented stubs -- each + // emits `@panic("not yet implemented")`, so sigmoid(0) == 0.5 and the loss + // itself cannot be checked here. The declared constants and config shape + // are what remain testable. + + test default_epsilon_satisfies_the_stated_constraint + // binary_ce_constraint_1 states 0 < epsilon << 1. The clamp exists so + // log(0) is never evaluated, which needs epsilon strictly positive and + // far below the 0/1 targets it guards. + then DEFAULT_EPSILON > 0.0 + and DEFAULT_EPSILON < 0.001 + and @abs(DEFAULT_EPSILON - 1e-7) < 1e-12 + + test epsilon_is_stored_at_the_same_precision_as_the_default + // A f64 field would silently widen the f32 default and change the + // clamp point. + then @FieldType(BinaryCEConfig, "epsilon") == f32 + and @TypeOf(DEFAULT_EPSILON) == f32 + + test from_logits_selects_the_fused_path + // A bool, so the caller chooses between raw logits and probabilities; + // sigmoid() exists to serve the from_logits == true case. + then @FieldType(BinaryCEConfig, "from_logits") == bool + + test reduction_can_represent_a_choice + // Reduction used to be written `enum : ,` -- an empty variant list -- + // and landed as a zero-sized struct with exactly one value, so the + // reduction field of BinaryCEConfig carried no information. The three + // names below were recovered from the upstream spec this file was + // converted from (trinity-fpga specs/algo/binary_ce.tri:24). + then @typeInfo(Reduction).@"enum".fields.len == 3 + and std.mem.eql(u8, @typeInfo(Reduction).@"enum".fields[0].name, "MEAN") + and std.mem.eql(u8, @typeInfo(Reduction).@"enum".fields[1].name, "SUM") + and std.mem.eql(u8, @typeInfo(Reduction).@"enum".fields[2].name, "NONE") + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant binary_ce_constraint_0 + given input = valid_input() + then true // targets[i] ∈ {0, 1} + + invariant binary_ce_constraint_1 + given input = valid_input() + then true // 0 < epsilon << 1 + + invariant binary_ce_constraint_2 + given input = valid_input() + then true // name: binary_ce + + invariant binary_ce_constraint_3 + given input = valid_input() + then true // name: gradient + + invariant binary_ce_constraint_4 + given input = valid_input() + then true // "Bishop (2006) - Pattern Recognition and Machine Learning" + + invariant binary_ce_constraint_5 + given input = valid_input() + then true // "Goodfellow et al. (2016) - Deep Learning" + diff --git a/apps/website/public/t27/specs/ml/loss/contrastive_loss.t27 b/apps/website/public/t27/specs/ml/loss/contrastive_loss.t27 new file mode 100644 index 0000000000..a8b4237f1f --- /dev/null +++ b/apps/website/public/t27/specs/ml/loss/contrastive_loss.t27 @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Minimum distance for negative pairs | φ² + 1/φ² = 3 | TRINITY + +module ContrastiveLoss; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const ContrastiveLossConfig = struct { + margin : f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(anchor: []const f32, positive: []const f32, negative: []const f32, margin: f32) → f32 + fn forward(anchor: []const f32, positive: []const f32, negative: []const f32, margin: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test forward_basic_case + given input = default_input() + when result = forward(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/ml/loss/cross_entropy_loss.t27 b/apps/website/public/t27/specs/ml/loss/cross_entropy_loss.t27 new file mode 100644 index 0000000000..b39f35991d --- /dev/null +++ b/apps/website/public/t27/specs/ml/loss/cross_entropy_loss.t27 @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Regularization for over-confident predictions | φ² + 1/φ² = 3 | TRINITY + +module CrossEntropy; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const MIN_LOG_PROB : f32 = 1.0e-8; + const DEFAULT_LABEL_SMOOTHING : f32 = 0.1; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const CrossEntropyConfig = struct { + reduction : ReductionType, + label_smoothing : f32, + epsilon : f32, + }; + + pub const ReductionType = struct { + enum_type : Enum, + values : , + Mean : Auto, + Sum : Auto, + None : Auto, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(predictions: []const f32, target: []const f32, output: []f32, config: CrossEntropyConfig) → void + fn forward(predictions: []const f32, target: []const f32, output: []f32, config: CrossEntropyConfig) -> void { + // TODO: Implement from .tri spec + } + + // backward(grad_output: []const f32, predictions: []const f32, target: []const f32, grad_input: []f32, config: CrossEntropyConfig) → void + fn backward(grad_output: []const f32, predictions: []const f32, target: []const f32, grad_input: []f32, config: CrossEntropyConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test forward_basic_case + given input = default_input() + when result = forward(input) + then result != undefined + + test backward_basic_case + given input = default_input() + when result = backward(input) + then result != undefined + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant cross_entropy_constraint_0 + given input = valid_input() + then true // predictions.shape[0] == predictions.shape[1] # batch, num_classes, ... + + invariant cross_entropy_constraint_1 + given input = valid_input() + then true // target.shape[0] == predictions.shape[0] # Batch dimension matches + + invariant cross_entropy_constraint_2 + given input = valid_input() + then true // epsilon > 0 + + invariant cross_entropy_constraint_3 + given input = valid_input() + then true // 0 < label_smoothing <= 1 + diff --git a/apps/website/public/t27/specs/ml/loss/huber_loss.t27 b/apps/website/public/t27/specs/ml/loss/huber_loss.t27 new file mode 100644 index 0000000000..400a4b3f8c --- /dev/null +++ b/apps/website/public/t27/specs/ml/loss/huber_loss.t27 @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Threshold where loss changes from quadratic to linear | φ² + 1/φ² = 3 | TRINITY + +module HuberLoss; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const HuberLossConfig = struct { + delta : f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(predictions: []const f32, targets: []const f32, delta: f32) → f32 + fn forward(predictions: []const f32, targets: []const f32, delta: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test forward_basic_case + given input = default_input() + when result = forward(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/ml/loss/kl_divergence.t27 b/apps/website/public/t27/specs/ml/loss/kl_divergence.t27 new file mode 100644 index 0000000000..b112c5d5c8 --- /dev/null +++ b/apps/website/public/t27/specs/ml/loss/kl_divergence.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// KL divergence - measure of difference between distributions | φ² + 1/φ² = 3 | TRINITY + +module KlDivergence; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(p: []const f32, q: []const f32) → f32 + fn forward(p: []const f32, q: []const f32) -> f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test forward_basic_case + given input = default_input() + when result = forward(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/ml/loss/mse_loss.t27 b/apps/website/public/t27/specs/ml/loss/mse_loss.t27 new file mode 100644 index 0000000000..e233b64cb4 --- /dev/null +++ b/apps/website/public/t27/specs/ml/loss/mse_loss.t27 @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Penalizes large errors more heavily | φ² + 1/φ² = 3 | TRINITY + +module MseLoss; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const MSELossConfig = struct { + reduction : []const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(predictions: []const f32, targets: []const f32) → f32 + fn forward(predictions: []const f32, targets: []const f32) -> f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test forward_basic_case + given input = default_input() + when result = forward(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/ml/optimizer/adagrad.t27 b/apps/website/public/t27/specs/ml/optimizer/adagrad.t27 new file mode 100644 index 0000000000..f0c9d37ee1 --- /dev/null +++ b/apps/website/public/t27/specs/ml/optimizer/adagrad.t27 @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Sum of squared gradients | φ² + 1/φ² = 3 | TRINITY + +module Adagrad; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_LR : f32 = 0.01; + const DEFAULT_EPSILON : f32 = 1e-8; + const DEFAULT_WEIGHT_DECAY : f32 = 0.0; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const AdagradConfig = struct { + learning_rate : f32, + epsilon : f32, + weight_decay : f32, + }; + + pub const AdagradState = struct { + sum_squared_grad : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init_state(num_params: u32) → AdagradState + fn init_state(num_params: u32) -> AdagradState { + // TODO: Implement from .tri spec + } + + // update(params: []f32, grads: []f32, state: AdagradState, config: AdagradConfig) → AdagradState + fn update(params: []f32, grads: []f32, state: AdagradState, config: AdagradConfig) -> AdagradState { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // init_state/update are unimplemented stubs returning void, so no + // accumulate-then-step behaviour can be exercised yet. What the module does + // declare -- the shipped defaults and the config/state shapes -- is tested + // here, including the two constraints that the invariants below only state + // in a comment. + + test default_hyperparameters_satisfy_the_declared_constraints + // Verify: adagrad_constraint_0 (learning_rate > 0) and + // adagrad_constraint_1 (epsilon > 0) hold for the shipped defaults + then (DEFAULT_LR > 0.0) and (DEFAULT_EPSILON > 0.0) + + test default_learning_rate_is_one_hundredth + // Verify: the published default is pinned, not drifting + then @abs(DEFAULT_LR - 0.01) < 1e-9 + + test epsilon_is_negligible_against_the_step_size + // Verify: epsilon exists only to keep the sqrt denominator away from + // zero, so it must sit orders of magnitude under the learning rate + // rather than participating in it + then DEFAULT_EPSILON < DEFAULT_LR * 1e-4 + + test regularisation_is_off_by_default + // Verify: weight decay is opt-in, so plain Adagrad is the default + then DEFAULT_WEIGHT_DECAY == 0.0 + + test config_built_from_the_module_defaults_is_well_formed + // Verify: the three constants populate the three config fields + given cfg = AdagradConfig{.learning_rate=DEFAULT_LR,.epsilon=DEFAULT_EPSILON,.weight_decay=DEFAULT_WEIGHT_DECAY} + then (cfg.learning_rate > 0.0) and (cfg.epsilon > 0.0) and (cfg.weight_decay == 0.0) + + test accumulator_holds_one_running_sum_per_parameter + // Verify: AdagradState carries a per-parameter accumulator, which is + // what makes the learning rate adaptive per coordinate + given state = AdagradState{.sum_squared_grad=@constCast(&[_]f32{0.0,0.0,0.0})} + then (state.sum_squared_grad.len == 3) and (state.sum_squared_grad[0] == 0.0) + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant adagrad_constraint_0 + given input = valid_input() + then true // learning_rate > 0 + + invariant adagrad_constraint_1 + given input = valid_input() + then true // epsilon > 0 + + invariant adagrad_constraint_2 + given input = valid_input() + then true // name: adagrad_update + + invariant adagrad_constraint_3 + given input = valid_input() + then true // name: accumulated_gradients + + invariant adagrad_constraint_4 + given input = valid_input() + then true // "Duchi et al. (2011) - Adaptive Subgradient Methods for Online Learning and Stochastic Optimization" + + invariant adagrad_constraint_5 + given input = valid_input() + then true // "Goodfellow et al. (2016) - Deep Learning (Chapter 8)" + diff --git a/apps/website/public/t27/specs/ml/optimizer/adam.t27 b/apps/website/public/t27/specs/ml/optimizer/adam.t27 new file mode 100644 index 0000000000..8f5d218cf0 --- /dev/null +++ b/apps/website/public/t27/specs/ml/optimizer/adam.t27 @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Adam adapts learning rate per parameter | φ² + 1/φ² = 3 | TRINITY + +module Adam; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_BETA1 : f32 = 0.9; + const DEFAULT_BETA2 : f32 = 0.999; + const DEFAULT_EPSILON : f32 = 1e-8; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const AdamConfig = struct { + learning_rate : f32, + beta1 : f32, + beta2 : f32, + epsilon : f32, + weight_decay : f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // step(grads: []const f32, params: []f32, m: []f32, v: []f32, t: f32, lr: f32) → void + fn step(grads: []const f32, params: []f32, m: []f32, v: []f32, t: f32, lr: f32) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test step_basic_case + given input = default_input() + when result = step(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/ml/optimizer/adamw.t27 b/apps/website/public/t27/specs/ml/optimizer/adamw.t27 new file mode 100644 index 0000000000..5e624a22e6 --- /dev/null +++ b/apps/website/public/t27/specs/ml/optimizer/adamw.t27 @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Weight decay applied to parameters, not gradients | φ² + 1/φ² = 3 | TRINITY + +module Adamw; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_WEIGHT_DECAY : f32 = 0.01; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const AdamWConfig = struct { + learning_rate : f32, + beta1 : f32, + beta2 : f32, + weight_decay : f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // step(grad: []const f32, param: []f32, m: []f32, v: []f32, lr: f32, wd: f32) → void + fn step(grad: []const f32, param: []f32, m: []f32, v: []f32, lr: f32, wd: f32) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test init_creates_zero_moments + given config = AdamWConfig{.learning_rate = 1e-3, .beta1 = 0.9, .beta2 = 0.999, .weight_decay = 0.01, .epsilon = 1e-8, .amsgrad = false, .use_phi_betas = false} + when state = init(config, 10) + then state.m.len == 10 + and state.v.len == 10 + and all(state.m, is_zero) + and all(state.v, is_zero) + and state.step == 0 + + test init_with_amsgrad_creates_v_max + given config = AdamWConfig{.amsgrad = true} + when state = init(config, 10) + then state.v_max.len == 10 + and all(state.v_max, is_zero) + + test compute_bias_correction_first_step + given beta = 0.9 + and t = 1 + when result = compute_bias_correction(beta, t) + then approximately_equal(result, 0.1) + + test compute_bias_correction_later_steps + given beta = 0.9 + and t = 10 + when result = compute_bias_correction(beta, t) + then result < 1.0 + and result > 0.0 + + test update_first_momentum_basic + given m_prev = 0.0 + and grad = 1.0 + and beta1 = 0.9 + when result = update_first_moment(m_prev, grad, beta1) + then approximately_equal(result, 0.1) // 0.9 * 0 + 0.1 * 1 + + test update_first_momentum_with_history + given m_prev = 0.5 + and grad = 1.0 + and beta1 = 0.9 + when result = update_first_moment(m_prev, grad, beta1) + then approximately_equal(result, 0.9 * 0.5 + 0.1) + + test update_second_momentum_basic + given v_prev = 0.0 + and grad = 2.0 + and beta2 = 0.999 + when result = update_second_moment(v_prev, grad, beta2) + then approximately_equal(result, 0.001 * 4.0) // 0.999 * 0 + 0.001 * 4 + + test compute_update_basic + given m = 0.1 + and v = 0.01 + and lr_t = 0.001 + and epsilon = 1e-8 + when result = compute_update(m, v, lr_t, epsilon) + then result > 0.0 + + test apply_weight_decay_reduces_parameters + given params = [1.0, 2.0, 3.0] + and lr = 0.01 + and weight_decay = 0.1 + when result = apply_weight_decay(params, lr, weight_decay) + then result[0] < 1.0 + and result[1] < 2.0 + and result[2] < 3.0 + + test step_without_weight_decay_matches_adam + given config = AdamWConfig{.learning_rate = 0.001, .beta1 = 0.9, .beta2 = 0.999, .weight_decay = 0.0} + and state = init(config, 1) + and params = [1.0] + and grads = [0.1] + when result = step(state, params, grads) + then result.updated_params.len == 1 + + test step_with_weight_decay_shrinks_params + given config = AdamWConfig{.learning_rate = 0.01, .beta1 = 0.9, .beta2 = 0.999, .weight_decay = 0.1} + and state = init(config, 1) + and params = [1.0] + and grads = [0.0] // No gradient, only weight decay + when result = step(state, params, grads) + then result.updated_params[0] < 1.0 + + test amsgrad_update_tracks_maximum + given v = 0.05 + and v_max = 0.03 + when result = amsgrad_update(v, v_max) + then result == 0.05 + + test amsgrad_update_preserves_maximum + given v = 0.02 + and v_max = 0.05 + when result = amsgrad_update(v, v_max) + then result == 0.05 + + test get_effective_beta_phi_damped + given config = AdamWConfig{.beta1 = 0.9, .beta2 = 0.999, .use_phi_betas = true} + when beta1 = get_effective_beta(config, "beta1") + and beta2 = get_effective_beta(config, "beta2") + then approximately_equal(beta1, PHI_BETA1) + and approximately_equal(beta2, PHI_BETA2) + + test get_effective_beta_uses_config + given config = AdamWConfig{.beta1 = 0.85, .beta2 = 0.995, .use_phi_betas = false} + when beta1 = get_effective_beta(config, "beta1") + and beta2 = get_effective_beta(config, "beta2") + then approximately_equal(beta1, 0.85) + and approximately_equal(beta2, 0.995) + + test step_increases_step_counter + given config = AdamWConfig{} + and state = init(config, 10) + and params = random_input(10) + and grads = random_input(10) + when result = step(state, params, grads) + then result.step_norm >= 0.0 // Step was executed + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants + // ═══════════════════════════════════════════════════════════ + + invariant step_preserves_param_count + given config = any_adamw_config() + and state = init(config, 100) + and params = random_input(100) + and grads = random_input(100) + when result = step(state, params, grads) + then result.updated_params.len == 100 + + invariant moments_have_same_length + given config = any_adamw_config() + and state = init(config, 50) + then state.m.len == 50 + and state.v.len == 50 + + invariant first_moment_remains_bounded + given config = any_adamw_config() + and state = init(config, 10) + and params = finite_input(10) + and grads = bounded_input(10, -1.0, 1.0) + and result = step(state, params, grads) + then all(result.m, is_finite) + + invariant second_moment_non_negative + given config = any_adamw_config() + and state = init(config, 10) + and params = finite_input(10) + and grads = finite_input(10) + and result = step(state, params, grads) + then all(result.v, fn(x) x >= 0.0) + + invariant bias_correction_in_zero_one_range + given beta = random_gf16_in_range(0.0, 0.999) + and t = random_u64_in_range(1, 1000) + when bc = compute_bias_correction(beta, t) + then bc >= 0.0 + and bc <= 1.0 + + invariant phi_betas_smaller_than_defaults + then PHI_BETA1 < DEFAULT_BETA1 + and PHI_BETA2 < DEFAULT_BETA2 + + invariant weight_decay_preserves_sign + given params = [-1.0, 0.0, 1.0] + and lr = 0.01 + and weight_decay = 0.1 + when result = apply_weight_decay(params, lr, weight_decay) + then result[0] < 0.0 + and result[2] > 0.0 + + invariant amsgrad_v_max_non_decreasing + given config = AdamWConfig{.amsgrad = true} + and state = init(config, 10) + and params = random_input(10) + and grads = random_input(10) + and result1 = step(state, params, grads) + and result2 = step(state, result1.updated_params, grads) + then all_pairs(result1.v_max, result2.v_max, fn(a, b) b >= a) + + invariant denominator_positive + given v = 0.0 + and epsilon = 1e-8 + when denom = sqrt(v) + epsilon + then denom > 0.0 + + // ═══════════════════════════════════════════════════════════ + // TDD: Benchmarks + // ═══════════════════════════════════════════════════════════ + + bench init_small + given config = any_adamw_config() + when result = init(config, 100) + then elapsed_time_us < 50 + + bench init_large + given config = any_adamw_config() + when result = init(config, 1000000) + then elapsed_time_ms < 20 + + bench step_small + given config = any_adamw_config() + and state = init(config, 100) + and params = random_input(100) + and grads = random_input(100) + when result = step(state, params, grads) + then elapsed_time_us < 200 + + bench step_medium + given config = any_adamw_config() + and state = init(config, 10000) + and params = random_input(10000) + and grads = random_input(10000) + when result = step(state, params, grads) + then elapsed_time_ms < 10 + + bench step_large + given config = any_adamw_config() + and state = init(config, 1000000) + and params = random_input(1000000) + and grads = random_input(1000000) + when result = step(state, params, grads) + then elapsed_time_ms < 200 + + bench compute_bias_correction + given beta = 0.9 + and t = 100 + when result = compute_bias_correction(beta, t) + then elapsed_time_ns < 50 + + bench amsgrad_update + given v = random_positive_gf16() + and v_max = random_positive_gf16() + when result = amsgrad_update(v, v_max) + then elapsed_time_ns < 10 + + // ═══════════════════════════════════════════════════════════ + // Mathematical Notes + // ═══════════════════════════════════════════════════════════ + // + // AdamW Update Rule: + // 1. Apply weight decay (decoupled): + // θ_t = θ_{t-1} * (1 - η * λ) + // + // 2. Update moments: + // m_t = β₁ * m_{t-1} + (1 - β₁) * g_t + // v_t = β₂ * v_{t-1} + (1 - β₂) * g_t² + // + // 3. Bias correction: + // m̂_t = m_t / (1 - β₁^t) + // v̂_t = v_t / (1 - β₂^t) + // + // 4. Parameter update: + // θ_t = θ_t - η * m̂_t / (√v̂_t + ε) + // + // AMSGrad Variant: + // v_max_t = max(v_max_{t-1}, v_t) + // Use √v_max_t instead of √v̂_t in denominator + // + // Phi-Damped Betas: + // β₁_eff = β₁ / φ ≈ 0.556 for β₁ = 0.9 + // β₂_eff = β₂ / φ ≈ 0.617 for β₂ = 0.999 + // Reduces momentum, provides smoother convergence + // + // Difference from Adam: + // Adam: weight decay added to gradients before moment update + // AdamW: weight decay applied directly to parameters (decoupled) + // + // ═══════════════════════════════════════════════════════════ diff --git a/apps/website/public/t27/specs/ml/optimizer/lamb.t27 b/apps/website/public/t27/specs/ml/optimizer/lamb.t27 new file mode 100644 index 0000000000..016a6fe317 --- /dev/null +++ b/apps/website/public/t27/specs/ml/optimizer/lamb.t27 @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// LAMB parameter update with trust ratio clipping | φ² + 1/φ² = 3 | TRINITY + +module Lamb; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_LR : f32 = 0.001; + const DEFAULT_BETA1 : f32 = 0.9; + const DEFAULT_BETA2 : f32 = 0.999; + const DEFAULT_EPSILON : f32 = 1e-6; + const DEFAULT_CLIP_THRESHOLD : f32 = 10.0; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const LAMBConfig = struct { + learning_rate : f32, + beta1 : f32, + beta2 : f32, + epsilon : f32, + weight_decay : f32, + clip_threshold : f32, + }; + + pub const LAMBState = struct { + m : []f32, + v : []f32, + step : u32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init_state(num_params: u32) → LAMBState + fn init_state(num_params: u32) -> LAMBState { + // TODO: Implement from .tri spec + } + + // compute_layer_update(params: []f32, grads: []f32, state: LAMBState, config: LAMBConfig) → []f32 + fn compute_layer_update(params: []f32, grads: []f32, state: LAMBState, config: LAMBConfig) -> []f32 { + // TODO: Implement from .tri spec + } + + // forward(layers: [][]f32, grads: [][]f32, states: []LAMBState, config: LAMBConfig) → [][]f32 + fn forward(layers: [][]f32, grads: [][]f32, states: []LAMBState, config: LAMBConfig) -> [][]f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // init_state, compute_layer_update and forward have no bodies, so nothing + // about the update rule can be exercised yet. What the module does decide + // is its default hyper-parameters, and those are exactly what the + // invariants below claim without asserting. Checked here instead. + + test default_betas_lie_in_the_open_unit_interval + // lamb_constraint_0 and lamb_constraint_1 + given beta1 = DEFAULT_BETA1 + and beta2 = DEFAULT_BETA2 + then beta1 > 0.0 and beta1 < 1.0 and beta2 > 0.0 and beta2 < 1.0 + + test second_moment_decays_slower_than_first + // v accumulates over a longer window than m, so beta2 > beta1 + given beta1 = DEFAULT_BETA1 + and beta2 = DEFAULT_BETA2 + then beta2 > beta1 + + test default_epsilon_is_positive_and_well_below_the_step_size + // lamb_constraint_2: epsilon only guards the division, it must not + // dominate the update + given epsilon = DEFAULT_EPSILON + and lr = DEFAULT_LR + then epsilon > 0.0 and epsilon < lr + + test default_clip_threshold_is_positive + // lamb_constraint_3: the trust ratio is clipped from above + given clip = DEFAULT_CLIP_THRESHOLD + then clip > 0.0 + + test default_learning_rate_is_a_positive_fraction + given lr = DEFAULT_LR + then lr > 0.0 and lr < 1.0 + + test default_config_carries_the_declared_defaults + given cfg = LAMBConfig{ .learning_rate = DEFAULT_LR, .beta1 = DEFAULT_BETA1, .beta2 = DEFAULT_BETA2, .epsilon = DEFAULT_EPSILON, .weight_decay = 0.0, .clip_threshold = DEFAULT_CLIP_THRESHOLD } + then cfg.beta1 > 0.0 and cfg.beta1 < 1.0 and cfg.beta2 > 0.0 and cfg.beta2 < 1.0 and cfg.epsilon > 0.0 and cfg.clip_threshold > 0.0 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant lamb_constraint_0 + given input = valid_input() + then true // 0 < beta1 < 1 + + invariant lamb_constraint_1 + given input = valid_input() + then true // 0 < beta2 < 1 + + invariant lamb_constraint_2 + given input = valid_input() + then true // epsilon > 0 + + invariant lamb_constraint_3 + given input = valid_input() + then true // clip_threshold > 0 + + invariant lamb_constraint_4 + given input = valid_input() + then true // name: trust_ratio + + invariant lamb_constraint_5 + given input = valid_input() + then true // name: lamb_update + + invariant lamb_constraint_6 + given input = valid_input() + then true // "You et al. (2020) - Large Batch Optimization for Deep Learning: Training BERT in 76 minutes" + + invariant lamb_constraint_7 + given input = valid_input() + then true // "Used in BERT, T5, and other large-scale Transformer training" + diff --git a/apps/website/public/t27/specs/ml/optimizer/lr_scheduler.t27 b/apps/website/public/t27/specs/ml/optimizer/lr_scheduler.t27 new file mode 100644 index 0000000000..6f65489608 --- /dev/null +++ b/apps/website/public/t27/specs/ml/optimizer/lr_scheduler.t27 @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Smooth decay following cosine curve | φ² + 1/φ² = 3 | TRINITY + +module Scheduler; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SchedulerConfig = struct { + max_steps : u32, + warmup_steps : u32, + min_lr : f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // get_lr(step: u32, max_steps: u32, base_lr: f32, min_lr: f32) → f32 + fn get_lr(step: u32, max_steps: u32, base_lr: f32, min_lr: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test init_creates_valid_state + given config = SchedulerConfig{.max_steps = 1000, .warmup_steps = 100, .min_lr = 1e-6, .max_lr = 1e-3, .use_phi_schedule = false} + when state = init(config) + then state.current_step == 0 + + test get_lr_at_step_zero_returns_min + given config = SchedulerConfig{.max_steps = 1000, .warmup_steps = 100, .min_lr = 1e-6, .max_lr = 1e-3} + when result = get_lr_at_step(config, 0) + then approximately_equal(result, 1e-6) + + test get_lr_at_warmup_end_returns_max + given config = SchedulerConfig{.max_steps = 1000, .warmup_steps = 100, .min_lr = 1e-6, .max_lr = 1e-3} + when result = get_lr_at_step(config, 100) + then approximately_equal(result, 1e-3) + + test get_lr_at_max_steps_returns_min + given config = SchedulerConfig{.max_steps = 1000, .warmup_steps = 100, .min_lr = 1e-6, .max_lr = 1e-3} + when result = get_lr_at_step(config, 1000) + then approximately_equal(result, 1e-6) + + test linear_warmup_increases_monotonically + given min_lr = 1e-6 + and max_lr = 1e-3 + and warmup_steps = 100 + when lr1 = linear_warmup(0, warmup_steps, min_lr, max_lr) + and lr50 = linear_warmup(50, warmup_steps, min_lr, max_lr) + and lr100 = linear_warmup(100, warmup_steps, min_lr, max_lr) + then lr1 < lr50 and lr50 < lr100 + + test cosine_decay_decreases_monotonically + given max_lr = 1e-3 + and min_lr = 1e-6 + and total_steps = 100 + when lr0 = cosine_decay(0, total_steps, max_lr, min_lr) + and lr50 = cosine_decay(50, total_steps, max_lr, min_lr) + and lr100 = cosine_decay(100, total_steps, max_lr, min_lr) + then lr0 > lr50 and lr50 > lr100 + + test step_increases_step_counter + given config = SchedulerConfig{.max_steps = 1000, .warmup_steps = 100} + and state = init(config) + when new_state = step(state) + then new_state.current_step == 1 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants + // ═══════════════════════════════════════════════════════════ + + invariant lr_always_between_min_and_max + given config = any_scheduler_config() + and step = random_u64_in_range(0, config.max_steps) + when lr = get_lr_at_step(config, step) + then lr >= config.min_lr + and lr <= config.max_lr + + invariant lr_at_step_zero_equals_min + given config = any_scheduler_config() + when lr = get_lr_at_step(config, 0) + then approximately_equal(lr, config.min_lr) + + invariant lr_at_warmup_end_equals_max + given config = any_scheduler_config() + when lr = get_lr_at_step(config, config.warmup_steps) + then approximately_equal(lr, config.max_lr) + + invariant lr_at_max_steps_equals_min + given config = any_scheduler_config() + when lr = get_lr_at_step(config, config.max_steps) + then approximately_equal(lr, config.min_lr) + + invariant phi_schedule_modifies_decay_curve + given config1 = SchedulerConfig{.use_phi_schedule = false} + and config2 = SchedulerConfig{.use_phi_schedule = true} + and step = config1.warmup_steps + 100 + when lr1 = get_lr_at_step(config1, step) + and lr2 = get_lr_at_step(config2, step) + then lr1 != lr2 + + invariant inv_phi_less_than_one + then INV_PHI < 1.0 + + // ═══════════════════════════════════════════════════════════ + // TDD: Benchmarks + // ═══════════════════════════════════════════════════════════ + + bench init + given config = any_scheduler_config() + when result = init(config) + then elapsed_time_ns < 100 + + bench get_lr + given config = any_scheduler_config() + and state = init(config) + when result = get_lr(state) + then elapsed_time_ns < 100 + + bench get_lr_at_step + given config = any_scheduler_config() + and step = random_u64() + when result = get_lr_at_step(config, step) + then elapsed_time_ns < 100 + + // ═══════════════════════════════════════════════════════════ + // Mathematical Notes + // ═══════════════════════════════════════════════════════════ + // + // Linear Warmup: + // lr(t) = lr_min + (lr_max - lr_min) * (t / T_warmup) for t < T_warmup + // + // Cosine Decay: + // lr(t) = lr_min + (lr_max - lr_min) * 0.5 * (1 + cos(π * progress)) + // where progress = (t - T_warmup) / (T_total - T_warmup) + // + // Φ-Optimized Cosine Decay: + // Uses progress^INV_PHI instead of linear progress + // Slower initial decay, more learning in early-mid training + // + // ═══════════════════════════════════════════════════════════ diff --git a/apps/website/public/t27/specs/ml/optimizer/rmsprop.t27 b/apps/website/public/t27/specs/ml/optimizer/rmsprop.t27 new file mode 100644 index 0000000000..4e99ef489d --- /dev/null +++ b/apps/website/public/t27/specs/ml/optimizer/rmsprop.t27 @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// RMSprop optimizer with moving average of squared gradients | φ² + 1/φ² = 3 | TRINITY + +module Rmsprop; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_DECAY : f32 = 0.9; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const RMSpropConfig = struct { + learning_rate : f32, + decay : f32, + epsilon : f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // step(grad: []const f32, param: []f32, cache: []f32) → void + fn step(grad: []const f32, param: []f32, cache: []f32) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test step_basic_case + given input = default_input() + when result = step(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/ml/optimizer/sgd.t27 b/apps/website/public/t27/specs/ml/optimizer/sgd.t27 new file mode 100644 index 0000000000..a968c45ab4 --- /dev/null +++ b/apps/website/public/t27/specs/ml/optimizer/sgd.t27 @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Nesterov accelerated gradient (NAG) | φ² + 1/φ² = 3 | TRINITY + +module Sgd; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_LR : f32 = 0.01; + const DEFAULT_MOMENTUM : f32 = 0.0; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SGDConfig = struct { + learning_rate : f32, + momentum : f32, + weight_decay : f32, + dampening : f32, + nesterov : bool, + }; + + pub const SGDState = struct { + velocity : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(state: SGDState, param_count: u32) → void + fn init(state: SGDState, param_count: u32) -> void { + // TODO: Implement from .tri spec + } + + // update(params: []f32, grads: []const f32, state: SGDState, config: SGDConfig) → void + fn update(params: []f32, grads: []const f32, state: SGDState, config: SGDConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // init() and update() have no body yet, so they panic when called. The + // defaults and the two config/state types are what this module states. + + test sgd_default_learning_rate_is_one_percent + // constraint sgd_constraint_0: learning_rate > 0 + given lr = DEFAULT_LR + then @abs(lr - 0.01) < 0.0000005 + and lr > 0.0 + + test sgd_default_momentum_is_plain_gradient_descent + // constraint sgd_constraint_1: 0 <= momentum < 1; the default is the + // no-momentum end of that range + given m = DEFAULT_MOMENTUM + then m >= 0.0 + and m < 1.0 + and @abs(m) < 0.0000005 + + test sgd_config_carries_every_hyperparameter + given cfg = SGDConfig{.learning_rate=0.1,.momentum=0.9,.weight_decay=0.0001,.dampening=0.0,.nesterov=true} + then @abs(cfg.learning_rate - 0.1) < 0.0000005 + and @abs(cfg.momentum - 0.9) < 0.0000005 + and @abs(cfg.weight_decay - 0.0001) < 0.0000005 + and cfg.nesterov + + test sgd_state_starts_with_no_velocity + // constraint sgd_constraint_4 ties velocity.size() to params.size(); + // with no params there is no velocity + given state = SGDState{.velocity=&[_]f32{}} + then state.velocity.len == 0 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant sgd_constraint_0 + given input = valid_input() + then true // learning_rate > 0 + + invariant sgd_constraint_1 + given input = valid_input() + then true // 0 <= momentum < 1 + + invariant sgd_constraint_2 + given input = valid_input() + then true // weight_decay >= 0 + + invariant sgd_constraint_3 + given input = valid_input() + then true // params.size() == grads.size() + + invariant sgd_constraint_4 + given input = valid_input() + then true // If momentum > 0: state.velocity.size() == params.size() + diff --git a/apps/website/public/t27/specs/ml/optimizer/sgd_momentum.t27 b/apps/website/public/t27/specs/ml/optimizer/sgd_momentum.t27 new file mode 100644 index 0000000000..f74915af03 --- /dev/null +++ b/apps/website/public/t27/specs/ml/optimizer/sgd_momentum.t27 @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Accumulates gradient direction for faster convergence | φ² + 1/φ² = 3 | TRINITY + +module SgdMomentum; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_MOMENTUM : f32 = 0.9; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SGDMomentumConfig = struct { + learning_rate : f32, + momentum : f32, + nesterov : bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // step(grad: []const f32, param: []f32, velocity: []f32, lr: f32, mu: f32, nesterov: bool) → void + fn step(grad: []const f32, param: []f32, velocity: []f32, lr: f32, mu: f32, nesterov: bool) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test init_creates_zero_velocities + given config = SgdMomentumConfig{.learning_rate = 0.01, .momentum = 0.9, .weight_decay = 0.0, .nesterov = false, .use_phi_damping = false, .dampening = 0.1} + when state = init(config, 10) + then state.velocities.len == 10 + and all(state.velocities, is_zero) + and state.step == 0 + + test compute_velocity_basic_case + given velocity = 0.0 + and grad = 1.0 + and momentum = 0.9 + and dampening = 0.1 + when result = compute_velocity(velocity, grad, momentum, dampening) + then approximately_equal(result, 0.1) + + test compute_velocity_with_previous_velocity + given velocity = 0.5 + and grad = 1.0 + and momentum = 0.9 + and dampening = 0.1 + when result = compute_velocity(velocity, grad, momentum, dampening) + then approximately_equal(result, 0.9 * 0.5 + 0.1) + + test standard_update_decreases_param + given param = 1.0 + and velocity = 0.1 + and lr = 0.01 + when result = standard_update(param, velocity, lr) + then result < param + and approximately_equal(result, 1.0 - 0.001) + + test nesterov_update_considers_lookahead + given param = 1.0 + and velocity = 0.1 + and grad = 0.5 + and lr = 0.01 + and momentum = 0.9 + when result = nesterov_update(param, velocity, grad, lr, momentum) + then approximately_equal(result, 1.0 - 0.01 * (0.9 * 0.1 + 0.5)) + + test step_without_momentum_behaves_like_sgd + given config = SgdMomentumConfig{.learning_rate = 0.01, .momentum = 0.0, .weight_decay = 0.0, .nesterov = false, .use_phi_damping = false, .dampening = 1.0} + and state = init(config, 3) + and params = [1.0, 2.0, 3.0] + and grads = [0.1, 0.2, 0.3] + when result = step(state, params, grads) + then approximately_equal(result.updated_params[0], 1.0 - 0.001) + and approximately_equal(result.updated_params[1], 2.0 - 0.002) + and approximately_equal(result.updated_params[2], 3.0 - 0.003) + + test step_with_momentum_accumulates_velocity + given config = SgdMomentumConfig{.learning_rate = 0.01, .momentum = 0.9, .weight_decay = 0.0, .nesterov = false, .use_phi_damping = false, .dampening = 0.1} + and state = init(config, 1) + and params = [1.0] + and grads = [0.1] + when result1 = step(state, params, grads) + and state2 = SgdMomentumState{.velocities = result1.velocities, .param_count = 1, .step = 1} + and result2 = step(state2, result1.updated_params, grads) + then result2.velocities[0] > result1.velocities[0] + + test step_with_weight_decay_modifies_gradients + given config = SgdMomentumConfig{.learning_rate = 0.01, .momentum = 0.9, .weight_decay = 0.1, .nesterov = false, .use_phi_damping = false, .dampening = 0.1} + and state = init(config, 1) + and params = [1.0] + and grads = [0.1] + when result = step(state, params, grads) + then result.updated_params[0] < 1.0 - 0.001 // More aggressive decrease due to weight decay + + test apply_weight_decay_increases_gradient_magnitude + given params = [1.0, -1.0] + and grads = [0.1, 0.1] + and weight_decay = 0.1 + when result = apply_weight_decay(params, grads, weight_decay) + then approximately_equal(result[0], 0.2) // 0.1 + 0.1 * 1.0 + and approximately_equal(result[1], 0.0) // 0.1 + 0.1 * (-1.0) + + test phi_damped_momentum_reduces_momentum + given base_momentum = 0.9 + when result = phi_damped_momentum(base_momentum) + then result < base_momentum + and approximately_equal(result, 0.9 / PHI) + + test get_effective_momentum_returns_phi_damped_when_enabled + given config = SgdMomentumConfig{.momentum = 0.9, .use_phi_damping = true} + when result = get_effective_momentum(config) + then approximately_equal(result, PHI_DAMPED_MOMENTUM) + + test get_effective_momentum_returns_base_when_disabled + given config = SgdMomentumConfig{.momentum = 0.9, .use_phi_damping = false} + when result = get_effective_momentum(config) + then approximately_equal(result, 0.9) + + test step_norm_is_correct + given config = SgdMomentumConfig{.learning_rate = 0.01, .momentum = 0.9, .weight_decay = 0.0, .nesterov = false, .use_phi_damping = false, .dampening = 0.1} + and state = init(config, 2) + and params = [1.0, 2.0] + and grads = [0.3, 0.4] + when result = step(state, params, grads) + then approximately_equal(result.step_norm, sqrt(pow(1.0 - result.updated_params[0], 2) + pow(2.0 - result.updated_params[1], 2))) + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants + // ═══════════════════════════════════════════════════════════ + + invariant step_preserves_param_count + given config = any_sgd_momentum_config() + and state = init(config, 100) + and params = random_input(100) + and grads = random_input(100) + when result = step(state, params, grads) + then result.updated_params.len == params.len + + invariant step_velocities_same_length + given config = any_sgd_momentum_config() + and state = init(config, 50) + and params = random_input(50) + and grads = random_input(50) + when result = step(state, params, grads) + then result.velocities.len == params.len + + invariant velocity_remains_bounded + given config = any_sgd_momentum_config() + and state = init(config, 10) + and params = finite_input(10) + and grads = bounded_input(10, -1.0, 1.0) + and result = step(state, params, grads) + then all(result.velocities, is_finite) + + invariant momentum_coefficient_in_valid_range + given config = SgdMomentumConfig{.momentum = 0.9, .use_phi_damping = false} + when effective = get_effective_momentum(config) + then effective >= 0.0 + and effective <= 1.0 + + invariant phi_damped_momentum_less_than_base + given base = random_gf16_in_range(0.5, 0.99) + when damped = phi_damped_momentum(base) + then damped < base + and damped > 0.0 + + invariant step_norm_non_negative + given config = any_sgd_momentum_config() + and state = init(config, 10) + and params = random_input(10) + and grads = random_input(10) + when result = step(state, params, grads) + then result.step_norm >= 0.0 + + invariant zero_grad_preserves_velocities + given config = any_sgd_momentum_config() + and state = init(config, 10) + and state.velocities[0] = 0.5 + when result = zero_grad(state) + then result.velocities[0] == 0.5 + + invariant phi_damped_momentum_constant + then approximately_equal_within(PHI_DAMPED_MOMENTUM, 0.9 / PHI, 1e-6) + + invariant dampening_equals_one_minus_momentum_by_default + given config = SgdMomentumConfig{.momentum = 0.9, .dampening = 0.0} + when result = 1.0 - config.momentum + then approximately_equal(result, 0.1) + + // ═══════════════════════════════════════════════════════════ + // TDD: Benchmarks + // ═══════════════════════════════════════════════════════════ + + bench init_small + given config = any_sgd_momentum_config() + when result = init(config, 100) + then elapsed_time_us < 50 + + bench init_large + given config = any_sgd_momentum_config() + when result = init(config, 1000000) + then elapsed_time_ms < 10 + + bench step_small + given config = any_sgd_momentum_config() + and state = init(config, 100) + and params = random_input(100) + and grads = random_input(100) + when result = step(state, params, grads) + then elapsed_time_us < 100 + + bench step_medium + given config = any_sgd_momentum_config() + and state = init(config, 10000) + and params = random_input(10000) + and grads = random_input(10000) + when result = step(state, params, grads) + then elapsed_time_ms < 5 + + bench step_large + given config = any_sgd_momentum_config() + and state = init(config, 1000000) + and params = random_input(1000000) + and grads = random_input(1000000) + when result = step(state, params, grads) + then elapsed_time_ms < 100 + + bench compute_velocity + given velocity = random_gf16() + and grad = random_gf16() + and momentum = 0.9 + and dampening = 0.1 + when result = compute_velocity(velocity, grad, momentum, dampening) + then elapsed_time_ns < 10 + + bench nesterov_update + given param = random_gf16() + and velocity = random_gf16() + and grad = random_gf16() + and lr = 0.01 + and momentum = 0.9 + when result = nesterov_update(param, velocity, grad, lr, momentum) + then elapsed_time_ns < 10 + + // ═══════════════════════════════════════════════════════════ + // Mathematical Notes + // ═══════════════════════════════════════════════════════════ + // + // Standard Momentum Update: + // v_t = μ * v_{t-1} + (1 - μ) * ∇L(θ_{t-1}) + // θ_t = θ_{t-1} - η * v_t + // + // Nesterov Accelerated Gradient: + // v_t = μ * v_{t-1} + (1 - μ) * ∇L(θ_{t-1} - η * μ * v_{t-1}) + // θ_t = θ_{t-1} - η * (μ * v_t + ∇L(θ_t)) + // + // With Weight Decay (L2 Regularization): + // ∇L'(θ) = ∇L(θ) + λ * θ + // + // Phi-Damped Momentum: + // μ_eff = μ / φ ≈ 0.556 for μ = 0.9 + // Reduces momentum accumulation, providing smoother convergence + // + // Dampening: + // Standard: dampening = 1 - momentum + // Alternative: dampening can be set independently + // + // ═══════════════════════════════════════════════════════════ diff --git a/apps/website/public/t27/specs/ml/pathway/mlp.t27 b/apps/website/public/t27/specs/ml/pathway/mlp.t27 new file mode 100644 index 0000000000..689d7f5090 --- /dev/null +++ b/apps/website/public/t27/specs/ml/pathway/mlp.t27 @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// ReLU activation for hidden layers | φ² + 1/φ² = 3 | TRINITY + +module Mlp; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const MNIST_INPUT_SIZE : u32 = 784; + const MNIST_OUTPUT_SIZE : u32 = 10; + const DEFAULT_HIDDEN_SIZE : u32 = 128; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const MLPConfig = struct { + input_size : u32, + hidden_size : u32, + output_size : u32, + }; + + pub const MLPState = struct { + w1 : []f32, + b1 : []f32, + w2 : []f32, + b2 : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, state: MLPState, output: []f32, config: MLPConfig) → void + fn forward(input: []const f32, state: MLPState, output: []f32, config: MLPConfig) -> void { + // TODO: Implement from .tri spec + } + + // init(state: MLPState, config: MLPConfig) → void + fn init(state: MLPState, config: MLPConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward and init have no bodies, so no activation can be checked. The + // module does fix the MNIST geometry, which is what the invariants below + // describe in comments and never assert. + + test mnist_input_is_a_flattened_28x28_image + // mlp_constraint_0 + given input_size = MNIST_INPUT_SIZE + then input_size == 784 and input_size == 28 * 28 + + test mnist_output_is_one_logit_per_digit + // mlp_constraint_1 + given output_size = MNIST_OUTPUT_SIZE + then output_size == 10 + + test default_hidden_layer_is_non_empty + // mlp_constraint_2 + given hidden = DEFAULT_HIDDEN_SIZE + then hidden > 0 + + test default_config_weight_counts_fit_a_u32 + // mlp_constraint_3 and mlp_constraint_4: w1 is input*hidden, w2 is + // hidden*output. 784*128 = 100352 and 128*10 = 1280, both well inside + // the u32 the config fields are declared as. + given cfg = MLPConfig{ .input_size = MNIST_INPUT_SIZE, .hidden_size = DEFAULT_HIDDEN_SIZE, .output_size = MNIST_OUTPUT_SIZE } + and w1_count = cfg.input_size * cfg.hidden_size + and w2_count = cfg.hidden_size * cfg.output_size + then w1_count == 100352 and w2_count == 1280 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant mlp_constraint_0 + given input = valid_input() + then true // input_size == MNIST_INPUT_SIZE (784) + + invariant mlp_constraint_1 + given input = valid_input() + then true // output_size == MNIST_OUTPUT_SIZE (10) + + invariant mlp_constraint_2 + given input = valid_input() + then true // hidden_size > 0 + + invariant mlp_constraint_3 + given input = valid_input() + then true // w1.size() == input_size * hidden_size + + invariant mlp_constraint_4 + given input = valid_input() + then true // w2.size() == hidden_size * output_size + + invariant mlp_constraint_5 + given input = valid_input() + then true // b1.size() == hidden_size + + invariant mlp_constraint_6 + given input = valid_input() + then true // b2.size() == output_size + diff --git a/apps/website/public/t27/specs/ml/recurrent/attention_mechanism.t27 b/apps/website/public/t27/specs/ml/recurrent/attention_mechanism.t27 new file mode 100644 index 0000000000..513c22d40e --- /dev/null +++ b/apps/website/public/t27/specs/ml/recurrent/attention_mechanism.t27 @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Memory-efficient attention for long sequences | φ² + 1/φ² = 3 | TRINITY + +module Attention; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const SCALE_FACTOR : f32 = 0.0; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const AttentionConfig = struct { + d_model : u32, + num_heads : u32, + dropout : f32, + causal : bool, + }; + + pub const AttentionOutput = struct { + output : []f32, + weights : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(query: []const f32, key: []const f32, value: []const f32, output: []f32, weights: []f32, config: AttentionConfig) → void + fn forward(query: []const f32, key: []const f32, value: []const f32, output: []f32, weights: []f32, config: AttentionConfig) -> void { + // TODO: Implement from .tri spec + } + + // backward(grad_output: []const f32, query: []const f32, key: []const f32, value: []const f32, attn_weights: []const f32, grad_query: []f32, grad_key: []f32, grad_value: []f32, config: AttentionConfig) → void + fn backward(grad_output: []const f32, query: []const f32, key: []const f32, value: []const f32, attn_weights: []const f32, grad_query: []f32, grad_key: []f32, grad_value: []f32, config: AttentionConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward and backward are unimplemented stubs -- both emit + // `@panic("not yet implemented")`, so attention_constraint_2 (matching + // query/key/value shapes) cannot be checked here. The declared constant and + // config shape are what remain testable. + + test head_split_and_dropout_are_declared_as_the_constraints_assume + // attention_constraint_0 divides d_model by num_heads and _1 bounds + // dropout in [0, 1), so both must be numeric fields of this config. + then @FieldType(AttentionConfig, "d_model") == u32 + and @FieldType(AttentionConfig, "num_heads") == u32 + and @FieldType(AttentionConfig, "dropout") == f32 + + test causal_masking_is_a_runtime_flag + then @FieldType(AttentionConfig, "causal") == bool + and @typeInfo(AttentionConfig).@"struct".fields.len == 4 + + test forward_returns_the_weights_alongside_the_output + // Both are flat f32 buffers, so the attention map stays inspectable + // rather than being folded away inside forward(). + then @FieldType(AttentionOutput, "output") == []f32 + and @FieldType(AttentionOutput, "weights") == []f32 + + test scale_factor_is_usable_as_a_divisor_of_the_scores + // FAILING ON PURPOSE. Scaled dot-product attention divides the scores + // by sqrt(d_k), but SCALE_FACTOR is declared 0.0. Multiplying by it + // annihilates every score, so softmax would return a uniform + // distribution for any input. It needs a positive value, or to be + // computed from d_model / num_heads at run time. + then SCALE_FACTOR > 0.0 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant attention_constraint_0 + given input = valid_input() + then true // d_model % num_heads == 0 + + invariant attention_constraint_1 + given input = valid_input() + then true // 0 <= dropout < 1 + + invariant attention_constraint_2 + given input = valid_input() + then true // query.shape == key.shape == value.shape + diff --git a/apps/website/public/t27/specs/ml/recurrent/bilstm.t27 b/apps/website/public/t27/specs/ml/recurrent/bilstm.t27 new file mode 100644 index 0000000000..dce8421125 --- /dev/null +++ b/apps/website/public/t27/specs/ml/recurrent/bilstm.t27 @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Bidirectional LSTM output concatenation | φ² + 1/φ² = 3 | TRINITY + +module Bilstm; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_INPUT_SIZE : u32 = 256; + const DEFAULT_HIDDEN_SIZE : u32 = 256; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const BiLSTMConfig = struct { + input_size : u32, + hidden_size : u32, + use_cell_state : bool, + }; + + pub const LSTMParams = struct { + w_i : []f32, + w_f : []f32, + w_o : []f32, + w_c : []f32, + b_i : []f32, + b_f : []f32, + b_o : []f32, + b_c : []f32, + }; + + pub const LSTMCellState = struct { + h : []f32, + c : []f32, + }; + + pub const BiLSTMOutput = struct { + h : []f32, + c : []f32, + h_fwd_final : []f32, + h_bwd_final : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward_direction(inputs: [][]f32, direction: Str, params: LSTMParams) → [][]f32 + fn forward_direction(inputs: [][]f32, direction: Str, params: LSTMParams) -> [][]f32 { + // TODO: Implement from .tri spec + } + + // forward(inputs: [][]f32, config: BiLSTMConfig) → BiLSTMOutput + fn forward(inputs: [][]f32, config: BiLSTMConfig) -> BiLSTMOutput { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test default_sizes_satisfy_the_module_constraints + // Verify: constraints 0 and 1 — input_size >= 1 and hidden_size >= 1 + given input_size = DEFAULT_INPUT_SIZE + and hidden_size = DEFAULT_HIDDEN_SIZE + then input_size >= 1 and hidden_size >= 1 + + test bidirectional_output_is_twice_the_hidden_size + // Verify: constraint bilstm_output — h is the forward and backward states + // concatenated, so it is 2 * hidden_size wide (512 for the defaults) + given hidden_size = DEFAULT_HIDDEN_SIZE + when concat_width = 2 * hidden_size + then concat_width == 512 and concat_width > hidden_size + + test config_from_defaults_is_admissible + // Verify: a config built straight from the module constants is well-formed + given config = BiLSTMConfig{ .input_size = DEFAULT_INPUT_SIZE, .hidden_size = DEFAULT_HIDDEN_SIZE, .use_cell_state = true } + then config.input_size >= 1 and config.hidden_size >= 1 and config.use_cell_state + + test output_h_is_the_two_final_states_joined + // Verify: h_fwd_final and h_bwd_final are each hidden_size wide and h is + // their concatenation, so h.len is exactly the sum of the two + given h_fwd_final = [_]f32{ 0.0, 1.0 } + and h_bwd_final = [_]f32{ 2.0, 3.0 } + and h = [_]f32{ 0.0, 1.0, 2.0, 3.0 } + when output = BiLSTMOutput{ .h = @constCast(&h), .c = &.{}, .h_fwd_final = @constCast(&h_fwd_final), .h_bwd_final = @constCast(&h_bwd_final) } + then output.h.len == output.h_fwd_final.len + output.h_bwd_final.len + + test forward_half_of_output_precedes_the_backward_half + // Verify: the concatenation order is forward-then-backward, so the first + // hidden_size entries of h are the forward state + given h_fwd_final = [_]f32{ 0.0, 1.0 } + and h = [_]f32{ 0.0, 1.0, 2.0, 3.0 } + when head = h[0..2] + then std.mem.eql(f32, head, &h_fwd_final) + + test lstm_params_carry_one_weight_and_one_bias_per_gate + // Verify: the input, forget, output and cell gates each get a w_ and a b_ + // field, and the biases are hidden_size wide + given w_i = [_]f32{ 0.0, 0.0 } + and b_i = [_]f32{ 0.0, 0.0 } + when params = LSTMParams{ .w_i = @constCast(&w_i), .w_f = &.{}, .w_o = &.{}, .w_c = &.{}, .b_i = @constCast(&b_i), .b_f = &.{}, .b_o = &.{}, .b_c = &.{} } + then params.w_i.len == params.b_i.len + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant bilstm_constraint_0 + given input = valid_input() + then true // input_size >= 1 + + invariant bilstm_constraint_1 + given input = valid_input() + then true // hidden_size >= 1 + + invariant bilstm_constraint_2 + given input = valid_input() + then true // name: bilstm_output + + invariant bilstm_constraint_3 + given input = valid_input() + then true // "Schuster & Paliwal (1997) - Bidirectional Recurrent Neural Networks" + + invariant bilstm_constraint_4 + given input = valid_input() + then true // "Graves et al. (2013) - Speech Recognition with Deep Recurrent Neural Networks" + diff --git a/apps/website/public/t27/specs/ml/recurrent/gru_cell.t27 b/apps/website/public/t27/specs/ml/recurrent/gru_cell.t27 new file mode 100644 index 0000000000..85018d15c7 --- /dev/null +++ b/apps/website/public/t27/specs/ml/recurrent/gru_cell.t27 @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Convex combination of old and new | φ² + 1/φ² = 3 | TRINITY + +module Gru; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const GRUConfig = struct { + input_size : u32, + hidden_size : u32, + }; + + pub const GRUState = struct { + h : []f32, + }; + + pub const GRUWeights = struct { + Wr : []f32, + br : []f32, + Wz : []f32, + bz : []f32, + Wh : []f32, + bh : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, h_prev: []const f32, weights: GRUWeights, h_next: []f32, config: GRUConfig) → void + fn forward(input: []const f32, h_prev: []const f32, weights: GRUWeights, h_next: []f32, config: GRUConfig) -> void { + // TODO: Implement from .tri spec + } + + // init(weights: GRUWeights, config: GRUConfig) → void + fn init(weights: GRUWeights, config: GRUConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test gate_matrices_match_the_configured_shape + // Verify: constraints 2-4 -- every gate matrix is + // (input_size + hidden_size) * hidden_size, and the three gates agree + given cfg = GRUConfig{ .input_size = 2, .hidden_size = 3 } + and Wr = [_]f32{0.0} ** 15 + and Wz = [_]f32{0.0} ** 15 + and Wh = [_]f32{0.0} ** 15 + and br = [_]f32{0.0} ** 3 + and bz = [_]f32{0.0} ** 3 + and bh = [_]f32{0.0} ** 3 + and w = GRUWeights{ .Wr = @constCast(&Wr), .br = @constCast(&br), .Wz = @constCast(&Wz), .bz = @constCast(&bz), .Wh = @constCast(&Wh), .bh = @constCast(&bh) } + then w.Wr.len == (cfg.input_size + cfg.hidden_size) * cfg.hidden_size and w.Wz.len == w.Wr.len and w.Wh.len == w.Wr.len + + test gate_biases_match_the_hidden_size + // Verify: constraints 5-7 -- each gate bias holds one entry per hidden unit + given cfg = GRUConfig{ .input_size = 2, .hidden_size = 3 } + and Wr = [_]f32{0.0} ** 15 + and Wz = [_]f32{0.0} ** 15 + and Wh = [_]f32{0.0} ** 15 + and br = [_]f32{0.0} ** 3 + and bz = [_]f32{0.0} ** 3 + and bh = [_]f32{0.0} ** 3 + and w = GRUWeights{ .Wr = @constCast(&Wr), .br = @constCast(&br), .Wz = @constCast(&Wz), .bz = @constCast(&bz), .Wh = @constCast(&Wh), .bh = @constCast(&bh) } + then w.br.len == cfg.hidden_size and w.bz.len == cfg.hidden_size and w.bh.len == cfg.hidden_size + + test state_carries_one_activation_per_hidden_unit + // Verify: constraints 0-1 -- both sizes are positive, and the hidden + // state h is sized by hidden_size, not by input_size + given cfg = GRUConfig{ .input_size = 2, .hidden_size = 3 } + and h = [_]f32{0.0} ** 3 + and state = GRUState{ .h = @constCast(&h) } + then cfg.input_size > 0 and cfg.hidden_size > 0 and state.h.len == cfg.hidden_size + + test gru_has_three_gate_matrices_where_lstm_has_four + // Verify: constraint_8 -- the parameter count behind "GRU trains faster + // than LSTM"; reset, update and candidate, no output gate + given cfg = GRUConfig{ .input_size = 2, .hidden_size = 3 } + and per_gate = (cfg.input_size + cfg.hidden_size) * cfg.hidden_size + cfg.hidden_size + when total = 3 * per_gate + then total == 54 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant gru_constraint_0 + given input = valid_input() + then true // input_size > 0 + + invariant gru_constraint_1 + given input = valid_input() + then true // hidden_size > 0 + + invariant gru_constraint_2 + given input = valid_input() + then true // Wr.size() == (input_size + hidden_size) * hidden_size + + invariant gru_constraint_3 + given input = valid_input() + then true // Wz.size() == (input_size + hidden_size) * hidden_size + + invariant gru_constraint_4 + given input = valid_input() + then true // Wh.size() == (input_size + hidden_size) * hidden_size + + invariant gru_constraint_5 + given input = valid_input() + then true // br.size() == hidden_size + + invariant gru_constraint_6 + given input = valid_input() + then true // bz.size() == hidden_size + + invariant gru_constraint_7 + given input = valid_input() + then true // bh.size() == hidden_size + + invariant gru_constraint_8 + given input = valid_input() + then true // "GRU trains faster than LSTM (fewer parameters)" + + invariant gru_constraint_9 + given input = valid_input() + then true // "GRU performance similar to LSTM on most tasks" + + invariant gru_constraint_10 + given input = valid_input() + then true // "Reset gate enables adaptive temporal resolution" + diff --git a/apps/website/public/t27/specs/ml/recurrent/lstm_cell.t27 b/apps/website/public/t27/specs/ml/recurrent/lstm_cell.t27 new file mode 100644 index 0000000000..c0c51cb3e5 --- /dev/null +++ b/apps/website/public/t27/specs/ml/recurrent/lstm_cell.t27 @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Short-term memory exposed to next layer | φ² + 1/φ² = 3 | TRINITY + +module Lstm; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const LSTM_DEFAULT_HIDDEN : u32 = 128; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const LSTMConfig = struct { + input_size : u32, + hidden_size : u32, + }; + + pub const LSTMState = struct { + h : []f32, + c : []f32, + }; + + pub const LSTMWeights = struct { + Wf : []f32, + bf : []f32, + Wi : []f32, + bi : []f32, + Wo : []f32, + bo : []f32, + Wg : []f32, + bg : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, state_prev: LSTMState, weights: LSTMWeights, state_next: LSTMState, config: LSTMConfig) → void + fn forward(input: []const f32, state_prev: LSTMState, weights: LSTMWeights, state_next: LSTMState, config: LSTMConfig) -> void { + // TODO: Implement from .tri spec + } + + // init(weights: LSTMWeights, config: LSTMConfig) → void + fn init(weights: LSTMWeights, config: LSTMConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward and init are unimplemented stubs returning void, so no behavioural + // assertion is possible yet. These tests execute the size constraints that + // the invariants below only state in comments. + + test default_hidden_size_is_positive + // Verify: constraint_1 (hidden_size > 0) holds for the documented default + given h = LSTM_DEFAULT_HIDDEN + then h == 128 and h > 0 + + test gate_block_size_is_input_plus_hidden_times_hidden + // Verify: constraints 2-5 -- each gate weight block covers the + // concatenated [input, hidden] vector once per hidden unit + given cfg = LSTMConfig{.input_size=2,.hidden_size=3} + when block = (cfg.input_size + cfg.hidden_size) * cfg.hidden_size + then block == 15 and cfg.input_size > 0 and cfg.hidden_size > 0 + + test forget_gate_weights_and_bias_match_the_constrained_sizes + // Verify: constraint_2 (Wf) and constraint_6 (bf) for input_size = 2, + // hidden_size = 3 -- a 15-element weight block and a 3-element bias + given w = LSTMWeights{.Wf=@constCast(&([_]f32{0.0} ** 15)),.bf=@constCast(&([_]f32{1.0} ** 3)),.Wi=@constCast(&([_]f32{0.0} ** 15)),.bi=@constCast(&([_]f32{0.0} ** 3)),.Wo=@constCast(&([_]f32{0.0} ** 15)),.bo=@constCast(&([_]f32{0.0} ** 3)),.Wg=@constCast(&([_]f32{0.0} ** 15)),.bg=@constCast(&([_]f32{0.0} ** 3))} + then w.Wf.len == 15 and w.bf.len == 3 and w.Wi.len == w.Wf.len and w.bo.len == w.bf.len + + test cell_and_hidden_state_have_the_same_width + // Verify: LSTMState carries h and c side by side -- the cell state + // highway is exactly as wide as the exposed hidden state + given st = LSTMState{.h=@constCast(&([_]f32{0.0} ** 3)),.c=@constCast(&([_]f32{0.0} ** 3))} + then st.h.len == st.c.len and st.h.len == 3 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant lstm_constraint_0 + given input = valid_input() + then true // input_size > 0 + + invariant lstm_constraint_1 + given input = valid_input() + then true // hidden_size > 0 + + invariant lstm_constraint_2 + given input = valid_input() + then true // Wf.size() == (input_size + hidden_size) * hidden_size + + invariant lstm_constraint_3 + given input = valid_input() + then true // Wi.size() == (input_size + hidden_size) * hidden_size + + invariant lstm_constraint_4 + given input = valid_input() + then true // Wo.size() == (input_size + hidden_size) * hidden_size + + invariant lstm_constraint_5 + given input = valid_input() + then true // Wg.size() == (input_size + hidden_size) * hidden_size + + invariant lstm_constraint_6 + given input = valid_input() + then true // bf.size() == hidden_size + + invariant lstm_constraint_7 + given input = valid_input() + then true // bi.size() == hidden_size + + invariant lstm_constraint_8 + given input = valid_input() + then true // bo.size() == hidden_size + + invariant lstm_constraint_9 + given input = valid_input() + then true // bg.size() == hidden_size + + invariant lstm_constraint_10 + given input = valid_input() + then true // "LSTM should learn long-term dependencies (>100 time steps)" + + invariant lstm_constraint_11 + given input = valid_input() + then true // "Forget gate bias initialization to 1.0 improves convergence" + + invariant lstm_constraint_12 + given input = valid_input() + then true // "Cell state acts as linear information highway" + diff --git a/apps/website/public/t27/specs/ml/recurrent/lstm_single.t27 b/apps/website/public/t27/specs/ml/recurrent/lstm_single.t27 new file mode 100644 index 0000000000..27c2348c24 --- /dev/null +++ b/apps/website/public/t27/specs/ml/recurrent/lstm_single.t27 @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// LSTM solves vanishing gradient problem | φ² + 1/φ² = 3 | TRINITY + +module LstmCell; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const FORGET_BIAS_INIT : f32 = 1.0; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const LSTMConfig = struct { + input_size : u32, + hidden_size : u32, + }; + + pub const LSTMState = struct { + h : []f32, + c : []f32, + }; + + pub const LSTMWeights = struct { + W_ii : []f32, + W_hi : []f32, + b_i : []f32, + W_if : []f32, + W_hf : []f32, + b_f : []f32, + W_ig : []f32, + W_hg : []f32, + b_g : []f32, + W_io : []f32, + W_ho : []f32, + b_o : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, state: LSTMState, weights: LSTMWeights, output_state: LSTMState, config: LSTMConfig) → void + fn forward(input: []const f32, state: LSTMState, weights: LSTMWeights, output_state: LSTMState, config: LSTMConfig) -> void { + // TODO: Implement from .tri spec + } + + // backward(grad_output: []const f32, grad_cell: []const f32, input: []const f32, state: LSTMState, weights: LSTMWeights, grad_input: []f32, grad_state: LSTMState, grad_weights: LSTMWeights) → void + fn backward(grad_output: []const f32, grad_cell: []const f32, input: []const f32, state: LSTMState, weights: LSTMWeights, grad_input: []f32, grad_state: LSTMState, grad_weights: LSTMWeights) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Both functions above are still `TODO: Implement`, so each compiles to + // `@panic("not yet implemented")`: calling one aborts the test binary. + // What this module does state is FORGET_BIAS_INIT, the state pair and the + // gate weight set. The constraints listed below are checked here against + // a concrete configuration rather than being left as prose. + + // A positive forget bias starts the cell near "remember everything", + // which is the point the header makes about vanishing gradients. + test forget_bias_starts_positive + given b = FORGET_BIAS_INIT + then b > 0.0 + and @abs(b - 1.0) < 1e-6 + + // h and c are both hidden_size long; the input is input_size long and is + // sized independently of them. + test hidden_and_cell_state_are_both_hidden_size_long + given cfg = LSTMConfig{ .input_size = 2, .hidden_size = 3 } + and st = LSTMState{ .h = @constCast(&[_]f32{ 0.0, 0.0, 0.0 }), .c = @constCast(&[_]f32{ 0.0, 0.0, 0.0 }) } + then st.h.len == cfg.hidden_size + and st.c.len == cfg.hidden_size + and st.h.len == st.c.len + + test input_size_is_sized_independently_of_hidden_size + given cfg = LSTMConfig{ .input_size = 2, .hidden_size = 3 } + and x = @constCast(&[_]f32{ 0.0, 0.0 }) + then x.len == cfg.input_size + and cfg.input_size != cfg.hidden_size + + test state_and_weights_are_stored_as_f32 + given h = @FieldType(LSTMState, "h") + then h == []f32 + and @FieldType(LSTMState, "c") == []f32 + and @FieldType(LSTMWeights, "W_ii") == []f32 + and @FieldType(LSTMWeights, "b_f") == []f32 + + // Four gates -- input, forget, cell candidate, output -- each with an + // input matrix, a recurrent matrix and a bias: twelve tensors in all. + test weights_hold_three_tensors_for_each_of_the_four_gates + given fields = @typeInfo(LSTMWeights).@"struct".fields + then fields.len == 12 + + // forward is given the input with no state and no weights, and backward + // an output gradient with nothing to propagate it through. These record + // the signatures as they stand. + test forward_and_backward_each_take_one_f32_slice_and_return_nothing + given f = forward + then @TypeOf(f) == fn ([]const f32) void + and @TypeOf(backward) == fn ([]const f32) void + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant lstm_cell_constraint_0 + given input = valid_input() + then true // input.size() == config.input_size + + invariant lstm_cell_constraint_1 + given input = valid_input() + then true // state.h.size() == config.hidden_size + + invariant lstm_cell_constraint_2 + given input = valid_input() + then true // state.c.size() == config.hidden_size + diff --git a/apps/website/public/t27/specs/ml/recurrent/rnn_cell.t27 b/apps/website/public/t27/specs/ml/recurrent/rnn_cell.t27 new file mode 100644 index 0000000000..2330d1d06a --- /dev/null +++ b/apps/website/public/t27/specs/ml/recurrent/rnn_cell.t27 @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Basic RNN recurrence equation | φ² + 1/φ² = 3 | TRINITY + +module RnnCell; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_INPUT_SIZE : u32 = 128; + const DEFAULT_HIDDEN_SIZE : u32 = 128; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const RNNCellConfig = struct { + input_size : u32, + hidden_size : u32, + }; + + pub const RNNState = struct { + hidden : []f32, + cell : []f32, + }; + + pub const RNNParams = struct { + w_x : []f32, + w_h : []f32, + b : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward_step(input: []f32, state: RNNState, params: RNNParams) → RNNState + fn forward_step(input: []f32, state: RNNState, params: RNNParams) -> RNNState { + // TODO: Implement from .tri spec + } + + // forward_sequence(inputs: [][]f32, initial_state: []f32, params: RNNParams) → [][]f32 + fn forward_sequence(inputs: [][]f32, initial_state: []f32, params: RNNParams) -> [][]f32 { + // TODO: Implement from .tri spec + } + + // init_state(hidden_size: u32) → []f32 + fn init_state(hidden_size: u32) -> []f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward_step/forward_sequence/init_state are unimplemented stubs -- each + // emits `@panic("not yet implemented")`, so the recurrence itself cannot be + // exercised. The declared constants and parameter shape are what remain + // testable. + + test defaults_satisfy_the_stated_size_constraints + // rnn_cell_constraint_0 and _1 require input_size >= 1 and + // hidden_size >= 1; the defaults must not violate them. + then DEFAULT_INPUT_SIZE >= 1 + and DEFAULT_HIDDEN_SIZE >= 1 + and DEFAULT_INPUT_SIZE == 128 + and DEFAULT_HIDDEN_SIZE == 128 + + test params_are_the_three_terms_of_the_elman_recurrence + // h_t = tanh(W_x x_t + W_h h_{t-1} + b), from Elman (1990): one input + // matrix, one recurrent matrix, one bias -- and nothing else. + then @typeInfo(RNNParams).@"struct".fields.len == 3 + and @hasField(RNNParams, "w_x") and @hasField(RNNParams, "w_h") + and @hasField(RNNParams, "b") + + test weights_are_flat_f32_slices + // Shape is carried by RNNCellConfig, not by the parameter type, so + // every matrix is a 1-D buffer the caller must index itself. + then @FieldType(RNNParams, "w_x") == []f32 + and @FieldType(RNNParams, "w_h") == []f32 + and @FieldType(RNNParams, "b") == []f32 + + test state_carries_a_cell_vector_a_plain_rnn_does_not_use + // An Elman cell has only a hidden vector; `cell` is LSTM state. It is + // declared here, so RNNState is really an LSTM-shaped state struct. + then @hasField(RNNState, "hidden") and @hasField(RNNState, "cell") + and @FieldType(RNNState, "cell") == []f32 + + test config_declares_sizes_as_unsigned + then @FieldType(RNNCellConfig, "input_size") == u32 + and @FieldType(RNNCellConfig, "hidden_size") == u32 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant rnn_cell_constraint_0 + given input = valid_input() + then true // input_size >= 1 + + invariant rnn_cell_constraint_1 + given input = valid_input() + then true // hidden_size >= 1 + + invariant rnn_cell_constraint_2 + given input = valid_input() + then true // name: rnn_formula + + invariant rnn_cell_constraint_3 + given input = valid_input() + then true // "Elman (1990) - Finding Structure in Time" + + invariant rnn_cell_constraint_4 + given input = valid_input() + then true // "Goodfellow et al. (2016) - Sequence Modeling with RNNs and LSTMs" + diff --git a/apps/website/public/t27/specs/ml/recurrent/self_attention.t27 b/apps/website/public/t27/specs/ml/recurrent/self_attention.t27 new file mode 100644 index 0000000000..09943dcb34 --- /dev/null +++ b/apps/website/public/t27/specs/ml/recurrent/self_attention.t27 @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Multiple attention heads computed independently | φ² + 1/φ² = 3 | TRINITY + +module SelfAttention; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const D_MODEL : u32 = 64; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SelfAttentionConfig = struct { + hidden_size : u32, + num_heads : u32, + d_model : u32, + }; + + pub const SelfAttentionWeights = struct { + Wq : []f32, + Wk : []f32, + Wv : []f32, + Wo : []f32, + }; + + pub const SelfAttentionState = struct { + q : []f32, + k : []f32, + v : []f32, + attn : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, weights: SelfAttentionWeights, state: SelfAttentionState, config: SelfAttentionConfig) → void + fn forward(input: []const f32, weights: SelfAttentionWeights, state: SelfAttentionState, config: SelfAttentionConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test forward_basic_case + given input = default_input() + when result = forward(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/ml/recurrent/seq2seq.t27 b/apps/website/public/t27/specs/ml/recurrent/seq2seq.t27 new file mode 100644 index 0000000000..7a663567ff --- /dev/null +++ b/apps/website/public/t27/specs/ml/recurrent/seq2seq.t27 @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Training technique using ground truth tokens | φ² + 1/φ² = 3 | TRINITY + +module Seq2seq; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Seq2SeqConfig = struct { + vocab_size : u32, + d_model : u32, + hidden_size : u32, + }; + + pub const Seq2SeqState = struct { + encoder_hidden : []f32, + encoder_context : []f32, + decoder_hidden : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // encode(input_seq: []const u32, state: Seq2SeqState, config: Seq2SeqConfig) → void + fn encode(input_seq: []const u32, state: Seq2SeqState, config: Seq2SeqConfig) -> void { + // TODO: Implement from .tri spec + } + + // decode(target_seq: []const u32, state: Seq2SeqState, output: []f32, config: Seq2SeqConfig) → void + fn decode(target_seq: []const u32, state: Seq2SeqState, output: []f32, config: Seq2SeqConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // encode and decode are both unimplemented stubs: each one emits + // `@panic("not yet implemented")`, so calling either aborts the test + // binary rather than failing a test. (Their signatures are also truncated + // to the first parameter, so decode has nowhere to receive the encoder + // context it is supposed to condition on.) The invariants below never + // reach the generated code either -- they emit an empty comptime block, + // which is what the first test here is about. + + test the_positivity_invariants_are_not_enforced_by_the_types + // The spec constrains vocab_size, d_model and hidden_size to be + // positive, but all three are plain u32 and the invariant blocks emit + // nothing, so an all-zero config is perfectly constructible. Whatever + // checks these has to be code, not the type. + given degenerate = Seq2SeqConfig{ .vocab_size = 0, .d_model = 0, .hidden_size = 0 } + then degenerate.vocab_size == 0 + and degenerate.hidden_size == 0 + and @typeInfo(@FieldType(Seq2SeqConfig, "vocab_size")).int.signedness == .unsigned + and @typeInfo(Seq2SeqConfig).@"struct".fields.len == 3 + + test the_context_vector_is_a_hidden_state_of_the_same_shape + // All three buffers are []f32: the context handed from encoder to + // decoder is an encoder hidden state, not a separate representation. + then @typeInfo(Seq2SeqState).@"struct".fields.len == 3 + and @FieldType(Seq2SeqState, "encoder_context") == @FieldType(Seq2SeqState, "encoder_hidden") + and @FieldType(Seq2SeqState, "decoder_hidden") == @FieldType(Seq2SeqState, "encoder_hidden") + and @FieldType(Seq2SeqState, "encoder_hidden") == []f32 + + test hidden_size_is_what_sizes_all_three_buffers + // A hidden_size of 4 means three length-4 runs. Built by hand, since + // encode is a stub -- the point is that one config field fixes every + // buffer length, so no per-buffer size is stored on the state. + given cfg = Seq2SeqConfig{ .vocab_size = 32, .d_model = 8, .hidden_size = 4 } + given buf = std.mem.zeroes([4]f32) + given state = Seq2SeqState{ .encoder_hidden = @constCast(&buf), .encoder_context = @constCast(&buf), .decoder_hidden = @constCast(&buf) } + then state.encoder_hidden.len == cfg.hidden_size + and state.encoder_context.len == cfg.hidden_size + and state.decoder_hidden.len == cfg.hidden_size + and @hasField(Seq2SeqState, "hidden_size") == false + + test both_sequences_are_token_ids_of_the_vocabulary_width + // encode takes source ids and decode takes target ids, both []const + // u32 -- the same width as vocab_size, so any id the config admits is + // representable. decode reading ground-truth targets is teacher + // forcing, which is what the module header describes. + given encode_arg = @typeInfo(@TypeOf(encode)).@"fn".params[0].type + given decode_arg = @typeInfo(@TypeOf(decode)).@"fn".params[0].type + then encode_arg == []const u32 + and decode_arg == []const u32 + and @FieldType(Seq2SeqConfig, "vocab_size") == u32 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant seq2seq_constraint_0 + given input = valid_input() + then true // vocab_size > 0 + + invariant seq2seq_constraint_1 + given input = valid_input() + then true // d_model > 0 + + invariant seq2seq_constraint_2 + given input = valid_input() + then true // hidden_size > 0 + + invariant seq2seq_constraint_3 + given input = valid_input() + then true // "Seq2Seq learns alignment between input and output" + + invariant seq2seq_constraint_4 + given input = valid_input() + then true // "Teacher forcing speeds up training" + + invariant seq2seq_constraint_5 + given input = valid_input() + then true // "Beam search improves generation quality" + diff --git a/apps/website/public/t27/specs/ml/rl/advantage_estimator.t27 b/apps/website/public/t27/specs/ml/rl/advantage_estimator.t27 new file mode 100644 index 0000000000..cab956d39d --- /dev/null +++ b/apps/website/public/t27/specs/ml/rl/advantage_estimator.t27 @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Efficient backward recurrence | φ² + 1/φ² = 3 | TRINITY + +module Advantage; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_GAMMA : f32 = 0.99; + const DEFAULT_LAMBDA : f32 = 0.95; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const GAEConfig = struct { + gamma : f32, + lambda_ : f32, + normalize : bool, + }; + + pub const TrajectoryBatch = struct { + states : [][]f32, + actions : [][]f32, + rewards : []f32, + values : []f32, + dones : []bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // compute_td_residual(reward: f32, value: f32, next_value: f32, gamma: f32, done: bool) → f32 + fn compute_td_residual(reward: f32, value: f32, next_value: f32, gamma: f32, done: bool) -> f32 { + // TODO: Implement from .tri spec + } + + // compute_gae(rewards: []f32, values: []f32, dones: []bool, gamma: f32, lambda_: f32) → []f32 + fn compute_gae(rewards: []f32, values: []f32, dones: []bool, gamma: f32, lambda_: f32) -> []f32 { + // TODO: Implement from .tri spec + } + + // compute_returns(rewards: []f32, dones: []bool, last_value: f32, gamma: f32) → []f32 + fn compute_returns(rewards: []f32, dones: []bool, last_value: f32, gamma: f32) -> []f32 { + // TODO: Implement from .tri spec + } + + // normalize_advantages(advantages: []f32) → []f32 + fn normalize_advantages(advantages: []f32) -> []f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test default_gamma_within_unit_interval + // Verify: the shipped discount satisfies constraint 0 of this module + given gamma = DEFAULT_GAMMA + then gamma >= 0.0 and gamma <= 1.0 + + test default_lambda_within_unit_interval + // Verify: the shipped trace decay satisfies constraint 1 of this module + given lambda_ = DEFAULT_LAMBDA + then lambda_ >= 0.0 and lambda_ <= 1.0 + + test default_lambda_below_default_gamma + // Verify: lambda < gamma, so the GAE weight gamma*lambda decays faster + // than the discount alone — the bias/variance trade-off of Schulman et al. + given gamma = DEFAULT_GAMMA + and lambda_ = DEFAULT_LAMBDA + then lambda_ < gamma + + test gae_weight_decays_geometrically + // Verify: successive GAE weights (gamma*lambda)^k shrink; the k=1 weight + // is 0.9405 for the shipped defaults + given w1 = DEFAULT_GAMMA * DEFAULT_LAMBDA + and err = @abs(w1 - 0.9405) + then err < 1.0e-6 and w1 < 1.0 + + test gae_config_from_defaults_meets_both_constraints + // Verify: a config built straight from the module constants is admissible + given config = GAEConfig{ .gamma = DEFAULT_GAMMA, .lambda_ = DEFAULT_LAMBDA, .normalize = true } + then config.gamma >= 0.0 and config.gamma <= 1.0 and config.lambda_ >= 0.0 and config.lambda_ <= 1.0 + + test trajectory_batch_arrays_share_one_horizon + // Verify: rewards, values and dones are all indexed by the same timestep t, + // so a well-formed batch has them at equal length + given rewards = [_]f32{ 1.0, 0.0, -1.0 } + and values = [_]f32{ 0.5, 0.25, 0.0 } + and dones = [_]bool{ false, false, true } + when batch = TrajectoryBatch{ .states = &.{}, .actions = &.{}, .rewards = @constCast(&rewards), .values = @constCast(&values), .dones = @constCast(&dones) } + then batch.rewards.len == batch.values.len and batch.values.len == batch.dones.len and batch.dones[2] + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant advantage_constraint_0 + given input = valid_input() + then true // 0 <= gamma <= 1 + + invariant advantage_constraint_1 + given input = valid_input() + then true // 0 <= lambda_ <= 1 + + invariant advantage_constraint_2 + given input = valid_input() + then true // name: gae_formula + + invariant advantage_constraint_3 + given input = valid_input() + then true // name: td_residual + + invariant advantage_constraint_4 + given input = valid_input() + then true // name: gae_recursive + + invariant advantage_constraint_5 + given input = valid_input() + then true // "Schulman et al. (2016) - High-Dimensional Continuous Control Using Generalized Advantage Estimation, arXiv:1506.02438" + diff --git a/apps/website/public/t27/specs/ml/rl/dqn.t27 b/apps/website/public/t27/specs/ml/rl/dqn.t27 new file mode 100644 index 0000000000..1b4cdd37a6 --- /dev/null +++ b/apps/website/public/t27/specs/ml/rl/dqn.t27 @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Balance exploration and exploitation | φ² + 1/φ² = 3 | TRINITY + +module Dqn; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_GAMMA : f32 = 0.99; + const DEFAULT_EPSILON_START : f32 = 1.0; + const DEFAULT_EPSILON_END : f32 = 0.01; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const DQNConfig = struct { + state_dim : u32, + action_dim : u32, + hidden_dims : []u32, + learning_rate : f32, + gamma : f32, + epsilon_start : f32, + epsilon_end : f32, + epsilon_decay : f32, + batch_size : u32, + target_update_freq : u32, + }; + + pub const Transition = struct { + state : []f32, + action : u32, + reward : f32, + next_state : []f32, + done : bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // select_action(state: []const f32, q_network: []const f32, epsilon: f32, config: DQNConfig) → u32 + fn select_action(state: []const f32, q_network: []const f32, epsilon: f32, config: DQNConfig) -> u32 { + // TODO: Implement from .tri spec + } + + // train_step(batch: []Transition, q_network: []f32, target_network: []f32, config: DQNConfig) → f32 + fn train_step(batch: []Transition, q_network: []f32, target_network: []f32, config: DQNConfig) -> f32 { + // TODO: Implement from .tri spec + } + + // update_target(q_network: []f32, target_network: []f32) → void + fn update_target(q_network: []f32, target_network: []f32) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test default_gamma_within_unit_interval + // Verify: the shipped discount satisfies constraint 0 of this module + given gamma = DEFAULT_GAMMA + then gamma >= 0.0 and gamma <= 1.0 + + test epsilon_schedule_runs_downhill + // Verify: constraint 1 — 0 <= epsilon_end <= epsilon_start <= 1, so the + // schedule can only move from exploration towards exploitation + given lo = DEFAULT_EPSILON_END + and hi = DEFAULT_EPSILON_START + then lo >= 0.0 and lo <= hi and hi <= 1.0 + + test epsilon_opens_at_pure_exploration + // Verify: epsilon_start is exactly 1, so step 0 picks uniformly at random + given hi = DEFAULT_EPSILON_START + and err = @abs(hi - 1.0) + then err < 1.0e-6 + + test epsilon_never_reaches_zero + // Verify: epsilon_end stays above 0, so a trained agent keeps a residual + // exploration rate rather than becoming fully greedy + given lo = DEFAULT_EPSILON_END + then lo > 0.0 + + test transition_states_have_matching_widths + // Verify: state and next_state are the same observation space, so a + // well-formed transition carries them at equal length + given state = [_]f32{ 0.0, 1.0, 0.0 } + and next_state = [_]f32{ 1.0, 0.0, 0.0 } + when step = Transition{ .state = @constCast(&state), .action = 1, .reward = -1.0, .next_state = @constCast(&next_state), .done = true } + then step.state.len == step.next_state.len and step.action == 1 and step.done + + test config_from_defaults_meets_every_constraint + // Verify: a config built straight from the module constants is admissible + // under constraints 0, 1 and 2 + given config = DQNConfig{ .state_dim = 4, .action_dim = 2, .hidden_dims = &.{}, .learning_rate = 0.001, .gamma = DEFAULT_GAMMA, .epsilon_start = DEFAULT_EPSILON_START, .epsilon_end = DEFAULT_EPSILON_END, .epsilon_decay = 0.995, .batch_size = 32, .target_update_freq = 100 } + then config.gamma >= 0.0 and config.gamma <= 1.0 and config.epsilon_end <= config.epsilon_start and config.epsilon_decay > 0.0 and config.epsilon_decay <= 1.0 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant dqn_constraint_0 + given input = valid_input() + then true // 0 <= gamma <= 1 + + invariant dqn_constraint_1 + given input = valid_input() + then true // 0 <= epsilon_end <= epsilon_start <= 1 + + invariant dqn_constraint_2 + given input = valid_input() + then true // 0 < epsilon_decay <= 1 + diff --git a/apps/website/public/t27/specs/ml/rl/dqn_target_network.t27 b/apps/website/public/t27/specs/ml/rl/dqn_target_network.t27 new file mode 100644 index 0000000000..2441e46aaf --- /dev/null +++ b/apps/website/public/t27/specs/ml/rl/dqn_target_network.t27 @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Polyak averaging formula | φ² + 1/φ² = 3 | TRINITY + +module DqnTarget; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_TAU : f32 = 0.005; + const DEFAULT_UPDATE_FREQ : u32 = 1000; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const UpdateMethod = struct { + enum : [HARD, SOFT], + }; + + pub const TargetUpdateConfig = struct { + method : UpdateMethod, + tau : f32, + update_freq : u32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // hard_update(source: []f32, target: []f32) → void + fn hard_update(source: []f32, target: []f32) -> void { + // TODO: Implement from .tri spec + } + + // soft_update(source: []f32, target: []f32, tau: f32) → void + fn soft_update(source: []f32, target: []f32, tau: f32) -> void { + // TODO: Implement from .tri spec + } + + // should_update(step: u32, config: TargetUpdateConfig) → bool + fn should_update(step: u32, config: TargetUpdateConfig) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test default_tau_is_a_valid_interpolation_weight + // Verify: constraint_0 -- 0 <= tau <= 1 -- holds for the shipped default + given tau = DEFAULT_TAU + then tau >= 0.0 and tau <= 1.0 + + test default_tau_sits_in_the_stability_band + // Verify: constraint_1 -- tau << 1, the 0.001-0.01 band named in the spec + given tau = DEFAULT_TAU + then tau >= 0.001 and tau <= 0.01 + + test default_update_freq_is_at_least_one_step + // Verify: constraint_2 -- update_freq >= 1, so should_update can fire + given freq = DEFAULT_UPDATE_FREQ + then freq >= 1 + + test soft_update_never_leaves_the_target_source_interval + // Verify: constraint_3 -- target' = tau*source + (1 - tau)*target is a + // convex combination at the declared tau, so it stays inside [0, 1] + // and moves the target toward the source by exactly tau + given target = 0.0 + and source = 1.0 + when moved = DEFAULT_TAU * source + (1.0 - DEFAULT_TAU) * target + and drift = @abs(moved - DEFAULT_TAU) + then moved >= target and moved <= source and drift < 1.0e-6 + + test config_defaults_satisfy_every_declared_constraint + // Verify: a config built from the two module defaults meets constraint_0, + // constraint_1 and constraint_2 at once + given cfg = TargetUpdateConfig{ .method = UpdateMethod.HARD, .tau = DEFAULT_TAU, .update_freq = DEFAULT_UPDATE_FREQ } + then cfg.tau > 0.0 and cfg.tau < 1.0 and cfg.update_freq >= 1 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant dqn_target_constraint_0 + given input = valid_input() + then true // 0 <= tau <= 1 + + invariant dqn_target_constraint_1 + given input = valid_input() + then true // tau << 1 for stability (typically 0.001-0.01) + + invariant dqn_target_constraint_2 + given input = valid_input() + then true // update_freq >= 1 + + invariant dqn_target_constraint_3 + given input = valid_input() + then true // name: soft_update_equation + + invariant dqn_target_constraint_4 + given input = valid_input() + then true // "Mnih et al. (2015) - Human-level control through deep RL" + + invariant dqn_target_constraint_5 + given input = valid_input() + then true // "Lillicrap et al. (2016) - Continuous control with DDPG (soft update)" + diff --git a/apps/website/public/t27/specs/ml/rl/ppo_actor.t27 b/apps/website/public/t27/specs/ml/rl/ppo_actor.t27 new file mode 100644 index 0000000000..b9f311375b --- /dev/null +++ b/apps/website/public/t27/specs/ml/rl/ppo_actor.t27 @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Reparameterization for gradient estimation | φ² + 1/φ² = 3 | TRINITY + +module PpoActor; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_INIT_LOG_STD : f32 = 0.0; + const MIN_LOG_STD : f32 = -20.0; + const MAX_LOG_STD : f32 = 2.0; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const ActorConfig = struct { + state_dim : u32, + action_dim : u32, + hidden_dims : []u32, + action_space : ActionSpace, + init_log_std : f32, + }; + + pub const ActionSpace = struct { + enum : [DISCRETE, CONTINUOUS], + }; + + pub const PolicyOutput = struct { + logits : []f32, + log_std : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward_discrete(state: []const f32, params: []const f32, config: ActorConfig) → []f32 + fn forward_discrete(state: []const f32, params: []const f32, config: ActorConfig) -> []f32 { + // TODO: Implement from .tri spec + } + + // forward_continuous(state: []const f32, params: []const f32, config: ActorConfig) → PolicyOutput + fn forward_continuous(state: []const f32, params: []const f32, config: ActorConfig) -> PolicyOutput { + // TODO: Implement from .tri spec + } + + // sample_action(policy_output: PolicyOutput, action_space: ActionSpace, deterministic: bool) → []f32 + fn sample_action(policy_output: PolicyOutput, action_space: ActionSpace, deterministic: bool) -> []f32 { + // TODO: Implement from .tri spec + } + + // log_prob(action: []f32, policy_output: PolicyOutput, action_space: ActionSpace) → f32 + fn log_prob(action: []f32, policy_output: PolicyOutput, action_space: ActionSpace) -> f32 { + // TODO: Implement from .tri spec + } + + // entropy(policy_output: PolicyOutput, action_space: ActionSpace) → f32 + fn entropy(policy_output: PolicyOutput, action_space: ActionSpace) -> f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward_discrete/forward_continuous/sample_action/log_prob/entropy are + // unimplemented stubs returning void, so no policy can be evaluated or + // sampled yet. What the module does declare -- the log-std clamp range, the + // initialisation inside it, and the config/output shapes -- is tested here, + // including the constraint that the invariants below only state in a + // comment. + + test log_std_clamp_range_brackets_the_initial_value + // Verify: ppo_actor_constraint_0 (MIN_LOG_STD <= log_std <= + // MAX_LOG_STD) holds for the value the actor starts at + then (MIN_LOG_STD <= DEFAULT_INIT_LOG_STD) and (DEFAULT_INIT_LOG_STD <= MAX_LOG_STD) + + test clamp_range_is_ordered + // Verify: the floor really is below the ceiling, so the clamp is + // satisfiable + then MIN_LOG_STD < MAX_LOG_STD + + test initial_policy_has_unit_standard_deviation + // Verify: log_std is a logarithm, so initialising it at 0 means the + // Gaussian starts at sigma = 1 -- neither collapsed nor diffuse + then @abs(@exp(DEFAULT_INIT_LOG_STD) - 1.0) < 1e-6 + + test clamp_floor_is_effectively_deterministic + // Verify: exp(-20) is ~2e-9, so the lower bound exists to stop sigma + // underflowing to zero rather than to be a usable policy width + then @exp(MIN_LOG_STD) < 1e-8 + + test clamp_ceiling_leaves_room_to_explore + // Verify: exp(2) is ~7.39, several times the unit initialisation + then @exp(MAX_LOG_STD) > 7.0 + + test the_floor_is_much_further_from_init_than_the_ceiling + // Verify: the range is deliberately asymmetric about the + // initialisation -- guarding against variance collapse needs far more + // headroom than capping exploration does + given down = DEFAULT_INIT_LOG_STD - MIN_LOG_STD + and up = MAX_LOG_STD - DEFAULT_INIT_LOG_STD + then down > up * 5.0 + + test config_carries_one_log_std_per_action_dimension + // Verify: ActorConfig and PolicyOutput agree on the action dimension, + // which is what makes the diagonal Gaussian well formed + given cfg = ActorConfig{.state_dim=4,.action_dim=2,.hidden_dims=@constCast(&[_]u32{64,64}),.action_space=ActionSpace.DISCRETE,.init_log_std=DEFAULT_INIT_LOG_STD} + and out = PolicyOutput{.logits=@constCast(&[_]f32{0.0,0.0}),.log_std=@constCast(&[_]f32{DEFAULT_INIT_LOG_STD,DEFAULT_INIT_LOG_STD})} + then (out.log_std.len == cfg.action_dim) and (out.logits.len == cfg.action_dim) + + test hidden_dims_is_non_empty_for_a_usable_config + // Verify: ppo_actor_constraint_2 (len(hidden_dims) >= 1) and + // constraint_1 (action_dim >= 1) hold for the config above + given cfg = ActorConfig{.state_dim=4,.action_dim=2,.hidden_dims=@constCast(&[_]u32{64,64}),.action_space=ActionSpace.DISCRETE,.init_log_std=DEFAULT_INIT_LOG_STD} + then (cfg.hidden_dims.len >= 1) and (cfg.action_dim >= 1) + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant ppo_actor_constraint_0 + given input = valid_input() + then true // MIN_LOG_STD <= log_std <= MAX_LOG_STD + + invariant ppo_actor_constraint_1 + given input = valid_input() + then true // action_dim >= 1 + + invariant ppo_actor_constraint_2 + given input = valid_input() + then true // len(hidden_dims) >= 1 + + invariant ppo_actor_constraint_3 + given input = valid_input() + then true // name: gaussian_log_prob + + invariant ppo_actor_constraint_4 + given input = valid_input() + then true // name: reparameterization + + invariant ppo_actor_constraint_5 + given input = valid_input() + then true // "Schulman et al. (2017) - Proximal Policy Optimization" + + invariant ppo_actor_constraint_6 + given input = valid_input() + then true // "Haarnoja et al. (2018) - Soft Actor-Critic" + diff --git a/apps/website/public/t27/specs/ml/rl/ppo_clip_loss.t27 b/apps/website/public/t27/specs/ml/rl/ppo_clip_loss.t27 new file mode 100644 index 0000000000..77ff5c270d --- /dev/null +++ b/apps/website/public/t27/specs/ml/rl/ppo_clip_loss.t27 @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Probability ratio between policies | φ² + 1/φ² = 3 | TRINITY + +module PpoClipLoss; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_CLIP_EPSILON : f32 = 0.2; + const DEFAULT_ENTROPY_COEF : f32 = 0.01; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const PPOClipConfig = struct { + clip_epsilon : f32, + entropy_coef : f32, + value_clip_coef : f32, + }; + + pub const PPOBatch = struct { + states : [][]f32, + actions : [][]f32, + old_log_probs : []f32, + advantages : []f32, + returns : []f32, + values : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // compute_ratio(new_log_prob: f32, old_log_prob: f32) → f32 + fn compute_ratio(new_log_prob: f32, old_log_prob: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // clipped_surrogate(ratio: f32, advantage: f32, clip_epsilon: f32) → f32 + fn clipped_surrogate(ratio: f32, advantage: f32, clip_epsilon: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // policy_loss(ratios: []f32, advantages: []f32, clip_epsilon: f32) → f32 + fn policy_loss(ratios: []f32, advantages: []f32, clip_epsilon: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // entropy_loss(entropies: []f32, entropy_coef: f32) → f32 + fn entropy_loss(entropies: []f32, entropy_coef: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // value_loss(predicted_values: []f32, target_values: []f32, old_values: []f32, clip_coef: f32) → f32 + fn value_loss(predicted_values: []f32, target_values: []f32, old_values: []f32, clip_coef: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // total_loss(policy_loss_value: f32, value_loss_value: f32, entropy_loss_value: f32, value_loss_coef: f32) → f32 + fn total_loss(policy_loss_value: f32, value_loss_value: f32, entropy_loss_value: f32, value_loss_coef: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // The six loss functions are unimplemented stubs returning void, so no + // surrogate objective can be evaluated yet. What the module does declare -- + // the shipped clip and entropy coefficients, the clipping window they + // imply, and the batch layout -- is tested here, including the two + // constraints that the invariants below only state in a comment. + + test default_coefficients_satisfy_the_declared_constraints + // Verify: ppo_clip_loss_constraint_0 (0 < clip_epsilon <= 1) and + // ppo_clip_loss_constraint_1 (entropy_coef >= 0) hold for the defaults + then (DEFAULT_CLIP_EPSILON > 0.0) and (DEFAULT_CLIP_EPSILON <= 1.0) and (DEFAULT_ENTROPY_COEF >= 0.0) + + test default_clip_epsilon_is_the_paper_value + // Verify: 0.2, the value used throughout Schulman et al. (2017), and + // inside the 0.1-0.3 band constraint_0 calls typical + then @abs(DEFAULT_CLIP_EPSILON - 0.2) < 1e-6 + + test clipping_window_is_symmetric_about_an_unchanged_policy + // Verify: a ratio of exactly 1 means the new policy matches the old + // one, and the window brackets it evenly + given lower = 1.0 - DEFAULT_CLIP_EPSILON + and upper = 1.0 + DEFAULT_CLIP_EPSILON + then @abs((upper - 1.0) - (1.0 - lower)) < 1e-6 + + test clipping_window_is_zero_point_eight_to_one_point_two + // Verify: the concrete bounds the default epsilon produces + given lower = 1.0 - DEFAULT_CLIP_EPSILON + and upper = 1.0 + DEFAULT_CLIP_EPSILON + then (@abs(lower - 0.8) < 1e-6) and (@abs(upper - 1.2) < 1e-6) + + test entropy_bonus_is_small_but_present + // Verify: exploration is encouraged, not dominant -- the coefficient is + // nonzero yet well under the clip range it competes with + then (DEFAULT_ENTROPY_COEF > 0.0) and (DEFAULT_ENTROPY_COEF < DEFAULT_CLIP_EPSILON) + + test config_built_from_the_module_defaults_is_well_formed + given cfg = PPOClipConfig{.clip_epsilon=DEFAULT_CLIP_EPSILON,.entropy_coef=DEFAULT_ENTROPY_COEF,.value_clip_coef=DEFAULT_CLIP_EPSILON} + then (cfg.clip_epsilon > 0.0) and (cfg.entropy_coef >= 0.0) + + test batch_fields_are_parallel_per_sample_arrays + // Verify: old_log_probs, advantages, returns and values carry one entry + // per transition, so a two-transition batch has length two in each + given batch = PPOBatch{.states=@constCast(&[_][]f32{@constCast(&[_]f32{0.0,1.0}),@constCast(&[_]f32{1.0,0.0})}),.actions=@constCast(&[_][]f32{@constCast(&[_]f32{1.0}),@constCast(&[_]f32{0.0})}),.old_log_probs=@constCast(&[_]f32{-0.5,-0.7}),.advantages=@constCast(&[_]f32{1.0,-1.0}),.returns=@constCast(&[_]f32{2.0,0.5}),.values=@constCast(&[_]f32{1.5,1.0})} + then (batch.states.len == 2) and (batch.old_log_probs.len == batch.states.len) and (batch.advantages.len == batch.returns.len) + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant ppo_clip_loss_constraint_0 + given input = valid_input() + then true // 0 < clip_epsilon <= 1 (typically 0.1-0.3) + + invariant ppo_clip_loss_constraint_1 + given input = valid_input() + then true // entropy_coef >= 0 + + invariant ppo_clip_loss_constraint_2 + given input = valid_input() + then true // name: ppo_objective + + invariant ppo_clip_loss_constraint_3 + given input = valid_input() + then true // name: probability_ratio + + invariant ppo_clip_loss_constraint_4 + given input = valid_input() + then true // "Schulman et al. (2017) - Proximal Policy Optimization Algorithms, arXiv:1707.06347" + diff --git a/apps/website/public/t27/specs/ml/rl/ppo_critic.t27 b/apps/website/public/t27/specs/ml/rl/ppo_critic.t27 new file mode 100644 index 0000000000..364ee6d338 --- /dev/null +++ b/apps/website/public/t27/specs/ml/rl/ppo_critic.t27 @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Temporal difference error | φ² + 1/φ² = 3 | TRINITY + +module PpoCritic; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_HIDDEN : []u32 = "[64, 64]"; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const CriticConfig = struct { + state_dim : u32, + hidden_dims : []u32, + activation : Activation, + }; + + pub const Activation = struct { + enum : [RELU, TANH, GELU], + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(state: []const f32, params: []const f32, config: CriticConfig) → f32 + fn forward(state: []const f32, params: []const f32, config: CriticConfig) -> f32 { + // TODO: Implement from .tri spec + } + + // compute_advantage(rewards: []f32, values: []f32, gamma: f32, lambda_: f32) → []f32 + fn compute_advantage(rewards: []f32, values: []f32, gamma: f32, lambda_: f32) -> []f32 { + // TODO: Implement from .tri spec + } + + // compute_returns(rewards: []f32, gamma: f32) → []f32 + fn compute_returns(rewards: []f32, gamma: f32) -> []f32 { + // TODO: Implement from .tri spec + } + + // value_loss(predicted_values: []f32, target_values: []f32) → f32 + fn value_loss(predicted_values: []f32, target_values: []f32) -> f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // NOTE: DEFAULT_HIDDEN is declared `[]u32 = "[64, 64]"` -- a string literal + // in a slice-of-u32 slot. Any test that names it is a compile error, so the + // two-layer default is restated here as a literal instead. + + test config_carries_two_hidden_layers_of_sixty_four + // Verify: the shape DEFAULT_HIDDEN is meant to describe -- two layers, + // 64 units each, sitting between the state input and the scalar value + given hidden = [_]u32{ 64, 64 } + and cfg = CriticConfig{ .state_dim = 4, .hidden_dims = @constCast(&hidden), .activation = Activation.RELU } + then cfg.hidden_dims.len == 2 and cfg.hidden_dims[0] == 64 and cfg.hidden_dims[1] == 64 + + test hidden_depth_is_not_fixed_by_the_config_type + // Verify: hidden_dims is a slice, so a one-layer critic and the + // two-layer default share one config type -- depth is data, not type + given shallow = [_]u32{ 64 } + and deep = [_]u32{ 64, 64 } + and a = CriticConfig{ .state_dim = 4, .hidden_dims = @constCast(&shallow), .activation = Activation.RELU } + and b = CriticConfig{ .state_dim = 4, .hidden_dims = @constCast(&deep), .activation = Activation.RELU } + when same_type = @TypeOf(a) == @TypeOf(b) + then same_type and a.hidden_dims.len == 1 and b.hidden_dims.len == 2 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant ppo_critic_constraint_0 + given input = valid_input() + then true // 0 <= gamma <= 1 + + invariant ppo_critic_constraint_1 + given input = valid_input() + then true // 0 <= lambda_ <= 1 + + invariant ppo_critic_constraint_2 + given input = valid_input() + then true // name: gae_formula + + invariant ppo_critic_constraint_3 + given input = valid_input() + then true // name: td_error + + invariant ppo_critic_constraint_4 + given input = valid_input() + then true // "Schulman et al. (2016) - High-dimensional continuous control using GAE" + + invariant ppo_critic_constraint_5 + given input = valid_input() + then true // "Schulman et al. (2017) - Proximal Policy Optimization Algorithms" + diff --git a/apps/website/public/t27/specs/ml/rl/sac_actor.t27 b/apps/website/public/t27/specs/ml/rl/sac_actor.t27 new file mode 100644 index 0000000000..1cd92c67f9 --- /dev/null +++ b/apps/website/public/t27/specs/ml/rl/sac_actor.t27 @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Log probability correction for tanh squashing | φ² + 1/φ² = 3 | TRINITY + +module SacActor; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_LOG_STD_MIN : f32 = -20.0; + const DEFAULT_LOG_STD_MAX : f32 = 2.0; + const DEFAULT_ACTION_RANGE : []f32 = "[-1.0, 1.0]"; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SACActorConfig = struct { + state_dim : u32, + action_dim : u32, + hidden_dims : []u32, + action_range : []f32, + log_std_min : f32, + log_std_max : f32, + }; + + pub const PolicyOutput = struct { + mean : []f32, + log_std : []f32, + }; + + pub const SquashedGaussianSample = struct { + action : []f32, + log_prob : f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(state: []const f32, params: []const f32, config: SACActorConfig) → PolicyOutput + fn forward(state: []const f32, params: []const f32, config: SACActorConfig) -> PolicyOutput { + // TODO: Implement from .tri spec + } + + // sample(policy_output: PolicyOutput, deterministic: bool) → SquashedGaussianSample + fn sample(policy_output: PolicyOutput, deterministic: bool) -> SquashedGaussianSample { + // TODO: Implement from .tri spec + } + + // scale_action(action: []f32, action_range: []f32) → []f32 + fn scale_action(action: []f32, action_range: []f32) -> []f32 { + // TODO: Implement from .tri spec + } + + // unscale_action(action: []f32, action_range: []f32) → []f32 + fn unscale_action(action: []f32, action_range: []f32) -> []f32 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // forward/sample/scale_action/unscale_action are unimplemented stubs + // returning void, so no behavioural assertion is possible yet. These tests + // execute the bound constraints that the invariants below only state in + // comments. DEFAULT_ACTION_RANGE is deliberately not referenced: it is + // declared []f32 but initialised with a string literal, so any use of it is + // a type error. + + test default_log_std_bounds_are_ordered + // Verify: constraint_0 (log_std_min < log_std_max) holds for the defaults + given lo = DEFAULT_LOG_STD_MIN + and hi = DEFAULT_LOG_STD_MAX + then lo < hi + + test default_log_std_bounds_give_a_positive_std_range + // Verify: exponentiating the clamp bounds yields a usable std range -- + // exp(-20) is tiny but strictly positive, exp(2) is about 7.389 + given std_min = @exp(DEFAULT_LOG_STD_MIN) + and std_max = @exp(DEFAULT_LOG_STD_MAX) + then @abs(std_max - 7.389056) < 1e-3 and std_min > 0.0 + + test config_action_range_is_low_then_high + // Verify: constraint_1 (action_low < action_high) for the [-1, 1] range + // the module documents, through the declared config fields + given cfg = SACActorConfig{.state_dim=3,.action_dim=1,.hidden_dims=@constCast(&[_]u32{256,256}),.action_range=@constCast(&[_]f32{-1.0,1.0}),.log_std_min=DEFAULT_LOG_STD_MIN,.log_std_max=DEFAULT_LOG_STD_MAX} + then cfg.log_std_min < cfg.log_std_max and cfg.action_range[0] < cfg.action_range[1] + + test squashed_sample_holds_an_action_and_a_log_probability + // Verify: SquashedGaussianSample has the declared fields, and a squashed + // action sits strictly inside the [-1, 1] range the module documents + given s = SquashedGaussianSample{.action=@constCast(&[_]f32{0.999}),.log_prob=-1.5} + then s.log_prob < 0.0 and s.action[0] < 1.0 and s.action[0] > -1.0 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant sac_actor_constraint_0 + given input = valid_input() + then true // log_std_min < log_std_max + + invariant sac_actor_constraint_1 + given input = valid_input() + then true // action_low < action_high + + invariant sac_actor_constraint_2 + given input = valid_input() + then true // name: reparameterization + + invariant sac_actor_constraint_3 + given input = valid_input() + then true // name: squashed_correction + + invariant sac_actor_constraint_4 + given input = valid_input() + then true // "Haarnoja et al. (2018) - Soft Actor-Critic: Off-Policy Maximum Entropy Deep RL" + + invariant sac_actor_constraint_5 + given input = valid_input() + then true // "Haarnoja et al. (2019) - Soft Actor-Critic Algorithms and Applications" + diff --git a/apps/website/public/t27/specs/ml/rl/sac_critic.t27 b/apps/website/public/t27/specs/ml/rl/sac_critic.t27 new file mode 100644 index 0000000000..a5fb1e1019 --- /dev/null +++ b/apps/website/public/t27/specs/ml/rl/sac_critic.t27 @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Clipped double Q-learning | φ² + 1/φ² = 3 | TRINITY + +module SacCritic; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_TAU : f32 = 0.005; + const DEFAULT_GAMMA : f32 = 0.99; + const DEFAULT_INIT_WEIGHT : f32 = 0.003; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SACCriticConfig = struct { + state_dim : u32, + action_dim : u32, + hidden_dims : []u32, + init_weight : f32, + }; + + pub const TwinQOutput = struct { + q1 : f32, + q2 : f32, + }; + + pub const TargetQUpdate = struct { + tau : f32, + gamma : f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(state: []const f32, action: []const f32, params_q1: []const f32, params_q2: []const f32, config: SACCriticConfig) → TwinQOutput + fn forward(state: []const f32, action: []const f32, params_q1: []const f32, params_q2: []const f32, config: SACCriticConfig) -> TwinQOutput { + // TODO: Implement from .tri spec + } + + // compute_target(reward: f32, next_state: []f32, done: bool, target_q1_params: []f32, target_q2_params: []f32, actor_params: []f32, config: SACCriticConfig, temperature: f32) → f32 + fn compute_target(reward: f32, next_state: []f32, done: bool, target_q1_params: []f32, target_q2_params: []f32, actor_params: []f32, config: SACCriticConfig, temperature: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // q_loss(q1_pred: f32, q2_pred: f32, target: f32) → f32 + fn q_loss(q1_pred: f32, q2_pred: f32, target: f32) -> f32 { + // TODO: Implement from .tri spec + } + + // soft_update_target(source_params: []f32, target_params: []f32, tau: f32) → void + fn soft_update_target(source_params: []f32, target_params: []f32, tau: f32) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above is still `TODO: Implement`, so each one compiles + // to `@panic("not yet implemented")`: calling one aborts the test binary. + // What this module does state is three defaults, the twin-Q shape, and + // the signature of each operation. The constraints listed below are + // checked here against the defaults the module ships, rather than being + // left as prose. + + test default_tau_is_a_small_positive_interpolation_factor + given tau = DEFAULT_TAU + then tau > 0.0 + and tau < 1.0 + and @abs(tau - 0.005) < 1e-9 + + test default_gamma_lies_in_the_unit_interval + given gamma = DEFAULT_GAMMA + then gamma >= 0.0 + and gamma <= 1.0 + and @abs(gamma - 0.99) < 1e-9 + + // A near-zero final-layer init keeps both Q heads small at step 0, so the + // clipped minimum does not start out wildly pessimistic. + test default_init_weight_is_small_and_positive + given w = DEFAULT_INIT_WEIGHT + then w > 0.0 + and w < 0.01 + + test a_target_update_built_from_the_defaults_satisfies_both_constraints + given upd = TargetQUpdate{ .tau = DEFAULT_TAU, .gamma = DEFAULT_GAMMA } + then upd.tau > 0.0 + and upd.tau < 1.0 + and upd.gamma >= 0.0 + and upd.gamma <= 1.0 + + // Clipped double Q: the target uses min(q1, q2), so the pair is ordered + // by the smaller head whichever way round the two estimates come out. + test clipped_double_q_takes_the_smaller_of_the_two_heads + given out = TwinQOutput{ .q1 = 2.5, .q2 = -1.0 } + and swapped = TwinQOutput{ .q1 = -1.0, .q2 = 2.5 } + then @abs(@min(out.q1, out.q2) - (-1.0)) < 1e-6 + and @abs(@min(swapped.q1, swapped.q2) - @min(out.q1, out.q2)) < 1e-6 + + test twin_q_output_carries_two_independent_f32_estimates + given q1 = @FieldType(TwinQOutput, "q1") + then q1 == f32 + and @FieldType(TwinQOutput, "q2") == f32 + + test critic_config_sizes_state_and_action_separately + given s = @FieldType(SACCriticConfig, "state_dim") + then s == u32 + and @FieldType(SACCriticConfig, "action_dim") == u32 + and @FieldType(SACCriticConfig, "hidden_dims") == []u32 + + // None of these signatures can express its operation: forward is given a + // state with no action, compute_target a reward with no next-state Q and + // no gamma, q_loss one prediction with no target, and soft_update_target + // one parameter buffer with no target buffer and no tau. They record what + // is declared, not what SAC needs. + test forward_takes_a_state_slice_and_returns_nothing + given f = forward + then @TypeOf(f) == fn ([]const f32) void + + test compute_target_and_q_loss_take_one_scalar_and_return_nothing + given f = compute_target + then @TypeOf(f) == fn (f32) void + and @TypeOf(q_loss) == fn (f32) void + + test soft_update_target_takes_one_mutable_parameter_slice + given f = soft_update_target + then @TypeOf(f) == fn ([]f32) void + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant sac_critic_constraint_0 + given input = valid_input() + then true // 0 < tau << 1 (typically 0.005) + + invariant sac_critic_constraint_1 + given input = valid_input() + then true // 0 <= gamma <= 1 + + invariant sac_critic_constraint_2 + given input = valid_input() + then true // name: sac_target + + invariant sac_critic_constraint_3 + given input = valid_input() + then true // name: clipped_double_q + + invariant sac_critic_constraint_4 + given input = valid_input() + then true // "Fujimoto et al. (2018) - Addressing Function Approximation Error in Actor-Critic Methods (TD3)" + + invariant sac_critic_constraint_5 + given input = valid_input() + then true // "Haarnoja et al. (2018) - Soft Actor-Critic: Off-Policy Maximum Entropy Deep RL" + diff --git a/apps/website/public/t27/specs/ml/transformer/encoder_block.t27 b/apps/website/public/t27/specs/ml/transformer/encoder_block.t27 new file mode 100644 index 0000000000..6e5e96ded3 --- /dev/null +++ b/apps/website/public/t27/specs/ml/transformer/encoder_block.t27 @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Standard Transformer encoder block with residual connections | φ² + 1/φ² = 3 | TRINITY + +module EncoderBlock; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_D_MODEL : u32 = 512; + const DEFAULT_N_HEADS : u32 = 8; + const DEFAULT_D_FF : u32 = 2048; + const DEFAULT_DROPOUT : f32 = 0.1; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const EncoderBlockConfig = struct { + d_model : u32, + n_heads : u32, + d_ff : u32, + dropout : f32, + use_pre_norm : bool, + }; + + pub const AttentionOutput = struct { + output : []f32, + attn_weights : [][]f32, + }; + + pub const FFNOutput = struct { + output : []f32, + }; + + pub const BlockOutput = struct { + output : []f32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // multi_head_attention(query: []f32, key: []f32, value: []f32, n_heads: u32, d_model: u32) → AttentionOutput + fn multi_head_attention(query: []f32, key: []f32, value: []f32, n_heads: u32, d_model: u32) -> AttentionOutput { + // TODO: Implement from .tri spec + } + + // feed_forward(input: []f32, d_model: u32, d_ff: u32, dropout: f32) → FFNOutput + fn feed_forward(input: []f32, d_model: u32, d_ff: u32, dropout: f32) -> FFNOutput { + // TODO: Implement from .tri spec + } + + // forward(input: []f32, config: EncoderBlockConfig) → BlockOutput + fn forward(input: []f32, config: EncoderBlockConfig) -> BlockOutput { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // The three functions above are unimplemented stubs returning void, so no + // behavioural assertion is possible yet. These tests execute the shape + // constraints that the invariants below only state in comments. + + test default_d_model_divides_evenly_across_heads + // Verify: constraint_0 (d_model % n_heads == 0) holds for the defaults, + // so each of the 8 heads gets 512/8 = 64 dimensions + given head_dim = DEFAULT_D_MODEL / DEFAULT_N_HEADS + then DEFAULT_D_MODEL % DEFAULT_N_HEADS == 0 and head_dim == 64 + + test default_ffn_is_at_least_as_wide_as_the_model + // Verify: constraint_1 (d_ff >= d_model) holds, at the 4x ratio of + // Vaswani et al. (2017) + given ratio = DEFAULT_D_FF / DEFAULT_D_MODEL + then DEFAULT_D_FF >= DEFAULT_D_MODEL and ratio == 4 + + test default_dropout_is_a_probability + // Verify: the default dropout rate lies in [0, 1] + given p = DEFAULT_DROPOUT + then p >= 0.0 and p <= 1.0 + + test config_built_from_defaults_satisfies_both_constraints + // Verify: EncoderBlockConfig has the declared field names, and a config + // filled from the defaults meets constraint_0 and constraint_1 + given cfg = EncoderBlockConfig{.d_model=DEFAULT_D_MODEL,.n_heads=DEFAULT_N_HEADS,.d_ff=DEFAULT_D_FF,.dropout=DEFAULT_DROPOUT,.use_pre_norm=true} + then cfg.d_model % cfg.n_heads == 0 and cfg.d_ff >= cfg.d_model and cfg.use_pre_norm + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant encoder_block_constraint_0 + given input = valid_input() + then true // d_model % n_heads == 0 + + invariant encoder_block_constraint_1 + given input = valid_input() + then true // d_ff >= d_model + + invariant encoder_block_constraint_2 + given input = valid_input() + then true // name: encoder_block + + invariant encoder_block_constraint_3 + given input = valid_input() + then true // "Vaswani et al. (2017) - Attention Is All You Need" + + invariant encoder_block_constraint_4 + given input = valid_input() + then true // "Baevski et al. (2020) - Revisiting the Self-Attention" + diff --git a/apps/website/public/t27/specs/ml/transformer/feed_forward.t27 b/apps/website/public/t27/specs/ml/transformer/feed_forward.t27 new file mode 100644 index 0000000000..17463ea68f --- /dev/null +++ b/apps/website/public/t27/specs/ml/transformer/feed_forward.t27 @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ml/transformer/feed_forward.t27 +// Transformer Feed-Forward Network (FFN) with φ-optimized dimensions | φ² + 1/φ² = 3 | TRINITY + +module FeedForward; + use base::types; + use math::constants; + use numeric::gf16; + use ml::activation::gelu; + use ml::layers::dense; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const PHI : gf16::GF16 = 1.618033988749895; + const INV_PHI : gf16::GF16 = 1.0 / PHI; // ≈ 0.618 + const PHI_SQUARED : gf16::GF16 = PHI * PHI; // ≈ 2.618 + const DEFAULT_EXPANSION_FACTOR : gf16::GF16 = 4.0; + const PHI_EXPANSION_FACTOR : gf16::GF16 = PHI_SQUARED; // ≈ 2.618, φ-optimized expansion + const DEFAULT_DROPOUT : gf16::GF16 = 0.1; + const DEFAULT_USE_BIAS : bool = true; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const FFNConfig = struct { + hidden_size : u32, // Input/output dimension (d_model) + expansion_factor : gf16::GF16, // Multiplier for intermediate dimension + activation : ActivationType, // Activation function + dropout : gf16::GF16, // Dropout probability + use_bias : bool, // Use bias in dense layers + use_phi_expansion : bool, // Use φ-optimized expansion factor + use_residual : bool, // Apply residual connection + }; + + pub const FFNState = struct { + w1 : []gf16::GF16, // Projection weights: [hidden_size * intermediate_size] + b1 : []gf16::GF16, // Projection bias: [intermediate_size] + w2 : []gf16::GF16, // Output weights: [intermediate_size * hidden_size] + b2 : []gf16::GF16, // Output bias: [hidden_size] + dropout_mask : []gf16::GF16, // Dropout mask for inference + is_training : bool, // Training/inference mode + }; + + pub const ActivationType = struct { + enum_type : "enum", + values : , + GELU : Auto, + ReLU : Auto, + SiLU : Auto, + GELU_Approx : Auto, + }; + + pub const FFNGradients = struct { + d_w1 : []gf16::GF16, // Gradient w.r.t. w1 + d_b1 : []gf16::GF16, // Gradient w.r.t. b1 + d_w2 : []gf16::GF16, // Gradient w.r.t. w2 + d_b2 : []gf16::GF16, // Gradient w.r.t. b2 + d_input : []gf16::GF16, // Gradient w.r.t. input + }; + + pub const FFNForwardResult = struct { + output : []gf16::GF16, // Final output (with residual if enabled) + hidden : []gf16::GF16, // After first projection and activation + dropout_mask : []gf16::GF16, // Applied dropout mask (for backward) + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(config: FFNConfig) → FFNState + // Initializes FFN with φ-optimized weight distribution. + fn init(config: FFNConfig) -> FFNState { + // intermediate_size = hidden_size * expansion_factor + // If use_phi_expansion: use PHI_EXPANSION_FACTOR (≈2.618) + // else: use DEFAULT_EXPANSION_FACTOR (4.0) + // + // Weight initialization: + // w1, w2: Xavier/Glorot initialization with φ adjustment + // If use_phi_init: std = sqrt(2.0 / (fan_in + fan_out)) * INV_PHI + } + + // get_intermediate_size(config: FFNConfig) → u32 + // Computes the intermediate dimension based on expansion factor. + fn get_intermediate_size(config: FFNConfig) -> u32 { + // if use_phi_expansion: return hidden_size * PHI_EXPANSION_FACTOR + // else: return hidden_size * DEFAULT_EXPANSION_FACTOR + } + + // forward(state: FFNState, input: []gf16::GF16) → FFNForwardResult + // Computes FFN forward pass: output = Dropout(Act(x @ W1 + b1)) @ W2 + b2 + fn forward(state: FFNState, input: []gf16::GF16) -> FFNForwardResult { + // 1. Project: hidden = input @ W1 + b1 (or W1 @ input depending on layout) + // 2. Activate: hidden = activation(hidden) + // 3. Dropout (if training): hidden = dropout(hidden, p) + // 4. Project: output = hidden @ W2 + b2 + // 5. Residual (if enabled): output = output + input + } + + // backward(state: FFNState, result: FFNForwardResult, grad_output: []gf16::GF16) → FFNGradients + // Computes gradients for backpropagation. + fn backward(state: FFNState, result: FFNForwardResult, grad_output: []gf16::GF16) -> FFNGradients { + // 1. Handle residual: if use_residual, add grad_output to d_input + // 2. d_hidden = grad_output @ W2.T + // 3. d_w2 = hidden.T @ grad_output + // 4. d_b2 = sum(grad_output, axis=0) + // 5. d_hidden = d_hidden * dropout_mask (if training) + // 6. d_hidden = activation_backward(hidden, d_hidden) + // 7. d_w1 = input.T @ d_hidden + // 8. d_b1 = sum(d_hidden, axis=0) + // 9. d_input = d_hidden @ W1.T + } + + // update(state: FFNState, grads: FFNGradients, lr: gf16::GF16) → FFNState + // Updates parameters using computed gradients. + fn update(state: FFNState, grads: FFNGradients, lr: gf16::GF16) -> FFNState { + // w1 = w1 - lr * d_w1 + // b1 = b1 - lr * d_b1 + // w2 = w2 - lr * d_w2 + // b2 = b2 - lr * d_b2 + } + + // apply_activation(x: []gf16::GF16, act: ActivationType) → []gf16::GF16 + // Applies the specified activation function. + fn apply_activation(x: []gf16::GF16, act: ActivationType) -> []gf16::GF16 { + // switch act: + // GELU: return gelu_forward(x) + // ReLU: return relu_forward(x) + // SiLU: return silu_forward(x) + // GELU_Approx: return gelu_approx_forward(x) + } + + // activation_backward(x: []gf16::GF16, grad: []gf16::GF16, act: ActivationType) → []gf16::GF16 + // Computes activation backward pass. + fn activation_backward(x: []gf16::GF16, grad: []gf16::GF16, act: ActivationType) -> []gf16::GF16 { + // switch act: + // GELU: return gelu_backward(x, grad) + // ReLU: return relu_backward(grad) + // SiLU: return silu_backward(x, grad) + // GELU_Approx: return gelu_approx_backward(x, grad) + } + + // dropout(input: []gf16::GF16, p: gf16::GF16, training: bool) → ([]gf16::GF16, []gf16::GF16) + // Applies dropout during training, scales during inference. + fn dropout(input: []gf16::GF16, p: gf16::GF16, training: bool) -> struct { output: []gf16::GF16, mask: []gf16::GF16 } { + // if training: + // mask = random_bernoulli(1 - p) + // output = input * mask / (1 - p) + // else: + // output = input + // mask = [] (empty) + } + + // phi_init_weights(fan_in: u32, fan_out: u32) → []gf16::GF16 + // φ-optimized weight initialization. + fn phi_init_weights(fan_in: u32, fan_out: u32) -> []gf16::GF16 { + // std = sqrt(2.0 / (fan_in + fan_out)) * INV_PHI + // Sample from N(0, std^2) + } + + // get_parameter_count(config: FFNConfig) → u64 + // Returns total number of trainable parameters. + fn get_parameter_count(config: FFNConfig) -> u64 { + // params = (hidden_size * intermediate_size) * 2 + // if use_bias: params += hidden_size + intermediate_size + // return params + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests + // ═══════════════════════════════════════════════════════════ + + test get_intermediate_size_default_expansion + given config = FFNConfig{.hidden_size = 512, .expansion_factor = 4.0, .use_phi_expansion = false} + when result = get_intermediate_size(config) + then result == 2048 + + test get_intermediate_size_phi_expansion + given config = FFNConfig{.hidden_size = 512, .expansion_factor = 0.0, .use_phi_expansion = true} + when result = get_intermediate_size(config) + then approximately_equal_within(result, 512.0 * PHI_EXPANSION_FACTOR, 1.0) + + test init_creates_correct_dimensions + given config = FFNConfig{.hidden_size = 128, .expansion_factor = 4.0, .use_bias = true, .use_phi_expansion = false} + when state = init(config) + then state.w1.len == 128 * 512 + and state.b1.len == 512 + and state.w2.len == 512 * 128 + and state.b2.len == 128 + + test init_creates_correct_dimensions_no_bias + given config = FFNConfig{.hidden_size = 128, .expansion_factor = 4.0, .use_bias = false, .use_phi_expansion = false} + when state = init(config) + then state.b1.len == 0 + and state.b2.len == 0 + + test forward_output_shape_matches_input + given config = FFNConfig{.hidden_size = 64, .use_residual = false} + and state = init(config) + and input = random_input(64) + when result = forward(state, input) + then result.output.len == 64 + + test forward_with_residual_preserves_shape + given config = FFNConfig{.hidden_size = 64, .use_residual = true} + and state = init(config) + and input = random_input(64) + when result = forward(state, input) + then result.output.len == 64 + + test forward_hidden_size_is_intermediate + given config = FFNConfig{.hidden_size = 64, .expansion_factor = 4.0, .use_phi_expansion = false} + and state = init(config) + and input = random_input(64) + when result = forward(state, input) + then result.hidden.len == 256 + + test dropout_scales_during_inference + given config = FFNConfig{.hidden_size = 10, .dropout = 0.5} + and state = init(config) + and state.is_training = false + and input = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + when (output, mask) = dropout(input, 0.5, false) + then all(output, fn(x) x == 1.0) + + test dropout_applies_mask_during_training + given config = FFNConfig{.hidden_size = 10, .dropout = 0.5} + and state = init(config) + and state.is_training = true + and input = [2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0] + when (output, mask) = dropout(input, 0.5, true) + then some(output, fn(x) x == 0.0 or x == 4.0) // 0.0 or 2.0/(1-0.5)=4.0 + + test backward_gradients_match_parameter_shapes + given config = FFNConfig{.hidden_size = 32, .expansion_factor = 4.0, .use_bias = true} + and state = init(config) + and input = random_input(32) + and forward_result = forward(state, input) + and grad_output = random_input(32) + when grads = backward(state, forward_result, grad_output) + then grads.d_w1.len == state.w1.len + and grads.d_b1.len == state.b1.len + and grads.d_w2.len == state.w2.len + and grads.d_b2.len == state.b2.len + and grads.d_input.len == 32 + + test backward_no_bias_has_zero_bias_gradients + given config = FFNConfig{.hidden_size = 32, .use_bias = false} + and state = init(config) + and input = random_input(32) + and forward_result = forward(state, input) + and grad_output = random_input(32) + when grads = backward(state, forward_result, grad_output) + then grads.d_b1.len == 0 + and grads.d_b2.len == 0 + + test update_modifies_parameters + given config = FFNConfig{.hidden_size = 10} + and state = init(config) + and input = random_input(10) + and forward_result = forward(state, input) + and grad_output = random_input(10) + and grads = backward(state, forward_result, grad_output) + and lr = 0.01 + when new_state = update(state, grads, lr) + then not all_equal(state.w1, new_state.w1) + + test get_parameter_count_with_bias + given config = FFNConfig{.hidden_size = 128, .expansion_factor = 4.0, .use_bias = true, .use_phi_expansion = false} + when result = get_parameter_count(config) + then result == 128 * 512 + 512 + 512 * 128 + 128 // w1 + b1 + w2 + b2 + + test get_parameter_count_no_bias + given config = FFNConfig{.hidden_size = 128, .expansion_factor = 4.0, .use_bias = false, .use_phi_expansion = false} + when result = get_parameter_count(config) + then result == 128 * 512 + 512 * 128 // w1 + w2 only + + test phi_expansion_reduces_parameters + given default_config = FFNConfig{.hidden_size = 512, .use_phi_expansion = false} + and phi_config = FFNConfig{.hidden_size = 512, .use_phi_expansion = true} + when default_params = get_parameter_count(default_config) + and phi_params = get_parameter_count(phi_config) + then phi_params < default_params + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants + // ═══════════════════════════════════════════════════════════ + + invariant forward_output_finite + given config = any_ffn_config() + and state = init(config) + and input = finite_input(config.hidden_size) + when result = forward(state, input) + then all(result.output, is_finite) + + invariant forward_hidden_finite + given config = any_ffn_config() + and state = init(config) + and input = finite_input(config.hidden_size) + when result = forward(state, input) + then all(result.hidden, is_finite) + + invariant dropout_preserves_expected_value + given input = [1.0, 1.0, 1.0, 1.0] + and p = 0.5 + when (output, _) = dropout(input, p, true) + then absolute_value(compute_mean(output) - 1.0) < 0.3 // Statistical tolerance + + invariant dropout_output_in_range + given input = positive_input(100) + and p = 0.5 + when (output, _) = dropout(input, p, true) + then all(output, fn(x) x >= 0.0) + + invariant intermediate_size_multiple_of_hidden + given config = any_ffn_config() + when intermediate = get_intermediate_size(config) + then intermediate % config.hidden_size == 0 or config.hidden_size % intermediate == 0 + + invariant phi_expansion_less_than_default + then PHI_EXPANSION_FACTOR < DEFAULT_EXPANSION_FACTOR + + invariant phi_squared_is_phi_plus_one + then approximately_equal_within(PHI_SQUARED, PHI + 1.0, 1e-9) + + invariant inv_phi_plus_phi_equals_one + then approximately_equal_within(INV_PHI + PHI, 1.0 + PHI_SQUARED, 1e-9) + + invariant parameter_count_non_negative + given config = any_ffn_config() + when count = get_parameter_count(config) + then count > 0 + + invariant backward_gradients_finite + given config = any_ffn_config() + and state = init(config) + and input = finite_input(config.hidden_size) + and forward_result = forward(state, input) + and grad_output = finite_input(config.hidden_size) + when grads = backward(state, forward_result, grad_output) + then all(grads.d_w1, is_finite) + and all(grads.d_w2, is_finite) + and all(grads.d_input, is_finite) + + // ═══════════════════════════════════════════════════════════ + // TDD: Benchmarks + // ═══════════════════════════════════════════════════════════ + + bench init_small + given config = FFNConfig{.hidden_size = 128, .expansion_factor = 4.0} + when result = init(config) + then elapsed_time_ms < 5 + + bench init_medium + given config = FFNConfig{.hidden_size = 1024, .expansion_factor = 4.0} + when result = init(config) + then elapsed_time_ms < 50 + + bench init_large + given config = FFNConfig{.hidden_size = 4096, .expansion_factor = 4.0} + when result = init(config) + then elapsed_time_ms < 200 + + bench forward_small + given config = FFNConfig{.hidden_size = 128} + and state = init(config) + and input = random_input(128) + when result = forward(state, input) + then elapsed_time_us < 200 + + bench forward_medium + given config = FFNConfig{.hidden_size = 1024} + and state = init(config) + and input = random_input(1024) + when result = forward(state, input) + then elapsed_time_ms < 5 + + bench forward_large + given config = FFNConfig{.hidden_size = 4096} + and state = init(config) + and input = random_input(4096) + when result = forward(state, input) + then elapsed_time_ms < 50 + + bench backward_small + given config = FFNConfig{.hidden_size = 128} + and state = init(config) + and input = random_input(128) + and forward_result = forward(state, input) + and grad_output = random_input(128) + when result = backward(state, forward_result, grad_output) + then elapsed_time_us < 300 + + bench dropout_small + given input = random_input(1000) + when (output, mask) = dropout(input, 0.1, true) + then elapsed_time_us < 100 + + bench phi_init_weights + given fan_in = 768 + and fan_out = 3072 + when result = phi_init_weights(fan_in, fan_out) + then elapsed_time_ms < 10 + + // ═══════════════════════════════════════════════════════════ + // Mathematical Notes + // ═══════════════════════════════════════════════════════════ + // + // FFN Architecture: + // FFN(x) = Dropout(Act(xW₁ + b₁))W₂ + b₂ + // With residual: Output = FFN(x) + x + // + // Dimensions: + // x: [batch, hidden_size] + // W₁: [hidden_size, intermediate_size] + // b₁: [intermediate_size] + // W₂: [intermediate_size, hidden_size] + // b₂: [hidden_size] + // + // Expansion Factor: + // Default: 4.0 → intermediate = 4 * hidden + // φ-optimized: φ² ≈ 2.618 → intermediate = φ² * hidden + // Parameter reduction: ~34% with φ-optimization + // + // GELU Activation: + // GELU(x) = x * Φ(x) where Φ is CDF of standard normal + // Approximation: 0.5 * x * (1 + tanh(√(2/π) * (x + 0.044715 * x³))) + // + // Phi-Optimized Weight Initialization: + // std = √(2 / (fan_in + fan_out)) / φ + // Reduces initial variance for more stable training + // + // ═══════════════════════════════════════════════════════════ diff --git a/apps/website/public/t27/specs/ml/transformer/feed_forward_network.t27 b/apps/website/public/t27/specs/ml/transformer/feed_forward_network.t27 new file mode 100644 index 0000000000..a6e041663a --- /dev/null +++ b/apps/website/public/t27/specs/ml/transformer/feed_forward_network.t27 @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Same FFN applied to each position independently | φ² + 1/φ² = 3 | TRINITY + +module FeedForward; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_D_FF : u32 = 2048; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const FFNConfig = struct { + d_model : u32, + d_ff : u32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(input: []const f32, W1: []const f32, b1: []const f32, W2: []const f32, b2: []const f32, output: []f32, config: FFNConfig) → void + fn forward(input: []const f32, W1: []const f32, b1: []const f32, W2: []const f32, b2: []const f32, output: []f32, config: FFNConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test forward_basic_case + given input = default_input() + when result = forward(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/ml/transformer/mha_block.t27 b/apps/website/public/t27/specs/ml/transformer/mha_block.t27 new file mode 100644 index 0000000000..d919dcb7ed --- /dev/null +++ b/apps/website/public/t27/specs/ml/transformer/mha_block.t27 @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ml/transformer/mha_block.t27 +// Transformer Encoder Block with Pre-LN & φ-optimized residuals | φ² + 1/φ² = 3 | TRINITY + +module MHABlock; + use base::types; + use math::constants; + use numeric::gf16; + use ml::transformer::norm; + use ml::transformer::multi_head_attn; + use ml::transformer::feed_forward; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const PHI : gf16::GF16 = 1.618033988749895; + const DEFAULT_DROPOUT : gf16::GF16 = 0.1; + const PHI_RESIDUAL_SCALE : gf16::GF16 = 1.0 / PHI; // φ-optimized residual scaling + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const BlockConfig = struct { + hidden_size : u32, // d_model: hidden dimension + num_heads : u32, // Number of attention heads + expansion_factor : gf16::GF16, // FFN expansion factor + dropout : gf16::GF16, // Dropout probability + use_pre_ln : bool, // Pre-LayerNorm (default: true) + use_post_ln : bool, // Post-LayerNorm (optional) + use_phi_residual : bool, // Apply φ-optimized residual scaling + }; + + pub const BlockState = struct { + norm1 : NormState, // First LayerNorm (Pre-Attention) + norm2 : NormState, // Second LayerNorm (Pre-FFN) + mha_state : MHAState, // Multi-Head Attention state + ffn_state : FFNState, // Feed-Forward Network state + is_training : bool, // Training/inference mode + }; + + pub const BlockForwardResult = struct { + output : []gf16::GF16, // Block output: [batch, seq_len, hidden_size] + attention_weights : []gf16::GF16, // Attention weights (for visualization) + intermediate_hidden : []gf16::GF16, // After MHA (for analysis) + }; + + pub const BlockGradients = struct { + d_input : []gf16::GF16, // Gradient wrt block input + mha_grads : MHAGradients, // Gradients for MHA + ffn_grads : FFNGradients, // Gradients for FFN + }; + + pub const ResidualConnection = struct { + scale : gf16::GF16, // Residual scaling factor + use_projection : bool, // Use projection for dimension mismatch + projection_weights : []gf16::GF16, // Projection matrix if needed + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(config: BlockConfig) → BlockState + // Initializes Transformer encoder block. + fn init(config: BlockConfig) -> BlockState { + // Initialize norm1, norm2 with hidden_size + // Initialize mha_state with hidden_size, num_heads + // Initialize ffn_state with hidden_size, expansion_factor + // is_training = true + } + + // forward(state: BlockState, input: []gf16::GF16) → BlockForwardResult + // Computes encoder block forward pass. + fn forward(state: BlockState, input: []gf16::GF16) -> BlockForwardResult { + // Pre-LN Architecture (default): + // 1. norm1_output = LayerNorm(input) + // 2. attn_output, attn_weights = MHA(norm1_output) + // 3. attn_output = dropout(attn_output) + // 4. residual1 = input + attn_output + // 5. norm2_output = LayerNorm(residual1) + // 6. ffn_output = FFN(norm2_output) + // 7. ffn_output = dropout(ffn_output) + // 8. output = residual1 + ffn_output + // + // If use_phi_residual: scale residuals by PHI_RESIDUAL_SCALE + } + + // forward_with_post_ln(state: BlockState, input: []gf16::GF16) → BlockForwardResult + // Computes forward pass with Post-LN architecture. + fn forward_with_post_ln(state: BlockState, input: []gf16::GF16) -> BlockForwardResult { + // Post-LN Architecture: + // 1. attn_output = MHA(input) + // 2. attn_output = dropout(attn_output) + // 3. residual1 = input + attn_output + // 4. norm1_output = LayerNorm(residual1) + // 5. ffn_output = FFN(norm1_output) + // 6. ffn_output = dropout(ffn_output) + // 7. output = residual1 + ffn_output + // 8. norm2_output = LayerNorm(output) + } + + // apply_residual_connection(x: []gf16::GF16, processed: []gf16::GF16, scale: gf16::GF16) → []gf16::GF16 + // Applies residual connection with optional scaling. + fn apply_residual_connection(x: []gf16::GF16, processed: []gf16::GF16, scale: gf16::GF16) -> []gf16::GF16 { + // return x + processed * scale + } + + // phi_scaled_residual(x: []gf16::GF16, processed: []gf16::GF16) → []gf16::GF16 + // Applies φ-optimized residual connection. + fn phi_scaled_residual(x: []gf16::GF16, processed: []gf16::GF16) -> []gf16::GF16 { + // return x + processed * PHI_RESIDUAL_SCALE + } + + // backward(state: BlockState, result: BlockForwardResult, grad_output: []gf16::GF16) → BlockGradients + // Computes gradients for backpropagation. + fn backward(state: BlockState, result: BlockForwardResult, grad_output: []gf16::GF16) -> BlockGradients { + // Pre-LN backward: + // 1. d_ffn_input = grad_output (from residual) + // 2. ffn_grads = FFN.backward(norm2_output, d_ffn_input) + // 3. d_norm2 = ffn_grads.d_input + grad_output + // 4. norm2_grads = Norm.backward(norm2_output, d_norm2) + // 5. d_attn_input = norm2_grads.d_input (from residual) + // 6. mha_grads = MHA.backward(norm1_output, d_attn_input) + // 7. d_norm1 = mha_grads.d_input + d_attn_input + // 8. norm1_grads = Norm.backward(input, d_norm1) + // 9. d_input = norm1_grads.d_input + grad_output (from first residual) + } + + // get_parameter_count(config: BlockConfig) → u64 + // Returns total number of trainable parameters. + fn get_parameter_count(config: BlockConfig) -> u64 { + // params = MHA_params + FFN_params + 2 * Norm_params + // return params + } + + // get_flops(config: BlockConfig, seq_len: u32) → u64 + // Estimates FLOPs for forward pass. + fn get_flops(config: BlockConfig, seq_len: u32) -> u64 { + // MHA FLOPs: 4 * seq_len^2 * hidden_size + // FFN FLOPs: 2 * seq_len * hidden_size * intermediate_size + // Norm FLOPs: 2 * seq_len * hidden_size * 2 + // return total + } + + // create_residual_connection(scale: gf16::GF16) → ResidualConnection + // Creates residual connection configuration. + fn create_residual_connection(scale: gf16::GF16) -> ResidualConnection { + // return ResidualConnection{.scale = scale, .use_projection = false} + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests + // ═══════════════════════════════════════════════════════════ + + test init_creates_valid_state + given config = BlockConfig{.hidden_size = 128, .num_heads = 8, .expansion_factor = 4.0, .dropout = 0.1, .use_pre_ln = true, .use_post_ln = false, .use_phi_residual = false} + when state = init(config) + then state.mha_state.w_q.len > 0 + and state.ffn_state.w1.len > 0 + + test forward_output_shape_matches_input + given config = BlockConfig{.hidden_size = 64, .num_heads = 4, .use_pre_ln = true} + and state = init(config) + and input = random_input(64) + when result = forward(state, input) + then result.output.len == 64 + + test forward_with_post_ln_shape_matches + given config = BlockConfig{.hidden_size = 64, .num_heads = 4, .use_pre_ln = false, .use_post_ln = true} + and state = init(config) + and input = random_input(64) + when result = forward_with_post_ln(state, input) + then result.output.len == 64 + + test apply_residual_adds_inputs + given x = [1.0, 2.0, 3.0] + and processed = [0.1, 0.2, 0.3] + and scale = 1.0 + when result = apply_residual_connection(x, processed, scale) + then approximately_equal(result[0], 1.1) + and approximately_equal(result[1], 2.2) + and approximately_equal(result[2], 3.3) + + test phi_scaled_residual_scales_processed + given x = [1.0, 2.0, 3.0] + and processed = [1.0, 1.0, 1.0] + when result = phi_scaled_residual(x, processed) + then approximately_equal(result[0], 1.0 + PHI_RESIDUAL_SCALE) + and approximately_equal(result[1], 2.0 + PHI_RESIDUAL_SCALE) + + test phi_residual_scale_less_than_one + then PHI_RESIDUAL_SCALE < 1.0 + + test get_parameter_count_positive + given config = any_block_config() + when result = get_parameter_count(config) + then result > 0 + + test get_parameter_count_matches_components + given config = BlockConfig{.hidden_size = 128, .num_heads = 8, .expansion_factor = 4.0} + when total = get_parameter_count(config) + and mha_params = 4 * 128 * 128 + 4 * 128 // Q,K,V,O + biases + and ffn_params = 2 * 128 * 512 + 2 * 512 // W1,W2 + biases + and norm_params = 2 * 2 * 128 // 2 Norms, gamma+beta each + then total == mha_params + ffn_params + norm_params + + test get_flops_scales_quadratically_with_seq_len + given config = any_block_config() + when flops_128 = get_flops(config, 128) + and flops_256 = get_flops(config, 256) + then flops_256 > flops_128 * 2 // More than 2x due to quadratic attention + + test forward_preserves_gradients_flow + given config = BlockConfig{.hidden_size = 32, .num_heads = 2} + and state = init(config) + and input = random_input(32) + and forward_result = forward(state, input) + and grad_output = random_input(32) + when grads = backward(state, forward_result, grad_output) + then grads.d_input.len == 32 + + test backward_gradients_match_component_shapes + given config = BlockConfig{.hidden_size = 32, .num_heads = 2} + and state = init(config) + and input = random_input(32) + and forward_result = forward(state, input) + and grad_output = random_input(32) + when grads = backward(state, forward_result, grad_output) + then grads.mha_grads.d_w_q.len == state.mha_state.w_q.len + and grads.ffn_grads.d_w1.len == state.ffn_state.w1.len + + test create_residual_connection_with_scale + given scale = 0.5 + when result = create_residual_connection(scale) + then result.scale == 0.5 + and result.use_projection == false + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants + // ═══════════════════════════════════════════════════════════ + + invariant forward_output_shape_invariant + given config = any_block_config() + and state = init(config) + and input = random_input(config.hidden_size) + when result = forward(state, input) + then result.output.len == input.len + + invariant residual_connection_preserves_dimension + given x = random_input(100) + and processed = random_input(100) + and scale = positive_gf16() + when result = apply_residual_connection(x, processed, scale) + then result.len == x.len + + invariant phi_residual_scale_positive + then PHI_RESIDUAL_SCALE > 0.0 + + invariant phi_residual_scale_less_than_one + then PHI_RESIDUAL_SCALE < 1.0 + + invariant parameter_count_grows_with_hidden_size + given config1 = BlockConfig{.hidden_size = 64, .num_heads = 4} + and config2 = BlockConfig{.hidden_size = 128, .num_heads = 8} + when params1 = get_parameter_count(config1) + and params2 = get_parameter_count(config2) + then params2 > params1 + + invariant flops_quadratic_in_sequence_length + given config = any_block_config() + and seq_len1 = 64 + and seq_len2 = 128 + when flops1 = get_flops(config, seq_len1) + and flops2 = get_flops(config, seq_len2) + // Attention is O(seq_len^2) + then flops2 > flops1 * 1.5 + + invariant forward_output_finite + given config = any_block_config() + and state = init(config) + and input = finite_input(config.hidden_size) + when result = forward(state, input) + then all(result.output, is_finite) + + invariant backward_gradients_finite + given config = any_block_config() + and state = init(config) + and input = finite_input(config.hidden_size) + and forward_result = forward(state, input) + and grad_output = finite_input(config.hidden_size) + when grads = backward(state, forward_result, grad_output) + then all(grads.d_input, is_finite) + + // ═══════════════════════════════════════════════════════════ + // TDD: Benchmarks + // ═══════════════════════════════════════════════════════════ + + bench init_small + given config = BlockConfig{.hidden_size = 128, .num_heads = 4} + when result = init(config) + then elapsed_time_ms < 20 + + bench init_medium + given config = BlockConfig{.hidden_size = 512, .num_heads = 8} + when result = init(config) + then elapsed_time_ms < 100 + + bench init_large + given config = BlockConfig{.hidden_size = 2048, .num_heads = 16} + when result = init(config) + then elapsed_time_ms < 500 + + bench forward_small_short_sequence + given config = BlockConfig{.hidden_size = 128, .num_heads = 4} + and state = init(config) + and input = random_input(128) + when result = forward(state, input) + then elapsed_time_ms < 5 + + bench forward_medium_medium_sequence + given config = BlockConfig{.hidden_size = 512, .num_heads = 8} + and state = init(config) + and input = random_input(512 * 32) // 32 tokens + when result = forward(state, input) + then elapsed_time_ms < 50 + + bench forward_large_long_sequence + given config = BlockConfig{.hidden_size = 2048, .num_heads = 16} + and state = init(config) + and input = random_input(2048 * 128) // 128 tokens + when result = forward(state, input) + then elapsed_time_ms < 500 + + bench backward_small + given config = BlockConfig{.hidden_size = 128, .num_heads = 4} + and state = init(config) + and input = random_input(128) + and forward_result = forward(state, input) + and grad_output = random_input(128) + when result = backward(state, forward_result, grad_output) + then elapsed_time_ms < 10 + + bench apply_residual_connection + given x = random_input(1000) + and processed = random_input(1000) + and scale = 1.0 + when result = apply_residual_connection(x, processed, scale) + then elapsed_time_us < 100 + + // ═══════════════════════════════════════════════════════════ + // Mathematical Notes + // ═══════════════════════════════════════════════════════════ + // + // Transformer Encoder Block (Pre-LN): + // x' = LN(x) + // x'' = MHA(x') + x + // x''' = LN(x'') + // output = FFN(x''') + x'' + // + // Where: + // MHA(x) = MultiHeadAttention(x) + // FFN(x) = Dropout(Act(xW_1 + b_1))W_2 + b_2 + // + // Φ-Optimized Residual: + // x'' = x + MHA(x') * (1/φ) + // Reduces gradient flow, improves training stability + // + // Parameter Count (per layer): + // MHA: 4 * d_model^2 (Q, K, V, O projections) + // FFN: 2 * d_model * d_ff + d_model + d_ff + // Norm: 2 * 2 * d_model (gamma + beta) + // Total: ~4 * d_model^2 + 4 * d_model * d_ff + // + // FLOPs (per token, per layer): + // MHA: 4 * d_model * d_k * seq_len (dominates for long sequences) + // FFN: 4 * d_model * d_ff + // Norm: 4 * d_model + // Total: O(d_model^2 + d_model * d_k * seq_len) + // + // ═══════════════════════════════════════════════════════════ diff --git a/apps/website/public/t27/specs/ml/transformer/multi_head_attention.t27 b/apps/website/public/t27/specs/ml/transformer/multi_head_attention.t27 new file mode 100644 index 0000000000..ea0b2547f8 --- /dev/null +++ b/apps/website/public/t27/specs/ml/transformer/multi_head_attention.t27 @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Multiple heads compute independently in parallel | φ² + 1/φ² = 3 | TRINITY + +module MultiHeadAttn; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const MHAConfig = struct { + d_model : u32, + num_heads : u32, + d_k : u32, + causal : bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(query: []const f32, key: []const f32, value: []const f32, output: []f32, config: MHAConfig) → void + fn forward(query: []const f32, key: []const f32, value: []const f32, output: []f32, config: MHAConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test forward_basic_case + given input = default_input() + when result = forward(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/ml/transformer/multi_head_attn.t27 b/apps/website/public/t27/specs/ml/transformer/multi_head_attn.t27 new file mode 100644 index 0000000000..bfe5f24e99 --- /dev/null +++ b/apps/website/public/t27/specs/ml/transformer/multi_head_attn.t27 @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ml/transformer/multi_head_attn.t27 +// Multi-Head Attention with φ-optimized head dimensions | φ² + 1/φ² = 3 | TRINITY + +module MultiHeadAttention; + use base::types; + use math::constants; + use math::statistics; + use numeric::gf16; + use ml::transformer::positional_enc; + use ml::transformer::norm; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const PHI : gf16::GF16 = 1.618033988749895; + const INV_PHI : gf16::GF16 = 1.0 / PHI; + const PHI_SQUARED : gf16::GF16 = PHI * PHI; + const DEFAULT_DROPOUT : gf16::GF16 = 0.1; + const DEFAULT_SCALE : gf16::GF16 = 1.0 / sqrt(PHI); // φ-optimized scaling + const PHI_HEAD_DIM_DIVISOR : u32 = 8; // Head dim must be divisible by 8 for φ-optimization + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const MHAConfig = struct { + hidden_size : u32, // d_model: total hidden dimension + num_heads : u32, // h: number of attention heads + head_dim : u32, // d_k = d_v: dimension per head (hidden_size / num_heads) + dropout : gf16::GF16, // Dropout probability + use_flash_attention : bool, // Use Flash Attention algorithm + use_rope : bool, // Apply Rotary Position Embedding + use_phi_scaling : bool, // Use φ-optimized attention scaling + causal : bool, // Causal mask (for decoder) + }; + + pub const MHAState = struct { + w_q : []gf16::GF16, // Query projection weights: [hidden_size, hidden_size] + w_k : []gf16::GF16, // Key projection weights: [hidden_size, hidden_size] + w_v : []gf16::GF16, // Value projection weights: [hidden_size, hidden_size] + w_o : []gf16::GF16, // Output projection weights: [hidden_size, hidden_size] + b_q : []gf16::GF16, // Query bias: [hidden_size] + b_k : []gf16::GF16, // Key bias: [hidden_size] + b_v : []gf16::GF16, // Value bias: [hidden_size] + b_o : []gf16::GF16, // Output bias: [hidden_size] + rope_state : RoPEState, // RoPE state (if use_rope) + norm_state : NormState, // LayerNorm state (for Pre-LN) + is_training : bool, // Training/inference mode + }; + + pub const AttentionMask = struct { + mask : []gf16::GF16, // Attention mask values + shape : [2]u32, // [seq_len, seq_len] or [batch, seq_len, seq_len] + is_causal : bool, // Causal mask flag + }; + + pub const MHAForwardResult = struct { + output : []gf16::GF16, // Attention output: [batch, seq_len, hidden_size] + attention_weights : []gf16::GF16, // Attention scores: [batch, num_heads, seq_len, seq_len] + dropout_mask : []gf16::GF16, // Applied dropout mask + }; + + pub const MHAGradients = struct { + d_w_q : []gf16::GF16, // Gradient wrt w_q + d_w_k : []gf16::GF16, // Gradient wrt w_k + d_w_v : []gf16::GF16, // Gradient wrt w_v + d_w_o : []gf16::GF16, // Gradient wrt w_o + d_b_q : []gf16::GF16, // Gradient wrt b_q + d_b_k : []gf16::GF16, // Gradient wrt b_k + d_b_v : []gf16::GF16, // Gradient wrt b_v + d_b_o : []gf16::GF16, // Gradient wrt b_o + d_input : []gf16::GF16, // Gradient wrt input + }; + + pub const FlashAttentionState = struct { + q_tile : []gf16::GF16, // Tiled Q for Flash Attention + k_tile : []gf16::GF16, // Tiled K for Flash Attention + v_tile : []gf16::GF16, // Tiled V for Flash Attention + o_tile : []gf16::GF16, // Output tile + l_tile : []gf16::GF16, // Logsumexp for each query + m_tile : []gf16::GF16, // Max for each query + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(config: MHAConfig) → MHAState + // Initializes MHA with φ-optimized weight distribution. + fn init(config: MHAConfig) -> MHAState { + // head_dim = hidden_size / num_heads + // Initialize weights with Xavier/Glorot scaled by INV_PHI + // If use_rope: init RoPEState with head_dim + // If Pre-LN: init NormState with hidden_size + } + + // forward(state: MHAState, input: []gf16::GF16, mask: AttentionMask) → MHAForwardResult + // Computes multi-head attention forward pass. + fn forward(state: MHAState, input: []gf16::GF16, mask: AttentionMask) -> MHAForwardResult { + // 1. Pre-LN (if enabled): x = LayerNorm(input) + // 2. Project: Q = x @ W_q, K = x @ W_k, V = x @ W_v + // 3. Reshape: Q,K,V → [batch, heads, seq_len, head_dim] + // 4. Apply RoPE (if use_rope): Q, K = RoPE(Q, K, positions) + // 5. Scale: Q = Q / sqrt(d_k) or Q * scale if use_phi_scaling + // 6. Attention: A = softmax(Q @ K.T + mask) + // 7. Dropout (if training): A = dropout(A) + // 8. Output: O = A @ V + // 9. Reshape: O → [batch, seq_len, hidden_size] + // 10. Project: O = O @ W_o + // 11. Residual (if Pre-LN): O = O + input + } + + // flash_attention_forward(state: MHAState, input: []gf16::GF16, mask: AttentionMask) → MHAForwardResult + // Flash Attention algorithm: O(N²) → O(N) memory. + fn flash_attention_forward(state: MHAState, input: []gf16::GF16, mask: AttentionMask) -> MHAForwardResult { + // Tiled computation of attention: + // For each query tile: + // For each key-value tile: + // Compute S = Q @ K.T + // Update m = max(m, max(S)) + // Update l = logsumexp(S) + l + // Update O = O @ V with scaling + // Returns O without storing full attention matrix + } + + // compute_attention_scores(q: []gf16::GF16, k: []gf16::GF16, scale: gf16::GF16) → []gf16::GF16 + // Computes scaled dot-product attention scores. + fn compute_attention_scores(q: []gf16::GF16, k: []gf16::GF16, scale: gf16::GF16) -> []gf16::GF16 { + // scores = q @ k.T + // scores = scores * scale + } + + // apply_causal_mask(scores: []gf16::GF16, seq_len: u32) → []gf16::GF16 + // Applies causal (autoregressive) mask to attention scores. + fn apply_causal_mask(scores: []gf16::GF16, seq_len: u32) -> []gf16::GF16 { + // For i in seq_len, j in seq_len: + // if j > i: scores[i,j] = -inf + } + + // apply_attention_mask(scores: []gf16::GF16, mask: AttentionMask) → []gf16::GF16 + // Applies custom attention mask. + fn apply_attention_mask(scores: []gf16::GF16, mask: AttentionMask) -> []gf16::GF16 { + // scores = scores + mask (mask has -inf for masked positions) + } + + // softmax_attention(scores: []gf16::GF16, dim: u32) → []gf16::GF16 + // Computes softmax over specified dimension. + fn softmax_attention(scores: []gf16::GF16, dim: u32) -> []gf16::GF16 { + // exp_scores = exp(scores - max(scores, dim)) + // softmax = exp_scores / sum(exp_scores, dim) + } + + // backward(state: MHAState, result: MHAForwardResult, grad_output: []gf16::GF16) → MHAGradients + // Computes gradients for backpropagation. + fn backward(state: MHAState, result: MHAForwardResult, grad_output: []gf16::GF16) -> MHAGradients { + // d_O = grad_output + // d_w_o = O.T @ d_O + // d_A = d_O @ V.T (before dropout) + // d_A = d_A * dropout_mask (if training) + // d_A = softmax_backward(d_A, attention_weights) + // d_Q = d_A @ K + // d_K = d_A.T @ Q + // d_V = attention_weights.T @ d_O + // d_input = d_Q @ W_q.T + d_K @ W_k.T + d_V @ W_v.T + d_O @ W_o.T + // Add residual gradient if Pre-LN + } + + // compute_phi_scale(head_dim: u32) → gf16::GF16 + // Computes φ-optimized attention scaling factor. + fn compute_phi_scale(head_dim: u32) -> gf16::GF16 { + // Standard: 1.0 / sqrt(head_dim) + // φ-optimized: 1.0 / (sqrt(head_dim) * PHI) + return 1.0 / (sqrt(head_dim as gf16::GF16) * PHI); + } + + // merge_heads(attention: []gf16::GF16, num_heads: u32, head_dim: u32) → []gf16::GF16 + // Merges multi-head outputs back to hidden dimension. + fn merge_heads(attention: []gf16::GF16, num_heads: u32, head_dim: u32) -> []gf16::GF16 { + // Reshape from [batch, heads, seq, head_dim] to [batch, seq, hidden] + } + + // split_heads(x: []gf16::GF16, num_heads: u32, head_dim: u32) → []gf16::GF16 + // Splits hidden dimension into multiple attention heads. + fn split_heads(x: []gf16::GF16, num_heads: u32, head_dim: u32) -> []gf16::GF16 { + // Reshape from [batch, seq, hidden] to [batch, heads, seq, head_dim] + } + + // create_causal_mask(seq_len: u32) → AttentionMask + // Creates a causal (lower-triangular) mask. + fn create_causal_mask(seq_len: u32) -> AttentionMask { + // mask[i,j] = 0 if j <= i else -inf + } + + // get_head_dim(config: MHAConfig) → u32 + // Returns dimension per attention head. + fn get_head_dim(config: MHAConfig) -> u32 { + // return config.hidden_size / config.num_heads + } + + // get_parameter_count(config: MHAConfig) → u64 + // Returns total number of trainable parameters. + fn get_parameter_count(config: MHAConfig) -> u64 { + // params = 4 * hidden_size^2 (W_q, W_k, W_v, W_o) + // if use_bias: params += 4 * hidden_size + // return params + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests + // ═══════════════════════════════════════════════════════════ + + test init_creates_correct_weight_dimensions + given config = MHAConfig{.hidden_size = 128, .num_heads = 8, .dropout = 0.1, .use_flash_attention = false, .use_rope = false, .use_phi_scaling = false, .causal = false} + when state = init(config) + then state.w_q.len == 128 * 128 + and state.w_k.len == 128 * 128 + and state.w_v.len == 128 * 128 + and state.w_o.len == 128 * 128 + + test init_with_bias_creates_bias_vectors + given config = MHAConfig{.hidden_size = 128, .num_heads = 8, .dropout = 0.1} + and state = init(config) + then state.b_q.len == 128 + and state.b_k.len == 128 + and state.b_v.len == 128 + and state.b_o.len == 128 + + test get_head_dim_divides_evenly + given config = MHAConfig{.hidden_size = 128, .num_heads = 8} + when result = get_head_dim(config) + then result == 16 + and config.hidden_size % config.num_heads == 0 + + test get_parameter_count_correct + given config = MHAConfig{.hidden_size = 128, .num_heads = 8, .dropout = 0.1, .use_flash_attention = false, .use_rope = false, .use_phi_scaling = false, .causal = false} + when result = get_parameter_count(config) + then result == 4 * 128 * 128 + 4 * 128 // Weights + biases + + test forward_output_shape_matches_input + given config = MHAConfig{.hidden_size = 64, .num_heads = 4} + and state = init(config) + and input = random_input(64) + and mask = create_causal_mask(1) + when result = forward(state, input, mask) + then result.output.len == 64 + + test split_heads_correct_reshaping + given input = random_input(96) // 3 tokens, 32 hidden + when result = split_heads(input, 4, 8) // 4 heads, 8 dim each + then result.len == 96 + + test merge_heads_reverses_split + given input = random_input(96) + and split_result = split_heads(input, 4, 8) + when merged = merge_heads(split_result, 4, 8) + then merged.len == input.len + + test compute_attention_scores_returns_correct_shape + given q = random_input(4 * 8) // 4 queries, 8 dim + and k = random_input(6 * 8) // 6 keys, 8 dim + and scale = 0.125 // 1/sqrt(64) + when result = compute_attention_scores(q, k, scale) + then result.len == 4 * 6 + + test apply_causal_mask_blocks_future + given seq_len = 4 + and scores = [1.0] * 16 // 4x4 matrix of ones + when result = apply_causal_mask(scores, seq_len) + then result[0] == 1.0 // (0,0): can attend + and result[1] == 1.0 // (0,1): can attend + and result[4] == 1.0 // (1,0): can attend + and result[7] == -inf // (1,3): cannot attend + + test softmax_sums_to_one + given scores = [1.0, 2.0, 3.0] + when result = softmax_attention(scores, 0) + then approximately_equal_within(sum(result), 1.0, 1e-4) + + test phi_scale_smaller_than_standard + given head_dim = 64 + when standard_scale = 1.0 / sqrt(head_dim as gf16::GF16) + and phi_scale = compute_phi_scale(head_dim) + then phi_scale < standard_scale + + test compute_phi_scale_formula + given head_dim = 64 + when result = compute_phi_scale(head_dim) + and expected = 1.0 / (sqrt(64.0) * PHI) + then approximately_equal_within(result, expected, 1e-6) + + test flash_attention_no_memory_explosion + given config = MHAConfig{.hidden_size = 128, .num_heads = 8, .use_flash_attention = true} + and state = init(config) + and input = random_input(128) + and mask = create_causal_mask(1) + when result = flash_attention_forward(state, input, mask) + then result.output.len == 128 + and result.attention_weights.len == 0 // Flash doesn't store full matrix + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants + // ═══════════════════════════════════════════════════════════ + + invariant head_dim_divisible_by_phi + given config = any_mha_config() + when head_dim = get_head_dim(config) + then head_dim % PHI_HEAD_DIM_DIVISOR == 0 + + invariant forward_preserves_batch_sequence_shape + given config = any_mha_config() + and state = init(config) + and input = random_input(config.hidden_size) + and mask = create_causal_mask(1) + when result = forward(state, input, mask) + then result.output.len == input.len + + invariant attention_weights_probabilities + given config = any_mha_config() + and state = init(config) + and input = random_input(config.hidden_size) + and mask = create_causal_mask(1) + and result = forward(state, input, mask) + then all(result.attention_weights, fn(x) x >= 0.0 and x <= 1.0) + + invariant causal_mask_prevents_future_attention + given seq_len = 10 + and mask = create_causal_mask(seq_len) + when mask_matrix = mask.mask + then for i in 0..seq_len-1: + for j in i+1..seq_len-1: + mask_matrix[i*seq_len + j] == -inf + + invariant phi_scale_positive + given head_dim = positive_u32() + when scale = compute_phi_scale(head_dim) + then scale > 0.0 + + invariant parameter_count_quartic_in_hidden_size + given config = any_mha_config() + when params = get_parameter_count(config) + // params ≈ 4 * hidden_size^2 + and params > config.hidden_size * config.hidden_size + + invariant backward_gradients_match_shapes + given config = any_mha_config() + and state = init(config) + and input = random_input(config.hidden_size) + and mask = create_causal_mask(1) + and forward_result = forward(state, input, mask) + and grad_output = random_input(config.hidden_size) + when grads = backward(state, forward_result, grad_output) + then grads.d_w_q.len == state.w_q.len + and grads.d_w_k.len == state.w_k.len + and grads.d_w_v.len == state.w_v.len + and grads.d_w_o.len == state.w_o.len + + invariant split_and_merge_roundtrip + given input = random_input(128) // Must be divisible by num_heads * head_dim + and num_heads = 8 + and head_dim = 4 + when split = split_heads(input, num_heads, head_dim) + and merged = merge_heads(split, num_heads, head_dim) + then all_equal(input, merged) + + // ═══════════════════════════════════════════════════════════ + // TDD: Benchmarks + // ═══════════════════════════════════════════════════════════ + + bench init_small + given config = MHAConfig{.hidden_size = 128, .num_heads = 4} + when result = init(config) + then elapsed_time_ms < 10 + + bench init_medium + given config = MHAConfig{.hidden_size = 512, .num_heads = 8} + when result = init(config) + then elapsed_time_ms < 50 + + bench init_large + given config = MHAConfig{.hidden_size = 2048, .num_heads = 16} + when result = init(config) + then elapsed_time_ms < 200 + + bench forward_small + given config = MHAConfig{.hidden_size = 128, .num_heads = 4} + and state = init(config) + and input = random_input(128) + and mask = create_causal_mask(1) + when result = forward(state, input, mask) + then elapsed_time_ms < 1 + + bench forward_medium + given config = MHAConfig{.hidden_size = 512, .num_heads = 8} + and state = init(config) + and input = random_input(512) + and mask = create_causal_mask(1) + when result = forward(state, input, mask) + then elapsed_time_ms < 10 + + bench forward_large + given config = MHAConfig{.hidden_size = 2048, .num_heads = 16} + and state = init(config) + and input = random_input(2048) + and mask = create_causal_mask(1) + when result = forward(state, input, mask) + then elapsed_time_ms < 100 + + bench flash_attention_medium + given config = MHAConfig{.hidden_size = 512, .num_heads = 8, .use_flash_attention = true} + and state = init(config) + and input = random_input(512) + and mask = create_causal_mask(1) + when result = flash_attention_forward(state, input, mask) + then elapsed_time_ms < 10 + + bench compute_attention_scores + given q = random_input(64 * 64) // 64 queries, 64 dim + and k = random_input(128 * 64) // 128 keys, 64 dim + and scale = compute_phi_scale(64) + when result = compute_attention_scores(q, k, scale) + then elapsed_time_ms < 5 + + bench softmax_attention + given scores = random_input(1024) + when result = softmax_attention(scores, 0) + then elapsed_time_ms < 1 + + // ═══════════════════════════════════════════════════════════ + // Mathematical Notes + // ═══════════════════════════════════════════════════════════ + // + // Multi-Head Attention: + // Q = XW_q, K = XW_k, V = XW_v + // Attention(Q, K, V) = softmax(QK^T / √d_k) V + // MultiHead(Q, K, V) = Concat(head_1, ..., head_h) W_o + // where head_i = Attention(QW_q^i, KW_k^i, VW_v^i) + // + // Dimensions: + // X: [batch, seq_len, d_model] + // W_q, W_k, W_v: [d_model, d_model] + // W_o: [d_model, d_model] + // Q, K, V: [batch, seq_len, d_model] + // After split: [batch, num_heads, seq_len, head_dim] + // Attention: [batch, num_heads, seq_len, seq_len] + // Output: [batch, seq_len, d_model] + // + // Φ-Optimized Scaling: + // Standard: scale = 1/√d_k + // Φ-optimized: scale = 1/(√d_k × φ) + // Reduces attention variance, improves training stability + // + // Flash Attention (IO-Aware): + // Computes attention without materializing the full N×N matrix + // Memory: O(N) instead of O(N²) + // Tiling: processes queries and keys in blocks + // + // Causal Mask: + // For autoregressive decoding: mask[i,j] = -∞ if j > i + // Prevents attending to future tokens + // + // ═══════════════════════════════════════════════════════════ diff --git a/apps/website/public/t27/specs/ml/transformer/norm.t27 b/apps/website/public/t27/specs/ml/transformer/norm.t27 new file mode 100644 index 0000000000..3e11eeb902 --- /dev/null +++ b/apps/website/public/t27/specs/ml/transformer/norm.t27 @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ml/transformer/norm.t27 +// Layer Normalization with φ-optimized β/γ | φ² + 1/φ² = 3 | TRINITY + +module LayerNorm; + use base::types; + use math::constants; + use math::statistics; + use numeric::gf16; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + // Phi-based constants for numerical stability + const PHI : gf16::GF16 = 1.618033988749895; + const INV_PHI_SQUARED : gf16::GF16 = 1.0 / (PHI * PHI); // ≈ 0.381966 + const PHI_EPSILON : gf16::GF16 = 1e-5 / PHI; // ≈ 6.18e-6, phi-optimized epsilon + + // Default epsilon for numerical stability + const DEFAULT_EPSILON : gf16::GF16 = 1e-5; + + // Strand partition constants + const STRAND_I_START : u32 = 0; + const STRAND_I_END : u32 = 64; + const STRAND_II_START : u32 = 64; + const STRAND_II_END : u32 = 128; + const STRAND_III_START : u32 = 128; + const STRAND_III_END : u32 = 192; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const NormConfig = struct { + hidden_size : u32, // Size of the hidden dimension + eps : f32, // Epsilon for numerical stability + elementwise_affine : bool, // Whether to use learnable β and γ + use_phi_init : bool, // Whether to use phi-based initialization + strand_mode : bool, // Enable strand partitioning + }; + + pub const NormState = struct { + gamma : []gf16::GF16, // Learnable scale parameter (γ) + beta : []gf16::GF16, // Learnable shift parameter (β) + mean : []gf16::GF16, // Running mean (for inference) + variance : []gf16::GF16, // Running variance (for inference) + is_training : bool, // Training/inference mode flag + momentum : f32, // Momentum for running statistics + }; + + pub const NormGradients = struct { + d_gamma : []gf16::GF16, // Gradient wrt γ + d_beta : []gf16::GF16, // Gradient wrt β + d_input : []gf16::GF16, // Gradient wrt input + }; + + pub const Strand = struct { + id : u32, // Strand ID: 0 (I), 1 (II), 2 (III) + start : u32, // Start index in hidden dimension + end : u32, // End index in hidden dimension + gamma : []gf16::GF16, // Strand-local γ + beta : []gf16::GF16, // Strand-local β + phi_gamma_offset : f32, // Phi-based offset for this strand + phi_beta_offset : f32, // Phi-based offset for this strand + }; + + pub const ForwardResult = struct { + output : []gf16::GF16, // Normalized output + mean : f32, // Computed mean (for backward) + variance : f32, // Computed variance (for backward) + centered : []gf16::GF16, // Input - mean (for backward) + normalized : []gf16::GF16, // Centered / sqrt(var + eps) (for backward) + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(config: NormConfig) → NormState + // Initializes layer norm state with phi-optimized parameters. + fn init(config: NormConfig) -> NormState { + // If use_phi_init: + // gamma[i] = 1.0 + (i % 3) * INV_PHI_SQUARED * 0.1 + // beta[i] = (i % 3) * PHI * 0.01 + // else: + // gamma[i] = 1.0 + // beta[i] = 0.0 + // + // If strand_mode: partition gamma/beta into 3 strands + } + + // forward(state: NormState, input: []gf16::GF16) → ForwardResult + // Computes layer normalization: output = γ * (x - μ) / √(σ² + ε) + β + fn forward(state: NormState, input: []gf16::GF16) -> ForwardResult { + // 1. Compute mean: μ = (1/n) * Σ x_i + // 2. Compute variance: σ² = (1/n) * Σ (x_i - μ)² + // 3. Normalize: x̂ = (x - μ) / √(σ² + ε) + // 4. Scale and shift: y = γ * x̂ + β + // + // Strand mode: process each strand independently with strand-local params + // Save intermediate values (mean, variance, centered, normalized) for backward + } + + // forward_strand(strand: Strand, input: []gf16::GF16) → ForwardResult + // Strand-local forward pass for parallel processing. + fn forward_strand(strand: Strand, input: []gf16::GF16) -> ForwardResult { + // Same as forward but operates on strand subset [start:end) + // Applies phi_gamma_offset and phi_beta_offset + } + + // backward(state: NormState, result: ForwardResult, grad_output: []gf16::GF16) → NormGradients + // Computes gradients for backpropagation. + fn backward(state: NormState, result: ForwardResult, grad_output: []gf16::GF16) -> NormGradients { + // d_γ = Σ (grad_output * normalized) + // d_β = Σ grad_output + // d_x = (1/n) * γ / √(σ²+ε) * (n * grad_output - Σgrad_output - x̂ * Σ(grad_output * x̂)) + } + + // backward_strand(strand: Strand, result: ForwardResult, grad_output: []gf16::GF16) → NormGradients + // Strand-local backward pass. + fn backward_strand(strand: Strand, result: ForwardResult, grad_output: []gf16::GF16) -> NormGradients { + // Same as backward but operates on strand subset + } + + // update(state: NormState, grads: NormGradients, lr: gf16::GF16) → NormState + // Updates learnable parameters using computed gradients. + fn update(state: NormState, grads: NormGradients, lr: gf16::GF16) -> NormState { + // gamma = gamma - lr * d_gamma + // beta = beta - lr * d_beta + } + + // normalize_vector(x: []gf16::GF16, eps: gf16::GF16) → []gf16::GF16 + // Utility function to normalize a vector without affine transform. + fn normalize_vector(x: []gf16::GF16, eps: gf16::GF16) -> []gf16::GF16 { + // Returns (x - μ) / √(σ² + eps) + } + + // ═══════════════════════════════════════════════════════════ + // 4. Phi-Optimized Functions + // ═══════════════════════════════════════════════════════════ + + // phi_init_gamma(hidden_size: u32) → []gf16::GF16 + // Initialize γ with phi-based pattern. + fn phi_init_gamma(hidden_size: u32) -> []gf16::GF16 { + // gamma[i] = 1.0 + ((i % 3) * INV_PHI_SQUARED * 0.1) + // Creates subtle variation across strands: [1.0, 1.038, 1.076, 1.0, ...] + } + + // phi_init_beta(hidden_size: u32) → []gf16::GF16 + // Initialize β with phi-based pattern. + fn phi_init_beta(hidden_size: u32) -> []gf16::GF16 { + // beta[i] = ((i % 3) * PHI * 0.01) + // Creates pattern: [0.0, 0.0162, 0.0324, 0.0, ...] + } + + // phi_normalize(x: gf16::GF16, mean: gf16::GF16, var: gf16::GF16) -> gf16::GF16 + // Phi-optimized normalization with reduced numerical error. + fn phi_normalize(x: gf16::GF16, mean: gf16::GF16, var: gf16::GF16) -> gf16::GF16 { + // Uses PHI_EPSILON instead of default + // Returns (x - mean) / sqrt(var + PHI_EPSILON) + } + + // create_strands(hidden_size: u32, gamma: []gf16::GF16, beta: []gf16::GF16) → []Strand + // Create 3 strands for parallel processing. + fn create_strands(hidden_size: u32, gamma: []gf16::GF16, beta: []gf16::GF16) -> []Strand { + // Strand I: [0, hidden_size/3) + // Strand II: [hidden_size/3, 2*hidden_size/3) + // Strand III: [2*hidden_size/3, hidden_size) + // + // Each strand gets phi_gamma_offset and phi_beta_offset: + // offset = strand_id * INV_PHI_SQUARED * 0.1 + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests + // ═══════════════════════════════════════════════════════════ + + test init_creates_correct_dimensions + given config = NormConfig{.hidden_size = 128, .eps = 1e-5, .elementwise_affine = true, .use_phi_init = false, .strand_mode = false} + when state = init(config) + then state.gamma.len == 128 + and state.beta.len == 128 + + test init_with_phi_init_creates_patterned_gamma + given config = NormConfig{.hidden_size = 9, .eps = 1e-5, .elementwise_affine = true, .use_phi_init = true, .strand_mode = false} + when state = init(config) + then approximately_equal(state.gamma[0], 1.0) + and approximately_equal(state.gamma[1], 1.0 + INV_PHI_SQUARED * 0.1) + and approximately_equal(state.gamma[2], 1.0 + 2.0 * INV_PHI_SQUARED * 0.1) + + test init_with_phi_init_creates_patterned_beta + given config = NormConfig{.hidden_size = 9, .eps = 1e-5, .elementwise_affine = true, .use_phi_init = true, .strand_mode = false} + when state = init(config) + then approximately_equal(state.beta[0], 0.0) + and approximately_equal(state.beta[1], PHI * 0.01) + and approximately_equal(state.beta[2], 2.0 * PHI * 0.01) + + test forward_normalizes_to_unit_variance + given config = NormConfig{.hidden_size = 4, .eps = 1e-5, .elementwise_affine = false, .use_phi_init = false, .strand_mode = false} + and state = init(config) + and input = [1.0, 2.0, 3.0, 4.0] + when result = forward(state, input) + then approximately_equal_within(compute_variance(result.normalized), 1.0, 1e-4) + + test forward_centers_to_zero_mean + given config = NormConfig{.hidden_size = 4, .eps = 1e-5, .elementwise_affine = false, .use_phi_init = false, .strand_mode = false} + and state = init(config) + and input = [1.0, 2.0, 3.0, 4.0] + when result = forward(state, input) + then approximately_equal_within(compute_mean(result.normalized), 0.0, 1e-6) + + test forward_with_affine_applies_gamma_and_beta + given config = NormConfig{.hidden_size = 2, .eps = 1e-5, .elementwise_affine = true, .use_phi_init = false, .strand_mode = false} + and state = init(config) + and state.gamma[0] = 2.0 + and state.gamma[1] = 3.0 + and state.beta[0] = 1.0 + and state.beta[1] = -1.0 + and input = [0.0, 0.0] + when result = forward(state, input) + then approximately_equal(result.output[0], 1.0) // 2.0 * 0 + 1.0 + and approximately_equal(result.output[1], -1.0) // 3.0 * 0 - 1.0 + + test forward_with_constant_input + given config = NormConfig{.hidden_size = 4, .eps = 1e-5, .elementwise_affine = false, .use_phi_init = false, .strand_mode = false} + and state = init(config) + and input = [5.0, 5.0, 5.0, 5.0] + when result = forward(state, input) + then result.variance == 0.0 + and result.mean == 5.0 + and all(result.normalized, is_zero) + + test backward_computes_correct_gradients + given config = NormConfig{.hidden_size = 2, .eps = 1e-5, .elementwise_affine = true, .use_phi_init = false, .strand_mode = false} + and state = init(config) + and input = [1.0, -1.0] + and forward_result = forward(state, input) + and grad_output = [1.0, 1.0] + when grads = backward(state, forward_result, grad_output) + then grads.d_beta.len == 2 + and grads.d_gamma.len == 2 + and grads.d_input.len == 2 + + test backward_with_affine_false_has_zero_parameter_gradients + given config = NormConfig{.hidden_size = 4, .eps = 1e-5, .elementwise_affine = false, .use_phi_init = false, .strand_mode = false} + and state = init(config) + and input = [1.0, 2.0, 3.0, 4.0] + and forward_result = forward(state, input) + and grad_output = [1.0, 1.0, 1.0, 1.0] + when grads = backward(state, forward_result, grad_output) + then all(grads.d_gamma, is_zero) + and all(grads.d_beta, is_zero) + + test strand_forward_processes_correct_range + given config = NormConfig{.hidden_size = 192, .eps = 1e-5, .elementwise_affine = true, .use_phi_init = true, .strand_mode = true} + and state = init(config) + and input = create_test_input(192) + and strands = create_strands(192, state.gamma, state.beta) + when result = forward_strand(strands[0], input) + then result.output.len == 64 + and strands[0].id == 0 + + test strand_mode_equivalent_to_standard + given config = NormConfig{.hidden_size = 192, .eps = 1e-5, .elementwise_affine = true, .use_phi_init = false, .strand_mode = false} + and state = init(config) + and input = create_test_input(192) + when standard = forward(state, input) + and strand_config = config + and strand_config.strand_mode = true + and strand_state = init(strand_config) + and strands = create_strands(192, strand_state.gamma, strand_state.beta) + and strand_result = combine_strand_results(forward_strand(strands[0], input), + forward_strand(strands[1], input), + forward_strand(strands[2], input)) + then outputs_equal_within_tolerance(standard.output, strand_result, 1e-4) + + test phi_normalize_reduces_numerical_error + given x = 1e6 + and mean = 1e6 + and var = 1e-10 + when standard = (x - mean) / sqrt(var + 1e-5) + and phi_result = phi_normalize(x, mean, var) + then is_finite(phi_result) + and phi_result != standard + + test update_modifies_parameters_correctly + given config = NormConfig{.hidden_size = 2, .eps = 1e-5, .elementwise_affine = true, .use_phi_init = false, .strand_mode = false} + and state = init(config) + and state.gamma[0] = 1.0 + and state.beta[0] = 0.0 + and grads = NormGradients{.d_gamma = [0.1, 0.2], .d_beta = [0.3, 0.4], .d_input = []} + and lr = 0.01 + when new_state = update(state, grads, lr) + then approximately_equal(new_state.gamma[0], 1.0 - 0.001) + and approximately_equal(new_state.beta[0], -0.003) + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants + // ═══════════════════════════════════════════════════════════ + + invariant norm_output_has_same_shape + given config = any_norm_config() + and state = init(config) + and input = random_input(config.hidden_size) + when result = forward(state, input) + then result.output.len == input.len + + invariant normalized_output_has_zero_mean + given config = NormConfig{.elementwise_affine = false} + and state = init(config) + and input = finite_input(config.hidden_size) + when result = forward(state, input) + then absolute_value(compute_mean(result.normalized)) < 1e-4 + + invariant normalized_output_has_unit_variance + given config = NormConfig{.elementwise_affine = false} + and state = init(config) + and input = finite_input(config.hidden_size) + when result = forward(state, input) + then absolute_value(compute_variance(result.normalized) - 1.0) < 1e-4 + + invariant forward_preserves_finite_values + given config = any_norm_config() + and state = init(config) + and input = finite_input(config.hidden_size) + when result = forward(state, input) + then all(result.output, is_finite) + + invariant backward_gradients_same_shape + given config = any_norm_config() + and state = init(config) + and input = finite_input(config.hidden_size) + and forward_result = forward(state, input) + and grad_output = finite_input(config.hidden_size) + when grads = backward(state, forward_result, grad_output) + then grads.d_gamma.len == config.hidden_size + and grads.d_beta.len == config.hidden_size + and grads.d_input.len == config.hidden_size + + invariant gamma_initially_positive_when_phi_init + given config = NormConfig{.use_phi_init = true, .elementwise_affine = true} + when state = init(config) + then all(state.gamma, is_positive) + + invariant phi_epsilon_is_smaller_than_default + then PHI_EPSILON < DEFAULT_EPSILON + + invariant strand_partitions_are_disjoint + given config = NormConfig{.hidden_size = 192, .strand_mode = true} + and state = init(config) + and strands = create_strands(192, state.gamma, state.beta) + then strands[0].end == strands[1].start + and strands[1].end == strands[2].start + + invariant strand_partitions_cover_full_range + given config = NormConfig{.hidden_size = 192, .strand_mode = true} + and state = init(config) + and strands = create_strands(192, state.gamma, state.beta) + then strands[0].start == 0 + and strands[2].end == 192 + + invariant phi_constraint_squared + then approximately_equal_within(PHI * PHI, PHI + 1.0, 1e-9) + + invariant phi_constraint_reciprocal_sum + then approximately_equal_within(PHI * PHI + 1.0 / (PHI * PHI), 3.0, 1e-9) + + invariant inv_phi_squared_is_correct + then approximately_equal_within(INV_PHI_SQUARED, 1.0 / (PHI * PHI), 1e-9) + + // ═══════════════════════════════════════════════════════════ + // TDD: Benchmarks + // ═══════════════════════════════════════════════════════════ + + bench init_small + given config = NormConfig{.hidden_size = 128} + when result = init(config) + then elapsed_time_ms < 1 + + bench init_medium + given config = NormConfig{.hidden_size = 2048} + when result = init(config) + then elapsed_time_ms < 5 + + bench init_large + given config = NormConfig{.hidden_size = 8192} + when result = init(config) + then elapsed_time_ms < 20 + + bench forward_small + given config = NormConfig{.hidden_size = 128} + and state = init(config) + and input = random_input(128) + when result = forward(state, input) + then elapsed_time_us < 100 + + bench forward_medium + given config = NormConfig{.hidden_size = 2048} + and state = init(config) + and input = random_input(2048) + when result = forward(state, input) + then elapsed_time_us < 500 + + bench forward_large + given config = NormConfig{.hidden_size = 8192} + and state = init(config) + and input = random_input(8192) + when result = forward(state, input) + then elapsed_time_us < 2000 + + bench forward_strand_small + given config = NormConfig{.hidden_size = 128, .strand_mode = true} + and state = init(config) + and input = random_input(128) + and strands = create_strands(128, state.gamma, state.beta) + when result = forward_strand(strands[0], input) + then elapsed_time_us < 50 + + bench backward_small + given config = NormConfig{.hidden_size = 128} + and state = init(config) + and input = random_input(128) + and forward_result = forward(state, input) + and grad_output = random_input(128) + when result = backward(state, forward_result, grad_output) + then elapsed_time_us < 200 + + bench backward_medium + given config = NormConfig{.hidden_size = 2048} + and state = init(config) + and input = random_input(2048) + and forward_result = forward(state, input) + and grad_output = random_input(2048) + when result = backward(state, forward_result, grad_output) + then elapsed_time_us < 1000 + + bench phi_normalize_single + given x = random_gf16() + and mean = random_gf16() + and var = positive_gf16() + when result = phi_normalize(x, mean, var) + then elapsed_time_ns < 50 + + bench create_strands_medium + given hidden_size = 2048 + and gamma = random_array(hidden_size) + and beta = random_array(hidden_size) + when result = create_strands(hidden_size, gamma, beta) + then elapsed_time_us < 100 + + // ═══════════════════════════════════════════════════════════ + // Mathematical Notes + // ═══════════════════════════════════════════════════════════ + // + // Layer Normalization: + // μ = (1/n) * Σᵢ xᵢ + // σ² = (1/n) * Σᵢ (xᵢ - μ)² + // x̂ᵢ = (xᵢ - μ) / √(σ² + ε) + // yᵢ = γᵢ * x̂ᵢ + βᵢ + // + // Backward Pass (per-element gradient): + // ∂L/∂γᵢ = Σ ∂L/∂yᵢ * x̂ᵢ + // ∂L/∂βᵢ = Σ ∂L/∂yᵢ + // ∂L/∂xᵢ = (γᵢ / √(σ²+ε)) * (∂L/∂yᵢ - (1/n) * ∂L/∂βᵢ - x̂ᵢ * (1/n) * ∂L/∂γᵢ) + // + // Phi Properties: + // φ = (1 + √5) / 2 ≈ 1.618033988749895 + // φ² = φ + 1 + // φ² + 1/φ² = 3 + // + // Strand Partitioning: + // Strand I: indices [0, n/3) → φ-offset: 0 + // Strand II: indices [n/3, 2n/3) → φ-offset: 1/φ² * 0.1 + // Strand III: indices [2n/3, n) → φ-offset: 2/φ² * 0.1 + // + // ═══════════════════════════════════════════════════════════ diff --git a/apps/website/public/t27/specs/ml/transformer/positional_enc.t27 b/apps/website/public/t27/specs/ml/transformer/positional_enc.t27 new file mode 100644 index 0000000000..82594e0b71 --- /dev/null +++ b/apps/website/public/t27/specs/ml/transformer/positional_enc.t27 @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ml/transformer/positional_enc.t27 +// Rotary Position Embedding (RoPE) with φ-optimized frequencies | φ² + 1/φ² = 3 | TRINITY + +module PositionalEncoding; + use base::types; + use math::constants; + use math::trigonometry; + use numeric::gf16; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const PHI : gf16::GF16 = 1.618033988749895; + const INV_PHI : gf16::GF16 = 1.0 / PHI; // ≈ 0.618 + const PHI_SQUARED : gf16::GF16 = PHI * PHI; // ≈ 2.618 + const THETA_BASE : gf16::GF16 = 10000.0; + const PHI_THETA_BASE : gf16::GF16 = THETA_BASE * PHI_SQUARED; // φ-optimized base frequency + const DEFAULT_MAX_POSITION : u32 = 8192; + const DEFAULT_ROTARY_DIM : u32 = 64; // Half of head_dim for 128-dim heads + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const RoPEConfig = struct { + dim : u32, // Rotary dimension (must be even) + max_position : u32, // Maximum sequence length + theta_base : gf16::GF16, // Base frequency for theta + use_phi_optimization : bool, // Use φ-optimized frequency base + scaling_factor : gf16::GF16, // Scaling for extended context + }; + + pub const RoPEState = struct { + cos_cache : []gf16::GF16, // Cached cosine values: [max_position, dim/2] + sin_cache : []gf16::GF16, // Cached sine values: [max_position, dim/2] + inv_freq : []gf16::GF16, // Inverse frequencies: [dim/2] + dim : u32, // Rotary dimension + max_position : u32, // Maximum sequence length + }; + + pub const RotaryResult = struct { + rotated_q : []gf16::GF16, // Rotated query embeddings + rotated_k : []gf16::GF16, // Rotated key embeddings + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(config: RoPEConfig) → RoPEState + // Initializes RoPE with precomputed cos/sin caches. + fn init(config: RoPEConfig) -> RoPEState { + // 1. Compute inverse frequencies: + // inv_freq[i] = 1.0 / (theta_base ^ (2i / dim)) for i in [0, dim/2) + // + // 2. Precompute cos/sin for all positions: + // position = [0, 1, ..., max_position-1] + // freqs = position * inv_freq (outer product) + // cos_cache = cos(freqs) + // sin_cache = sin(freqs) + } + + // rotate_half(x: []gf16::GF16) → ([]gf16::GF16, []gf16::GF16) + // Splits tensor into first and second half for rotation. + fn rotate_half(x: []gf16::GF16) -> struct { x1: []gf16::GF16, x2: []gf16::GF16 } { + // x1 = x[0 : dim/2] + // x2 = x[dim/2 : dim] + } + + // apply rotary(x: []gf16::GF16, cos: []gf16::GF16, sin: []gf16::GF16) → []gf16::GF16 + // Applies rotary rotation to a single vector. + fn apply_rotary(x: []gf16::GF16, cos: []gf16::GF16, sin: []gf16::GF16) -> []gf16::GF16 { + // (x1, x2) = rotate_half(x) + // x1_new = x1 * cos - x2 * sin + // x2_new = x1 * sin + x2 * cos + // return concat(x1_new, x2_new) + } + + // forward(state: RoPEState, q: []gf16::GF16, k: []gf16::GF16, positions: []u32) → RotaryResult + // Applies RoPE to query and key tensors. + fn forward(state: RoPEState, q: []gf16::GF16, k: []gf16::GF16, positions: []u32) -> RotaryResult { + // For each position in positions: + // cos = state.cos_cache[position] + // sin = state.sin_cache[position] + // q_rotated = apply_rotary(q, cos, sin) + // k_rotated = apply_rotary(k, cos, sin) + } + + // forward_batch(state: RoPEState, q: []gf16::GF16, k: []gf16::GF16, seq_len: u32) → RotaryResult + // Batched forward pass for sequential positions. + fn forward_batch(state: RoPEState, q: []gf16::GF16, k: []gf16::GF16, seq_len: u32) -> RotaryResult { + // Processes positions 0..seq_len-1 + // Uses precomputed cos/sin caches directly + } + + // compute_inv_freq(dim: u32, theta_base: gf16::GF16) → []gf16::GF16 + // Computes inverse frequencies for RoPE. + fn compute_inv_freq(dim: u32, theta_base: gf16::GF16) -> []gf16::GF16 { + // inv_freq[i] = 1.0 / (theta_base ^ (2i / dim)) + // for i in [0, dim/2) + } + + // phi_optimized_theta(base: gf16::GF16) → f32 + // Applies φ-optimization to theta base. + fn phi_optimized_theta(base: gf16::GF16) -> gf16::GF16 { + // return base * PHI_SQUARED + } + + // extend_cache(state: RoPEState, new_max_position: u32) → RoPEState + // Extends the cos/sin cache for longer sequences. + fn extend_cache(state: RoPEState, new_max_position: u32) -> RoPEState { + // Rebuild cos/sin caches with new max_position + // Keep existing inv_freq + } + + // get_theta_base(config: RoPEConfig) → f32 + // Returns effective theta base. + fn get_theta_base(config: RoPEConfig) -> gf16::GF16 { + // if use_phi_optimization: return PHI_THETA_BASE + // else: return config.theta_base + } + + // scaling_position(pos: gf16::GF16, factor: gf16::GF16) → f32 + // Applies position scaling for extended context. + fn scaling_position(pos: gf16::GF16, factor: gf16::GF16) -> gf16::GF16 { + // return pos / factor + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests + // ═══════════════════════════════════════════════════════════ + + test init_creates_correct_dimensions + given config = RoPEConfig{.dim = 64, .max_position = 128, .theta_base = 10000.0, .use_phi_optimization = false} + when state = init(config) + then state.cos_cache.len == 128 * 32 // max_position * dim/2 + and state.sin_cache.len == 128 * 32 + and state.inv_freq.len == 32 // dim/2 + + test init_with_phi_optimization + given config = RoPEConfig{.dim = 64, .max_position = 128, .theta_base = 10000.0, .use_phi_optimization = true} + when state = init(config) + then state.inv_freq[0] < 1.0 / 10000.0 // Higher base = lower freqs + + test rotate_half_splits_correctly + given x = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] + when result = rotate_half(x) + then result.x1.len == 4 + and result.x2.len == 4 + and result.x1 == [1.0, 2.0, 3.0, 4.0] + and result.x2 == [5.0, 6.0, 7.0, 8.0] + + test apply_rotary_preserves_norm + given x = [1.0, 0.0, 0.0, 1.0] // Two 2D vectors + and cos = [1.0, 1.0] + and sin = [0.0, 0.0] + when result = apply_rotary(x, cos, sin) + then approximately_equal(compute_norm(result), compute_norm(x)) + + test apply_rotary_rotates_correctly + given x = [1.0, 0.0, 1.0, 0.0] // Two (1, 0) vectors + and cos = [0.0, 0.0] // cos(90°) = 0 + and sin = [1.0, 1.0] // sin(90°) = 1 + when result = apply_rotary(x, cos, sin) + then approximately_equal_within(result[0], 0.0, 1e-6) // x1 * cos - x2 * sin = 0 + and approximately_equal_within(result[1], -1.0, 1e-6) // x1 * sin + x2 * cos = -1 + and approximately_equal_within(result[2], 0.0, 1e-6) + and approximately_equal_within(result[3], -1.0, 1e-6) + + test forward_rotates_q_and_k + given config = RoPEConfig{.dim = 4, .max_position = 10, .theta_base = 10000.0} + and state = init(config) + and q = [1.0, 0.0, 1.0, 0.0] + and k = [1.0, 0.0, 1.0, 0.0] + and positions = [0] + when result = forward(state, q, k, positions) + then result.rotated_q.len == 4 + and result.rotated_k.len == 4 + + test forward_different_positions_different_rotations + given config = RoPEConfig{.dim = 4, .max_position = 10, .theta_base = 100.0} // Low base for visible rotation + and state = init(config) + and q = [1.0, 0.0, 1.0, 0.0] + and k = [1.0, 0.0, 1.0, 0.0] + when result1 = forward(state, q, k, [0]) + and result2 = forward(state, q, k, [1]) + then not all_equal(result1.rotated_q, result2.rotated_q) + + test compute_inv_freq_decreases + given dim = 64 + and theta_base = 10000.0 + when inv_freq = compute_inv_freq(dim, theta_base) + then inv_freq[0] > inv_freq[1] + and inv_freq[1] > inv_freq[2] + + test phi_optimized_theta_increases_base + given base = 10000.0 + when result = phi_optimized_theta(base) + then result > base + and approximately_equal_within(result, base * PHI_SQUARED, 1e-6) + + test get_theta_base_phi_enabled + given config = RoPEConfig{.theta_base = 10000.0, .use_phi_optimization = true} + when result = get_theta_base(config) + then approximately_equal_within(result, PHI_THETA_BASE, 1e-6) + + test get_theta_base_phi_disabled + given config = RoPEConfig{.theta_base = 5000.0, .use_phi_optimization = false} + when result = get_theta_base(config) + then approximately_equal_within(result, 5000.0, 1e-6) + + test scaling_position_reduces_position + given pos = 100.0 + and factor = 2.0 + when result = scaling_position(pos, factor) + then approximately_equal(result, 50.0) + + test extend_cache_increases_max_position + given config = RoPEConfig{.dim = 64, .max_position = 100, .theta_base = 10000.0} + and state = init(config) + when extended = extend_cache(state, 200) + then extended.max_position == 200 + and extended.cos_cache.len == 200 * 32 + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants + // ═══════════════════════════════════════════════════════════ + + invariant init_creates_even_dimension + given config = RoPEConfig{.dim = 64} + when state = init(config) + then state.dim % 2 == 0 + + invariant cos_sin_same_size + given config = any_rope_config() + when state = init(config) + then state.cos_cache.len == state.sin_cache.len + + invariant inv_freq_is_half_dim + given config = RoPEConfig{.dim = 64} + when state = init(config) + then state.inv_freq.len == 32 + + invariant inv_freq_positive + given config = any_rope_config() + when state = init(config) + then all(state.inv_freq, is_positive) + + invariant inv_freq_monotonically_decreasing + given config = any_rope_config() + when state = init(config) + then for i in 1..state.inv_freq.len-1: + state.inv_freq[i] < state.inv_freq[i-1] + + invariant cos_in_valid_range + given config = any_rope_config() + when state = init(config) + then all(state.cos_cache, fn(x) x >= -1.0 and x <= 1.0) + + invariant sin_in_valid_range + given config = any_rope_config() + when state = init(config) + then all(state.sin_cache, fn(x) x >= -1.0 and x <= 1.0) + + invariant apply_rotary_preserves_vector_norm + given x = random_input(64) + and cos = random_array(32) // Will be normalized in actual test + and sin = random_array(32) + when norm_x = compute_norm(x) + and result = apply_rotary(x, normalize_to_range(cos, -1, 1), normalize_to_range(sin, -1, 1)) + // Rotation is norm-preserving: |Rx| = |x| + // (This is approximate due to per-dimension rotation) + + invariant phi_theta_base_larger_than_default + then PHI_THETA_BASE > THETA_BASE + + invariant scaling_position_preserves_zero + given pos = 0.0 + and factor = positive_gf16() + when result = scaling_position(pos, factor) + then result == 0.0 + + invariant scaling_position_with_factor_one_unchanged + given pos = random_positive_gf16() + and factor = 1.0 + when result = scaling_position(pos, factor) + then approximately_equal(result, pos) + + // ═══════════════════════════════════════════════════════════ + // TDD: Benchmarks + // ═══════════════════════════════════════════════════════════ + + bench init_small + given config = RoPEConfig{.dim = 64, .max_position = 1024} + when result = init(config) + then elapsed_time_ms < 5 + + bench init_large + given config = RoPEConfig{.dim = 128, .max_position = 8192} + when result = init(config) + then elapsed_time_ms < 50 + + bench apply_rotary_small + given x = random_input(64) + and cos = random_array(32) + and sin = random_array(32) + when result = apply_rotary(x, cos, sin) + then elapsed_time_us < 50 + + bench apply_rotary_large + given x = random_input(128) + and cos = random_array(64) + and sin = random_array(64) + when result = apply_rotary(x, cos, sin) + then elapsed_time_us < 100 + + bench forward_batch_short_sequence + given config = RoPEConfig{.dim = 64, .max_position = 1024} + and state = init(config) + and q = random_input(64 * 10) // 10 tokens + and k = random_input(64 * 10) + when result = forward_batch(state, q, k, 10) + then elapsed_time_us < 200 + + bench forward_batch_long_sequence + given config = RoPEConfig{.dim = 64, .max_position = 8192} + and state = init(config) + and q = random_input(64 * 1024) // 1024 tokens + and k = random_input(64 * 1024) + when result = forward_batch(state, q, k, 1024) + then elapsed_time_ms < 20 + + bench compute_inv_freq + given dim = 128 + and theta_base = 10000.0 + when result = compute_inv_freq(dim, theta_base) + then elapsed_time_us < 50 + + // ═══════════════════════════════════════════════════════════ + // Mathematical Notes + // ═══════════════════════════════════════════════════════════ + // + // RoPE (Rotary Position Embedding): + // Rotates query/key vectors by their position in sequence. + // Provides relative positional information directly. + // + // Rotation Matrix (2D): + // R(θ) = [[cos(θ), -sin(θ)], + // [sin(θ), cos(θ)]] + // + // For a vector [x₁, x₂, ..., x_d]: + // Split into pairs: (x₁, x₂), (x₃, x₄), ..., (x_{d-1}, x_d) + // Each pair rotated by different frequency: + // θ_i = pos / (θ_base^(2i/d)) + // + // Position 0: + // cos(0) = 1, sin(0) = 0 → No rotation + // + // Position p > 0: + // x'_1 = x₁ * cos(θ₁) - x₂ * sin(θ₁) + // x'_2 = x₁ * sin(θ₁) + x₂ * cos(θ₁) + // + // Φ-Optimized Theta Base: + // θ_φ = θ_base * φ² ≈ θ_base * 2.618 + // Higher base = slower frequency decay + // Better long-range position encoding + // + // Extended Context Scaling: + // pos_scaled = pos / scaling_factor + // Allows model to handle sequences longer than training max_length + // + // Cosine Similarity after RoPE: + // cos(q₁, k₂) = q₁ · k₂ = |q₁||k₂|cos(θ_q - θ_k) + // Directly encodes relative position! + // + // ═══════════════════════════════════════════════════════════ diff --git a/apps/website/public/t27/specs/ml/transformer/positional_encoding.t27 b/apps/website/public/t27/specs/ml/transformer/positional_encoding.t27 new file mode 100644 index 0000000000..5bc22257ba --- /dev/null +++ b/apps/website/public/t27/specs/ml/transformer/positional_encoding.t27 @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Uses sin/cos at different frequencies for each dimension | φ² + 1/φ² = 3 | TRINITY + +module PositionalEnc; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const DEFAULT_MAX_LEN : u32 = 2048; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const PosEncConfig = struct { + d_model : u32, + max_len : u32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // forward(positions: []const u32, embeddings: []f32, config: PosEncConfig) → void + fn forward(positions: []const u32, embeddings: []f32, config: PosEncConfig) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test forward_basic_case + given input = default_input() + when result = forward(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/neural/forward_pass.t27 b/apps/website/public/t27/specs/neural/forward_pass.t27 new file mode 100644 index 0000000000..219713502f --- /dev/null +++ b/apps/website/public/t27/specs/neural/forward_pass.t27 @@ -0,0 +1,1094 @@ +// Forward Pass Demo - VSA-based Neural Network +// Implements transformer-style forward pass using Vector Symbolic Architecture +// with multi-head attention, residual connections, and autoregressive generation +// +// Author: Dmitrii Vasilev +// SPDX-License-Identifier: Apache-2.0 + +module forward_pass; + +import numeric::gf16; +import tritype-base::Trit; +import vsa::vsa_core::{bind, unbind, bundle2, bundle3, permute, cosine_similarity}; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default hypervector dimension +pub const DEFAULT_DIM: usize = 1024; + +/// Number of role vectors for multi-head attention +pub const NUM_ROLES: usize = 11; + +/// Context window size (number of tokens in context) +pub const CONTEXT_SIZE: usize = 8; + +/// Number of attention heads +pub const NUM_HEADS: usize = 3; + +/// Vectors per head (Q, K, V) +pub const VECTORS_PER_HEAD: usize = 3; + +/// FFN role indices +pub const FF1_ROLE_IDX: usize = 9; +pub const FF2_ROLE_IDX: usize = 10; + +/// Maximum tokens for autoregressive generation +pub const MAX_GENERATION_TOKENS: usize = 100; + +/// Default learning rate for resonator training +pub const DEFAULT_LEARNING_RATE: gf16 = 0.5; + +/// Number of resonator iterations +pub const RESONATOR_ITERS: usize = 5; + +/// Similarity threshold for early stopping +pub const SIMILARITY_THRESHOLD: gf16 = 0.5; + +// ============================================================================ +// Types +// ============================================================================ + +/// Hypervector representation using ternary values +pub type Hypervector = [DEFAULT_DIM]Trit; + +/// Role vectors for attention and FFN +/// Layout: [Q0,K0,V0, Q1,K1,V1, Q2,K2,V2, FF1, FF2] +pub type Roles = [NUM_ROLES]Hypervector; + +/// Context window of hypervectors +pub type Context = [CONTEXT_SIZE]Hypervector; + +/// Training result with loss +pub struct TrainResult { + pub loss: gf16, + pub similarity: gf16, + pub iterations: usize, +} + +// ============================================================================ +// Core Operations +// ============================================================================ + +/// Initialize role vectors with random seeds +pub fn init_roles(dim: usize, seed: u64) -> Roles { + let mut roles: Roles = [[TRIT_ZERO; DEFAULT_DIM]; NUM_ROLES]; + + for i in 0..NUM_ROLES { + for j in 0..dim { + // Simple deterministic random based on seed + i + j + let r = ((seed + (i as u64) * 1000 + (j as u64)) % 3) as i8 - 1; + roles[i][j] = r as Trit; + } + } + + roles +} + +/// Single-head attention mechanism +/// Computes: query = bind(last_position, Q_role) +/// Finds best matching key via similarity +/// Returns: bind(best_position, V_role) +pub fn single_head_attention( + positioned: &Context, + q_role: &Hypervector, + k_role: &Hypervector, + v_role: &Hypervector, + dim: usize, +) -> Hypervector { + // Query = bind(last_position, Q_role) + let last_pos = positioned[CONTEXT_SIZE - 1]; + let query = bind(last_pos, q_role, dim); + + // Find best-matching key + let mut best_sim: gf16 = -2.0; + let mut best_idx: usize = 0; + + for i in 0..CONTEXT_SIZE { + let key_i = bind(positioned[i], k_role, dim); + let sim = cosine_similarity(query, key_i, dim); + + if sim > best_sim { + best_sim = sim; + best_idx = i; + } + } + + // Value = bind(best_position, V_role) + bind(positioned[best_idx], v_role, dim) +} + +/// Multi-head forward pass with residual connection +/// Pipeline: position -> 3-head attention -> bundle3 -> FFN -> residual +pub fn forward_pass_multi_head( + context: &Context, + roles: &Roles, + dim: usize, +) -> Hypervector { + // Position encoding + let mut positioned: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + positioned[i] = permute(context[i], i, dim); + } + + // 3-head attention + let head0 = single_head_attention(&positioned, &roles[0], &roles[1], &roles[2], dim); + let head1 = single_head_attention(&positioned, &roles[3], &roles[4], &roles[5], dim); + let head2 = single_head_attention(&positioned, &roles[6], &roles[7], &roles[8], dim); + + // Merge heads via bundle3 + let merged = bundle3(head0, head1, head2, dim); + + // FFN: bind(FF1), then bind(FF2) + let ffn_mid = bind(merged, &roles[FF1_ROLE_IDX], dim); + let ffn_out = bind(ffn_mid, &roles[FF2_ROLE_IDX], dim); + + // Residual connection: bundle with last positioned vector + bundle2(ffn_out, positioned[CONTEXT_SIZE - 1], dim) +} + +/// Summarize context into a single hypervector +/// Uses positional permutation + sequential bundling +pub fn summarize_context(context: &Context, dim: usize) -> Hypervector { + let mut summary = permute(context[0], 0, dim); + + for i in 1..CONTEXT_SIZE { + let positioned = permute(context[i], i, dim); + summary = bundle2(summary, positioned, dim); + } + + summary +} + +/// Direct forward pass: just bind(context_summary, role) +/// Only 1 bind operation -> clean signal +pub fn forward_pass_direct( + context: &Context, + role: &Hypervector, + dim: usize, +) -> Hypervector { + let summary = summarize_context(context, dim); + bind(summary, role, dim) +} + +// ============================================================================ +// Resonator Training +// ============================================================================ + +/// Resonator training step for a single (context, target) pair +/// Iteratively refines FF roles to reduce error +pub fn resonator_train_step( + context: &Context, + target: &Hypervector, + roles: &mut Roles, + dim: usize, + learning_rate: gf16, + seed: u64, +) -> TrainResult { + // Forward pass to get initial output + let output = forward_pass_multi_head(context, roles, dim); + let initial_sim = cosine_similarity(output, *target, dim); + let mut best_loss: gf16 = 1.0 - initial_sim; + let mut best_sim = initial_sim; + + // Resonator iterations + for iter in 0..RESONATOR_ITERS { + // Position encoding + let mut positioned: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + positioned[i] = permute(context[i], i, dim); + } + + // Compute merged attention output + let head0 = single_head_attention(&positioned, &roles[0], &roles[1], &roles[2], dim); + let head1 = single_head_attention(&positioned, &roles[3], &roles[4], &roles[5], dim); + let head2 = single_head_attention(&positioned, &roles[6], &roles[7], &roles[8], dim); + let merged = bundle3(head0, head1, head2, dim); + + // Compute ideal FF2 direction + let merged_ff1 = bind(merged, &roles[FF1_ROLE_IDX], dim); + let ideal_ff2 = unbind(*target, merged_ff1, dim); + + // Compute ideal FF1 direction + let ideal_ff1_input = unbind(*target, roles[FF2_ROLE_IDX], dim); + let ideal_ff1 = unbind(ideal_ff1_input, merged, dim); + + // Apply corrections (simplified - sparse updates) + roles[FF2_ROLE_IDX] = bind(roles[FF2_ROLE_IDX], ideal_ff2, dim); + roles[FF1_ROLE_IDX] = bind(roles[FF1_ROLE_IDX], ideal_ff1, dim); + + // Re-check + let new_output = forward_pass_multi_head(context, roles, dim); + let new_sim = cosine_similarity(new_output, *target, dim); + let new_loss = 1.0 - new_sim; + + if new_loss < best_loss { + best_loss = new_loss; + best_sim = new_sim; + } + + // Early stop if similarity is good + if new_sim > SIMILARITY_THRESHOLD { + break; + } + } + + TrainResult { + loss: best_loss, + similarity: best_sim, + iterations: RESONATOR_ITERS, + } +} + +/// Compute direct role from a corpus of (context, target) pairs +/// One-shot computation, no iterative training needed +pub fn compute_direct_role( + contexts: &[Context], + targets: &[Hypervector], + dim: usize, +) -> Hypervector { + if contexts.is_empty() { + return [TRIT_ZERO; DEFAULT_DIM]; + } + + // Start with first sample + let mut accumulated_role = unbind(targets[0], summarize_context(&contexts[0], dim), dim); + + // Bundle remaining samples + for i in 1..contexts.len() { + let summary = summarize_context(&contexts[i], dim); + let ideal = unbind(targets[i], summary, dim); + accumulated_role = bundle2(accumulated_role, ideal, dim); + } + + accumulated_role +} + +// ============================================================================ +// Autoregressive Generation +// ============================================================================ + +/// Autoregressive generation: predict next token, shift context, repeat +pub fn generate_autoregressive( + initial_context: &Context, + roles: &Roles, + role: &Hypervector, // For direct mode + dim: usize, + max_tokens: usize, + use_direct: bool, +) -> Vec { + let mut context = *initial_context; + let mut generated: Vec = Vec::new(); + + for _ in 0..max_tokens { + let output = if use_direct { + forward_pass_direct(&context, role, dim) + } else { + forward_pass_multi_head(&context, roles, dim) + }; + + generated.push(output); + + // Shift context: drop first, append new prediction + for i in 0..(CONTEXT_SIZE - 1) { + context[i] = context[i + 1]; + } + context[CONTEXT_SIZE - 1] = output; + } + + generated +} + +// ============================================================================ +// Utilities +// ============================================================================ + +/// Measure perplexity on a dataset +/// PPL = exp(-average_log_likelihood) +pub fn measure_perplexity( + contexts: &[Context], + targets: &[Hypervector], + roles: &Roles, + dim: usize, +) -> gf16 { + if contexts.is_empty() { + return 0.0; + } + + let mut total_log_likelihood: gf16 = 0.0; + + for i in 0..contexts.len() { + let output = forward_pass_multi_head(&contexts[i], roles, dim); + let sim = cosine_similarity(output, targets[i], dim); + + // Convert similarity to pseudo-log-likelihood + // Similarity in [-1, 1] maps to [low, high] likelihood + let log_likelihood = if sim > -1.0 { + ((sim + 1.0) / 2.0).ln() + } else { + -10.0 // Floor for very negative similarities + }; + + total_log_likelihood += log_likelihood; + } + + let avg_log_likelihood = total_log_likelihood / (contexts.len() as gf16); + (-avg_log_likelihood).exp() +} + +/// Compute accuracy: fraction of predictions with similarity > threshold +pub fn compute_accuracy( + contexts: &[Context], + targets: &[Hypervector], + roles: &Roles, + dim: usize, + threshold: gf16, +) -> gf16 { + if contexts.is_empty() { + return 0.0; + } + + let mut correct: usize = 0; + + for i in 0..contexts.len() { + let output = forward_pass_multi_head(&contexts[i], roles, dim); + let sim = cosine_similarity(output, targets[i], dim); + + if sim > threshold { + correct += 1; + } + } + + (correct as gf16) / (contexts.len() as gf16) +} + +// ============================================================================ +// Tests +// ============================================================================ + +test "init_roles creates 11 role vectors" { + let roles = init_roles(1024, 12345); + assert_eq!(roles.len(), 11); +} + +test "single_head_attention_returns_valid_output" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..100 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + + let mut roles = init_roles(1024, 12345); + + let result = single_head_attention( + &context, + &roles[0], + &roles[1], + &roles[2], + 1024, + ); + + // Result should be a valid hypervector + let mut non_zero_count = 0; + for i in 0..100 { + if result[i] != TRIT_ZERO { + non_zero_count += 1; + } + } + assert!(non_zero_count > 0); +} + +test "forward_pass_multi_head_produces_output" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..100 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + + let roles = init_roles(1024, 12345); + + let output = forward_pass_multi_head(&context, &roles, 1024); + + // Check output is valid + let mut sum: i32 = 0; + for i in 0..100 { + sum += output[i] as i32; + } + // Should have some non-zero values + assert!(sum != 0); +} + +test "summarize_context_produces_valid_hypervector" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..100 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + + let summary = summarize_context(&context, 1024); + + // Summary should be non-zero + let mut non_zero = 0; + for i in 0..100 { + if summary[i] != TRIT_ZERO { + non_zero += 1; + } + } + assert!(non_zero > 0); +} + +test "forward_pass_direct_produces_output" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..100 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + + let role = init_roles(1024, 12345)[0]; + + let output = forward_pass_direct(&context, &role, 1024); + + // Check output is valid + let mut non_zero = 0; + for i in 0..100 { + if output[i] != TRIT_ZERO { + non_zero += 1; + } + } + assert!(non_zero > 0); +} + +test "resonator_train_step_returns_valid_result" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..100 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + + let mut target: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + for i in 0..100 { + target[i] = (i % 3) as i8 - 1; + } + + let mut roles = init_roles(1024, 12345); + + let result = resonator_train_step(&context, &target, &mut roles, 1024, 0.5, 999); + + // Loss should be in [0, 1] + assert!(result.loss >= 0.0 && result.loss <= 1.0); + // Similarity should be in [-1, 1] + assert!(result.similarity >= -1.0 && result.similarity <= 1.0); +} + +test "generate_autoregressive_produces_tokens" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..100 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + + let roles = init_roles(1024, 12345); + let role = roles[0]; + + let generated = generate_autoregressive(&context, &roles, &role, 1024, 10, true); + + assert_eq!(generated.len(), 10); +} + +test "measure_perplexity_returns_valid_value" { + let mut contexts: Vec = Vec::new(); + let mut targets: Vec = Vec::new(); + + for sample in 0..5 { + let mut ctx: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..100 { + ctx[i][j] = ((sample + i + j) % 3) as i8 - 1; + } + } + contexts.push(ctx); + + let mut tgt: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + for j in 0..100 { + tgt[j] = ((sample + j) % 3) as i8 - 1; + } + targets.push(tgt); + } + + let roles = init_roles(1024, 12345); + let ppl = measure_perplexity(&contexts, &targets, &roles, 1024); + + // PPL should be positive + assert!(ppl > 0.0); +} + +test "compute_accuracy_returns_valid_value" { + let mut contexts: Vec = Vec::new(); + let mut targets: Vec = Vec::new(); + + for sample in 0..5 { + let mut ctx: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..100 { + ctx[i][j] = ((sample + i + j) % 3) as i8 - 1; + } + } + contexts.push(ctx); + + let mut tgt: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + for j in 0..100 { + tgt[j] = ((sample + j) % 3) as i8 - 1; + } + targets.push(tgt); + } + + let roles = init_roles(1024, 12345); + let acc = compute_accuracy(&contexts, &targets, &roles, 1024, 0.0); + + // Accuracy should be in [0, 1] + assert!(acc >= 0.0 && acc <= 1.0); +} + +test "compute_direct_role_with_empty_corpus" { + let contexts: Vec = Vec::new(); + let targets: Vec = Vec::new(); + + let role = compute_direct_role(&contexts, &targets, 1024); + + // Should return zero hypervector + for i in 0..100 { + assert_eq!(role[i], TRIT_ZERO); + } +} + +test "compute_direct_role_with_single_sample" { + let mut ctx: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..100 { + ctx[i][j] = ((i + j) % 3) as i8 - 1; + } + } + + let mut tgt: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + for j in 0..100 { + tgt[j] = (j % 3) as i8 - 1; + } + + let contexts = vec![ctx]; + let targets = vec![tgt]; + + let role = compute_direct_role(&contexts, &targets, 1024); + + // Role should be non-zero + let mut non_zero = 0; + for i in 0..100 { + if role[i] != TRIT_ZERO { + non_zero += 1; + } + } + assert!(non_zero > 0); +} + +test "bundle3_merges_three_heads" { + let mut h1: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let mut h2: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let mut h3: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + + for i in 0..100 { + h1[i] = TRIT_POS; + h2[i] = TRIT_POS; + h3[i] = TRIT_POS; + } + + let merged = bundle3(h1, h2, h3, 1024); + + // With all +1, majority vote should be +1 + assert_eq!(merged[0], TRIT_POS); +} + +test "bind_unbind_self_inverse" { + let mut a: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let mut b: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + + for i in 0..100 { + a[i] = (i % 3) as i8 - 1; + b[i] = ((i + 1) % 3) as i8 - 1; + } + + let bound = bind(a, b, 1024); + let recovered = unbind(bound, b, 1024); + + // Check similarity + let sim = cosine_similarity(a, recovered, 1024); + // Should be high (perfect in theory, noise in practice) + assert!(sim > 0.8); +} + +test "permute_changes_hypervector" { + let mut hv: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + for i in 0..100 { + hv[i] = (i % 3) as i8 - 1; + } + + let permuted = permute(hv, 5, 1024); + + // Permuted should be different + let mut same = true; + for i in 0..100 { + if hv[i] != permuted[i] { + same = false; + break; + } + } + assert!(!same); +} + +test "generate_autoregressive_with_multi_head" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..100 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + + let roles = init_roles(1024, 12345); + + let generated = generate_autoregressive(&context, &roles, &roles[0], 1024, 5, false); + + assert_eq!(generated.len(), 5); +} + +test "measure_perplexity_empty_dataset" { + let contexts: Vec = Vec::new(); + let targets: Vec = Vec::new(); + + let roles = init_roles(1024, 12345); + let ppl = measure_perplexity(&contexts, &targets, &roles, 1024); + + // Empty dataset should return 0 + assert_eq!(ppl, 0.0); +} + +test "resonator_improves_similarity" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..100 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + + let mut target: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + for i in 0..100 { + target[i] = (i % 3) as i8 - 1; + } + + let mut roles = init_roles(1024, 12345); + + // Get initial similarity + let initial_output = forward_pass_multi_head(&context, &roles, 1024); + let initial_sim = cosine_similarity(initial_output, target, 1024); + + // Train + let result = resonator_train_step(&context, &target, &mut roles, 1024, 0.5, 999); + + // Similarity should not decrease (may stay same or improve) + assert!(result.similarity >= initial_sim - 0.1); +} + +test "forward_pass_direct_faster_than_multi_head" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..100 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + + let roles = init_roles(1024, 12345); + let role = roles[0]; + + // Both should produce valid output + let _ = forward_pass_multi_head(&context, &roles, 1024); + let _ = forward_pass_direct(&context, &role, 1024); + + // Test passes if both complete without error + assert!(true); +} + +test "compute_accuracy_with_high_threshold" { + let mut contexts: Vec = Vec::new(); + let mut targets: Vec = Vec::new(); + + for sample in 0..3 { + let mut ctx: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..100 { + ctx[i][j] = ((sample + i + j) % 3) as i8 - 1; + } + } + contexts.push(ctx); + + let mut tgt: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + for j in 0..100 { + tgt[j] = ((sample + j) % 3) as i8 - 1; + } + targets.push(tgt); + } + + let roles = init_roles(1024, 12345); + let acc = compute_accuracy(&contexts, &targets, &roles, 1024, 0.9); + + // Accuracy should be in [0, 1] + assert!(acc >= 0.0 && acc <= 1.0); +} + +test "summarize_context_order_sensitive" { + let mut ctx1: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + let mut ctx2: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + + for i in 0..CONTEXT_SIZE { + for j in 0..100 { + ctx1[i][j] = (i as i8) - 1; + ctx2[i][j] = ((CONTEXT_SIZE - 1 - i) as i8) - 1; + } + } + + let sum1 = summarize_context(&ctx1, 1024); + let sum2 = summarize_context(&ctx2, 1024); + + // Different orders should produce different summaries + let mut same = true; + for i in 0..100 { + if sum1[i] != sum2[i] { + same = false; + break; + } + } + assert!(!same); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +invariant "roles_count_is_11" { + let roles = init_roles(1024, 12345); + assert_eq!(roles.len(), 11); +} + +invariant "forward_pass_output_has_same_dim" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + let roles = init_roles(1024, 12345); + let output = forward_pass_multi_head(&context, &roles, 1024); + assert_eq!(output.len(), 1024); +} + +invariant "resonator_loss_in_valid_range" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + let mut target: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let mut roles = init_roles(1024, 12345); + let result = resonator_train_step(&context, &target, &mut roles, 1024, 0.5, 999); + assert!(result.loss >= 0.0 && result.loss <= 1.0); +} + +invariant "resonator_similarity_in_valid_range" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + let mut target: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let mut roles = init_roles(1024, 12345); + let result = resonator_train_step(&context, &target, &mut roles, 1024, 0.5, 999); + assert!(result.similarity >= -1.0 && result.similarity <= 1.0); +} + +invariant "generate_autoregressive_returns_correct_count" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + let roles = init_roles(1024, 12345); + let generated = generate_autoregressive(&context, &roles, &roles[0], 1024, 10, true); + assert_eq!(generated.len(), 10); +} + +invariant "measure_perplexity_non_negative" { + let contexts: Vec = vec![[[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]]; + let targets: Vec = vec![[TRIT_ZERO; DEFAULT_DIM]]; + let roles = init_roles(1024, 12345); + let ppl = measure_perplexity(&contexts, &targets, &roles, 1024); + assert!(ppl >= 0.0); +} + +invariant "compute_accuracy_in_valid_range" { + let contexts: Vec = vec![[[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]]; + let targets: Vec = vec![[TRIT_ZERO; DEFAULT_DIM]]; + let roles = init_roles(1024, 12345); + let acc = compute_accuracy(&contexts, &targets, &roles, 1024, 0.5); + assert!(acc >= 0.0 && acc <= 1.0); +} + +invariant "summarize_context_returns_valid_dim" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + let summary = summarize_context(&context, 1024); + assert_eq!(summary.len(), 1024); +} + +invariant "forward_pass_direct_returns_valid_dim" { + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + let role = init_roles(1024, 12345)[0]; + let output = forward_pass_direct(&context, &role, 1024); + assert_eq!(output.len(), 1024); +} + +invariant "compute_direct_role_returns_valid_dim" { + let contexts: Vec = vec![[[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]]; + let targets: Vec = vec![[TRIT_ZERO; DEFAULT_DIM]]; + let role = compute_direct_role(&contexts, &targets, 1024); + assert_eq!(role.len(), 1024); +} + +invariant "bind_unbind_preserves_dimension" { + let a: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let b: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let bound = bind(a, b, 1024); + let recovered = unbind(bound, b, 1024); + assert_eq!(recovered.len(), 1024); +} + +invariant "permute_preserves_dimension" { + let hv: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let permuted = permute(hv, 5, 1024); + assert_eq!(permuted.len(), 1024); +} + +invariant "bundle2_preserves_dimension" { + let a: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let b: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let bundled = bundle2(a, b, 1024); + assert_eq!(bundled.len(), 1024); +} + +invariant "bundle3_preserves_dimension" { + let a: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let b: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let c: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let bundled = bundle3(a, b, c, 1024); + assert_eq!(bundled.len(), 1024); +} + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench "init_roles" { + let iterations = 1000; + for _ in 0..iterations { + let _ = init_roles(1024, 12345); + } +} + +bench "single_head_attention" { + let iterations = 1000; + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..1024 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + let roles = init_roles(1024, 12345); + + for _ in 0..iterations { + let _ = single_head_attention(&context, &roles[0], &roles[1], &roles[2], 1024); + } +} + +bench "forward_pass_multi_head" { + let iterations = 100; + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..1024 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + let roles = init_roles(1024, 12345); + + for _ in 0..iterations { + let _ = forward_pass_multi_head(&context, &roles, 1024); + } +} + +bench "forward_pass_direct" { + let iterations = 1000; + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..1024 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + let role = init_roles(1024, 12345)[0]; + + for _ in 0..iterations { + let _ = forward_pass_direct(&context, &role, 1024); + } +} + +bench "summarize_context" { + let iterations = 1000; + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..1024 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + + for _ in 0..iterations { + let _ = summarize_context(&context, 1024); + } +} + +bench "resonator_train_step" { + let iterations = 10; + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..1024 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + let mut target: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + for j in 0..1024 { + target[j] = (j % 3) as i8 - 1; + } + + for _ in 0..iterations { + let mut roles = init_roles(1024, 12345); + let _ = resonator_train_step(&context, &target, &mut roles, 1024, 0.5, 999); + } +} + +bench "generate_autoregressive" { + let iterations = 10; + let mut context: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..1024 { + context[i][j] = ((i + j) % 3) as i8 - 1; + } + } + let roles = init_roles(1024, 12345); + + for _ in 0..iterations { + let _ = generate_autoregressive(&context, &roles, &roles[0], 1024, 10, true); + } +} + +bench "measure_perplexity" { + let iterations = 10; + let mut contexts: Vec = Vec::new(); + let mut targets: Vec = Vec::new(); + + for sample in 0..10 { + let mut ctx: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..1024 { + ctx[i][j] = ((sample + i + j) % 3) as i8 - 1; + } + } + contexts.push(ctx); + + let mut tgt: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + for j in 0..1024 { + tgt[j] = ((sample + j) % 3) as i8 - 1; + } + targets.push(tgt); + } + + let roles = init_roles(1024, 12345); + + for _ in 0..iterations { + let _ = measure_perplexity(&contexts, &targets, &roles, 1024); + } +} + +bench "compute_accuracy" { + let iterations = 10; + let mut contexts: Vec = Vec::new(); + let mut targets: Vec = Vec::new(); + + for sample in 0..10 { + let mut ctx: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..1024 { + ctx[i][j] = ((sample + i + j) % 3) as i8 - 1; + } + } + contexts.push(ctx); + + let mut tgt: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + for j in 0..1024 { + tgt[j] = ((sample + j) % 3) as i8 - 1; + } + targets.push(tgt); + } + + let roles = init_roles(1024, 12345); + + for _ in 0..iterations { + let _ = compute_accuracy(&contexts, &targets, &roles, 1024, 0.5); + } +} + +bench "compute_direct_role" { + let iterations = 10; + let mut contexts: Vec = Vec::new(); + let mut targets: Vec = Vec::new(); + + for sample in 0..10 { + let mut ctx: Context = [[TRIT_ZERO; DEFAULT_DIM]; CONTEXT_SIZE]; + for i in 0..CONTEXT_SIZE { + for j in 0..1024 { + ctx[i][j] = ((sample + i + j) % 3) as i8 - 1; + } + } + contexts.push(ctx); + + let mut tgt: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + for j in 0..1024 { + tgt[j] = ((sample + j) % 3) as i8 - 1; + } + targets.push(tgt); + } + + for _ in 0..iterations { + let _ = compute_direct_role(&contexts, &targets, 1024); + } +} + +bench "bind_operation" { + let iterations = 10000; + let a: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let b: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + + for _ in 0..iterations { + let _ = bind(a, b, 1024); + } +} + +bench "bundle3_operation" { + let iterations = 10000; + let a: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let b: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + let c: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + + for _ in 0..iterations { + let _ = bundle3(a, b, c, 1024); + } +} + +bench "permute_operation" { + let iterations = 10000; + let hv: Hypervector = [TRIT_ZERO; DEFAULT_DIM]; + + for _ in 0..iterations { + let _ = permute(hv, 5, 1024); + } +} diff --git a/apps/website/public/t27/specs/nn/attention.t27 b/apps/website/public/t27/specs/nn/attention.t27 new file mode 100644 index 0000000000..0c8d10c874 --- /dev/null +++ b/apps/website/public/t27/specs/nn/attention.t27 @@ -0,0 +1,633 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/nn/attention.t27 +// Sacred Attention Specification +// Multi-head attention with phi-RoPE and sacred scaling (d_k^(-phi^3)) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module SacredAttention { + // Import base types, operations, and math constants + use base::types; + use base::ops; + use math::constants; + + // ================================================================= + // 1. Constants + // ========================================================================= + + // TRINITY-based configuration + const NUM_HEADS : usize = 3; // 3 heads (TRINITY) + const HEAD_DIM : usize = 81; // 81 dim per head (3^4) + const EMBED_DIM : usize = 243; // Total embedding (3 * 81) + const CONTEXT_LEN : usize = 81; // Max sequence length + const ROPE_PAIRS : usize = 40; // 81/2 = 40 pairs (1 unrotated) + + // Sacred scaling: phi^-^3 and d_k^(-phi^3) + const SACRED_GAMMA : f64 = constants::PHI_CUBED_INV; // phi^-^3 ~= 0.236 + const SACRED_SCALE : f64 = pow(81.0, -SACRED_GAMMA); // 81^(-phi^-^3) ~= 0.354 + + // Attention types + const ATTN_CAUSAL : u8 = 0; // Causal (autoregressive) attention + const ATTN_BIDIR : u8 = 1; // Bidirectional attention + const ATTN_SPARSE : u8 = 2; // Sparse (local) attention + + // Attention phase states + const PHASE_QUERY : u8 = 0; // Compute Q projections + const PHASE_KEY : u8 = 1; // Compute K projections + const PHASE_VALUE : u8 = 2; // Compute V projections + const PHASE_SCORE : u8 = 3; // Compute attention scores + const PHASE_SOFTMAX : u8 = 4; // Apply softmax + const PHASE_WEIGHT : u8 = 5; // Apply to values + + // ================================================================= + // 2. RoPE Tables + // ========================================================================= + + // phi-RoPE tables: [CONTEXT_LEN * ROPE_PAIRS] + // Each entry is precomputed cos(p * theta_i) and sin(p * theta_i) + // where theta_i = phi^(-2i/HEAD_DIM) for i=0..ROPE_PAIRS-1 + struct RoPETables { + cos : [CONTEXT_LEN * ROPE_PAIRS]f64, + sin : [CONTEXT_LEN * ROPE_PAIRS]f64, + } + + var rope_tables : RoPETables; + + // ================================================================= + // 3. Attention State + // ========================================================================= + + // Attention buffers (allocated per forward pass) + struct AttentionBuffers { + q_buffer : [EMBED_DIM]f64, // Query projections + k_buffer : [EMBED_DIM]f64, // Key projections + v_buffer : [EMBED_DIM]f64, // Value projections + scores : [NUM_HEADS * CONTEXT_LEN]f64, // Attention scores + concat : [EMBED_DIM]f64, // Concatenated head outputs + } + + // ================================================================= + // 4. Initialization + // ========================================================================= + + // sacred_attention_init() -> void + // Initialize phi-RoPE tables + // theta_i = phi^(-2i/HEAD_DIM) for i=0..ROPE_PAIRS-1 + // For each position p: cos(p*theta_i), sin(p*theta_i) + fn sacred_attention_init() -> void { + var p : usize = 0; + + while (p < CONTEXT_LEN) { + var i : usize = 0; + + while (i < ROPE_PAIRS) { + // Compute freq = phi^(-2i/HEAD_DIM) + const freq_exponent = -2.0 * (i as f64) / (HEAD_DIM as f64); + const freq = pow(constants::PHI, freq_exponent); + + // angle = position * freq + const angle = (p as f64) * freq; + + // cos and sin + const cos_val = cos(angle); + const sin_val = sin(angle); + + // Store in tables + const table_offset = p * ROPE_PAIRS + i; + rope_tables.cos[table_offset] = cos_val; + rope_tables.sin[table_offset] = sin_val; + + i = i + 1; + } + + p = p + 1; + } + } + + // ================================================================= + // 5. Main Attention Kernel + // ========================================================================= + + // sacred_attention_kernel( + // input: []f32, // [EMBED_DIM] input embeddings + // w_q: []Trit, w_k: []Trit, w_v: []Trit, w_o: []Trit, // Ternary weights + // position: usize, + // seq_len: usize, + // output: []f32, // [EMBED_DIM] output + // cache_k: []f32, cache_v: []f32, // [CONTEXT_LEN][EMBED_DIM] + // ) -> void + // Single position forward pass with sacred scaling + fn sacred_attention_kernel( + input: []f64, + w_q: []Trit, w_k: []Trit, w_v: []Trit, w_o: []Trit, + position: usize, + seq_len: usize, + output: []f64, + cache_k: []f64, cache_v: []f64, + ) -> void { + var buffers = AttentionBuffers{ + .q_buffer = [0.0; EMBED_DIM], + .k_buffer = [0.0; EMBED_DIM], + .v_buffer = [0.0; EMBED_DIM], + .scores = [0.0; NUM_HEADS * CONTEXT_LEN], + .concat = [0.0; EMBED_DIM], + }; + + // Step 1: Project Q, K, V via ternary matmul + project_qkv(&buffers, input, w_q, w_k, w_v); + + // Step 2: Apply phi-RoPE to Q and K + apply_rope_qk(&buffers, position); + + // Step 3: Cache K and V + cache_kv(&buffers, position, cache_k, cache_v); + + // Step 4: Compute attention scores (Q @ K^T * SACRED_SCALE) + compute_scores(&buffers, position, seq_len, cache_k); + + // Step 5: Apply softmax to scores + apply_softmax(&buffers, seq_len); + + // Step 6: Weighted sum of values + weighted_values(&buffers, seq_len, cache_v); + + // Step 7: Output projection via W_o + project_output(&buffers, w_o, output); + + // Step 8: Add residual connection + add_residual(output, input); + } + + // ================================================================= + // 6. Q/K/V Projection + // ========================================================================= + + // project_qkv(buffers, input, w_q, w_k, w_v) -> void + // Compute Q, K, V projections using ternary matrix multiplication + // Q[i] = Sigma_j input[j] * W_q[j][i] + // Using ternary weights: W_q[j][i] in {-1, 0, +1} + fn project_qkv( + buffers: *AttentionBuffers, + input: []f64, + w_q: []Trit, w_k: []Trit, w_v: []Trit, + ) -> void { + // Compute Q + ternary_matmul(input, w_q, &buffers.q_buffer, EMBED_DIM, EMBED_DIM); + + // Compute K + ternary_matmul(input, w_k, &buffers.k_buffer, EMBED_DIM, EMBED_DIM); + + // Compute V + ternary_matmul(input, w_v, &buffers.v_buffer, EMBED_DIM, EMBED_DIM); + } + + // ternary_matmul(input, weights, output, in_dim, out_dim) -> void + // Matrix multiplication with ternary weights + fn ternary_matmul( + input: []f64, + weights: []Trit, + output: []f64, + in_dim: usize, + out_dim: usize, + ) -> void { + var i : usize = 0; + + while (i < out_dim) { + var acc : f64 = 0.0; + var j : usize = 0; + + while (j < in_dim) { + const weight_val = weights[j * out_dim + i] as i8; + + if (weight_val == 1) { + acc = acc + input[j]; + } else if (weight_val == -1) { + acc = acc - input[j]; + } + + j = j + 1; + } + + output[i] = acc; + i = i + 1; + } + } + + // ================================================================= + // 7. phi-RoPE Application + // ========================================================================= + + // apply_rope_qk(buffers, position) -> void + // Apply phi-RoPE rotation to Q and K + // Rotates pairs of dimensions using precomputed tables + fn apply_rope_qk(buffers: *AttentionBuffers, position: usize) -> void { + var h : usize = 0; + + while (h < NUM_HEADS) { + const head_offset = h * HEAD_DIM; + var pair_idx : usize = 0; + + while (pair_idx < ROPE_PAIRS) { + const idx0 = head_offset + pair_idx; + const idx1 = head_offset + pair_idx + ROPE_PAIRS; + + const table_offset = position * ROPE_PAIRS + pair_idx; + const cos_val = rope_tables.cos[table_offset]; + const sin_val = rope_tables.sin[table_offset]; + + // Rotate Q + const q0 = buffers.q_buffer[idx0]; + const q1 = buffers.q_buffer[idx1]; + buffers.q_buffer[idx0] = q0 * cos_val - q1 * sin_val; + buffers.q_buffer[idx1] = q0 * sin_val + q1 * cos_val; + + // Rotate K + const k0 = buffers.k_buffer[idx0]; + const k1 = buffers.k_buffer[idx1]; + buffers.k_buffer[idx0] = k0 * cos_val - k1 * sin_val; + buffers.k_buffer[idx1] = k0 * sin_val + k1 * cos_val; + + pair_idx = pair_idx + 1; + } + + h = h + 1; + } + } + + // ================================================================= + // 8. KV Caching + // ========================================================================= + + // cache_kv(buffers, position, cache_k, cache_v) -> void + // Cache K and V at the current position + fn cache_kv( + buffers: *AttentionBuffers, + position: usize, + cache_k: []f64, cache_v: []f64, + ) -> void { + const offset = position * EMBED_DIM; + var i : usize = 0; + + while (i < EMBED_DIM) { + cache_k[offset + i] = buffers.k_buffer[i]; + cache_v[offset + i] = buffers.v_buffer[i]; + i = i + 1; + } + } + + // ================================================================= + // 9. Score Computation + // ========================================================================= + + // compute_scores(buffers, position, seq_len, cache_k) -> void + // Compute attention scores: Q @ K^T * SACRED_SCALE + // For causal attention: only positions <= current position + fn compute_scores( + buffers: *AttentionBuffers, + position: usize, + seq_len: usize, + cache_k: []f64, + ) -> void { + var h : usize = 0; + + while (h < NUM_HEADS) { + const head_offset = h * HEAD_DIM; + var j : usize = 0; + + while (j < seq_len) { + // Skip if j > position (causal mask) + if (j > position) { + buffers.scores[h * CONTEXT_LEN + j] = 0.0; + j = j + 1; + continue; + } + + // Compute dot product: Q_head * K_head[j] + var score : f64 = 0.0; + var d : usize = 0; + + while (d < HEAD_DIM) { + const q_val = buffers.q_buffer[head_offset + d]; + const k_val = cache_k[j * EMBED_DIM + head_offset + d]; + score = score + q_val * k_val; + d = d + 1; + } + + // Apply sacred scale + buffers.scores[h * CONTEXT_LEN + j] = score * SACRED_SCALE; + j = j + 1; + } + + h = h + 1; + } + } + + // ================================================================= + // 10. Softmax + // ========================================================================= + + // apply_softmax(buffers, seq_len) -> void + // Apply softmax to attention scores for each head + // softmax(x) = exp(x - max) / Sigma exp(x - max) + fn apply_softmax(buffers: *AttentionBuffers, seq_len: usize) -> void { + var h : usize = 0; + + while (h < NUM_HEADS) { + // Find max + var max_score : f64 = -1.0e30; + var j : usize = 0; + + while (j < seq_len) { + const s = buffers.scores[h * CONTEXT_LEN + j]; + if (s > max_score) { + max_score = s; + } + j = j + 1; + } + + // Compute exp and sum + var sum_exp : f64 = 0.0; + j = 0; + + while (j < seq_len) { + const s = exp(buffers.scores[h * CONTEXT_LEN + j] - max_score); + buffers.scores[h * CONTEXT_LEN + j] = s; + sum_exp = sum_exp + s; + j = j + 1; + } + + // Normalize + j = 0; + while (j < seq_len) { + buffers.scores[h * CONTEXT_LEN + j] = + buffers.scores[h * CONTEXT_LEN + j] / sum_exp; + j = j + 1; + } + + h = h + 1; + } + } + + // ================================================================= + // 11. Weighted Value Sum + // ========================================================================= + + // weighted_values(buffers, seq_len, cache_v) -> void + // Compute weighted sum of values: output = Sigma attention[j] * V[j] + fn weighted_values( + buffers: *AttentionBuffers, + seq_len: usize, + cache_v: []f64, + ) -> void { + var h : usize = 0; + + while (h < NUM_HEADS) { + const head_offset = h * HEAD_DIM; + var d : usize = 0; + + while (d < HEAD_DIM) { + var weighted_sum : f64 = 0.0; + var j : usize = 0; + + while (j < seq_len) { + const weight = buffers.scores[h * CONTEXT_LEN + j]; + const v_val = cache_v[j * EMBED_DIM + head_offset + d]; + weighted_sum = weighted_sum + weight * v_val; + j = j + 1; + } + + buffers.concat[head_offset + d] = weighted_sum; + d = d + 1; + } + + h = h + 1; + } + } + + // ================================================================= + // 12. Output Projection + // ========================================================================= + + // project_output(buffers, w_o, output) -> void + // Apply output projection: output = concat @ W_o + // Using ternary weights + fn project_output( + buffers: *AttentionBuffers, + w_o: []Trit, + output: []f64, + ) -> void { + ternary_matmul(buffers.concat, w_o, output, EMBED_DIM, EMBED_DIM); + } + + // ================================================================= + // 13. Residual Connection + // ========================================================================= + + // add_residual(output, input) -> void + // Add residual connection: output = output + input + fn add_residual(output: []f64, input: []f64) -> void { + var i : usize = 0; + + while (i < output.len()) { + output[i] = output[i] + input[i]; + i = i + 1; + } + } + + // ======================================================================================================= + // TDD-Inside-Spec: Tests and Invariants for SacredAttention + // ======================================================================================================= + + test attn_sacred_scaling_constant + given scale = SACRED_SCALE + and expected = pow(81.0, -0.2360679) + then abs(scale - expected) < 0.001 + + test attn_sacred_gamma_is_phi_cubed_inv + given gamma = SACRED_GAMMA + and phi_inv_cubed = pow(constants::PHI_INV, 3.0) + then abs(gamma - phi_inv_cubed) < 0.00001 + + test attn_num_heads_is_trinity + given heads = NUM_HEADS + then heads == 3 + + test attn_head_dim_is_three_pow_four + given dim = HEAD_DIM + then dim == 81 // 3^4 + + test attn_embed_dim_is_heads_times_head_dim + given embed = EMBED_DIM + and computed = NUM_HEADS * HEAD_DIM + then embed == computed + + test attn_rope_pairs_is_context_len_div_two + given pairs = ROPE_PAIRS + and computed = CONTEXT_LEN / 2 + then pairs == computed + + test attn_ternary_matmul_identity + given input = [1.0, 2.0, 3.0, 4.0] + and weights = [Trit.pos, Trit.zero, Trit.zero, Trit.zero, + Trit.zero, Trit.pos, Trit.zero, Trit.zero, + Trit.zero, Trit.zero, Trit.pos, Trit.zero, + Trit.zero, Trit.zero, Trit.zero, Trit.pos] + and output = [0.0; 4] + when ternary_matmul(input, weights, output, 4, 4) + then output[0] == 1.0 and output[1] == 2.0 and output[2] == 3.0 and output[3] == 4.0 + + test attn_ternary_matmul_negation + given input = [1.0, 2.0, 3.0, 4.0] + and weights = [Trit.neg, Trit.neg, Trit.neg, Trit.neg, + Trit.neg, Trit.neg, Trit.neg, Trit.neg, + Trit.neg, Trit.neg, Trit.neg, Trit.neg, + Trit.neg, Trit.neg, Trit.neg, Trit.neg] + and output = [0.0; 4] + when ternary_matmul(input, weights, output, 4, 4) + then output[0] == -10.0 and output[1] == -10.0 and output[2] == -10.0 and output[3] == -10.0 + + test attn_add_residual_identity + given output = [5.0, 10.0, 15.0, 20.0] + and input = [2.0, 4.0, 6.0, 8.0] + when add_residual(output, input) + then output[0] == 7.0 and output[1] == 14.0 and output[2] == 21.0 and output[3] == 28.0 + + test attn_softmax_normalization + given scores = [1.0, 2.0, 3.0, 4.0] + and buffers = AttentionBuffers{...} + when buffers.scores[0..4] = scores + and apply_softmax(&buffers, 4) + and sum = buffers.scores[0] + buffers.scores[1] + buffers.scores[2] + buffers.scores[3] + then abs(sum - 1.0) < 0.0001 + + test attn_softmax_positive + given scores = [1.0, -1.0, 2.0, -2.0] + and buffers = AttentionBuffers{...} + when buffers.scores[0..4] = scores + and apply_softmax(&buffers, 4) + and all_positive = (buffers.scores[0] >= 0.0) and + (buffers.scores[1] >= 0.0) and + (buffers.scores[2] >= 0.0) and + (buffers.scores[3] >= 0.0) + then all_positive == true + + test attn_sacred_scale_range + given scale = SACRED_SCALE + then scale > 0.3 and scale < 0.4 + + test attn_rope_tables_initialized + given tables = rope_tables + and sacred_attention_init() + then tables.cos[0] > 0.0 and tables.cos.len() == CONTEXT_LEN * ROPE_PAIRS + + test attn_cache_kv_stores_values + given buffers = AttentionBuffers{...} + and cache_k = [0.0; EMBED_DIM * CONTEXT_LEN] + and cache_v = [0.0; EMBED_DIM * CONTEXT_LEN] + and buffers.k_buffer = [1.0, 2.0, 3.0] + and buffers.v_buffer = [4.0, 5.0, 6.0] + when cache_kv(&buffers, 0, cache_k, cache_v) + then cache_k[0] == 1.0 and cache_k[1] == 2.0 and cache_v[0] == 4.0 and cache_v[1] == 5.0 + + test attn_compute_scores_applies_scale + given buffers = AttentionBuffers{...} + and cache_k = [0.0; EMBED_DIM * CONTEXT_LEN] + and buffers.q_buffer = [1.0, 1.0, 1.0] + and buffers.k_buffer = [1.0, 1.0, 1.0] + and sacred_attention_init() + when cache_kv(&buffers, 0, cache_k, [0.0; EMBED_DIM * CONTEXT_LEN]) + and compute_scores(&buffers, 0, 1, cache_k) + then buffers.scores[0] == 3.0 * SACRED_SCALE + + invariant attn_sacred_gamma_positive + assert SACRED_GAMMA > 0.0 + + invariant attn_sacred_gamma_less_than_one + assert SACRED_GAMMA < 1.0 + + invariant attn_sacred_scale_positive + assert SACRED_SCALE > 0.0 + + invariant attn_sacred_scale_reasonable + assert SACRED_SCALE > 0.3 and SACRED_SCALE < 0.4 + + invariant attn_num_heads_constant + assert NUM_HEADS == 3 + + invariant attn_head_dim_constant + assert HEAD_DIM == 81 + + invariant attn_embed_dim_constant + assert EMBED_DIM == 243 + + invariant attn_context_len_constant + assert CONTEXT_LEN == 81 + + invariant attn_rope_pairs_constant + assert ROPE_PAIRS == 40 + + invariant attn_embed_dim_equals_heads_times_head_dim + assert EMBED_DIM == NUM_HEADS * HEAD_DIM + + invariant attn_rope_pairs_is_half_context_len + assert ROPE_PAIRS == CONTEXT_LEN / 2 + + invariant attn_ternary_matmul_output_dim + given input = [0.0; 10] + and weights = [Trit.zero; 100] + and output = [0.0; 10] + and ternary_matmul(input, weights, output, 10, 10) + assert output.len() == 10 + + invariant attn_add_residual_preserves_dim + given output = [0.0; 100] + and input = [0.0; 100] + and add_residual(output, input) + assert output.len() == 100 + + invariant attn_softmax_output_probability_distribution + given buffers = AttentionBuffers{...} + and buffers.scores = [1.0, 2.0, 3.0, 4.0] + and apply_softmax(&buffers, 4) + and sum = buffers.scores[0] + buffers.scores[1] + buffers.scores[2] + buffers.scores[3] + and all_non_neg = (buffers.scores[0] >= 0.0) and + (buffers.scores[1] >= 0.0) and + (buffers.scores[2] >= 0.0) and + (buffers.scores[3] >= 0.0) + assert abs(sum - 1.0) < 0.001 and all_non_neg + + invariant attn_rope_cos_in_valid_range + given tables = rope_tables + and sacred_attention_init() + and idx = tables.cos.len() / 2 + then assert tables.cos[idx] >= -1.0 and tables.cos[idx] <= 1.0 + + invariant attn_rope_sin_in_valid_range + given tables = rope_tables + and sacred_attention_init() + and idx = tables.sin.len() / 2 + then assert tables.sin[idx] >= -1.0 and tables.sin[idx] <= 1.0 + + bench attn_ternary_matmul_latency + measure: nanoseconds to ternary_matmul([1.0; 243], [Trit.pos; 243*243], [0.0; 243], 243, 243) + target: < 5000ns + + bench attn_apply_rope_latency + measure: nanoseconds to apply_rope_qk(&buffers, 40) + target: < 2000ns + + bench attn_softmax_latency + measure: nanoseconds to apply_softmax(&buffers, 81) + target: < 3000ns + + bench attn_compute_scores_latency + measure: nanoseconds to compute_scores(&buffers, 40, 41, cache_k) + target: < 10000ns + + bench attn_weighted_values_latency + measure: nanoseconds to weighted_values(&buffers, 41, cache_v) + target: < 8000ns + + bench attn_sacred_attention_kernel_latency + measure: nanoseconds for sacred_attention_kernel on single position + target: < 30000ns +} diff --git a/apps/website/public/t27/specs/nn/hslm.t27 b/apps/website/public/t27/specs/nn/hslm.t27 new file mode 100644 index 0000000000..e79fd77955 --- /dev/null +++ b/apps/website/public/t27/specs/nn/hslm.t27 @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/nn/hslm.t27 +// HSLM (Hierarchical Sacred Learning Model) Specification +// Ternary neural network with sacred constants and VSA attention +// phi^2 + 1/phi^2 = 3 | TRINITY + +module HSLM { + // Import base types, math, numeric, and attention + use base::types; + use base::ops; + use math::constants; + use math::sacred_physics; + use numeric::gf16; + use nn::attention; + + // ================================================================= + // 1. HSLM Configuration + // ========================================================================= + + // TRINITY-based architecture + const NUM_LAYERS : usize = 6; // 6 transformer-like layers + const NUM_HEADS : usize = 3; // 3 attention heads per layer + const HEAD_DIM : usize = 81; // 81 dim per head (3^4) + const EMBED_DIM : usize = 243; // 243 total embedding (3 * 81) + const FF_DIM : usize = 972; // 4 * EMBED_DIM (3^4 * 4) + const CONTEXT_LEN : usize = 81; // Max sequence length + const VSA_DIM : usize = 1024; // VSA hypervector dimension + + // Layer phases + const PHASE_NORM : u8 = 0; // Layer normalization + const PHASE_ATTN : u8 = 1; // Attention block + const PHASE_FFN : u8 = 2; // Feed-forward network + const PHASE_RESIDUAL : u8 = 3; // Residual connection + + // HSLM activation functions + const ACT_RELU : u8 = 0; // ReLU + const ACT_GELU : u8 = 1; // GELU + const ACT_SWISH : u8 = 2; // Swish + const ACT_TERNARY : u8 = 3; // Ternary (sign) + + // HSLM training phases + const PHASE_FORWARD : u8 = 0; // Forward pass + const PHASE_BACKWARD : u8 = 1; // Backward pass + const PHASE_UPDATE : u8 = 2; // Weight update + + // ================================================================= + // 2. HSLM State + // ========================================================================= + + // Training/inference mode + var hslm_mode : u8 = PHASE_FORWARD; + + // Layer buffers (per position, per layer) + struct LayerBuffers { + input : [EMBED_DIM]f64, + output : [EMBED_DIM]f64, + temp : [EMBED_DIM]f64, // For residual connections + ffn_intermediate : [FF_DIM]f64, + } + + // Attention caches (KV cache for causal attention) + struct AttentionCache { + cache_k : [CONTEXT_LEN * EMBED_DIM]f64, + cache_v : [CONTEXT_LEN * EMBED_DIM]f64, + } + + // Layer weights (ternary) + struct LayerWeights { + w_q : [EMBED_DIM * EMBED_DIM]Trit, // Query projection + w_k : [EMBED_DIM * EMBED_DIM]Trit, // Key projection + w_v : [EMBED_DIM * EMBED_DIM]Trit, // Value projection + w_o : [EMBED_DIM * EMBED_DIM]Trit, // Output projection + w1 : [EMBED_DIM * FF_DIM]Trit, // FFN first projection + w2 : [FF_DIM * EMBED_DIM]Trit, // FFN second projection + norm1_gamma : [EMBED_DIM]f64, // RMSNorm scale + norm2_gamma : [EMBED_DIM]f64, // RMSNorm scale + } + + // Full HSLM weights + struct HSLMWeights { + layers : [NUM_LAYERS]LayerWeights, + } + + // ================================================================= + // 3. HSLM Forward Pass + // ========================================================================= + + // hslm_forward(input, weights, seq_len, output, caches) -> void + // Full forward pass through all HSLM layers + fn hslm_forward( + input: [][]f64, // [seq_len][EMBED_DIM] + weights: *HSLMWeights, + seq_len: usize, + output: [][]f64, // [seq_len][EMBED_DIM] + caches: []AttentionCache, // [NUM_LAYERS] + ) -> void { + var position : usize = 0; + + while (position < seq_len) { + // Initialize with input embedding + var layer_input = input[position]; + var layer_output : [EMBED_DIM]f64 = [0.0; EMBED_DIM]; + + // Process through all layers + var layer_idx : usize = 0; + + while (layer_idx < NUM_LAYERS) { + const layer_weights = &weights.layers[layer_idx]; + const layer_cache = &caches[layer_idx]; + + var buffers = LayerBuffers{ + .input = layer_input, + .output = [0.0; EMBED_DIM], + .temp = [0.0; EMBED_DIM], + .ffn_intermediate = [0.0; FF_DIM], + }; + + // Layer forward: norm -> attention -> residual -> norm -> ffn -> residual + hslm_layer_forward(&buffers, layer_weights, position, seq_len, layer_cache); + + layer_input = buffers.output; + layer_output = buffers.output; + + layer_idx = layer_idx + 1; + } + + output[position] = layer_output; + position = position + 1; + } + } + + // hslm_layer_forward(buffers, weights, position, seq_len, cache) -> void + // Single transformer layer: Attention + FFN with residual connections + fn hslm_layer_forward( + buffers: *LayerBuffers, + weights: *LayerWeights, + position: usize, + seq_len: usize, + cache: *AttentionCache, + ) -> void { + // Copy input to output for residual + var i : usize = 0; + while (i < EMBED_DIM) { + buffers.output[i] = buffers.input[i]; + i = i + 1; + } + + // Step 1: RMSNorm before attention + rms_norm_forward(&buffers.output, weights.norm1_gamma); + + // Save attention output for residual + i = 0; + while (i < EMBED_DIM) { + buffers.temp[i] = buffers.output[i]; + i = i + 1; + } + + // Step 2: Multi-head sacred attention + attention::sacred_attention_kernel( + buffers.output, + weights.w_q, weights.w_k, weights.w_v, weights.w_o, + position, seq_len, buffers.output, + cache.cache_k, cache.cache_v, + ); + + // Step 3: Residual connection (attention + input) + i = 0; + while (i < EMBED_DIM) { + buffers.output[i] = buffers.output[i] + buffers.input[i]; + i = i + 1; + } + + // Save attention output for second residual + i = 0; + while (i < EMBED_DIM) { + buffers.input[i] = buffers.output[i]; + i = i + 1; + } + + // Step 4: RMSNorm before FFN + rms_norm_forward(&buffers.output, weights.norm2_gamma); + + // Step 5: Feed-forward network + ffn_forward(&buffers, weights); + + // Step 6: Residual connection (ffn + attention_output) + i = 0; + while (i < EMBED_DIM) { + buffers.output[i] = buffers.output[i] + buffers.input[i]; + i = i + 1; + } + } + + // ================================================================= + // 4. RMS Normalization + // ========================================================================= + + // rms_norm_forward(x, gamma) -> void (in-place) + // RMSNorm: output = (input / sqrt(mean(input^2) + eps)) * gamma + // Uses sacred gamma from math/sacred_physics + fn rms_norm_forward(x: []f64, gamma: []f64) -> void { + // Compute mean of squares + var sum_squares : f64 = 0.0; + var i : usize = 0; + + while (i < x.len()) { + sum_squares = sum_squares + x[i] * x[i]; + i = i + 1; + } + + // rms = sqrt(mean + eps) + const mean = sum_squares / (x.len() as f64); + const rms = sqrt(mean + 1e-6); + + // Normalize and scale by gamma + i = 0; + while (i < x.len()) { + x[i] = (x[i] / rms) * gamma[i]; + i = i + 1; + } + } + + // ================================================================= + // 5. Feed-Forward Network + // ========================================================================= + + // ffn_forward(buffers, weights) -> void + // Feed-forward network: Gelu(x @ W1) @ W2 + // Using ternary weights, sacred expansion + fn ffn_forward(buffers: *LayerBuffers, weights: *LayerWeights) -> void { + // Step 1: Project to FF_DIM: intermediate = input @ W1 + ternary_matmul(buffers.output, weights.w1, buffers.ffn_intermediate, EMBED_DIM, FF_DIM); + + // Step 2: Apply GELU activation + gelu_activation(buffers.ffn_intermediate); + + // Step 3: Project back to EMBED_DIM: output = intermediate @ W2 + ternary_matmul(buffers.ffn_intermediate, weights.w2, buffers.output, FF_DIM, EMBED_DIM); + } + + // ternary_matmul(input, weights, output, in_dim, out_dim) -> void + // Matrix multiplication with ternary weights + fn ternary_matmul( + input: []f64, + weights: []Trit, + output: []f64, + in_dim: usize, + out_dim: usize, + ) -> void { + var i : usize = 0; + + while (i < out_dim) { + var acc : f64 = 0.0; + var j : usize = 0; + + while (j < in_dim) { + const weight_val = weights[j * out_dim + i] as i8; + + if (weight_val == 1) { + acc = acc + input[j]; + } else if (weight_val == -1) { + acc = acc - input[j]; + } + + j = j + 1; + } + + output[i] = acc; + i = i + 1; + } + } + + // gelu_activation(x) -> void (in-place) + // GELU: x * Phi(x) where Phi is standard normal CDF + // Approximation: x * 0.5 * (1 + tanh(sqrt(2/pi) * (x + 0.044715x^3))) + fn gelu_activation(x: []f64) -> void { + const sqrt_2_over_pi = sqrt(2.0 / 3.141592653589793); + var i : usize = 0; + + while (i < x.len()) { + const val = x[i]; + const cube = val * val * val; + const inner = sqrt_2_over_pi * (val + 0.044715 * cube); + const tanh_val = tanh(inner); + x[i] = 0.5 * val * (1.0 + tanh_val); + i = i + 1; + } + } + + // ================================================================= + // 6. HSLM Backward Pass + // ========================================================================= + + // hslm_backward(grad_output, weights, seq_len, grad_input) -> void + // Full backward pass with gradient computation + fn hslm_backward( + grad_output: [][]f64, // [seq_len][EMBED_DIM] + weights: *HSLMWeights, + seq_len: usize, + grad_input: [][]f64, // [seq_len][EMBED_DIM] + ) -> void { + // Initialize weight gradients to zero + var weight_grads = zero_weight_gradients(); + + // Backward through layers (reverse order) + var layer_idx : usize = NUM_LAYERS; + + while (layer_idx > 0) { + layer_idx = layer_idx - 1; + // Layer backward: FFN + Attention gradients + hslm_layer_backward(&weight_grads, layer_idx); + } + } + + // zero_weight_gradients() -> HSLMWeights + // Initialize all weight gradients to zero + fn zero_weight_gradients() -> HSLMWeights { + var weights : HSLMWeights = undefined; + var layer : usize = 0; + + while (layer < NUM_LAYERS) { + // All ternary weight gradients stored as f64 for accumulation + var i : usize = 0; + while (i < EMBED_DIM * EMBED_DIM) { + // In real implementation, would use proper gradient storage + i = i + 1; + } + layer = layer + 1; + } + + return weights; + } + + // hslm_layer_backward(grads, layer_idx) -> void + // Single layer backward: FFN + Attention gradients + fn hslm_layer_backward(grads: *HSLMWeights, layer_idx: usize) -> void { + // Gradient flows backward through: residual -> ffn -> norm -> residual -> attn -> norm + // FFN backward (with residual) + ffn_backward(grads, layer_idx); + + // Attention backward (with residual) + attention_backward(grads, layer_idx); + } + + // ffn_backward(grads, layer_idx) -> void + // Backward through FFN: compute gradients for W2, W1, and input + fn ffn_backward(grads: *HSLMWeights, layer_idx: usize) -> void { + // Step 1: Gradient through second projection (W2) + ffn_backward_w2(grads, layer_idx); + + // Step 2: Gradient through GELU activation + gelu_backward(layer_idx); + + // Step 3: Gradient through first projection (W1) + ffn_backward_w1(grads, layer_idx); + } + + // ffn_backward_w2(grads, layer_idx) -> void + // Gradient through second FFN projection + fn ffn_backward_w2(grads: *HSLMWeights, layer_idx: usize) -> void { + // Gradient: dL/dW2 = dL/dout * x^T + // dL/dx = W2^T * dL/dout + // Implementation depends on stored intermediate values + } + + // gelu_backward(layer_idx) -> void + // Gradient through GELU activation + fn gelu_backward(layer_idx: usize) -> void { + // GELU derivative: dGELU/dx = Phi(x) + x * phi(x) + // where phi is standard normal PDF + } + + // ffn_backward_w1(grads, layer_idx) -> void + // Gradient through first FFN projection + fn ffn_backward_w1(grads: *HSLMWeights, layer_idx: usize) -> void { + // Gradient: dL/dW1 = dL/dmid * x^T + // dL/dx = W1^T * dL/dmid + } + + // attention_backward(grads, layer_idx) -> void + // Gradient through attention block + fn attention_backward(grads: *HSLMWeights, layer_idx: usize) -> void { + // Gradient flows through: output projection -> attention weights -> Q/K/V projections + } + + // ================================================================= + // 7. Phase Management + // ========================================================================= + + // hslm_phase(phase: u8) -> void + // Switch HSLM phase (forward, backward, update) + // Used for streaming inference and training + fn hslm_phase(phase: u8) -> void { + if (phase == PHASE_FORWARD) { + hslm_mode = 1; + } else if (phase == PHASE_BACKWARD) { + hslm_mode = 2; + } else if (phase == PHASE_UPDATE) { + hslm_mode = 3; + } + } + + // get_hslm_mode() -> u8 + // Get current HSLM mode + fn get_hslm_mode() -> u8 { + return hslm_mode; + } + + // ======================================================================================================= + // TDD-Inside-Spec: Tests and Invariants for HSLM + // ======================================================================================================= + + test hslm_num_layers_is_six + given layers = NUM_LAYERS + then layers == 6 + + test hslm_num_heads_is_trinity + given heads = NUM_HEADS + then heads == 3 + + test hslm_head_dim_is_three_pow_four + given dim = HEAD_DIM + then dim == 81 // 3^4 + + test hslm_embed_dim_is_heads_times_head_dim + given embed = EMBED_DIM + and computed = NUM_HEADS * HEAD_DIM + then embed == computed + + test hslm_ff_dim_is_four_times_embed_dim + given ff_dim = FF_DIM + and computed = 4 * EMBED_DIM + then ff_dim == computed + + test hslm_context_len_is_eighty_one + given ctx = CONTEXT_LEN + then ctx == 81 + + test hslm_vsa_dim_is_1024 + given vsa = VSA_DIM + then vsa == 1024 + + test hslm_phase_constants_are_unique + given a = PHASE_NORM and b = PHASE_ATTN and c = PHASE_FFN and d = PHASE_RESIDUAL + then a != b and b != c and c != d + + test hslm_activation_constants_are_unique + given a = ACT_RELU and b = ACT_GELU and c = ACT_SWISH and d = ACT_TERNARY + then a != b and b != c and c != d + + test hslm_training_phase_constants_are_unique + given a = PHASE_FORWARD and b = PHASE_BACKWARD and c = PHASE_UPDATE + then a != b and b != c + + test hslm_phase_forward_sets_mode_one + given hslm_phase(PHASE_FORWARD) + and mode = get_hslm_mode() + then mode == 1 + + test hslm_phase_backward_sets_mode_two + given hslm_phase(PHASE_BACKWARD) + and mode = get_hslm_mode() + then mode == 2 + + test hslm_phase_update_sets_mode_three + given hslm_phase(PHASE_UPDATE) + and mode = get_hslm_mode() + then mode == 3 + + test hslm_rms_norm_preserves_shape + given x = [1.0, 2.0, 3.0, 4.0, 5.0] + and gamma = [1.0, 1.0, 1.0, 1.0, 1.0] + and len_before = x.len() + when rms_norm_forward(x, gamma) + then x.len() == len_before + + test hslm_rms_norm_zero_input_returns_zero + given x = [0.0, 0.0, 0.0] + and gamma = [1.0, 1.0, 1.0] + when rms_norm_forward(x, gamma) + then x[0] == 0.0 and x[1] == 0.0 and x[2] == 0.0 + + test hslm_ternary_matmul_identity + given input = [1.0, 2.0, 3.0] + and weights = [Trit.pos, Trit.zero, Trit.zero, + Trit.zero, Trit.pos, Trit.zero, + Trit.zero, Trit.zero, Trit.pos] + and output = [0.0; 3] + when ternary_matmul(input, weights, output, 3, 3) + then output[0] == 1.0 and output[1] == 2.0 and output[2] == 3.0 + + test hslm_ternary_matmul_negation + given input = [1.0, 2.0, 3.0] + and weights = [Trit.neg, Trit.neg, Trit.neg, + Trit.neg, Trit.neg, Trit.neg, + Trit.neg, Trit.neg, Trit.neg] + and output = [0.0; 3] + when ternary_matmul(input, weights, output, 3, 3) + then output[0] == -6.0 and output[1] == -6.0 and output[2] == -6.0 + + test hslm_gelu_activation_preserves_shape + given x = [1.0, 2.0, -1.0, 0.0] + and len_before = x.len() + when gelu_activation(x) + then x.len() == len_before + + test hslm_gelu_activation_positive_is_positive + given x = [1.0; 10] + when gelu_activation(x) + then x[0] > 0.0 + + test hslm_gelu_activation_zero_is_zero + given x = [0.0] + when gelu_activation(x) + then abs(x[0]) < 0.0001 + + test hslm_gelu_activation_negative_is_negative + given x = [-1.0] + when gelu_activation(x) + then x[0] < 0.0 + + test hslm_ffn_forward_intermediate_dimension + given buffers = LayerBuffers{...} + and weights = LayerWeights{...} + when buffers.output = [1.0; EMBED_DIM] + and ffn_forward(&buffers, &weights) + then buffers.ffn_intermediate.len() == FF_DIM + + test hslm_zero_weight_gradients_initializes_all + given grads = zero_weight_gradients() + then grads.layers.len() == NUM_LAYERS + + invariant hslm_num_layers_constant + assert NUM_LAYERS == 6 + + invariant hslm_num_heads_constant + assert NUM_HEADS == 3 + + invariant hslm_head_dim_constant + assert HEAD_DIM == 81 + + invariant hslm_embed_dim_constant + assert EMBED_DIM == 243 + + invariant hslm_ff_dim_constant + assert FF_DIM == 972 + + invariant hslm_context_len_constant + assert CONTEXT_LEN == 81 + + invariant hslm_vsa_dim_constant + assert VSA_DIM == 1024 + + invariant hslm_embedding_dimensional_consistency + assert EMBED_DIM == NUM_HEADS * HEAD_DIM + + invariant hslm_ffn_expansion_ratio + assert FF_DIM == 4 * EMBED_DIM + + invariant hslm_layer_weights_size + given weights = LayerWeights{...} + and wq_size = weights.w_q.len() + and wk_size = weights.w_k.len() + and wv_size = weights.w_v.len() + and wo_size = weights.w_o.len() + assert wq_size == EMBED_DIM * EMBED_DIM + assert wk_size == EMBED_DIM * EMBED_DIM + assert wv_size == EMBED_DIM * EMBED_DIM + assert wo_size == EMBED_DIM * EMBED_DIM + + invariant hslm_ffn_weights_size + given weights = LayerWeights{...} + and w1_size = weights.w1.len() + and w2_size = weights.w2.len() + assert w1_size == EMBED_DIM * FF_DIM + assert w2_size == FF_DIM * EMBED_DIM + + invariant hslm_rms_norm_gamma_size + given weights = LayerWeights{...} + assert weights.norm1_gamma.len() == EMBED_DIM + assert weights.norm2_gamma.len() == EMBED_DIM + + invariant hslm_ternary_matmul_output_dim + given input = [0.0; 10] + and weights = [Trit.zero; 100] + and output = [0.0; 5] + and ternary_matmul(input, weights, output, 10, 5) + assert output.len() == 5 + + invariant hslm_gelu_monotonic_positive + given x = [0.0, 1.0, 2.0, 3.0] + and gelu_activation(x) + assert x[0] <= x[1] and x[1] <= x[2] and x[2] <= x[3] + + invariant hslm_gelu_smooth + // GELU is C1 continuous (smooth) - verified by lack of discontinuities + given x = [0.0, 0.001, 0.002] + and gelu_activation(x) + and delta1 = x[1] - x[0] + and delta2 = x[2] - x[1] + // Derivative should not have large jumps + assert abs(delta2 - delta1) < 0.01 + + invariant hslm_mode_in_valid_range + given mode = get_hslm_mode() + assert mode >= 1 and mode <= 3 + + bench hslm_rms_norm_latency + measure: nanoseconds to rms_norm_forward([1.0; EMBED_DIM], [1.0; EMBED_DIM]) + target: < 1000ns + + bench hslm_ternary_matmul_embed_to_ff + measure: nanoseconds to ternary_matmul([1.0; EMBED_DIM], [Trit.pos; EMBED_DIM*FF_DIM], [0.0; FF_DIM], EMBED_DIM, FF_DIM) + target: < 5000ns + + bench hslm_gelu_activation_latency + measure: nanoseconds to gelu_activation([1.0; FF_DIM]) + target: < 2000ns + + bench hslm_ffn_forward_latency + measure: nanoseconds for ffn_forward on single layer + target: < 10000ns + + bench hslm_layer_forward_latency + measure: nanoseconds for hslm_layer_forward on single position + target: < 30000ns + + bench hslm_forward_throughput + measure: microseconds for full forward pass on [81, 243] input + target: < 50000us +} diff --git a/apps/website/public/t27/specs/nn/phi_rope.t27 b/apps/website/public/t27/specs/nn/phi_rope.t27 new file mode 100644 index 0000000000..44bb36308e --- /dev/null +++ b/apps/website/public/t27/specs/nn/phi_rope.t27 @@ -0,0 +1,46 @@ +// specs/nn/phi_rope.t27 +// φ-RoPE: Rotary Position Embedding using Golden Ratio +// θ_i = PHI^(-2i/d) instead of standard 10000^(-2i/d) + +algorithm phi_rope { + module: brain.prefrontal.rotary_temporal + + strand_i: { + phi_identity: "theta_i = PHI^(-2i/d)", + numeric_format: GF16, + PHI: 1.6180339887498948 + } + + strand_ii: { + brain_region: prefrontal_cortex, + biological_analog: "mPFC temporal context encoding" + } + + strand_iii: { + t27_target: "nn/phi_rope", + backends: [rust, zig, c, verilog] + } + + complexity: O(d) + + inputs: [x: Float[seq, dim], position: Int] + outputs: [x_rotated: Float[seq, dim]] + + notes: | + for i in 0..dim/2: + theta = PHI.pow(-2.0 * i / dim) + angle = position * theta + x_rotated[2i] = x[2i]*cos(angle) - x[2i+1]*sin(angle) + x_rotated[2i+1] = x[2i]*sin(angle) + x[2i+1]*cos(angle) + + invariants: [orthogonality_preserved, phi_decay_monotone] + + tests: [ + { input: pos=0, assert: x_rotated == x }, + { input: dim=81, assert: theta[0] == 1.0 }, + { input: dim=4, theta: [1.0, 0.886, 0.618, 0.486] } + ] +} + test "phi_rope_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/nn/sacred_attention.t27 b/apps/website/public/t27/specs/nn/sacred_attention.t27 new file mode 100644 index 0000000000..8dcde6436f --- /dev/null +++ b/apps/website/public/t27/specs/nn/sacred_attention.t27 @@ -0,0 +1,49 @@ +// specs/nn/sacred_attention.t27 +// Sacred Attention: Multi-head attention with φ-based scaling +// scale = head_dim^(-PHI^3) instead of standard 1/sqrt(head_dim) + +algorithm sacred_attention { + module: brain.prefrontal.parallel_attention + + strand_i: { + phi_identity: "scale = head_dim^(-PHI^3)", + n_heads: 3, + head_dim: 81, + embed_dim: 243, + numeric_format: GF16 + } + + strand_ii: { + brain_region: prefrontal_cortex, + biological_analog: "dlPFC parallel attention streams" + } + + strand_iii: { + t27_target: "nn/sacred_attention", + backends: [rust, zig, c, verilog] + } + + complexity: O(n^2 * d) + + inputs: [x: Float[B, seq, 243]] + outputs: [out: Float[B, seq, 243]] + + depends: [phi_rope] + + notes: | + scale = head_dim^(-PHI^3) + Q,K,V = x @ Wq, x @ Wk, x @ Wv + scores = (Q @ K^T) * scale + attn = softmax(scores) + out = (attn @ V) @ Wo + + invariants: [scale_in_0_1, n_heads_times_head_dim_eq_embed_dim] + + tests: [ + { assert: N_HEADS * HEAD_DIM == EMBED_DIM }, + { assert: sacred_scale() > 0.0 && sacred_scale() < 1.0 } + ] +} + test "sacred_attention_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/numeric/bigint.t27 b/apps/website/public/t27/specs/numeric/bigint.t27 new file mode 100644 index 0000000000..17500aaa43 --- /dev/null +++ b/apps/website/public/t27/specs/numeric/bigint.t27 @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: Apache-2.0 +// Module: Balanced Ternary BigInt +// phi^2 + 1/phi^2 = 3 | TRINITY + +module BigInt { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; // Trit enum for trit values + use numeric::gf16; // GF16 for intermediate values + + // ======================================================================== + // 1. Trit Type Constants + // ======================================================================== + + // Trit values for balanced ternary representation + pub const TRIT_NEG : i8 = -1; + pub const TRIT_ZERO : i8 = 0; + pub const TRIT_POS : i8 = 1; + + // Maximum number of trits in BigInt + pub const MAX_TRITS : u16 = 256; + + // Number of SIMD chunks (MAX_TRITS / 32) + pub const SIMD_CHUNKS : u16 = 8; + + // Threshold for switching between simple and Karatsuba multiplication + pub const KARATSUBA_THRESHOLD : u16 = 32; + + // ======================================================================== + // 2. BigInt Type + // ======================================================================== + + // TVCBigInt: Arbitrary precision integer in balanced ternary + // Stores trits as array with least significant first + // Length tracks number of significant trits (excluding leading zeros) + pub struct TVCBigInt { + trits : [MAX_TRITS]i8, // Trit array (LSB first) + len : usize, // Number of significant trits + } + + // ======================================================================== + // 3. Construction Functions + // ======================================================================== + + // zero() -> TVCBigInt + // Create zero BigInt + // Returns BigInt with len=1, trits[0]=0 + // Complexity: O(1) + pub fn zero() -> TVCBigInt; + + // fromI64(value: i64) -> TVCBigInt + // Create BigInt from signed 64-bit integer + // Converts using balanced ternary division + // Returns BigInt with normalized trits + // Complexity: O(n) where n = number of trits needed + pub fn fromI64(value: i64) -> TVCBigInt; + + // ======================================================================== + // 4. Conversion Functions + // ======================================================================== + + // toI64(bigint: &TVCBigInt) -> i64 + // Convert BigInt to signed 64-bit integer + // May overflow for large numbers + // Complexity: O(n) where n = bigint.len + pub fn toI64(bigint: &TVCBigInt) -> i64; + + // normalize(bigint: &TVCBigInt) + // Remove leading zero trits + // Updates len to exclude leading zeros + // Complexity: O(n) where n = current len + pub fn normalize(bigint: &TVCBigInt); + + // ======================================================================== + // 5. Comparison Functions + // ======================================================================== + + // isZero(bigint: &TVCBigInt) -> bool + // Check if BigInt equals zero + // Returns true if len=1 and trits[0]=0 + // Complexity: O(1) + pub fn isZero(bigint: &TVCBigInt) -> bool; + + // isNegative(bigint: &TVCBigInt) -> bool + // Check if BigInt is negative + // In balanced ternary, sign is most significant non-zero trit + // Complexity: O(1) + pub fn isNegative(bigint: &TVCBigInt) -> bool; + + // compareAbs(a: &TVCBigInt, b: &TVCBigInt) -> i8 + // Compare absolute values of two BigInts + // Returns: -1 if |a| < |b|, 0 if equal, 1 if |a| > |b| + // Complexity: O(n) where n = max(len of a, len of b) + pub fn compareAbs(a: &TVCBigInt, b: &TVCBigInt) -> i8; + + // ======================================================================== + // 6. Arithmetic Operations + // ======================================================================== + + // addScalar(a: &TVCBigInt, b: &TVCBigInt) -> TVCBigInt + // Add two BigInts (scalar implementation) + // Uses balanced ternary addition with carry propagation + // Returns normalized sum + // Complexity: O(n) where n = max(len of a, len of b) + pub fn addScalar(a: &TVCBigInt, b: &TVCBigInt) -> TVCBigInt; + + // sub(a: &TVCBigInt, b: &TVCBigInt) -> TVCBigInt + // Subtract b from a + // Implemented as a + negate(b) + // Complexity: O(n) where n = max(len of a, len of b) + pub fn sub(a: &TVCBigInt, b: &TVCBigInt) -> TVCBigInt; + + // negate(bigint: &TVCBigInt) -> TVCBigInt + // Negate BigInt (flip all trit signs) + // Used for subtraction: a - b = a + negate(b) + // Complexity: O(n) where n = bigint.len + pub fn negate(bigint: &TVCBigInt) -> TVCBigInt; + + // abs(bigint: &TVCBigInt) -> TVCBigInt + // Get absolute value + // Returns bigint if non-negative, else negated bigint + // Complexity: O(n) where n = bigint.len + pub fn abs(bigint: &TVCBigInt) -> TVCBigInt; + + // ======================================================================== + // 7. Multiplication Operations + // ======================================================================== + + // mulSimple(a: &TVCBigInt, b: &TVCBigInt) -> TVCBigInt + // Grade-school multiplication algorithm + // Uses O(n*m) elementary multiplications with carry + // Suitable for small numbers (below KARATSUBA_THRESHOLD) + // Complexity: O(n*m) where n,m are operand lengths + pub fn mulSimple(a: &TVCBigInt, b: &TVCBigInt) -> TVCBigInt; + + // mulKaratsuba(a: &TVCBigInt, b: &TVCBigInt) -> TVCBigInt + // Karatsuba multiplication for large numbers + // Splits numbers, computes partial products, combines efficiently + // Complexity: O(n^1.585) vs O(n^2) for simple multiplication + // Only used for numbers above KARATSUBA_THRESHOLD + pub fn mulKaratsuba(a: &TVCBigInt, b: &TVCBigInt) -> TVCBigInt; + + // mul(a: &TVCBigInt, b: &TVCBigInt) -> TVCBigInt + // Multiply two BigInts with optimal algorithm + // Uses Karatsuba for large numbers, simple for small numbers + // Complexity: O(n^1.585) for large, O(n*m) for small + pub fn mul(a: &TVCBigInt, b: &TVCBigInt) -> TVCBigInt; + + // ======================================================================== + // TDD - Tests + // ======================================================================== + + test bigint_zero_is_zero + // Verify: zero() returns BigInt equal to zero + given result = zero() + when is_zero = isZero(&result) + then is_zero and result.len == 1 + + test bigint_from_i64_zero + // Verify: fromI64(0) creates zero BigInt + given result = fromI64(0) + then result.len == 1 and isZero(&result) + + test bigint_from_i64_positive + // Verify: fromI64 creates correct positive BigInt + given result = fromI64(42) + then result.len > 1 and !isNegative(&result) + + test bigint_from_i64_negative + // Verify: fromI64 creates correct negative BigInt + given result = fromI64(-42) + then result.len > 1 and isNegative(&result) + + test bigint_normalize_removes_leading_zeros + // Verify: normalize removes leading zeros + given result = fromI64(27) + and original_len = result.len + when normalize(&result) + then result.len <= original_len + + test bigint_is_zero_after_normalize + // Verify: normalize sets single zero for zero value + given result = zero() + when normalize(&result) + then result.len == 1 + + test bigint_negate_flips_signs + // Verify: negate flips all trit values + given positive = fromI64(7) + and negative = negate(&positive) + then positive.len == negative.len + + test bigint_negate_zero_is_zero + // Verify: negate(0) equals zero + given result = negate(&zero()) + then isZero(&result) + + test bigint_add_commutes + // Verify: a + b = b + a + given a = fromI64(5) + and b = fromI64(7) + and ab = addScalar(&a, &b) + and ba = addScalar(&b, &a) + then compareAbs(&ab, &ba) == 0 + + test bigint_add_zero_identity + // Verify: a + 0 = a + given a = fromI64(42) + and zero_val = zero() + when result = addScalar(&a, &zero_val) + then compareAbs(&result, &a) == 0 + + test bigint_sub_reverses_addition + // Verify: a - b reverses a + b + given a = fromI64(42) + and b = fromI64(7) + and diff = sub(&a, &b) + and sum = addScalar(&b, &diff) + then compareAbs(&sum, &a) == 0 + + test bigint_mul_by_zero_returns_zero + // Verify: a * 0 = 0 + given a = fromI64(42) + and zero_val = zero() + when result = mul(&a, &zero_val) + then isZero(&result) + + test bigint_mul_commutates + // Verify: a * b = b * a + given a = fromI64(3) + and b = fromI64(5) + and ab = mul(&a, &b) + and ba = mul(&b, &a) + then compareAbs(&ab, &ba) == 0 + + test bigint_compare_abs_returns_correct_comparison + // Verify: compareAbs returns correct comparison + given a = fromI64(5) + and b = fromI64(7) + when cmp = compareAbs(&a, &b) + then cmp < 0 and cmp != 0 + + test bigint_abs_returns_positive_for_negative + // Verify: abs returns positive for negative input + given negative = fromI64(-42) + and positive = abs(&negative) + then compareAbs(&positive, &negative) == 1 + + test bigint_to_i64_roundtrip_small + // Verify: toI64(fromI64(x)) returns x for small values + given original = 42 + and big = fromI64(original) + and result = toI64(&big) + then result == original + + // ======================================================================== + // TDD - Invariants + // ======================================================================== + + invariant zero_is_additive_identity + // Verify: Adding zero returns same value + // This is verified by bigint_add_zero_identity test + assert true; + + invariant add_commutativity + // Verify: Addition is commutative + // This is verified by bigint_add_commutes test + assert true; + + invariant negation_involves_sign_flip + // Verify: Negating flips all trit values + // This is verified by bigint_negate_flips_signs test + assert true; + + invariant zero_negation_is_zero + // Verify: Negating zero returns zero + // This is verified by bigint_negate_zero_is_zero test + assert true; + + invariant mul_commutativity + // Verify: Multiplication is commutative + // This is verified by bigint_mul_commutates test + assert true; + + invariant mul_zero_is_absorbing + // Verify: Multiplying by zero returns zero + // This is verified by bigint_mul_by_zero_returns_zero test + assert true; + + invariant subtraction_definition + // Verify: Subtraction is defined as a + negate(b) + // a - b should equal a + negate(b) + assert true; + + invariant normalization_preserves_value + // Verify: Normalization preserves numeric value + // Leading zeros are cosmetic, value unchanged + assert true; + + invariant trit_values_in_valid_range + // Verify: All trit values are -1, 0, or +1 + // Balanced ternary constraint + assert true; + + invariant max_trits_enforces_capacity + // Verify: MAX_TRITS enforces storage capacity + // Operations must respect 256-trit limit + assert true; + + // ======================================================================== + // TDD - Benchmarks + // ======================================================================== + + bench bigint_from_i64_latency + // Measure: cycles to convert i64 to BigInt (small value) + // Target: < 500 cycles (division algorithm) + @setEvalBranchQuota(10000); + var result = fromI64(42); + _ = result; + + bench bigint_to_i64_latency + // Measure: cycles to convert BigInt to i64 (small value) + // Target: < 300 cycles (traversal + multiplication) + @setEvalBranchQuota(10000); + var big = fromI64(42); + _ = toI64(&big); + _ = big; + + bench bigint_add_latency_32_trits + // Measure: cycles to add two 32-trit BigInts + // Target: < 5000 cycles (carry propagation) + @setEvalBranchQuota(10000); + var a = fromI64(1000000); + var b = fromI64(1000000); + _ = addScalar(&a, &b); + _ = a; + + bench bigint_sub_latency_32_trits + // Measure: cycles to subtract two 32-trit BigInts + // Target: < 5000 cycles (negate + add) + @setEvalBranchQuota(10000); + var a = fromI64(2000000); + var b = fromI64(1000000); + _ = sub(&a, &b); + _ = a; + + bench bigint_mul_simple_latency_16_trits + // Measure: cycles for simple multiplication (16-trit operands) + // Target: < 10000 cycles (grade school O(n*m)) + @setEvalBranchQuota(10000); + var a = fromI64(10000); + var b = fromI64(100); + _ = mulSimple(&a, &b); + _ = a; + + bench bigint_mul_karatsuba_latency_64_trits + // Measure: cycles for Karatsuba multiplication (64-trit operands) + // Target: < 30000 cycles (O(n^1.585) complexity) + @setEvalBranchQuota(10000); + var a = fromI64(10000000000000000); + var b = fromI64(10000000000000000); + _ = mul(&a, &b); + _ = a; + + bench bigint_normalize_latency_64_trits + // Measure: cycles to normalize 64-trit BigInt + // Target: < 200 cycles (single pass) + @setEvalBranchQuota(10000); + var big = fromI64(10000000000000000); + _ = normalize(&big); + _ = big; + + bench bigint_negate_latency_64_trits + // Measure: cycles to negate 64-trit BigInt + // Target: < 200 cycles (single pass) + @setEvalBranchQuota(10000); + var big = fromI64(10000000000000000); + _ = negate(&big); + _ = big; + + bench bigint_compare_latency_64_trits + // Measure: cycles to compare two 64-trit BigInts + // Target: < 1000 cycles (abs + compare) + @setEvalBranchQuota(10000); + var a = fromI64(10000000000000000); + var b = fromI64(10000000000000000); + _ = compareAbs(&a, &b); + _ = a; +} diff --git a/apps/website/public/t27/specs/numeric/formats.t27 b/apps/website/public/t27/specs/numeric/formats.t27 new file mode 100644 index 0000000000..8944e3ed48 --- /dev/null +++ b/apps/website/public/t27/specs/numeric/formats.t27 @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/numeric/formats.t27 +// Format Conversion Utilities - GF16, f32, ternary encoding +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Formats { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; + use numeric::gf16; + + // ======================================================================== + // 1. GF16 Bit Layout Constants + // ======================================================================== + // + // GF16 bit layout (as specified in whitepaper): + // [S(1) E(6) M(9)] = [15:15][14:9][8:0] + // + // - Sign: bit 15 (0x8000) + // - Exponent: bits 14-9 (0x7E00), bias = 31 + // - Mantissa: bits 8-0 (0x01FF) + // + // Range: 2^-31 to 2^32 + // ======================================================================== + + pub const SignMask : u16 = 0x8000; + pub const ExpMask : u16 = 0x7E00; + pub const MantMask : u16 = 0x01FF; + + pub const ExpShift : u5 = 9; + pub const SignShift : u4 = 15; + pub const Bias : i32 = 31; + + pub const ExpMax : u16 = 63; + pub const ExpMin : u16 = 0; + + // ======================================================================== + // 2. GF16 -> f32 (decode) + // ======================================================================== + // + // Converts GF16 encoding to IEEE 754 binary32 floating point. + // Handles signed zero, denormals, normals, infinities, and NaN. + // ======================================================================== + + // gf16_to_f32(x: u16) -> gf16 + // Decode GF16 to f32 + // + // Algorithm: + // 1. Extract sign (bit 15) + // 2. Extract exponent (bits 14-9) and mantissa (bits 8-0) + // 3. Handle special cases: + // - e=0, m=0: signed zero + // - e=0, m!=0: denormal (subnormal) + // - e=ExpMax, m=0: +/- infinity + // - e=ExpMax, m!=0: NaN + // 4. Normal case: + // value = (-1)^s * (1 + m/2^9) * 2^(e - Bias) + // + // Complexity: O(1) + pub fn gf16_to_f32(x: u16) -> gf16; + + // ======================================================================== + // 3. f32 -> GF16 (encode, round-to-nearest) + // ======================================================================== + // + // Converts IEEE 754 binary32 floating point to GF16 encoding. + // Handles signed zero, special cases, overflow, and underflow. + // + // Algorithm: + // 1. Handle signed zero explicitly (sign bit preserved) + // 2. Handle special cases (Inf, NaN) + // 3. Get exponent and mantissa via frexp: abs = m * 2^e, m in [0.5, 1] + // 4. Normalize: want 1.x * 2^(E - Bias), frexp gives m in [0.5, 1] + // 5. Mantissa: (m - 1.0) * 2^9, round to nearest + // 6. Check underflow/overflow + // + // Complexity: O(1) + pub fn f32_to_gf16(a: f32) -> u16; + + // ======================================================================== + // 4. Ternary Quantization + // ======================================================================== + // + // Ternary quantization: maps f32 to ternary {-1, 0, +1} + // Threshold: |w| > 0.5 -> +/-1, else -> 0 + // + // WHY: Enables efficient ternary representation of continuous values + // Useful for VSA (Vector Symbolic Architecture) operations + // ======================================================================== + + // f32_to_ternary(x: f32) -> Trit + // Quantize f32 to ternary {-1, 0, +1} + // + // Algorithm: + // - If x > 0.5: return +1 + // - If x < -0.5: return -1 + // - Otherwise: return 0 + // + // Complexity: O(1) + pub fn f32_to_ternary(x: f32) -> Trit; + + // ternary_to_f32(t: Trit) -> gf16 + // Convert ternary to f32 + // + // Mapping: -1 -> -1.0, 0 -> 0.0, +1 -> 1.0 + // Complexity: O(1) + pub fn ternary_to_f32(t: Trit) -> gf16; + + // ======================================================================== + // 5. Format Enum + // ======================================================================== + + pub const Format = enum(u8) { + fp32, + fp16, + bf16, + gf16, + ternary, + }; + + // format_bytes(fmt: Format) -> usize + // Returns byte size for each format + // + // Complexity: O(1) + pub fn format_bytes(fmt: Format) -> usize; + + // ======================================================================== + // 6. Quantization Utility + // ======================================================================== + + // quantize_value(x: f32, fmt: Format) -> gf16 + // Quantize f32 to target format + // + // Complexity: O(1) + pub fn quantize_value(x: f32, fmt: Format) -> gf16; + + // ======================================================================== + // TDD - Tests + // ======================================================================== + + test gf16_to_f32_zero_positive + // Verify: zero encodes to zero + given x: u16 = 0 + when result = gf16_to_f32(x) + then gf16.to_f64(result) == 0.0 + + test gf16_to_f32_zero_negative + // Verify: negative zero encodes to -0 + given x: u16 = 0x8000 + when result = gf16_to_f32(x) + then gf16.to_f64(result) == -0.0 + + test gf16_to_f32_denormal + // Verify: denormal value decodes to small positive + given x: u16 = 0x0080 + when result = gf16_to_f32(x) + and val = gf16.to_f64(result) + then val > 0.0 and val < 1.0 + + test gf16_to_f32_normal_one + // Verify: 1.0 encodes correctly + given x: u16 = 0x3C00 + when result = gf16_to_f32(x) + and decoded = gf16.to_f64(result) + then decoded == 1.0 + + test gf16_to_f32_positive_inf + // Verify: positive infinity encodes correctly + given x: u16 = 0x7E00 + when result = gf16_to_f32(x) + and decoded = gf16.to_f64(result) + then decoded == std.math.inf(f32) + + test gf16_to_f32_negative_inf + // Verify: negative infinity encodes correctly + given x: u16 = 0xFE00 + when result = gf16_to_f32(x) + and decoded = gf16.to_f64(result) + then decoded == -std.math.inf(f32) + + test gf16_to_f32_nan + // Verify: NaN encodes to NaN + given x: u16 = 0x7F01 + when result = gf16_to_f32(x) + then result != result // NaN check + + test f32_to_gf16_zero_positive + // Verify: +0 encodes to 0 + given a: f32 = 0.0 + when result = f32_to_gf16(a) + then result == 0 + + test f32_to_gf16_zero_negative + // Verify: -0 encodes to 0x8000 + given a: f32 = -0.0 + when result = f32_to_gf16(a) + then result == 0x8000 + + test f32_to_gf16_one + // Verify: 1.0 encodes and roundtrips correctly + given a: f32 = 1.0 + and encoded = f32_to_gf16(a) + and decoded = gf16_to_f32(encoded) + and recovered = gf16.to_f64(decoded) + then recovered >= 0.99 and recovered <= 1.01 + + test f32_to_gf16_inf_positive + // Verify: +Inf encodes to 0x7E00 + given a: f32 = std.math.inf(f32) + when result = f32_to_gf16(a) + then result == 0x7E00 + + test f32_to_gf16_inf_negative + // Verify: -Inf encodes to 0xFE00 + given a: f32 = -std.math.inf(f32) + when result = f32_to_gf16(a) + then result == 0xFE00 + + test f32_to_gf16_nan + // Verify: NaN encodes to 0x7F01 + given a: f32 = std.math.nan(f32) + when result = f32_to_gf16(a) + then result == 0x7F01 + + test f32_to_ternary_positive + // Verify: 1.0 quantizes to pos + given a: f32 = 1.0 + when result = f32_to_ternary(a) + then result == .pos + + test f32_to_ternary_zero + // Verify: 0.0 quantizes to zero + given a: f32 = 0.0 + when result = f32_to_ternary(a) + then result == .zero + + test f32_to_ternary_negative + // Verify: -1.0 quantizes to neg + given a: f32 = -1.0 + when result = f32_to_ternary(a) + then result == .neg + + test f32_to_ternary_threshold + // Verify: 0.6 quantizes to pos (above 0.5 threshold) + given a: f32 = 0.6 + when result = f32_to_ternary(a) + then result == .pos + + test f32_to_ternary_negative_threshold + // Verify: -0.6 quantizes to neg (below -0.5 threshold) + given a: f32 = -0.6 + when result = f32_to_ternary(a) + then result == .neg + + test ternary_to_f32_positive + // Verify: pos maps to 1.0 + given t: Trit = .pos + when result = ternary_to_f32(t) + and result_val = gf16.to_f64(result) + then result_val == 1.0 + + test ternary_to_f32_zero + // Verify: zero maps to 0.0 + given t: Trit = .zero + when result = ternary_to_f32(t) + and result_val = gf16.to_f64(result) + then result_val == 0.0 + + test ternary_to_f32_negative + // Verify: neg maps to -1.0 + given t: Trit = .neg + when result = ternary_to_f32(t) + and result_val = gf16.to_f64(result) + then result_val == -1.0 + + test format_bytes_fp32 + // Verify: fp32 is 4 bytes + when result = format_bytes(.fp32) + then result == 4 + + test format_bytes_fp16 + // Verify: fp16 is 2 bytes + when result = format_bytes(.fp16) + then result == 2 + + test format_bytes_ternary + // Verify: ternary is 1 byte + when result = format_bytes(.ternary) + then result == 1 + + test quantize_value_fp32 + // Verify: quantizing to fp32 preserves value + given x: f32 = 1.5 + and result = quantize_value(x, .fp32) + and result_val = gf16.to_f64(result) + then result_val >= 1.49 and result_val <= 1.51 + + test quantize_value_ternary + // Verify: quantizing to ternary gives +1 + given x: f32 = 1.5 + and result = quantize_value(x, .ternary) + and expected = ternary_to_f32(.pos) + then result == expected + + // ======================================================================== + // TDD - Invariants + // ======================================================================== + + invariant gf16_to_f32_preserves_zero + // Zero should decode to zero + assert gf16.to_f64(gf16_to_f32(0)) == 0.0; + assert gf16.to_f64(gf16_to_f32(0x8000)) == -0.0; + + invariant gf16_to_f32_preserves_infinity + // Infinity should decode to infinity + assert gf16.to_f64(gf16_to_f32(0x7E00)) == std.math.inf(f32); + assert gf16.to_f64(gf16_to_f32(0xFE00)) == -std.math.inf(f32); + + invariant f32_to_gf16_roundtrip_loss + // Roundtrip should be within tolerance for normal values + const original = gf16.from_f64(1.5); + const encoded = f32_to_gf16(gf16.to_f64(original)); + const decoded = gf16_to_f32(encoded); + const error = gf16.abs(gf16.sub(original, decoded)); + assert gf16.to_f64(error) < gf16.from_f64(0.01); + + invariant ternary_quantization_symmetric + // Quantization threshold is symmetric + const p = f32_to_ternary(0.5); + const n = f32_to_ternary(-0.5); + const p_decoded = ternary_to_f32(p); + const n_decoded = ternary_to_f32(n); + assert gf16.to_f64(p_decoded) == -gf16.to_f64(n_decoded); + + invariant ternary_to_f32_is_inverse + // ternary_to_f32 is inverse of f32_to_ternary + const values = [_]f32{ -1.0, -0.5, 0.0, 0.5, 1.0 }; + for (values) |v| { + const t = f32_to_ternary(v); + const recovered = ternary_to_f32(t); + const diff = gf16.abs(gf16.sub(v, recovered)); + assert gf16.eq(v, recovered) or (gf16.to_f64(diff) < gf16.from_f64(0.01)); + } + + invariant format_bytes_positive + // All format byte sizes should be positive + assert format_bytes(.fp32) > 0; + assert format_bytes(.fp16) > 0; + assert format_bytes(.ternary) > 0; + + // ======================================================================== + // TDD - Benchmarks + // ======================================================================== + + bench gf16_to_f32_latency + // Measure: cycles for gf16_to_f32 conversion + // Target: < 50 cycles (simple bit extraction + lookup) + @setEvalBranchQuota(10000); + var result : gf16; + const test_value: u16 = 0x3C00; + for (0..1000) |_| { + result = gf16_to_f32(test_value); + } + _ = result; + + bench f32_to_gf16_latency + // Measure: cycles for f32_to_gf16 conversion + // Target: < 100 cycles (frexp + bit packing) + @setEvalBranchQuota(10000); + var result : u16; + const test_value: f32 = 1.5; + for (0..1000) |_| { + result = f32_to_gf16(test_value); + } + _ = result; + + bench f32_to_ternary_latency + // Measure: cycles for f32_to_ternary conversion + // Target: < 10 cycles (single comparison) + @setEvalBranchQuota(10000); + var result : Trit; + const test_value: f32 = 0.75; + for (0..1000) |_| { + result = f32_to_ternary(test_value); + } + _ = result; + + bench ternary_to_f32_latency + // Measure: cycles for ternary_to_f32 conversion + // Target: < 10 cycles (simple switch) + @setEvalBranchQuota(10000); + var result : gf16; + const test_trit: Trit = .pos; + for (0..1000) |_| { + result = ternary_to_f32(test_trit); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/numeric/gf12.t27 b/apps/website/public/t27/specs/numeric/gf12.t27 new file mode 100644 index 0000000000..023d72591f --- /dev/null +++ b/apps/website/public/t27/specs/numeric/gf12.t27 @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf12.t27 +// GoldenFloat12 -- 12-bit phi-structured floating point +// NUMERIC-STANDARD-001 -- Agent 4 (P1) + +module GF12 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // ================================================================= + // 1. Format Definition + // ========================================================================= + + // GF12 bit layout: [S|EEEE|MMM MMMM] + // S: 1 bit (sign) + // E: 4 bits (exponent) + // M: 7 bits (mantissa) + + const BITS : u8 = 12; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 4; + const MANT_BITS : u8 = 7; + + // Bias for exponent (2^(4-1) - 1 = 7) + const EXP_BIAS : u8 = 7; + + // phi-ratio: exp/mant = 4/7 ~= 0.571 (phi_distance = 0.047) + // This is the closest to 1/phi among all formats + const PHI_DISTANCE : f64 = 0.04660512288042107; + + // ================================================================= + // 2. GoldenFloat12 Type + // ========================================================================= + + struct GF12 { + raw : u16, // 12-bit value stored in u16 + } + + // ================================================================= + // 3. Encoding/Decoding + // ========================================================================= + + // Encode f32 to GF12 + fn encode(value: f32) -> GF12 { + if (value == 0.0) { + return GF12{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i8; + const exp_biased = (exp_unbiased + EXP_BIAS as i8) as u8; + + // Clamp exponent + const exp_clamped = clamp(exp_biased, 0, (1 << EXP_BITS) - 1); + + // Extract mantissa (7 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF12{ + raw = ((sign as u16) << 11) | ((exp_clamped as u16) << MANT_BITS) | (mant as u16) + }; + } + + // Decode GF12 to f32 + fn decode(gf: GF12) -> f32 { + const sign = (gf.raw >> 11) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x0F) as u8; + const mant = (gf.raw & 0x7F) as u8; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS as i8 + 1 + } else { + (exp_biased as i8) - EXP_BIAS as i8 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 128.0 + } else { + 1.0 + (mant as f32) / 128.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // ================================================================= + // 4. Format Properties + // ========================================================================= + + fn max_value() -> f32 { + const mant_max = 1.0 + 127.0 / 128.0; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 128.0; + const exp_min = -EXP_BIAS as i8 + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 128.0; // 0.0078125 + } + + // ================================================================= + // 5. Validation + // ========================================================================= + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF12"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // ================================================================= + // 6. Use Cases + // ========================================================================= + + // GF12 is optimal for: + // - Best phi-approximation (lowest phi_distance) + // - High-precision quantization + // - Critical path weights + // - Attention matrices + + // Memory: 12 bits = 1.5 bytes (~2.67x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 12.0 / 32.0; // 0.375 + + // ================================================================= + // 7. Helper Functions + // ========================================================================= + + fn floor_log2(x: f32) -> i8 { + if (x <= 0.0) { return -128; } + let exp : i8 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i8, mant_bits: u8) -> u8 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u8; + } + + fn clamp(x: u8, min: u8, max: u8) -> u8 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF12 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // ======================================================================================================= + // TDD-Inside-Spec: Tests and Invariants for GF12 + // ======================================================================================================= + + test gf12_decode_zero + given gf = GF12{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf12_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf12_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf12_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf12_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf12_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf12_phi_distance_lowest + given phi_dist = PHI_DISTANCE + then phi_dist < 0.05 + + test gf12_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.375) < 0.01 + + test gf12_validate_format_success + given valid = validate_format() + then valid == true + + test gf12_floor_log2_power_of_two + given log_result = floor_log2(8.0) + then log_result == 3 + + test gf12_extract_mantissa_in_range + given mant = extract_mantissa(1.5, 0, 7) + then mant < 128 + + invariant gf12_bits_constant + assert BITS == 12 + + invariant gf12_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf12_exp_bits_is_four + assert EXP_BITS == 4 + + invariant gf12_mant_bits_is_seven + assert MANT_BITS == 7 + + invariant gf12_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf12_phi_distance_below_threshold + assert PHI_DISTANCE < 0.05 + + invariant gf12_exp_bias_positive + assert EXP_BIAS > 0 + + test gf12_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf12_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf12_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf12_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf12_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf12_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf12_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf12_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf12_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf12_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf12_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf12_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf12_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf12_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf12_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf12_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf12_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf12_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf12_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf12_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf12_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf12_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf12_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf12_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf12_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + invariant gf12_floor_log2_non_negative_input + assert floor_log2(1.0) >= 0 + + invariant gf12_extract_mantissa_in_valid_range + assert extract_mantissa(1.0, 0, 7) < 128 + + bench gf12_encode_latency + measure: nanoseconds to encode(1.0) + target: < 150ns + + bench gf12_decode_latency + measure: nanoseconds to decode(GF12{raw = 1024}) + target: < 100ns +} diff --git a/apps/website/public/t27/specs/numeric/gf16.t27 b/apps/website/public/t27/specs/numeric/gf16.t27 new file mode 100644 index 0000000000..b0ea5bf609 --- /dev/null +++ b/apps/website/public/t27/specs/numeric/gf16.t27 @@ -0,0 +1,3391 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf16.t27 -- GoldenFloat16 Encode/Decode +; GF16: 16-bit floating point with 1 sign + 6 exponent + 9 mantissa +; Bit layout: [S(1) E(6) M(9)] = [15:15][14:9][8:0] +; phi^2 + 1/phi^2 = 3 | TRINITY + +module triformat-gf16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const SIGN_SHIFT : u8 = 15; +pub const EXP_SHIFT : u8 = 9; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u16 = 0x8000; // 1 << 15 +pub const EXP_MASK : u16 = 0x7E00; // 0b111111 << 9 +pub const MANT_MASK : u16 = 0x01FF; // 0b111111111 + +pub const EXP_MAX : u8 = 0x3F; // 63 (all ones in 6 bits) +pub const EXP_MIN : u8 = 0x00; + +pub const BIAS : i8 = 31; // Exponent bias for GF16 +pub const SPECIAL_EXP : u8 = 0x3F; // All ones = special (Inf/NaN) + +pub const MANT_DIVISOR : u16 = 512; // 2^9 +pub const MANT_DIVISOR_SHIFT : u8 = 9; // log2(512) + +pub const PHI_BIAS : u16 = 60; // Phi-optimized rounding bias + +// GF16 special values +pub const GF16_ZERO_POS : u16 = 0x0000; +pub const GF16_ZERO_NEG : u16 = 0x8000; +pub const GF16_INF_POS : u16 = 0x7E00; +pub const GF16_INF_NEG : u16 = 0xFE00; +pub const GF16_NAN : u16 = 0xFE01; // Sign + all exp + mantissa != 0 + +// ============================================================================ +// Types +// ============================================================================ + +pub const GF16 = u16; + +// ============================================================================ +// Lookup Tables +// ============================================================================ + +// Powers of 2 for exponents 0-31 +pub const pow2_table : [32]u16 = [32]u16{ + 0x3C00, 0x3D00, 0x3D80, 0x3E00, 0x3E40, 0x3E80, 0x3EC0, 0x3F00, + 0x3F40, 0x3F80, 0x3FC0, 0x3FE0, 0x3FF0, 0x4000, 0x4040, 0x4080, + 0x40C0, 0x4100, 0x4140, 0x4180, 0x41C0, 0x4200, 0x4240, 0x4280, + 0x42C0, 0x4300, 0x4340, 0x4380, 0x43C0, 0x4400, 0x4440, 0x4480, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +// gf16_extract_sign(gf16: GF16) -> i8 +// Extract sign bit (bit 15) +// Returns: 0 for positive, -1 for negative +pub fn gf16_extract_sign(gf16: GF16) i8 { + const bit = (gf16 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// gf16_extract_exponent(gf16: GF16) -> i8 +// Extract exponent bits (bits 14-9) +// Returns: 0-63 +pub fn gf16_extract_exponent(gf16: GF16) i8 { + return @as(i8, @intCast((gf16 >> EXP_SHIFT) & EXP_MASK)); +} + +// gf16_extract_mantissa(gf16: GF16) -> i16 +// Extract mantissa bits (bits 8-0) +// Returns: 0-511 +pub fn gf16_extract_mantissa(gf16: GF16) i16 { + return @as(i16, gf16 & MANT_MASK); +} + +// gf16_from_components(sign: i8, exp: i8, mant: i16) -> GF16 +// Assemble GF16 from sign, exponent, mantissa +pub fn gf16_from_components(sign: i8, exp: i8, mant: i16) GF16 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(GF16, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(GF16, @intCast(exp)) << EXP_SHIFT) | + @as(GF16, @intCast(mant)); +} + +// gf16_is_zero(gf16: GF16) -> bool +// Check if GF16 is zero (positive or negative) +pub fn gf16_is_zero(gf16: GF16) bool { + return gf16 == GF16_ZERO_POS or gf16 == GF16_ZERO_NEG; +} + +// gf16_is_special(gf16: GF16) -> bool +// Check if GF16 is Inf or NaN (exp == 63) +pub fn gf16_is_special(gf16: GF16) bool { + return gf16_extract_exponent(gf16) == EXP_MAX; +} + +// gf16_encode_f32(f32: f32) -> GF16 +// Encode IEEE 754 single precision to GF16 +// Round-to-nearest, ties to even +// Range: 2^-31 to 2^32 (normal), subnormals flushed to zero +pub fn gf16_encode_f32(value: f32) GF16 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) GF16_ZERO_NEG else GF16_ZERO_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i8 = @as(i8, @intCast((f32_bits >> 23) & 0xFF)) - 127; + var f32_mant: u32 = f32_bits & 0x7FFFFF; + + // Convert exp from f32 bias (127) to GF16 bias (31) + // gf16_exp = f32_exp + 31 - 127 = f32_exp - 96 + var gf16_exp = f32_exp - 96; + + // Clamp exponent + if (gf16_exp < 0) { + gf16_exp = 0; // Underflow to zero + } else if (gf16_exp > EXP_MAX) { + gf16_exp = EXP_MAX; // Overflow to Inf + } + + // Extract mantissa and scale to 9 bits + // f32 mantissa is 23 bits, GF16 needs 9 bits + // Shift right by 14 bits (23 - 9 = 14) + var mant = @as(u16, @intCast(f32_mant >> 14)); + + // Round-to-nearest + const discarded = f32_mant & 0x3FFF; + if ((discarded & 0x2000) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (gf16_exp < EXP_MAX) { + gf16_exp += 1; + } + } + } + + return gf16_from_components(sign, gf16_exp, mant); +} + +// gf16_decode_to_f32(gf16: GF16) -> f32 +// Decode GF16 to IEEE 754 single precision +pub fn gf16_decode_to_f32(gf16: GF16) f32 { + // Handle zero + if (gf16_is_zero(gf16)) { + const sign = gf16_extract_sign(gf16); + return if (sign < 0) -0.0 else 0.0; + } + + // Handle special values (Inf/NaN) + if (gf16_is_special(gf16)) { + const mant = gf16_extract_mantissa(gf16); + const sign = gf16_extract_sign(gf16); + if (mant == 0) { + // Infinity + return if (sign < 0) -std.math.inf(f32) else std.math.inf(f32); + } else { + // NaN + return std.math.nan(f32); + } + } + + // Normal number: value = (-1)^s * (1 + m/2^9) * 2^(e - 31) + const sign = gf16_extract_sign(gf16); + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + + const sign_mult = if (sign < 0) -1.0 else 1.0; + const mant_mult = 1.0 + @as(f32, @floatFromInt(mant)) / 512.0; + const exp_mult = @as(f32, @exp2(@as(f32, @floatFromInt(exp - BIAS)))); + + return sign_mult * mant_mult * exp_mult; +} + +// gf16_round_phi(value: f32) -> GF16 +// Phi-optimized rounding for GF16 +// Uses golden ratio bias for rounding decisions instead of standard round-to-nearest +// Bias = (1/phi - 0.5) * scale, where 1/phi ~= 0.618 +// This improves numerical stability for sacred physics calculations +pub fn gf16_round_phi(value: f32) GF16 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) GF16_ZERO_NEG else GF16_ZERO_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i8 = @as(i8, @intCast((f32_bits >> 23) & 0xFF)) - 127; + var f32_mant: u32 = f32_bits & 0x7FFFFF; + + // Convert exp from f32 bias (127) to GF16 bias (31) + var gf16_exp = f32_exp - 96; + + // Clamp exponent + if (gf16_exp < 0) { + gf16_exp = 0; + } else if (gf16_exp > EXP_MAX) { + gf16_exp = EXP_MAX; + } + + // Add implied 1 for normalization + const normalized_mant: u32 = f32_mant | 0x00800000; + + // Scale to 9 bits with phi bias + var mant = @as(u16, @intCast((normalized_mant >> 15) + PHI_BIAS)); + + // Check for overflow and adjust + if (mant > MANT_MASK) { + mant = 0; + if (gf16_exp < EXP_MAX) { + gf16_exp += 1; + } else { + gf16_exp = EXP_MAX; // Overflow to Inf + } + } + + return gf16_from_components(sign, gf16_exp, mant); +} + +// gf16_is_inf(gf16: GF16) -> bool +// Check if GF16 represents infinity +pub fn gf16_is_inf(gf16: GF16) bool { + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + return (exp == EXP_MAX) and (mant == 0); +} + +// gf16_is_nan(gf16: GF16) -> bool +// Check if GF16 represents NaN (Not a Number) +pub fn gf16_is_nan(gf16: GF16) bool { + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + return (exp == EXP_MAX) and (mant != 0); +} + +// gf16_is_negative(gf16: GF16) -> bool +// Check if GF16 is negative (excluding negative zero) +pub fn gf16_is_negative(gf16: GF16) bool { + const sign = gf16_extract_sign(gf16); + return (sign < 0) and !gf16_is_zero(gf16); +} + +// gf16_is_positive(gf16: GF16) -> bool +// Check if GF16 is positive (excluding positive zero) +pub fn gf16_is_positive(gf16: GF16) bool { + const sign = gf16_extract_sign(gf16); + return (sign >= 0) and !gf16_is_zero(gf16); +} + +// gf16_negate(gf16: GF16) -> GF16 +// Negate a GF16 value (flip sign bit) +pub fn gf16_negate(gf16: GF16) GF16 { + return gf16 ^ SIGN_MASK; +} + +// gf16_abs(gf16: GF16) -> GF16 +// Absolute value of GF16 (clear sign bit) +pub fn gf16_abs(gf16: GF16) GF16 { + return gf16 & ~SIGN_MASK; +} + +// gf16_copy_sign(gf16: GF16, sign_source: GF16) -> GF16 +// Copy sign from sign_source to gf16 value +pub fn gf16_copy_sign(gf16: GF16, sign_source: GF16) GF16 { + const sign_mask = sign_source & SIGN_MASK; + const value_mask = gf16 & ~SIGN_MASK; + return value_mask | sign_mask; +} + +// gf16_max(a: GF16, b: GF16) -> GF16 +// Return the greater of two GF16 values +pub fn gf16_max(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a)) return b; + if (gf16_is_nan(b)) return a; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + if (a_val >= b_val) return a else return b; +} + +// gf16_min(a: GF16, b: GF16) -> GF16 +// Return the smaller of two GF16 values +pub fn gf16_min(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a)) return b; + if (gf16_is_nan(b)) return a; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + if (a_val <= b_val) return a else return b; +} + +// gf16_add(a: GF16, b: GF16) -> GF16 +// Add two GF16 values (decode, add, re-encode) +// Returns NaN if either operand is NaN, Inf if overflow +pub fn gf16_add(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_inf(a) and gf16_is_inf(b)) { + // Inf + Inf = NaN (if same sign) + // Inf + (-Inf) = NaN + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + return if (a_sign == b_sign) a else GF16_NAN; + } + if (gf16_is_inf(a)) return a; + if (gf16_is_inf(b)) return b; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val + b_val; + + return gf16_encode_f32(result); +} + +// gf16_sub(a: GF16, b: GF16) -> GF16 +// Subtract two GF16 values (decode, subtract, re-encode) +// Returns NaN if either operand is NaN, Inf if overflow +pub fn gf16_sub(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_inf(a) and gf16_is_inf(b)) { + // Inf - Inf = NaN + return GF16_NAN; + } + if (gf16_is_inf(a)) return a; + if (gf16_is_inf(b)) { + // -Inf + something = Inf (with sign flip) + return gf16_negate(b); + } + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val - b_val; + + return gf16_encode_f32(result); +} + +// gf16_mul(a: GF16, b: GF16) -> GF16 +// Multiply two GF16 values (decode, multiply, re-encode) +// Returns NaN if either operand is NaN +pub fn gf16_mul(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_zero(a) or gf16_is_zero(b)) { + // 0 * x = 0, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_ZERO_NEG else GF16_ZERO_POS; + } + if (gf16_is_inf(a) or gf16_is_inf(b)) { + // Inf * non-zero = Inf, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_INF_NEG else GF16_INF_POS; + } + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val * b_val; + + return gf16_encode_f32(result); +} + +// gf16_div(a: GF16, b: GF16) -> GF16 +// Divide two GF16 values (decode, divide, re-encode) +// Returns NaN if division by zero or either operand is NaN +// Returns Inf if numerator is Inf and denominator is finite non-zero +pub fn gf16_div(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_zero(b)) { + // Division by zero = Inf with sign of a + const a_sign = gf16_extract_sign(a); + return if (a_sign != 0) GF16_INF_NEG else GF16_INF_POS; + } + if (gf16_is_inf(a)) { + // Inf / finite = Inf, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_INF_NEG else GF16_INF_POS; + } + if (gf16_is_inf(b)) { + // Finite / Inf = 0, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_ZERO_NEG else GF16_ZERO_POS; + } + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val / b_val; + + return gf16_encode_f32(result); +} + +// gf16_fma(a: GF16, b: GF16, c: GF16) -> GF16 +// Fused multiply-add: a * b + c with single rounding +// More accurate than separate mul and add +pub fn gf16_fma(a: GF16, b: GF16, c: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) return GF16_NAN; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const c_val = gf16_decode_to_f32(c); + + // Handle special cases + if (gf16_is_zero(a) or gf16_is_zero(b)) { + return gf16_add(c, gf16_encode_f32(0.0)); + } + + // Compute a * b + c + const product = a_val * b_val; + const result = product + c_val; + + return gf16_encode_f32(result); +} + +// gf16_sqrt(a: GF16) -> GF16 +// Square root of GF16 value +// Returns NaN for negative values, Inf for infinity +pub fn gf16_sqrt(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a) and !gf16_is_negative(a)) return a; + if (gf16_is_inf(a)) return GF16_NAN; // -Inf sqrt = NaN + if (gf16_is_zero(a)) return a; + if (gf16_is_negative(a)) return GF16_NAN; + + const a_val = gf16_decode_to_f32(a); + const result = @sqrt(a_val); + + return gf16_encode_f32(result); +} + +// gf16_square(a: GF16) -> GF16 +// Square of GF16 value +// Uses gf16_mul internally +pub fn gf16_square(a: GF16) GF16 { + return gf16_mul(a, a); +} + +// gf16_eq(a: GF16, b: GF16) -> bool +// Equality comparison for GF16 +// NaN values are never equal to anything (including themselves) +pub fn gf16_eq(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + // For zero values, treat +0 and -0 as equal + if (gf16_is_zero(a) and gf16_is_zero(b)) return true; + return a == b; +} + +// gf16_ne(a: GF16, b: GF16) -> bool +// Not-equal comparison for GF16 +// NaN values are not equal to anything (including themselves) +pub fn gf16_ne(a: GF16, b: GF16) bool { + return !gf16_eq(a, b); +} + +// gf16_lt(a: GF16, b: GF16) -> bool +// Less-than comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_lt(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val < b_val; +} + +// gf16_le(a: GF16, b: GF16) -> bool +// Less-than-or-equal comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_le(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val <= b_val; +} + +// gf16_gt(a: GF16, b: GF16) -> bool +// Greater-than comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_gt(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val > b_val; +} + +// gf16_ge(a: GF16, b: GF16) -> bool +// Greater-than-or-equal comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_ge(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val >= b_val; +} + +// gf16_floor(a: GF16) -> GF16 +// Round down to the nearest integer (toward -inf) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_floor(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @floor(a_val); + + return gf16_encode_f32(result); +} + +// gf16_ceil(a: GF16) -> GF16 +// Round up to the nearest integer (toward +inf) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_ceil(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @ceil(a_val); + + return gf16_encode_f32(result); +} + +// gf16_round(a: GF16) -> GF16 +// Round to nearest integer, ties to even (IEEE 754 roundTiesToEven) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_round(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @round(a_val); + + return gf16_encode_f32(result); +} + +// gf16_trunc(a: GF16) -> GF16 +// Round toward zero (truncate fractional part) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_trunc(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @trunc(a_val); + + return gf16_encode_f32(result); +} + +// gf16_fms(a: GF16, b: GF16, c: GF16) -> GF16 +// Fused multiply-subtract: a * b - c with single rounding +// More accurate than separate mul and sub +// Useful for neural network backpropagation +pub fn gf16_fms(a: GF16, b: GF16, c: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) return GF16_NAN; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const c_val = gf16_decode_to_f32(c); + + // Handle special cases + if (gf16_is_zero(a) or gf16_is_zero(b)) { + return gf16_sub(gf16_encode_f32(0.0), c); + } + + // Compute a * b - c + const product = a_val * b_val; + const result = product - c_val; + + return gf16_encode_f32(result); +} + +// gf16_hypot(a: GF16, b: GF16) -> GF16 +// Compute sqrt(a^2 + b^2) without overflow/underflow +// Returns NaN if either operand is NaN, Inf if both are Inf +// Useful for distance calculations, neural network normalization +pub fn gf16_hypot(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_inf(a) or gf16_is_inf(b)) return GF16_INF_POS; + + if (gf16_is_zero(a) and gf16_is_zero(b)) return GF16_ZERO_POS; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + // Standard algorithm to avoid overflow: scale by max(|a|, |b|) + const abs_a = @abs(a_val); + const abs_b = @abs(b_val); + const max_val = @max(abs_a, abs_b); + const min_val = @min(abs_a, abs_b); + + if (max_val == 0.0) return GF16_ZERO_POS; + + const ratio = min_val / max_val; + const result = max_val * @sqrt(1.0 + ratio * ratio); + + return gf16_encode_f32(result); +} + +// gf16_fmod(a: GF16, b: GF16) -> GF16 +// Compute remainder of a / b (IEEE 754 style) +// Result has same sign as dividend (a) +// Returns NaN if divisor is zero or either operand is NaN +pub fn gf16_fmod(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_zero(b)) return GF16_NAN; + if (gf16_is_inf(a)) return GF16_NAN; + if (gf16_is_inf(b)) return a; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + // Handle zero dividend + if (a_val == 0.0) return a; + + const result = @mod(a_val, b_val); + + return gf16_encode_f32(result); +} + +// gf16_is_finite(gf16: GF16) -> bool +// Check if GF16 value is finite (not NaN, not infinity) +pub fn gf16_is_finite(gf16: GF16) bool { + return !gf16_is_nan(gf16) and !gf16_is_inf(gf16); +} + +// gf16_is_normal(gf16: GF16) -> bool +// Check if GF16 value is a normal (normalized) number +// Normal numbers have exponent in range [1, EXP_MAX-1] and are not zero +pub fn gf16_is_normal(gf16: GF16) bool { + if (gf16_is_zero(gf16) or gf16_is_nan(gf16) or gf16_is_inf(gf16)) { + return false; + } + + const exp = gf16_extract_exponent(gf16); + // GF16: exp = 0 is subnormal, exp = 31 is inf/nan, 1-30 is normal + return exp > 0 and exp < GF16_EXP_MAX; +} + +// gf16_is_subnormal(gf16: GF16) -> bool +// Check if GF16 value is subnormal (denormal) +// Subnormal numbers have exponent = 0 and mantissa != 0 +pub fn gf16_is_subnormal(gf16: GF16) bool { + if (gf16_is_zero(gf16) or gf16_is_nan(gf16) or gf16_is_inf(gf16)) { + return false; + } + + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + + // Subnormal: exp = 0 and mantissa != 0 + return exp == 0 and mant != 0; +} + +// gf16_signbit(gf16: GF16) -> bool +// Check if the sign bit is set (value is negative or negative zero) +// Returns true for negative values and negative zero +pub fn gf16_signbit(gf16: GF16) bool { + return (gf16 & GF16_SIGN_MASK) != 0; +} + +// gf16_sign(gf16: GF16) -> i8 +// Return the sign of the GF16 value: -1 for negative, 0 for zero, +1 for positive +// Returns 0 for NaN (IEEE 754 specifies sign of NaN is undefined) +pub fn gf16_sign(gf16: GF16) i8 { + if (gf16_is_nan(gf16)) { + return 0; + } + + if (gf16_is_zero(gf16)) { + return 0; + } + + if (gf16_signbit(gf16)) { + return -1; + } else { + return 1; + } +} + +// gf16_clamp(x: GF16, min_val: GF16, max_val: GF16) -> GF16 +// Clamp x to the range [min_val, max_val] +// Returns min_val if x < min_val, max_val if x > max_val, otherwise x +pub fn gf16_clamp(x: GF16, min_val: GF16, max_val: GF16) GF16 { + if (gf16_is_nan(x) or gf16_is_nan(min_val) or gf16_is_nan(max_val)) { + return GF16_NAN; + } + + // Decode for comparison + const x_decoded = gf16_decode_to_f32(x); + const min_decoded = gf16_decode_to_f32(min_val); + const max_decoded = gf16_decode_to_f32(max_val); + + if (x_decoded < min_decoded) { + return min_val; + } else if (x_decoded > max_decoded) { + return max_val; + } else { + return x; + } +} + +// gf16_lerp(a: GF16, b: GF16, t: GF16) -> GF16 +// Linear interpolation: a + t * (b - a) +// Returns a when t=0, b when t=1, and interpolates for other values +pub fn gf16_lerp(a: GF16, b: GF16, t: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(t)) { + return GF16_NAN; + } + + // Decode to f32 for computation + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const t_val = gf16_decode_to_f32(t); + + // Compute: a + t * (b - a) + const result = a_val + t_val * (b_val - a_val); + + return gf16_encode_f32(result); +} + +// gf16_fnma(a: GF16, b: GF16, c: GF16) -> GF16 +// Fused negative multiply-add: -(a * b) + c +// More accurate than computing gf16_sub(c, gf16_mul(a, b)) +pub fn gf16_fnma(a: GF16, b: GF16, c: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) { + return GF16_NAN; + } + + // Handle infinity cases + if (gf16_is_inf(a) or gf16_is_inf(b)) { + if (gf16_is_inf(c)) { + return GF16_NAN; + } + // -(inf * b) + c = -inf (with appropriate sign) + if (gf16_is_inf(a) or gf16_is_inf(b)) { + const sign_a = gf16_signbit(a); + const sign_b = gf16_signbit(b); + const result_sign = (sign_a != sign_b); // XOR for negative result + return if (result_sign) GF16_INF_NEG else GF16_INF_POS; + } + } + + if (gf16_is_inf(c)) { + return c; + } + + // Handle zero cases + if (gf16_is_zero(a) or gf16_is_zero(b)) { + return c; + } + + if (gf16_is_zero(c)) { + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const neg_product = -(a_val * b_val); + return gf16_encode_f32(neg_product); + } + + // Decode to f32 for computation + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const c_val = gf16_decode_to_f32(c); + + const result = -(a_val * b_val) + c_val; + + return gf16_encode_f32(result); +} + +// gf16_exp(x: GF16) -> GF16 +// Compute e^x (exponential function) +// Uses Taylor series approximation for small values +// Returns Inf for very large positive inputs, 0 for very large negative inputs +pub fn gf16_exp(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_ZERO_POS; + return GF16_INF_POS; + } + + const x_val = gf16_decode_to_f32(x); + + // For large positive values, return Inf + if (x_val > 88.0) { // ln(MAX_FLOAT) for f32 + return GF16_INF_POS; + } + + // For large negative values, return 0 + if (x_val < -88.0) { + return GF16_ZERO_POS; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + x^4/4! + ... + // Use 5 terms for reasonable accuracy with GF16 precision + var result: f32 = 1.0; + var term: f32 = 1.0; + const num_terms: u32 = 5; + + for (0..num_terms) |i| { + if (i > 0) { + term *= x_val / @as(f32, @floatFromInt(i)); + result += term; + } + } + + return gf16_encode_f32(result); +} + +// gf16_log(x: GF16) -> GF16 +// Compute natural logarithm ln(x) +// Returns NaN for x <= 0, Inf for very large x +pub fn gf16_log(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_NAN; + return GF16_INF_POS; + } + if (gf16_is_zero(x) or gf16_is_negative(x)) { + return GF16_NAN; + } + + const x_val = gf16_decode_to_f32(x); + + // For very large values, return Inf + if (x_val > 1.0e38) { + return GF16_INF_POS; + } + + // Use natural log from standard library + const result = @log(x_val); + + return gf16_encode_f32(result); +} + +// gf16_log2(x: GF16) -> GF16 +// Compute base-2 logarithm log2(x) +pub fn gf16_log2(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_NAN; + return GF16_INF_POS; + } + if (gf16_is_zero(x) or gf16_is_negative(x)) { + return GF16_NAN; + } + + const x_val = gf16_decode_to_f32(x); + const result = @log2(x_val); + + return gf16_encode_f32(result); +} + +// gf16_log10(x: GF16) -> GF16 +// Compute base-10 logarithm log10(x) +pub fn gf16_log10(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_NAN; + return GF16_INF_POS; + } + if (gf16_is_zero(x) or gf16_is_negative(x)) { + return GF16_NAN; + } + + const x_val = gf16_decode_to_f32(x); + const result = @log10(x_val); + + return gf16_encode_f32(result); +} + +// gf16_pow(base: GF16, exponent: GF16) -> GF16 +// Compute base^exponent +// Handles various special cases: 0^0 = 1, 1^x = 1, x^0 = 1, etc. +pub fn gf16_pow(base: GF16, exponent: GF16) GF16 { + if (gf16_is_nan(base) or gf16_is_nan(exponent)) return GF16_NAN; + + // 0^0 = 1 (by convention) + if (gf16_is_zero(base) and gf16_is_zero(exponent)) return gf16_encode_f32(1.0); + + // 0^x = 0 for x > 0 + if (gf16_is_zero(base) and gf16_is_positive(exponent)) return GF16_ZERO_POS; + + // 0^x = Inf for x < 0 (division by zero) + if (gf16_is_zero(base) and gf16_is_negative(exponent)) return GF16_INF_POS; + + // 1^x = 1 for any finite x + const base_val = gf16_decode_to_f32(base); + if (base_val == 1.0 and !gf16_is_inf(exponent)) return gf16_encode_f32(1.0); + + // x^0 = 1 for any x != 0 + if (gf16_is_zero(exponent)) { + if (gf16_is_zero(base)) return GF16_NAN; + return gf16_encode_f32(1.0); + } + + // x^1 = x + const exp_val = gf16_decode_to_f32(exponent); + if (exp_val == 1.0) return base; + + // Use stdlib pow for general case + const result = pow(base_val, exp_val); + + return gf16_encode_f32(result); +} + +// gf16_sin(x: GF16) -> GF16 +// Compute sine function sin(x) where x is in radians +// Uses Taylor series approximation for small values +pub fn gf16_sin(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) return GF16_NAN; + + const x_val = gf16_decode_to_f32(x); + + // Taylor series: sin(x) = x - x^3/3! + x^5/5! - x^7/7! + ... + // Use 4 terms for reasonable accuracy + const x_sq = x_val * x_val; + const x_cub = x_sq * x_val; + const x_5 = x_cub * x_sq; + const x_7 = x_5 * x_sq; + + const term1 = x_val; + const term2 = -x_cub / 6.0; + const term3 = x_5 / 120.0; + const term4 = -x_7 / 5040.0; + + const result = term1 + term2 + term3 + term4; + + return gf16_encode_f32(result); +} + +// gf16_cos(x: GF16) -> GF16 +// Compute cosine function cos(x) where x is in radians +// Uses Taylor series approximation for small values +pub fn gf16_cos(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) return GF16_NAN; + + const x_val = gf16_decode_to_f32(x); + + // Taylor series: cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + ... + // Use 4 terms for reasonable accuracy + const x_sq = x_val * x_val; + const x_4 = x_sq * x_sq; + const x_6 = x_4 * x_sq; + + const term0 = 1.0; + const term1 = -x_sq / 2.0; + const term2 = x_4 / 24.0; + const term3 = -x_6 / 720.0; + + const result = term0 + term1 + term2 + term3; + + return gf16_encode_f32(result); +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "gf16_roundtrip_phi" { + // Verify: encoding f32 PHI to GF16 and decoding back preserves value within tolerance + const PHI: f32 = 1.6180339887498948; + const encoded = gf16_encode_f32(PHI); + const decoded = gf16_decode_to_f32(encoded); + try std.testing.expectApproxEqAbs(PHI, decoded, 0.001); +} + +test "gf16_zero_encoding" { + // Verify: zero (positive and negative) encodes to correct GF16 patterns + try std.testing.expectEqual(@as(GF16, GF16_ZERO_POS), gf16_encode_f32(0.0)); + try std.testing.expectEqual(@as(GF16, GF16_ZERO_NEG), gf16_encode_f32(-0.0)); +} + +test "gf16_phi_roundtrip_high_precision" { + // Verify: PHI roundtrip with higher tolerance for golden ratio + const PHI: f32 = 1.6180339887498948; + const encoded = gf16_encode_f32(PHI); + const decoded = gf16_decode_to_f32(encoded); + try std.testing.expectApproxEqAbs(PHI, decoded, 0.01); +} + +test "gf16_inf_encoding" { + // Verify: overflow encodes to Inf correctly + const encoded = gf16_encode_f32(1.0e38); + try std.testing.expect(gf16_is_special(encoded)); + try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(encoded)); +} + +test "gf16_sign_extraction" { + try std.testing.expectEqual(@as(i8, -1), gf16_extract_sign(0x8000)); + try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(0x3C00)); + try std.testing.expectEqual(@as(i8, 1), gf16_extract_sign(0x8000)); + try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(0x3C00)); +} + +test "gf16_exponent_extraction" { + try std.testing.expectEqual(@as(i8, 0), gf16_extract_exponent(0x3C00)); + try std.testing.expectEqual(@as(i8, 1), gf16_extract_exponent(0x3D00)); +} + +test "gf16_mantissa_extraction" { + try std.testing.expectEqual(@as(i16, 0), gf16_extract_mantissa(0x3C00)); + try std.testing.expectEqual(@as(i16, 1), gf16_extract_mantissa(0x3C01)); + try std.testing.expectEqual(@as(i16, 511), gf16_extract_mantissa(0x3DFF)); +} + +test "gf16_zero_detection" { + try std.testing.expect(gf16_is_zero(0x0000)); + try std.testing.expect(gf16_is_zero(0x8000)); + try std.testing.expect(!gf16_is_zero(0x0001)); +} + +test "gf16_special_detection" { + try std.testing.expect(gf16_is_special(0x7E00)); + try std.testing.expect(gf16_is_special(0xFE01)); + try std.testing.expect(!gf16_is_special(0x3C00)); +} + +test "gf16_from_components" { + const result = gf16_from_components(0, 0, 0); + try std.testing.expectEqual(@as(GF16, 0x3C00), result); +} + +test "gf16_nan_encoding" { + const nan_val = gf16_from_components(0, 63, 1); + const decoded = gf16_decode_to_f32(nan_val); + try std.testing.expect(std.math.isNan(decoded)); +} + +test "gf16_round_phi_preserves_phi" { + const PHI: f32 = 1.6180339887498948; + const encoded = gf16_round_phi(PHI); + const decoded = gf16_decode_to_f32(encoded); + try std.testing.expectApproxEqAbs(PHI, decoded, 0.005); +} + +test "gf16_round_phi_zero" { + try std.testing.expectEqual(@as(GF16, GF16_ZERO_POS), gf16_round_phi(0.0)); + try std.testing.expectEqual(@as(GF16, GF16_ZERO_NEG), gf16_round_phi(-0.0)); +} + +test "gf16_round_phi_positive" { + try std.testing.expectApproxEqAbs(1.0, gf16_decode_to_f32(gf16_round_phi(1.0)), 0.01); + try std.testing.expectApproxEqAbs(2.0, gf16_decode_to_f32(gf16_round_phi(2.0)), 0.01); + try std.testing.expectApproxEqAbs(3.0, gf16_decode_to_f32(gf16_round_phi(3.0)), 0.01); +} + +test "gf16_round_phi_negative" { + try std.testing.expectApproxEqAbs(-1.0, gf16_decode_to_f32(gf16_round_phi(-1.0)), 0.01); + try std.testing.expectApproxEqAbs(-2.0, gf16_decode_to_f32(gf16_round_phi(-2.0)), 0.01); + const PHI: f32 = 1.6180339887498948; + try std.testing.expectApproxEqAbs(-PHI, gf16_decode_to_f32(gf16_round_phi(-PHI)), 0.01); +} + +test "gf16_pow2_table_consistency" { + try std.testing.expectEqual(@as(u16, 0x3C00), pow2_table[0]); // 2^0 = 1.0 + try std.testing.expectEqual(@as(u16, 0x3D00), pow2_table[1]); // 2^1 = 2.0 + try std.testing.expectEqual(@as(u16, 0x3D80), pow2_table[2]); // 2^2 = 4.0 +} + +test "gf16_exp_bias_identity" { + try std.testing.expectEqual(@as(i8, 31), BIAS); +} + +test "gf16_identity_encoding" { + // For GF16 representing 1.0: sign=0, exp=0, mant=0, raw value = 0x3C00 + try std.testing.expectEqual(@as(GF16, 0x3C00), gf16_from_components(0, 0, 0)); +} + +test "gf16_special_exp_all_ones" { + try std.testing.expectEqual(@as(u8, 0x3F), EXP_MAX); +} + +test "gf16_is_inf_positive" { + try std.testing.expect(gf16_is_inf(GF16_INF_POS)); + try std.testing.expect(!gf16_is_inf(GF16_ZERO_POS)); + try std.testing.expect(!gf16_is_inf(0x3C00)); +} + +test "gf16_is_inf_negative" { + try std.testing.expect(gf16_is_inf(GF16_INF_NEG)); + try std.testing.expect(!gf16_is_inf(GF16_ZERO_NEG)); +} + +test "gf16_is_nan_detection" { + try std.testing.expect(gf16_is_nan(GF16_NAN)); + try std.testing.expect(!gf16_is_nan(GF16_INF_POS)); + try std.testing.expect(!gf16_is_nan(GF16_ZERO_POS)); +} + +test "gf16_is_negative_detection" { + try std.testing.expect(gf16_is_negative(GF16_INF_NEG)); + try std.testing.expect(gf16_is_negative(gf16_encode_f32(-1.5))); + try std.testing.expect(!gf16_is_negative(GF16_ZERO_NEG)); // -0 is not considered "negative" + try std.testing.expect(!gf16_is_negative(GF16_INF_POS)); +} + +test "gf16_is_positive_detection" { + try std.testing.expect(gf16_is_positive(GF16_INF_POS)); + try std.testing.expect(gf16_is_positive(gf16_encode_f32(1.5))); + try std.testing.expect(!gf16_is_positive(GF16_ZERO_POS)); // +0 is not considered "positive" + try std.testing.expect(!gf16_is_positive(GF16_INF_NEG)); +} + +test "gf16_negate_sign_flip" { + const pos_one = gf16_encode_f32(1.0); + const neg_one = gf16_negate(pos_one); + const decoded = gf16_decode_to_f32(neg_one); + try std.testing.expectApproxEqAbs(-1.0, decoded, 0.01); +} + +test "gf16_negate_zero_stays_zero" { + try std.testing.expectEqual(GF16_ZERO_POS, gf16_negate(GF16_ZERO_POS)); + try std.testing.expectEqual(GF16_ZERO_NEG, gf16_negate(GF16_ZERO_NEG)); +} + +test "gf16_negate_double_negate" { + const original = gf16_encode_f32(1.5); + const negated = gf16_negate(original); + const double_negated = gf16_negate(negated); + const orig_decoded = gf16_decode_to_f32(original); + const double_decoded = gf16_decode_to_f32(double_negated); + try std.testing.expectApproxEqAbs(orig_decoded, double_decoded, 0.001); +} + +test "gf16_abs_clears_sign" { + const neg_value = gf16_encode_f32(-2.5); + const abs_value = gf16_abs(neg_value); + const decoded = gf16_decode_to_f32(abs_value); + try std.testing.expectApproxEqAbs(2.5, decoded, 0.01); +} + +test "gf16_abs_positive_unchanged" { + const pos_value = gf16_encode_f32(3.5); + const abs_value = gf16_abs(pos_value); + try std.testing.expectEqual(pos_value, abs_value); +} + +test "gf16_abs_zero_unchanged" { + try std.testing.expectEqual(GF16_ZERO_POS, gf16_abs(GF16_ZERO_POS)); + try std.testing.expectEqual(GF16_ZERO_POS, gf16_abs(GF16_ZERO_NEG)); +} + +test "gf16_copy_sign_from_negative" { + const pos_value = gf16_encode_f32(2.5); + const neg_source = gf16_encode_f32(-1.0); + const result = gf16_copy_sign(pos_value, neg_source); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.5, decoded, 0.01); +} + +test "gf16_copy_sign_from_positive" { + const neg_value = gf16_encode_f32(-2.5); + const pos_source = gf16_encode_f32(1.0); + const result = gf16_copy_sign(neg_value, pos_source); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.5, decoded, 0.01); +} + +test "gf16_max_returns_greater" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(5.0); + const result = gf16_max(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.01); +} + +test "gf16_max_equal_values" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(3.0); + const result = gf16_max(a, b); + try std.testing.expectEqual(a, result); +} + +test "gf16_max_with_nan" { + const a = gf16_encode_f32(2.0); + const nan_val = GF16_NAN; + const result = gf16_max(a, nan_val); + try std.testing.expectEqual(a, result); +} + +test "gf16_min_returns_smaller" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(5.0); + const result = gf16_min(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.01); +} + +test "gf16_min_equal_values" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(3.0); + const result = gf16_min(a, b); + try std.testing.expectEqual(a, result); +} + +test "gf16_min_with_nan" { + const a = gf16_encode_f32(2.0); + const nan_val = GF16_NAN; + const result = gf16_min(a, nan_val); + try std.testing.expectEqual(a, result); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant gf16_identity_encoding { + // For GF16 representing 1.0: sign=0, exp=0, mant=0, raw value = 0x3C00 + @compileAssert(gf16_from_components(0, 0, 0) == 0x3C00); +} + +invariant gf16_sign_mask_bit_position { + // SIGN_MASK = 0x8000 has bit 15 set (MSB) + @compileAssert(SIGN_MASK == 0x8000); +} + +invariant gf16_exp_mask_range { + // EXP_MASK = 0x7E00 covers bits 14-9 (6 bits for exponent) + @compileAssert(EXP_MASK == 0x7E00); +} + +invariant gf16_mant_mask_range { + // MANT_MASK = 0x01FF covers bits 8-0 (9 bits for mantissa) + @compileAssert(MANT_MASK == 0x01FF); +} + +invariant gf16_exp_bias_identity { + // BIAS = 31, so unbiased exp = encoded_exp - 31 + @compileAssert(BIAS == 31); +} + +invariant gf16_roundtrip_symmetry { + // For all normal values x: |decode(encode(x)) - x| < epsilon + @compileAssert(true); +} + +invariant gf16_zero_uniqueness { + // Both 0x0000 and 0x8000 represent zero (positive/negative) + @compileAssert(GF16_ZERO_POS == 0x0000); + @compileAssert(GF16_ZERO_NEG == 0x8000); +} + +invariant gf16_special_exp_all_ones { + // EXP_MAX = 0x3F (63) all ones indicates Inf/NaN + @compileAssert(EXP_MAX == 0x3F); +} + +invariant gf16_pow2_table_consistency { + // pow2_table[n] encodes 2^n for n = 0 to 31 + @compileAssert(pow2_table.len == 32); +} + +invariant gf16_mantissa_implicit_one { + // For normal numbers: actual mantissa = 1 + (stored_mant / 512) + @compileAssert(MANT_DIVISOR == 512); +} + +invariant gf16_phi_bias_positive { + // PHI_BIAS = 60 > 0 + @compileAssert(PHI_BIAS > 0); +} + +invariant gf16_phi_bias_less_than_mantissa_scale { + // PHI_BIAS = 60 < 512 (MANT_DIVISOR) + @compileAssert(PHI_BIAS < MANT_DIVISOR); +} + +invariant gf16_round_phi_preserves_sign { + // For all x: sign(gf16_round_phi(x)) = sign(x) + @compileAssert(true); +} + +invariant gf16_inf_exp_all_ones_mant_zero { + // Infinity: exp = 63, mant = 0 + @compileAssert(gf16_extract_exponent(GF16_INF_POS) == EXP_MAX); + @compileAssert(gf16_extract_mantissa(GF16_INF_POS) == 0); +} + +invariant gf16_nan_exp_all_ones_mant_nonzero { + // NaN: exp = 63, mant != 0 + @compileAssert(gf16_extract_exponent(GF16_NAN) == EXP_MAX); + @compileAssert(gf16_extract_mantissa(GF16_NAN) != 0); +} + +invariant gf16_negate_flips_sign_bit { + // gf16_negate(x) = x ^ 0x8000 + @compileAssert(gf16_negate(0x3C00) == 0xBC00); + @compileAssert(gf16_negate(0xBC00) == 0x3C00); +} + +invariant gf16_negate_involutive { + // gf16_negate(gf16_negate(x)) = x + @compileAssert(true); +} + +invariant gf16_abs_clears_sign_bit { + // gf16_abs(x) = x & ~0x8000 + @compileAssert(gf16_abs(0xBC00) == 0x3C00); + @compileAssert(gf16_abs(0x3C00) == 0x3C00); +} + +invariant gf16_abs_non_negative { + // gf16_abs(x) is always non-negative (sign bit cleared) + @compileAssert((gf16_abs(0xBC00) & SIGN_MASK) == 0); +} + +invariant gf16_copy_sign_preserves_sign_source { + // sign(gf16_copy_sign(x, s)) = sign(s) + @compileAssert(true); +} + +invariant gf16_copy_sign_preserves_magnitude { + // |gf16_copy_sign(x, s)| = |x| + @compileAssert(true); +} + +invariant gf16_max_idempotent { + // gf16_max(x, x) = x + @compileAssert(true); +} + +invariant gf16_min_idempotent { + // gf16_min(x, x) = x + @compileAssert(true); +} + +invariant gf16_max_commutative { + // gf16_max(a, b) = gf16_max(b, a) + @compileAssert(true); +} + +invariant gf16_min_commutative { + // gf16_min(a, b) = gf16_min(b, a) + @compileAssert(true); +} + +invariant gf16_is_inf_and_is_nan_exclusive { + // A value cannot be both Inf and NaN + @compileAssert(!gf16_is_inf(GF16_NAN)); + @compileAssert(!gf16_is_nan(GF16_INF_POS)); +} + +invariant gf16_add_zero_identity { + // gf16_add(x, 0) = gf16_add(0, x) = x (approximately, due to encoding) + @compileAssert(true); +} + +invariant gf16_mul_zero_annihilates { + // gf16_mul(x, 0) = gf16_mul(0, x) = 0 + @compileAssert(true); +} + +invariant gf16_mul_one_identity { + // gf16_mul(x, 1) = gf16_mul(1, x) = x (approximately) + @compileAssert(true); +} + +invariant gf16_negate_involutive { + // gf16_negate(gf16_negate(x)) = x + @compileAssert(true); +} + +invariant gf16_add_commutative { + // gf16_add(a, b) = gf16_add(b, a) + @compileAssert(true); +} + +invariant gf16_mul_commutative { + // gf16_mul(a, b) = gf16_mul(b, a) + @compileAssert(true); +} + +invariant gf16_div_by_one_identity { + // gf16_div(x, 1) = x (approximately) + @compileAssert(true); +} + +invariant gf16_sqrt_non_negative { + // gf16_sqrt(x) >= 0 for all x >= 0 + @compileAssert(true); +} + +invariant gf16_sqrt_of_square_less_than_or_equal { + // gf16_sqrt(gf16_square(x)) <= x for all x >= 0 + @compileAssert(true); +} + +invariant gf16_fma_distributive_approximation { + // gf16_fma(a, b, c) ~= gf16_add(gf16_mul(a, b), c) + // Not exact due to encoding rounding + @compileAssert(true); +} + +invariant gf16_square_positive { + // gf16_square(x) >= 0 for all x + @compileAssert(true); +} + +invariant gf16_eq_reflexive_for_non_nan { + // For all x != NaN: gf16_eq(x, x) = true + @compileAssert(true); +} + +invariant gf16_ne_irreflexive_for_non_nan { + // For all x != NaN: gf16_ne(x, x) = false + @compileAssert(true); +} + +invariant gf16_lt_and_gt_mutually_exclusive { + // For all a, b: not (gf16_lt(a, b) and gf16_gt(a, b)) + @compileAssert(true); +} + +invariant gf16_le_and_ge_mutually_inclusive { + // For all a, b: gf16_le(a, b) or gf16_ge(a, b) (for non-NaN) + @compileAssert(true); +} + +invariant gf16_lt_implies_le { + // For all a, b: gf16_lt(a, b) implies gf16_le(a, b) + @compileAssert(true); +} + +invariant gf16_gt_implies_ge { + // For all a, b: gf16_gt(a, b) implies gf16_ge(a, b) + @compileAssert(true); +} + +invariant gf16_eq_implies_le_and_ge { + // For all a, b: gf16_eq(a, b) implies gf16_le(a, b) and gf16_ge(a, b) + @compileAssert(true); +} + +invariant gf16_ne_nan_is_true { + // gf16_ne(NaN, NaN) = true per IEEE 754 + @compileAssert(true); +} + +invariant gf16_lt_nan_is_false { + // gf16_lt(NaN, x) = false for all x + @compileAssert(true); +} + +invariant gf16_gt_nan_is_false { + // gf16_gt(NaN, x) = false for all x + @compileAssert(true); +} + +invariant gf16_floor_yields_integer { + // For all x != NaN, Inf: floor(gf16_floor(x)) = gf16_floor(x) + @compileAssert(true); +} + +invariant gf16_ceil_yields_integer { + // For all x != NaN, Inf: ceil(gf16_ceil(x)) = gf16_ceil(x) + @compileAssert(true); +} + +invariant gf16_round_yields_integer { + // For all x != NaN, Inf: round(gf16_round(x)) = gf16_round(x) + @compileAssert(true); +} + +invariant gf16_trunc_yields_integer { + // For all x != NaN, Inf: trunc(gf16_trunc(x)) = gf16_trunc(x) + @compileAssert(true); +} + +invariant gf16_floor_le_value { + // For all x: floor(x) <= x + @compileAssert(true); +} + +invariant gf16_ceil_ge_value { + // For all x: ceil(x) >= x + @compileAssert(true); +} + +invariant gf16_round_closest_integer { + // For all x: |round(x) - x| <= 0.5 + @compileAssert(true); +} + +invariant gf16_trunc_magnitude_less_or_equal { + // For all x: |trunc(x)| <= |x| + @compileAssert(true); +} + +invariant gf16_trunc_positive_equals_floor { + // For all x >= 0: trunc(x) = floor(x) + @compileAssert(true); +} + +invariant gf16_trunc_negative_equals_ceil { + // For all x <= 0: trunc(x) = ceil(x) + @compileAssert(true); +} + +invariant gf16_fms_related_to_fma { + // gf16_fms(a, b, c) = gf16_fma(a, b, -c) (approximately, due to encoding) + @compileAssert(true); +} + +invariant gf16_fms_with_zero_subtractand { + // gf16_fms(a, b, 0) = gf16_mul(a, b) (approximately) + @compileAssert(true); +} + +invariant gf16_hypot_non_negative { + // For all a, b: gf16_hypot(a, b) >= 0 + @compileAssert(true); +} + +invariant gf16_hypot_symmetric { + // For all a, b: gf16_hypot(a, b) = gf16_hypot(b, a) + @compileAssert(true); +} + +invariant gf16_hypot_pythagorean_identity { + // For all a, b: hypot(a, b)^2 = a^2 + b^2 (approximately, due to encoding) + @compileAssert(true); +} + +invariant gf16_hypot_ge_max_input { + // For all a, b: gf16_hypot(a, b) >= max(|a|, |b|) + @compileAssert(true); +} + +invariant gf16_hypot_zero_with_zeros { + // gf16_hypot(0, 0) = 0 + @compileAssert(true); +} + +invariant gf16_fmod_result_sign_matches_dividend { + // For all a, b where b != 0: sign(gf16_fmod(a, b)) = sign(a) + @compileAssert(true); +} + +invariant gf16_fmod_less_than_divisor { + // For all a, b where b > 0: |gf16_fmod(a, b)| < |b| + @compileAssert(true); +} + +invariant gf16_fmod_with_divisible_values { + // For all a, b where a = k*b: gf16_fmod(a, b) = 0 + @compileAssert(true); +} + +invariant gf16_is_finite_excludes_inf_nan { + // gf16_is_finite(x) = true implies !gf16_is_inf(x) and !gf16_is_nan(x) + @compileAssert(true); +} + +invariant gf16_is_normal_implies_finite { + // gf16_is_normal(x) = true implies gf16_is_finite(x) + @compileAssert(true); +} + +invariant gf16_is_subnormal_implies_finite { + // gf16_is_subnormal(x) = true implies gf16_is_finite(x) + @compileAssert(true); +} + +invariant gf16_is_normal_and_subnormal_mutually_exclusive { + // gf16_is_normal(x) and gf16_is_subnormal(x) cannot both be true + @compileAssert(true); +} + +invariant gf16_zero_neither_normal_nor_subnormal { + // gf16_is_zero(x) = true implies !gf16_is_normal(x) and !gf16_is_subnormal(x) + @compileAssert(true); +} + +invariant gf16_classification_exhaustive { + // For all x: (is_finite and (is_normal or is_subnormal or is_zero)) or is_inf or is_nan + @compileAssert(true); +} + +invariant gf16_signbit_positive_no_signbit { + // gf16_signbit(x) = false for x >= 0 (including +0 and +inf) + @compileAssert(true); +} + +invariant gf16_signbit_negative_has_signbit { + // gf16_signbit(x) = true for x < 0 (including -0 and -inf) + @compileAssert(true); +} + +invariant gf16_sign_positive_returns_one { + // For x > 0 and x is not NaN: gf16_sign(x) = 1 + @compileAssert(true); +} + +invariant gf16_sign_negative_returns_minus_one { + // For x < 0 and x is not NaN: gf16_sign(x) = -1 + @compileAssert(true); +} + +invariant gf16_sign_zero_returns_zero { + // For x = 0 (positive or negative): gf16_sign(x) = 0 + @compileAssert(true); +} + +invariant gf16_sign_nan_returns_zero { + // For NaN: gf16_sign(x) = 0 (sign of NaN is undefined) + @compileAssert(true); +} + +invariant gf16_clamp_in_range_returns_value { + // For x in [min, max]: gf16_clamp(x, min, max) = x + @compileAssert(true); +} + +invariant gf16_clamp_below_min_returns_min { + // For x < min: gf16_clamp(x, min, max) = min + @compileAssert(true); +} + +invariant gf16_clamp_above_max_returns_max { + // For x > max: gf16_clamp(x, min, max) = max + @compileAssert(true); +} + +invariant gf16_lerp_t_zero_returns_a { + // gf16_lerp(a, b, 0) = a + @compileAssert(true); +} + +invariant gf16_lerp_t_one_returns_b { + // gf16_lerp(a, b, 1) = b + @compileAssert(true); +} + +invariant gf16_lerp_monotonic { + // For fixed a < b: gf16_lerp(a, b, t) is monotonic in t + @compileAssert(true); +} + +invariant gf16_fnma_equals_neg_mul_plus_c { + // gf16_fnma(a, b, c) = -(a*b) + c (approximately, with better precision) + @compileAssert(true); +} + +invariant gf16_fnma_zero_multiplier_returns_c { + // gf16_fnma(0, b, c) = c + @compileAssert(true); +} + +invariant gf16_exp_zero_returns_one { + // gf16_exp(0) = 1 + @compileAssert(true); +} + +invariant gf16_exp_positive_greater_than_one { + // gf16_exp(x) > 1 for x > 0 + @compileAssert(true); +} + +invariant gf16_exp_negative_between_zero_and_one { + // 0 < gf16_exp(x) < 1 for x < 0 + @compileAssert(true); +} + +invariant gf16_log_one_returns_zero { + // gf16_log(1) = 0 + @compileAssert(true); +} + +invariant gf16_log_zero_or_negative_nan { + // gf16_log(x) = NaN for x <= 0 + @compileAssert(true); +} + +invariant gf16_pow_zero_to_zero_returns_one { + // gf16_pow(0, 0) = 1 (by convention) + @compileAssert(true); +} + +invariant gf16_pow_any_to_zero_returns_one { + // gf16_pow(x, 0) = 1 for x != 0 + @compileAssert(true); +} + +invariant gf16_pow_one_to_any_returns_one { + // gf16_pow(1, x) = 1 for finite x + @compileAssert(true); +} + +invariant gf16_sin_zero_returns_zero { + // gf16_sin(0) = 0 + @compileAssert(true); +} + +invariant gf16_cos_zero_returns_one { + // gf16_cos(0) = 1 + @compileAssert(true); +} + +invariant gf16_trig_identity_approx { + // sin^2(x) + cos^2(x) ~= 1 for reasonable x values + @compileAssert(true); +} + +test "gf16_add_positive_values" { + const a = gf16_encode_f32(1.5); + const b = gf16_encode_f32(2.5); + const result = gf16_add(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.1); +} + +test "gf16_add_negative_values" { + const a = gf16_encode_f32(-1.5); + const b = gf16_encode_f32(-2.5); + const result = gf16_add(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-4.0, decoded, 0.1); +} + +test "gf16_add_opposite_values" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(-2.0); + const result = gf16_add(a, b); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_add_with_zero" { + const a = gf16_encode_f32(3.5); + const zero = gf16_encode_f32(0.0); + const result1 = gf16_add(a, zero); + const result2 = gf16_add(zero, a); + const decoded1 = gf16_decode_to_f32(result1); + const decoded2 = gf16_decode_to_f32(result2); + try std.testing.expectApproxEqAbs(3.5, decoded1, 0.05); + try std.testing.expectApproxEqAbs(3.5, decoded2, 0.05); +} + +test "gf16_sub_positive_values" { + const a = gf16_encode_f32(5.0); + const b = gf16_encode_f32(2.0); + const result = gf16_sub(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_sub_negative_result" { + const a = gf16_encode_f32(1.0); + const b = gf16_encode_f32(3.0); + const result = gf16_sub(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_sub_with_zero" { + const a = gf16_encode_f32(2.5); + const zero = gf16_encode_f32(0.0); + const result1 = gf16_sub(a, zero); + const result2 = gf16_sub(zero, a); + const decoded1 = gf16_decode_to_f32(result1); + const decoded2 = gf16_decode_to_f32(result2); + try std.testing.expectApproxEqAbs(2.5, decoded1, 0.05); + try std.testing.expectApproxEqAbs(-2.5, decoded2, 0.05); +} + +test "gf16_mul_positive_values" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const result = gf16_mul(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(6.0, decoded, 0.1); +} + +test "gf16_mul_negative_positive" { + const a = gf16_encode_f32(-2.0); + const b = gf16_encode_f32(3.0); + const result = gf16_mul(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-6.0, decoded, 0.1); +} + +test "gf16_mul_with_zero" { + const a = gf16_encode_f32(5.0); + const zero = gf16_encode_f32(0.0); + const result1 = gf16_mul(a, zero); + const result2 = gf16_mul(zero, a); + try std.testing.expect(gf16_is_zero(result1)); + try std.testing.expect(gf16_is_zero(result2)); +} + +test "gf16_mul_by_one" { + const a = gf16_encode_f32(3.5); + const one = gf16_encode_f32(1.0); + const result = gf16_mul(a, one); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.5, decoded, 0.05); +} + +test "gf16_div_positive_values" { + const a = gf16_encode_f32(6.0); + const b = gf16_encode_f32(3.0); + const result = gf16_div(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_div_negative_result" { + const a = gf16_encode_f32(6.0); + const b = gf16_encode_f32(-3.0); + const result = gf16_div(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_div_by_one" { + const a = gf16_encode_f32(2.5); + const one = gf16_encode_f32(1.0); + const result = gf16_div(a, one); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.5, decoded, 0.05); +} + +test "gf16_div_zero_by_value" { + const zero = gf16_encode_f32(0.0); + const a = gf16_encode_f32(5.0); + const result = gf16_div(zero, a); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_div_value_by_zero" { + const a = gf16_encode_f32(5.0); + const zero = gf16_encode_f32(0.0); + const result = gf16_div(a, zero); + try std.testing.expect(gf16_is_inf(result)); +} + +test "gf16_div_inf_by_finite" { + const inf = GF16_INF_POS; + const a = gf16_encode_f32(5.0); + const result = gf16_div(inf, a); + try std.testing.expect(gf16_is_inf(result)); +} + +test "gf16_fma_basic" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(4.0); + const result = gf16_fma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.2); +} + +test "gf16_fma_with_zero" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const zero = gf16_encode_f32(0.0); + const result = gf16_fma(a, b, zero); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(6.0, decoded, 0.15); +} + +test "gf16_sqrt_positive" { + const a = gf16_encode_f32(4.0); + const result = gf16_sqrt(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.05); +} + +test "gf16_sqrt_of_one" { + const a = gf16_encode_f32(1.0); + const result = gf16_sqrt(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.05); +} + +test "gf16_sqrt_of_zero" { + const zero = gf16_encode_f32(0.0); + const result = gf16_sqrt(zero); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_sqrt_negative_nan" { + const neg = gf16_encode_f32(-4.0); + const result = gf16_sqrt(neg); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_square_of_two" { + const a = gf16_encode_f32(2.0); + const result = gf16_square(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.1); +} + +test "gf16_square_of_zero" { + const zero = gf16_encode_f32(0.0); + const result = gf16_square(zero); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_square_of_negative" { + const neg = gf16_encode_f32(-2.0); + const result = gf16_square(neg); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.1); +} + +test "gf16_add_commutative" { + const a = gf16_encode_f32(1.5); + const b = gf16_encode_f32(2.5); + const result1 = gf16_add(a, b); + const result2 = gf16_add(b, a); + try std.testing.expectEqual(result1, result2); +} + +test "gf16_mul_commutative" { + const a = gf16_encode_f32(1.5); + const b = gf16_encode_f32(2.5); + const result1 = gf16_mul(a, b); + const result2 = gf16_mul(b, a); + try std.testing.expectEqual(result1, result2); +} + +test "gf16_sqrt_square_roundtrip" { + const a = gf16_encode_f32(4.0); + const squared = gf16_square(a); + const rooted = gf16_sqrt(squared); + const decoded = gf16_decode_to_f32(rooted); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.2); +} + +test "gf16_eq_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(gf16_eq(a, b)); +} + +test "gf16_eq_different_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(3.5); + try std.testing.expect(!gf16_eq(a, b)); +} + +test "gf16_eq_pos_zero_eq_neg_zero" { + // IEEE 754: +0.0 == -0.0 is true + try std.testing.expect(gf16_eq(GF16_ZERO_POS, GF16_ZERO_NEG)); +} + +test "gf16_eq_nan_not_equal_nan" { + // NaN != NaN per IEEE 754 + try std.testing.expect(!gf16_eq(GF16_NAN, GF16_NAN)); +} + +test "gf16_eq_nan_not_equal_value" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_eq(GF16_NAN, value)); + try std.testing.expect(!gf16_eq(value, GF16_NAN)); +} + +test "gf16_ne_different_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(3.5); + try std.testing.expect(gf16_ne(a, b)); +} + +test "gf16_ne_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(!gf16_ne(a, b)); +} + +test "gf16_ne_nan_not_equal_nan" { + // NaN != NaN per IEEE 754 + try std.testing.expect(gf16_ne(GF16_NAN, GF16_NAN)); +} + +test "gf16_ne_nan_not_equal_value" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(gf16_ne(GF16_NAN, value)); + try std.testing.expect(gf16_ne(value, GF16_NAN)); +} + +test "gf16_lt_less_than" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(gf16_lt(a, b)); +} + +test "gf16_lt_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(!gf16_lt(a, b)); +} + +test "gf16_lt_greater_than" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(!gf16_lt(a, b)); +} + +test "gf16_lt_negative_positive" { + const neg = gf16_encode_f32(-2.0); + const pos = gf16_encode_f32(1.0); + try std.testing.expect(gf16_lt(neg, pos)); +} + +test "gf16_lt_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_lt(GF16_NAN, value)); + try std.testing.expect(!gf16_lt(value, GF16_NAN)); +} + +test "gf16_le_less_than_or_equal" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(gf16_le(a, b)); +} + +test "gf16_le_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(gf16_le(a, b)); +} + +test "gf16_le_greater_than" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(!gf16_le(a, b)); +} + +test "gf16_le_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_le(GF16_NAN, value)); + try std.testing.expect(!gf16_le(value, GF16_NAN)); +} + +test "gf16_gt_greater_than" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(gf16_gt(a, b)); +} + +test "gf16_gt_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(!gf16_gt(a, b)); +} + +test "gf16_gt_less_than" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(!gf16_gt(a, b)); +} + +test "gf16_gt_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_gt(GF16_NAN, value)); + try std.testing.expect(!gf16_gt(value, GF16_NAN)); +} + +test "gf16_ge_greater_than_or_equal" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(gf16_ge(a, b)); +} + +test "gf16_ge_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(gf16_ge(a, b)); +} + +test "gf16_ge_less_than" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(!gf16_ge(a, b)); +} + +test "gf16_ge_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_ge(GF16_NAN, value)); + try std.testing.expect(!gf16_ge(value, GF16_NAN)); +} + +test "gf16_comparison_consistency" { + // Verify: lt, le, gt, ge, eq, ne are mutually consistent + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + + // a < b implies a <= b and !a > b and !a >= b + try std.testing.expect(gf16_lt(a, b)); + try std.testing.expect(gf16_le(a, b)); + try std.testing.expect(!gf16_gt(a, b)); + try std.testing.expect(!gf16_ge(a, b)); +} + +test "gf16_floor_positive_value" { + const a = gf16_encode_f32(2.7); + const result = gf16_floor(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_floor_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_floor(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-3.0, decoded, 0.1); +} + +test "gf16_floor_integer" { + const a = gf16_encode_f32(5.0); + const result = gf16_floor(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.05); +} + +test "gf16_floor_zero" { + const pos_zero = gf16_encode_f32(0.0); + const neg_zero = gf16_encode_f32(-0.0); + try std.testing.expect(gf16_is_zero(gf16_floor(pos_zero))); + try std.testing.expect(gf16_is_zero(gf16_floor(neg_zero))); +} + +test "gf16_floor_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_floor(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_floor(GF16_INF_NEG)); +} + +test "gf16_floor_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_floor(GF16_NAN))); +} + +test "gf16_ceil_positive_value" { + const a = gf16_encode_f32(2.3); + const result = gf16_ceil(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_ceil_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_ceil(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_ceil_integer" { + const a = gf16_encode_f32(5.0); + const result = gf16_ceil(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.05); +} + +test "gf16_ceil_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_ceil(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_ceil(GF16_INF_NEG)); +} + +test "gf16_ceil_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_ceil(GF16_NAN))); +} + +test "gf16_round_half_up" { + const a = gf16_encode_f32(2.5); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); // roundTiesToEven +} + +test "gf16_round_positive_value" { + const a = gf16_encode_f32(2.7); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_round_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-3.0, decoded, 0.1); +} + +test "gf16_round_fractional_down" { + const a = gf16_encode_f32(2.3); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_round_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_round(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_round(GF16_INF_NEG)); +} + +test "gf16_round_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_round(GF16_NAN))); +} + +test "gf16_trunc_positive_value" { + const a = gf16_encode_f32(2.7); + const result = gf16_trunc(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_trunc_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_trunc(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_trunc_zero" { + const pos_zero = gf16_encode_f32(0.0); + const neg_zero = gf16_encode_f32(-0.0); + try std.testing.expect(gf16_is_zero(gf16_trunc(pos_zero))); + try std.testing.expect(gf16_is_zero(gf16_trunc(neg_zero))); +} + +test "gf16_trunc_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_trunc(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_trunc(GF16_INF_NEG)); +} + +test "gf16_trunc_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_trunc(GF16_NAN))); +} + +test "gf16_rounding_floor_vs_trunc_negative" { + // floor(-2.7) = -3.0, trunc(-2.7) = -2.0 + const a = gf16_encode_f32(-2.7); + const floored = gf16_decode_to_f32(gf16_floor(a)); + const truncated = gf16_decode_to_f32(gf16_trunc(a)); + try std.testing.expectApproxEqAbs(-3.0, floored, 0.1); + try std.testing.expectApproxEqAbs(-2.0, truncated, 0.1); +} + +test "gf16_rounding_ceil_vs_trunc_positive" { + // ceil(2.3) = 3.0, trunc(2.3) = 2.0 + const a = gf16_encode_f32(2.3); + const ceiled = gf16_decode_to_f32(gf16_ceil(a)); + const truncated = gf16_decode_to_f32(gf16_trunc(a)); + try std.testing.expectApproxEqAbs(3.0, ceiled, 0.1); + try std.testing.expectApproxEqAbs(2.0, truncated, 0.1); +} + +test "gf16_fms_basic" { + const a = gf16_encode_f32(5.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(2.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(13.0, decoded, 0.2); // 5*3 - 2 = 13 +} + +test "gf16_fms_with_zero_c" { + const a = gf16_encode_f32(4.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(0.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(12.0, decoded, 0.2); // 4*3 - 0 = 12 +} + +test "gf16_fms_negative_result" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-4.0, decoded, 0.2); // 2*3 - 10 = -4 +} + +test "gf16_fms_with_nan" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fms(a, b, GF16_NAN); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_fms_zero_a" { + const a = gf16_encode_f32(0.0); + const b = gf16_encode_f32(5.0); + const c = gf16_encode_f32(3.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-3.0, decoded, 0.2); // 0 - 3 = -3 +} + +test "gf16_hypot_pythagorean_triple" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(4.0); + const result = gf16_hypot(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); // sqrt(9 + 16) = 5 +} + +test "gf16_hypot_both_zero" { + try std.testing.expectEqual(GF16_ZERO_POS, gf16_hypot(GF16_ZERO_POS, GF16_ZERO_POS)); +} + +test "gf16_hypot_one_zero" { + const a = gf16_encode_f32(3.0); + const result = gf16_hypot(a, GF16_ZERO_POS); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.05); +} + +test "gf16_hypot_negative_inputs" { + const a = gf16_encode_f32(-3.0); + const b = gf16_encode_f32(-4.0); + const result = gf16_hypot(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); // sqrt(9 + 16) = 5 +} + +test "gf16_hypot_with_nan" { + const a = gf16_encode_f32(3.0); + const result = gf16_hypot(a, GF16_NAN); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_hypot_with_inf" { + const a = gf16_encode_f32(3.0); + try std.testing.expectEqual(GF16_INF_POS, gf16_hypot(a, GF16_INF_POS)); +} + +test "gf16_fmod_basic" { + const a = gf16_encode_f32(10.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fmod(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); // 10 % 3 = 1 +} + +test "gf16_fmod_exact_division" { + const a = gf16_encode_f32(12.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fmod(a, b); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_fmod_negative_dividend" { + const a = gf16_encode_f32(-10.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fmod(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-1.0, decoded, 0.1); // -10 % 3 = -1 (sign follows dividend) +} + +test "gf16_fmod_fractional" { + const a = gf16_encode_f32(5.5); + const b = gf16_encode_f32(2.0); + const result = gf16_fmod(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.5, decoded, 0.1); // 5.5 % 2 = 1.5 +} + +test "gf16_fmod_zero_divisor" { + const a = gf16_encode_f32(10.0); + const zero = gf16_encode_f32(0.0); + const result = gf16_fmod(a, zero); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_fmod_with_nan" { + const a = gf16_encode_f32(10.0); + const result = gf16_fmod(a, GF16_NAN); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_is_finite_normal_numbers" { + // Verify: normal numbers are finite + const n1 = gf16_encode_f32(1.0); + const n2 = gf16_encode_f32(-1.0); + const n3 = gf16_encode_f32(100.5); + try std.testing.expect(gf16_is_finite(n1)); + try std.testing.expect(gf16_is_finite(n2)); + try std.testing.expect(gf16_is_finite(n3)); +} + +test "gf16_is_finite_zero" { + // Verify: zero is finite + const z1 = gf16_encode_f32(0.0); + const z2 = gf16_encode_f32(-0.0); + try std.testing.expect(gf16_is_finite(z1)); + try std.testing.expect(gf16_is_finite(z2)); +} + +test "gf16_is_finite_false_for_inf" { + // Verify: infinity is not finite + const pos_inf = GF16_INF_POS; + const neg_inf = GF16_INF_NEG; + try std.testing.expect(!gf16_is_finite(pos_inf)); + try std.testing.expect(!gf16_is_finite(neg_inf)); +} + +test "gf16_is_finite_false_for_nan" { + // Verify: NaN is not finite + try std.testing.expect(!gf16_is_finite(GF16_NAN)); +} + +test "gf16_is_normal_true_for_normal" { + // Verify: normal numbers return true + const n1 = gf16_encode_f32(1.0); + const n2 = gf16_encode_f32(-2.5); + const n3 = gf16_encode_f32(100.0); + try std.testing.expect(gf16_is_normal(n1)); + try std.testing.expect(gf16_is_normal(n2)); + try std.testing.expect(gf16_is_normal(n3)); +} + +test "gf16_is_normal_false_for_zero" { + // Verify: zero is not normal + const z1 = gf16_encode_f32(0.0); + const z2 = gf16_encode_f32(-0.0); + try std.testing.expect(!gf16_is_normal(z1)); + try std.testing.expect(!gf16_is_normal(z2)); +} + +test "gf16_is_normal_false_for_inf" { + // Verify: infinity is not normal + try std.testing.expect(!gf16_is_normal(GF16_INF_POS)); + try std.testing.expect(!gf16_is_normal(GF16_INF_NEG)); +} + +test "gf16_is_normal_false_for_nan" { + // Verify: NaN is not normal + try std.testing.expect(!gf16_is_normal(GF16_NAN)); +} + +test "gf16_is_subnormal_true_for_subnormal" { + // Verify: subnormal (denormal) numbers return true + // Smallest subnormal in GF16: exp=0, mant=1 (approximately 2^-14 * 2^-9 = 2^-23) + // We'll check a value that decodes to subnormal + const sub = gf16_encode_f32(0.000001); + const decoded = gf16_decode_to_f32(sub); + // If the value rounds to subnormal, is_subnormal should be true + // This test depends on GF16 subnormal threshold (~6.1e-5) + const is_sub = gf16_is_subnormal(sub); + _ = decoded; + _ = is_sub; + // We just verify the function doesn't crash for now + try std.testing.expect(true); +} + +test "gf16_is_subnormal_false_for_normal" { + // Verify: normal numbers are not subnormal + const n1 = gf16_encode_f32(1.0); + const n2 = gf16_encode_f32(100.0); + try std.testing.expect(!gf16_is_subnormal(n1)); + try std.testing.expect(!gf16_is_subnormal(n2)); +} + +test "gf16_is_subnormal_false_for_zero" { + // Verify: zero is not subnormal (zero is a special case) + const z1 = gf16_encode_f32(0.0); + const z2 = gf16_encode_f32(-0.0); + try std.testing.expect(!gf16_is_subnormal(z1)); + try std.testing.expect(!gf16_is_subnormal(z2)); +} + +test "gf16_is_subnormal_false_for_special" { + // Verify: NaN and infinity are not subnormal + try std.testing.expect(!gf16_is_subnormal(GF16_NAN)); + try std.testing.expect(!gf16_is_subnormal(GF16_INF_POS)); + try std.testing.expect(!gf16_is_subnormal(GF16_INF_NEG)); +} + +test "gf16_classification_complete_coverage" { + // Verify: all GF16 values can be classified + // For any value, exactly one of these should be true: + // - is_finite and (is_normal or is_subnormal or is_zero) + // OR is_inf + // OR is_nan + + const test_values = [_]f32{ + 0.0, -0.0, 1.0, -1.0, 100.0, -100.0, + 0.0001, -0.0001, + }; + + for (test_values) |val| { + const gf = gf16_encode_f32(val); + const is_fin = gf16_is_finite(gf); + const is_inf = gf16_is_inf(gf); + const is_nan = gf16_is_nan(gf); + + // Exactly one of finite, inf, nan should be true + const count = @as(u8, @intFromBool(is_fin)) + + @as(u8, @intFromBool(is_inf)) + + @as(u8, @intFromBool(is_nan)); + try std.testing.expectEqual(@as(u8, 1), count); + } +} + +test "gf16_signbit_positive" { + // Verify: positive values have signbit = false + const val = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_signbit(val)); +} + +test "gf16_signbit_negative" { + // Verify: negative values have signbit = true + const val = gf16_encode_f32(-1.5); + try std.testing.expect(gf16_signbit(val)); +} + +test "gf16_signbit_positive_zero" { + // Verify: positive zero has signbit = false + const zero_pos = GF16_ZERO_POS; + try std.testing.expect(!gf16_signbit(zero_pos)); +} + +test "gf16_signbit_negative_zero" { + // Verify: negative zero has signbit = true + const zero_neg = GF16_ZERO_NEG; + try std.testing.expect(gf16_signbit(zero_neg)); +} + +test "gf16_signbit_infinity" { + // Verify: signbit is set for negative infinity, not for positive + try std.testing.expect(!gf16_signbit(GF16_INF_POS)); + try std.testing.expect(gf16_signbit(GF16_INF_NEG)); +} + +test "gf16_signbit_nan" { + // Verify: NaN can have signbit set or not (we check both cases) + // Most NaN implementations propagate signbit + const nan_with_sign = GF16_NAN | 0x8000; + try std.testing.expect(gf16_signbit(nan_with_sign)); +} + +test "gf16_sign_positive" { + // Verify: positive values return +1 + const v1 = gf16_encode_f32(1.0); + const v2 = gf16_encode_f32(100.5); + try std.testing.expectEqual(@as(i8, 1), gf16_sign(v1)); + try std.testing.expectEqual(@as(i8, 1), gf16_sign(v2)); +} + +test "gf16_sign_negative" { + // Verify: negative values return -1 + const v1 = gf16_encode_f32(-1.0); + const v2 = gf16_encode_f32(-100.5); + try std.testing.expectEqual(@as(i8, -1), gf16_sign(v1)); + try std.testing.expectEqual(@as(i8, -1), gf16_sign(v2)); +} + +test "gf16_sign_zero" { + // Verify: zero (positive or negative) returns 0 + try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_ZERO_POS)); + try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_ZERO_NEG)); +} + +test "gf16_sign_nan" { + // Verify: NaN returns 0 (IEEE 754 specifies sign of NaN is undefined) + try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_NAN)); +} + +test "gf16_sign_infinity" { + // Verify: positive infinity returns +1, negative returns -1 + try std.testing.expectEqual(@as(i8, 1), gf16_sign(GF16_INF_POS)); + try std.testing.expectEqual(@as(i8, -1), gf16_sign(GF16_INF_NEG)); +} + +test "gf16_sign_matches_signbit" { + // Verify: gf16_sign and gf16_signbit are consistent for non-zero values + const pos_val = gf16_encode_f32(5.5); + const neg_val = gf16_encode_f32(-5.5); + + try std.testing.expect(!gf16_signbit(pos_val) and gf16_sign(pos_val) > 0); + try std.testing.expect(gf16_signbit(neg_val) and gf16_sign(neg_val) < 0); +} + +test "gf16_clamp_in_range" { + // Verify: value within range is unchanged + const x = gf16_encode_f32(5.0); + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); +} + +test "gf16_clamp_below_min" { + // Verify: value below min returns min + const x = gf16_encode_f32(-5.0); + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.0, decoded, 0.1); +} + +test "gf16_clamp_above_max" { + // Verify: value above max returns max + const x = gf16_encode_f32(15.0); + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.1); +} + +test "gf16_clamp_with_nan" { + // Verify: NaN propagates + const x = GF16_NAN; + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_lerp_t_zero" { + // Verify: lerp with t=0 returns a + const a = gf16_encode_f32(10.0); + const b = gf16_encode_f32(20.0); + const t = gf16_encode_f32(0.0); + const result = gf16_lerp(a, b, t); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.1); +} + +test "gf16_lerp_t_one" { + // Verify: lerp with t=1 returns b + const a = gf16_encode_f32(10.0); + const b = gf16_encode_f32(20.0); + const t = gf16_encode_f32(1.0); + const result = gf16_lerp(a, b, t); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(20.0, decoded, 0.1); +} + +test "gf16_lerp_t_half" { + // Verify: lerp with t=0.5 returns midpoint + const a = gf16_encode_f32(0.0); + const b = gf16_encode_f32(10.0); + const t = gf16_encode_f32(0.5); + const result = gf16_lerp(a, b, t); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); +} + +test "gf16_lerp_with_nan" { + // Verify: NaN propagates + const a = GF16_NAN; + const b = gf16_encode_f32(20.0); + const t = gf16_encode_f32(0.5); + const result = gf16_lerp(a, b, t); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_fnma_basic" { + // Verify: fnma(a, b, c) = -(a*b) + c + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fnma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-(2.0 * 3.0) + 10.0, decoded, 0.1); // = 4.0 +} + +test "gf16_fnma_zero_multiplier" { + // Verify: fnma with zero multiplier returns c + const a = gf16_encode_f32(0.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fnma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.1); +} + +test "gf16_fnma_zero_addend" { + // Verify: fnma with c=0 returns -(a*b) + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(0.0); + const result = gf16_fnma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-(2.0 * 3.0), decoded, 0.1); // = -6.0 +} + +test "gf16_fnma_with_nan" { + // Verify: NaN propagates + const a = GF16_NAN; + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fnma(a, b, c); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_exp_zero" { + // Verify: e^0 = 1 + const x = gf16_encode_f32(0.0); + const result = gf16_exp(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); +} + +test "gf16_exp_one" { + // Verify: e^1 ~= 2.718 + const x = gf16_encode_f32(1.0); + const result = gf16_exp(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.718, decoded, 0.1); +} + +test "gf16_exp_negative" { + // Verify: e^-1 ~= 0.368 + const x = gf16_encode_f32(-1.0); + const result = gf16_exp(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.368, decoded, 0.05); +} + +test "gf16_exp_large_positive" { + // Verify: e^88 is very large (returns Inf) + const x = gf16_encode_f32(88.0); + const result = gf16_exp(x); + try std.testing.expect(gf16_is_inf(result)); +} + +test "gf16_log_one" { + // Verify: ln(1) = 0 + const x = gf16_encode_f32(1.0); + const result = gf16_log(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.0, decoded, 0.05); +} + +test "gf16_log_e" { + // Verify: ln(e) ~= 1 + const e = gf16_encode_f32(2.71828); + const result = gf16_log(e); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); +} + +test "gf16_log_zero_or_negative" { + // Verify: ln(0) or ln(x<0) = NaN + const zero = gf16_encode_f32(0.0); + const neg = gf16_encode_f32(-1.0); + try std.testing.expect(gf16_is_nan(gf16_log(zero))); + try std.testing.expect(gf16_is_nan(gf16_log(neg))); +} + +test "gf16_log2_eight" { + // Verify: log2(8) = 3 + const x = gf16_encode_f32(8.0); + const result = gf16_log2(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_log10_ten" { + // Verify: log10(10) = 1 + const x = gf16_encode_f32(10.0); + const result = gf16_log10(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); +} + +test "gf16_pow_two_cubed" { + // Verify: 2^3 = 8 + const base = gf16_encode_f32(2.0); + const exp = gf16_encode_f32(3.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(8.0, decoded, 0.1); +} + +test "gf16_pow_zero_to_zero" { + // Verify: 0^0 = 1 (by convention) + const base = gf16_encode_f32(0.0); + const exp = gf16_encode_f32(0.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.01); +} + +test "gf16_pow_any_to_zero" { + // Verify: x^0 = 1 for x != 0 + const base = gf16_encode_f32(5.5); + const exp = gf16_encode_f32(0.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.01); +} + +test "gf16_pow_zero_to_positive" { + // Verify: 0^x = 0 for x > 0 + const base = gf16_encode_f32(0.0); + const exp = gf16_encode_f32(2.0); + const result = gf16_pow(base, exp); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_pow_one_to_any" { + // Verify: 1^x = 1 + const base = gf16_encode_f32(1.0); + const exp = gf16_encode_f32(5.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.01); +} + +test "gf16_sin_zero" { + // Verify: sin(0) = 0 + const x = gf16_encode_f32(0.0); + const result = gf16_sin(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.0, decoded, 0.05); +} + +test "gf16_sin_small_angle" { + // Verify: sin(pi/6) ~= 0.5 + const pi_six = gf16_encode_f32(3.14159 / 6.0); + const result = gf16_sin(pi_six); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.5, decoded, 0.05); +} + +test "gf16_cos_zero" { + // Verify: cos(0) = 1 + const x = gf16_encode_f32(0.0); + const result = gf16_cos(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.05); +} + +test "gf16_cos_small_angle" { + // Verify: cos(pi/6) ~= 0.866 + const pi_six = gf16_encode_f32(3.14159 / 6.0); + const result = gf16_cos(pi_six); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.866, decoded, 0.05); +} + +test "gf16_trig_identity" { + // Verify: sin^2(x) + cos^2(x) ~= 1 for small x + const x = gf16_encode_f32(0.5); + const sin_val = gf16_decode_to_f32(gf16_sin(x)); + const cos_val = gf16_decode_to_f32(gf16_cos(x)); + const sum = sin_val * sin_val + cos_val * cos_val; + try std.testing.expectApproxEqAbs(1.0, sum, 0.1); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "gf16_encode_throughput" { + // Measure: gf16_encode_f32 calls per second + // Target: > 10M encodes/sec on typical hardware + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_encode_f32(1.5); + } +} + +bench "gf16_decode_throughput" { + // Measure: gf16_decode_to_f32 calls per second + // Target: > 10M decodes/sec on typical hardware + @setEvalBranchQuota(10000); + var result: f32 = 0; + for (0..1000) |_| { + result = gf16_decode_to_f32(0x3C00); + } +} + +bench "gf16_roundtrip_latency" { + // Measure: encode + decode latency in nanoseconds + // Target: < 100ns for typical values + @setEvalBranchQuota(10000); + var result: f32 = 0; + const value: f32 = 1.5; + for (0..1000) |_| { + result = gf16_decode_to_f32(gf16_encode_f32(value)); + } +} + +bench "gf16_round_phi_latency" { + // Measure: nanoseconds to gf16_round_phi(1.0) + // Target: < 200ns + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_round_phi(1.0); + } +} + +bench "gf16_extract_sign_latency" { + // Measure: nanoseconds to extract sign + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: i8 = 0; + for (0..1000) |_| { + result = gf16_extract_sign(0x8000); + } +} + +bench "gf16_extract_exponent_latency" { + // Measure: nanoseconds to extract exponent + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: i8 = 0; + for (0..1000) |_| { + result = gf16_extract_exponent(0x3C00); + } +} + +bench "gf16_is_inf_latency" { + // Measure: nanoseconds to check if infinity + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = gf16_is_inf(0x7E00); + } +} + +bench "gf16_is_nan_latency" { + // Measure: nanoseconds to check if NaN + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = gf16_is_nan(0xFE01); + } +} + +bench "gf16_negate_latency" { + // Measure: nanoseconds to negate + // Target: < 10ns (single XOR operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_negate(0x3C00); + } +} + +bench "gf16_abs_latency" { + // Measure: nanoseconds to compute absolute value + // Target: < 10ns (single AND operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_abs(0xBC00); + } +} + +bench "gf16_max_latency" { + // Measure: nanoseconds to compute max of two values + // Target: < 100ns (includes decode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_max(a, b); + } +} + +bench "gf16_min_latency" { + // Measure: nanoseconds to compute min of two values + // Target: < 100ns (includes decode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_min(a, b); + } +} + +bench "gf16_add_latency" { + // Measure: nanoseconds to add two values + // Target: < 200ns (includes decode + add + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_add(a, b); + } +} + +bench "gf16_sub_latency" { + // Measure: nanoseconds to subtract two values + // Target: < 200ns (includes decode + sub + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_sub(a, b); + } +} + +bench "gf16_mul_latency" { + // Measure: nanoseconds to multiply two values + // Target: < 200ns (includes decode + mul + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3D80; + for (0..1000) |_| { + result = gf16_mul(a, b); + } +} + +bench "gf16_div_latency" { + // Measure: nanoseconds to divide two values + // Target: < 300ns (includes decode + div + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + for (0..1000) |_| { + result = gf16_div(a, b); + } +} + +bench "gf16_sqrt_latency" { + // Measure: nanoseconds to compute square root + // Target: < 300ns (includes decode + sqrt + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_sqrt(a); + } +} + +bench "gf16_fma_latency" { + // Measure: nanoseconds for fused multiply-add + // Target: < 300ns (fused operation, more accurate than separate) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + const c: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_fma(a, b, c); + } +} + +bench "gf16_square_latency" { + // Measure: nanoseconds to square a value + // Target: < 200ns (uses mul internally) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_square(a); + } +} + +bench "gf16_eq_latency" { + // Measure: nanoseconds to compare equality + // Target: < 30ns (simple comparison with NaN check) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_eq(a, b); + } +} + +bench "gf16_ne_latency" { + // Measure: nanoseconds to compare not-equal + // Target: < 30ns (negation of eq) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_ne(a, b); + } +} + +bench "gf16_lt_latency" { + // Measure: nanoseconds to compare less-than + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_lt(a, b); + } +} + +bench "gf16_le_latency" { + // Measure: nanoseconds to compare less-than-or-equal + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_le(a, b); + } +} + +bench "gf16_gt_latency" { + // Measure: nanoseconds to compare greater-than + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_gt(a, b); + } +} + +bench "gf16_ge_latency" { + // Measure: nanoseconds to compare greater-than-or-equal + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_ge(a, b); + } +} + +bench "gf16_floor_latency" { + // Measure: nanoseconds to compute floor + // Target: < 200ns (includes decode + floor + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_floor(a); + } +} + +bench "gf16_ceil_latency" { + // Measure: nanoseconds to compute ceil + // Target: < 200ns (includes decode + ceil + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_ceil(a); + } +} + +bench "gf16_round_latency" { + // Measure: nanoseconds to compute round + // Target: < 200ns (includes decode + round + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_round(a); + } +} + +bench "gf16_trunc_latency" { + // Measure: nanoseconds to compute trunc + // Target: < 200ns (includes decode + trunc + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_trunc(a); + } +} + +bench "gf16_fms_latency" { + // Measure: nanoseconds for fused multiply-subtract + // Target: < 300ns (fused operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + const c: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_fms(a, b, c); + } +} + +bench "gf16_hypot_latency" { + // Measure: nanoseconds to compute hypotenuse + // Target: < 400ns (includes sqrt) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D80; + const b: GF16 = 0x3E00; + for (0..1000) |_| { + result = gf16_hypot(a, b); + } +} + +bench "gf16_fmod_latency" { + // Measure: nanoseconds to compute modulo + // Target: < 300ns (includes decode + mod + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3F00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_fmod(a, b); + } +} + +bench "gf16_is_finite_latency" { + // Measure: nanoseconds to check if value is finite + // Target: < 30ns (simple bit checks) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_is_finite(val); + } +} + +bench "gf16_is_normal_latency" { + // Measure: nanoseconds to check if value is normal + // Target: < 40ns (extraction + range check) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_is_normal(val); + } +} + +bench "gf16_is_subnormal_latency" { + // Measure: nanoseconds to check if value is subnormal + // Target: < 40ns (extraction + mantissa check) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x0001; + for (0..1000) |_| { + result = gf16_is_subnormal(val); + } +} + +bench "gf16_signbit_latency" { + // Measure: nanoseconds to check sign bit + // Target: < 5ns (single bit test) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x8000; + for (0..1000) |_| { + result = gf16_signbit(val); + } +} + +bench "gf16_sign_latency" { + // Measure: nanoseconds to get sign value + // Target: < 30ns (includes zero/nan/inf checks) + @setEvalBranchQuota(10000); + var result: i8 = 0; + const val: GF16 = 0xBC00; + for (0..1000) |_| { + result = gf16_sign(val); + } +} + +bench "gf16_clamp_latency" { + // Measure: nanoseconds to clamp value to range + // Target: < 200ns (includes decode + compare + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3F00; + const min_val: GF16 = 0x3C00; + const max_val: GF16 = 0x4800; + for (0..1000) |_| { + result = gf16_clamp(x, min_val, max_val); + } +} + +bench "gf16_lerp_latency" { + // Measure: nanoseconds to compute linear interpolation + // Target: < 300ns (includes decode + computation + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3C00; + const b: GF16 = 0x4800; + const t: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_lerp(a, b, t); + } +} + +bench "gf16_fnma_latency" { + // Measure: nanoseconds for fused negative multiply-add + // Target: < 300ns (fused operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + const c: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_fnma(a, b, c); + } +} + +bench "gf16_exp_latency" { + // Measure: nanoseconds to compute exponential + // Target: < 500ns (Taylor series) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_exp(x); + } +} + +bench "gf16_log_latency" { + // Measure: nanoseconds to compute natural log + // Target: < 300ns (includes decode + log + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3E00; + for (0..1000) |_| { + result = gf16_log(x); + } +} + +bench "gf16_pow_latency" { + // Measure: nanoseconds to compute power + // Target: < 400ns (includes decode + pow + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const base: GF16 = 0x3D00; + const exp: GF16 = 0x3D80; + for (0..1000) |_| { + result = gf16_pow(base, exp); + } +} + +bench "gf16_sin_latency" { + // Measure: nanoseconds to compute sine + // Target: < 500ns (Taylor series) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_sin(x); + } +} + +bench "gf16_cos_latency" { + // Measure: nanoseconds to compute cosine + // Target: < 500ns (Taylor series) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_cos(x); + } +} + + + + diff --git a/apps/website/public/t27/specs/numeric/gf20.t27 b/apps/website/public/t27/specs/numeric/gf20.t27 new file mode 100644 index 0000000000..35e6852485 --- /dev/null +++ b/apps/website/public/t27/specs/numeric/gf20.t27 @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf20.t27 +// GoldenFloat20 -- 20-bit phi-structured floating point +// NUMERIC-STANDARD-001 -- Agent 6 (P1) + +module GF20 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // ================================================================= + // 1. Format Definition + // ========================================================================= + + // GF20 bit layout: [S|EEE EEE|MMM MMMM MMMM MMM] + // S: 1 bit (sign) + // E: 7 bits (exponent) + // M: 12 bits (mantissa) + + const BITS : u8 = 20; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 7; + const MANT_BITS : u8 = 12; + + // Bias for exponent (2^(7-1) - 1 = 63) + const EXP_BIAS : u8 = 63; + + // phi-ratio: exp/mant = 7/12 ~= 0.583 (phi_distance = 0.035) + const PHI_DISTANCE : f64 = 0.03463264154356299; + + // ================================================================= + // 2. GoldenFloat20 Type + // ========================================================================= + + struct GF20 { + raw : u32, // 20-bit value stored in u32 + } + + // ================================================================= + // 3. Encoding/Decoding + // ========================================================================= + + // Encode f32 to GF20 + fn encode(value: f32) -> GF20 { + if (value == 0.0) { + return GF20{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i16; + const exp_biased = (exp_unbiased + EXP_BIAS as i16) as u8; + + // Clamp exponent + const exp_clamped = clamp(exp_biased, 0, (1 << EXP_BITS) - 1); + + // Extract mantissa (12 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF20{ + raw = ((sign as u32) << 19) | + ((exp_clamped as u32) << MANT_BITS) | + (mant as u32) + }; + } + + // Decode GF20 to f32 + fn decode(gf: GF20) -> f32 { + const sign = (gf.raw >> 19) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x7F) as u8; + const mant = (gf.raw & 0xFFF) as u16; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS as i16 + 1 + } else { + (exp_biased as i16) - EXP_BIAS as i16 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 4096.0 + } else { + 1.0 + (mant as f32) / 4096.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // ================================================================= + // 4. Format Properties + // ========================================================================= + + fn max_value() -> f32 { + const mant_max = 1.0 + 4095.0 / 4096.0; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 4096.0; + const exp_min = -EXP_BIAS as i16 + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 4096.0; // 0.00024414 + } + + // ================================================================= + // 5. Validation + // ========================================================================= + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF20"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // ================================================================= + // 6. Use Cases + // ========================================================================= + + // GF20 is optimal for: + // - High-precision ML training + // - Gradient accumulation + // - Scientific computing + // - Near-fp32 quality with 38% memory savings + + // Memory: 20 bits = 2.5 bytes (~1.6x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 20.0 / 32.0; // 0.625 + + // ================================================================= + // 7. Helper Functions + // ========================================================================= + + fn floor_log2(x: f32) -> i16 { + if (x <= 0.0) { return -32768; } + let exp : i16 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i16, mant_bits: u8) -> u16 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1u16 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u16; + } + + fn clamp(x: u8, min: u8, max: u8) -> u8 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF20 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // ======================================================================================================= + // TDD-Inside-Spec: Tests and Invariants for GF20 + // ======================================================================================================= + + test gf20_decode_zero + given gf = GF20{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf20_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf20_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf20_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf20_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf20_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf20_phi_distance_within_tolerance + given phi_dist = PHI_DISTANCE + then phi_dist < 0.04 + + test gf20_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.625) < 0.01 + + test gf20_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf20_bits_constant + assert BITS == 20 + + invariant gf20_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf20_exp_bits_is_seven + assert EXP_BITS == 7 + + invariant gf20_mant_bits_is_twelve + assert MANT_BITS == 12 + + invariant gf20_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf20_phi_distance_below_threshold + assert PHI_DISTANCE < 0.04 + + invariant gf20_exp_bias_positive + assert EXP_BIAS > 0 + + test gf20_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf20_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf20_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf20_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf20_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf20_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf20_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf20_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf20_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf20_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf20_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf20_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf20_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf20_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf20_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf20_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf20_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf20_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf20_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf20_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf20_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf20_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf20_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf20_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf20_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf20_encode_latency + measure: nanoseconds to encode(1.0) + target: < 200ns + + bench gf20_decode_latency + measure: nanoseconds to decode(GF20{raw = 524288}) + target: < 150ns +} diff --git a/apps/website/public/t27/specs/numeric/gf24.t27 b/apps/website/public/t27/specs/numeric/gf24.t27 new file mode 100644 index 0000000000..8ef14eef62 --- /dev/null +++ b/apps/website/public/t27/specs/numeric/gf24.t27 @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf24.t27 +// GoldenFloat24 -- 24-bit phi-structured floating point +// NUMERIC-STANDARD-001 -- Agent 7 (P1) + +module GF24 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // ================================================================= + // 1. Format Definition + // ========================================================================= + + // GF24 bit layout: [S|EEEE EEEE|MMM MMMM MMMM MMMM MM] + // S: 1 bit (sign) + // E: 9 bits (exponent) + // M: 14 bits (mantissa) + + const BITS : u8 = 24; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 9; + const MANT_BITS : u8 = 14; + + // Bias for exponent (2^(9-1) - 1 = 255) + const EXP_BIAS : u16 = 255; + + // phi-ratio: exp/mant = 9/14 ~= 0.643 (phi_distance = 0.025) + const PHI_DISTANCE : f64 = 0.02482317991669112; + + // ================================================================= + // 2. GoldenFloat24 Type + // ========================================================================= + + struct GF24 { + raw : u32, // 24-bit value stored in u32 + } + + // ================================================================= + // 3. Encoding/Decoding + // ========================================================================= + + // Encode f32 to GF24 + fn encode(value: f32) -> GF24 { + if (value == 0.0) { + return GF24{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i16; + const exp_biased = (exp_unbiased + EXP_BIAS as i16) as u16; + + // Clamp exponent + const exp_clamped = clamp_u16(exp_biased, 0, (1u16 << EXP_BITS) - 1); + + // Extract mantissa (14 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF24{ + raw = ((sign as u32) << 23) | + ((exp_clamped as u32) << MANT_BITS) | + (mant as u32) + }; + } + + // Decode GF24 to f32 + fn decode(gf: GF24) -> f32 { + const sign = (gf.raw >> 23) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x1FF) as u16; + const mant = (gf.raw & 0x3FFF) as u16; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -(EXP_BIAS as i16) + 1 + } else { + (exp_biased as i16) - EXP_BIAS as i16 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 16384.0 + } else { + 1.0 + (mant as f32) / 16384.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // ================================================================= + // 4. Format Properties + // ========================================================================= + + fn max_value() -> f32 { + const mant_max = 1.0 + 16383.0 / 16384.0; + const exp_max = (1i16 << EXP_BITS) - 1 - EXP_BIAS as i16; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 16384.0; + const exp_min = -(EXP_BIAS as i16) + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 16384.0; // 0.000061035 + } + + // ================================================================= + // 5. Validation + // ========================================================================= + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF24"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // ================================================================= + // 6. Use Cases + // ========================================================================= + + // GF24 is optimal for: + // - Very high precision quantization + // - Critical numerical stability + // - Financial calculations + // - 25% memory savings vs FP32 + + // Memory: 24 bits = 3 bytes (~1.33x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 24.0 / 32.0; // 0.75 + + // ================================================================= + // 7. Helper Functions + // ========================================================================= + + fn floor_log2(x: f32) -> i16 { + if (x <= 0.0) { return -32768; } + let exp : i16 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i16, mant_bits: u8) -> u16 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1u16 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u16; + } + + fn clamp_u16(x: u16, min: u16, max: u16) -> u16 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF24 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // ======================================================================================================= + // TDD-Inside-Spec: Tests and Invariants for GF24 + // ======================================================================================================= + + test gf24_decode_zero + given gf = GF24{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf24_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf24_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf24_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf24_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf24_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf24_phi_distance_within_tolerance + given phi_dist = PHI_DISTANCE + then phi_dist < 0.03 + + test gf24_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.75) < 0.01 + + test gf24_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf24_bits_constant + assert BITS == 24 + + invariant gf24_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf24_exp_bits_is_nine + assert EXP_BITS == 9 + + invariant gf24_mant_bits_is_fourteen + assert MANT_BITS == 14 + + invariant gf24_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf24_phi_distance_below_threshold + assert PHI_DISTANCE < 0.03 + + invariant gf24_exp_bias_positive + assert EXP_BIAS > 0 + + test gf24_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf24_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf24_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf24_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf24_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf24_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf24_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf24_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf24_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf24_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf24_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf24_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf24_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf24_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf24_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf24_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf24_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf24_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf24_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf24_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf24_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf24_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf24_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf24_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf24_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf24_encode_latency + measure: nanoseconds to encode(1.0) + target: < 250ns + + bench gf24_decode_latency + measure: nanoseconds to decode(GF24{raw = 8388608}) + target: < 200ns +} diff --git a/apps/website/public/t27/specs/numeric/gf32.t27 b/apps/website/public/t27/specs/numeric/gf32.t27 new file mode 100644 index 0000000000..947e010d18 --- /dev/null +++ b/apps/website/public/t27/specs/numeric/gf32.t27 @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf32.t27 +// GoldenFloat32 -- 32-bit phi-structured floating point +// NUMERIC-STANDARD-001 -- Agent 8 (P1) + +module GF32 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // ================================================================= + // 1. Format Definition + // ========================================================================= + + // GF32 bit layout: [S|EEEE EEEE EEEE|MMM MMMM MMMM MMMM MMMM MMM] + // S: 1 bit (sign) + // E: 12 bits (exponent) + // M: 19 bits (mantissa) + + const BITS : u8 = 32; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 12; + const MANT_BITS : u8 = 19; + + // Bias for exponent (2^(12-1) - 1 = 2047) + const EXP_BIAS : u16 = 2047; + + // phi-ratio: exp/mant = 12/19 ~= 0.632 (phi_distance = 0.014) + // This is the second-best phi-approximation after GF12 + const PHI_DISTANCE : f64 = 0.01354495894042812; + + // ================================================================= + // 2. GoldenFloat32 Type + // ========================================================================= + + struct GF32 { + raw : u32, // 32-bit raw value + } + + // ================================================================= + // 3. Encoding/Decoding + // ========================================================================= + + // Encode f32 to GF32 + fn encode(value: f32) -> GF32 { + if (value == 0.0) { + return GF32{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1u32 } else { 0u32 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i16; + const exp_biased = (exp_unbiased + EXP_BIAS as i16) as u16; + + // Clamp exponent + const exp_clamped = clamp_u16(exp_biased, 0, (1u16 << EXP_BITS) - 1); + + // Extract mantissa (19 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF32{ + raw = (sign << 31) | + ((exp_clamped as u32) << MANT_BITS) | + (mant as u32) + }; + } + + // Decode GF32 to f32 + fn decode(gf: GF32) -> f32 { + const sign = (gf.raw >> 31) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0xFFF) as u16; + const mant = (gf.raw & 0x7FFFF) as u32; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -(EXP_BIAS as i16) + 1 + } else { + (exp_biased as i16) - EXP_BIAS as i16 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 524288.0 + } else { + 1.0 + (mant as f32) / 524288.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // ================================================================= + // 4. Format Properties + // ========================================================================= + + fn max_value() -> f32 { + const mant_max = 1.0 + 524287.0 / 524288.0; + const exp_max = (1i16 << EXP_BITS) - 1 - EXP_BIAS as i16; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 524288.0; + const exp_min = -(EXP_BIAS as i16) + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 524288.0; // 0.000001907 + } + + // ================================================================= + // 5. Validation + // ========================================================================= + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF32"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // ================================================================= + // 6. Use Cases + // ========================================================================= + + // GF32 is optimal for: + // - Near-IEEE 754 precision with phi-optimized layout + // - 12-bit exponent (vs IEEE's 8-bit) for wider dynamic range + // - 19-bit mantissa (vs IEEE's 23-bit) - still good precision + // - Same memory footprint as FP32, better phi-ratio + + // Comparison with IEEE FP32: + // - IEEE: 1 sign, 8 exp, 23 mant -> exp/mant = 0.348 (phi_distance = 0.270) + // - GF32: 1 sign, 12 exp, 19 mant -> exp/mant = 0.632 (phi_distance = 0.014) + + // Memory: 32 bits = 4 bytes (same as FP32) + const MEMORY_RATIO_VS_FP32 : f32 = 1.0; + + // ================================================================= + // 7. Helper Functions + // ========================================================================= + + fn floor_log2(x: f32) -> i16 { + if (x <= 0.0) { return -32768; } + let exp : i16 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i16, mant_bits: u8) -> u32 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1u32 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u32; + } + + fn clamp_u16(x: u16, min: u16, max: u16) -> u16 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF32 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // ======================================================================================================= + // TDD-Inside-Spec: Tests and Invariants for GF32 + // ======================================================================================================= + + test gf32_decode_zero + given gf = GF32{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf32_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf32_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf32_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf32_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf32_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf32_phi_distance_near_optimal + given phi_dist = PHI_DISTANCE + then phi_dist < 0.015 + + test gf32_memory_ratio_equals_one + given ratio = MEMORY_RATIO_VS_FP32 + then ratio == 1.0 + + test gf32_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf32_bits_constant + assert BITS == 32 + + invariant gf32_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf32_exp_bits_is_twelve + assert EXP_BITS == 12 + + invariant gf32_mant_bits_is_nineteen + assert MANT_BITS == 19 + + invariant gf32_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf32_phi_distance_near_optimal + assert PHI_DISTANCE < 0.015 + + invariant gf32_exp_bias_positive + assert EXP_BIAS > 0 + + invariant gf32_exp_wider_than_ieee + assert EXP_BITS > 8 // IEEE FP32 has 8-bit exponent + + invariant gf32_mant_narrower_than_ieee + assert MANT_BITS < 23 // IEEE FP32 has 23-bit mantissa + + test gf32_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf32_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf32_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf32_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf32_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf32_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf32_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf32_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf32_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf32_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf32_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf32_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf32_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf32_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf32_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf32_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf32_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf32_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf32_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf32_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf32_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf32_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf32_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf32_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf32_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf32_encode_latency + measure: nanoseconds to encode(1.0) + target: < 300ns + + bench gf32_decode_latency + measure: nanoseconds to decode(GF32{raw = 1065353216}) + target: < 250ns +} diff --git a/apps/website/public/t27/specs/numeric/gf4.t27 b/apps/website/public/t27/specs/numeric/gf4.t27 new file mode 100644 index 0000000000..b62f0acc69 --- /dev/null +++ b/apps/website/public/t27/specs/numeric/gf4.t27 @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf4.t27 +// GoldenFloat4 -- 4-bit phi-structured floating point +// NUMERIC-STANDARD-001 -- Agent 2 (P1) + +module GF4 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // ================================================================= + // 1. Format Definition + // ========================================================================= + + // GF4 bit layout: [S|E|MM] + // S: 1 bit (sign) + // E: 1 bit (exponent) + // M: 2 bits (mantissa) + + const BITS : u8 = 4; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 1; + const MANT_BITS : u8 = 2; + + // Bias for exponent (0-biased for GF4) + const EXP_BIAS : u8 = 0; + + // phi-ratio: exp/mant = 1/2 = 0.5 (phi_distance = 0.118) + const PHI_DISTANCE : f64 = 0.1180339887498949; + + // ================================================================= + // 2. GoldenFloat4 Type + // ========================================================================= + + struct GF4 { + raw : u4, // 4-bit raw value + } + + // ================================================================= + // 3. Encoding/Decoding + // ========================================================================= + + // Encode f32 to GF4 + fn encode(value: f32) -> GF4 { + // Special cases + if (value == 0.0) { + return GF4{ raw = 0b0000 }; + } + if (value < 0.0) { + const pos = encode(-value).raw; + return GF4{ raw = pos | 0b1000 }; // Set sign bit + } + + // For GF4, quantize to available values + // Available positive values (mant * exp_scale): + // mant=0.00, exp=1.0 -> 0.00 + // mant=0.25, exp=1.0 -> 0.25 + // mant=0.50, exp=1.0 -> 0.50 + // mant=0.75, exp=1.0 -> 0.75 + // mant=0.00, exp=2.0 -> 0.00 + // mant=0.25, exp=2.0 -> 0.50 + // mant=0.50, exp=2.0 -> 1.00 + // mant=0.75, exp=2.0 -> 1.50 + + // Unique positive non-zero values: 0.25, 0.5, 0.75, 1.0, 1.5 + + if (value <= 0.375) { + // 0.25 + return GF4{ raw = 0b0001 }; + } else if (value <= 0.625) { + // 0.5 + return GF4{ raw = 0b0010 }; + } else if (value <= 0.875) { + // 0.75 + return GF4{ raw = 0b0011 }; + } else if (value <= 1.25) { + // 1.0 + return GF4{ raw = 0b0101 }; + } else { + // 1.5 (max) + return GF4{ raw = 0b0111 }; + } + } + + // Decode GF4 to f32 + fn decode(gf: GF4) -> f32 { + const sign_bit = (gf.raw & 0b1000) != 0; + const exp_bit = (gf.raw & 0b0100) != 0; + const mant_bits = gf.raw & 0b0011; + + // Zero + if (gf.raw == 0) { + return 0.0; + } + + // Decode mantissa (2 bits -> values 0, 0.25, 0.5, 0.75) + const mant = (mant_bits as f32) / 4.0; + + // Decode exponent (1 bit -> 1.0 or 2.0) + const exp_scale = if (exp_bit) { 2.0 } else { 1.0 }; + + const value = mant * exp_scale; + + if (sign_bit) { + return -value; + } + return value; + } + + // ================================================================= + // 4. Format Properties + // ========================================================================= + + fn max_value() -> f32 { + // Max: mant=0.75, exp=2.0 -> 1.5 + return 1.5; + } + + fn min_positive() -> f32 { + // Min positive: mant=0.25, exp=1.0 -> 0.25 + return 0.25; + } + + fn epsilon() -> f32 { + // Smallest representable difference at 1.0 + return 0.25; + } + + // ================================================================= + // 5. Validation + // ========================================================================= + + fn validate_format() -> bool { + // Check that we match the goldenfloat_family definition + const fmt = goldenfloat_family::get_format_by_name("GF4"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // ================================================================= + // 6. Use Cases + // ========================================================================= + + // GF4 is optimal for: + // - Extreme compression (87.5% smaller than FP32) + // - Binary/ternary classification + // - Attention masks + // - Activation sparsity indicators + + // Memory: 4 bits = 0.5 bytes (8x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 4.0 / 32.0; // 0.125 + + // ======================================================================================================= + // TDD-Inside-Spec: Tests and Invariants for GF4 + // ======================================================================================================= + + test gf4_decode_zero + given gf = GF4{ raw = 0b0000 } + when value = decode(gf) + then value == 0.0 + + test gf4_decode_positive_max + given gf = GF4{ raw = 0b0111 } + when value = decode(gf) + then value == 1.5 + + test gf4_decode_negative + given gf = GF4{ raw = 0b1001 } + when value = decode(gf) + then value < 0.0 + + test gf4_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf4_encode_0_25 + given original = 0.25 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.25) < 0.01 + + test gf4_encode_0_5 + given original = 0.5 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.5) < 0.01 + + test gf4_encode_0_75 + given original = 0.75 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.75) < 0.01 + + test gf4_encode_1_0 + given original = 1.0 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 1.0) < 0.01 + + test gf4_encode_1_5 + given original = 1.5 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 1.5) < 0.01 + + test gf4_encode_negative_values + given original = -0.5 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded < 0.0 and abs(decoded - (-0.5)) < 0.01 + + test gf4_encode_clamps_to_max + given original = 10.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded <= 1.5 + + test gf4_encode_quantization_small + given original = 0.3 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.25) < 0.01 + + test gf4_max_value_is_1_5 + given max_val = max_value() + then max_val == 1.5 + + test gf4_min_positive_is_0_25 + given min_pos = min_positive() + then min_pos == 0.25 + + test gf4_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf4_exp_mant_ratio_matches_phi_split + given ratio = (EXP_BITS as f64) / (MANT_BITS as f64) + and expected = 0.5 + then abs(ratio - expected) < 0.01 + + test gf4_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then ratio == 0.125 + + test gf4_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf4_bits_constant + assert BITS == 4 + + invariant gf4_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf4_exp_bits_is_one + assert EXP_BITS == 1 + + invariant gf4_mant_bits_is_two + assert MANT_BITS == 2 + + invariant gf4_max_value_positive + assert max_value() > 0.0 + + invariant gf4_min_positive_greater_than_zero + assert min_positive() > 0.0 + + invariant gf4_epsilon_positive + assert epsilon() > 0.0 + + invariant gf4_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf4_phi_distance_within_tolerance + assert PHI_DISTANCE < 0.12 + + invariant gf4_encode_decode_roundtrip + given encoded = encode(x) for x in {0.25, 0.5, 0.75, 1.0, 1.5} + when decoded = decode(encoded) + then abs(decoded - x) < 0.01 + + invariant gf4_encode_zero_returns_zero + assert encode(0.0).raw == 0b0000 + + invariant gf4_encode_positive_no_sign_bit + given result = encode(1.0) + when has_sign = (result.raw & 0b1000) != 0 + then has_sign == false + + invariant gf4_encode_negative_has_sign_bit + given result = encode(-1.0) + when has_sign = (result.raw & 0b1000) != 0 + then has_sign == true + + bench gf4_encode_latency + measure: nanoseconds to encode(1.0) + target: < 100ns + + bench gf4_decode_latency + measure: nanoseconds to decode(GF4{raw = 0b0101}) + target: < 50ns +} diff --git a/apps/website/public/t27/specs/numeric/gf8.t27 b/apps/website/public/t27/specs/numeric/gf8.t27 new file mode 100644 index 0000000000..17f9250901 --- /dev/null +++ b/apps/website/public/t27/specs/numeric/gf8.t27 @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf8.t27 +// GoldenFloat8 -- 8-bit phi-structured floating point +// NUMERIC-STANDARD-001 -- Agent 3 (P1) + +module GF8 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // ================================================================= + // 1. Format Definition + // ========================================================================= + + // GF8 bit layout: [S|EEE|MMMM] + // S: 1 bit (sign) + // E: 3 bits (exponent) + // M: 4 bits (mantissa) + + const BITS : u8 = 8; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 3; + const MANT_BITS : u8 = 4; + + // Bias for exponent (2^(3-1) - 1 = 3) + const EXP_BIAS : u8 = 3; + + // phi-ratio: exp/mant = 3/4 = 0.75 + // phi-distance: math::constants::PHI_DISTANCE + const PHI_DISTANCE : f64 = 0.132; + + // ================================================================= + // 2. GoldenFloat8 Type + // ========================================================================= + + struct GF8 { + raw : u8, // 8-bit raw value + } + + // ================================================================= + // 3. Encoding/Decoding + // ========================================================================= + + // Encode f32 to GF8 + fn encode(value: f32) -> GF8 { + if (value == 0.0) { + return GF8{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i8; + const exp_biased = (exp_unbiased + EXP_BIAS as i8) as u8; + + // Clamp exponent + const exp_clamped = clamp(exp_biased, 0, (1 << EXP_BITS) - 1); + + // Extract mantissa (4 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF8{ + raw = (sign << 7) | (exp_clamped << MANT_BITS) | mant + }; + } + + // Test: gf8_phi_distance invariant + test gf8_phi_distance + given phi = 1.618033988749894848 // math::sacred_physics::PHI + when gf8_phi = GF8::encode(phi) + then gf8_phi.raw == 0b01000000 // |S|EEE (phi = math::constants::PHI_DISTANCE) + + // Decode GF8 to f32 + fn decode(gf: GF8) -> f32 { + const sign = (gf.raw >> 7) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x07) as u8; + const mant = (gf.raw & 0x0F) as u8; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent (with special case for subnormals) + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS as i8 + 1 + } else { + (exp_biased as i8) - EXP_BIAS as i8 + }; + + // Mantissa (with implicit 1 for normalized, 0 for subnormal) + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 16.0 + } else { + 1.0 + (mant as f32) / 16.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // ================================================================= + // 4. Format Properties + // ========================================================================= + + fn max_value() -> f32 { + // Max normalized: mant=1.9375, exp=3 -> 15.5 + const mant_max = 1.0 + 15.0 / 16.0; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + // Min subnormal: mant=1/16, exp=-2 -> 0.0625 + const mant_min = 1.0 / 16.0; + const exp_min = -EXP_BIAS as i8 + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + // Smallest representable difference at 1.0 + return 1.0 / 16.0; // 0.0625 + } + + // ================================================================= + // 5. Validation + // ========================================================================= + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF8"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // ================================================================= + // 6. Use Cases + // ========================================================================= + + // GF8 is optimal for: + // - High compression (75% smaller than FP32) + // - Weight quantization for lightweight models + // - Activation caching + // - Intermediate feature maps + + // Memory: 8 bits = 1 byte (4x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 8.0 / 32.0; // 0.25 + + // ================================================================= + // 7. Helper Functions + // ========================================================================= + + fn floor_log2(x: f32) -> i8 { + if (x <= 0.0) { return -128; } + let exp : i8 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i8, mant_bits: u8) -> u8 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u8; + } + + fn clamp(x: u8, min: u8, max: u8) -> u8 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF8 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs to maintain accuracy + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // ======================================================================================================= + // TDD-Inside-Spec: Tests and Invariants for GF8 + // ======================================================================================================= + + test gf8_decode_zero + given gf = GF8{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf8_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf8_decode_positive_value + given gf = GF8{ raw = 0b01000000 } + when value = decode(gf) + then value > 0.0 + + test gf8_decode_negative_value + given gf = GF8{ raw = 0b10000000 } + when value = decode(gf) + then value < 0.0 + + test gf8_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf8_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf8_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf8_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf8_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then ratio == 0.25 + + test gf8_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf8_bits_constant + assert BITS == 8 + + invariant gf8_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf8_exp_bits_is_three + assert EXP_BITS == 3 + + invariant gf8_mant_bits_is_four + assert MANT_BITS == 4 + + invariant gf8_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf8_phi_distance_within_tolerance + assert PHI_DISTANCE < 0.14 + + invariant gf8_exp_bias_positive + assert EXP_BIAS > 0 + + test gf8_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf8_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf8_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf8_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf8_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf8_pow_phi_squared + given phi = 1.6180339887498948 as f32 + and result = pow(phi, 2.0) + and expected = phi * phi + then abs(result - expected) < 1e-5 + + test gf8_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf8_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf8_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf8_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf8_ln_approx_of_e_squared + given e = 2.718281828459045 as f32 + and result = ln_approx(e * e) + then abs(result - 2.0) < 0.02 + + test gf8_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf8_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf8_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf8_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf8_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf8_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf8_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf8_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf8_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf8_pow_multiply_exponents + given a = 2.0 + and b = 3.0 + assert abs(pow(pow(a, 2.0), b) - pow(a, 2.0 * b)) < 1e-5 + + invariant gf8_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf8_exp_ln_inversion + given x = 1.5 + and y = exp_approx(x) + then abs(ln_approx(y) - x) < 0.01 + + invariant gf8_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf8_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf8_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf8_pow_fractional_exponent + measure: nanoseconds to compute pow(4.0, 0.5) + target: < 1000ns + + bench gf8_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf8_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf8_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf8_encode_latency + measure: nanoseconds to encode(1.0) + target: < 100ns + + bench gf8_decode_latency + measure: nanoseconds to decode(GF8{raw = 64}) + target: < 50ns + + // Bench: GF8 weight quantization (NN weights from N(0, 0.1)) + bench gf8_weight_quantize + measure: nanoseconds to encode(0.1) and decode(GF8{raw = encode(0.1).raw}) + target: < 200ns + + // Invariant: GF8 phi_distance + invariant gf8_phi_distance + assert PHI_DISTANCE == 0.132 within 0.001 + // Rationale: exp/mant = 3/4 = 0.75, phi_distance = |0.75 - 0.618| = 0.132 +} diff --git a/apps/website/public/t27/specs/numeric/gf_competitive.t27 b/apps/website/public/t27/specs/numeric/gf_competitive.t27 new file mode 100644 index 0000000000..b427c0c719 --- /dev/null +++ b/apps/website/public/t27/specs/numeric/gf_competitive.t27 @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf_competitive.t27 +// GF Competitive Analysis Specification +// Ring 028 — Proving GoldenFloat is not random +// 01 + 1/23 = 3 | TRINITY + +module GFCompetitive { + use base::types; + + const PHI : f64 = 1.6180339887498948482; + const TRINITY : f64 = 3.0; + const TOLERANCE_1E4 : f64 = 1e-4; + const TOLERANCE_1E3 : f64 = 5.0e-3; + + // gf16_phi_distance: Compute |GF16(phi) - phi| / phi + fn gf16_phi_relative_error(encoded: f64) f64 { + if (PHI == 0.0) { return 0.0; } + var diff : f64 = encoded - PHI; + if (diff < 0.0) { diff = -diff; } + return diff / PHI; + } + + // phi_identity_check: Verify phi^2 = phi + 1 in encoded format + fn phi_identity_check(phi_sq: f64, phi_plus_1: f64) f64 { + var diff : f64 = phi_sq - phi_plus_1; + if (diff < 0.0) { diff = -diff; } + return diff; + } + + // trinity_identity_check: Verify phi^2 + phi^-2 = 3 + fn trinity_identity_check(computed: f64) f64 { + var diff : f64 = computed - TRINITY; + if (diff < 0.0) { diff = -diff; } + return diff; + } + + // gf16_encode_decode_roundtrip: Test roundtrip precision + fn roundtrip_error(original: f64, roundtripped: f64) f64 { + if (original == 0.0) { return 0.0; } + var diff : f64 = roundtripped - original; + if (diff < 0.0) { diff = -diff; } + return diff / original; + } + + // accumulation_stability: Sum N uniform terms, measure relative error + fn accumulation_check(n: usize, expected_sum: f64, actual_sum: f64) f64 { + if (expected_sum == 0.0) { return 0.0; } + var diff : f64 = actual_sum - expected_sum; + if (diff < 0.0) { diff = -diff; } + return diff / expected_sum; + } + + // test: GF32 phi representation error < 5e-4 + test gf32_phi_representation { + var encoded_phi : f64 = 1.618033988749894; + var err = gf16_phi_relative_error(encoded_phi); + try err < TOLERANCE_1E3; + } + + // test: phi identity in GF16 + test phi_identity_gf16 { + var phi_sq : f64 = 2.618015; + var phi_p1 : f64 = 2.618042; + var err = phi_identity_check(phi_sq, phi_p1); + try err < TOLERANCE_1E3; + } + + // test: trinity identity + test trinity_identity { + var computed : f64 = 2.999954; + var err = trinity_identity_check(computed); + try err < TOLERANCE_1E4; + } + + // test: roundtrip precision + test roundtrip_precision { + var original : f64 = 1.618034; + var roundtripped : f64 = 1.618042; + var err = roundtrip_error(original, roundtripped); + try err < TOLERANCE_1E3; + } + + // test: accumulation stability + test accumulation { + var expected : f64 = 1000.0; + var actual : f64 = 999.95; + var err = accumulation_check(1000, expected, actual); + try err < TOLERANCE_1E4; + } + + // invariant: gf16_phi_distance_is_measurable + invariant gf16_phi_measurable { + var phi_approx : f64 = 1.618034; + gf16_phi_relative_error(phi_approx) > 0.0; + } + + // invariant: phi_split bounds + invariant phi_split_bounds { + PHI > 1.618 and PHI < 1.619; + } + + // bench: encode_decode_latency + bench gf16_encode_decode { + var x : f64 = 1.618033988749894; + var y = roundtrip_error(x, x); + } +} diff --git a/apps/website/public/t27/specs/numeric/goldenfloat_family.t27 b/apps/website/public/t27/specs/numeric/goldenfloat_family.t27 new file mode 100644 index 0000000000..59ee875da6 --- /dev/null +++ b/apps/website/public/t27/specs/numeric/goldenfloat_family.t27 @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/goldenfloat_family.t27 +// GoldenFloat Family -- phi-structured floating point formats +// NUMERIC-STANDARD-001 -- Agent 1 (P0) + +module GoldenFloatFamily { + // Import sacred constants for phi-structured design + use math::constants; + use math::sacred_physics; + + // ================================================================= + // 1. GoldenFloatFormat -- Canonical format descriptor + // ========================================================================= + + struct GoldenFloatFormat { + name : string, // "GF4", "GF8", ..., "GF32" + bits : u8, // Total bits: 4, 8, 12, 16, 20, 24, 32 + sign_bits : u8, // Always 1 + exp_bits : u8, // Exponent bits + mant_bits : u8, // Mantissa bits + exp_mant_ratio : f64, // exp / mantissa ratio + phi_distance : f64, // |exp/mant - 1/phi| (lower = better) + is_primary : bool, // true only for GF16 + } + + // ================================================================= + // 2. GOLDEN_FLOAT_FAMILY -- The canonical format registry + // ========================================================================= + + // phi-ratio target: 1/phi ~= 0.618 + // exp/mant ratios closer to 0.618 are more "golden" + const PHI_RATIO_TARGET : f64 = sacred_physics::PHI_INV; + + // Format array: ordered by bits (4 -> 32) + const GOLDEN_FLOAT_FAMILY : [7]GoldenFloatFormat = [ + // name, bits, S, E, M, ratio, phi_dist, primary + GoldenFloatFormat{ + name = "GF4", + bits = 4, + sign_bits = 1, + exp_bits = 1, + mant_bits = 2, + exp_mant_ratio = 0.5, + phi_distance = abs(0.5 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF8", + bits = 8, + sign_bits = 1, + exp_bits = 3, + mant_bits = 4, + exp_mant_ratio = 0.75, + phi_distance = abs(0.75 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF12", + bits = 12, + sign_bits = 1, + exp_bits = 4, + mant_bits = 7, + exp_mant_ratio = 0.5714285714285714, + phi_distance = abs(0.5714285714285714 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF16", + bits = 16, + sign_bits = 1, + exp_bits = 6, + mant_bits = 9, + exp_mant_ratio = 0.6666666666666667, + phi_distance = abs(0.6666666666666667 - PHI_RATIO_TARGET), + is_primary = true, // PRIMARY FORMAT + }, + GoldenFloatFormat{ + name = "GF20", + bits = 20, + sign_bits = 1, + exp_bits = 7, + mant_bits = 12, + exp_mant_ratio = 0.5833333333333333, + phi_distance = abs(0.5833333333333333 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF24", + bits = 24, + sign_bits = 1, + exp_bits = 9, + mant_bits = 14, + exp_mant_ratio = 0.6428571428571429, + phi_distance = abs(0.6428571428571429 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF32", + bits = 32, + sign_bits = 1, + exp_bits = 12, + mant_bits = 19, + exp_mant_ratio = 0.631578947368421, + phi_distance = abs(0.631578947368421 - PHI_RATIO_TARGET), + is_primary = false, + }, + ]; + + // ================================================================= + // 3. Query functions + // ========================================================================= + + fn get_format_by_name(name: string) -> Option { + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + if (fmt.name == name) { + return fmt; + } + } + return null; + } + + fn get_format_by_bits(bits: u8) -> Option { + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + if (fmt.bits == bits) { + return fmt; + } + } + return null; + } + + fn get_primary_format() -> GoldenFloatFormat { + return GOLDEN_FLOAT_FAMILY[3]; // GF16 at index 3 + } + + // ================================================================= + // 4. Verification functions + // ========================================================================= + + struct VerificationReport { + all_valid : bool, + primary_is_gf16 : bool, + phi_distances_ok : bool, + best_phi_format : string, + best_phi_distance : f64, + avg_phi_distance : f64, + } + + fn verify_golden_family() -> VerificationReport { + var primary_count : u8 = 0; + var best_dist : f64 = 1.0; + var best_name : string = ""; + var total_dist : f64 = 0.0; + var format_count : u8 = 0; + var all_names_unique : bool = true; + var all_bit_sums_valid : bool = true; + var all_phi_distances_non_negative : bool = true; + + // Check for duplicate names + var names_seen : [7]string = ["", "", "", "", "", "", ""]; + + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + format_count = format_count + 1; + + // Count primary formats (should be exactly 1) + if (fmt.is_primary) { + primary_count = primary_count + 1; + } + + // Track best phi distance + if (fmt.phi_distance < best_dist) { + best_dist = fmt.phi_distance; + best_name = fmt.name; + } + + total_dist = total_dist + fmt.phi_distance; + + // Check for duplicate names + for (const names_seen) |name| { + if (name != "" && name == fmt.name) { + all_names_unique = false; + } + } + names_seen[format_count - 1] = fmt.name; + + // Check that exp_bits + mant_bits + 1 = bits (sign bit) + if (fmt.exp_bits + fmt.mant_bits + 1 != fmt.bits) { + all_bit_sums_valid = false; + } + + // Check phi_distance is non-negative + if (fmt.phi_distance < 0.0) { + all_phi_distances_non_negative = false; + } + } + + const avg_dist = total_dist / 7.0; + + // All checks must pass + const all_checks_valid = + format_count == 7 && + all_names_unique && + all_bit_sums_valid && + all_phi_distances_non_negative && + primary_count == 1; + + return VerificationReport{ + all_valid = all_checks_valid, + primary_is_gf16 = (primary_count == 1) && (GOLDEN_FLOAT_FAMILY[3].is_primary), + phi_distances_ok = best_dist < 0.1, // All within 0.1 of 1/phi + best_phi_format = best_name, + best_phi_distance = best_dist, + avg_phi_distance = avg_dist, + }; + } + + // ================================================================= + // 5. Utility functions + // ========================================================================= + + fn max_value(format: GoldenFloatFormat) -> f64 { + // Max value = (2 - 2^(-M)) * 2^(2^E - 1) + const mant_max = 2.0 - pow(2.0, -(format.mant_bits as f64)); + const exp_max = pow(2.0, format.exp_bits as f64) - 1.0; + return mant_max * pow(2.0, exp_max); + } + + fn min_positive(format: GoldenFloatFormat) -> f64 { + // Min positive = 2^(-M) * 2^(1 - bias) + const mant_min = pow(2.0, -(format.mant_bits as f64)); + const bias = pow(2.0, format.exp_bits as f64 - 1.0) - 1.0; + return mant_min * pow(2.0, 1.0 - bias); + } + + fn memory_efficiency(format: GoldenFloatFormat) -> f64 { + // Memory efficiency vs FP32 (1.0 = same, 0.5 = half size) + return format.bits as f64 / 32.0; + } + + // ======================================================================================================= + // TDD-Inside-Spec: Tests and Invariants for GoldenFloatFamily + // ======================================================================================================= + + test gffamily_get_format_by_name_gf16 + given fmt = get_format_by_name("GF16") + then fmt != null and fmt.?.name == "GF16" and fmt.?.bits == 16 + + test gffamily_get_format_by_bits_8 + given fmt = get_format_by_bits(8) + then fmt != null and fmt.?.name == "GF8" and fmt.?.bits == 8 + + test gffamily_get_primary_format_is_gf16 + given primary = get_primary_format() + then primary.name == "GF16" and primary.is_primary == true + + test gffamily_family_size_7 + given size = GOLDEN_FLOAT_FAMILY.len() + then size == 7 + + test gffamily_phi_ratio_target_is_phi_inverse + given target = PHI_RATIO_TARGET + and phi_inv = sacred_physics::PHI_INV + then abs(target - phi_inv) < 0.000001 + + test gffamily_gf4_has_correct_bit_counts + given fmt = get_format_by_name("GF4").? + then fmt.sign_bits == 1 and fmt.exp_bits == 1 and fmt.mant_bits == 2 + + test gffamily_gf32_has_correct_bit_counts + given fmt = get_format_by_name("GF32").? + then fmt.sign_bits == 1 and fmt.exp_bits == 12 and fmt.mant_bits == 19 + + test gffamily_only_gf16_is_primary + var count = 0 + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + if (fmt.is_primary) { count = count + 1; } + } + then count == 1 + + test gffamily_verify_primary_is_gf16 + given report = verify_golden_family() + then report.primary_is_gf16 == true + + test gffamily_phi_distances_within_tolerance + given report = verify_golden_family() + then report.phi_distances_ok == true + + test gffamily_best_phi_format_is_gf12 + given report = verify_golden_family() + then report.best_phi_format == "GF12" + + test gffamily_memory_efficiency_gf8 + given fmt = get_format_by_name("GF8").? + and eff = memory_efficiency(fmt) + then abs(eff - 0.25) < 0.01 + + test gffamily_memory_efficiency_gf16 + given fmt = get_format_by_name("GF16").? + and eff = memory_efficiency(fmt) + then abs(eff - 0.5) < 0.01 + + test gffamily_max_value_positive + given fmt = get_format_by_name("GF8").? + and max_val = max_value(fmt) + then max_val > 0.0 + + test gffamily_min_positive_greater_than_zero + given fmt = get_format_by_name("GF8").? + and min_pos = min_positive(fmt) + then min_pos > 0.0 + + test gffamily_get_format_by_unknown_name + given fmt = get_format_by_name("GF999") + then fmt == null + + test gffamily_get_format_by_unknown_bits + given fmt = get_format_by_bits(100) + then fmt == null + + test gffamily_verify_all_valid + given report = verify_golden_family() + then report.all_valid == true + + test gffamily_verify_format_count_is_7 + given report = verify_golden_family() + then report.all_valid == true // implies format_count == 7 + + test gffamily_verify_names_unique + given report = verify_golden_family() + then report.all_valid == true // implies names are unique + + test gffamily_verify_bit_sums_valid + given report = verify_golden_family() + then report.all_valid == true // implies bit sums are valid + + test gffamily_verify_phi_distances_non_negative + given report = verify_golden_family() + then report.all_valid == true // implies phi_distances are non-negative + + test gffamily_verify_exactly_one_primary + given report = verify_golden_family() + then report.all_valid == true // implies exactly 1 primary format + + test gffamily_best_phi_distance_is_small + given report = verify_golden_family() + then report.best_phi_distance < 0.05 + + test gffamily_avg_phi_distance_reasonable + given report = verify_golden_family() + and avg = report.avg_phi_distance + then avg > 0.0 and avg < 0.2 + + invariant gffamily_phi_ratio_target_positive + assert PHI_RATIO_TARGET > 0.0 + + invariant gffamily_phi_ratio_target_less_than_one + assert PHI_RATIO_TARGET < 1.0 + + invariant gffamily_family_size_constant + assert GOLDEN_FLOAT_FAMILY.len() == 7 + + invariant gffamily_gf4_at_index_0 + assert GOLDEN_FLOAT_FAMILY[0].name == "GF4" + + invariant gffamily_gf32_at_index_6 + assert GOLDEN_FLOAT_FAMILY[6].name == "GF32" + + invariant gffamily_all_formats_have_sign_bits_1 + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + assert fmt.sign_bits == 1; + } + + invariant gffamily_all_formats_bits_sum_correct + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + assert fmt.sign_bits + fmt.exp_bits + fmt.mant_bits == fmt.bits; + } + + invariant gffamily_primary_is_gf16 + assert GOLDEN_FLOAT_FAMILY[3].is_primary == true + + invariant gffamily_phi_distances_non_negative + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + assert fmt.phi_distance >= 0.0; + } + + invariant gffamily_memory_efficiency_gf4 + assert abs(memory_efficiency(GOLDEN_FLOAT_FAMILY[0]) - 0.125) < 0.01 + + invariant gffamily_memory_efficiency_gf32 + assert abs(memory_efficiency(GOLDEN_FLOAT_FAMILY[6]) - 1.0) < 0.01 + + bench gffamily_get_format_by_name_latency + measure: nanoseconds to get_format_by_name("GF16") + target: < 100ns + + bench gffamily_get_format_by_bits_latency + measure: nanoseconds to get_format_by_bits(16) + target: < 100ns + + bench gffamily_get_primary_format_latency + measure: nanoseconds to get_primary_format() + target: < 50ns + + bench gffamily_verify_golden_family_latency + measure: nanoseconds to verify_golden_family() + target: < 500ns + + bench gffamily_memory_efficiency_latency + measure: nanoseconds to memory_efficiency(GOLDEN_FLOAT_FAMILY[3]) + target: < 100ns +} diff --git a/apps/website/public/t27/specs/numeric/pellis_verify.t27 b/apps/website/public/t27/specs/numeric/pellis_verify.t27 new file mode 100644 index 0000000000..392dc13f08 --- /dev/null +++ b/apps/website/public/t27/specs/numeric/pellis_verify.t27 @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/pellis_verify.t27 +// Pellis Verification Specification +// Phase 2 of GF Competitive Analysis (issue #289) +// GMP-backed high-precision verification of Pellis closed form +// 01 + 1/23 = 3 | TRINITY + +module PellisVerify { + use base::types; + + const CODATA_ALPHA_INV : f64 = 137.035999166; + const PELLIS_PRELIMINARY : f64 = 137.035999164; + const VERIFICATION_TOLERANCE : f64 = 0.001; + + // pellis_closed_form: 360/phi^2 - 2/phi^4 + 1/(3*phi)^5 + fn pellis_closed_form(phi_sq: f64, phi_4: f64, phi_5_3: f64) f64 { + return 360.0 / phi_sq - 2.0 / phi_4 + 1.0 / phi_5_3; + } + + // compare_with_codata: |pellis - alpha^-1| / alpha^-1 + fn compare_with_codata(pellis: f64) f64 { + if (CODATA_ALPHA_INV == 0.0) { return 0.0; } + var diff : f64 = pellis - CODATA_ALPHA_INV; + if (diff < 0.0) { diff = -diff; } + return diff / CODATA_ALPHA_INV; + } + + // test: pellis within 0.1% of CODATA alpha^-1 + test pellis_near_alpha_inv { + var phi_sq : f64 = 2.618033988749895; + var phi_4 : f64 = 6.854101966249685; + var phi_5_3 : f64 = 44.322849; + var pellis = pellis_closed_form(phi_sq, phi_4, phi_5_3); + var rel_err = compare_with_codata(pellis); + try rel_err < VERIFICATION_TOLERANCE; + } + + // test: pellis > 137 (basic sanity) + test pellis_gt_137 { + var phi_sq : f64 = 2.618033988749895; + var phi_4 : f64 = 6.854101966249685; + var phi_5_3 : f64 = 44.322849; + var pellis = pellis_closed_form(phi_sq, phi_4, phi_5_3); + try pellis > 137.0; + } + + // invariant: pellis_preregistered_checkpoint + invariant pellis_preregistered { + PELLIS_PRELIMINARY > 137.035 and PELLIS_PRELIMINARY < 137.036; + } + + // invariant: codata_alpha_inv_positive + invariant codata_positive { + CODATA_ALPHA_INV > 0.0; + } + + // bench: pellis computation + bench pellis_compute { + var phi_sq : f64 = 2.618033988749895; + var phi_4 : f64 = 6.854101966249685; + var phi_5_3 : f64 = 44.322849; + var p = pellis_closed_form(phi_sq, phi_4, phi_5_3); + } +} diff --git a/apps/website/public/t27/specs/numeric/phi_ratio.t27 b/apps/website/public/t27/specs/numeric/phi_ratio.t27 new file mode 100644 index 0000000000..0daa50a017 --- /dev/null +++ b/apps/website/public/t27/specs/numeric/phi_ratio.t27 @@ -0,0 +1,652 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/phi_ratio.t27 +// 0-Ratio Proof 1 Derivation of GoldenFloat exp/mantissa split +// NUMERIC-STANDARD-001 2 Agent 9 (P0) + +module PhiRatio { + // Import sacred constants + use math::constants; + use math::sacred_physics; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Golden Ratio Target for Float Formats + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // The ideal exp/mantissa ratio for floating point formats + // Derived from sacred physics: 1/141 142 0.618 + const PHI_RATIO_TARGET : f64 = sacred_physics::PHI_INV; // 0.618... + + // 143144 = 145 + 1 (golden ratio identity) + // This gives us: 1/146 = 147 - 1 148 0.618 + const PHI_SQ : f64 = sacred_physics::PHI * sacred_physics::PHI; + + // 149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 + // 2. 214-Split Formula 215 Derive optimal exp/mantissa bits + // 216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 + + // For a floating point format with N bits total (including sign): + // bits = sign + exp + mant + // sign = 1 (always) + // available = N - 1 = exp + mant + // + // The 289-principle states: exp/mant = 1/290 + // exp = (available) / (291 + 1) + // mant = available - exp + // + // Since 292 + 1 = 293294, we have: + // exp = (N - 1) / 295296 + // mant = N - 1 - exp + + struct PhiSplitResult { + exp_bits : u8, + mant_bits : u8, + ratio : f64, + phi_dist : f64, + } + + fn phi_split(bits: u8) -> PhiSplitResult { + const available = bits - 1; // Exclude sign bit + const phi_sq = sacred_physics::PHI * sacred_physics::PHI; + + // exp = round((N-1) / 297298) + const exp_raw = (available as f64) / phi_sq; + const exp_bits = round(exp_raw) as u8; + + // mant = N - 1 - exp + const mant_bits = available - exp_bits; + + const ratio = (exp_bits as f64) / (mant_bits as f64); + const phi_dist = abs(ratio - PHI_RATIO_TARGET); + + return PhiSplitResult{ + exp_bits = exp_bits, + mant_bits = mant_bits, + ratio = ratio, + phi_dist = phi_dist, + }; + } + + // 299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 + // 3. Verify GoldenFloat Family against 364-Split + // 365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 + + struct FormatComparison { + name : string, + bits : u8, + actual_exp : u8, + actual_mant : u8, + phi_split_exp : u8, + phi_split_mant : u8, + matches_phi_split : bool, + tradeoff_note : string, + } + + fn verify_phi_split() -> [7]FormatComparison { + return [ + // GF4: 438-split gives exp=1, mant=2 439 MATCH + FormatComparison{ + name = "GF4", + bits = 4, + actual_exp = 1, + actual_mant = 2, + phi_split_exp = 1, + phi_split_mant = 2, + matches_phi_split = true, + tradeoff_note = "Perfect 440-split match", + }, + // GF8: 441-split gives exp=2, mant=5 442 actual is 3/4 + FormatComparison{ + name = "GF8", + bits = 8, + actual_exp = 3, + actual_mant = 4, + phi_split_exp = 3, + phi_split_mant = 4, + matches_phi_split = true, + tradeoff_note = "Exact match: round(7/φ²)=3", + }, + // GF12: 443-split gives exp=3, mant=8 444 actual is 4/7 + FormatComparison{ + name = "GF12", + bits = 12, + actual_exp = 4, + actual_mant = 7, + phi_split_exp = 4, + phi_split_mant = 7, + matches_phi_split = true, + tradeoff_note = "Exact match: round(11/φ²)=4", + }, + // GF16: 445-split gives exp=4, mant=11 446 actual is 6/9 + FormatComparison{ + name = "GF16", + bits = 16, + actual_exp = 6, + actual_mant = 9, + phi_split_exp = 6, + phi_split_mant = 9, + matches_phi_split = true, + tradeoff_note = "PRIMARY FORMAT: exact match: round(15/φ²)=6", + }, + // GF20: 447-split gives exp=5, mant=14 448 actual is 7/12 + FormatComparison{ + name = "GF20", + bits = 20, + actual_exp = 7, + actual_mant = 12, + phi_split_exp = 7, + phi_split_mant = 12, + matches_phi_split = true, + tradeoff_note = "Exact match: round(19/φ²)=7", + }, + // GF24: 449-split gives exp=6, mant=17 450 actual is 9/14 + FormatComparison{ + name = "GF24", + bits = 24, + actual_exp = 9, + actual_mant = 14, + phi_split_exp = 6, + phi_split_mant = 17, + matches_phi_split = false, + tradeoff_note = "Closer to 451-split than GF16", + }, + // GF32: 452-split gives exp=8, mant=23 453 actual is 12/19 + FormatComparison{ + name = "GF32", + bits = 32, + actual_exp = 12, + actual_mant = 19, + phi_split_exp = 8, + phi_split_mant = 23, + matches_phi_split = false, + tradeoff_note = "Near 454-split with good precision", + }, + ]; + } + + // 455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 + // 4. Theoretical Proofs + // 520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592 + + // Proof that 593-split minimizes information loss + // for a given bit budget under scale-invariant assumptions. + + fn golden_self_similarity_proof() -> string { + // The golden ratio φ is defined by identity: φ² = φ + 1 + // Dividing both sides by φ² gives: 1 = 1/φ + 1/φ² + // + // Self-similarity constraint for bit allocation: + // The ratio e/m should equal ratio m/(e+m) + // This means: e/m = 1/(e/m + 1) + // + // Let r = e/m. Then: r = 1/(r + 1) + // Solving: r² + r - 1 = 0 + // r = (√5 - 1)/2 = 1/φ ≈ 0.618 + // + // This is NOT an optimization problem (maximizing e×m gives r=1 by AM-GM). + // It is a self-similarity constraint — a defining property of φ. + return "φ is unique self-similar proportion: e/m = m/(e+m) → r = 1/φ"; + } + + // Theorem 2: Optimal Rounding + // The function round((N-1)/φ²) gives integer closest to φ-proportion. + + fn optimal_rounding_proof() -> string { + // For integer bit allocation, we must choose between floor and ceil. + // The φ-proportion gives exp_ideal = (N-1)/φ² (real number). + // + // Taking derivative and setting to zero: + // d/dr [r * (N/(1+r))^2] = 0 + // r = 1/(1+r) 594 r^2 + r - 1 = 0 + // r = (sqrt(5) - 1) / 2 = 1/595 + // + // Therefore: exp/mant = 1/596 is optimal + return "exp/mant = 1/597 maximizes (dynamic_range * precision) for fixed bit budget"; + } + + // 598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662 + // 5. Connection to Sacred Physics + // 663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735 + + // The 736-ratio appears throughout sacred physics: + // - Consciousness threshold C = 737738739 + // - Specious present t = 740741742 seconds + // - Neural gamma band f_743 = 744745 * 746 / 747 + // + // GoldenFloat formats inherit this sacred proportion. + + fn sacred_connection() -> string { + return "GoldenFloat exp/mant = 1/748 = consciousness threshold = sacred_physics::C_THRESHOLD"; + } + + // 749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813 + // 6. Utility functions + // 814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886 + + fn compute_phi_distance(exp_bits: u8, mant_bits: u8) -> f64 { + const ratio = (exp_bits as f64) / (mant_bits as f64); + return abs(ratio - PHI_RATIO_TARGET); + } + + fn is_phi_optimal(exp_bits: u8, mant_bits: u8, tolerance: f64) -> bool { + return compute_phi_distance(exp_bits, mant_bits) < tolerance; + } + + fn recommend_format(total_bits: u8) -> PhiSplitResult { + return phi_split(total_bits); + } + + // 887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951 + // 7. Round function (stub) + // 9529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024 + + fn round(x: f64) -> f64 { + // Round to nearest integer (round half away from zero) + if (x < 0.0) { + let xi = x as i64; + let frac = x - (xi as f64); + if (frac <= -0.5) { + return (xi - 1) as f64; + } + return xi as f64; + } + let xi = x as i64; + let frac = x - (xi as f64); + if (frac >= 0.5) { + return (xi + 1) as f64; + } + return xi as f64; + } + + fn abs(x: f64) -> f64 { + if (x < 0.0) { + return -x; + } + return x; + } + + fn pow(base: f64, exp: f64) -> f64 { + // Power function with binary exponentiation for integer exponents + // and logarithm approximation for fractional exponents + if (base <= 0.0) { + if (exp == 0.0) { + return 1.0; // 0^0 defined as 1 in this context + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + if (base < 0.0 && exp == floor(exp)) { + // Negative base with integer exponent: handle via absolute value + let exp_int = exp as i64; + let result = pow(-base, exp); + if (exp_int % 2 == 0) { + return result; + } + return -result; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Handle n = 0 + if (exp == 0.0) { + return 1.0; + } + + // Check if exponent is an integer + let is_integer = exp == floor(exp); + + if is_integer { + // Integer exponent: use binary exponentiation + let exp_int = exp as i64; + let mut result = 1.0; + let mut base_acc = base; + let mut e = exp_int; + + if e < 0 { + e = -e; + base_acc = 1.0 / base_acc; + } + + while e > 0 { + if e % 2 == 1 { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + let ln_x = ln_approx(base); + let result = exp_approx(exp * ln_x); + + return result; + } + + // Natural logarithm approximation + fn ln_approx(x: f64) -> f64 { + if x <= 0.0 { + return 0.0 / 0.0; // NaN for non-positive + } + if x == 1.0 { + return 0.0; + } + + // Use series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + let t = (x - 1.0) / (x + 1.0); + let t2 = t * t; + let t3 = t2 * t; + let t5 = t3 * t2; + let t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f64) -> f64 { + if x == 0.0 { + return 1.0; + } + + // Use Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let mut result = 1.0; + let mut term = 1.0; + let mut n = 1; + + // For better range, use x/2^k approach + let mut exp_x = x; + if x > 10.0 { + let k = floor(x / 10.0) as i64; + exp_x = x - (k as f64) * 10.0; + } else if x < -10.0 { + let k = floor(-x / 10.0) as i64; + exp_x = x + (k as f64) * 10.0; + } + + // Taylor series (10 terms) + for i in 1..=10 { + term = term * exp_x / (i as f64); + result = result + term; + } + + return result; + } + + // Floor function + fn floor(x: f64) -> f64 { + let xi = x as i64; + if x >= 0.0 || x == xi as f64 { + return xi as f64; + } + return (xi - 1) as f64; + } + + // 1025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127 + // TDD-Inside-Spec: Tests and Invariants for 1128-Ratio + // 1129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231 + + test phi_split_for_gf4_perfect_match + given bits = 4 + when result = phi_split(bits) + then result.exp_bits == 1 and result.mant_bits == 2 and result.phi_dist < 0.01 + + test phi_split_for_gf16_primary_format + given bits = 16 + when result = phi_split(bits) + then result.exp_bits == 6 and result.mant_bits == 9 and result.phi_dist < 0.05 + + test phi_split_for_gf32_near_optimal + given bits = 32 + when result = phi_split(bits) + then result.exp_bits == 12 and result.mant_bits == 19 and result.phi_dist < 0.02 + + test phi_split_sum_constraint + given bits = 16 + when result = phi_split(bits) + then result.exp_bits + result.mant_bits == bits - 1 + + test phi_ratio_target_equals_phi_inverse + given target = PHI_RATIO_TARGET + when inverse = sacred_physics::PHI_INV + then abs(target - inverse) < 1e-15 + + test phi_split_ratio_approximates_phi_inverse + given bits = 16 + when result = phi_split(bits) + and ratio = result.exp_bits as f64 / result.mant_bits as f64 + then abs(ratio - PHI_RATIO_TARGET) < 0.05 + + test phi_optimality_proof_derivative + given proof = phi_optimality_proof() + when contains_optimal = proof.contains("exp/mant = 1/1232") + then contains_optimal == true + + test compute_phi_distance_for_gf16 + given exp = 6 + and mant = 9 + when distance = compute_phi_distance(exp, mant) + then distance > 0.1 // GF16 intentionally deviates for ML range + + test is_phi_optimal_tolerance_check + given exp = 4 + and mant = 11 + and tolerance = 0.05 + when optimal = is_phi_optimal(exp, mant, tolerance) + then optimal == true + + test verify_phi_split_all_formats_compared + given comparisons = verify_phi_split() + when gf4_matches = comparisons[0].matches_phi_split + and gf16_primary = comparisons[3].tradeoff_note.contains("PRIMARY") + then gf4_matches == true and gf16_primary == true + + test sacred_connection_phi_ratio_equals_threshold + given connection = sacred_connection() + when has_threshold = connection.contains("C_THRESHOLD") + and has_phi_inverse = connection.contains("PHI_INV") + then has_threshold == true and has_phi_inverse == true + + test phi_ratio_round_positive + given result = round(3.7) + then result == 4.0 + + test phi_ratio_round_negative + given result = round(-3.7) + then result == -4.0 + + test phi_ratio_round_half_up + given result = round(3.5) + then result == 4.0 + + test phi_ratio_round_half_down + given result = round(-3.5) + then result == -4.0 + + test phi_ratio_round_integer + given result = round(5.0) + then result == 5.0 + + test phi_ratio_round_zero + given result = round(0.0) + then result == 0.0 + + test phi_ratio_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-15 + + test phi_ratio_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-15 + + test phi_ratio_pow_positive_integer_exponent + given result = pow(2.0, 10.0) + and expected = 1024.0 + then abs(result - expected) < 1e-10 + + test phi_ratio_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-10 + + test phi_ratio_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-6 + + test phi_ratio_pow_phi_squared + given result = pow(PHI, 2.0) + and expected = PHI * PHI + then abs(result - expected) < 1e-10 + + test phi_ratio_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test phi_ratio_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-15 and abs(result2 - 1.0) < 1e-15 + + test phi_ratio_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-15 + + test phi_ratio_ln_approx_of_e + given e = 2.718281828459045 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test phi_ratio_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test phi_ratio_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-15 + + test phi_ratio_exp_approx_one + given e = 2.718281828459045 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test phi_ratio_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 + then abs(result - expected) < 0.01 + + test phi_ratio_floor_positive + given result = floor(3.7) + then result == 3.0 + + test phi_ratio_floor_negative + given result = floor(-3.2) + then result == -4.0 + + test phi_ratio_floor_integer + given result = floor(5.0) + then result == 5.0 + + test phi_ratio_floor_zero + given result = floor(0.0) + then result == 0.0 + + invariant phi_round_returns_integer + assert round(x) == i64 for all f64 x + + invariant phi_round_half_away_from_zero + assert round(2.5) == 3.0 and round(-2.5) == -3.0 + + invariant phi_round_symmetric + assert round(-x) == -round(x) for all x >= 0.0 + + invariant phi_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant phi_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant phi_pow_multiply_exponents + given a = 2.0 + and b = 3.0 + assert abs(pow(pow(a, 2.0), b) - pow(a, 6.0)) < 1e-10 + + invariant phi_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant phi_exp_ln_inversion + given x = 1.5 + and y = exp_approx(x) + then abs(ln_approx(y) - x) < 0.01 + + invariant phi_floor_returns_integer + assert floor(x) == i64 for all f64 x + + invariant phi_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + invariant phi_floor_zero_or_less + assert floor(x) <= x for all f64 x + + invariant phi_split_sum_equals_available_bits + assert forall bits: u8, phi_split(bits).exp_bits + phi_split(bits).mant_bits == bits - 1 + + invariant phi_ratio_target_is_phi_inverse + assert PHI_RATIO_TARGET == sacred_physics::PHI_INV + + invariant phi_distance_non_negative + assert forall exp, mant: u8, compute_phi_distance(exp, mant) >= 0.0 + + invariant phi_optimal_proof_valid + assert phi_optimality_proof().contains("1/1233") + + invariant gf4_format_is_phi_optimal + assert phi_split(4).phi_dist < 0.01 + + invariant exp_bits_less_than_total + assert forall bits: u8, phi_split(bits).exp_bits < bits + + invariant mant_bits_less_than_total + assert forall bits: u8, phi_split(bits).mant_bits < bits + + invariant phi_split_round_matches_all_formats + // CRITICAL: Verify that round((N-1)/φ²) matches ALL GF formats exactly + assert phi_split(4).exp_bits == 1 // GF4: round(3/φ²) = round(1.146) = 1 + + invariant phi_split_gf8_matches_round + assert phi_split(8).exp_bits == 3 // GF8: round(7/φ²) = round(2.674) = 3 + + invariant phi_split_gf12_matches_round + assert phi_split(12).exp_bits == 4 // GF12: round(11/φ²) = round(4.202) = 4 + + invariant phi_split_gf16_matches_round + assert phi_split(16).exp_bits == 6 // GF16: round(15/φ²) = round(5.729) = 6 + + invariant phi_split_gf20_matches_round + assert phi_split(20).exp_bits == 7 // GF20: round(19/φ²) = round(7.257) = 7 + + invariant phi_split_gf24_matches_round + assert phi_split(24).exp_bits == 9 // GF24: round(23/φ²) = round(8.785) = 9 + + invariant phi_split_gf32_matches_round + assert phi_split(32).exp_bits == 12 // GF32: round(31/φ²) = round(11.841) = 12 + + invariant phi_distance_bound_by_zero + assert compute_phi_distance(0, 1) == abs(0.0 - PHI_RATIO_TARGET) + + bench phi_split_computation_time + measure: nanoseconds to compute phi_split(32) + target: < 100ns + + bench verify_phi_split_computation_time + measure: nanoseconds to verify all 7 formats + target: < 500ns + + bench compute_phi_distance_throughput + measure: phi_distance computations per second + target: > 1M computations/sec +} diff --git a/apps/website/public/t27/specs/numeric/requant_boundary.t27 b/apps/website/public/t27/specs/numeric/requant_boundary.t27 new file mode 100644 index 0000000000..38338eef36 --- /dev/null +++ b/apps/website/public/t27/specs/numeric/requant_boundary.t27 @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/requant_boundary.t27 +// Activation requantizer: the threshold boundary convention. +// +// Recorded because Wave 669 had to settle a semantic question with no +// specification to appeal to. Two independently written implementations of the +// same rule -- the emitted `activation_requant` RTL and the end-to-end +// testbench reference in sim/tb_data_check.v -- agreed everywhere except at +// acc == +-threshold, and nothing in specs/ said which was right. +module RequantBoundary { + const MUTANT : i32 = -1; + const MUTANT2 : i32 = -2; + const MUTANT3 : i32 = -3; + +// 1. Encoding +// Claim ID: C-requant-000 (PRE_RULE, mirrors the emitted localparams) + + // Balanced trit codes. 0b11 is reserved and must never be produced; + // an unreachability property for it already exists in the RTL. + const TRIT_N : u2 = 0b00; // -1 + const TRIT_Z : u2 = 0b01; // 0 + const TRIT_P : u2 = 0b10; // +1 + const TRIT_RESERVED : u2 = 0b11; // never produced + +// 2. The boundary convention +// Claim ID: C-requant-001 (VERIFIED_SW, [simulated] -- NOT measured on FPGA) +// +// The boundary is INCLUSIVE on both sides. Provenance: PRE_RULE -- this value +// was fixed before this spec existed, stated in the RTL as a documented +// priority chain and asserted by activation_requant's own inline properties. +// Matching it here is therefore a RECORD, not independent evidence. +// +// A PRIORITY CHAIN, not parallel comparisons: a host may program a negative +// threshold, which makes both comparisons true at once. The chain keeps the +// output inside the legal alphabet for every input rather than relying on a +// precondition the host might not honour. That decision predates this spec. + + fn requantize(acc: i16, threshold: i16) -> u2 { + if acc >= threshold { + return TRIT_P; + } + if acc <= -threshold { + return TRIT_N; + } + return TRIT_Z; + } + +// 3. Consequence of the chain under a negative threshold +// Claim ID: C-requant-002 (VERIFIED_SW by reading + inline properties; +// NO VECTOR -- see scope limits below) +// +// Not a claim that a negative threshold is meaningful. A claim that it cannot +// produce TRIT_RESERVED or an ambiguous encoding: the first arm wins. + + fn negative_threshold_yields_p(acc: i16, threshold: i16) -> bool { + if threshold < 0 { + return requantize(acc, threshold) == TRIT_P; + } + return true; + } + +// 4. Verification record +// Claim ID: C-requant-003 (VERIFIED_SW, [simulated]) +// +// 26 configurations, engine output against a reference computed inside the +// testbench, via `python3 formal/value_sweep.py`. All 26 matched. +// +// Seeds 5 and 7 land on acc == -threshold EXACTLY. Before Wave 669 no vector +// in this campaign ever reached the boundary, because every input and weight +// was +1 and the accumulator was therefore always 27*chunks -- never within 24 +// of the threshold. A boundary disagreement is visible only from the boundary. + + const VERIFY_CHECKED : u32 = 26; + const VERIFY_MATCHED : u32 = 26; + const VERIFY_BOUNDARY_REACHED : bool = true; + const VERIFY_ACC_MIN_OBSERVED : i16 = -6; + const VERIFY_ACC_MAX_OBSERVED : i16 = 81; + +// 5. Finding F-requant-001 (RESOLVED) +// +// The testbench reference used strict `>` and `<`. It agreed with the design at +// every other accumulator value in the observed range. The REFERENCE was +// corrected, not the design. +// +// The adjudication is recorded because the direction matters more than the +// outcome: there was no authority above the two implementations. The design's +// convention is stated twice in the artifact -- RTL chain plus inline +// properties -- and the reference agreed with neither. Had the intended +// semantics been exclusive, the same evidence would have condemned the design. +// This module now supplies the authority that was missing, so a future +// disagreement is decidable without re-running the argument. + + const FINDING_SITE : str = "sim/tb_data_check.v ref_trit()"; + const FINDING_RESOLVED : bool = true; + +// 6. Open question Q-requant-001 -- DO NOT GUESS +// +// Is INCLUSIVE the INTENDED semantics, or merely the implemented one? This +// module records what the artifact does and that its two internal statements +// agree. It does NOT establish that a BitNet-style requantizer ought to round +// at the boundary this way rather than the other, and no source in this +// repository settles it. Owner: author. + + const OPEN_QUESTION_DO_NOT_GUESS : bool = true; + +// 7. Scope limits -- what this module does NOT establish +// +// * measured on FPGA -- every result here is [simulated] only +// * the semantic intent behind the boundary (Q-requant-001) +// * thresholds other than 3 -- the sweep holds threshold fixed, because +// varying it exercises the testbench's arithmetic rather than the design +// * negative thresholds -- C-requant-002 is read from the RTL and its inline +// properties and has NO vector in the sweep +// * accumulator values outside [-6, +81], the observed range + + const SUPERIORITY_CLAIMED : bool = false; + const MEASURED_ON_FPGA : bool = false; + +// 8. Tests +// +// These execute the rule stated above. They are a RECORD of the convention in +// section 2, not independent evidence for it: the same author wrote both. What +// they do buy is that a silent change to `requantize` can no longer pass. + + // The three legal codes are distinct, and none of them is the reserved one. + test trit_alphabet_is_distinct + then TRIT_N != TRIT_Z + and TRIT_Z != TRIT_P + and TRIT_N != TRIT_P + and TRIT_N != TRIT_RESERVED + and TRIT_Z != TRIT_RESERVED + and TRIT_P != TRIT_RESERVED + + // C-requant-001. The boundary itself, both sides, at threshold 3 -- the + // exact points where the testbench reference disagreed (F-requant-001). + test boundary_is_inclusive_both_sides + then requantize(3, 3) == TRIT_P + and requantize(-3, 3) == TRIT_N + + // One step inside the boundary is zero on both sides. + test just_inside_boundary_is_zero + then requantize(2, 3) == TRIT_Z + and requantize(-2, 3) == TRIT_Z + and requantize(0, 3) == TRIT_Z + + // One step outside keeps the sign. + test just_outside_boundary_saturates + then requantize(4, 3) == TRIT_P + and requantize(-4, 3) == TRIT_N + + // Symmetry about zero for a positive threshold: -acc maps to the mirror + // code of acc. + test positive_threshold_is_symmetric + then requantize(7, 3) == TRIT_P + and requantize(-7, 3) == TRIT_N + and requantize(1, 3) == TRIT_Z + and requantize(-1, 3) == TRIT_Z + + // The extremes of the range the sweep actually observed, [-6, +81]. + test observed_range_endpoints + then requantize(VERIFY_ACC_MIN_OBSERVED, 3) == TRIT_N + and requantize(VERIFY_ACC_MAX_OBSERVED, 3) == TRIT_P + + // C-requant-002, the part that holds. A negative threshold makes both + // comparisons true only on the OVERLAP [threshold, -threshold]; there the + // chain's first arm wins, exactly as claimed. + test negative_threshold_takes_the_first_arm_on_the_overlap + then requantize(-1, -1) == TRIT_P + and requantize(0, -1) == TRIT_P + and requantize(1, -1) == TRIT_P + and negative_threshold_yields_p(0, -1) == true + + // C-requant-002, the part that holds. Below the negative threshold the + // arms no longer overlap: the first is false, the second is true, and the + // code is N. Still inside the legal alphabet -- never TRIT_RESERVED, which + // is what section 3's prose actually claims. + test negative_threshold_stays_inside_the_alphabet + then requantize(-100, -1) == TRIT_N + and requantize(-100, -1) != TRIT_RESERVED + and requantize(100, -1) == TRIT_P + and requantize(100, -1) != TRIT_RESERVED + + // FINDING F-requant-002 (OPEN). This test FAILS, and it is meant to. + // + // `negative_threshold_yields_p` guards on `threshold < 0` alone and then + // asserts TRIT_P, i.e. it states that ANY negative threshold yields P for + // ANY accumulator. It does not. For acc < threshold < 0 only the second arm + // is true and the code is N: acc = -100, threshold = -1 returns false. + // + // Section 3's prose claim -- "it cannot produce TRIT_RESERVED" -- is sound + // and is covered by the test above. The FUNCTION encodes a stronger claim + // than the prose and that stronger claim is false. The predicate needs the + // overlap condition `acc >= threshold` in its guard, or the claim needs + // restating. Deciding which is the author's call, so the test is left + // failing rather than weakened to match the code. + test negative_threshold_yields_p_holds_universally + then negative_threshold_yields_p(-100, -1) == true + + // The guard: for a non-negative threshold the predicate is vacuously true + // and says nothing about the code produced. + test negative_threshold_predicate_is_vacuous_when_non_negative + then negative_threshold_yields_p(0, 3) == true + and negative_threshold_yields_p(81, 3) == true + + // At threshold 0 the two arms tile the whole range and TRIT_Z is + // unreachable: acc >= 0 takes the first arm, acc < 0 satisfies acc <= -0. + test zero_threshold_leaves_no_dead_zone + then requantize(0, 0) == TRIT_P + and requantize(1, 0) == TRIT_P + and requantize(-1, 0) == TRIT_N + + // Section 4's own record: every configuration checked also matched. + test verification_record_is_complete + then VERIFY_CHECKED == VERIFY_MATCHED + and VERIFY_BOUNDARY_REACHED == true +} diff --git a/apps/website/public/t27/specs/numeric/tf3.t27 b/apps/website/public/t27/specs/numeric/tf3.t27 new file mode 100644 index 0000000000..d51cfcee44 --- /dev/null +++ b/apps/website/public/t27/specs/numeric/tf3.t27 @@ -0,0 +1,1661 @@ +// SPDX-License-Identifier: Apache-2.0 +; tf3.t27 -- TF3 (Ternary Float 3) Format Specification +; 8-bit representation for ternary neural network weights +; Bit layout: [S(1) E(3) M(4)] = [7:7][6:4][3:0] +; phi^2 + 1/phi^2 = 3 | TRINITY + +module triformat-tf3; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const SIGN_SHIFT : u8 = 7; +pub const EXP_SHIFT : u8 = 4; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u8 = 0x80; // 1 << 7 +pub const EXP_MASK : u8 = 0x78; // 0b111 << 4 = 0x78 +pub const MANT_MASK : u8 = 0x0F; // 0b1111 = 0x0F + +pub const EXP_MAX : u8 = 0x07; // 7 (all ones in 3 bits) +pub const EXP_MIN : u8 = 0x00; + +pub const BIAS : i8 = 3; // Exponent bias for TF3 +pub const MANT_BITS : u8 = 4; // 4 bits mantissa + +// TF3 special values +pub const TF3_ZERO_POS : u8 = 0x00; +pub const TF3_ZERO_NEG : u8 = 0x80; +pub const TF3_INF_POS : u8 = 0x78; // exp=7, mant=0 +pub const TF3_INF_NEG : u8 = 0xF8; + +// ============================================================================ +// Types +// ============================================================================ + +pub const TF3 = u8; + +// ============================================================================ +// Mantissa Lookup Table +// ============================================================================ +// TF3 mantissa lookup: (1 + m/2^4) * 2^(e-3) +// m in [0, 15], e in [0, 7], bias = 3 +// Indexed as: table[e * 16 + m] +pub const mant_lookup_table : [128]u16 = [128]u16{ + // e=0: (1+m/16) * 2^(-3) = (1+m/16) / 8 + 0x3C00, 0x3CC0, 0x3D80, 0x3E40, 0x3F00, 0x3FC0, 0x4080, 0x4140, + 0x4200, 0x42C0, 0x4380, 0x4440, 0x4500, 0x45C0, 0x4680, 0x4740, + // e=1: (1+m/16) * 2^(-2) = (1+m/16) / 4 + 0x3C80, 0x3D00, 0x3D80, 0x3E00, 0x3E80, 0x3F00, 0x3F80, 0x4000, + 0x4080, 0x4100, 0x4180, 0x4200, 0x4280, 0x4300, 0x4380, 0x4400, + // e=2: (1+m/16) * 2^(-1) = (1+m/16) / 2 + 0x3D00, 0x3D80, 0x3E00, 0x3E80, 0x3F00, 0x3F80, 0x4000, 0x4080, + 0x4100, 0x4180, 0x4200, 0x4280, 0x4300, 0x4380, 0x4400, 0x4480, + // e=3: (1+m/16) * 2^0 = (1+m/16) + 0x3C80, 0x3D00, 0x3D80, 0x3E00, 0x3E80, 0x3F00, 0x3F80, 0x4000, + 0x4080, 0x4100, 0x4180, 0x4200, 0x4280, 0x4300, 0x4380, 0x4400, + // e=4: (1+m/16) * 2^1 = (1+m/16) * 2 + 0x3D00, 0x3D80, 0x3E00, 0x3E80, 0x3F00, 0x3F80, 0x4000, 0x4080, + 0x4100, 0x4180, 0x4200, 0x4280, 0x4300, 0x4380, 0x4400, 0x4480, + // e=5: (1+m/16) * 2^2 = (1+m/16) * 4 + 0x3D00, 0x3D80, 0x3E00, 0x3E80, 0x3F00, 0x3F80, 0x4000, 0x4080, + 0x4100, 0x4180, 0x4200, 0x4280, 0x4300, 0x4380, 0x4400, 0x4480, + // e=6: (1+m/16) * 2^3 = (1+m/16) * 8 + 0x3D00, 0x3D80, 0x3E00, 0x3E80, 0x3F00, 0x3F80, 0x4000, 0x4080, + 0x4100, 0x4180, 0x4200, 0x4280, 0x4300, 0x4380, 0x4400, 0x4480, + // e=7: (1+m/16) * 2^4 = (1+m/16) * 16 + 0x3D00, 0x3D80, 0x3E00, 0x3E80, 0x3F00, 0x3F80, 0x4000, 0x4080, + 0x4100, 0x4180, 0x4200, 0x4280, 0x4300, 0x4380, 0x4400, 0x4480, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +// tf3_extract_sign(tf3: TF3) -> i8 +// Extract sign bit (bit 7) +// Returns: 0 for positive, 1 for negative +pub fn tf3_extract_sign(tf3: TF3) i8 { + return @as(i8, @intCast((tf3 & SIGN_MASK) >> SIGN_SHIFT)); +} + +// tf3_extract_exponent(tf3: TF3) -> i8 +// Extract exponent bits (bits 6-4) +// Returns: 0-7 +pub fn tf3_extract_exponent(tf3: TF3) i8 { + return @as(i8, @intCast((tf3 & EXP_MASK) >> EXP_SHIFT)); +} + +// tf3_extract_mantissa(tf3: TF3) -> i8 +// Extract mantissa bits (bits 3-0) +// Returns: 0-15 +pub fn tf3_extract_mantissa(tf3: TF3) i8 { + return @as(i8, @intCast(tf3 & MANT_MASK)); +} + +// tf3_from_components(sign: i8, exp: i8, mant: i8) -> TF3 +// Build TF3 from sign, exponent, mantissa components +pub fn tf3_from_components(sign: i8, exp: i8, mant: i8) TF3 { + return (@as(TF3, @intCast(sign)) << SIGN_SHIFT) | + (@as(TF3, @intCast(exp)) << EXP_SHIFT) | + (@as(TF3, @intCast(mant)) << MANT_SHIFT); +} + +// tf3_is_zero(tf3: TF3) -> bool +// Check if TF3 is zero +pub fn tf3_is_zero(tf3: TF3) bool { + return tf3 == TF3_ZERO_POS or tf3 == TF3_ZERO_NEG; +} + +// tf3_is_inf(tf3: TF3) -> bool +// Check if TF3 is infinity (exp == 7, mant == 0) +pub fn tf3_is_inf(tf3: TF3) bool { + const exp = tf3_extract_exponent(tf3); + const mant = tf3_extract_mantissa(tf3); + return exp == EXP_MAX and mant == 0; +} + +// tf3_from_f32(f32: f32) -> TF3 +// Encode IEEE 754 single precision to TF3 +// Round-to-nearest, clamped to [-8, +8] range +pub fn tf3_from_f32(value: f32) TF3 { + // Handle zero + if (value == 0.0) { + return if (value < 0.0) TF3_ZERO_NEG else TF3_ZERO_POS; + } + + // Extract sign + const sign = if (value < 0.0) 1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Clamp to representable range + const clamped = @min(abs_value, 8.0); + + // Find exponent (biased) + var exp: i8 = 0; + var scaled = clamped; + while (scaled >= 1.0 and exp < 7) { + scaled /= 2.0; + exp += 1; + } + + // Calculate mantissa (4 bits) + const mantissa = @as(i8, @intFromFloat(@round((scaled - 0.5) * 16.0))); + const mant = @max(0, @min(15, mantissa)); + + return tf3_from_components(sign, exp + BIAS, mant); +} + +// tf3_to_f32(tf3: TF3) -> f32 +// Decode TF3 to IEEE 754 single precision +pub fn tf3_to_f32(tf3: TF3) f32 { + // Handle special cases + if (tf3_is_zero(tf3)) { + return if (tf3_extract_sign(tf3) != 0) -0.0 else 0.0; + } + if (tf3_is_inf(tf3)) { + const sign = tf3_extract_sign(tf3); + return if (sign != 0) -std.math.inf(f32) else std.math.inf(f32); + } + + // Extract components + const sign = tf3_extract_sign(tf3); + const exp = tf3_extract_exponent(tf3); + const mant = tf3_extract_mantissa(tf3); + + // Calculate value: (-1)^s * (1 + m/16) * 2^(e-3) + const sign_mult = if (sign != 0) -1.0 else 1.0; + const mant_mult = 1.0 + @as(f32, @floatFromInt(mant)) / 16.0; + const exp_mult = @as(f32, @exp2(@as(f32, @floatFromInt(exp - BIAS)))); + + return sign_mult * mant_mult * exp_mult; +} + +// tf3_negate(tf3: TF3) -> TF3 +// Negate a TF3 value by flipping the sign bit +pub fn tf3_negate(tf3: TF3) TF3 { + return tf3 ^ SIGN_MASK; +} + +// tf3_abs(tf3: TF3) -> TF3 +// Absolute value of TF3 (clear sign bit) +pub fn tf3_abs(tf3: TF3) TF3 { + return tf3 & ~SIGN_MASK; +} + +// tf3_is_negative(tf3: TF3) -> bool +// Check if TF3 is negative (sign bit set, excluding negative zero) +pub fn tf3_is_negative(tf3: TF3) bool { + const sign = tf3_extract_sign(tf3); + return (sign != 0) and !tf3_is_zero(tf3); +} + +// tf3_is_positive(tf3: TF3) -> bool +// Check if TF3 is positive (sign bit clear, excluding positive zero) +pub fn tf3_is_positive(tf3: TF3) bool { + const sign = tf3_extract_sign(tf3); + return (sign == 0) and !tf3_is_zero(tf3); +} + +// tf3_copy_sign(tf3: TF3, sign_source: TF3) -> TF3 +// Copy sign from sign_source to tf3 value +pub fn tf3_copy_sign(tf3: TF3, sign_source: TF3) TF3 { + const sign_mask = sign_source & SIGN_MASK; + const value_mask = tf3 & ~SIGN_MASK; + return value_mask | sign_mask; +} + +// tf3_max(a: TF3, b: TF3) -> TF3 +// Return the greater of two TF3 values +pub fn tf3_max(a: TF3, b: TF3) TF3 { + if (tf3_is_inf(a) and !tf3_is_negative(a)) return a; + if (tf3_is_inf(b) and !tf3_is_negative(b)) return b; + + const a_val = tf3_to_f32(a); + const b_val = tf3_to_f32(b); + + if (a_val >= b_val) return a else return b; +} + +// tf3_min(a: TF3, b: TF3) -> TF3 +// Return the smaller of two TF3 values +pub fn tf3_min(a: TF3, b: TF3) TF3 { + if (tf3_is_inf(a) and tf3_is_negative(a)) return a; + if (tf3_is_inf(b) and tf3_is_negative(b)) return b; + + const a_val = tf3_to_f32(a); + const b_val = tf3_to_f32(b); + + if (a_val <= b_val) return a else return b; +} + +// tf3_from_f32_phi(value: f32) -> TF3 +// Encode IEEE 754 to TF3 with phi-optimized rounding +// Uses golden ratio bias (1/phi ~= 0.618) for rounding decisions +pub fn tf3_from_f32_phi(value: f32) TF3 { + // Handle zero + if (value == 0.0) { + return if (value < 0.0) TF3_ZERO_NEG else TF3_ZERO_POS; + } + + // Extract sign + const sign = if (value < 0.0) 1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Clamp to representable range + const clamped = @min(abs_value, 8.0); + + // Find exponent (biased) + var exp: i8 = 0; + var scaled = clamped; + while (scaled >= 1.0 and exp < 7) { + scaled /= 2.0; + exp += 1; + } + + // Phi-optimized rounding: bias = 1/phi ~= 0.618 + const PHI_BIAS: f32 = 0.618; + const mantissa_raw = (scaled - 0.5 + PHI_BIAS / 16.0) * 16.0; + const mantissa = @as(i8, @intFromFloat(@round(mantissa_raw))); + const mant = @max(0, @min(15, mantissa)); + + return tf3_from_components(sign, exp + BIAS, mant); +} + +// tf3_eq(a: TF3, b: TF3) -> bool +// Equality comparison for TF3 +// Treats +0 and -0 as equal +pub fn tf3_eq(a: TF3, b: TF3) bool { + // For zero values, treat +0 and -0 as equal + if (tf3_is_zero(a) and tf3_is_zero(b)) return true; + return a == b; +} + +// tf3_ne(a: TF3, b: TF3) -> bool +// Not-equal comparison for TF3 +pub fn tf3_ne(a: TF3, b: TF3) bool { + return !tf3_eq(a, b); +} + +// tf3_lt(a: TF3, b: TF3) -> bool +// Less-than comparison for TF3 +pub fn tf3_lt(a: TF3, b: TF3) bool { + const a_val = tf3_to_f32(a); + const b_val = tf3_to_f32(b); + return a_val < b_val; +} + +// tf3_le(a: TF3, b: TF3) -> bool +// Less-than-or-equal comparison for TF3 +pub fn tf3_le(a: TF3, b: TF3) bool { + const a_val = tf3_to_f32(a); + const b_val = tf3_to_f32(b); + return a_val <= b_val; +} + +// tf3_gt(a: TF3, b: TF3) -> bool +// Greater-than comparison for TF3 +pub fn tf3_gt(a: TF3, b: TF3) bool { + const a_val = tf3_to_f32(a); + const b_val = tf3_to_f32(b); + return a_val > b_val; +} + +// tf3_ge(a: TF3, b: TF3) -> bool +// Greater-than-or-equal comparison for TF3 +pub fn tf3_ge(a: TF3, b: TF3) bool { + const a_val = tf3_to_f32(a); + const b_val = tf3_to_f32(b); + return a_val >= b_val; +} + +// tf3_add(a: TF3, b: TF3) -> TF3 +// Add two TF3 values (decode, add, re-encode) +// Handles overflow by clamping to Inf +pub fn tf3_add(a: TF3, b: TF3) TF3 { + if (tf3_is_inf(a) and !tf3_is_negative(a)) return a; + if (tf3_is_inf(b) and !tf3_is_negative(b)) return b; + if (tf3_is_inf(a) and tf3_is_negative(a)) return a; + if (tf3_is_inf(b) and tf3_is_negative(b)) return b; + if (tf3_is_inf(a) and tf3_is_inf(b)) { + // Inf + (-Inf) or (-Inf) + Inf = clamp to representable range + return if (tf3_is_negative(a)) TF3_INF_NEG else TF3_INF_POS; + } + + const a_val = tf3_to_f32(a); + const b_val = tf3_to_f32(b); + const result = a_val + b_val; + + return tf3_from_f32(result); +} + +// tf3_sub(a: TF3, b: TF3) -> TF3 +// Subtract two TF3 values (decode, subtract, re-encode) +pub fn tf3_sub(a: TF3, b: TF3) TF3 { + if (tf3_is_inf(a)) return a; + if (tf3_is_inf(b)) { + // x - Inf = -Inf (approximately, clamped) + return if (tf3_is_negative(b)) TF3_INF_POS else TF3_INF_NEG; + } + + const a_val = tf3_to_f32(a); + const b_val = tf3_to_f32(b); + const result = a_val - b_val; + + return tf3_from_f32(result); +} + +// tf3_mul(a: TF3, b: TF3) -> TF3 +// Multiply two TF3 values (decode, multiply, re-encode) +pub fn tf3_mul(a: TF3, b: TF3) TF3 { + if (tf3_is_zero(a) or tf3_is_zero(b)) { + const a_sign = tf3_extract_sign(a); + const b_sign = tf3_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) TF3_ZERO_NEG else TF3_ZERO_POS; + } + if (tf3_is_inf(a) or tf3_is_inf(b)) { + const a_sign = tf3_extract_sign(a); + const b_sign = tf3_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) TF3_INF_NEG else TF3_INF_POS; + } + + const a_val = tf3_to_f32(a); + const b_val = tf3_to_f32(b); + const result = a_val * b_val; + + return tf3_from_f32(result); +} + +// tf3_div(a: TF3, b: TF3) -> TF3 +// Divide two TF3 values (decode, divide, re-encode) +// Returns Inf if divisor is zero +pub fn tf3_div(a: TF3, b: TF3) TF3 { + if (tf3_is_zero(b)) { + const a_sign = tf3_extract_sign(a); + return if (a_sign != 0) TF3_INF_NEG else TF3_INF_POS; + } + if (tf3_is_inf(a)) { + const a_sign = tf3_extract_sign(a); + const b_sign = tf3_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) TF3_INF_NEG else TF3_INF_POS; + } + if (tf3_is_inf(b)) { + const a_sign = tf3_extract_sign(a); + const b_sign = tf3_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) TF3_ZERO_NEG else TF3_ZERO_POS; + } + + const a_val = tf3_to_f32(a); + const b_val = tf3_to_f32(b); + const result = a_val / b_val; + + return tf3_from_f32(result); +} + +// tf3_is_nan(tf3: TF3) -> bool +// Check if TF3 value is NaN +// TF3 uses 0b111 (all ones) for NaN in the 3 exponent bits +pub fn tf3_is_nan(tf3: TF3) bool { + // Extract exponent bits (bits 4-6) + const exp = (tf3 >> 4) & 0x07; + return exp == 0x07; // All exponent bits set = NaN or Inf +} + +// tf3_is_finite(tf3: TF3) -> bool +// Check if TF3 value is finite (not NaN, not infinity) +pub fn tf3_is_finite(tf3: TF3) bool { + return !tf3_is_nan(tf3) and !tf3_is_inf(tf3); +} + +// tf3_signbit(tf3: TF3) -> bool +// Check if the sign bit is set (value is negative or negative zero) +pub fn tf3_signbit(tf3: TF3) bool { + return (tf3 & TF3_SIGN_MASK) != 0; +} + +// tf3_sign(tf3: TF3) -> i8 +// Return the sign of the TF3 value: -1 for negative, 0 for zero, +1 for positive +pub fn tf3_sign(tf3: TF3) i8 { + if (tf3_is_nan(tf3)) { + return 0; + } + + if (tf3_is_zero(tf3)) { + return 0; + } + + if (tf3_signbit(tf3)) { + return -1; + } else { + return 1; + } +} + +// tf3_clamp(x: TF3, min_val: TF3, max_val: TF3) -> TF3 +// Clamp x to the range [min_val, max_val] +pub fn tf3_clamp(x: TF3, min_val: TF3, max_val: TF3) TF3 { + if (tf3_is_nan(x) or tf3_is_nan(min_val) or tf3_is_nan(max_val)) { + return TF3_NAN; + } + + const x_decoded = tf3_to_f32(x); + const min_decoded = tf3_to_f32(min_val); + const max_decoded = tf3_to_f32(max_val); + + if (x_decoded < min_decoded) { + return min_val; + } else if (x_decoded > max_decoded) { + return max_val; + } else { + return x; + } +} + +// tf3_lerp(a: TF3, b: TF3, t: TF3) -> TF3 +// Linear interpolation: a + t * (b - a) +pub fn tf3_lerp(a: TF3, b: TF3, t: TF3) TF3 { + if (tf3_is_nan(a) or tf3_is_nan(b) or tf3_is_nan(t)) { + return TF3_NAN; + } + + const a_val = tf3_to_f32(a); + const b_val = tf3_to_f32(b); + const t_val = tf3_to_f32(t); + + const result = a_val + t_val * (b_val - a_val); + + return tf3_from_f32(result); +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "test_tf3_is_zero_detects_zero" { + // given tf3 = TF3_ZERO_POS + // when result = tf3_is_zero(tf3) + // then result == 1 + try std.testing.expect(tf3_is_zero(TF3_ZERO_POS)); +} + +test "test_tf3_is_zero_detects_negative_zero" { + try std.testing.expect(tf3_is_zero(TF3_ZERO_NEG)); +} + +test "test_tf3_is_zero_rejects_nonzero" { + // given tf3 = 0x01 + // when result = tf3_is_zero(tf3) + // then result == 0 + try std.testing.expect(!tf3_is_zero(0x01)); +} + +test "test_tf3_inf_positive_encoding" { + // given tf3 = TF3_INF_POS + // when exp = (tf3 & EXP_MASK) >> EXP_SHIFT + // and mant = tf3 & MANT_MASK + // then exp == EXP_MAX and mant == 0 + const exp = tf3_extract_exponent(TF3_INF_POS); + const mant = tf3_extract_mantissa(TF3_INF_POS); + try std.testing.expectEqual(@as(i8, EXP_MAX), exp); + try std.testing.expectEqual(@as(i8, 0), mant); +} + +test "test_tf3_inf_negative_encoding" { + // given tf3 = TF3_INF_NEG + // when sign = (tf3 & SIGN_MASK) >> SIGN_SHIFT + // and exp = (tf3 & EXP_MASK) >> EXP_SHIFT + // and mant = tf3 & MANT_MASK + // then sign == 1 and exp == EXP_MAX and mant == 0 + const sign = tf3_extract_sign(TF3_INF_NEG); + const exp = tf3_extract_exponent(TF3_INF_NEG); + const mant = tf3_extract_mantissa(TF3_INF_NEG); + try std.testing.expectEqual(@as(i8, 1), sign); + try std.testing.expectEqual(@as(i8, EXP_MAX), exp); + try std.testing.expectEqual(@as(i8, 0), mant); +} + +test "test_tf3_bits_masks_cover_all" { + // given combined = SIGN_MASK | EXP_MASK | MANT_MASK + // then combined == 0xFF + const combined = SIGN_MASK | EXP_MASK | MANT_MASK; + try std.testing.expectEqual(@as(u8, 0xFF), combined); +} + +test "test_tf3_exp_bias_correct" { + // given bias = BIAS + // then bias == 3 + try std.testing.expectEqual(@as(i8, 3), BIAS); +} + +test "test_tf3_mant_bits_correct" { + // given mant_bits = MANT_BITS + // then mant_bits == 4 + try std.testing.expectEqual(@as(u8, 4), MANT_BITS); +} + +test "test_tf3_lookup_table_size" { + // given expected_entries = 128 + // 8 exponents * 16 mantissa values = 128 entries + // then expected_entries == 128 + try std.testing.expectEqual(@as(usize, 128), mant_lookup_table.len); +} + +test "test_tf3_zero_roundtrip" { + // given original = 0.0 + // and encoded = tf3_from_f32(original) + // and decoded = tf3_to_f32(encoded) + // then abs(decoded - original) < 0.001 + const original = 0.0; + const encoded = tf3_from_f32(original); + const decoded = tf3_to_f32(encoded); + try std.testing.expectApproxEqAbs(@as(f32, 0.0), decoded, 0.001); +} + +test "test_tf3_positive_value_roundtrip" { + const original: f32 = 1.5; + const encoded = tf3_from_f32(original); + const decoded = tf3_to_f32(encoded); + try std.testing.expectApproxEqRel(original, decoded, 0.2); +} + +test "test_tf3_negative_value_roundtrip" { + const original: f32 = -2.5; + const encoded = tf3_from_f32(original); + const decoded = tf3_to_f32(encoded); + try std.testing.expectApproxEqRel(original, decoded, 0.2); +} + +test "test_tf3_extract_sign" { + try std.testing.expectEqual(@as(i8, 0), tf3_extract_sign(0x00)); + try std.testing.expectEqual(@as(i8, 1), tf3_extract_sign(0x80)); + try std.testing.expectEqual(@as(i8, 0), tf3_extract_sign(0x7F)); + try std.testing.expectEqual(@as(i8, 1), tf3_extract_sign(0xFF)); +} + +test "test_tf3_extract_exponent" { + try std.testing.expectEqual(@as(i8, 0), tf3_extract_exponent(0x00)); + try std.testing.expectEqual(@as(i8, 7), tf3_extract_exponent(0x78)); + try std.testing.expectEqual(@as(i8, 3), tf3_extract_exponent(0x30)); +} + +test "test_tf3_extract_mantissa" { + try std.testing.expectEqual(@as(i8, 0), tf3_extract_mantissa(0x00)); + try std.testing.expectEqual(@as(i8, 15), tf3_extract_mantissa(0x0F)); + try std.testing.expectEqual(@as(i8, 7), tf3_extract_mantissa(0x47)); +} + +test "test_tf3_from_components" { + const result = tf3_from_components(1, 7, 0); + try std.testing.expectEqual(@as(TF3, 0xF8), result); +} + +test "test_tf3_clamps_to_max" { + const encoded = tf3_from_f32(100.0); + try std.testing.expectLess(tf3_to_f32(encoded), 10.0); +} + +test "test_tf3_from_f32_positive" { + const encoded = tf3_from_f32(2.0); + try std.testing.expectEqual(@as(i8, 0), tf3_extract_sign(encoded)); + try std.testing.expect(tf3_to_f32(encoded) > 1.5 and tf3_to_f32(encoded) < 2.5); +} + +test "test_tf3_from_f32_negative" { + const encoded = tf3_from_f32(-1.5); + try std.testing.expectEqual(@as(i8, 1), tf3_extract_sign(encoded)); +} + +test "test_tf3_is_inf_positive" { + try std.testing.expect(tf3_is_inf(TF3_INF_POS)); +} + +test "test_tf3_is_inf_negative" { + try std.testing.expect(tf3_is_inf(TF3_INF_NEG)); +} + +test "test_tf3_is_inf_false_for_normal" { + try std.testing.expect(!tf3_is_inf(0x00)); + try std.testing.expect(!tf3_is_inf(0x48)); +} + +test "test_tf3_sign_mask_bit_position" { + try std.testing.expectEqual(@as(u8, 0x80), SIGN_MASK); +} + +test "test_tf3_exp_mask_range" { + try std.testing.expectEqual(@as(u8, 0x78), EXP_MASK); +} + +test "test_tf3_mant_mask_range" { + try std.testing.expectEqual(@as(u8, 0x0F), MANT_MASK); +} + +test "test_tf3_negate_flips_sign" { + const pos_value = tf3_from_f32(1.5); + const neg_value = tf3_negate(pos_value); + const decoded = tf3_to_f32(neg_value); + try std.testing.expect(decoded < -1.0 and decoded > -2.0); +} + +test "test_tf3_negate_double_negate" { + const original = tf3_from_f32(2.0); + const negated = tf3_negate(original); + const double_negated = tf3_negate(negated); + const orig_decoded = tf3_to_f32(original); + const double_decoded = tf3_to_f32(double_negated); + try std.testing.expectApproxEqAbs(orig_decoded, double_decoded, 0.1); +} + +test "test_tf3_negate_zero" { + try std.testing.expectEqual(TF3_ZERO_POS, tf3_negate(TF3_ZERO_POS)); + try std.testing.expectEqual(TF3_ZERO_NEG, tf3_negate(TF3_ZERO_NEG)); +} + +test "test_tf3_abs_clears_sign" { + const neg_value = tf3_from_f32(-1.5); + const abs_value = tf3_abs(neg_value); + const decoded = tf3_to_f32(abs_value); + try std.testing.expect(decoded > 0.5 and decoded < 2.5); +} + +test "test_tf3_abs_positive_unchanged" { + const pos_value = tf3_from_f32(2.0); + const abs_value = tf3_abs(pos_value); + try std.testing.expectEqual(pos_value, abs_value); +} + +test "test_tf3_abs_zero" { + try std.testing.expectEqual(TF3_ZERO_POS, tf3_abs(TF3_ZERO_POS)); + try std.testing.expectEqual(TF3_ZERO_POS, tf3_abs(TF3_ZERO_NEG)); +} + +test "test_tf3_is_negative_true" { + const neg_value = tf3_from_f32(-1.5); + try std.testing.expect(tf3_is_negative(neg_value)); +} + +test "test_tf3_is_negative_false_for_positive" { + const pos_value = tf3_from_f32(1.5); + try std.testing.expect(!tf3_is_negative(pos_value)); +} + +test "test_tf3_is_negative_false_for_zero" { + try std.testing.expect(!tf3_is_negative(TF3_ZERO_NEG)); +} + +test "test_tf3_is_positive_true" { + const pos_value = tf3_from_f32(1.5); + try std.testing.expect(tf3_is_positive(pos_value)); +} + +test "test_tf3_is_positive_false_for_negative" { + const neg_value = tf3_from_f32(-1.5); + try std.testing.expect(!tf3_is_positive(neg_value)); +} + +test "test_tf3_is_positive_false_for_zero" { + try std.testing.expect(!tf3_is_positive(TF3_ZERO_POS)); +} + +test "test_tf3_copy_sign_from_negative" { + const pos_value = tf3_from_f32(2.0); + const neg_source = tf3_from_f32(-1.0); + const result = tf3_copy_sign(pos_value, neg_source); + const decoded = tf3_to_f32(result); + try std.testing.expect(decoded < -1.5 and decoded > -2.5); +} + +test "test_tf3_copy_sign_from_positive" { + const neg_value = tf3_from_f32(-2.0); + const pos_source = tf3_from_f32(1.0); + const result = tf3_copy_sign(neg_value, pos_source); + const decoded = tf3_to_f32(result); + try std.testing.expect(decoded > 1.5 and decoded < 2.5); +} + +test "test_tf3_max_returns_greater" { + const a = tf3_from_f32(2.0); + const b = tf3_from_f32(5.0); + const result = tf3_max(a, b); + const decoded = tf3_to_f32(result); + try std.testing.expect(decoded > 4.0); +} + +test "test_tf3_max_equal_values" { + const a = tf3_from_f32(3.0); + const b = tf3_from_f32(3.0); + const result = tf3_max(a, b); + try std.testing.expectEqual(a, result); +} + +test "test_tf3_min_returns_smaller" { + const a = tf3_from_f32(2.0); + const b = tf3_from_f32(5.0); + const result = tf3_min(a, b); + const decoded = tf3_to_f32(result); + try std.testing.expect(decoded < 3.0); +} + +test "test_tf3_min_equal_values" { + const a = tf3_from_f32(3.0); + const b = tf3_from_f32(3.0); + const result = tf3_min(a, b); + try std.testing.expectEqual(a, result); +} + +test "test_tf3_from_f32_phi_rounds_positive" { + const PHI: f32 = 1.6180339887498948; + const encoded = tf3_from_f32_phi(PHI); + const decoded = tf3_to_f32(encoded); + try std.testing.expectApproxEqAbs(PHI, decoded, 0.15); +} + +test "test_tf3_from_f32_phi_rounds_negative" { + const PHI: f32 = 1.6180339887498948; + const encoded = tf3_from_f32_phi(-PHI); + const decoded = tf3_to_f32(encoded); + try std.testing.expectApproxEqAbs(-PHI, decoded, 0.15); +} + +test "test_tf3_from_f32_phi_zero" { + try std.testing.expectEqual(TF3_ZERO_POS, tf3_from_f32_phi(0.0)); + try std.testing.expectEqual(TF3_ZERO_NEG, tf3_from_f32_phi(-0.0)); +} + +test "test_tf3_eq_equal_values" { + const a = tf3_from_f32(2.5); + const b = tf3_from_f32(2.5); + try std.testing.expect(tf3_eq(a, b)); +} + +test "test_tf3_eq_different_values" { + const a = tf3_from_f32(2.5); + const b = tf3_from_f32(3.5); + try std.testing.expect(!tf3_eq(a, b)); +} + +test "test_tf3_eq_pos_zero_eq_neg_zero" { + try std.testing.expect(tf3_eq(TF3_ZERO_POS, TF3_ZERO_NEG)); +} + +test "test_tf3_ne_different_values" { + const a = tf3_from_f32(2.5); + const b = tf3_from_f32(3.5); + try std.testing.expect(tf3_ne(a, b)); +} + +test "test_tf3_ne_equal_values" { + const a = tf3_from_f32(2.5); + const b = tf3_from_f32(2.5); + try std.testing.expect(!tf3_ne(a, b)); +} + +test "test_tf3_lt_less_than" { + const a = tf3_from_f32(2.0); + const b = tf3_from_f32(3.0); + try std.testing.expect(tf3_lt(a, b)); +} + +test "test_tf3_lt_equal_values" { + const a = tf3_from_f32(2.5); + const b = tf3_from_f32(2.5); + try std.testing.expect(!tf3_lt(a, b)); +} + +test "test_tf3_lt_negative_positive" { + const neg = tf3_from_f32(-2.0); + const pos = tf3_from_f32(1.0); + try std.testing.expect(tf3_lt(neg, pos)); +} + +test "test_tf3_le_less_than_or_equal" { + const a = tf3_from_f32(2.0); + const b = tf3_from_f32(3.0); + try std.testing.expect(tf3_le(a, b)); +} + +test "test_tf3_le_equal_values" { + const a = tf3_from_f32(2.5); + const b = tf3_from_f32(2.5); + try std.testing.expect(tf3_le(a, b)); +} + +test "test_tf3_gt_greater_than" { + const a = tf3_from_f32(3.0); + const b = tf3_from_f32(2.0); + try std.testing.expect(tf3_gt(a, b)); +} + +test "test_tf3_gt_equal_values" { + const a = tf3_from_f32(2.5); + const b = tf3_from_f32(2.5); + try std.testing.expect(!tf3_gt(a, b)); +} + +test "test_tf3_ge_greater_than_or_equal" { + const a = tf3_from_f32(3.0); + const b = tf3_from_f32(2.0); + try std.testing.expect(tf3_ge(a, b)); +} + +test "test_tf3_ge_equal_values" { + const a = tf3_from_f32(2.5); + const b = tf3_from_f32(2.5); + try std.testing.expect(tf3_ge(a, b)); +} + +test "test_tf3_comparison_consistency" { + const a = tf3_from_f32(2.0); + const b = tf3_from_f32(3.0); + try std.testing.expect(tf3_lt(a, b)); + try std.testing.expect(tf3_le(a, b)); + try std.testing.expect(!tf3_gt(a, b)); + try std.testing.expect(!tf3_ge(a, b)); +} + +test "test_tf3_add_positive_values" { + const a = tf3_from_f32(1.5); + const b = tf3_from_f32(2.5); + const result = tf3_add(a, b); + const decoded = tf3_to_f32(result); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.5); +} + +test "test_tf3_add_negative_values" { + const a = tf3_from_f32(-1.5); + const b = tf3_from_f32(-2.5); + const result = tf3_add(a, b); + const decoded = tf3_to_f32(result); + try std.testing.expectApproxEqAbs(-4.0, decoded, 0.5); +} + +test "test_tf3_add_opposite_values" { + const a = tf3_from_f32(2.0); + const b = tf3_from_f32(-2.0); + const result = tf3_add(a, b); + try std.testing.expect(tf3_is_zero(result)); +} + +test "test_tf3_sub_positive_values" { + const a = tf3_from_f32(5.0); + const b = tf3_from_f32(2.0); + const result = tf3_sub(a, b); + const decoded = tf3_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.5); +} + +test "test_tf3_sub_negative_result" { + const a = tf3_from_f32(1.0); + const b = tf3_from_f32(3.0); + const result = tf3_sub(a, b); + const decoded = tf3_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.5); +} + +test "test_tf3_mul_positive_values" { + const a = tf3_from_f32(2.0); + const b = tf3_from_f32(3.0); + const result = tf3_mul(a, b); + const decoded = tf3_to_f32(result); + try std.testing.expectApproxEqAbs(6.0, decoded, 1.0); +} + +test "test_tf3_mul_negative_positive" { + const a = tf3_from_f32(-2.0); + const b = tf3_from_f32(3.0); + const result = tf3_mul(a, b); + const decoded = tf3_to_f32(result); + try std.testing.expectApproxEqAbs(-6.0, decoded, 1.0); +} + +test "test_tf3_mul_with_zero" { + const a = tf3_from_f32(5.0); + const zero = tf3_from_f32(0.0); + const result = tf3_mul(a, zero); + try std.testing.expect(tf3_is_zero(result)); +} + +test "test_tf3_div_positive_values" { + const a = tf3_from_f32(6.0); + const b = tf3_from_f32(3.0); + const result = tf3_div(a, b); + const decoded = tf3_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.5); +} + +test "test_tf3_div_negative_result" { + const a = tf3_from_f32(6.0); + const b = tf3_from_f32(-3.0); + const result = tf3_div(a, b); + const decoded = tf3_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.5); +} + +test "test_tf3_div_value_by_zero" { + const a = tf3_from_f32(5.0); + const zero = tf3_from_f32(0.0); + const result = tf3_div(a, zero); + try std.testing.expect(tf3_is_inf(result)); +} + +test "test_tf3_div_zero_by_value" { + const zero = tf3_from_f32(0.0); + const a = tf3_from_f32(5.0); + const result = tf3_div(zero, a); + try std.testing.expect(tf3_is_zero(result)); +} + +test "test_tf3_div_inf_by_finite" { + const inf = TF3_INF_POS; + const a = tf3_from_f32(5.0); + const result = tf3_div(inf, a); + try std.testing.expect(tf3_is_inf(result)); +} + +test "test_tf3_add_commutative" { + const a = tf3_from_f32(1.5); + const b = tf3_from_f32(2.5); + const result1 = tf3_add(a, b); + const result2 = tf3_add(b, a); + try std.testing.expectEqual(result1, result2); +} + +test "test_tf3_mul_commutative" { + const a = tf3_from_f32(1.5); + const b = tf3_from_f32(2.5); + const result1 = tf3_mul(a, b); + const result2 = tf3_mul(b, a); + try std.testing.expectEqual(result1, result2); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant tf3_sign_mask_bit_7 { + // assert SIGN_MASK == 0x80 + @compileAssert(SIGN_MASK == 0x80); +} + +invariant tf3_exp_mask_bits_6_to_4 { + // assert EXP_MASK == 0x78 + @compileAssert(EXP_MASK == 0x78); +} + +invariant tf3_mant_mask_lower_4_bits { + // assert MANT_MASK == 0x0F + @compileAssert(MANT_MASK == 0x0F); +} + +invariant tf3_exp_max_equals_7 { + // assert EXP_MAX == 0x07 + @compileAssert(EXP_MAX == 0x07); +} + +invariant tf3_exp_min_equals_0 { + // assert EXP_MIN == 0x00 + @compileAssert(EXP_MIN == 0x00); +} + +invariant tf3_bias_equals_3 { + // assert BIAS == 3 + @compileAssert(BIAS == 3); +} + +invariant tf3_mant_bits_equals_4 { + // assert MANT_BITS == 4 + @compileAssert(MANT_BITS == 4); +} + +invariant tf3_zero_pos_encoding { + // assert TF3_ZERO_POS == 0x00 + @compileAssert(TF3_ZERO_POS == 0x00); +} + +invariant tf3_zero_neg_encoding { + // assert TF3_ZERO_NEG == 0x80 + @compileAssert(TF3_ZERO_NEG == 0x80); +} + +invariant tf3_inf_pos_encoding { + // assert TF3_INF_POS == 0x78 + @compileAssert(TF3_INF_POS == 0x78); +} + +invariant tf3_inf_neg_encoding { + // assert TF3_INF_NEG == 0xF8 + @compileAssert(TF3_INF_NEG == 0xF8); +} + +invariant tf3_negate_flips_sign_bit { + // tf3_negate(x) = x ^ 0x80 + @compileAssert(tf3_negate(0x48) == 0xC8); + @compileAssert(tf3_negate(0xC8) == 0x48); +} + +invariant tf3_negate_involutive { + // tf3_negate(tf3_negate(x)) = x + @compileAssert(true); +} + +invariant tf3_abs_clears_sign_bit { + // tf3_abs(x) = x & ~0x80 + @compileAssert(tf3_abs(0xC8) == 0x48); + @compileAssert(tf3_abs(0x48) == 0x48); +} + +invariant tf3_abs_non_negative { + // tf3_abs(x) is always non-negative + @compileAssert((tf3_abs(0xC8) & SIGN_MASK) == 0); +} + +invariant tf3_copy_sign_preserves_sign_source { + // sign(tf3_copy_sign(x, s)) = sign(s) + @compileAssert(true); +} + +invariant tf3_copy_sign_preserves_magnitude { + // |tf3_copy_sign(x, s)| = |x| + @compileAssert(true); +} + +invariant tf3_max_idempotent { + // tf3_max(x, x) = x + @compileAssert(true); +} + +invariant tf3_min_idempotent { + // tf3_min(x, x) = x + @compileAssert(true); +} + +invariant tf3_max_commutative { + // tf3_max(a, b) = tf3_max(b, a) + @compileAssert(true); +} + +invariant tf3_min_commutative { + // tf3_min(a, b) = tf3_min(b, a) + @compileAssert(true); +} + +invariant tf3_negate_zero_unchanged { + // tf3_negate(0) = 0, tf3_negate(-0) = -0 + @compileAssert(tf3_negate(TF3_ZERO_POS) == TF3_ZERO_POS); + @compileAssert(tf3_negate(TF3_ZERO_NEG) == TF3_ZERO_NEG); +} + +invariant tf3_is_negative_excludes_zero { + // tf3_is_negative(-0) == false + @compileAssert(!tf3_is_negative(TF3_ZERO_NEG)); +} + +invariant tf3_is_positive_excludes_zero { + // tf3_is_positive(0) == false + @compileAssert(!tf3_is_positive(TF3_ZERO_POS)); +} + +invariant tf3_eq_reflexive { + // For all x: tf3_eq(x, x) = true + @compileAssert(true); +} + +invariant tf3_ne_irreflexive { + // For all x: tf3_ne(x, x) = false + @compileAssert(true); +} + +invariant tf3_lt_and_gt_mutually_exclusive { + // For all a, b: not (tf3_lt(a, b) and tf3_gt(a, b)) + @compileAssert(true); +} + +invariant tf3_le_and_ge_mutually_inclusive { + // For all a, b: tf3_le(a, b) or tf3_ge(a, b) + @compileAssert(true); +} + +invariant tf3_lt_implies_le { + // For all a, b: tf3_lt(a, b) implies tf3_le(a, b) + @compileAssert(true); +} + +invariant tf3_gt_implies_ge { + // For all a, b: tf3_gt(a, b) implies tf3_ge(a, b) + @compileAssert(true); +} + +invariant tf3_eq_implies_le_and_ge { + // For all a, b: tf3_eq(a, b) implies tf3_le(a, b) and tf3_ge(a, b) + @compileAssert(true); +} + +invariant tf3_add_zero_identity { + // tf3_add(x, 0) = tf3_add(0, x) = x (approximately, due to encoding) + @compileAssert(true); +} + +invariant tf3_add_commutative { + // tf3_add(a, b) = tf3_add(b, a) + @compileAssert(true); +} + +invariant tf3_mul_zero_annihilates { + // tf3_mul(x, 0) = tf3_mul(0, x) = 0 + @compileAssert(true); +} + +invariant tf3_mul_one_identity { + // tf3_mul(x, 1) = tf3_mul(1, x) = x (approximately) + @compileAssert(true); +} + +invariant tf3_mul_commutative { + // tf3_mul(a, b) = tf3_mul(b, a) + @compileAssert(true); +} + +invariant tf3_div_by_one_identity { + // tf3_div(x, 1) = x (approximately) + @compileAssert(true); +} + +invariant tf3_div_by_zero_is_inf { + // tf3_div(x, 0) = Inf (or -Inf if x < 0) + @compileAssert(true); +} + +invariant tf3_sub_with_zero { + // tf3_sub(x, 0) = x (approximately) + @compileAssert(true); +} + +invariant tf3_is_finite_excludes_inf_nan { + // tf3_is_finite(x) = true implies !tf3_is_inf(x) and !tf3_is_nan(x) + @compileAssert(true); +} + +invariant tf3_sign_positive_returns_one { + // For x > 0 and x is not NaN: tf3_sign(x) = 1 + @compileAssert(true); +} + +invariant tf3_sign_negative_returns_minus_one { + // For x < 0 and x is not NaN: tf3_sign(x) = -1 + @compileAssert(true); +} + +invariant tf3_sign_zero_returns_zero { + // For x = 0: tf3_sign(x) = 0 + @compileAssert(true); +} + +invariant tf3_clamp_in_range_returns_value { + // For x in [min, max]: tf3_clamp(x, min, max) = x + @compileAssert(true); +} + +invariant tf3_lerp_t_zero_returns_a { + // tf3_lerp(a, b, 0) = a + @compileAssert(true); +} + +invariant tf3_lerp_t_one_returns_b { + // tf3_lerp(a, b, 1) = b + @compileAssert(true); +} + +test "test_tf3_is_nan_true" { + // given tf3 = TF3_NAN + // when result = tf3_is_nan(tf3) + // then result == true + try std.testing.expect(tf3_is_nan(TF3_NAN)); +} + +test "test_tf3_is_nan_false_for_normal" { + const val = tf3_from_f32(1.5); + try std.testing.expect(!tf3_is_nan(val)); +} + +test "test_tf3_is_finite_normal" { + const val = tf3_from_f32(1.5); + try std.testing.expect(tf3_is_finite(val)); +} + +test "test_tf3_is_finite_false_for_inf" { + try std.testing.expect(!tf3_is_finite(TF3_INF_POS)); + try std.testing.expect(!tf3_is_finite(TF3_INF_NEG)); +} + +test "test_tf3_is_finite_false_for_nan" { + try std.testing.expect(!tf3_is_finite(TF3_NAN)); +} + +test "test_tf3_signbit_positive" { + const val = tf3_from_f32(1.5); + try std.testing.expect(!tf3_signbit(val)); +} + +test "test_tf3_signbit_negative" { + const val = tf3_from_f32(-1.5); + try std.testing.expect(tf3_signbit(val)); +} + +test "test_tf3_sign_positive" { + const val = tf3_from_f32(1.5); + try std.testing.expectEqual(@as(i8, 1), tf3_sign(val)); +} + +test "test_tf3_sign_negative" { + const val = tf3_from_f32(-1.5); + try std.testing.expectEqual(@as(i8, -1), tf3_sign(val)); +} + +test "test_tf3_sign_zero" { + try std.testing.expectEqual(@as(i8, 0), tf3_sign(TF3_ZERO_POS)); + try std.testing.expectEqual(@as(i8, 0), tf3_sign(TF3_ZERO_NEG)); +} + +test "test_tf3_sign_nan" { + try std.testing.expectEqual(@as(i8, 0), tf3_sign(TF3_NAN)); +} + +test "test_tf3_clamp_in_range" { + const x = tf3_from_f32(5.0); + const min_val = tf3_from_f32(0.0); + const max_val = tf3_from_f32(10.0); + const result = tf3_clamp(x, min_val, max_val); + const decoded = tf3_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.5); +} + +test "test_tf3_clamp_below_min" { + const x = tf3_from_f32(-5.0); + const min_val = tf3_from_f32(0.0); + const max_val = tf3_from_f32(10.0); + const result = tf3_clamp(x, min_val, max_val); + const decoded = tf3_to_f32(result); + try std.testing.expectApproxEqAbs(0.0, decoded, 0.5); +} + +test "test_tf3_clamp_above_max" { + const x = tf3_from_f32(15.0); + const min_val = tf3_from_f32(0.0); + const max_val = tf3_from_f32(10.0); + const result = tf3_clamp(x, min_val, max_val); + const decoded = tf3_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 1.0); +} + +test "test_tf3_lerp_t_zero" { + const a = tf3_from_f32(10.0); + const b = tf3_from_f32(20.0); + const t = tf3_from_f32(0.0); + const result = tf3_lerp(a, b, t); + const decoded = tf3_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.5); +} + +test "test_tf3_lerp_t_one" { + const a = tf3_from_f32(10.0); + const b = tf3_from_f32(20.0); + const t = tf3_from_f32(1.0); + const result = tf3_lerp(a, b, t); + const decoded = tf3_to_f32(result); + try std.testing.expectApproxEqAbs(20.0, decoded, 1.0); +} + +test "test_tf3_lerp_t_half" { + const a = tf3_from_f32(0.0); + const b = tf3_from_f32(10.0); + const t = tf3_from_f32(0.5); + const result = tf3_lerp(a, b, t); + const decoded = tf3_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.5); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "bench_tf3_from_f32_latency" { + // measure: nanoseconds to tf3_from_f32(1.5) + // target: < 150ns + @setEvalBranchQuota(10000); + var result: TF3 = 0; + for (0..1000) |_| { + result = tf3_from_f32(1.5); + } +} + +bench "bench_tf3_to_f32_latency" { + // measure: nanoseconds to tf3_to_f32(0x48) + // target: < 100ns + @setEvalBranchQuota(10000); + var result: f32 = 0; + for (0..1000) |_| { + result = tf3_to_f32(0x48); + } +} + +bench "bench_tf3_is_zero_latency" { + // measure: nanoseconds to tf3_is_zero(0) + // target: < 50ns + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = tf3_is_zero(0); + } +} + +bench "bench_tf3_lookup_table_latency" { + // measure: nanoseconds to access mant_lookup_table[16] + // target: < 20ns + @setEvalBranchQuota(10000); + var result: u16 = 0; + for (0..1000) |_| { + result = mant_lookup_table[16]; + } +} + +bench "bench_tf3_extract_sign_latency" { + // measure: nanoseconds to extract sign + // target: < 20ns + @setEvalBranchQuota(10000); + var result: i8 = 0; + for (0..1000) |_| { + result = tf3_extract_sign(0x80); + } +} + +bench "bench_tf3_extract_exponent_latency" { + // measure: nanoseconds to extract exponent + // target: < 20ns + @setEvalBranchQuota(10000); + var result: i8 = 0; + for (0..1000) |_| { + result = tf3_extract_exponent(0x78); + } +} + +bench "bench_tf3_negate_latency" { + // measure: nanoseconds to negate + // target: < 10ns (single XOR operation) + @setEvalBranchQuota(10000); + var result: TF3 = 0; + for (0..1000) |_| { + result = tf3_negate(0x48); + } +} + +bench "bench_tf3_abs_latency" { + // measure: nanoseconds to compute absolute value + // target: < 10ns (single AND operation) + @setEvalBranchQuota(10000); + var result: TF3 = 0; + for (0..1000) |_| { + result = tf3_abs(0xC8); + } +} + +bench "bench_tf3_is_negative_latency" { + // measure: nanoseconds to check if negative + // target: < 20ns + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = tf3_is_negative(0xC8); + } +} + +bench "bench_tf3_is_positive_latency" { + // measure: nanoseconds to check if positive + // target: < 20ns + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = tf3_is_positive(0x48); + } +} + +bench "bench_tf3_copy_sign_latency" { + // measure: nanoseconds to copy sign + // target: < 30ns + @setEvalBranchQuota(10000); + var result: TF3 = 0; + const a: TF3 = 0x48; + const b: TF3 = 0x88; + for (0..1000) |_| { + result = tf3_copy_sign(a, b); + } +} + +bench "bench_tf3_max_latency" { + // measure: nanoseconds to compute max of two values + // target: < 150ns (includes decode) + @setEvalBranchQuota(10000); + var result: TF3 = 0; + const a: TF3 = 0x48; + const b: TF3 = 0x58; + for (0..1000) |_| { + result = tf3_max(a, b); + } +} + +bench "bench_tf3_min_latency" { + // measure: nanoseconds to compute min of two values + // target: < 150ns (includes decode) + @setEvalBranchQuota(10000); + var result: TF3 = 0; + const a: TF3 = 0x48; + const b: TF3 = 0x58; + for (0..1000) |_| { + result = tf3_min(a, b); + } +} + +bench "bench_tf3_from_f32_phi_latency" { + // measure: nanoseconds to encode with phi-optimized rounding + // target: < 200ns + @setEvalBranchQuota(10000); + var result: TF3 = 0; + for (0..1000) |_| { + result = tf3_from_f32_phi(1.618); + } +} + +bench "bench_tf3_eq_latency" { + // measure: nanoseconds to compare equality + // target: < 30ns + @setEvalBranchQuota(10000); + var result: bool = false; + const a: TF3 = 0x48; + const b: TF3 = 0x48; + for (0..1000) |_| { + result = tf3_eq(a, b); + } +} + +bench "bench_tf3_ne_latency" { + // measure: nanoseconds to compare not-equal + // target: < 30ns + @setEvalBranchQuota(10000); + var result: bool = false; + const a: TF3 = 0x48; + const b: TF3 = 0x58; + for (0..1000) |_| { + result = tf3_ne(a, b); + } +} + +bench "bench_tf3_lt_latency" { + // measure: nanoseconds to compare less-than + // target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: TF3 = 0x48; + const b: TF3 = 0x58; + for (0..1000) |_| { + result = tf3_lt(a, b); + } +} + +bench "bench_tf3_le_latency" { + // measure: nanoseconds to compare less-than-or-equal + // target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: TF3 = 0x48; + const b: TF3 = 0x58; + for (0..1000) |_| { + result = tf3_le(a, b); + } +} + +bench "bench_tf3_gt_latency" { + // measure: nanoseconds to compare greater-than + // target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: TF3 = 0x58; + const b: TF3 = 0x48; + for (0..1000) |_| { + result = tf3_gt(a, b); + } +} + +bench "bench_tf3_ge_latency" { + // measure: nanoseconds to compare greater-than-or-equal + // target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: TF3 = 0x58; + const b: TF3 = 0x48; + for (0..1000) |_| { + result = tf3_ge(a, b); + } +} + +bench "bench_tf3_add_latency" { + // measure: nanoseconds to add two values + // target: < 200ns (includes decode + add + encode) + @setEvalBranchQuota(10000); + var result: TF3 = 0; + const a: TF3 = 0x48; + const b: TF3 = 0x40; + for (0..1000) |_| { + result = tf3_add(a, b); + } +} + +bench "bench_tf3_sub_latency" { + // measure: nanoseconds to subtract two values + // target: < 200ns (includes decode + sub + encode) + @setEvalBranchQuota(10000); + var result: TF3 = 0; + const a: TF3 = 0x48; + const b: TF3 = 0x40; + for (0..1000) |_| { + result = tf3_sub(a, b); + } +} + +bench "bench_tf3_mul_latency" { + // measure: nanoseconds to multiply two values + // target: < 200ns (includes decode + mul + encode) + @setEvalBranchQuota(10000); + var result: TF3 = 0; + const a: TF3 = 0x48; + const b: TF3 = 0x50; + for (0..1000) |_| { + result = tf3_mul(a, b); + } +} + +bench "bench_tf3_div_latency" { + // measure: nanoseconds to divide two values + // target: < 250ns (includes decode + div + encode) + @setEvalBranchQuota(10000); + var result: TF3 = 0; + const a: TF3 = 0x50; + const b: TF3 = 0x48; + for (0..1000) |_| { + result = tf3_div(a, b); + } +} + +bench "bench_tf3_is_nan_latency" { + // measure: nanoseconds to check if NaN + // target: < 20ns (single bit check) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: TF3 = 0xE0; + for (0..1000) |_| { + result = tf3_is_nan(val); + } +} + +bench "bench_tf3_is_finite_latency" { + // measure: nanoseconds to check if finite + // target: < 30ns (bit checks) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: TF3 = 0x48; + for (0..1000) |_| { + result = tf3_is_finite(val); + } +} + +bench "bench_tf3_signbit_latency" { + // measure: nanoseconds to check sign bit + // target: < 10ns (single bit test) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: TF3 = 0x80; + for (0..1000) |_| { + result = tf3_signbit(val); + } +} + +bench "bench_tf3_sign_latency" { + // measure: nanoseconds to get sign value + // target: < 30ns (includes zero/nan checks) + @setEvalBranchQuota(10000); + var result: i8 = 0; + const val: TF3 = 0x88; + for (0..1000) |_| { + result = tf3_sign(val); + } +} + +bench "bench_tf3_clamp_latency" { + // measure: nanoseconds to clamp to range + // target: < 200ns (includes decode + compare + encode) + @setEvalBranchQuota(10000); + var result: TF3 = 0; + const x: TF3 = 0x50; + const min_val: TF3 = 0x48; + const max_val: TF3 = 0x60; + for (0..1000) |_| { + result = tf3_clamp(x, min_val, max_val); + } +} + +bench "bench_tf3_lerp_latency" { + // measure: nanoseconds to compute lerp + // target: < 300ns (includes decode + computation + encode) + @setEvalBranchQuota(10000); + var result: TF3 = 0; + const a: TF3 = 0x48; + const b: TF3 = 0x60; + const t: TF3 = 0x48; + for (0..1000) |_| { + result = tf3_lerp(a, b, t); + } +} + diff --git a/apps/website/public/t27/specs/numeric/trinity_numeric_surface.t27 b/apps/website/public/t27/specs/numeric/trinity_numeric_surface.t27 new file mode 100644 index 0000000000..7c608f348d --- /dev/null +++ b/apps/website/public/t27/specs/numeric/trinity_numeric_surface.t27 @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +; trinity_numeric_surface.t27 -- Public numeric interchange policy (GoldenFloat-first) +; NUMERIC-STANDARD-001 -- integer-backed GF raw words are the portable surface +; phi^2 + 1/phi^2 = 3 | TRINITY + +module trinity-numeric-surface; + +// ----------------------------------------------------------------------------- +// Policy (normative for new modules) +// ----------------------------------------------------------------------------- +// - **Public** tensor/weight/score interchange SHOULD use GoldenFloat **raw** +// integer types (GF16 = u16, GF8 = u8, etc.), not IEEE f32/f64 fields. +// - **f32** / **f64** are **[BRIDGE]** types: host math, legacy APIs, decode-test +// helpers. They MUST NOT appear in new **public** structs exported to AR/nn/vsa +// unless tagged [BRIDGE] and scheduled for GF migration (see +// docs/nona-02-organism/NUMERIC-GF16-DEBT-INVENTORY.md). +// - **TF3** (u8) and **ternary** trits remain experimental sidecars -- not a +// substitute for GF16-primary inference policy. +// ----------------------------------------------------------------------------- + +pub const POLICY_VERSION : u8 = 1; + +// Raw bit widths for GoldenFloat family (portable interchange) +pub const GF4_RAW_BITS : u8 = 4; +pub const GF8_RAW_BITS : u8 = 8; +pub const GF12_RAW_BITS : u8 = 12; +pub const GF16_RAW_BITS : u8 = 16; +pub const GF20_RAW_BITS : u8 = 20; +pub const GF24_RAW_BITS : u8 = 24; +pub const GF32_RAW_BITS : u8 = 32; + +// Primary inference format per NUMERIC-STANDARD-001 (GF16 = 16-bit raw word) +pub const PRIMARY_INFERENCE_RAW_BITS : u8 = 16; + +test "surface_primary_is_gf16" { + try std.testing.expectEqual(@as(u8, 16), PRIMARY_INFERENCE_RAW_BITS); +} diff --git a/apps/website/public/t27/specs/physics/chimera_best_gamma.t27 b/apps/website/public/t27/specs/physics/chimera_best_gamma.t27 new file mode 100644 index 0000000000..1faf063d78 --- /dev/null +++ b/apps/website/public/t27/specs/physics/chimera_best_gamma.t27 @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/physics/chimera_best_gamma.t27 +// Best gamma formula from PDG 2024 P35_new (Delta = 0.140%) +// φ² + 1/φ² = 3 | TRINITY + +module chimera { + use base::math; + use compiler::codegen; + + const PHI: Float = base::PHI; + + pub fn formula() -> Float { + // P35_new from PDG 2024: GAMMA.pow(PHI) + PI + GAMMA.pow(PHI) + PI + } +} + test "chimera_best_gamma_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/physics/e8_lqg_bridge.t27 b/apps/website/public/t27/specs/physics/e8_lqg_bridge.t27 new file mode 100644 index 0000000000..788c9de32d --- /dev/null +++ b/apps/website/public/t27/specs/physics/e8_lqg_bridge.t27 @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 +# E8-QUANTUM GRAVITY BRIDGE + +## Specification + +Cycle #133 -- Ko Samui -- v9.5 E8-QUANTUM GRAVITY + +This module bridges E8 Lie Group, VSA hypervectors, and quantum gravity observables. +Implements sacred formula V = n * 3^k * pi^m * phi^p * e^q for encoding +quantum gravity parameters (gamma, Lambda, graviton mass, holographic entropy). + +## Key Features + +- E8 root -> LQG (Loop Quantum Gravity) spin encoding +- Barbero-Immirzi parameter gamma sacred encoding +- Cosmological constant Lambda via sacred formula +- Holographic entropy bound: S = A/4 -> hypervector area +- Graviton mass prediction from E8 root mapping +- AdS/CFT boundary projection via VSA + +## Mathematical Foundation + +``` +phi = 1.618033988749895 +phi^2 + phi^(-2) = 3 +``` + +## Constants + +``` +PHI = 1.618033988749895 +PHI_INV = 0.618033988749895 +PHI_SQ = 2.618033988749895 +PHI_CUBED = 4.23606797749979 + +PLANCK_LENGTH = 1.616255e-35 m +PLANCK_MASS = 2.176434e-8 kg +PLANCK_TIME = 5.391247e-44 s +PLANCK_TEMP = 1.416784e32 K + +LAMBDA_CDM = 1.1056e-52 m^-2 +RHO_LAMBDA = 5.96e-10 GeV/m^3 + +GAMMA_STANDARD = 0.2375 +GAMMA_PHI = (phi - 1) / sqrt2 ~= 0.261 + +GRAVITON_MASS_BOUND = 1e-22 eV +GRAVITON_MASS_PREDICTION = m_Pl * phi^(-8) eV + +HOLOGRAPHIC_CONSTANT = 0.25 +HYPERVECTOR_DIM = 1024 +``` + +## E8 Root Structure + +### Type 1: (+/-1, +/-1, 0, 0, 0, 0, 0, 0) with permutations +- 112 roots +- Norm squared = 2 + +### Type 2: (+/-1/2, +/-1/2, +/-1/2, +/-1/2, +/-1/2, +/-1/2, +/-1/2, +/-1/2) with even parity +- 128 roots +- Norm squared = 2 + +Total: 240 E8 roots + +## Sacred Formula Encoding + +``` +V = n * 3^k * pi^m * phi^p * e^q +``` + +### SacredParams Mapping + +| Parameter | Encoding (gamma) | Encoding (Lambda) | Encoding (m_g) | +|----------|--------------|---------------|----------------| +| gamma < 0.24 | {n=1, k=-2, m=0, p=-1, q=1} | | | +| gamma < 0.26 | {n=1, k=-1, m=0, p=-2, q=0} | | | +| gamma >= 0.26 | {n=2, k=-2, m=0, p=-2, q=-1} | | | +| Lambda | | {n=1, k=-4, m=0, p=-8, q=2} | | +| m_g < 1e-24 eV | | | {n=1, k=0, m=0, p=-10, q=-5} | +| m_g >= 1e-24 eV | | | {n=1, k=0, m=0, p=-8, q=0} | + +## Quantum Gravity Projection + +Maps E8 coordinates to LQG parameters: +- First 4 coordinates -> spin network (j1, j2, j3, j4) +- Next 2 coordinates -> Barbero-Immirzi parameter gamma +- Last 2 coordinates -> scaled cosmological constant Lambda + +## Tests + +``` +test "E8: root generation" { + const roots = generateAll() + expect(roots.len == 240) + expect(roots[0].isValid() == true) + expect(roots[0].normSquared() ~= 2.0) +} + +test "E8: sacred formula" { + const params = SacredParams{ .n = 1, .k = -1, .m = 0, .p = -1, .q = 0 } + const V = params.calculate() + expect(V > 0) +} + +test "E8-LQG: projection" { + const root = E8Root.init([_]f64{1, 1, 0, 0, 0.5, 0, 0, 0}) + const proj = root.quantumProjection() + expect(proj.gamma > 0) + expect(proj.gamma < 1) +} +``` diff --git a/apps/website/public/t27/specs/physics/formula_discovery.t27 b/apps/website/public/t27/specs/physics/formula_discovery.t27 new file mode 100644 index 0000000000..0b8d83506c --- /dev/null +++ b/apps/website/public/t27/specs/physics/formula_discovery.t27 @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: Apache-2.0 +// Formula Discovery v1.0 — ULTRA ENGINE Specification +// φ² + 1/φ² = 3 | TRINITY + +module FormulaDiscovery { + use math::constants; + + // ===== Sacred Constants ===== + const PHI: f64 = 1.6180339887498948; + const PI: f64 = 3.1415926535897932; + const E: f64 = 2.7182818284590452; + + // ===== Discovery Methods ===== + + // Base formulas from formula_registry.t27 for chimera search + // These serve as parent formulas for discovery operations + const S1_gamma: f64 = pow(PHI, -3.0); + const PM1b_alpha_inv_exact: f64 = (360.0 / pow(PHI, 2.0)) - 2.0 / pow(PHI, 3.0) + 1.0 / pow(3.0 * PHI, 5.0); + const N1_alpha_s: f64 = 1.0 / (pow(PHI, 4.0) + PHI); + const N2_Tc: f64 = 156.5; + const CKM1_theta_C: f64 = (360.0 / pow(PHI, 2.0)) / 16.0; + const CKM2_V_cb: f64 = 1.0 / (7.0 * pow(PHI, 2.0) * pow(PI, 2.0) * pow(E, 2.0)); + const PMNS2_sin2th23: f64 = 3.0 * pow(PHI, -8.0) * PI * E; + const PMNS3_delta_CP: f64 = 9.0 * pow(PHI, -2.0) * 180.0 / PI; + const PMNS4_sin2th12: f64 = 4.0 / (pow(PHI, 2.0) * pow(PI, 4.0) * pow(E, 4.0)); + const H1_mH_mZ: f64 = (1.0 / 8.0) * pow(PHI, 2.0) * pow(PI, 3.0) * pow(E, -2.0); + + // Chimera v07 formulas + const P10_V_ud: f64 = 7.0 * pow(PHI, -5.0) * pow(PI, 3.0) * pow(E, -3.0); + const P11_V_cs: f64 = 7.0 * pow(PHI, -5.0) * pow(PI, 3.0) * pow(E, -3.0); + const P12_V_td: f64 = 2.0 * pow(PHI, -4.0) * pow(PI, -4.0) * E; + const P13_sin2th12_chimera: f64 = 8.0 * pow(PHI, -5.0) * PI * pow(E, -2.0); + const P14_delta_CP_rad: f64 = 9.0 * pow(PHI, -2.0); + const P15_ms_mmu: f64 = pow(PHI, -2.0) / PI * pow(E, 2.0); + const P16_mb_mt: f64 = 4.0 * pow(PHI, -2.0) / PI * pow(E, -3.0); + const P17_Omega_b: f64 = 4.0 * pow(PHI, -2.0) * pow(PI, -3.0); + const P18_ns: f64 = 3.0 * pow(PHI, 3.0) * pow(PI, -4.0) * pow(E, 2.0); + + // ===== Chimera Search Functions ===== + + /// Combine two formulas with arithmetic operations + fn chimera_mul(a: f64, b: f64) -> f64 { + return a * b; + } + + fn chimera_div(a: f64, b: f64) -> f64 { + if abs(b) < 1e-15 { + return 0.0; + } + return a / b; + } + + fn chimera_add(a: f64, b: f64) -> f64 { + return a + b; + } + + fn chimera_sub(a: f64, b: f64) -> f64 { + return a - b; + } + + // ===== Trigonometric Functions ===== + + fn chimera_sin(x: f64) -> f64 { + return sin(x); + } + + fn chimera_cos(x: f64) -> f64 { + return cos(x); + } + + // ===== Logarithmic and Exponential Functions ===== + + fn chimera_ln(x: f64) -> f64 { + if x <= 0.0 { + return 0.0; + } + return ln(x); + } + + fn chimera_exp(x: f64) -> f64 { + return exp(x); + } + + // ===== Power Function ===== + + fn chimera_pow(x: f64, n: f64) -> f64 { + return powf(x, n); + } + + // ===== Pattern Generation ===== + + /// Generate n·φⁱ·πʲ·eᵏ patterns + fn generate_basis(max_pow: i32) -> Vec<(String, f64)> { + let mut basis = Vec::new(); + + for i in -max_pow..=max_pow { + for j in -max_pow..=max_pow { + for k in -max_pow..=max_pow { + let val = 1.0_f64 * powi(PHI, i) * powi(PI, j) * powi(E, k); + basis.push((format!("φ^{}π^{}e^{}", i, j, k), val)); + } + } + } + + basis + } + + // ===== Tests ===== + + test "S1_gamma_verified" { + given g = S1_gamma + and pdg = 0.23607 + when err = abs(g - pdg) / pdg + then err < 0.001 + } + + test "N1_alpha_s_verified" { + given as = N1_alpha_s + and pdg = 0.118034 + when err = abs(as - pdg) / pdg + then err < 0.001 + } + + test "PMNS2_sin2th23_verified" { + given s = PMNS2_sin2th23 + and pdg = 0.547 + when err = abs(s - pdg) / pdg + then err < 0.001 + } + + test "trinity_identity" { + given t = pow(PHI, 2.0) + 1.0 / pow(PHI, 2.0) + then abs(t - 3.0) < 1e-12 + } + + // ===== Invariants ===== + + invariant "phi_positive" { + assert PHI > 1.5 and PHI < 1.7 + } + + invariant "gamma_positive" { + assert S1_gamma > 0.0 and S1_gamma < 1.0 + } + + invariant "sin2th23_in_bounds" { + let s = PMNS2_sin2th23; + assert s > 0.0 and s < 1.0 + } + + invariant "ckm_unitarity_check" { + let v_us = 1.0 / (E * PHI); + let v_cb = CKM2_V_cb; + let v_ud = P10_V_ud; + assert v_us > 0.0 and v_cb > 0.0 and v_ud > 0.0 and v_ud < 1.0 + } +} diff --git a/apps/website/public/t27/specs/physics/formula_registry.t27 b/apps/website/public/t27/specs/physics/formula_registry.t27 new file mode 100644 index 0000000000..5dc0521664 --- /dev/null +++ b/apps/website/public/t27/specs/physics/formula_registry.t27 @@ -0,0 +1,212 @@ +// Trinity Formula Registry — All 69 φ-parametrizations from v06/v07 +// Generated from FORMULA_TABLE_v06.md and FORMULA_TABLE_v07.md +// SSOT for Trinity formula discovery + +// ============================================================================ +// CONSTANTS +// ============================================================================ + +const PHI: f64 = 1.6180339887498948; +const PI: f64 = std::f64::consts::PI; +const E: f64 = std::f64::consts::E; +const GA: f64 = 360.0 / (PHI * PHI); // Golden angle = 222.5° + +// ============================================================================ +// SECTOR 1 — GAUGE COUPLINGS (8 formulas) +// ============================================================================ + +// [VERIFIED] sector=gauge-coupling cx=1 Δ=-0.62% +fn gamma_phi() -> f64 { + return pow(PHI, -3.0); +} + +// [VERIFIED] sector=gauge-coupling cx=1 Δ=0.00% +fn ln2_over_pi() -> f64 { + return ln(2.0) / PI; +} + +// [VERIFIED] sector=gauge-coupling cx=1 Δ=0.00% +fn ln3_over_pi() -> f64 { + return ln(3.0) / PI; +} + +// [VERIFIED] sector=gauge-coupling cx=1 Δ=0.029% +fn alpha_inv_pellis_exact() -> f64 { + return GA - 2.0 / pow(PHI, 3.0) + pow(3.0 * PHI, -5.0); +} + +// [VERIFIED] sector=gauge-coupling cx=1 Δ=0.029% +fn alpha_s() -> f64 { + return 1.0 / (pow(PHI, 4.0) + PHI); +} + +// [VERIFIED] sector=gauge-coupling cx=1 Δ=0.00% +fn tc_qcd() -> f64 { + return 156.5; +} + +// ============================================================================ +// SECTOR 2 — ELECTROWEAK & NUCLEAR (2 formulas) +// ============================================================================ + +// [VERIFIED] sector=electroweak cx=1 Δ=0.034% +fn neutron_proton_ratio() -> f64 { + let alpha_em: f64 = 1.0 / 137.035999084; + return 1.0 + alpha_em * gamma_phi(); +} + +// [VERIFIED] sector=electroweak cx=1 Δ=0.027% +fn muon_electron_ratio() -> f64 { + return 8.0 * pow(PHI, 2.0) * pow(PI, 2.0); +} + +// ============================================================================ +// SECTOR 3 — LEPTON MASSES (5 formulas) +// ============================================================================ + +// [VERIFIED] sector=lepton cx=1 Δ=0.029% +fn electron_mass_mev() -> f64 { + return 1.0 / (E * PHI); +} + +// [VERIFIED] sector=lepton cx=1 Δ=0.029% +fn muon_mass_mev() -> f64 { + return 2.0 * pow(PHI, 2.0) * pow(PI, 2.0); +} + +// [VERIFIED] sector=lepton cx=1 Δ=0.028% +fn tau_mass_mev() -> f64 { + return 4.0 / (PHI * PHI); +} + +// [VERIFIED] sector=lepton cx=1 Δ=0.000% +fn koide_q() -> f64 { + return 2.0 / 3.0; +} + +// ============================================================================ +// SECTOR 4 — QUARK MASSES (8 formulas) +// ============================================================================ + +// [VERIFIED] sector=quark cx=1 Δ=0.034% +fn bottom_mass_gev() -> f64 { + return 5.0 * PI * pow(PHI, -2.0) * pow(E, -1.0); +} + +// [VERIFIED] sector=quark cx=1 Δ=0.043% +fn top_mass_gev() -> f64 { + return 4.0 * 9.0 * PI * pow(PHI, 4.0) * pow(E, 2.0); +} + +// [VERIFIED] sector=quark cx=1 Δ=0.000% +fn strange_down_ratio() -> f64 { + return 2.0 * PI * PHI / 3.0; +} + +// ============================================================================ +// SECTOR 5 — CKM MATRIX (3 formulas) +// ============================================================================ + +// [VERIFIED] sector=ckm cx=1 Δ=0.096% +fn theta_cabibbo() -> f64 { + return GA / 16.0; +} + +// [VERIFIED] sector=ckm cx=1 Δ=0.043% +fn v_cb() -> f64 { + return 1.0 / (7.0 * pow(PHI, 2.0) * pow(PI, 2.0) * pow(E, 2.0)); +} + +// [VERIFIED] sector=ckm cx=1 Δ=1.36% +fn v_us() -> f64 { + return 1.0 / (E * PHI); +} + +// ============================================================================ +// SECTOR 6 — PMNS NEUTRINOS (4 formulas) +// ============================================================================ + +// [VERIFIED] sector=pmns cx=1 Δ=0.062% +fn sin2theta23_pmns() -> f64 { + return 3.0 * pow(PHI, -8.0) * PI * E; +} + +// [VERIFIED] sector=pmns cx=1 Δ=0.018% +fn delta_cp_pmns() -> f64 { + return 9.0 / (PHI * PHI); +} + +// [VERIFIED] sector=pmns cx=1 Δ=0.036% +fn sin2theta12_pmns() -> f64 { + return 4.0 / (pow(PHI, 2.0) * pow(PI, 4.0) * pow(E, 4.0)); +} + +// ============================================================================ +// SECTOR 7 — COSMOLOGY (1 formula) +// ============================================================================ + +// [VERIFIED] sector=cosmology cx=1 Δ=0.00% +fn lambda_exponent() -> f64 { + return 122; +} + +// ============================================================================ +// SECTOR 8 — HIGGS (1 formula) +// ============================================================================ + +// [VERIFIED] sector=higgs cx=1 Δ=0.022% +fn higgs_z_ratio() -> f64 { + return (1.0 / 8.0) * pow(PHI, 2.0) * pow(PI, 3.0) * pow(E, -2.0); +} + +// ============================================================================ +// V07 CHIMERA ADDITIONS (9 new VERIFIED formulas) +// ============================================================================ + +// [VERIFIED] sector=ckm cx=7 Δ=0.017% +fn v_ud_chimera() -> f64 { + return 7.0 * pow(PHI, -5.0) * pow(PI, 3.0) * pow(E, -3.0); +} + +// [VERIFIED] sector=ckm cx=7 Δ=0.080% +fn v_cs_chimera() -> f64 { + return 7.0 * pow(PHI, -5.0) * pow(PI, 3.0) * pow(E, -3.0); +} + +// [VERIFIED] sector=ckm cx=6 Δ=0.037% +fn v_td_chimera() -> f64 { + return 2.0 * pow(PHI, -4.0) * pow(PI, -4.0) * E; +} + +// [VERIFIED] sector=pmns cx=6 Δ=0.098% +fn sin2theta12_chimera() -> f64 { + return 8.0 * pow(PHI, -5.0) * PI * pow(E, -2.0); +} + +// [VERIFIED] sector=pmns cx=2 Δ=0.017% +fn delta_cp_rad() -> f64 { + return 9.0 * pow(PHI, -2.0); +} + +// [VERIFIED] sector=lepton cx=5 Δ=0.078% +fn strange_muon_ratio() -> f64 { + return pow(PHI, -2.0) * pow(PI, -1.0) * pow(E, 2.0); +} + +// [VERIFIED] sector=qcd cx=6 Δ=0.021% +fn bottom_top_ratio() -> f64 { + return 4.0 * pow(PHI, -2.0) * pow(PI, -1.0) * pow(E, -3.0); +} + +// [VERIFIED] sector=cosmology cx=5 Δ=0.041% +fn omega_b_chimera() -> f64 { + return 4.0 * pow(PHI, -2.0) * pow(PI, -3.0); +} + +// [VERIFIED] sector=cosmology cx=6 Δ=0.094% +fn ns_chimera() -> f64 { + return 3.0 * pow(PHI, 3.0) * pow(PI, -4.0) * pow(E, 2.0); +} + test "formula_registry_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/physics/gamma-conflict.t27 b/apps/website/public/t27/specs/physics/gamma-conflict.t27 new file mode 100644 index 0000000000..02643ac33f --- /dev/null +++ b/apps/website/public/t27/specs/physics/gamma-conflict.t27 @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: Apache-2.0 +# Gamma Conflict Conjecture (GI1) + +**Status:** CONJECTURAL +**Tier:** CANDIDATE +**Owner:** physics/trinity +**Issue:** #303 (Gamma Conflict CLI Verification) +**Date:** 2026-04-08 + +## Abstract + +This spec defines Conjecture GI1: the Barbero-Immirzi parameter 0 equals 123 = 45 5 2. The conjecture addresses the "gamma conflict" between Trinity framework and standard LQG values by proposing an algebraically exact candidate that differs from the Meissner (2004) LQG value 67 by only 0.62%. + +**Key correction:** 89 = ln2/(1031112) 13 0.1274 is the *entropy coefficient* in S = 1415A/(416), NOT the Immirzi parameter itself. 1718 19 0.2375 is the Immirzi parameter. + +--- + +## Conjecture Statement + +**Conjecture GI1:** 20_true = 212223 = 245 25 2 + +Where: +- 26 = (1 + 275)/2 is the golden ratio (L5 identity: 2829 + 303132 = 3) +- 33 is the Barbero-Immirzi parameter of Loop Quantum Gravity +- 3435 = ln2/(36373) 38 0.237532958... (Meissner 2004, numerical) +- 3940 41 0.273985635... (Ghosh-Mitra 2004, alternative LQG) + +**Gap analysis:** +- 42(4344 - 45_46)/4748 = 0.6167% (2249 smaller than internal LQG dispute) +- 50(5152 - 5354)/5556 = 13.9% (internal LQG dispute) + +--- + +## Theoretical Constraints + +### Domagala-Lewandowski Bounds + +The Barbero-Immirzi parameter must satisfy: + +``` +ln(2)/57 < 58 < ln(3)/59 +``` + +**Values:** +- Lower bound: ln(2)/60 61 0.220635600... +- Upper bound: ln(3)/62 63 0.349699152... + +**Verification for 64_65:** +- 66_67 = 0.236067977... +- ln(2)/68 < 69_70 < ln(3)/71 72 + +**Verification for 7374:** +- 7576 = 0.237532958... +- ln(2)/77 < 7879 < ln(3)/80 81 + +**Both candidates satisfy DL bounds.** + +### Minimum Area Eigenvalue + +In LQG, the minimum area eigenvalue is: + +``` +A_min = 8828384_P85 +``` + +**Values:** +- With 86_87: A_min = 288893(905912)92_P93 94 2.569195_P96 +- With 9798: A_min = 899(ln2/(1001013))102_P103 104 2.5850105_P106 + +--- + +## Cascading Implications + +### Formula G1: Newton's Gravitational Constant + +**Specification:** +``` +G = 107108109110/111 112 G_Pl +``` + +**With 113_114 = 115116117:** +``` +G = 118119120121122/123 = 124125126127128 +``` +The 129 parameter is eliminated entirely. + +**Predictions:** +- With 130_131: G/G_Pl = 1.0679... (0.0679% deviation from G_Pl baseline) +- With 132133: G/G_Pl = 1.0812... (0.0812% deviation) + +**Status:** 134_135 gives 3.4136 better fit to CODATA 2022 than 137138. + +### Formula BH1: Black Hole Entropy + +**Specification:** +``` +S = A/(4139) + O(140141) +``` + +**Relative shift:** +``` +142S/S = 2143144145/146 +``` + +**Predictions:** +- 147148 149 150_151: 152S/S = 2 153 0.6167% = 1.233% + +**Status:** Currently below observational sensitivity for stellar-mass black holes. + +### Formula BH2: Hawking Temperature + +**Specification:** +``` +T_H = T_H^(Hawking) 154 [1 - 155156157158/6 + O(159160)] +``` + +**Predictions:** +- With 161_162: Correction = 1639.1669% +- With 164165: Correction = 1669.2810% +- Difference: 0.1141% + +**Status:** Below any conceivable measurement at current technology. + +### Formulas SC3-SC4: Superconductivity + +**SC3 (BCS gap ratio):** +``` +2167/(k_B T_c) = 4168 exp(1691/170) +``` +With 171_172: 4173 exp(1741/175176177) 178 3.528 (BCS: 3.528, exact match) + +**SC4 (Debye model):** +``` +T_c(max) = 179180181_D/(2182k_B) +``` + +**Status:** Critical temperature predictions shift by 0.62%, within experimental 1831 K (1%) precision. + +--- + +## Test Suite + +### test: gamma_candidates_comparison + +Compare 184_185, 186187, and 188189 across four 190-dependent formulas. + +**Formulae tested:** +- G1: Newton's G = 191192193194/195 +- BH1: Entropy shift = 2196197198/199 +- BH2: Temperature correction = 200201202203204/6 +- SC3: BCS gap = 4205 exp(2061/207) + +**Expected results:** +1. G1 with 208_209: 0.0679% deviation from G_Pl +2. G1 with 210211: 0.0812% deviation from G_Pl +3. 212_213 within DL bounds: True +4. 214215 within DL bounds: True +5. 216(217218 - 219_220)/221222 = 0.6167% + +### test: dl_bounds_containment + +Verify that 223_224 and 225226 both lie within Domagala-Lewandowski bounds. + +**Expected results:** +1. ln(2)/227 228 0.2206 +2. ln(3)/229 230 0.3497 +3. 0.2206 < 231_232 < 0.3497 +4. 0.2206 < 233234 < 0.3497 + +### invariant: exact_closed_form + +235_236 has an exact closed form; 237238 is transcendental. + +**Statement:** +``` +239_240 = 2415 242 2 243 Q(2445) (algebraic field) +245246 = ln2/(2472483) 249 Q(2505) (transcendental) +``` + +**Expected result:** Verify that 251_252 can be expressed as a polynomial in 253 with rational coefficients, while 254255 cannot. + +--- + +## Benchmarks + +### bench: precision_comparison + +Compute 50-digit precision values for comparison. + +**Targets:** +1. 256 = (1+2575)/2 (50 digits) +2. 258_259 = 260261262 (50 digits) +3. 263264 = ln2/(2652663) (50 digits) +4. 267(268269 - 270_271)/272273 (6 significant figures) + +### bench: gap_ratio + +Compare 274_275 proximity to 276277 vs 278279 proximity to 280281. + +**Target:** +``` +ratio = 282(283284 - 285286) / 287(288289 - 290_291) +``` + +**Expected value:** ratio 292 22.5 + +--- + +## Formal Verification Links + +| Coq Proof | File | Purpose | +|-----------|------|---------| +| L5 Identity | `proofs/sacred/l5_identity.v` | Prove 293294 + 295296297 = 3 | +| 298 = 299300301 | `proofs/sacred/gamma_phi3.v` | Prove 302_303 = 3045 305 2 | +| DL Bounds | `proofs/gravity/dl_bounds.v` | Prove 306_307 within [ln2/308, ln3/309] | + +--- + +## Falsification Criteria + +1. **DL bounds violation:** 310_311 falls outside [ln2/312, ln3/313] + - Current status: NOT VIOLATED + +2. **LQG exclusion:** Rigorous LQG state counting proves 314 315 316317318 + - Current status: OPEN + +3. **High-precision discrimination:** EHT or LIGO resolves 319 to < 0.5% and excludes 320_321 + - Current EHT precision: ~1.5% + - Required: ngEHT 2027+ with < 0.6% precision + +4. **Cascade contradiction:** 322-dependent formulas perform worse with 323_324 than 325326 + - Current status: NOT OBSERVED (G1 shows 3.4327 better fit with 328_329) + +--- + +## References + +1. Meissner, T. (2004). "Entropy of a large quantum black hole..." +2. Domagala, T., Lewandowski, J. (2004). "Black hole entropy from Loop Quantum Gravity" +3. Ghosh, A., Mitra, P. (2004). "Black hole entropy counting..." +4. research/trinity-pellis-paper/FORMULA_TABLE.md +5. scripts/compare_gamma_candidates.py +6. proofs/sacred/l5_identity.v +7. proofs/sacred/gamma_phi3.v +8. proofs/gravity/dl_bounds.v + +--- + +## Version History + +- v0.2 (2026-04-08): Initial version with corrected 330331 distinction (entropy coefficient vs Immirzi parameter) +- Gap corrected from 13.9% to 0.62% (332333 vs 334_335, not 336337 vs 338339) + test "gamma-conflict_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/physics/gamma_conjecture.t27 b/apps/website/public/t27/specs/physics/gamma_conjecture.t27 new file mode 100644 index 0000000000..19c5491748 --- /dev/null +++ b/apps/website/public/t27/specs/physics/gamma_conjecture.t27 @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/physics/gamma_conjecture.t27 +// Strand I -- Loop Quantum Gravity +// Conjecture GI1: Barbero-Immirzi Parameter from Golden Section +module GammaConjecture { + // Import base constants: PHI, PI + use math::constants; + +// ----------------------------------------------------- +// 1. Conjecture GI1 Definition +// ----------------------------------------------------- +// Claim ID: C-gamma-001 (CONJECTURAL) + + // Trinity gamma conjecture: gamma_phi = phi^{-3} + // Algebraic forms: sqrt(5) - 2, 1/(2*phi + 1) + // Claim: C-gamma-001 (CONJECTURAL), tolerance: CONJECTURAL + const GAMMA_PHI : f64 = pow(PHI, -3.0); + + // LQG standard (Meissner 2004): gamma_1 = ln(2)/(pi*sqrt(3)) + // Reference: Loop Quantum Gravity literature + // Claim: REFERENCE (external LQG), tolerance: N/A + const GAMMA_LQG_STANDARD : f64 = (2.0_f64.ln()) / (PI * 3.0_f64.sqrt()); + + // LQG alternative (Ghosh-Mitra): gamma_2 from black hole entropy fit + // Reference: Ghosh, Mitra (2010s) + // Claim: REFERENCE (external LQG), tolerance: N/A + const GAMMA_LQG_ALT : f64 = 0.27398563520394157868_f64; + + // Delta between gamma_1 and gamma_phi (relative percentage) + const DELTA_GAMMA_1_PHI_PERCENT : f64 = + ((GAMMA_LQG_STANDARD - GAMMA_PHI).abs() / GAMMA_LQG_STANDARD) * 100.0; + + // Delta between gamma_2 and gamma_1 (relative percentage) + const DELTA_GAMMA_2_1_PERCENT : f64 = + ((GAMMA_LQG_ALT - GAMMA_LQG_STANDARD).abs() / GAMMA_LQG_STANDARD) * 100.0; + +// ----------------------------------------------------- +// 2. Formula Definitions (affected by gamma) +// ----------------------------------------------------- +// Claim IDs: C-gamma-002 to C-gamma-006 + + // G1: Newton's constant G = pi^3 * gamma^2 / phi + // Claim: C-gamma-002 (EMPIRICAL_FIT), tolerance: WITHIN_UNCERTAINTY + fn newtons_constant_from_gamma(gamma: f64, pi: f64) -> f64 { + const pi_sq = pi * pi; + const pi_cub = pi_sq * pi; + const gamma_sq = gamma * gamma; + return (pi_cub * gamma_sq) / PHI; + } + + // BH1: Black hole entropy S = gamma * A / pi + // Claim: C-gamma-003 (CONJECTURAL), tolerance: CONJECTURAL + fn black_hole_entropy_from_gamma(gamma: f64, area: f64) -> f64 { + return gamma * area / PI; + } + + // SH1: Black hole shadow angular radius theta = 3*sqrt(3)*gamma*M/r + // Claim: C-gamma-004 (CONJECTURAL), tolerance: CONJECTURAL + fn black_hole_shadow_from_gamma(gamma: f64, mass: f64, radius: f64) -> f64 { + return 3.0 * 3.0_f64.sqrt() * gamma * mass / radius; + } + + // SC3: Superconductivity critical temperature Tc = gamma^2 / pi * scale + // Claim: C-gamma-005 (CONJECTURAL), tolerance: CONJECTURAL + fn superconductor_tc_sc3(gamma: f64, scale: f64) -> f64 { + const gamma_sq = gamma * gamma; + return (gamma_sq / PI) * scale; + } + + // SC4: Superconductivity critical temperature Tc = gamma * pi / phi * scale + // Claim: C-gamma-006 (CONJECTURAL), tolerance: CONJECTURAL + fn superconductor_tc_sc4(gamma: f64, scale: f64) -> f64 { + return (gamma * PI / PHI) * scale; + } + +// ----------------------------------------------------- +// 3. Verification API +// ----------------------------------------------------- + + struct GammaConjectureReport { + gamma_phi_value : f64; + gamma_1_value : f64; + gamma_2_value : f64; + delta_1_phi : f64; // % difference + delta_2_1 : f64; // % difference + + g_pred_gamma_phi : f64; + g_pred_gamma_1 : f64; + + bh_entropy_phi : f64; + bh_entropy_1 : f64; + } + + fn verify_gamma_conjecture(area: f64, mass: f64, radius: f64, scale: f64) -> GammaConjectureReport { + const PI = PI; + const g_phi = newtons_constant_from_gamma(GAMMA_PHI, PI); + const g_1 = newtons_constant_from_gamma(GAMMA_LQG_STANDARD, PI); + const bh_s_phi = black_hole_entropy_from_gamma(GAMMA_PHI, area); + const bh_s_1 = black_hole_entropy_from_gamma(GAMMA_LQG_STANDARD, area); + + return GammaConjectureReport{ + gamma_phi_value = GAMMA_PHI, + gamma_1_value = GAMMA_LQG_STANDARD, + gamma_2_value = GAMMA_LQG_ALT, + delta_1_phi = DELTA_GAMMA_1_PHI_PERCENT, + delta_2_1 = DELTA_GAMMA_2_1_PERCENT, + + g_pred_gamma_phi = g_phi, + g_pred_gamma_1 = g_1, + + bh_entropy_phi = bh_s_phi, + bh_entropy_1 = bh_s_1, + }; + } + + // ======================================================================================================= + // TDD-Inside-Spec: Tests and Invariants for Gamma Conjecture GI1 + // ======================================================================================================= + + test gamma_phi_from_phi_inverse_cubed + // Claim: C-gamma-001 (CONJECTURAL), tolerance: CONJECTURAL + given gamma_expected = pow(PHI, -3.0) + and gamma_actual = GAMMA_PHI + then abs(gamma_expected - gamma_actual) < 1e-15 + + test gamma_phi_equals_sqrt_five_minus_two + // Claim: C-gamma-001 (CONJECTURAL), tolerance: CONJECTURAL + given gamma_phi = GAMMA_PHI + and sqrt_form = 5.0_f64.sqrt() - 2.0 + then abs(gamma_phi - sqrt_form) < 1e-12 + + test gamma_phi_equals_one_over_two_phi_plus_one + // Claim: C-gamma-001 (CONJECTURAL), tolerance: CONJECTURAL + given gamma_phi = GAMMA_PHI + and reciprocal_form = 1.0 / (2.0 * PHI + 1.0) + then abs(gamma_phi - reciprocal_form) < 1e-12 + + test gamma_lqg_standard_value + // Claim: REFERENCE (LQG standard), tolerance: N/A + given gamma_1 = GAMMA_LQG_STANDARD + and ln2_over_pi_sqrt3 = (2.0_f64.ln()) / (PI * 3.0_f64.sqrt()) + then abs(gamma_1 - ln2_over_pi_sqrt3) < 1e-12 + + test gamma_delta_1_phi_percent + // Claim: C-gamma-001 (CONJECTURAL), tolerance: CONJECTURAL + given delta = DELTA_GAMMA_1_PHI_PERCENT + then delta > 0.6 and delta < 0.7 + + test gamma_delta_2_1_percent + // Claim: REFERENCE (LQG internal dispute), tolerance: N/A + given delta = DELTA_GAMMA_2_1_PERCENT + then delta > 13.0 and delta < 14.5 + + test gamma_delta_comparison + // Claim: C-gamma-001 (CONJECTURAL), tolerance: CONJECTURAL + given delta_1_phi = DELTA_GAMMA_1_PHI_PERCENT + and delta_2_1 = DELTA_GAMMA_2_1_PERCENT + then delta_2_1 > delta_1_phi * 10.0 + + test newtons_constant_formula + // Claim: C-gamma-002 (EMPIRICAL_FIT), tolerance: WITHIN_UNCERTAINTY + given area_test = 1e6 + and g_phi = newtons_constant_from_gamma(GAMMA_PHI, PI) + and g_1 = newtons_constant_from_gamma(GAMMA_LQG_STANDARD, PI) + then g_phi > 0.0 and g_1 > 0.0 + + test black_hole_entropy_formula + // Claim: C-gamma-003 (CONJECTURAL), tolerance: CONJECTURAL + given area = 1.0 + and s_phi = black_hole_entropy_from_gamma(GAMMA_PHI, area) + and s_1 = black_hole_entropy_from_gamma(GAMMA_LQG_STANDARD, area) + then s_phi > 0.0 and s_1 > 0.0 + + test black_hole_shadow_formula + // Claim: C-gamma-004 (CONJECTURAL), tolerance: CONJECTURAL + given mass = 1.0 + and radius = 1.0 + and theta_phi = black_hole_shadow_from_gamma(GAMMA_PHI, mass, radius) + and theta_1 = black_hole_shadow_from_gamma(GAMMA_LQG_STANDARD, mass, radius) + then theta_phi > 0.0 and theta_1 > 0.0 + + test superconductor_tc_sc3_formula + // Claim: C-gamma-005 (CONJECTURAL), tolerance: CONJECTURAL + given scale = 1.0 + and tc_phi = superconductor_tc_sc3(GAMMA_PHI, scale) + and tc_1 = superconductor_tc_sc3(GAMMA_LQG_STANDARD, scale) + then tc_phi > 0.0 and tc_1 > 0.0 + + test superconductor_tc_sc4_formula + // Claim: C-gamma-006 (CONJECTURAL), tolerance: CONJECTURAL + given scale = 1.0 + and tc_phi = superconductor_tc_sc4(GAMMA_PHI, scale) + and tc_1 = superconductor_tc_sc4(GAMMA_LQG_STANDARD, scale) + then tc_phi > 0.0 and tc_1 > 0.0 + + test gamma_values_are_positive + // Claim: C-gamma-001 (CONJECTURAL), tolerance: CONJECTURAL + then GAMMA_PHI > 0.0 and GAMMA_LQG_STANDARD > 0.0 and GAMMA_LQG_ALT > 0.0 + + test gamma_values_less_than_one + // Claim: C-gamma-001 (CONJECTURAL), tolerance: CONJECTURAL + then GAMMA_PHI < 1.0 and GAMMA_LQG_STANDARD < 1.0 and GAMMA_LQG_ALT < 1.0 + + test gamma_in_expected_range + // Claim: C-gamma-001 (CONJECTURAL), tolerance: CONJECTURAL + given gamma_phi = GAMMA_PHI + then gamma_phi > 0.2 and gamma_phi < 0.3 + + test verify_gamma_conjecture_report + // Claim: C-gamma-001 (CONJECTURAL), tolerance: CONJECTURAL + given report = verify_gamma_conjecture(1e6, 1e30, 1e16, 100.0) + then report.gamma_phi_value > 0.0 + and report.gamma_1_value > 0.0 + and report.delta_1_phi > 0.6 + and report.delta_2_1 > 13.0 + + invariant gamma_phi_equals_phi_inverse_cubed + // Claim: C-gamma-001 (CONJECTURAL), tolerance: CONJECTURAL + assert abs(GAMMA_PHI - pow(PHI, -3.0)) < 1e-15 + + invariant gamma_phi_less_than_gamma_lqg_alt + // Claim: C-gamma-001 (CONJECTURAL), tolerance: CONJECTURAL + assert GAMMA_PHI < GAMMA_LQG_ALT + + invariant delta_gamma_2_1_greater_than_delta_1_phi + // Claim: C-gamma-001 (CONJECTURAL), tolerance: CONJECTURAL + assert DELTA_GAMMA_2_1_PERCENT > DELTA_GAMMA_1_PHI_PERCENT * 10.0 + + invariant gamma_phi_structurally_simple + // Claim: C-gamma-001 (CONJECTURAL), tolerance: CONJECTURAL + // gamma_phi = sqrt5 - 2 uses only sqrt5 (from phi) and integer 2 + // gamma_1 = ln(2)/(pisqrt3) uses ln(2), pi, and sqrt3 + given sqrt5_form = 5.0_f64.sqrt() - 2.0 + and gamma_phi = GAMMA_PHI + then abs(gamma_phi - sqrt5_form) < 1e-12 + + invariant newtons_constant_positive + // Claim: C-gamma-002 (EMPIRICAL_FIT), tolerance: WITHIN_UNCERTAINTY + assert newtons_constant_from_gamma(GAMMA_PHI, PI) > 0.0 + + invariant black_hole_entropy_positive + // Claim: C-gamma-003 (CONJECTURAL), tolerance: CONJECTURAL + given area = 1.0 + assert black_hole_entropy_from_gamma(GAMMA_PHI, area) > 0.0 + + invariant black_hole_shadow_positive + // Claim: C-gamma-004 (CONJECTURAL), tolerance: CONJECTURAL + given mass = 1.0 and radius = 1.0 + assert black_hole_shadow_from_gamma(GAMMA_PHI, mass, radius) > 0.0 + + invariant superconductor_tc_sc3_positive + // Claim: C-gamma-005 (CONJECTURAL), tolerance: CONJECTURAL + given scale = 1.0 + assert superconductor_tc_sc3(GAMMA_PHI, scale) > 0.0 + + invariant superconductor_tc_sc4_positive + // Claim: C-gamma-006 (CONJECTURAL), tolerance: CONJECTURAL + given scale = 1.0 + assert superconductor_tc_sc4(GAMMA_PHI, scale) > 0.0 + + bench gamma_conjecture_verification_time + measure: nanoseconds to compute verify_gamma_conjecture(1e6, 1e30, 1e16, 100.0) + target: < 2000ns + + bench newtons_constant_computation_time + measure: nanoseconds to compute newtons_constant_from_gamma(GAMMA_PHI, PI) + target: < 500ns + + bench black_hole_entropy_computation_time + measure: nanoseconds to compute black_hole_entropy_from_gamma(GAMMA_PHI, 1e6) + target: < 200ns + + bench black_hole_shadow_computation_time + measure: nanoseconds to compute black_hole_shadow_from_gamma(GAMMA_PHI, 1e30, 1e16) + target: < 300ns +} diff --git a/apps/website/public/t27/specs/physics/gi1_analysis.t27 b/apps/website/public/t27/specs/physics/gi1_analysis.t27 new file mode 100644 index 0000000000..a747f18840 --- /dev/null +++ b/apps/website/public/t27/specs/physics/gi1_analysis.t27 @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/physics/gi1_analysis.t27 +// GI1 Pre-Registration Analysis: γ_φ vs γ₁ comparison +// Three hypotheses tested against empirical data + +module GI1Analysis { + // Import base constants: PHI, PI from math::constants + use math::constants; + +// ───────────────────────────────────────────────────── +// 1. Hypotheses Definitions (pre-registered) +// ───────────────────────────────────────────────────── + + // H-A: γ_φ has superior structural simplicity + // γ_φ = √5 − 2 uses complexity = 3 (√5, integer 2) + // γ₁ = ln(2)/(π√3) uses complexity > 3 (transcendentals) + const STRUCTURAL_COMPLEXITY_PHI : f64 = 3.0; + const STRUCTURAL_COMPLEXITY_1 : f64 = 5.0; + + // H-B: γ_φ is numerically proximate to γ₁ + // Claim: Δ(γ₁ − γ_φ) / γ₁ < 1% + + // γ_φ = φ⁻³ = √5 − 2 (Trinity conjecture) + const GAMMA_PHI : f64 = pow(PHI, -3.0); + + // γ₁ = ln(2)/(π√3) (LQG standard, Meissner 2004) + const GAMMA_LQG_STANDARD : f64 = (2.0_f64.ln()) / (PI * 3.0_f64.sqrt()); + + // Delta between gamma values + const DELTA_GAMMA_1_PHI_PERCENT : f64 = + ((GAMMA_LQG_STANDARD - GAMMA_PHI).abs() / GAMMA_LQG_STANDARD) * 100.0; + + // LQC: Real falsifiable test (LiteBIRD n_T/r measurement) + // Measured value ~2032 (integer) vs LQG predictions + const LITEBIRD_NT_DIV_R_MEASURED : f64 = 2032.0; + +// ───────────────────────────────────────────────────── +// 2. Test Functions +// ───────────────────────────────────────────────────── + + fn verify_structural_simplicity() -> bool { + // H-A: γ_φ has complexity 3, γ₁ has complexity 5 + return STRUCTURAL_COMPLEXITY_PHI < STRUCTURAL_COMPLEXITY_1; + } + + fn verify_numerical_proximity() -> bool { + // H-B: Δ(γ₁ − γ_φ) / γ₁ < 1% + return DELTA_GAMMA_1_PHI_PERCENT < 1.0; + } + + fn verify_gamma_uniqueness() -> bool { + // H-C: γ_φ is unique complexity-3 expression + // in Trinity basis that satisfies Domagala-Lewandowski bounds + const DL_LOWER_BOUND : f64 = 0.220636; + const DL_UPPER_BOUND : f64 = 0.349699; + return STRUCTURAL_COMPLEXITY_PHI == 3.0; + } + + fn verify_gamma_in_dl_bounds() -> bool { + // Verify γ_φ lies within Domagala-Lewandowski bounds + return GAMMA_PHI > DL_LOWER_BOUND and GAMMA_PHI < DL_UPPER_BOUND; + } + + fn compute_litebird_prediction(gamma: f64) -> f64 { + // LQC: LiteBIRD n_T/r ratio predicted by LQG + // n_T/r = 8πγ (standard LQG area spectrum) + return 8.0 * PI * gamma; + } + +// ───────────────────────────────────────────────────── +// 3. Analysis Report Structure +// ───────────────────────────────────────────────────── + + struct GI1Report { + // Hypothesis test results + ha_structural_simplicity : bool, // H-A passed? + hb_numerical_proximity : bool, // H-B passed? + hc_gamma_uniqueness : bool, // H-C passed? + hc_gamma_in_dl_bounds : bool, // γ_φ in DL bounds + lqc_litebird_falsifiable : bool, // LQC passed? + + // Gamma values + gamma_phi_value : f64, + gamma_1_value : f64, + delta_percent : f64, + + // LQC test + litebird_phi : f64, + litebird_1 : f64, + litebird_measured : f64, + litebird_better_gamma : bool, + } + + fn verify_all_hypotheses() -> GI1Report { + const ha_passed = verify_structural_simplicity(); + const hb_passed = verify_numerical_proximity(); + const hc_passed = verify_gamma_uniqueness(); + const hc_dl_passed = verify_gamma_in_dl_bounds(); + + // LQC test: compute predictions + const ntr_phi = compute_litebird_prediction(GAMMA_PHI); + const ntr_1 = compute_litebird_prediction(GAMMA_LQG_STANDARD); + + // Compare to measured value + const diff_phi = (ntr_phi - LITEBIRD_NT_DIV_R_MEASURED).abs() / LITEBIRD_NT_DIV_R_MEASURED * 100.0; + const diff_1 = (ntr_1 - LITEBIRD_NT_DIV_R_MEASURED).abs() / LITEBIRD_NT_DIV_R_MEASURED * 100.0; + + return GI1Report{ + ha_structural_simplicity = ha_passed, + hb_numerical_proximity = hb_passed, + hc_gamma_uniqueness = hc_passed, + hc_gamma_in_dl_bounds = hc_dl_passed, + lqc_litebird_falsifiable = diff_phi < diff_1, + + gamma_phi_value = GAMMA_PHI, + gamma_1_value = GAMMA_LQG_STANDARD, + delta_percent = DELTA_GAMMA_1_PHI_PERCENT, + + litebird_phi = ntr_phi, + litebird_1 = ntr_1, + litebird_measured = LITEBIRD_NT_DIV_R_MEASURED, + litebird_better_gamma = diff_phi < diff_1, + }; + } + + // ═══════════════════════════════════════════════════════════════════════════ + // TDD-Inside-Spec: Tests and Invariants for GI1 Analysis + // ═══════════════════════════════════════════════════════════════════════════════════════ + + test ha_gamma_phi_complexity_is_3 + given complexity = STRUCTURAL_COMPLEXITY_PHI + then complexity == 3.0 + + test ha_gamma_1_complexity_greater_than_phi + given complexity_phi = STRUCTURAL_COMPLEXITY_PHI + and complexity_1 = STRUCTURAL_COMPLEXITY_1 + then complexity_1 > complexity_phi + + test ha_structural_simplicity_passes + given result = verify_structural_simplicity() + then result == true + + test hb_delta_gamma_1_phi_less_than_1_percent + given delta = DELTA_GAMMA_1_PHI_PERCENT + then delta > 0.0 and delta < 1.0 + + test hb_numerical_proximity_passes + given result = verify_numerical_proximity() + then result == true + + test hc_gamma_phi_has_complexity_3 + given complexity = STRUCTURAL_COMPLEXITY_PHI + then complexity == 3.0 + + test hc_gamma_uniqueness_passes + given result = verify_gamma_uniqueness() + then result == true + + test hc_gamma_phi_in_dl_bounds + given result = verify_gamma_in_dl_bounds() + then result == true + + test litebird_prediction_positive + given pred = compute_litebird_prediction(GAMMA_PHI) + then pred > 0.0 + + test litebird_prediction_increases_with_gamma + given pred_phi = compute_litebird_prediction(GAMMA_PHI) + and pred_1 = compute_litebird_prediction(GAMMA_LQG_STANDARD) + then pred_1 > pred_phi + + test gi1_report_hypothesis_results + given report = verify_all_hypotheses() + then report.ha_structural_simplicity == true + and report.hb_numerical_proximity == true + and report.hc_gamma_uniqueness == true + + test gi1_report_gamma_values_valid + given report = verify_all_hypotheses() + then report.gamma_phi_value > 0.0 + and report.gamma_1_value > 0.0 + and report.delta_percent < 1.0 + + test gi1_report_litebird_values_valid + given report = verify_all_hypotheses() + then report.litebird_phi > 0.0 + and report.litebird_1 > 0.0 + and report.litebird_measured > 0.0 + + invariant gamma_phi_less_than_gamma_1 + assert GAMMA_PHI < GAMMA_LQG_STANDARD + + invariant delta_gamma_1_phi_within_1_percent + assert DELTA_GAMMA_1_PHI_PERCENT < 1.0 + + invariant structural_complexity_ordering + assert STRUCTURAL_COMPLEXITY_PHI < STRUCTURAL_COMPLEXITY_1 + + invariant gamma_phi_in_domagala_lewandowski_bounds + assert GAMMA_PHI > 0.220636 and GAMMA_PHI < 0.349699 + + invariant gamma_uniqueness_complexity_3 + assert STRUCTURAL_COMPLEXITY_PHI == 3.0 + + invariant litebird_prediction_monotonic_gamma + assert compute_litebird_prediction(GAMMA_LQG_STANDARD) > compute_litebird_prediction(GAMMA_PHI) + + invariant ha_all_hypotheses_supported + given report = verify_all_hypotheses() + assert report.ha_structural_simplicity == true + and report.hb_numerical_proximity == true + and report.hc_gamma_uniqueness == true + + bench gi1_analysis_computation_time + measure: nanoseconds to compute verify_all_hypotheses() + target: < 2000ns + + bench structural_simplicity_check_time + measure: nanoseconds to compute verify_structural_simplicity() + target: < 500ns + + bench numerical_proximity_check_time + measure: nanoseconds to compute verify_numerical_proximity() + target: < 500ns + + bench gamma_uniqueness_check_time + measure: nanoseconds to compute gamma_uniqueness() + target: < 500ns + + bench dl_bounds_check_time + measure: nanoseconds to compute verify_gamma_in_dl_bounds() + target: < 500ns + + bench litebird_prediction_time + measure: nanoseconds to compute litebird_prediction(GAMMA_PHI) + target: < 300ns +} diff --git a/apps/website/public/t27/specs/physics/hslm_benchmark.t27 b/apps/website/public/t27/specs/physics/hslm_benchmark.t27 new file mode 100644 index 0000000000..a6d414a1a9 --- /dev/null +++ b/apps/website/public/t27/specs/physics/hslm_benchmark.t27 @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 +# HSLM BENCHMARK -- Platform Benchmark Suite + +## Specification + +Standalone executable for arXiv paper Evaluation section. +Measures: single-thread, multi-thread inference, ternary matmul, platform comparison. + +NOTE: model.zig uses 3 TrinityBlocks, FPGA uses 4 TrinityBlocks. +Table notes configuration explicitly for paper honesty. + +## Configuration + +``` +VOCAB_SIZE = 8192 +EMBED_DIM = 512 +HIDDEN_DIM = 2048 +NUM_BLOCKS = 3 +ESTIMATED_PARAMS ~= 1.58M ternary parameters +CONTEXT_LEN = 512 +WARMUP_ITERS = 50 +BENCH_ITERS = 1000 +MATMUL_ITERS = 100_000 +``` + +## Benchmark Sections + +### Part 1: Single-thread forward pass +- Initializes HSLM model with 3 TrinityBlocks +- Warmup with 50 iterations +- Benchmark 1000 iterations +- Measures: min, avg, max latency, throughput + +### Part 2: Multi-thread inference +- Up to 8 threads (CPU count cap) +- Parallel inference across threads +- Measures: wall time, effective throughput, avg latency + +### Part 3: Ternary MatVec bandwidth +- Scalar vs SIMD comparison +- Matrix: EMBED_DIM * HIDDEN_DIM (512 * 2048) +- Measures: ns/op, GOPS, speedup + +### Part 4: Memory usage +- Ternary (1.58 bit/param) vs Float32 equivalent +- Compression ratio calculation +- Parameter count + +### Part 5: Platform comparison table + +| Platform | Latency | Throughput | Power | Cost | +|----------|----------|------------|-------|------| +| M1 Pro (1-thread)* | ~X ms | ~Y tok/s | 15W | $0/hr | +| M1 Pro (N-thread)* | ~X ms | ~Y tok/s | 20W | $0/hr | +| FPGA Artix-7** | 28.50 ms | 35 tok/s | 0.5W | $0/hr | +| Railway CPU (est)*** | ~2X ms | ~Y/2 tok/s | ?W | $0.02/hr | + +## Key Functions + +``` +benchSingleThread(allocator, writer) -> LatencyStats + Single-thread forward pass benchmark + +benchMultiThread(allocator, writer) -> LatencyStats + Multi-thread inference benchmark + +benchMatmul(writer) -> void + Ternary matmul bandwidth benchmark + +printMemory(writer) -> void + Memory usage and compression ratio + +printPlatformTable(writer, single, multi) -> void + Platform comparison table for arXiv paper +``` + +## Tests + +``` +test "HSLM-Bench: parameter estimation" { + expect(ESTIMATED_PARAMS ~= 1.58M) +} + +test "HSLM-Bench: configuration consistency" { + expect(NUM_BLOCKS == 3) + expect(EMBED_DIM == 512) + expect(HIDDEN_DIM == 2048) +} +``` diff --git a/apps/website/public/t27/specs/physics/lqg_cs_bridge.t27 b/apps/website/public/t27/specs/physics/lqg_cs_bridge.t27 new file mode 100644 index 0000000000..9c7c271ff7 --- /dev/null +++ b/apps/website/public/t27/specs/physics/lqg_cs_bridge.t27 @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 +# KEPLER->NEWTON Implementation Summary + +**Date**: 2026-04-06 +**Branch**: ring-71-philoop-clean +**Session**: Week 2 Research - LQG Entropy (COMPLETE) + +--- + +## Executive Summary + +Week 1 deliverables completed. Week 2 research task is now COMPLETE. + +**Week 1 Status**: ok COMPLETE +- `specs/physics/su2_chern_simons.t27` -- SU(2)_3 Chern-Simons formalism +- `specs/math/e8_lie_algebra.t27` -- E_8 Lie algebra wrapper +- `specs/physics/lqg_entropy.t27` -- LQG entropy research (updated) +- `docs/KEPLER-NEWTON-CHERN-SIMONS.md` -- Theory documentation +- `conformance/kepler_newton_tests.py` -- 152 formula verification + +**Test Results**: 12/16 tests passing (75.0%) + +--- + +## Week 2: LQG Entropy Research (COMPLETE) + +### Research Question + +**Does SU(2)_3 Chern-Simons entropy produce gamma = phi^-^3?** + +### Honest Assessment + +**CONCLUSION**: gamma = phi^-^3 does NOT come from Chern-Simons theory. + +### New Deliverable Created + +**`specs/physics/lqg_cs_bridge.t27`** -- Comprehensive LQG-CS bridge research + +This new spec provides: + +#### 1. Theoretical Framework Comparison +- CS theory: 2+1D TQFT, topological, Wilson loops, no local degrees of freedom +- LQG: 3+1D canonical quantization, area operator, infinite degrees of freedom + +#### 2. Three Fundamental Incompatibilities + +| Barrier | Description | +|---------|-------------| +| Dimensional | 2D CS theory <-> 3D LQG geometry - no canonical mapping | +| Parametric | gamma has no role in CS (appears only as d_tau = phi), fundamental in LQG | +| Formula | CS entropy logarithmic in A, LQG entropy linear in A | + +#### 3. Three Hypothetical Bridge Pathways + +**Pathway A**: CS Effective Action -> Wilson Loop Effective Action +- Status: No derivation exists +- Barriers: CS is topological (metric-independent), Wilson loop action would be metric-dependent + +**Pathway B**: Wilson Loop -> LQG Area Operator with CS Corrections +- Status: No calculation exists +- Barriers: CS Wilson loops are 1D in 2D, LQG area is 3D surface operator + +**Pathway C**: Area Spectrum from CS-Corrected LQG -> gamma +- Status: No result exists +- Barriers: No known CS correction form 2728(29), ln(30) too small to fix 31 mismatch + +#### 4. Honest Conclusion + +gamma = phi^-^3 does NOT emerge from Chern-Simons theory. The connection, if any, would require three novel theoretical steps that face fundamental obstacles and are not established in literature. + +#### 5. Alternative Research Directions + +- **3D Generalization of Chern-Simons**: Find 3+1D TQFT where phi is fundamental +- **Group Theoretical Bridge**: Relate SU(2)_3 to E_8 preserving phi (but E_8 doesn't justify gamma = phi^-^3) +- **Alternative LQG Formulation**: Modify area operator to include ln(phi) term (ad hoc, not derived) + +--- + +## Test Status + +### Conformance Tests + +**Overall**: 12/16 tests passing (75.0%) + +**Passed**: +- CS Category: 4/5 (80.0%) +- Sacred Category: 2/5 (40.0%) +- E8 Category: 3/3 (100.0%) +- Catalog Category: 3/3 (100.0%) + +**Known Issues** (not blocking): +- Jones polynomial (trefoil): Error 2.36e-01 +- Barbero-Immirzi from phi: Error 2.10e-13 +- Sacred gravity constant: Error 8.40e+10 (G/G_measured ratio issue) +- Sacred dark energy: Error 6.84e-01 (formula interpretation issue) + +--- + +## Files Created/Modified in This Session + +1. `specs/physics/lqg_cs_bridge.t27` -- NEW: LQG-CS bridge research + - Theoretical framework comparison (CS vs LQG) + - Three fundamental incompatibilities documented + - Three hypothetical bridge pathways analyzed with challenges + - Honest conclusion: gamma = phi^-^3 NOT from CS + - Alternative research directions proposed + - Full bibliography included + +--- + +## Status Summary + +**Week 1**: ok COMPLETE -- Chern-Simons foundation, E_8 wrapper, documentation +**Week 2**: ok COMPLETE -- LQG entropy research with comprehensive bridge analysis + +**Next Steps**: Week 3 tasks (E_8 integration, verification, synthesis) + +// ============================================================================ +// TDD-Inside-Spec: Tests and Invariants +// ============================================================================ + +// This is a research documentation spec for the LQG-CS bridge. +// Tests verify that the research documentation is properly structured. + +test lqg_cs_bridge_module_exists + // Verify this module is accessible for research documentation + then true + +invariant lqg_cs_bridge_research_complete + // Week 2 research is COMPLETE with comprehensive analysis + assert true + +test lqg_cs_gamma_conclusion_documented + // Honest conclusion: gamma = phi^-^3 does NOT emerge from Chern-Simons + then true + +invariant three_incompatibilities_documented + // Dimensional, Parametric, and Formula incompatibilities + assert true + diff --git a/apps/website/public/t27/specs/physics/lqg_entropy.t27 b/apps/website/public/t27/specs/physics/lqg_entropy.t27 new file mode 100644 index 0000000000..238e8780eb --- /dev/null +++ b/apps/website/public/t27/specs/physics/lqg_entropy.t27 @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/physics/lqg_entropy.t27 +// KEPLER->NEWTON Direction B: LQG -> gamma (PRIORITY 3 - HONEST INQUIRY) +// Status: Final v2.2 +// Date: 2026-04-05 +// +// HONEST ASSESSMENT: gamma = phi^-^3 does NOT come from CS theory. +// This spec documents research needed to find: +// 1. Does SU(2) Chern-Simons entropy produce gamma = phi^-^3? +// 2. If not, what alternative gamma emerges from theory? +// +// References: +// - Meissner 2004: Black hole area gap and Immirzi parameter +// - Rovelli 2015: LQG entropy review +// - Perez 2017: LQG black hole spectroscopy +// +// HONEST CONCLUSION: The relationship between SU(2)_3 Chern-Simons and LQG Immirzi gamma +// is NOT established in the literature. Both theories treat gamma differently: +// +// CS theory: gamma emerges from quantum dimension d = phi via topological +// invariants (quantum dimension appears in CS entropy). +// LQG theory: gamma = Barbero-Immirzi parameter, fixed from area +// spectrum quantization (Meissner gap formula). +// +// These are DIFFERENT origins for gamma: +// - CS gamma: Property of anyons, emerges from topological structure +// - LQG gamma: Quantization parameter from LQG area operator +// +// UNRESOLVED: How does phi = d_tau in CS theory relate to gamma in LQG? +// This is a FUNDAMENTAL GAP in theoretical foundation. +// +// RESEARCH PATHWAY: To establish gamma = phi^-^3 would require: +// 1. CS effective action -> Wilson loop effective action +// 2. Wilson loop -> LQG area operator with CS corrections +// 3. Area spectrum from CS-corrected LQG -> gamma that equals phi^-^3 +// +// This is NOT found in published papers and would be NOVEL RESEARCH. + +// ============================================================================ +// TDD-Inside-Spec: Tests and Invariants +// ============================================================================ + +// This is a research documentation spec documenting the relationship +// between SU(2) Chern-Simons theory and LQG entropy. Since this is +// documentation rather than executable code, the tests verify +// that the research claims are properly documented. + +test lqg_entropy_module_exists + // Verify this module is accessible for research documentation + then true + +invariant lqg_entropy_research_documented + // This spec documents that gamma = phi^-^3 does NOT come from CS theory + // and identifies the research gap between CS and LQG theories + assert true + +test lqg_cs_gamma_origin_different_from_lqg + // Verify documentation: CS gamma emerges from quantum dimension d = phi + // LQG gamma = Barbero-Immirzi parameter from area spectrum quantization + then true + +invariant lqg_unresolved_gap_documented + // The relationship between CS quantum dimension and LQG Immirzi gamma + // is NOT established in the literature + assert true + diff --git a/apps/website/public/t27/specs/physics/p2_brain_physics.t27 b/apps/website/public/t27/specs/physics/p2_brain_physics.t27 new file mode 100644 index 0000000000..009ff6daf5 --- /dev/null +++ b/apps/website/public/t27/specs/physics/p2_brain_physics.t27 @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: Apache-2.0 +// Module: P2 Brain -- Physics Engine Framework +// phi^2 + 1/phi^2 = 3 | TRINITY + +module P2Brain { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; + use numeric::gf16; + use numeric::formats; + + // ======================================================================== + // 1. Constants + // ======================================================================== + + // Golden Ratio and related constants (from math/phi_ratio.t27) + pub const PHI : gf16 = numeric::phi_ratio::PHI; + pub const PHI_SQ : gf16 = numeric::phi_ratio::PHI_SQ; + pub const PHI_INV : gf16 = numeric::phi_ratio::PHI_INV; + pub const PHI_INV_SQ : gf16 = numeric::phi_ratio::PHI_INV_SQ; + + // Trinity identity: phi^2 + phi^-^2 = 3 + pub const TRINITY_PHI_SQ_INV : gf16 = numeric::phi_ratio::TRINITY_PHI_SQ_INV; + + // Planck constant (from physics/sacred_constants.t27) + pub const PLANCK : gf16 = 1.616227660168e-25; // Reduced speed of light ~ 299792 km/s + + // Fine structure constant + pub const ALPHA_FS : gf16 = 0.00729727022361138; // Fine-structure constant + + // ======================================================================== + // 2. LQG-CS Bridge Types + // ======================================================================== + + // LQGCSymbol: Single symbol in {0, 1, 2, +1, -1, -2} + pub const LQGCSymbol = u8; + + // LQGCState: 8-element state for bridge operations + pub struct LQGCState { + symbols : [LQGCSymbol; 8], + position : u32, // Bit position 0-31 + carry : u8, // Bit 32-64 + overflow : bool, + } + + // ======================================================================== + // 3. LQG-CS Operations + // ======================================================================== + + // stateInit(initialState: LQGCState) -> LQGCState + // Initialize LQG-CS bridge with initial state + fn stateInit(initialState: LQGCState) -> LQGCState; + + // next(state: LQGCState, symbol: LQGCSymbol, action: u8) -> LQGCState + // Compute next state for LQG-CS transition + fn next(state: LQGCState, symbol: LQGCSymbol, action: u8) -> LQGCState; + + // ======================================================================== + // 4. Entropy Operations + // ======================================================================== + + // su2Entropy(h: gf16, n: u8) -> gf16 + // Compute SU(2) Chern entropy from histogram data + fn su2Entropy(h: gf16, n: u8) -> gf16; + + // ======================================================================== + // 5. Simulation Types + // ======================================================================== + + // Simulation: Monte Carlo simulation framework + pub const Simulation = struct { + method : SimulationMethod, // What simulation method to use + samples : u32, // Number of Monte Carlo samples + confidence : gf16, // Statistical confidence (0.0-1.0) + }; + + // SimulationMethod: Enum for simulation approach + pub const SimulationMethod = enum(u8) { + metropolis_hastings, // Metropolis-Hastings algorithm + gibbs_sampling, // Gibbs sampling + quantum_monte_carlo, // Quantum Monte Carlo (theoretical) + classical_monte_carlo, // Classical Monte Carlo + direct_simulation, // Direct numerical integration + }; + + // ======================================================================== + // 6. Tests + // ======================================================================== + + test state_init_creates_valid_state + given state = stateInit(LQGCState{ symbols = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], position = 0, carry = 0, overflow = false}) + then state.symbols[0] == 0 + + test next_updates_state_correctly + given state = stateInit(LQGCState{ symbols = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], position = 0, carry = 0, overflow = false}) + and result = next(state, 0, 0, 0b00000000u8) + then result.symbols[0] == 0b00000000u8 + + test su2_entropy_computes_valid_gamma + given h = gf16::from_f64(1.0) // Half probability + and n = 2 // 2 symbols + when entropy = su2Entropy(h, n) + then gf16::to_f64(entropy) > 0.5 + + // ======================================================================== + // 7. Benchmarks + // ======================================================================== + + bench state_init_latency + // Measure: cycles to initialize LQG-CS state + // Target: < 1000 cycles + @setEvalBranchQuota(10000); + var state = stateInit(LQGCState{ symbols = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], position = 0, carry = 0, overflow = false}); + _ = state; + + bench next_transition_latency + // Measure: cycles for one LQG-CS transition + // Target: < 500 cycles + @setEvalBranchQuota(10000); + var state = stateInit(LQGCState{ symbols = [0, 0, 0, 0, 0, 0, 0, 0, 0], position = 0, carry = 0, overflow = false}); + _ = next(state, 0, 1); + _ = state; + + bench su2_entropy_latency_2_symbols + // Measure: cycles to compute SU(2) entropy for 2 symbols + // Target: < 5000 cycles + @setEvalBranchQuota(10000); + var h = gf16::from_f64(1.0); + _ = su2Entropy(h, 2); + + // ======================================================================== + // 8. Invariants + // ======================================================================== + + invariant trinity_phi_squared_plus_inverse_squared_equals_three + // Verify: PHI_SQ + PHI_INV_SQ equals TRINITY (exactly 3.0 in GF16) + // This is the fundamental Trinity identity + assert numeric::gf16::to_f64(PHI_SQ) + numeric::gf16::to_f64(PHI_INV_SQ) == numeric::gf16::from_f64(3.0); + + invariant lqgc_symbols_cover_all_states + // Verify: LQGCSymbol enum {0, 1, 2, +1, -1, -2} covers 2^8 = 256 states + // Bridge operations must handle all symbol transitions + assert LQGCSymbol::NUM_VALUES == 8; + + invariant entropy_non_negative + // Verify: Entropy is always non-negative + // H >= 0 for all probability distributions + assert true; + + invariant simulation_samples_positive + // Verify: Simulation samples parameter is valid + assert Simulation.samples > 0; + + invariant lqgc_state_valid + // Verify: LQGCSState position is always valid (< 64) + // Position must be in range 0-31 + assert true; + + invariant carry_bit_within_u32 + // Verify: Carry bit is within u32 range + // Carry must be bit 32-64 + assert true; +} diff --git a/apps/website/public/t27/specs/physics/pellis-formulas.t27 b/apps/website/public/t27/specs/physics/pellis-formulas.t27 new file mode 100644 index 0000000000..333ed0a1ab --- /dev/null +++ b/apps/website/public/t27/specs/physics/pellis-formulas.t27 @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/physics/pellis-formulas.t27 +// Trinity x Pellis hybrid -- thin-structure formulas anchored on L5 (issue #277). +// SSOT: invariants tie Pell ladders to phi; observables are references for tri math compare. +// +// Anchor (L5): phi^2 + phi^-2 = 3 | TRINITY + +module PellisFormulas { + use math::constants; + use math::sacred_physics; + + const PHI : f64 = constants::PHI; + const PHI_SQ : f64 = PHI * PHI; + const PHI_INV : f64 = 1.0 / PHI; + const PHI_INV_SQ : f64 = PHI_INV * PHI_INV; + + // L5 numeric sum (must match sacred_physics::TRINITY within tolerance) + const TRINITY_FROM_PHI : f64 = PHI_SQ + PHI_INV_SQ; + + // Reference inverse fine-structure constant (CODATA 2018 class), dimensionless + const ALPHA_INV_REFERENCE : f64 = 137.035999084; + + // Structural phi^5 (not claimed equal to ALPHA_INV_REFERENCE) + const PHI_POW_FIVE : f64 = PHI * PHI * PHI * PHI * PHI; + + // Standard Pell numbers P_1..P_5 (sqrt(2) ladder): 1, 2, 5, 12, 29 + const PELL_P1 : f64 = 1.0; + const PELL_P2 : f64 = 2.0; + const PELL_P3 : f64 = 5.0; + const PELL_P4 : f64 = 12.0; + const PELL_P5 : f64 = 29.0; + + fn trinity_anchor_error() -> f64 { + return abs(TRINITY_FROM_PHI - 3.0); + } + + fn matches_sacred_trinity() -> f64 { + return abs(TRINITY_FROM_PHI - sacred_physics::TRINITY); + } + + test l5_trinity_anchor + then trinity_anchor_error() < 1e-12 + + test trinity_matches_sacred_physics_module + then matches_sacred_trinity() < 1e-12 + + test phi_pow_five_distinct_from_alpha_inv + then abs(PHI_POW_FIVE - ALPHA_INV_REFERENCE) > 1.0 + + test pell_block_defined + then PELL_P5 == 29.0 + and PELL_P1 == 1.0 + + invariant pell_sequence_increasing_through_p5 + given a = PELL_P1 + and b = PELL_P5 + then b > a + + invariant alpha_inv_reference_positive + then ALPHA_INV_REFERENCE > 100.0 + and ALPHA_INV_REFERENCE < 200.0 + + bench pellis_formula_spec_touch + measure: constant-fold structural checks for Pellis SSOT block + target: < 1ms +} diff --git a/apps/website/public/t27/specs/physics/quantum.t27 b/apps/website/public/t27/specs/physics/quantum.t27 new file mode 100644 index 0000000000..fc3c6915cd --- /dev/null +++ b/apps/website/public/t27/specs/physics/quantum.t27 @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +# TERNARY QUANTUM VM -- CLI Runner + +## Specification + +Ternary Quantum VM implementation using sacred golden ratio constants. +Implements qutrit (3-state quantum bit) operations with sacred phase encoding. + +## Usage + +``` +quantum chsh [--trials N] [--sacred] [--entangled] +quantum cglmp [--trials N] +quantum demo +quantum bench +quantum weights [--entangled] +``` + +## Mathematical Foundation + +``` +phi^2 + 1/phi^2 = 3 = TRINITY +``` + +## Commands + +### chsh -- CHSH Correlation Test +- Runs CHSH-like correlation test on qutrits +- Options: --trials N (default: 10000), --sacred, --entangled +- Measures correlation, classical bound, violation detection + +### cglmp -- CGLMP Inequality Test +- Runs CGLMP inequality test with entangled pairs +- Collins-Gisin-Linden-Massar-Popescu 2002 +- Tests: entangled non-maximally pair vs separable product state + +### demo -- Demonstration +- Shows qutrit gates and measurement operations +- Displays basis states, superposition, entanglement + +### bench -- Benchmark +- Benchmarks gate operations +- Measures per-gate latency and throughput + +### weights -- Weight Generation +- Generates quantum-derived weights for FPGA dot product +- Option: --entangled for entanglement-derived signatures + +## Sacred Phase Encoding + +- Sacred golden angle phase applied to qutrits +- Phase shift: phi-based sacred angle +- Used in entangled pair measurements + +## Tests + +``` +test "Quantum-CHSH: correlation calculation" { + expect(correlation <= 1.0) + expect(correlation >= -1.0) +} + +test "Quantum-CGLMP: entangled vs separable" { + expect(entangled_i3 > 2.0) + expect(separable_i3 <= 2.0) +} + +test "Quantum: TRINITY identity" { + expect(phi^2 + phi^(-2) ~= 3.0) +} +``` diff --git a/apps/website/public/t27/specs/physics/sacred_verification.t27 b/apps/website/public/t27/specs/physics/sacred_verification.t27 new file mode 100644 index 0000000000..a810f623ff --- /dev/null +++ b/apps/website/public/t27/specs/physics/sacred_verification.t27 @@ -0,0 +1,604 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/physics/sacred_verification.t27 +// KEPLER->NEWTON Sacred Formula Verification Spec +// Status: Final v1.0 +// Date: 2026-04-06 +// +// This spec defines the verification framework for [planned] 152 Sacred Formula +// equations (N implemented today). It provides a structured approach to testing which +// formulas work directly, which require scale factors, and which need +// further theoretical development. JSON source: TBD. +// +// References: +// - Conformance framework: conformance/kepler_newton_tests.py +// - Chern-Simons theorems: docs/KEPLER-NEWTON-CHERN-SIMONS.md +// - Verification results: docs/KEPLER-NEWTON-VERIFICATION.md + +module SacredVerification { + // Import base constants + use math::constants; + use math::sacred_physics; + + // =============================================================== + // 1. Sacred Formula Categories + // ================================================================= + + // The [planned] 152 Sacred Formulas can be classified into: + // - EXACT: Mathematically exact identities (phi^2 + phi^-^2 = 3) + // - PHYSICAL: Formulas relating to measured constants (G, Omega_Lambda) + // - DERIVED: Values derived from phi (gamma = phi^-^3) + // - CONJECTURAL: Hypotheses not yet proven + + enum FormulaCategory { + EXACT, + PHYSICAL, + DERIVED, + CONJECTURAL, + } + + struct FormulaDefinition { + id : u16, + name : string, + formula : string, + category : FormulaCategory, + expected : f64, + tolerance : f64, + scale_factor : f64, // multiplier for dimensional analysis + notes : string, + } + + // ============================================================= + // 2. Verification Status + // =============================================================== + + enum VerificationStatus { + PASS, // Within tolerance + FAIL, // Outside tolerance + ADJUSTED, // Requires scale factor + NEEDS_WORK, // Formula incomplete/incorrect + UNKNOWN, // Cannot be evaluated + } + + struct VerificationResult { + formula_id : u16, + expected : f64, + computed : f64, + absolute_error : f64, + relative_error : f64, + status : VerificationStatus, + notes : string, + } + + // ================================================================= + // 3. Core Sacred Formulas (EXACT Category) + // ================================================================= + + fn phi_identity_results() -> [5]FormulaDefinition { + return [ + // TRINITY identity (Chern-Simons level k=3) + FormulaDefinition{ + id = 1, + name = "TRINITY Identity", + formula = "phi^2 + phi^-^2", + category = EXACT, + expected = sacred_physics::TRINITY, + tolerance = 1e-12, + scale_factor = 1.0, + notes = "Chern-Simons level k=3 theorem", + }, + // Golden ratio definition + FormulaDefinition{ + id = 2, + name = "Golden Ratio", + formula = "phi", + category = EXACT, + expected = sacred_physics::PHI, + tolerance = 1e-15, + scale_factor = 1.0, + notes = "Fundamental constant: (1+sqrt5)/2", + }, + // Inverse golden ratio + FormulaDefinition{ + id = 3, + name = "Inverse Golden Ratio", + formula = "phi^-^1", + category = EXACT, + expected = sacred_physics::PHI_INV, + tolerance = 1e-15, + scale_factor = 1.0, + notes = "IIT consciousness threshold", + }, + // phi squared + FormulaDefinition{ + id = 4, + name = "Golden Ratio Squared", + formula = "phi^2", + category = EXACT, + expected = sacred_physics::PHI_SQ, + tolerance = 1e-15, + scale_factor = 1.0, + notes = "Appears in CS level theorem", + }, + // phi inverse squared + FormulaDefinition{ + id = 5, + name = "Inverse Golden Ratio Squared", + formula = "phi^-^2", + category = EXACT, + expected = sacred_physics::PHI_INV_SQ, + tolerance = 1e-15, + scale_factor = 1.0, + notes = "Specious present: 382ms", + }, + ]; + } + + // ================================================================= + // 4. Chern-Simons Formulas (EXACT Category) + // =================================================================== + + fn chern_simons_formulas() -> [5]FormulaDefinition { + return [ + // Quantum dimension formula + FormulaDefinition{ + id = 10, + name = "Fibonacci Anyon Dimension", + formula = "d_tau = sin(3pi/5)/sin(pi/5)", + category = EXACT, + expected = sacred_physics::PHI, + tolerance = 1e-10, + scale_factor = 1.0, + notes = "SU(2)_3 Chern-Simons: d_tau = phi", + }, + // CS level theorem + FormulaDefinition{ + id = 11, + name = "CS Level Theorem", + formula = "k = d_tau^2 + d_tau^-^2", + category = EXACT, + expected = 3.0, + tolerance = 1e-10, + scale_factor = 1.0, + notes = "Chern-Simons level k=3 from phi", + }, + // Jones polynomial magnitude + FormulaDefinition{ + id = 12, + name = "Jones Polynomial (Trefoil)", + formula = "|V(e^{2pii/5})|^2 = 3 - phi^-^1 = phi^2 - gamma", + category = EXACT, + expected = 3.0 - sacred_physics::PHI_INV, // ~= 2.382 + tolerance = 1e-10, + scale_factor = 1.0, + notes = "Connects Jones polynomial to phi and gamma (Barbero-Immirzi)", + }, + // Fibonacci fusion probability + FormulaDefinition{ + id = 13, + name = "Fibonacci Fusion Sum", + formula = "p_1 + p_tau", + category = EXACT, + expected = 1.0, + tolerance = 1e-10, + scale_factor = 1.0, + notes = "tau*tau = 1+tau fusion normalization", + }, + // Braiding phase + FormulaDefinition{ + id = 14, + name = "Fibonacci Braiding Phase", + formula = "R(tau,tau,tau)", + category = EXACT, + expected = 4.0 * constants::PI / 5.0, + tolerance = 1e-15, + scale_factor = 1.0, + notes = "Topological spin: exp(4pii/5)", + }, + ]; + } + + // ===================================================================== + // 5. LQG and gamma Formulas (DERIVED Category) + // ===================================================================== + + fn lqg_gamma_formulas() -> [5]FormulaDefinition { + return [ + // Barbero-Immirzi parameter + FormulaDefinition{ + id = 20, + name = "Barbero-Immirzi Parameter", + formula = "gamma = phi^-^3", + category = DERIVED, + expected = sacred_physics::GAMMA_LQG, + tolerance = 1e-15, + scale_factor = 1.0, + notes = "LQG Immirzi parameter: 13.9% gap to Meissner", + }, + // Area gap formula (Meissner) + FormulaDefinition{ + id = 21, + name = "Area Gap (Meissner)", + formula = "Delta = gamma^2 + sqrt(2gamma^2)", + category = DERIVED, + expected = 0.0857, + tolerance = 1e-3, + scale_factor = 1.0, + notes = "For gamma = phi^-^3: Delta ~= 0.0857", + }, + // Meissner gap (standard) + FormulaDefinition{ + id = 22, + name = "Meissner Gap (gamma=0.274)", + formula = "Delta(gamma_Meissner)", + category = PHYSICAL, + expected = 0.110, + tolerance = 1e-2, + scale_factor = 1.0, + notes = "Standard LQG solution", + }, + // Immirzi ratio to Meissner + FormulaDefinition{ + id = 23, + name = "Immirzi Ratio", + formula = "gamma_phi / gamma_Meissner", + category = DERIVED, + expected = 0.861, + tolerance = 1e-3, + scale_factor = 1.0, + notes = "phi^-^3 / 0.274 ~= 0.861 (13.9% gap)", + }, + ]; + } + + // ===================================================================== + // 6. E_8 Formulas (EXACT Category) + // ===================================================================== + + fn e8_formulas() -> [5]FormulaDefinition { + return [ + // E_8 dimension + FormulaDefinition{ + id = 30, + name = "E_8 Dimension", + formula = "dim(E_8)", + category = EXACT, + expected = 248.0, + tolerance = 0.0, + scale_factor = 1.0, + notes = "Adjoint representation dimension", + }, + // E_8 root count + FormulaDefinition{ + id = 31, + name = "E_8 Root Count", + formula = "roots(E_8)", + category = EXACT, + expected = 240.0, + tolerance = 0.0, + scale_factor = 1.0, + notes = "240 + 8 Cartan = 248", + }, + // Cartan eigenvalue lambda_3 + FormulaDefinition{ + id = 32, + name = "E_8 Cartan lambda_3", + formula = "lambda_3 = 2 - 2cos(pi/5)", + category = EXACT, + expected = sacred_physics::PHI_INV_SQ, + tolerance = 0.01, + scale_factor = 1.0, + notes = "Confirmed: lambda_3 = phi^-^2", + }, + // E_8 -> 2D projection + FormulaDefinition{ + id = 33, + name = "E_8 -> Golden Icosahedron", + formula = "E_8_2D_quasi_projection", + category = EXACT, + expected = 20.0, + tolerance = 0.0, + scale_factor = 1.0, + notes = "Koca 2019: E_8 projects to 2D golden structure", + }, + ]; + } + + // ========================================================================= + // 7. Physical Constants (PHYSICAL Category - AMBIGUOUS) + // ========================================================================= + + fn physical_constants_formulas() -> [5]FormulaDefinition { + return [ + // Sacred gravity constant + FormulaDefinition{ + id = 40, + name = "Sacred Gravity", + formula = "G = pi^3gamma^2/phi (dimensionless)", + category = PHYSICAL, + expected = 1.6e11, + tolerance = 0.2, + scale_factor = 1.0, + notes = "10561057 Off by 84% - needs scale factor", + }, + // Sacred dark energy + FormulaDefinition{ + id = 41, + name = "Sacred Dark Energy", + formula = "Omega_Lambda = gamma^8pi^4/phi^2", + category = PHYSICAL, + expected = sacred_physics::OMEGA_LAMBDA_MEASURED, + tolerance = 0.001, + scale_factor = 1.0, + notes = "10661067 Off by 99.9% - computed 1068 0.0009", + }, + // Hubble constant + FormulaDefinition{ + id = 42, + name = "Hubble Constant", + formula = "H_0 (sacred)", + category = PHYSICAL, + expected = 70.0, + tolerance = 5.0, + scale_factor = 1.0, + notes = "Measured: 70 km/s/Mpc", + }, + // Sacred gravity constant (updated) + FormulaDefinition{ + id = 43, + name = "Sacred Gravity (Calibrated)", + formula = "G_calibrated = G_raw * G_SCALE", + category = PHYSICAL, + expected = sacred_physics::G_MEASURED, + tolerance = 0.01, + scale_factor = 1.0, + notes = "G_raw ~= 1.068, G_SCALE ~= 6.25e-11", + }, + // Sacred dark energy (updated) + FormulaDefinition{ + id = 44, + name = "Sacred Dark Energy (Calibrated)", + formula = "Omega_Lambda_calibrated = Omega_Lambda_raw * OMEGA_COARSE_SCALE", + category = PHYSICAL, + expected = sacred_physics::OMEGA_LAMBDA_MEASURED, + tolerance = 0.01, + scale_factor = 1.0, + notes = "Omega_Lambda_raw ~= 0.000359, OMEGA_COARSE_SCALE ~= 1909", + }, + ]; + } + + // ============================================================================================= + // 8. Scale Factors for Sacred Formulas (Raw -> Calibrated) + // ===================================================================================== + + // Gravitational Constant Scale Factor + const G_RAW: f64 = 1.06791364671254; // pi^3 * gamma^2 / phi (dimensionless sacred value) + const G_SCALE: f64 = 6.24984990176514e-11; // G_measured / G_raw + + // Dark Energy Density Scale Factor + const OMEGA_LAMBDA_RAW: f64 = 0.000358856522493947; // gamma^8 * pi^4 / phi^2 = pi^4 / phi^2^6 + const OMEGA_COARSE_SCALE: f64 = 1908.84; // Omega_Lambda_measured / Omega_Lambda_raw + + // ===================================================================================== + // 9. Verification Functions + // ========================================================================= + + fn verify_formula(formula: FormulaDefinition, computed: f64) -> VerificationResult { + const abs_error = computed - formula.expected; + const abs_err = if abs_error < 0.0 { -abs_error } else { abs_error }; + const rel_error = abs_err / formula.expected; + + let status = VerificationStatus::PASS; + if abs_err > formula.tolerance { + // Check if scale factor would fix it + const scale_factor_needed = formula.expected / computed; + const is_scale_issue = abs(scale_factor_needed - 1.0) < 0.1; + + if is_scale_issue { + status = VerificationStatus::ADJUSTED; + } else { + status = VerificationStatus::FAIL; + } + } + + return VerificationResult{ + formula_id = formula.id, + expected = formula.expected, + computed = computed, + absolute_error : abs_err, + relative_error : rel_error, + status : status, + notes = formula.notes, + }; + } + + struct VerificationReport { + total_formulas : u16, + verified : u16, + passed : u16, + failed : u16, + adjusted : u16, + needs_work : u16, + by_category : [4]u16, // [EXACT, PHYSICAL, DERIVED, CONJECTURAL] + } + + fn generate_verification_report(formulas: []FormulaDefinition) -> VerificationReport { + var verified: u16 = 0; + var passed: u16 = 0; + var failed: u16 = 0; + var adjusted: u16 = 0; + var needs_work: u16 = 0; + var by_category = [0, 0, 0, 0]; + + for formula in formulas { + // In practice, compute from formula string + // For spec purposes, we return expected if trivial + const result = verify_formula(formula, formula.expected); + + verified = verified + 1; + + if result.status == VerificationStatus::PASS { + passed = passed + 1; + by_category[@enumToInt(formula.category)] = + by_category[@enumToInt(formula.category)] + 1; + } else if result.status == VerificationStatus::FAIL { + failed = failed + 1; + } else if result.status == VerificationStatus::ADJUSTED { + adjusted = adjusted + 1; + by_category[@enumToInt(formula.category)] = + by_category[@enumToInt(formula.category)] + 1; + } else { + needs_work = needs_work + 1; + } + } + + return VerificationReport{ + total_formulas : verified, + verified : verified, + passed : passed, + failed : failed, + adjusted : adjusted, + needs_work : needs_work, + by_category : by_category, + }; + } + + // ========================================================================================================= + // TDD-Inside-Spec: Tests and Invariants for Sacred Formula Verification + // =================================================================================================================================== + + test trinity_identity_exact + given formula = phi_identity_results()[0] + when computed = phi_identity_results()[0].expected + and result = verify_formula(formula, computed) + then result.status == VerificationStatus::PASS + + test golden_ratio_exact + given formula = phi_identity_results()[1] + when computed = phi_identity_results()[1].expected + and result = verify_formula(formula, computed) + then result.status == VerificationStatus::PASS + and result.relative_error < 1e-15 + + test cs_quantum_dimension_exact + given formula = chern_simons_formulas()[0] + when computed = chern_simons_formulas()[0].expected + and result = verify_formula(formula, computed) + then result.status == VerificationStatus::PASS + + test gamma_from_phi_exact + given formula = lqg_gamma_formulas()[0] + when computed = formula.expected + and result = verify_formula(formula, computed) + then result.status == VerificationStatus::PASS + and abs(result.absolute_error) < 1e-12 // Adjusted tolerance for constant precision + + test gamma_meissner_gap_derived + given formula = lqg_gamma_formulas()[1] + when computed = formula.expected + and result = verify_formula(formula, computed) + then result.status == VerificationStatus::PASS + and abs(result.relative_error) < 1e-3 + + test e8_cartan_eigenvalue_exact + given formula = e8_formulas()[2] + when computed = e8_formulas()[2].expected + and result = verify_formula(formula, computed) + then result.status == VerificationStatus::PASS + and abs(result.absolute_error) < 0.01 + + test e8_dimension_exact + given formula = e8_formulas()[0] + when computed = formula.expected + and result = verify_formula(formula, computed) + then result.status == VerificationStatus::PASS + + test sacred_gravity_calibrated_passes + // Test: G_calibrated = G_raw * G_SCALE ~= G_measured + given g_raw = G_RAW + and g_scale = G_SCALE + and g_measured = sacred_physics::G_MEASURED + when g_calibrated = g_raw * g_scale + and error = abs(g_calibrated - g_measured) + and rel_error = error / g_measured + then rel_error < 0.01 // 1% tolerance for calibrated pipeline + + test sacred_dark_energy_calibrated_passes + // Test: Omega_Lambda_calibrated = Omega_Lambda_raw * OMEGA_COARSE_SCALE ~= Omega_Lambda_measured + given omega_raw = OMEGA_LAMBDA_RAW + and omega_scale = OMEGA_COARSE_SCALE + and omega_measured = sacred_physics::OMEGA_LAMBDA_MEASURED + when omega_calibrated = omega_raw * omega_scale + and error = abs(omega_calibrated - omega_measured) + and rel_error = error / omega_measured + then rel_error < 0.01 // 1% tolerance for calibrated pipeline + + test jones_polynomial_identity + // Test: |V(e^{2pii/5})|^2 = 3 - phi^-^1 = phi^2 - gamma + given expected = 3.0 - sacred_physics::PHI_INV + and gamma = sacred_physics::GAMMA_LQG + and alt_expected = sacred_physics::PHI_SQ - gamma + when computed = expected // In practice, compute from Jones polynomial formula + then abs(computed - expected) < 1e-10 + and abs(alt_expected - expected) < 1e-15 // Both forms are equivalent + + // Report generation tests + test verification_report_contains_all_categories + given exact = phi_identity_results() + and cs = chern_simons_formulas() + and lqg = lqg_gamma_formulas() + and e8 = e8_formulas() + and phys = physical_constants_formulas() + and report = generate_verification_report(exact ++ cs ++ lqg ++ e8 ++ phys) + then report.by_category[0] > 0 + and report.by_category[1] > 0 + and report.by_category[2] > 0 + and report.by_category[3] > 0 + + test verification_report_pass_rate_reasonable + given report = generate_verification_report(phi_identity_results()) + and total = @len(phi_identity_results()) + when report.passed >= total * 0.5 + then report.passed >= report.verified / 2 + + invariant total_formulas_less_than_or_equal_152 + // The catalog should contain exactly [planned] 152 formulas (N implemented today) + // This invariant checks the framework limit + assert 152 >= 0 + + invariant phi_identity_sum_equals_trinity + given phi_sq = sacred_physics::PHI_SQ + and phi_inv_sq = sacred_physics::PHI_INV_SQ + when sum = phi_sq + phi_inv_sq + then abs(sum - sacred_physics::TRINITY) < 1e-12 + + invariant gamma_from_phi_matches_lqg_value + given gamma_phi = sacred_physics::PHI ** -3.0 + when gamma_lqg = sacred_physics::GAMMA_LQG + then abs(gamma_phi - gamma_lqg) < 1e-15 + + invariant e8_dimension_plus_roots_equals_248 + given dim = 248.0 + and roots = 240.0 + and cartan = 8 + when total = dim + roots - cartan + then abs(total - 248.0) < 1e-10 + + // Tolerance benchmarks for different categories + invariant exact_formula_tolerance_1e12 + assert 1e-12 < 1e-10 + + invariant derived_formula_tolerance_1e3 + assert 1e-3 < 0.01 + + invariant physical_formula_tolerance_10_percent + assert 0.1 < 0.2 + + bench verification_report_generation + measure: nanoseconds to generate_verification_report(152 formulas) + target: < 10ms + + bench formula_evaluation + measure: nanoseconds to verify_formula(test_formula, computed) + target: < 100ns +} diff --git a/apps/website/public/t27/specs/physics/su2_chern_simons.t27 b/apps/website/public/t27/specs/physics/su2_chern_simons.t27 new file mode 100644 index 0000000000..d5c8be0557 --- /dev/null +++ b/apps/website/public/t27/specs/physics/su2_chern_simons.t27 @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/physics/su2_chern_simons.t27 +// SU(2)_k Chern-Simons Theory -- Topological QFT Foundation +// Direction F (Priority 1) of PROJECT KEPLER->NEWTON +// +// This module formalizes the PROVEN THEOREM: golden ratio phi emerges +// from SU(2) Chern-Simons theory at level k=3 as quantum dimension +// of Fibonacci anyons. This is NOT numerology -- it is a mathematical +// consequence of the fusion rule tau x tau = 1 + tau. +// +// Key results: +// 1. d_tau = phi from Fibonacci fusion rule (Kitaev 2006) +// 2. d_1 = phi from Kac-Peterson S-matrix at k=3 (Nayak et al. 2008) +// 3. CS level k = phi^2 + phi^{-2} = 3 (TRINITY identity = CS level) +// 4. Jones [2]_5 = 2cos(pi/5) = phi (Witten 1989) +// 5. Hilbert space dim for n anyons = F_n (Fibonacci numbers) +// +// References: +// - Kitaev, Annals of Physics 321 (2006) 2-111 +// - Nayak et al., Rev. Mod. Phys. 80 (2008) 1083 +// - Freedman, Kitaev, Larsen, Wang, arXiv:quant-ph/0101025 (2003) +// - Witten, Commun. Math. Phys. 121 (1989) 351 +// - Zamolodchikov, Int. J. Mod. Phys. A4 (1989) 4235 +// - Minev et al., IBM/Cornell (2024) -- experimental Fibonacci anyon gates + +module SU2ChernSimons { + use math::constants; + + // ========================================================================= + // 1. Chern-Simons Level and TRINITY Connection + // ========================================================================= + + // SU(2) Chern-Simons level k + // At k=3: the theory contains Fibonacci anyons with quantum dimension phi + // CRITICAL CONNECTION: k = phi^2 + phi^{-2} = 3 (exact) + const CS_LEVEL : i64 = 3; + + // Number of anyon sectors at level k: (k+1) sectors + // At k=3: sectors labeled j = 0, 1/2, 1, 3/2 + const NUM_SECTORS : i64 = CS_LEVEL + 1; // = 4 + + // k+2 = 5 -- this is the cyclotomic index + // All appearances of phi trace to Q(sqrt(5)) and 5th roots of unity + const CYCLOTOMIC_INDEX : i64 = CS_LEVEL + 2; // = 5 + + // ========================================================================= + // 2. Fibonacci Anyon Fusion Rules + // ========================================================================= + + // The Fibonacci category has two objects: vacuum (1) and anyon (tau) + // Fusion rule: tau x tau = 1 + tau + // This single equation forces d_tau = phi + + // Fusion matrix N_tau for the Fibonacci category + // N_tau = [[0, 1], [1, 1]] + // Rows/cols: index 0 = vacuum (1), index 1 = tau + struct FusionMatrix { + entries: [[i64; 2]; 2]; + } + + fn fibonacci_fusion_matrix() -> FusionMatrix { + return FusionMatrix{ + entries = [[0, 1], [1, 1]], + }; + } + + // Quantum dimension d_tau = largest eigenvalue of N_tau + // Characteristic equation: lambda^2 - lambda - 1 = 0 + // Positive root: lambda = (1 + sqrt(5))/2 = phi + fn quantum_dimension_tau() -> f64 { + // d_tau = phi, forced by fusion rule tau x tau = 1 + tau + // This is a THEOREM, not an approximation + return constants::PHI; + } + + // Total quantum dimension D^2 = sum of d_j^2 + // D^2 = 1^2 + phi^2 = 1 + phi + 1 = 2 + phi + fn total_quantum_dimension_squared() -> f64 { + const d_1 = 1.0; + const d_tau = constants::PHI; + return d_1 * d_1 + d_tau * d_tau; + } + + // Hilbert space dimension for n Fibonacci anyons = F_n (Fibonacci number) + // Growth rate: F_{n+1}/F_n -> phi as n -> infinity + fn fibonacci_hilbert_dim(n: i64) -> i64 { + if n <= 1 { + return 1; + } + let a : i64 = 1; + let b : i64 = 1; + let i : i64 = 2; + while i <= n { + let temp = a + b; + a = b; + b = temp; + i = i + 1; + } + return b; + } + + // ========================================================================= + // 3. Modular S-Matrix (Kac-Peterson Formula) + // ========================================================================= + + // S_{j,j'} = sqrt(2/(k+2)) * sin((2j+1)(2j'+1)*pi/(k+2)) + // At k=3, k+2=5: + // S_{0,0} = sqrt(2/5) * sin(pi/5) + // S_{0,1} = sqrt(2/5) * sin(3*pi/5) + // d_1 = S_{0,1}/S_{0,0} = sin(3*pi/5)/sin(pi/5) = phi + + fn s_matrix_element(j: f64, jp: f64, k: i64) -> f64 { + const kp2 = (k + 2) as f64; + const prefactor = sqrt(2.0 / kp2); + const angle = (2.0 * j + 1.0) * (2.0 * jp + 1.0) * constants::PI / kp2; + return prefactor * sin(angle); + } + + fn quantum_dimension_at_k(j: f64, k: i64) -> f64 { + // d_j = S_{0,j} / S_{0,0} + const s_0j = s_matrix_element(0.0, j, k); + const s_00 = s_matrix_element(0.0, 0.0, k); + return s_0j / s_00; + } + + // Simplified: d_j = sin((2j+1)*pi/(k+2)) / sin(pi/(k+2)) + fn quantum_dimension_simplified(j: f64, k: i64) -> f64 { + const kp2 = (k + 2) as f64; + return sin((2.0 * j + 1.0) * constants::PI / kp2) / sin(constants::PI / kp2); + } + + // ========================================================================= + // 4. Jones Polynomial Quantum Integer + // ========================================================================= + + // At q = exp(2*pi*i / (k+2)), the quantum integer is: + // [n]_q = sin(n*pi/(k+2)) / sin(pi/(k+2)) + // At k=3: [2]_5 = sin(2*pi/5) / sin(pi/5) = 2*cos(pi/5) = phi + + fn jones_quantum_integer(n: i64, k: i64) -> f64 { + const kp2 = (k + 2) as f64; + return sin(n as f64 * constants::PI / kp2) / sin(constants::PI / kp2); + } + + // ========================================================================= + // 5. TRINITY Connection: k = phi^2 + phi^{-2} + // ========================================================================= + + // The deepest connection: CS level k=3 and the TRINITY identity + // phi^2 + phi^{-2} = 3 = k + // This is not a coincidence -- it reflects that phi arises from + // the cyclotomic field Q(sqrt(5)) where 5 = k + 2 + + fn trinity_equals_cs_level() -> bool { + const trinity = constants::PHI * constants::PHI + 1.0 / (constants::PHI * constants::PHI); + return abs(trinity - CS_LEVEL as f64) < 1.0e-15; + } + + // ========================================================================= + // 6. Utility: trigonometric and math functions (stubs for t27 spec) + // ========================================================================= + + fn sin(x: f64) -> f64 { + // Taylor series: sin(x) = x - x^3/6 + x^5/120 - x^7/5040 + ... + let result = 0.0; + let term = x; + let sign = 1.0; + let n = 1; + while n < 20 { + result = result + sign * term; + term = term * x * x / ((2 * n) as f64 * (2 * n + 1) as f64); + sign = -sign; + n = n + 1; + } + return result; + } + + fn cos(x: f64) -> f64 { + // cos(x) = 1 - x^2/2 + x^4/24 - ... + let result = 0.0; + let term = 1.0; + let sign = 1.0; + let n = 0; + while n < 20 { + result = result + sign * term; + term = term * x * x / ((2 * n + 1) as f64 * (2 * n + 2) as f64); + sign = -sign; + n = n + 1; + } + return result; + } + + fn sqrt(x: f64) -> f64 { + if x <= 0.0 { return 0.0; } + // Newton's method: y_{n+1} = (y_n + x/y_n) / 2 + let y = x; + let i = 0; + while i < 50 { + y = (y + x / y) / 2.0; + i = i + 1; + } + return y; + } + + fn abs(x: f64) -> f64 { + if x < 0.0 { return -x; } + return x; + } + + // ========================================================================= + // TDD-Inside-Spec: Tests + // ========================================================================= + + // --- Fusion Rule Tests --- + + test fibonacci_fusion_matrix_entries + given fm = fibonacci_fusion_matrix() + then fm.entries[0][0] == 0 + and fm.entries[0][1] == 1 + and fm.entries[1][0] == 1 + and fm.entries[1][1] == 1 + + test quantum_dimension_tau_equals_phi + given d_tau = quantum_dimension_tau() + then abs(d_tau - constants::PHI) < 1.0e-15 + + test quantum_dimension_characteristic_equation + given d = quantum_dimension_tau() + when residual = d * d - d - 1.0 + then abs(residual) < 1.0e-14 + + test total_quantum_dimension_is_2_plus_phi + given d_sq = total_quantum_dimension_squared() + and expected = 2.0 + constants::PHI + then abs(d_sq - expected) < 1.0e-14 + + // --- S-Matrix Tests (k=3) --- + + test s_matrix_d_half_equals_phi + given d = quantum_dimension_simplified(0.5, 3) + then abs(d - constants::PHI) < 1.0e-10 + + test s_matrix_d_1_equals_phi + given d = quantum_dimension_simplified(1.0, 3) + then abs(d - constants::PHI) < 1.0e-10 + + test s_matrix_d_0_equals_1 + given d = quantum_dimension_simplified(0.0, 3) + then abs(d - 1.0) < 1.0e-10 + + test s_matrix_d_three_half_equals_1 + given d = quantum_dimension_simplified(1.5, 3) + then abs(d - 1.0) < 1.0e-10 + + // --- Jones Polynomial Tests --- + + test jones_quantum_2_at_k3_equals_phi + given j2 = jones_quantum_integer(2, 3) + then abs(j2 - constants::PHI) < 1.0e-10 + + test jones_quantum_2_equals_2cos_pi_5 + given j2 = jones_quantum_integer(2, 3) + and expected = 2.0 * cos(constants::PI / 5.0) + then abs(j2 - expected) < 1.0e-10 + + // --- TRINITY Connection Tests --- + + test cs_level_equals_trinity_identity + given result = trinity_equals_cs_level() + then result == true + + test trinity_identity_exact + given trinity = constants::PHI * constants::PHI + 1.0 / (constants::PHI * constants::PHI) + then abs(trinity - 3.0) < 1.0e-15 + + test cyclotomic_index_is_5 + then CYCLOTOMIC_INDEX == 5 + + // --- Fibonacci Hilbert Space Tests --- + + test fibonacci_hilbert_dim_2_anyons + given dim = fibonacci_hilbert_dim(2) + then dim == 2 + + test fibonacci_hilbert_dim_5_anyons + given dim = fibonacci_hilbert_dim(5) + then dim == 8 + + test fibonacci_hilbert_dim_10_anyons + given dim = fibonacci_hilbert_dim(10) + then dim == 89 + + test fibonacci_hilbert_dim_12_anyons + given dim = fibonacci_hilbert_dim(12) + then dim == 233 + + test fibonacci_ratio_approaches_phi + given f11 = fibonacci_hilbert_dim(11) + and f10 = fibonacci_hilbert_dim(10) + and ratio = f11 as f64 / f10 as f64 + then abs(ratio - constants::PHI) < 0.01 + + // ========================================================================= + // TDD-Inside-Spec: Invariants + // ========================================================================= + + invariant cs_level_positive + assert CS_LEVEL > 0 + + invariant num_sectors_is_k_plus_1 + assert NUM_SECTORS == CS_LEVEL + 1 + + invariant quantum_dimension_tau_positive + assert quantum_dimension_tau() > 1.0 + + invariant quantum_dimension_tau_is_phi + assert abs(quantum_dimension_tau() - constants::PHI) < 1.0e-15 + + invariant trinity_is_cs_level + assert trinity_equals_cs_level() == true + + invariant total_quantum_dimension_positive + assert total_quantum_dimension_squared() > 0.0 + + invariant fibonacci_growth_rate_is_phi + given f20 = fibonacci_hilbert_dim(20) + and f19 = fibonacci_hilbert_dim(19) + assert abs(f20 as f64 / f19 as f64 - constants::PHI) < 1.0e-4 + + invariant cyclotomic_index_equals_k_plus_2 + assert CYCLOTOMIC_INDEX == CS_LEVEL + 2 + + // ========================================================================= + // TDD-Inside-Spec: Benchmarks + // ========================================================================= + + bench quantum_dimension_computation_time + measure: nanoseconds to compute quantum_dimension_tau() + target: < 100ns + + bench s_matrix_element_computation_time + measure: nanoseconds to compute s_matrix_element(0.5, 1.0, 3) + target: < 500ns + + bench fibonacci_hilbert_dim_20_time + measure: nanoseconds to compute fibonacci_hilbert_dim(20) + target: < 200ns +} diff --git a/apps/website/public/t27/specs/physics/zamolodchikov_4d_conjecture.t27 b/apps/website/public/t27/specs/physics/zamolodchikov_4d_conjecture.t27 new file mode 100644 index 0000000000..ebb20a9b53 --- /dev/null +++ b/apps/website/public/t27/specs/physics/zamolodchikov_4d_conjecture.t27 @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/physics/zamolodchikov_4d_conjecture.t27 +// 4D Zamolodchikov Conjecture -- The Breakthrough Hypothesis +// Direction E of PROJECT KEPLER->NEWTON +// +// HYPOTHESIS: A 4D quantum field theory with E8 integrable structure +// fixes the fundamental constants of the Standard Model through the +// same algebraic mechanism that fixes the 8 Zamolodchikov masses in 2D. +// +// EVIDENCE: +// 1. In 2D: Ising CFT + magnetic perturbation -> E8 theory -> 8 masses with phi +// (Zamolodchikov 1989, Coldea 2010 experiment) +// 2. 4D Chern-Simons theory produces 2D integrable models via defects +// (Ashwinkumar, Sakamoto, Yamazaki, arXiv:2309.14412, 2023) +// 3. N=4 Super Yang-Mills IS integrable in 4D (Beisert et al. 2003-2010) +// 4. Seiberg-Witten theory gives EXACT mass formulas in N=2 SYM +// (Seiberg & Witten 1994) +// 5. E8 flavor symmetry appears in F-theory/string compactifications +// (Razamat et al., SciPost 2019/2021) +// +// CONCRETE TEST: +// In N=2 SYM with E8 flavor symmetry, compute mass ratios of BPS states. +// If m2/m1 = phi -> the 2D E8 integrable structure survives in 4D. +// If mass ratios match Sacred Formula values -> derivation from first principles. +// +// STATUS: Hypothesis. No computation performed yet. +// +// References: +// - Zamolodchikov, Int. J. Mod. Phys. A4 (1989) 4235 +// - Seiberg & Witten, Nucl. Phys. B426 (1994) 19-52 +// - Ashwinkumar et al., arXiv:2309.14412 (2023) +// - Beisert et al., arXiv:1012.3982 (2010) -- N=4 SYM integrability review +// - Razamat et al., SciPost Phys. 8 (2020) 014 -- rank Q E-string with E8 +// - Kaushik et al., arXiv:2206.06911 (2024) -- E8 x E8 unification + +module Zamolodchikov4DConjecture { + use math::constants; + use math::e8_lie_algebra; + use math::zamolodchikov_e8; + use physics::su2_chern_simons; + + // ========================================================================= + // 1. The Conjecture (Formal Statement) + // ========================================================================= + + // CONJECTURE (Zamolodchikov 4D): + // + // There exists a 4-dimensional quantum field theory T_4D such that: + // (a) T_4D has E8 as a global symmetry (flavor or gauge) + // (b) T_4D admits an integrable deformation preserving E8 + // (c) The BPS mass spectrum of the deformed T_4D contains mass ratios + // that are components of the E8 Perron-Frobenius eigenvector + // (d) In particular, m2/m1 = phi (golden ratio) + // (e) The remaining mass ratios match the ~25 free parameters of the + // Standard Model within the precision of Sacred Formula (~100 ppm) + // + // EVIDENCE FOR (a): E8 appears as flavor symmetry in: + // - Rank Q E-string theories (Razamat et al.) + // - E8 x E8 heterotic string compactifications + // - Minahan-Nemeschansky E8 SCFT (N=2, rank 1) + // + // EVIDENCE FOR (b): 4D Chern-Simons theory (Costello 2013, Costello-Witten- + // Yamazaki 2018) generates 2D integrable models via defects. The reverse + // direction (lifting 2D integrability to 4D) is under active investigation + // (Ashwinkumar et al. 2023). + // + // EVIDENCE FOR (c): The Seiberg-Witten solution gives EXACT masses for + // BPS states in N=2 SYM. When the gauge group is E8-related (e.g., via + // F-theory on K3), the mass spectrum is algebraically constrained. + // + // EVIDENCE FOR (d): The 2D Zamolodchikov result (m2/m1 = phi) follows + // from the Perron-Frobenius eigenvector of E8. If the 4D BPS masses + // are similarly constrained, phi would appear. + // + // EVIDENCE FOR (e): Sacred Formula n-values match E8 marks with 5.5x + // statistical enrichment (p < 0.0001). This suggests a deeper structural + // connection. + + // ========================================================================= + // 2. The 4D-2D Bridge (Ashwinkumar et al. 2023) + // ========================================================================= + + // 4D Chern-Simons theory on M4 = Sigma x C (where C is a curve) + // produces 2D integrable models on Sigma when C has defects. + // + // The action is: + // S_4dCS = (1/2pi) int_{M4} omega ^ CS(A) + // + // where omega is a meromorphic 1-form on C and CS(A) is the + // Chern-Simons 3-form. + // + // KEY INSIGHT: If the gauge algebra is E8 and the defects on C + // produce the Ising CFT perturbation, the 2D theory on Sigma + // would be exactly Zamolodchikov's E8 integrable theory. + // + // This means: the 8 mass ratios (including phi) would be DERIVED + // from the 4D Chern-Simons action, not fitted. + + struct FourDimensionalBridge { + gauge_algebra: string; // "E8" or "su(N)" + curve: string; // "P1 with defects" etc. + two_d_theory: string; // "E8 integrable" or "Ising + h*sigma" + mass_spectrum: [8]f64; // Zamolodchikov masses if E8 + } + + fn conjectured_bridge() -> FourDimensionalBridge { + return FourDimensionalBridge{ + gauge_algebra = "E8", + curve = "P1 with magnetic defect", + two_d_theory = "Zamolodchikov E8 integrable field theory", + mass_spectrum = zamolodchikov_e8::mass_spectrum(), + }; + } + + // ========================================================================= + // 3. Concrete Test: Seiberg-Witten with E8 Flavor + // ========================================================================= + + // The Minahan-Nemeschansky E8 SCFT is an N=2 rank-1 theory + // with E8 flavor symmetry and central charges a = 95/24, c = 31/6. + // + // When mass-deformed (turning on E8 mass parameters), BPS states + // appear with masses determined by the Seiberg-Witten curve. + // + // TEST: Compute BPS mass ratios in the Minahan-Nemeschansky E8 theory + // with specific mass deformation. Check if m2/m1 = phi. + // + // This is a COMPUTABLE test that can be done with existing technology + // (Nekrasov partition function, AGT correspondence, etc.) + + struct MinahanNemeschanskyE8 { + rank: i64; + flavor_group: string; + central_charge_a: f64; + central_charge_c: f64; + } + + fn mn_e8_theory() -> MinahanNemeschanskyE8 { + return MinahanNemeschanskyE8{ + rank = 1, + flavor_group = "E8", + central_charge_a = 95.0 / 24.0, // 3.958333... + central_charge_c = 31.0 / 6.0, // 5.166666... + }; + } + + // ========================================================================= + // 4. Prediction Structure + // ========================================================================= + + // IF the conjecture is correct, then: + // + // 1. The 8 Zamolodchikov masses correspond to 8 BPS states + // in the mass-deformed MN E8 theory + // + // 2. The Sacred Formula V = n * 3^k * pi^m * phi^p * e^q * gamma^r + // should emerge from the Seiberg-Witten prepotential F(a) + // as special values of periods + // + // 3. The n-values should equal E8 marks (as observed: p < 0.0001) + // because marks = coefficients in the highest root expansion + // = Dynkin labels = quantum numbers of BPS states + // + // 4. New prediction: the BPS mass spectrum of MN E8 theory + // should contain masses proportional to Sacred Formula values + // for the SAME physical constants + // + // This is FALSIFIABLE: compute the spectrum, compare with SM. + + // ========================================================================= + // 5. Sacred Formula as BPS Spectrum (Speculative Mapping) + // ========================================================================= + + // Hypothesis: n = E8 mark at Dynkin position i + // k = power of 3 from Trinity identity + // (m, p, q, r) = quantum numbers of BPS state + // + // The mapping would be: + // Dynkin node 1 (mark=2): sin2_thetaW, mp/me, MW + // Dynkin node 4 (mark=4): alpha_inv, alpha_s, sin2_theta23 + // Dynkin node 5 (mark=5): Z boson, T_CMB, Higgs mass + // + // Each node -> physical domain is a prediction that can be tested + // by computing BPS states associated with each simple root. + + struct DynkinPhysicsMapping { + dynkin_node: i64; + mark: i64; + physics_domain: string; + formulas: string; + } + + fn observed_mapping() -> [3]DynkinPhysicsMapping { + return [ + DynkinPhysicsMapping{ + dynkin_node = 1, mark = 2, + physics_domain = "electroweak", + formulas = "sin2_thetaW, mp/me, MW", + }, + DynkinPhysicsMapping{ + dynkin_node = 4, mark = 4, + physics_domain = "couplings", + formulas = "1/alpha, alpha_s, sin2_theta23", + }, + DynkinPhysicsMapping{ + dynkin_node = 5, mark = 5, + physics_domain = "bosons_cosmology", + formulas = "MZ, T_CMB, MH", + }, + ]; + } + + // ========================================================================= + // TDD-Inside-Spec: Tests (for verifiable parts only) + // ========================================================================= + + test mn_e8_central_charges + given theory = mn_e8_theory() + then abs(theory.central_charge_a - 95.0/24.0) < 1.0e-10 + and abs(theory.central_charge_c - 31.0/6.0) < 1.0e-10 + + test bridge_uses_zamolodchikov_masses + given bridge = conjectured_bridge() + and zam = zamolodchikov_e8::mass_spectrum() + then abs(bridge.mass_spectrum[1] - zam[1]) < 1.0e-10 + + test mapping_covers_three_domains + given maps = observed_mapping() + then maps[0].mark == 2 and maps[1].mark == 4 and maps[2].mark == 5 + + test bridge_gauge_is_e8 + given bridge = conjectured_bridge() + then bridge.gauge_algebra == "E8" + + // ========================================================================= + // TDD-Inside-Spec: Invariants + // ========================================================================= + + invariant mn_e8_rank_is_1 + assert mn_e8_theory().rank == 1 + + invariant bridge_has_8_masses + given bridge = conjectured_bridge() + assert bridge.mass_spectrum[0] > 0.0 and bridge.mass_spectrum[7] > 0.0 + + // ========================================================================= + // TDD-Inside-Spec: Benchmarks + // ========================================================================= + + bench bridge_construction_time + measure: nanoseconds to compute conjectured_bridge() + target: < 500ns +} diff --git a/apps/website/public/t27/specs/pins/emitter_xdc.t27 b/apps/website/public/t27/specs/pins/emitter_xdc.t27 new file mode 100644 index 0000000000..29f0df518c --- /dev/null +++ b/apps/website/public/t27/specs/pins/emitter_xdc.t27 @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/pins/emitter_xdc.t27 +// XDC Constraint Emitter from Pins IR +// Generates nextpnr-compatible XDC from Design/Binding/ClockDef +// Output format matches t27c fpga-build --minimal exactly +// phi^2 + 1/phi^2 = 3 | TRINITY + +module EmitterXDC { + use base::types; + use base::ops; + + const HEADER_COMMENT : &str = "# nextpnr-compatible XDC"; + + struct XDCLine { + text : &str, + is_comment : bool, + is_empty : bool, + } + + struct XDCOutput { + lines : [512]XDCLine, + count : usize, + } + + fn empty_xdc() -> XDCOutput { + return XDCOutput{ + .lines = [XDCLine{.text = "", .is_comment = false, .is_empty = true}; 512], + .count = 0, + }; + } + + fn add_line(out: XDCOutput, text: &str, is_comment: bool) -> XDCOutput { + var result = out; + if (result.count < 512) { + result.lines[result.count] = XDCLine{ + .text = text, + .is_comment = is_comment, + .is_empty = false, + }; + result.count = result.count + 1; + } + return result; + } + + fn add_empty(out: XDCOutput) -> XDCOutput { + var result = out; + if (result.count < 512) { + result.lines[result.count] = XDCLine{ + .text = "", + .is_comment = false, + .is_empty = true, + }; + result.count = result.count + 1; + } + return result; + } + + fn emit_pin( + out: XDCOutput, + package_pin: &str, + iostandard: &str, + port_name: &str, + ) -> XDCOutput { + var result = out; + var line_text : &str = "set_property -dict { PACKAGE_PIN "; + line_text = line_text + package_pin; + line_text = line_text + " IOSTANDARD "; + line_text = line_text + iostandard; + line_text = line_text + " } [get_ports "; + line_text = line_text + port_name; + line_text = line_text + "]"; + result = add_line(result, line_text, false); + return result; + } + + fn emit_clock( + out: XDCOutput, + port_name: &str, + clock_name: &str, + period_ns: u32, + waveform_high_ns: u32, + ) -> XDCOutput { + var result = out; + var line_text : &str = "create_clock -add -name "; + line_text = line_text + clock_name; + line_text = line_text + " -period "; + var period_str : &str = "83.333"; + if (period_ns == 83) { + period_str = "83.333"; + } + line_text = line_text + period_str; + line_text = line_text + " -waveform {0 "; + var high_str : &str = "41.666"; + if (waveform_high_ns == 41) { + high_str = "41.666"; + } + line_text = line_text + high_str; + line_text = line_text + "} [get_ports "; + line_text = line_text + port_name; + line_text = line_text + "]"; + result = add_line(result, line_text, false); + return result; + } + + fn emit_header(out: XDCOutput, design_name: &str) -> XDCOutput { + var result = out; + var hdr : &str = "# nextpnr-compatible XDC for "; + hdr = hdr + design_name; + result = add_line(result, hdr, true); + return result; + } + + fn qmtech_xc7a100t_minimal() -> XDCOutput { + var out = empty_xdc(); + out = emit_header(out, "minimal design (prjxray-verified pins)"); + + out = emit_pin(out, "E3", "LVCMOS33", "clk"); + out = emit_clock(out, "clk", "sys_clk", 83, 41); + out = emit_pin(out, "C14", "LVCMOS33", "rst_n"); + out = emit_pin(out, "T14", "LVCMOS33", "uart_rx"); + out = emit_pin(out, "T15", "LVCMOS33", "uart_tx"); + + out = emit_pin(out, "H17", "LVCMOS33", "led[0]"); + out = emit_pin(out, "K15", "LVCMOS33", "led[1]"); + out = emit_pin(out, "J13", "LVCMOS33", "led[2]"); + out = emit_pin(out, "N14", "LVCMOS33", "led[3]"); + out = emit_pin(out, "R18", "LVCMOS33", "led[4]"); + out = emit_pin(out, "U18", "LVCMOS33", "led[5]"); + out = emit_pin(out, "T13", "LVCMOS33", "led[6]"); + out = emit_pin(out, "T11", "LVCMOS33", "led[7]"); + + return out; + } + + fn arty_a7_minimal() -> XDCOutput { + var out = empty_xdc(); + out = emit_header(out, "Arty A7 minimal (4 LEDs + UART + buttons)"); + + out = emit_pin(out, "E3", "LVCMOS33", "clk"); + out = emit_clock(out, "clk", "sys_clk", 10, 5); + out = emit_pin(out, "C12", "LVCMOS33", "rst_n"); + out = emit_pin(out, "A9", "LVCMOS33", "uart_tx"); + out = emit_pin(out, "C9", "LVCMOS33", "uart_rx"); + + out = emit_pin(out, "R5", "LVCMOS33", "led[0]"); + out = emit_pin(out, "T5", "LVCMOS33", "led[1]"); + out = emit_pin(out, "T8", "LVCMOS33", "led[2]"); + out = emit_pin(out, "T9", "LVCMOS33", "led[3]"); + + return out; + } + + fn line_count(out: XDCOutput) -> usize { + return out.count; + } + + fn has_clk_constraint(out: XDCOutput) -> bool { + var i : usize = 0; + while (i < out.count) { + if (!out.lines[i].is_empty and !out.lines[i].is_comment) { + if (out.lines[i].text == "create_clock") { + return true; + } + } + i = i + 1; + } + return false; + } + + fn count_set_property_lines(out: XDCOutput) -> usize { + var count : usize = 0; + var i : usize = 0; + while (i < out.count) { + if (!out.lines[i].is_empty and !out.lines[i].is_comment) { + count = count + 1; + } + i = i + 1; + } + return count; + } + + test empty_xdc_has_zero_lines + given out = empty_xdc() + then line_count(out) == 0 + + test add_line_increments_count + given out = empty_xdc() + and out2 = add_line(out, "test line", false) + then line_count(out2) == 1 + + test add_comment_line + given out = empty_xdc() + and out2 = add_line(out, "# comment", true) + then line_count(out2) == 1 and out2.lines[0].is_comment == true + + test emit_pin_format + given out = empty_xdc() + and out2 = emit_pin(out, "E3", "LVCMOS33", "clk") + then line_count(out2) == 1 + + test emit_pin_rst_n + given out = empty_xdc() + and out2 = emit_pin(out, "C14", "LVCMOS33", "rst_n") + then line_count(out2) == 1 + + test emit_clock_format + given out = empty_xdc() + and out2 = emit_clock(out, "clk", "sys_clk", 83, 41) + then line_count(out2) == 1 + + test emit_header_produces_comment + given out = empty_xdc() + and out2 = emit_header(out, "test") + then line_count(out2) == 1 and out2.lines[0].is_comment == true + + test qmtech_minimal_line_count + given out = qmtech_xc7a100t_minimal() + then line_count(out) == 13 + + test qmtech_minimal_has_12_non_comment_lines + given out = qmtech_xc7a100t_minimal() + and n = count_set_property_lines(out) + then n == 13 + + test qmtech_minimal_first_line_is_comment + given out = qmtech_xc7a100t_minimal() + then out.lines[0].is_comment == true + + test qmtech_minimal_second_line_is_clk_pin + given out = qmtech_xc7a100t_minimal() + then out.lines[1].is_comment == false and out.lines[1].is_empty == false + + test qmtech_minimal_third_line_is_clock + given out = qmtech_xc7a100t_minimal() + then out.lines[2].is_comment == false and out.lines[2].is_empty == false + + test qmtech_minimal_led_pins_count + given out = qmtech_xc7a100t_minimal() + and n = count_set_property_lines(out) + then n == 13 + + test qmtech_minimal_has_all_12_signal_pins + given out = qmtech_xc7a100t_minimal() + then line_count(out) == 13 + + test arty_a7_line_count + given out = arty_a7_minimal() + then line_count(out) == 9 + + test arty_a7_starts_with_comment + given out = arty_a7_minimal() + then out.lines[0].is_comment == true + + test arty_a7_has_clk_and_clock + given out = arty_a7_minimal() + then line_count(out) == 9 + + test arty_a7_4_leds + given out = arty_a7_minimal() + and n = count_set_property_lines(out) + then n == 9 + + invariant empty_xdc_no_lines + given out = empty_xdc() + assert line_count(out) == 0 + + invariant qmtech_minimal_has_exactly_13_lines + given out = qmtech_xc7a100t_minimal() + assert line_count(out) == 13 + + invariant qmtech_minimal_starts_with_comment + given out = qmtech_xc7a100t_minimal() + assert out.lines[0].is_comment == true + + invariant qmtech_minimal_line_count_positive + given out = qmtech_xc7a100t_minimal() + assert line_count(out) > 0 + + invariant line_count_never_exceeds_capacity + given out = qmtech_xc7a100t_minimal() + assert line_count(out) <= 512 + + invariant arty_a7_line_count_positive + given out = arty_a7_minimal() + assert line_count(out) > 0 and line_count(out) == 9 + + bench emit_pin_latency + measure: nanoseconds to emit_pin(empty_xdc(), "E3", "LVCMOS33", "clk") + target: < 500ns + + bench qmtech_minimal_gen_latency + measure: nanoseconds to qmtech_xc7a100t_minimal() + target: < 5000ns +} diff --git a/apps/website/public/t27/specs/pins/ir.t27 b/apps/website/public/t27/specs/pins/ir.t27 new file mode 100644 index 0000000000..c8d87ac23a --- /dev/null +++ b/apps/website/public/t27/specs/pins/ir.t27 @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/pins/ir.t27 +// Pins Intermediate Representation (IR) +// Models FPGA pin assignments, I/O standards, clock constraints +// phi^2 + 1/phi^2 = 3 | TRINITY + +module PinsIR { + use base::types; + use base::ops; + + struct PinLocation { + package_pin : &str, + port_name : &str, + bank : u8, + } + + struct IoStandard { + name : &str, + voltage : &str, + drive_strength_ma : u8, + slew_fast : bool, + } + + struct SignalReference { + port_name : &str, + direction : &str, + width : usize, + index : i32, + } + + struct Binding { + location : PinLocation, + standard : IoStandard, + signal : SignalReference, + is_clock : bool, + pullup : bool, + pulldown : bool, + } + + struct ClockDef { + port_name : &str, + name : &str, + period_ns : u32, + waveform_high_ns : u32, + add : bool, + } + + struct Design { + name : &str, + fpga_part : &str, + bindings : [256]Binding, + clocks : [16]ClockDef, + binding_count : usize, + clock_count : usize, + } + + const LVCMOS33 : IoStandard = IoStandard{ + .name = "LVCMOS33", + .voltage = "3.3", + .drive_strength_ma = 12, + .slew_fast = false, + }; + + fn make_input_signal(port: &str, width: usize) -> SignalReference { + return SignalReference{ + .port_name = port, + .direction = "input", + .width = width, + .index = -1, + }; + } + + fn make_output_signal(port: &str, width: usize) -> SignalReference { + return SignalReference{ + .port_name = port, + .direction = "output", + .width = width, + .index = -1, + }; + } + + fn make_indexed_signal(port: &str, idx: i32) -> SignalReference { + return SignalReference{ + .port_name = port, + .direction = "output", + .width = 1, + .index = idx, + }; + } + + fn make_location(pin: &str, port: &str, bank: u8) -> PinLocation { + return PinLocation{ + .package_pin = pin, + .port_name = port, + .bank = bank, + }; + } + + fn make_binding( + loc: PinLocation, + sig: SignalReference, + is_clk: bool, + ) -> Binding { + return Binding{ + .location = loc, + .standard = LVCMOS33, + .signal = sig, + .is_clock = is_clk, + .pullup = false, + .pulldown = false, + }; + } + + fn make_clock(port: &str, name: &str, period: u32, high: u32) -> ClockDef { + return ClockDef{ + .port_name = port, + .name = name, + .period_ns = period, + .waveform_high_ns = high, + .add = true, + }; + } + + fn empty_design(name: &str, part: &str) -> Design { + return Design{ + .name = name, + .fpga_part = part, + .bindings = [Binding{ + .location = PinLocation{.package_pin = "", .port_name = "", .bank = 0}, + .standard = LVCMOS33, + .signal = SignalReference{.port_name = "", .direction = "", .width = 0, .index = -1}, + .is_clock = false, + .pullup = false, + .pulldown = false, + }; 256], + .clocks = [ClockDef{ + .port_name = "", + .name = "", + .period_ns = 0, + .waveform_high_ns = 0, + .add = false, + }; 16], + .binding_count = 0, + .clock_count = 0, + }; + } + + fn add_binding(d: Design, b: Binding) -> Design { + var out = d; + if (out.binding_count < 256) { + out.bindings[out.binding_count] = b; + out.binding_count = out.binding_count + 1; + } + return out; + } + + fn add_clock(d: Design, c: ClockDef) -> Design { + var out = d; + if (out.clock_count < 16) { + out.clocks[out.clock_count] = c; + out.clock_count = out.clock_count + 1; + } + return out; + } + + fn has_pin_conflict(d: Design) -> bool { + var i : usize = 0; + while (i < d.binding_count) { + var j : usize = i + 1; + while (j < d.binding_count) { + if (d.bindings[i].location.package_pin == d.bindings[j].location.package_pin) { + if (d.bindings[i].location.package_pin != "") { + return true; + } + } + j = j + 1; + } + i = i + 1; + } + return false; + } + + fn has_port_conflict(d: Design) -> bool { + var i : usize = 0; + while (i < d.binding_count) { + var j : usize = i + 1; + while (j < d.binding_count) { + if (d.bindings[i].signal.port_name == d.bindings[j].signal.port_name) { + if (d.bindings[i].signal.index == d.bindings[j].signal.index) { + if (d.bindings[i].signal.port_name != "") { + return true; + } + } + } + j = j + 1; + } + i = i + 1; + } + return false; + } + + fn clock_bound(d: Design, port: &str) -> bool { + var i : usize = 0; + while (i < d.clock_count) { + if (d.clocks[i].port_name == port) { + return true; + } + i = i + 1; + } + return false; + } + + fn all_clock_ports_bound(d: Design) -> bool { + var i : usize = 0; + while (i < d.binding_count) { + if (d.bindings[i].is_clock) { + if (!clock_bound(d, d.bindings[i].signal.port_name)) { + return false; + } + } + i = i + 1; + } + return true; + } + + fn binding_count(d: Design) -> usize { + return d.binding_count; + } + + fn clock_count(d: Design) -> usize { + return d.clock_count; + } + + fn signal_direction(sig: SignalReference) -> &str { + return sig.direction; + } + + fn is_indexed(sig: SignalReference) -> bool { + return sig.index >= 0; + } + + fn format_port_name(sig: SignalReference) -> &str { + if (sig.index >= 0) { + return sig.port_name; + } + return sig.port_name; + } + + test make_input_signal_direction + given sig = make_input_signal("clk", 1) + then sig.direction == "input" and sig.port_name == "clk" and sig.width == 1 + + test make_output_signal_direction + given sig = make_output_signal("led", 8) + then sig.direction == "output" and sig.port_name == "led" and sig.width == 8 + + test make_indexed_signal + given sig = make_indexed_signal("led", 3) + then sig.index == 3 and sig.direction == "output" + + test make_location_fields + given loc = make_location("E3", "clk", 0) + then loc.package_pin == "E3" and loc.port_name == "clk" and loc.bank == 0 + + test make_binding_fields + given loc = make_location("E3", "clk", 0) + and sig = make_input_signal("clk", 1) + and b = make_binding(loc, sig, true) + then b.is_clock == true and b.standard.name == "LVCMOS33" and b.pullup == false + + test make_clock_fields + given clk = make_clock("clk", "sys_clk", 83, 41) + then clk.port_name == "clk" and clk.name == "sys_clk" and clk.period_ns == 83 and clk.waveform_high_ns == 41 + + test empty_design_zero_bindings + given d = empty_design("test", "xc7a100t") + then d.binding_count == 0 and d.clock_count == 0 and d.name == "test" + + test add_binding_increments_count + given d = empty_design("test", "xc7a100t") + and loc = make_location("E3", "clk", 0) + and sig = make_input_signal("clk", 1) + and b = make_binding(loc, sig, true) + and d2 = add_binding(d, b) + then d2.binding_count == 1 + + test add_clock_increments_count + given d = empty_design("test", "xc7a100t") + and clk = make_clock("clk", "sys_clk", 83, 41) + and d2 = add_clock(d, clk) + then d2.clock_count == 1 + + test no_conflict_single_pin + given d = empty_design("test", "xc7a100t") + and loc = make_location("E3", "clk", 0) + and sig = make_input_signal("clk", 1) + and b = make_binding(loc, sig, true) + and d2 = add_binding(d, b) + then has_pin_conflict(d2) == false + + test conflict_detected_duplicate_pin + given d = empty_design("test", "xc7a100t") + and loc1 = make_location("E3", "clk", 0) + and sig1 = make_input_signal("clk", 1) + and b1 = make_binding(loc1, sig1, true) + and d2 = add_binding(d, b1) + and loc2 = make_location("E3", "rst_n", 0) + and sig2 = make_input_signal("rst_n", 1) + and b2 = make_binding(loc2, sig2, false) + and d3 = add_binding(d2, b2) + then has_pin_conflict(d3) == true + + test no_port_conflict_different_indices + given d = empty_design("test", "xc7a100t") + and loc1 = make_location("H17", "led", 1) + and sig1 = make_indexed_signal("led", 0) + and b1 = make_binding(loc1, sig1, false) + and d2 = add_binding(d, b1) + and loc2 = make_location("K15", "led", 1) + and sig2 = make_indexed_signal("led", 1) + and b2 = make_binding(loc2, sig2, false) + and d3 = add_binding(d2, b2) + then has_port_conflict(d3) == false + + test all_clocks_bound_true + given d = empty_design("test", "xc7a100t") + and loc = make_location("E3", "clk", 0) + and sig = make_input_signal("clk", 1) + and b = make_binding(loc, sig, true) + and d2 = add_binding(d, b) + and clk = make_clock("clk", "sys_clk", 83, 41) + and d3 = add_clock(d2, clk) + then all_clock_ports_bound(d3) == true + + test all_clocks_bound_false_missing_clock + given d = empty_design("test", "xc7a100t") + and loc = make_location("E3", "clk", 0) + and sig = make_input_signal("clk", 1) + and b = make_binding(loc, sig, true) + and d2 = add_binding(d, b) + then all_clock_ports_bound(d2) == false + + test lvcmos33_standard + given std = LVCMOS33 + then std.name == "LVCMOS33" and std.voltage == "3.3" + + test is_indexed_true + given sig = make_indexed_signal("led", 0) + then is_indexed(sig) == true + + test is_indexed_false + given sig = make_input_signal("clk", 1) + then is_indexed(sig) == false + + test signal_direction_input + given sig = make_input_signal("rx", 1) + then signal_direction(sig) == "input" + + test signal_direction_output + given sig = make_output_signal("tx", 1) + then signal_direction(sig) == "output" + + invariant lvcmos33_name + given std = LVCMOS33 + assert std.name == "LVCMOS33" + + invariant empty_design_has_no_conflicts + given d = empty_design("test", "xc7a100t") + assert has_pin_conflict(d) == false and has_port_conflict(d) == false + + invariant empty_design_no_clocks_bound_vacuously + given d = empty_design("test", "xc7a100t") + assert all_clock_ports_bound(d) == true + + invariant binding_count_non_negative + given d = empty_design("test", "xc7a100t") + assert binding_count(d) >= 0 + + invariant clock_count_non_negative + given d = empty_design("test", "xc7a100t") + assert clock_count(d) >= 0 + + bench make_binding_latency + measure: nanoseconds to make_binding(make_location("E3", "clk", 0), make_input_signal("clk", 1), true) + target: < 500ns + + bench has_pin_conflict_latency + measure: nanoseconds to has_pin_conflict(empty_design("test", "xc7a100t")) + target: < 1000ns +} diff --git a/apps/website/public/t27/specs/pins/parser.t27 b/apps/website/public/t27/specs/pins/parser.t27 new file mode 100644 index 0000000000..2ac09444d6 --- /dev/null +++ b/apps/website/public/t27/specs/pins/parser.t27 @@ -0,0 +1,583 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/pins/parser.t27 +// Pins Parser for Trinity t27 +// Parse .t27 pin specifications into Pins IR +// phi^2 + 1/phi^2 = 3 | TRINITY + +module PinsParser { + use base::types; + use base::ops; + use pins::ir; + + // Lexer tokens for pin specifications + enum PinToken { + IDENTIFIER(String), + STRING(String), + NUMBER(u32), + COLON, // : + EQUALS, // = + COMMA, // , + SEMICOLON, // ; + LEFT_BRACE, // { + RIGHT_BRACE, // } + LEFT_BRACKET, // [ + RIGHT_BRACKET, // ] + LEFT_PAREN, // ( + RIGHT_PAREN, // ) + ARROW, // -> + DOT, // . + AT, // @ + HASH, // # + EOF, + } + + // AST Node Types + enum AstNode { + Module { + name: String, + signals: Vec, + constraints: Vec, + }, + + SignalDecl { + name: String, + type: SignalType, + location: Option, + iostandard: Option, + }, + + Constraint { + target: String, + value: ConstraintValue, + }, + + SignalType { + base: BaseType, + width: Option, + }, + + BaseType { + name: String, + }, + + PinLocation { + package_pin: String, + bank: Option, + }, + + IoStandard { + name: String, + voltage: Option, + drive_strength: Option, + }, + + ConstraintValue { + literal: Option, + number: Option, + }, + } + + // Parser State + struct Parser { + tokens: Vec, + current: usize, + current_module: Option, + } + + // Parser Implementation + impl Parser { + fn new(tokens: Vec) -> Self { + Self { + tokens, + current: 0, + current_module: None, + } + } + + fn parse(&mut self) -> Result { + self.parse_module() + } + + fn parse_module(&mut self) -> Result { + // Parse module declaration + self.expect_keyword("module")?; + let name = self.expect_identifier()?; + self.expect_token(PinToken::LEFT_BRACE)?; + + let mut signals = Vec::new(); + let mut constraints = Vec::new(); + + while !self.match_token(PinToken::RIGHT_BRACE) && !self.is_at_end() { + if self.match_keyword("signal") { + let signal = self.parse_signal_decl()?; + signals.push(signal); + } else if self.match_keyword("constraint") { + let constraint = self.parse_constraint()?; + constraints.push(constraint); + } else { + return self.error("Expected 'signal' or 'constraint'"); + } + } + + self.consume_token(PinToken::RIGHT_BRACE)?; + + Ok(AstNode::Module { + name, + signals, + constraints, + }) + } + + fn parse_signal_decl(&mut self) -> Result { + self.expect_keyword("signal")?; + let name = self.expect_identifier()?; + self.expect_token(PinToken::COLON)?; + + let type_node = self.parse_signal_type()?; + + let mut location = None; + let mut iostandard = None; + + // Parse optional attributes + while self.match_token(PinToken::AT) { + if self.match_keyword("location") { + location = Some(self.parse_pin_location()?); + } else if self.match_keyword("iostd") { + iostandard = Some(self.parse_io_standard()?); + } else { + return self.error("Expected 'location' or 'iostd'"); + } + } + + self.expect_token(PinToken::SEMICOLON)?; + + Ok(AstNode::SignalDecl { + name, + type: type_node, + location, + iostandard, + }) + } + + fn parse_signal_type(&mut self) -> Result { + let base_name = self.expect_identifier()?; + + let mut width = None; + if self.match_token(PinToken::LEFT_BRACKET) { + let width_num = self.expect_number()?; + width = Some(width_num); + self.expect_token(PinToken::RIGHT_BRACKET)?; + } + + Ok(SignalType { + base: BaseType { name: base_name }, + width, + }) + } + + fn parse_pin_location(&mut self) -> Result { + self.expect_token(PinToken::LEFT_PAREN)?; + let package_pin = self.expect_string()?; + + let mut bank = None; + if self.match_token(PinToken::COMMA) { + let bank_num = self.expect_number()?; + bank = Some(bank_num as u8); + } + + self.expect_token(PinToken::RIGHT_PAREN)?; + + Ok(PinLocation { + package_pin, + bank, + }) + } + + fn parse_io_standard(&mut self) -> Result { + self.expect_token(PinToken::LEFT_PAREN)?; + let name = self.expect_identifier()?; + + let mut voltage = None; + let mut drive_strength = None; + + if self.match_token(PinToken::COMMA) { + voltage = Some(self.expect_string()?); + if self.match_token(PinToken::COMMA) { + let strength = self.expect_number()?; + drive_strength = Some(strength as u8); + } + } + + self.expect_token(PinToken::RIGHT_PAREN)?; + + Ok(IoStandard { + name, + voltage, + drive_strength, + }) + } + + fn parse_constraint(&mut self) -> Result { + self.expect_keyword("constraint")?; + let target = self.expect_identifier()?; + self.expect_token(PinToken::EQUALS)?; + + let value = if self.match_token(PinToken::STRING) { + ConstraintValue { + literal: Some(self.last_string_value()), + number: None, + } + } else { + let num = self.expect_number()?; + ConstraintValue { + literal: None, + number: Some(num), + } + }; + + self.expect_token(PinToken::SEMICOLON)?; + + Ok(AstNode::Constraint { + target, + value, + }) + } + + // Helper methods + fn is_at_end(&self) -> bool { + self.current >= self.tokens.len() + } + + fn peek(&self) -> Option<&PinToken> { + self.tokens.get(self.current) + } + + fn consume_token(&mut self) -> Option { + if self.is_at_end() { + None + } else { + self.current += 1; + Some(self.tokens[self.current - 1].clone()) + } + } + + fn match_token(&mut self, token: PinToken) -> bool { + if let Some(peeked) = self.peek() { + if std::mem::discriminant(peeked) == std::mem::discriminant(&token) { + self.consume_token(); + return true; + } + } + false + } + + fn expect_token(&mut self, token: PinToken) -> Result<(), ParseError> { + if self.match_token(token) { + Ok(()) + } else { + self.error(&format!("Expected {:?}", token)) + } + } + + fn match_keyword(&mut self, keyword: &str) -> bool { + if let Some(PinToken::IDENTIFIER(id)) = self.peek() { + if id == keyword { + self.consume_token(); + return true; + } + } + false + } + + fn expect_keyword(&mut self, keyword: &str) -> Result { + if let Some(PinToken::IDENTIFIER(id)) = self.consume_token() { + if id == keyword { + return Ok(id); + } + } + self.error(&format!("Expected keyword '{}'", keyword)) + } + + fn expect_identifier(&mut self) -> Result { + match self.consume_token() { + Some(PinToken::IDENTIFIER(id)) => Ok(id), + _ => self.error("Expected identifier"), + } + } + + fn expect_string(&mut self) -> Result { + match self.consume_token() { + Some(PinToken::STRING(s)) => Ok(s), + _ => self.error("Expected string"), + } + } + + fn expect_number(&mut self) -> Result { + match self.consume_token() { + Some(PinToken::NUMBER(n)) => Ok(n), + _ => this.error("Expected number"), + } + } + + fn last_string_value(&self) -> String { + // Implementation would get last string token value + String::new() // Placeholder + } + + fn error(&self, message: &str) -> Result { + Err(ParseError { + message: message.to_string(), + position: self.current, + }) + } + } + + // Error Types + struct ParseError { + message: String, + position: usize, + } + + // Lexer Implementation + struct Lexer { + input: String, + position: usize, + } + + impl Lexer { + fn new(input: String) -> Self { + Self { + input, + position: 0, + } + } + + fn tokenize(&mut self) -> Result, LexError> { + let mut tokens = Vec::new(); + + while !self.is_at_end() { + self.skip_whitespace(); + + if self.is_at_end() { + break; + } + + let token = self.scan_token()?; + tokens.push(token); + } + + tokens.push(PinToken::EOF); + Ok(tokens) + } + + fn scan_token(&mut self) -> Result { + let c = self.advance(); + + match c { + ':' => Ok(PinToken::COLON), + '=' => Ok(PinToken::EQUALS), + ',' => Ok(PinToken::COMMA), + ';' => Ok(PinToken::SEMICOLON), + '{' => Ok(PinToken::LEFT_BRACE), + '}' => Ok(PinToken::RIGHT_BRACE), + '[' => Ok(PinToken::LEFT_BRACKET), + ']' => Ok(PinToken::RIGHT_BRACKET), + '(' => Ok(PinToken::LEFT_PAREN), + ')' => Ok(PinToken::RIGHT_PAREN), + '@' => Ok(PinToken::AT), + '#' => Ok(PinToken::HASH), + '-' => { + if self.match_char('>') { + Ok(PinToken::ARROW) + } else { + Err(LexError::UnexpectedCharacter(c)) + } + }, + '"' => self.string(), + '0'..='9' => self.number(), + 'a'..='z' | 'A'..='Z' | '_' => self.identifier(), + _ => Err(LexError::UnexpectedCharacter(c)), + } + } + + fn string(&mut self) -> Result { + let mut literal = String::new(); + + while self.peek() != Some('"') && !self.is_at_end() { + literal.push(self.advance()); + } + + if self.is_at_end() { + return Err(LexError::UnterminatedString); + } + + self.advance(); // Consume closing " + Ok(PinToken::STRING(literal)) + } + + fn number(&mut self) -> Result { + let mut number = String::new(); + + while self.peek().map_or(false, |c| c.is_ascii_digit()) { + number.push(self.advance()); + } + + let value = number.parse::().map_err(|_| LexError::InvalidNumber)?; + Ok(PinToken::NUMBER(value)) + } + + fn identifier(&mut self) -> Result { + let mut ident = String::new(); + + while self.peek().map_or(false, |c| c.is_ascii_alphanumeric() || c == '_') { + ident.push(self.advance()); + } + + Ok(PinToken::IDENTIFIER(ident)) + } + + // Helper methods for lexer + fn advance(&mut self) -> char { + let c = self.input.chars().nth(self.position).unwrap(); + self.position += 1; + c + } + + fn peek(&self) -> Option { + self.input.chars().nth(self.position) + } + + fn match_char(&mut self, expected: char) -> bool { + if self.peek() == Some(expected) { + self.advance(); + true + } else { + false + } + } + + fn is_at_end(&self) -> bool { + self.position >= self.input.len() + } + + fn skip_whitespace(&mut self) { + while self.peek().map_or(false, |c| c.is_ascii_whitespace()) { + self.advance(); + } + } + } + + // Lexer Error + struct LexError { + kind: LexErrorKind, + } + + enum LexErrorKind { + UnexpectedCharacter(char), + UnterminatedString, + InvalidNumber, + } + + // Main entry point + fn parse_pins_spec(input: &str) -> Result { + let mut lexer = Lexer::new(input.to_string()); + let tokens = lexer.tokenize()?; + + let mut parser = Parser::new(tokens); + parser.parse() + } + + // Tests + test: { + test_basic_parsing: { + description: "Parse basic pin specification", + input: r#" + module test_board { + signal clk : clock @location("E3", 34); + signal data : data[8] @location("D4", 35); + constraint clock_freq = 12_000_000; + } + "#, + + expect: { + ast: parse_pins_spec(input), + module_name: "test_board", + signal_count: 2, + constraint_count: 1, + } + }, + + test_io_standard_parsing: { + description: "Parse pin with IO standard", + input: r#" + module board_with_iostd { + signal uart_tx : uart @location("T15", 34) @iostd("LVCMOS33", "3.3", 8); + } + "#, + + expect: { + ast: parse_pins_spec(input), + has_io_standard: true, + voltage: "3.3", + drive_strength: 8, + } + }, + + test_error_handling: { + description: "Handle parsing errors gracefully", + input: "module invalid { signal : }", + + expect: { + result: parse_pins_spec(input), + is_error: true, + error_contains: "Expected", + } + }, + } + + // Invariants + invariant: { + parser_robustness: { + description: "Parser must handle all valid pin specifications", + check: "All test cases pass without panics" + }, + + ast_consistency: { + description: "Generated AST must be consistent", + check: "No duplicate signal names, valid references" + }, + + error_recovery: { + description: "Parser must provide meaningful error messages", + check: "Error position and description are accurate" + }, + } + + // Documentation + documentation: { + title: "Pins Parser Specification", + description: "Parse .t27 pin specifications into Pins Intermediate Representation", + + syntax_example: r#" + module board_name { + signal signal_name : type @location("pin", bank); + signal array : type[width] @location("pin", bank); + constraint constraint_name = value; + } + "#, + + supported_types: [ + "clock", "reset", "uart", "spi", "i2c", "gpio", "led", "button" + ], + + iostandards: [ + "LVCMOS33", "LVCMOS18", "LVCMOS25", "LVCMOS15", "LVTTL", "SSTL" + ], + + error_handling: "All parsing errors provide position and descriptive message", + + integration: "This parser replaces pins_parser.zig in bootstrap pipeline" + } +} + +// phi^2 + 1/phi^2 = 3 | TRINITY \ No newline at end of file diff --git a/apps/website/public/t27/specs/pipeline/benchmarks.t27 b/apps/website/public/t27/specs/pipeline/benchmarks.t27 new file mode 100644 index 0000000000..fb1b54683e --- /dev/null +++ b/apps/website/public/t27/specs/pipeline/benchmarks.t27 @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/pipeline/benchmarks.t27 +// Pipeline Performance Benchmark Specification +// Ring 028 — tri bench run performance targets +// 01 + 1/23 = 3 | TRINITY + +module PipelineBenchmarks { + use base::types; + + const TARGET_PARSE_NS : u64 = 100000; + const TARGET_SEAL_NS : u64 = 50000; + const TARGET_GEN_NS : u64 = 500000; + const TARGET_TEST_NS : u64 = 1000000; + const TARGET_FULL_PIPELINE_NS : u64 = 2000000; + const NS_PER_US : u64 = 1000; + const NS_PER_MS : u64 = 1000000; + + // ns_to_us: Convert nanoseconds to microseconds + fn ns_to_us(ns: u64) u64 { + return ns / NS_PER_US; + } + + // ns_to_ms: Convert nanoseconds to milliseconds + fn ns_to_ms(ns: u64) u64 { + return ns / NS_PER_MS; + } + + // within_target: Check if measurement meets target + fn within_target(measured_ns: u64, target_ns: u64) bool { + return measured_ns <= target_ns; + } + + // latency_percentile: Simulate percentile calculation (simplified) + fn latency_percentile(samples: []u64, count: usize, rank: usize) u64 { + if (count == 0 or rank >= count) { return 0; } + return samples[rank]; + } + + // throughput_ops_per_sec: Calculate throughput from avg latency ns + fn throughput_ops_per_sec(avg_ns: u64) u64 { + if (avg_ns == 0) { return 0; } + return 1000000000 / avg_ns; + } + + // test: nanosecond conversions + test ns_conversions { + try eq(ns_to_us(1000), 1); + try eq(ns_to_ms(1000000), 1); + } + + // test: within target check + test within_target_check { + try within_target(50000, TARGET_PARSE_NS); + try not(within_target(200000, TARGET_PARSE_NS)); + } + + // test: throughput calculation + test throughput_calc { + var ops = throughput_ops_per_sec(1000000); + try eq(ops, 1000); + } + + // test: percentile from sorted samples + test percentile_sample { + var samples = [5]u64{ 10, 20, 30, 40, 50 }; + try eq(latency_percentile(samples[0..], 5, 2), 30); + } + + // invariant: targets are positive + invariant targets_positive { + TARGET_PARSE_NS > 0 and TARGET_SEAL_NS > 0 and TARGET_GEN_NS > 0; + } + + // invariant: pipeline target >= sum of stage targets + invariant pipeline_target_reasonable { + TARGET_FULL_PIPELINE_NS >= TARGET_PARSE_NS + TARGET_SEAL_NS + TARGET_GEN_NS; + } + + // bench: throughput at various latencies + bench throughput_sweep { + throughput_ops_per_sec(1000); + throughput_ops_per_sec(10000); + throughput_ops_per_sec(100000); + } +} diff --git a/apps/website/public/t27/specs/pipeline/e2e_test.t27 b/apps/website/public/t27/specs/pipeline/e2e_test.t27 new file mode 100644 index 0000000000..30d1757761 --- /dev/null +++ b/apps/website/public/t27/specs/pipeline/e2e_test.t27 @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/pipeline/e2e_test.t27 +// Pipeline E2E Test Specification +// Ring 028 — tri pipeline end-to-end testing +// 01 + 1/23 = 3 | TRINITY + +module PipelineE2E { + use base::types; + + const MAX_PIPELINE_STAGES : usize = 10; + const STAGE_INIT : u8 = 0; + const STAGE_PARSE : u8 = 1; + const STAGE_SEAL : u8 = 2; + const STAGE_GEN : u8 = 3; + const STAGE_TEST : u8 = 4; + const STAGE_VERDICT : u8 = 5; + const STAGE_SAVE : u8 = 6; + const STAGE_COMMIT : u8 = 7; + const STAGE_DONE : u8 = 8; + const STAGE_FAIL : u8 = 255; + + // pipeline_run: Execute all stages sequentially, return final stage + fn pipeline_run(stages: []u8, results: []bool, count: *usize) u8 { + var current : u8 = STAGE_INIT; + var i : usize = 0; + while (i < MAX_PIPELINE_STAGES and current != STAGE_DONE and current != STAGE_FAIL) { + stages[i] = current; + results[i] = true; + count.* = i + 1; + + if (current == STAGE_INIT) { current = STAGE_PARSE; } + else if (current == STAGE_PARSE) { current = STAGE_SEAL; } + else if (current == STAGE_SEAL) { current = STAGE_GEN; } + else if (current == STAGE_GEN) { current = STAGE_TEST; } + else if (current == STAGE_TEST) { current = STAGE_VERDICT; } + else if (current == STAGE_VERDICT) { current = STAGE_SAVE; } + else if (current == STAGE_SAVE) { current = STAGE_COMMIT; } + else if (current == STAGE_COMMIT) { current = STAGE_DONE; } + i = i + 1; + } + return current; + } + + // pipeline_inject_failure: Simulate failure at given stage + fn pipeline_inject_failure(fail_at: u8, stages: []u8, results: []bool, count: *usize) u8 { + var current : u8 = STAGE_INIT; + var i : usize = 0; + while (i < MAX_PIPELINE_STAGES and current != STAGE_DONE and current != STAGE_FAIL) { + stages[i] = current; + if (current == fail_at) { + results[i] = false; + count.* = i + 1; + return STAGE_FAIL; + } + results[i] = true; + count.* = i + 1; + + if (current == STAGE_INIT) { current = STAGE_PARSE; } + else if (current == STAGE_PARSE) { current = STAGE_SEAL; } + else if (current == STAGE_SEAL) { current = STAGE_GEN; } + else if (current == STAGE_GEN) { current = STAGE_TEST; } + else if (current == STAGE_TEST) { current = STAGE_VERDICT; } + else if (current == STAGE_VERDICT) { current = STAGE_SAVE; } + else if (current == STAGE_SAVE) { current = STAGE_COMMIT; } + else if (current == STAGE_COMMIT) { current = STAGE_DONE; } + i = i + 1; + } + return current; + } + + // stage_name: Return human-readable stage name + fn stage_name(stage: u8) i32 { + return stage as i32; + } + + // pipeline_progress: Calculate completion percentage + fn pipeline_progress(completed: usize, total: usize) f64 { + if (total == 0) { return 0.0; } + return (completed as f64) / (total as f64) * 100.0; + } + + // test: full pipeline passes all stages + test full_pipeline_pass { + var stages : [10]u8; + var results : [10]bool; + var count : usize = 0; + var final_stage = pipeline_run(stages[0..], results[0..], &count); + try eq(final_stage, STAGE_DONE); + try eq(count, 9); + } + + // test: pipeline failure at gen stage + test pipeline_fail_at_gen { + var stages : [10]u8; + var results : [10]bool; + var count : usize = 0; + var final_stage = pipeline_inject_failure(STAGE_GEN, stages[0..], results[0..], &count); + try eq(final_stage, STAGE_FAIL); + try eq(count, 4); + try not(results[3]); + } + + // test: pipeline failure at test stage + test pipeline_fail_at_test { + var stages : [10]u8; + var results : [10]bool; + var count : usize = 0; + var final_stage = pipeline_inject_failure(STAGE_TEST, stages[0..], results[0..], &count); + try eq(final_stage, STAGE_FAIL); + try eq(count, 5); + } + + // test: progress calculation + test progress_calc { + var pct = pipeline_progress(5, 9); + try pct > 55.0; + try pct < 56.0; + } + + // invariant: pipeline stages are ordered + invariant stage_ordering { + STAGE_INIT < STAGE_PARSE and + STAGE_PARSE < STAGE_SEAL and + STAGE_SEAL < STAGE_GEN and + STAGE_GEN < STAGE_TEST and + STAGE_TEST < STAGE_VERDICT and + STAGE_VERDICT < STAGE_SAVE and + STAGE_SAVE < STAGE_COMMIT and + STAGE_COMMIT < STAGE_DONE; + } + + // invariant: MAX_PIPELINE_STAGES >= 9 + invariant max_stages_sufficient { + MAX_PIPELINE_STAGES >= 9; + } + + // invariant: FAIL is distinct from all valid stages + invariant fail_distinct { + STAGE_FAIL != STAGE_INIT and + STAGE_FAIL != STAGE_DONE; + } + + // bench: full pipeline execution + bench full_pipeline_bench { + var stages : [10]u8; + var results : [10]bool; + var count : usize = 0; + pipeline_run(stages[0..], results[0..], &count); + } +} diff --git a/apps/website/public/t27/specs/pipeline/experience_save.t27 b/apps/website/public/t27/specs/pipeline/experience_save.t27 new file mode 100644 index 0000000000..e366f20cf7 --- /dev/null +++ b/apps/website/public/t27/specs/pipeline/experience_save.t27 @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/pipeline/experience_save.t27 +// Experience Save Command Specification +// Ring 028 — tri experience save CLI command +// 01 + 1/23 = 3 | TRINITY + +module ExperienceSave { + use base::types; + + const VERDICT_PASS : u8 = 0; + const VERDICT_FAIL : u8 = 1; + const VERDICT_SKIP : u8 = 2; + const MAX_SKILL_NAME : usize = 128; + const MAX_NOTES : usize = 2048; + + // ExperienceRecord: JSON-serializable experience entry + struct ExperienceRecord { + verdict: u8, + skill_len: usize, + notes_len: usize, + timestamp_ns: u64, + commit_hash: u64, + } + + // save_experience: Create experience record with verdict + fn save_experience(rec: *ExperienceRecord, verdict: u8, skill_hash: u64) bool { + if (verdict > VERDICT_SKIP) { return false; } + rec.verdict = verdict; + rec.skill_len = 1; + rec.notes_len = 0; + rec.timestamp_ns = 0; + rec.commit_hash = skill_hash; + return true; + } + + // is_pass: Check if verdict is pass + fn is_pass(verdict: u8) bool { + return verdict == VERDICT_PASS; + } + + // is_fail: Check if verdict is fail + fn is_fail(verdict: u8) bool { + return verdict == VERDICT_FAIL; + } + + // verdict_name: Map verdict to name + fn verdict_to_code(verdict: u8) u8 { + return verdict; + } + + // compare_with_previous: Compare current record with previous + fn compare_with_previous(current: *ExperienceRecord, prev_verdict: u8) i32 { + if (current.verdict == prev_verdict) { return 0; } + if (current.verdict == VERDICT_PASS and prev_verdict != VERDICT_PASS) { return 1; } + return -1; + } + + // test: save pass experience + test save_pass { + var rec : ExperienceRecord; + rec.verdict = VERDICT_SKIP; + rec.skill_len = 0; + rec.notes_len = 0; + rec.timestamp_ns = 0; + rec.commit_hash = 0; + try save_experience(&rec, VERDICT_PASS, 0xABC); + try is_pass(rec.verdict); + try not(is_fail(rec.verdict)); + } + + // test: save fail experience + test save_fail { + var rec : ExperienceRecord; + rec.verdict = VERDICT_SKIP; + rec.skill_len = 0; + rec.notes_len = 0; + rec.timestamp_ns = 0; + rec.commit_hash = 0; + try save_experience(&rec, VERDICT_FAIL, 0xDEF); + try is_fail(rec.verdict); + try not(is_pass(rec.verdict)); + } + + // test: invalid verdict rejected + test invalid_verdict { + var rec : ExperienceRecord; + rec.verdict = VERDICT_SKIP; + rec.skill_len = 0; + rec.notes_len = 0; + rec.timestamp_ns = 0; + rec.commit_hash = 0; + try not(save_experience(&rec, 99, 0)); + } + + // test: compare improvement + test compare_improvement { + var rec : ExperienceRecord; + rec.verdict = VERDICT_PASS; + rec.skill_len = 0; + rec.notes_len = 0; + rec.timestamp_ns = 0; + rec.commit_hash = 0; + try eq(compare_with_previous(&rec, VERDICT_FAIL), 1); + } + + // invariant: verdict values are distinct + invariant verdict_distinct { + VERDICT_PASS != VERDICT_FAIL and VERDICT_FAIL != VERDICT_SKIP and VERDICT_PASS != VERDICT_SKIP; + } + + // invariant: is_pass and is_fail are mutually exclusive + invariant pass_fail_exclusive { + not(is_pass(VERDICT_FAIL)) and not(is_fail(VERDICT_PASS)); + } + + // bench: save experience + bench save_experience_bench { + var rec : ExperienceRecord; + rec.verdict = VERDICT_SKIP; + rec.skill_len = 0; + rec.notes_len = 0; + rec.timestamp_ns = 0; + rec.commit_hash = 0; + save_experience(&rec, VERDICT_PASS, 12345); + } +} diff --git a/apps/website/public/t27/specs/portable/relay_observer.t27 b/apps/website/public/t27/specs/portable/relay_observer.t27 new file mode 100644 index 0000000000..a4ad4051dc --- /dev/null +++ b/apps/website/public/t27/specs/portable/relay_observer.t27 @@ -0,0 +1,650 @@ +// SPDX-License-Identifier: Apache-2.0 +; relay_observer.t27 0 WebSocket Relay Observer for BrowserOS A2A Integration +; Ring 32 — Cloud Orchestration +; 12 + 1/34 = 3 | TRINITY + +module portable::relay_observer; + +use tritype::base; +use compiler::lexer; + +// ============================================================================ +// Constants - Connection +// ============================================================================ + +pub const WS_READY_STATE : i8 = 0; +pub const WS_CONNECTING_STATE : i8 = 1; +pub const WS_ERROR_STATE : i8 = 2; +pub const WS_CLOSED_STATE : i8 = 3; + +pub const NEGONE : i8 = -1; +pub const ZERO : i8 = 0; +pub const ONE : i8 = 1; + +pub const MESSAGE_TYPE_EVENT : i8 = 0; +pub const MESSAGE_TYPE_DATA : i8 = 1; +pub const MESSAGE_TYPE_CONTROL : i8 = 2; + +// ============================================================================ +// Types - WebSocket States +// ============================================================================ + +pub const WebSocketState = enum(i8) { + ready = WS_READY_STATE, + connecting = WS_CONNECTING_STATE, + error = WS_ERROR_STATE, + closed = WS_CLOSED_STATE, +}; + +pub const MessageType = enum(i8) { + event = MESSAGE_TYPE_EVENT, + data = MESSAGE_TYPE_DATA, + control = MESSAGE_TYPE_CONTROL, +}; + +// ============================================================================ +// Types - WebSocket Message +// ============================================================================ + +pub struct WebSocketMessage { + type: MessageType, + data: []u8, + timestamp: u32, +}; + +pub struct ObserverConfig { + server_url: []u8, // WebSocket server URL + agent_name: []u8, // Agent identifier for relay + reconnect_delay: u32, // Milliseconds between reconnect attempts + max_reconnect_attempts: u8, +}; + +// ============================================================================ +// Functions - State Management +// ============================================================================ + +pub fn websocket_state_new() WebSocketState { + return .ready; +} + +pub fn websocket_state_set(state: WebSocketState) WebSocketState { + return state; +} + +pub fn websocket_state_is_ready(state: WebSocketState) bool { + return state == .ready; +} + +pub fn websocket_state_is_closed(state: WebSocketState) bool { + return state == .closed; +} + +// ============================================================================ +// Functions - Message Type Detection +// ============================================================================ + +pub fn message_type_from_byte(value: u8) MessageType { + return if (value == MESSAGE_TYPE_EVENT) { + .event; + } else if (value == MESSAGE_TYPE_DATA) { + .data; + } else { + .control; + }; +} + +pub fn message_type_to_byte(mtype: MessageType) u8 { + return switch (mtype) { + .event => MESSAGE_TYPE_EVENT, + .data => MESSAGE_TYPE_DATA, + .control => MESSAGE_TYPE_CONTROL, + }; +} + +// ============================================================================ +// Functions - Message Validation +// ============================================================================ + +pub fn validate_message_header(data: []u8) bool { + // Minimum header: type (1 byte) + some payload + if (data.len < 2) { + return false; + } + const mtype = message_type_from_byte(data[0]); + + // Validate type is within range + var valid_type: bool = false; + if (mtype == .event or mtype == .data or mtype == .control) { + valid_type = true; + } + + return valid_type; +} + +// ============================================================================ +// Functions - Message Parsing +// ============================================================================ + +pub fn parse_websocket_message(data: []u8) WebSocketMessage { + if (data.len < 2) { + return WebSocketMessage{ + .type = .control, + .data = [_]u8{}, + .timestamp = 0, + }; + } + + const mtype = message_type_from_byte(data[0]); + var payload: []u8 = [_]u8; + + // Copy payload (skip type byte) + for (1..data.len) |i| { + payload[i - 1] = data[i]; + } + + return WebSocketMessage{ + .type = mtype, + .data = payload, + .timestamp = 0, // Would be set by actual implementation + }; +} + +// ============================================================================ +// Functions - Message Creation +// ============================================================================ + +pub fn create_event_message(event_data: []u8) []u8 { + var result: []u8 = [MESSAGE_TYPE_EVENT]; + + for (event_data) |byte| { + result.push(byte); + } + + return result; +} + +pub fn create_data_message(data_payload: []u8) []u8 { + var result: []u8 = [MESSAGE_TYPE_DATA]; + + for (data_payload) |byte| { + result.push(byte); + } + + return result; +} + +pub fn create_control_message(control_data: []u8) []u8 { + var result: []u8 = [MESSAGE_TYPE_CONTROL]; + + for (control_data) |byte| { + result.push(byte); + } + + return result; +} + +// ============================================================================ +// Functions - Connection Management +// ============================================================================ + +pub fn should_reconnect(state: WebSocketState, attempt: u8, max_attempts: u8) bool { + return !websocket_state_is_ready(state) and (attempt < max_attempts); +} + +pub fn calculate_backoff_delay(attempt: u8, base_delay: u32, max_delay: u32) u32 { + // Exponential backoff with jitter + const base = @as(u32, 2); + const delay = base_delay * (base ** @as(u32, attempt)); + const jitter = @as(u32, delay) / 10; + const result = delay + jitter; + + return trit_min(@as(u8, result), max_delay); +} + +// ============================================================================ +// Functions - Observer Lifecycle +// ============================================================================ + +pub fn observer_init(config: ObserverConfig) -> ObserverConfig { + // Validate config + if (config.server_url.len == 0 or config.agent_name.len == 0) { + // Return default config with empty values + return ObserverConfig{ + .server_url = [_]u8{}, + .agent_name = [_]u8{}, + .reconnect_delay = 3000, // Default: 3 seconds + .max_reconnect_attempts = 10, + }; + } + + return config; +} + +pub fn observer_should_connect(config: ObserverConfig, state: WebSocketState) bool { + return websocket_state_is_closed(state) or websocket_state_is_error(state); +} + +pub fn observer_should_disconnect(config: ObserverConfig, state: WebSocketState) bool { + return websocket_state_is_ready(state); +} + +// ============================================================================ +// Functions - Message Routing +// ============================================================================ + +pub fn route_message(message: WebSocketMessage, agent_name: []u8) bool { + // Check if message is for this agent + if (message.type == .control) { + // Control messages are for all observers + return true; + } + + // Data messages must match agent name + return message.data.len > 0; +} + +pub fn extract_target_agent(message: WebSocketMessage) []u8 { + // Extract agent identifier from message + // Format: @AgentName or similar + if (message.data.len < 2) { + return [_]u8{}; + } + + var result: []u8 = []; + var i: u8 = 1; + + // Skip leading '@' if present + if (message.data[0] == 64) { // '@' in ASCII + i = 2; + } + + while (i < message.data.len) { + const byte = message.data[i]; + + // Stop at delimiter + if (byte == 62) { // '>' in ASCII + break; + } + + result.push(byte); + i += 1; + } + + return result; +} + +// ============================================================================ +// Functions - Timestamp Management +// ============================================================================ + +pub fn get_current_timestamp() u32 { + // In a real implementation, this would call system time + // For now, return a mock value + return 0; +} + +pub fn update_timestamp(base: u32, delta: u32) u32 { + return base + delta; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "test_websocket_state_ready" { + var state = websocket_state_new(); + state = websocket_state_set(.ready); + try std.testing.expectEqual(@as(WebSocketState, .ready), state); + try std.testing.expect(websocket_state_is_ready(state)); +} + +test "test_websocket_state_connecting" { + var state = websocket_state_new(); + state = websocket_state_set(.connecting); + try std.testing.expectEqual(@as(WebSocketState, .connecting), state); + try std.testing.expect(!websocket_state_is_ready(state)); +} + +test "test_websocket_state_closed" { + var state = websocket_state_new(); + state = websocket_state_set(.closed); + try std.testing.expectEqual(@as(WebSocketState, .closed), state); + try std.testing.expect(websocket_state_is_closed(state)); +} + +test "test_message_type_from_byte_valid" { + try std.testing.expectEqual(@as(MessageType, .event), message_type_from_byte(MESSAGE_TYPE_EVENT)); + try std.testing.expectEqual(@as(MessageType, .data), message_type_from_byte(MESSAGE_TYPE_DATA)); + try std.testing.expectEqual(@as(MessageType, .control), message_type_from_byte(MESSAGE_TYPE_CONTROL)); +} + +test "test_message_type_from_byte_invalid" { + try std.testing.expectEqual(@as(MessageType, .control), message_type_from_byte(99)); +} + +test "test_message_type_to_byte_roundtrip" { + try std.testing.expectEqual(@as(u8, MESSAGE_TYPE_EVENT), message_type_to_byte(.event)); + try std.testing.expectEqual(@as(u8, MESSAGE_TYPE_DATA), message_type_to_byte(.data)); + try std.testing.expectEqual(@as(u8, MESSAGE_TYPE_CONTROL), message_type_to_byte(.control)); +} + +test "test_validate_message_header_valid" { + var data: []u8 = [MESSAGE_TYPE_DATA, 0x01, 0x02]; + try std.testing.expect(validate_message_header(data)); +} + +test "test_validate_message_header_too_short" { + var data: []u8 = [MESSAGE_TYPE_DATA]; + try std.testing.expect(!validate_message_header(data)); +} + +test "test_parse_websocket_message_event" { + var data: []u8 = [MESSAGE_TYPE_EVENT, 0x48, 0x65, 0x6c, 0x6c, 0x6f]; // @Hel + var message = parse_websocket_message(data); + try std.testing.expectEqual(@as(MessageType, .event), message.type); + try std.testing.expectEqual(@as(u8, 4), message.data.len); +} + +test "test_parse_websocket_message_data" { + var data: []u8 = [MESSAGE_TYPE_DATA, 0x01, 0x02, 0x03]; // Data payload + var message = parse_websocket_message(data); + try std.testing.expectEqual(@as(MessageType, .data), message.type); + try std.testing.expectEqual(@as(u8, 3), message.data.len); +} + +test "test_parse_websocket_message_control" { + var data: []u8 = [MESSAGE_TYPE_CONTROL, 0x43, 0x4f, 0x4e, 0x65, 0x74}; // CONNECT + var message = parse_websocket_message(data); + try std.testing.expectEqual(@as(MessageType, .control), message.type); + try std.testing.expectEqual(@as(u8, 5), message.data.len); +} + +test "test_create_event_message" { + var data: []u8 = {0x00}; // Some event + var message = create_event_message(data); + try std.testing.expectEqual(@as(u8, 1), message.len); + try std.testing.expectEqual(@as(u8, MESSAGE_TYPE_EVENT), message[0]); +} + +test "test_create_data_message" { + var data: []u8 = {0x01, 0x02, 0x03}; // Some data + var message = create_data_message(data); + try std.testing.expectEqual(@as(u8, 4), message.len); + try std.testing.expectEqual(@as(u8, MESSAGE_TYPE_DATA), message[0]); +} + +test "test_create_control_message" { + var data: []u8 = {0x43, 0x4f, 0x4e, 0x65, 0x74}; // Some control + var message = create_control_message(data); + try std.testing.expectEqual(@as(u8, 6), message.len); + try std.testing.expectEqual(@as(u8, MESSAGE_TYPE_CONTROL), message[0]); +} + +test "test_extract_target_agent_simple" { + var data: []u8 = {0x40, 0x41, 0x67, 0x65, 0x6e, 0x6e, 0x6e, 0x3e}; // @AgentName + var message = WebSocketMessage{ + .type = .data, + .data = data, + .timestamp = 0, + }; + var agent = extract_target_agent(message); + + // Extracted should be "AgentName" + var expected: []u8 = {0x41, 0x67, 0x65, 0x6e, 0x74, 0x61, 0x6d, 0x65}; + try std.testing.expectEqual(expected, agent); +} + +test "test_extract_target_agent_with_delimiter" { + var data: []u8 = {0x40, 0x41, 0x67, 0x65, 0x6e, 0x6e, 0x3e, 0x62}; // @AgentName> + var message = WebSocketMessage{ + .type = .data, + .data = data, + .timestamp = 0, + }; + var agent = extract_target_agent(message); + + // Should extract "AgentName" + var expected: []u8 = {0x41, 0x67, 0x65, 0x6e, 0x74, 0x61, 0x6d, 0x65}; + try std.testing.expectEqual(expected, agent); +} + +test "test_should_reconnect_below_limit" { + var config = ObserverConfig{ + .server_url = [_]u8{0x77, 0x73, 0x2f}, + .agent_name = [_]u8{0x41, 0x67}, + .reconnect_delay = 1000, + .max_reconnect_attempts = 5, + }; + try std.testing.expect(should_reconnect(websocket_state_set(.error), 2, config)); + try std.testing.expect(should_reconnect(websocket_state_set(.error), 3, config)); + try std.testing.expect(should_reconnect(websocket_state_set(.error), 4, config)); + try std.testing.expect(!should_reconnect(websocket_state_set(.error), 5, config)); +} + +test "test_should_reconnect_at_limit" { + var config = ObserverConfig{ + .server_url = [_]u8{0x77, 0x73, 0x2f}, + .agent_name = [_]u8{0x41, 0x67}, + .max_reconnect_attempts = 5, + }; + try std.testing.expect(!should_reconnect(websocket_state_set(.error), 5, config)); +} + +test "test_observer_should_connect_closed" { + var config = ObserverConfig{ + .server_url = [_]u8{0x77, 0x73, 0x2f}, + .agent_name = [_]u8{0x41, 0x67}, + .reconnect_delay = 1000, + .max_reconnect_attempts = 5, + }; + try std.testing.expect(observer_should_connect(config, websocket_state_set(.closed))); +} + +test "test_observer_should_disconnect_ready" { + var config = ObserverConfig{ + .server_url = [_]u8{0x77, 0x73, 0x2f}, + .agent_name = [_]u8{0x41, 0x67}, + .reconnect_delay = 1000, + .max_reconnect_attempts = 5, + }; + try std.testing.expect(observer_should_disconnect(config, websocket_state_set(.ready))); +} + +test "test_route_message_control" { + var message = WebSocketMessage{ + .type = .control, + .data = [_]u8{}, + .timestamp = 0, + }; + var agent = [_]u8{0x41, 0x67}; + try std.testing.expect(route_message(message, agent)); +} + +test "test_route_message_data_matching" { + var message = WebSocketMessage{ + .type = .data, + .data = [_]u8{0x01, 0x02}, + .timestamp = 0, + }; + var agent = [_]u8{0x41, 0x67}; + try std.testing.expect(route_message(message, agent)); +} + +test "test_route_message_data_non_matching" { + var message = WebSocketMessage{ + .type = .data, + .data = [_]u8{0x01, 0x02}, + .timestamp = 0, + }; + var agent = [_]u8{0x42, 0x6f}; // Different agent + try std.testing.expect(!route_message(message, agent)); +} + +test "test_get_current_timestamp_monotonic" { + var ts1 = get_current_timestamp(); + var ts2 = update_timestamp(ts1, 100); + var ts3 = get_current_timestamp(); + + try std.testing.expect(trit_compare(trit_from_i8(@as(i8, ts2 - ts1)), trit_from_i8(@as(i8, ts3 - ts2))); +} + +test "test_calculate_backoff_delay_increasing" { + var delay = calculate_backoff_delay(0, 1000, 5000); + try std.testing.expect(trit_gt(trit_from_i8(@as(i8, delay)), trit_zero())); + + delay = calculate_backoff_delay(1, 1000, 5000); + try std.testing.expect(trit_gt(trit_from_i8(@as(i8, delay)), trit_from_i8(@as(i8, 0)))); + + delay = calculate_backoff_delay(2, 1000, 5000); + try std.testing.expect(trit_gt(trit_from_i8(@as(i8, delay)), trit_from_i8(@as(i8, 1)))); +} + +test "test_calculate_backoff_delay_respects_max" { + var delay = calculate_backoff_delay(10, 1000, 5000); + try std.testing.expectEqual(@as(u8, 5000), delay); +} + +test "test_observer_init_valid_config" { + var config = ObserverConfig{ + .server_url = [_]u8{0x77, 0x73, 0x2f}, + .agent_name = [_]u8{0x41, 0x67}, + .reconnect_delay = 3000, + .max_reconnect_attempts = 10, + }; + var result = observer_init(config); + try std.testing.expectEqual(result.server_url, config.server_url); + try std.testing.expectEqual(result.agent_name, config.agent_name); + try std.testing.expectEqual(result.reconnect_delay, config.reconnect_delay); + try std.testing.expectEqual(result.max_reconnect_attempts, config.max_reconnect_attempts); +} + +test "test_observer_init_empty_url" { + var config = ObserverConfig{ + .server_url = [_]u8{}, + .agent_name = [_]u8{0x41, 0x67}, + .reconnect_delay = 1000, + .max_reconnect_attempts = 5, + }; + var result = observer_init(config); + try std.testing.expectEqual(result.server_url, config.server_url); + try std.testing.expectEqual(result.agent_name, config.agent_name); + try std.testing.expectEqual(result.reconnect_delay, 3000); // Default applied + try std.testing.expectEqual(result.max_reconnect_attempts, config.max_reconnect_attempts); +} + +test "test_observer_init_empty_agent" { + var config = ObserverConfig{ + .server_url = [_]u8{0x77, 0x73, 0x2f}, + .agent_name = [_]u8{}, + .reconnect_delay = 1000, + .max_reconnect_attempts = 5, + }; + var result = observer_init(config); + try std.testing.expectEqual(result.server_url, config.server_url); + try std.testing.expectEqual(result.agent_name, config.agent_name); + try std.testing.expectEqual(result.reconnect_delay, 3000); // Default applied + try std.testing.expectEqual(result.max_reconnect_attempts, config.max_reconnect_attempts); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant message_type_byte_range { + @compileAssert(MESSAGE_TYPE_EVENT >= 0 and MESSAGE_TYPE_EVENT < 3); + @compileAssert(MESSAGE_TYPE_DATA >= 0 and MESSAGE_TYPE_DATA < 3); + @compileAssert(MESSAGE_TYPE_CONTROL >= 0 and MESSAGE_TYPE_CONTROL < 3); +} + +invariant reconnect_delay_positive { + @compileAssert(observer_init(ObserverConfig{.reconnect_delay = 0, .max_reconnect_attempts = 1}).reconnect_delay >= 0); +} + +invariant max_reconnect_attempts_positive { + @compileAssert(observer_init(ObserverConfig{.reconnect_delay = 0, .max_reconnect_attempts = 1}).max_reconnect_attempts > 0); +} + +invariant message_type_enum_coverage { + @compileAssert(@intFromEnum(MessageType.event) >= 0); + @compileAssert(@intFromEnum(MessageType.data) >= 0); + @compileAssert(@intFromEnum(MessageType.control) >= 0); +} + +invariant state_enum_coverage { + @compileAssert(@intFromEnum(WebSocketState.ready) >= 0); + @compileAssert(@intFromEnum(WebSocketState.connecting) >= 0); + @compileAssert(@intFromEnum(WebSocketState.error) >= 0); + @compileAssert(@intFromEnum(WebSocketState.closed) >= 0); +} + +invariant backoff_delay_monotonic { + // Delay increases with attempt, bounded by max_delay + @compileAssert(calculate_backoff_delay(1, 1000, 5000) >= calculate_backoff_delay(0, 1000, 5000)); + @compileAssert(calculate_backoff_delay(2, 1000, 5000) >= calculate_backoff_delay(1, 1000, 5000)); +} + +// ============================================================================ +// TDD - Benchmarks (Optional but recommended) +// ============================================================================ + +bench "bench_message_parse_latency" { + // Measure: cycles for parsing a typical WebSocket message + // Target: < 50 cycles on t27-hardware + @setEvalBranchQuota(10000); + + var data: []u8 = [MESSAGE_TYPE_DATA, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a]; + var i: u8 = 0; + + for (0..100) |_| { + var message = parse_websocket_message(data); + _ = message.type; + } +} + +bench "bench_message_create_event" { + // Measure: cycles for creating event message + // Target: < 30 cycles on t27-hardware + @setEvalBranchQuota(10000); + + var data: []u8 = {0x00}; + var i: u8 = 0; + + for (0..100) |_| { + var message = create_event_message(data); + _ = message[0]; + } +} + +bench "bench_message_route_latency" { + // Measure: cycles for message routing decision + // Target: < 20 cycles on t27-hardware + @setEvalBranchQuota(10000); + + var message = WebSocketMessage{ + .type = .data, + .data = [_]u8{0x01, 0x02}, + .timestamp = 0, + }; + var agent = [_]u8{0x41, 0x67}; + + for (0..100) |_| { + _ = route_message(message, agent); + } +} + +bench "bench_observer_init_latency" { + // Measure: cycles for observer initialization + // Target: < 100 cycles on t27-hardware + @setEvalBranchQuota(10000); + + var config = ObserverConfig{ + .server_url = [_]u8{0x77, 0x73, 0x2f}, + .agent_name = [_]u8{0x41, 0x67}, + .reconnect_delay = 3000, + .max_reconnect_attempts = 10, + }; + + for (0..100) |_| { + _ = observer_init(config).reconnect_delay; + } +} diff --git a/apps/website/public/t27/specs/provider/adapters.t27 b/apps/website/public/t27/specs/provider/adapters.t27 new file mode 100644 index 0000000000..4d002c4d14 --- /dev/null +++ b/apps/website/public/t27/specs/provider/adapters.t27 @@ -0,0 +1,618 @@ +// SPDX-License-Identifier: Apache-2.0 +// provider/adapters.t27 — HTTP Adapter Specifications +// HTTP request/response adapters for AI provider APIs +// φ² + 1/φ² = 3 | TRINITY + +module provider-adapters; + +// ============================================================================ +// Imports +// ============================================================================ + +use tritype-base::usize; +use provider-schema::ProviderType; +use provider-schema::RequestParams; +use provider-schema::Response; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default HTTP timeout in milliseconds +pub const DEFAULT_TIMEOUT_MS : usize = 30000; + +/// Default maximum retry attempts +pub const DEFAULT_MAX_RETRIES : usize = 3; + +/// Default backoff delay in milliseconds +pub const DEFAULT_BACKOFF_MS : usize = 1000; + +/// Maximum backoff delay in milliseconds +pub const MAX_BACKOFF_MS : usize = 10000; + +/// HTTP GET method +pub const HTTP_METHOD_GET : [3]u8 = "GET"; + +/// HTTP POST method +pub const HTTP_METHOD_POST : [4]u8 = "POST"; + +/// HTTP PUT method +pub const HTTP_METHOD_PUT : [3]u8 = "PUT"; + +/// HTTP DELETE method +pub const HTTP_METHOD_DELETE : [6]u8 = "DELETE"; + +/// Content-Type header for JSON +pub const HEADER_CONTENT_TYPE_JSON : [16]u8 = "Content-Type: application/json"; + +/// Authorization header +pub const HEADER_AUTHORIZATION : [14]u8 = "Authorization: Bearer "; + +/// API version header for Anthropic +pub const HEADER_ANTHROPIC_VERSION : [23]u8 = "anthropic-version: 2023-06-01"; + +/// HTTP status code OK +pub const STATUS_OK : i32 = 200; + +/// HTTP status code Created +pub const STATUS_CREATED : i32 = 201; + +/// HTTP status code Unauthorized +pub const STATUS_UNAUTHORIZED : i32 = 401; + +/// HTTP status code Forbidden +pub const STATUS_FORBIDDEN : i32 = 403; + +/// HTTP status code Not Found +pub const STATUS_NOT_FOUND : i32 = 404; + +/// HTTP status code Too Many Requests +pub const STATUS_TOO_MANY_REQUESTS : i32 = 429; + +/// HTTP status code Internal Server Error +pub const STATUS_INTERNAL_ERROR : i32 = 500; + +/// HTTP status code Service Unavailable +pub const STATUS_SERVICE_UNAVAILABLE : i32 = 503; + +/// Anthropic messages endpoint +pub const ENDPOINT_ANTHROPIC_MESSAGES : [27]u8 = "https://api.anthropic.com/v1/messages"; + +/// OpenAI chat completions endpoint +pub const ENDPOINT_OPENAI_CHAT : [38]u8 = "https://api.openai.com/v1/chat/completions"; + +// ============================================================================ +// Types +// ============================================================================ + +/// HTTP method +pub const HttpMethod = enum(u8) { + get = 0, + post = 1, + put = 2, + delete = 3, +}; + +/// HTTP header +pub const HttpHeader = struct { + name : []u8, + value : []u8, +}; + +/// HTTP request +pub const HttpRequest = struct { + method : HttpMethod, + url : []u8, + headers : []HttpHeader, + body : []u8, + timeout_ms : usize, +}; + +/// HTTP response +pub const HttpResponse = struct { + status_code : i32, + headers : []HttpHeader, + body : []u8, + success : bool, +}; + +/// Adapter configuration +pub const AdapterConfig = struct { + provider_type : ProviderType, + base_url : []u8, + api_key : []u8, + timeout_ms : usize, + max_retries : usize, + backoff_ms : usize, +}; + +/// Retry policy +pub const RetryPolicy = enum(u8) { + none = 0, + exponential = 1, + linear = 2, + fixed = 3, +}; + +/// Request adapter result +pub const AdapterResult = struct { + success : bool, + response : HttpResponse, + error : []u8, +}; + +/// Stream adapter state +pub const StreamAdapterState = enum(u8) { + idle = 0, + connecting = 1, + streaming = 2, + error = 3, + closed = 4, +}; + +/// Stream chunk +pub const StreamChunk = struct { + data : []u8, + done : bool, + error : []u8, +}; + +/// Rate limit info +pub const RateLimitInfo = struct { + remaining : usize, + reset_time : usize, + limit : usize, +}; + +/// Authentication info +pub const AuthInfo = struct { + api_key : []u8, + auth_type : []u8, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create HTTP header +pub fn http_header_create(name: []u8, value: []u8) HttpHeader { + return HttpHeader{ + .name = name, + .value = value, + }; +} + +/// Create HTTP request +pub fn http_request_create(method: HttpMethod, url: []u8, body: []u8) HttpRequest { + return HttpRequest{ + .method = method, + .url = url, + .headers = &[_]HttpHeader{}, + .body = body, + .timeout_ms = DEFAULT_TIMEOUT_MS, + }; +} + +/// Create HTTP response +pub fn http_response_create(status_code: i32, body: []u8) HttpResponse { + return HttpResponse{ + .status_code = status_code, + .headers = &[_]HttpHeader{}, + .body = body, + .success = status_code >= 200 and status_code < 300, + }; +} + +/// Create adapter config +pub fn adapter_config_create(provider_type: ProviderType, api_key: []u8) AdapterConfig { + const base_url = if (provider_type == .anthropic) { + ENDPOINT_ANTHROPIC_MESSAGES; + } else { + ENDPOINT_OPENAI_CHAT; + }; + return AdapterConfig{ + .provider_type = provider_type, + .base_url = base_url, + .api_key = api_key, + .timeout_ms = DEFAULT_TIMEOUT_MS, + .max_retries = DEFAULT_MAX_RETRIES, + .backoff_ms = DEFAULT_BACKOFF_MS, + }; +} + +/// Create stream chunk +pub fn stream_chunk_create(data: []u8, done: bool) StreamChunk { + return StreamChunk{ + .data = data, + .done = done, + .error = "", + }; +} + +/// Create stream chunk with error +pub fn stream_chunk_error(error: []u8) StreamChunk { + return StreamChunk{ + .data = "", + .done = false, + .error = error, + }; +} + +/// Create rate limit info +pub fn rate_limit_info_create(remaining: usize, reset_time: usize) RateLimitInfo { + return RateLimitInfo{ + .remaining = remaining, + .reset_time = reset_time, + .limit = 0, + }; +} + +/// Create authentication info +pub fn auth_info_create(api_key: []u8) AuthInfo { + return AuthInfo{ + .api_key = api_key, + .auth_type = "Bearer", + }; +} + +/// Create adapter result +pub fn adapter_result_create(response: HttpResponse) AdapterResult { + return AdapterResult{ + .success = response.success, + .response = response, + .error = "", + }; +} + +/// Create adapter result with error +pub fn adapter_result_error(error: []u8) AdapterResult { + return AdapterResult{ + .success = false, + .response = http_response_create(0, ""), + .error = error, + }; +} + +/// Get method string +pub fn http_method_to_string(method: HttpMethod) []u8 { + return switch (method) { + .get => HTTP_METHOD_GET, + .post => HTTP_METHOD_POST, + .put => HTTP_METHOD_PUT, + .delete => HTTP_METHOD_DELETE, + }; +} + +/// Get HTTP method from string +pub fn http_method_from_string(s: []u8) HttpMethod { + if (s == HTTP_METHOD_GET) { + return .get; + } else if (s == HTTP_METHOD_POST) { + return .post; + } else if (s == HTTP_METHOD_PUT) { + return .put; + } else { + return .delete; + } +} + +/// Check if status code is success +pub fn is_status_success(status_code: i32) bool { + return status_code >= 200 and status_code < 300; +} + +/// Check if status code is client error +pub fn is_status_client_error(status_code: i32) bool { + return status_code >= 400 and status_code < 500; +} + +/// Check if status code is server error +pub fn is_status_server_error(status_code: i32) bool { + return status_code >= 500 and status_code < 600; +} + +/// Check if status code is rate limited +pub fn is_status_rate_limited(status_code: i32) bool { + return status_code == STATUS_TOO_MANY_REQUESTS; +} + +/// Check if status code is unauthorized +pub fn is_status_unauthorized(status_code: i32) bool { + return status_code == STATUS_UNAUTHORIZED; +} + +/// Check if response is successful +pub fn is_response_success(resp: HttpResponse) bool { + return resp.success; +} + +/// Check if adapter result is successful +pub fn is_adapter_success(result: AdapterResult) bool { + return result.success; +} + +/// Build headers for request +pub fn build_headers(api_key: []u8, provider_type: ProviderType) []HttpHeader { + var headers : []HttpHeader = undefined; + headers = headers ++ &[_]HttpHeader{ + http_header_create("Authorization", HEADER_AUTHORIZATION ++ api_key), + http_header_create("Content-Type", "application/json"), + }; + if (provider_type == .anthropic) { + headers = headers ++ &[_]HttpHeader{ + http_header_create("anthropic-version", "2023-06-01"), + }; + } + return headers; +} + +/// Calculate retry delay with backoff +pub fn calculate_retry_delay(attempt: usize, policy: RetryPolicy, base_delay: usize) usize { + if (policy == .none) { + return 0; + } else if (policy == .fixed) { + return base_delay; + } else if (policy == .linear) { + return attempt * base_delay; + } else { + const delay = base_delay * @as(usize, 2) ** attempt; + return if (delay > MAX_BACKOFF_MS) { MAX_BACKOFF_MS } else { delay }; + } +} + +/// Get endpoint URL for provider +pub fn get_endpoint(provider_type: ProviderType) []u8 { + return if (provider_type == .anthropic) { + ENDPOINT_ANTHROPIC_MESSAGES; + } else { + ENDPOINT_OPENAI_CHAT; + }; +} + +/// Get endpoint URL for Anthropic messages +pub fn get_endpoint_anthropic() []u8 { + return ENDPOINT_ANTHROPIC_MESSAGES; +} + +/// Get endpoint URL for OpenAI chat +pub fn get_endpoint_openai() []u8 { + return ENDPOINT_OPENAI_CHAT; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "provider_http_header_create" { + const header = http_header_create("Content-Type", "application/json"); + try std.testing.expectEqual(@as(usize, header.name.len), @as(usize, 12)); +} + +test "provider_http_request_create" { + const req = http_request_create(.post, "http://example.com", "{}"); + try std.testing.expect(req.method == .post); +} + +test "provider_http_response_create" { + const resp = http_response_create(200, "body"); + try std.testing.expect(resp.success == true); +} + +test "provider_adapter_config_create" { + const config = adapter_config_create(.anthropic, "key"); + try std.testing.expect(config.provider_type == .anthropic); +} + +test "provider_stream_chunk_create" { + const chunk = stream_chunk_create("data", false); + try std.testing.expect(chunk.done == false); +} + +test "provider_rate_limit_info_create" { + const info = rate_limit_info_create(100, 1000); + try std.testing.expect(info.remaining == 100); +} + +test "provider_auth_info_create" { + const auth = auth_info_create("api-key"); + try std.testing.expect(auth.auth_type == "Bearer"); +} + +test "provider_adapter_result_create" { + const resp = http_response_create(200, "response"); + const result = adapter_result_create(resp); + try std.testing.expect(result.success == true); +} + +test "provider_adapter_result_error" { + const result = adapter_result_error("error"); + try std.testing.expect(result.success == false); +} + +test "provider_http_method_to_string" { + const str = http_method_to_string(.post); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 4)); +} + +test "provider_http_method_from_string" { + const method = http_method_from_string(HTTP_METHOD_POST); + try std.testing.expect(method == .post); +} + +test "provider_is_status_success" { + try std.testing.expect(is_status_success(200)); + try std.testing.expect(!is_status_success(404)); +} + +test "provider_is_status_client_error" { + try std.testing.expect(is_status_client_error(404)); + try std.testing.expect(!is_status_client_error(500)); +} + +test "provider_is_status_server_error" { + try std.testing.expect(is_status_server_error(500)); + try std.testing.expect(!is_status_server_error(404)); +} + +test "provider_is_status_rate_limited" { + try std.testing.expect(is_status_rate_limited(429)); +} + +test "provider_is_status_unauthorized" { + try std.testing.expect(is_status_unauthorized(401)); +} + +test "provider_is_response_success" { + const resp = http_response_create(200, "body"); + try std.testing.expect(is_response_success(resp)); +} + +test "provider_is_adapter_success" { + const result = adapter_result_create(http_response_create(200, "body")); + try std.testing.expect(is_adapter_success(result)); +} + +test "provider_build_headers" { + const headers = build_headers("key", .anthropic); + try std.testing.expect(headers.len >= 2); +} + +test "provider_calculate_retry_delay" { + const delay = calculate_retry_delay(2, .exponential, 100); + try std.testing.expect(delay >= 100); +} + +test "provider_get_endpoint" { + const endpoint = get_endpoint(.anthropic); + try std.testing.expectEqual(@as(usize, endpoint.len), @as(usize, 27)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant http_method_in_range { + // HttpMethod is in [0, 3] + @compileAssert(@as(u8, HttpMethod.get) == 0); +} + +invariant http_header_has_name { + // HttpHeader has non-empty name + @compileAssert(true); +} + +invariant http_request_has_url { + // HttpRequest has non-empty URL + @compileAssert(true); +} + +invariant http_response_has_status { + // HttpResponse has valid status code + @compileAssert(true); +} + +invariant adapter_config_valid { + // AdapterConfig has valid fields + @compileAssert(true); +} + +invariant retry_policy_in_range { + // RetryPolicy is in [0, 3] + @compileAssert(@as(u8, RetryPolicy.none) == 0); +} + +invariant stream_chunk_valid { + // StreamChunk has valid state + @compileAssert(true); +} + +invariant rate_limit_info_valid { + // RateLimitInfo has valid remaining + @compileAssert(true); +} + +invariant auth_info_valid { + // AuthInfo has non-empty API key + @compileAssert(true); +} + +invariant default_timeout_positive { + // DEFAULT_TIMEOUT_MS is positive + @compileAssert(DEFAULT_TIMEOUT_MS > 0); +} + +invariant max_retries_positive { + // DEFAULT_MAX_RETRIES is positive + @compileAssert(DEFAULT_MAX_RETRIES > 0); +} + +invariant backoff_delay_valid { + // Backoff delays are in valid range + @compileAssert(DEFAULT_BACKOFF_MS > 0); + @compileAssert(DEFAULT_BACKOFF_MS <= MAX_BACKOFF_MS); +} + +invariant success_status_in_range { + // Success status codes are [200, 299] + @compileAssert(STATUS_OK == 200); + @compileAssert(STATUS_CREATED == 201); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "provider_http_header_create_latency" { + // Measure: cycles for HTTP header creation + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : HttpHeader = undefined; + for (0..1000) |_| { + result = http_header_create("Content-Type", "application/json"); + } + _ = result; +} + +bench "provider_http_request_create_latency" { + // Measure: cycles for HTTP request creation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : HttpRequest = undefined; + for (0..1000) |_| { + result = http_request_create(.post, "http://example.com", "{}"); + } + _ = result; +} + +bench "provider_http_response_create_latency" { + // Measure: cycles for HTTP response creation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : HttpResponse = undefined; + for (0..1000) |_| { + result = http_response_create(200, "response"); + } + _ = result; +} + +bench "provider_build_headers_latency" { + // Measure: cycles for header building + // Target: < 200 cycles + @setEvalBranchQuota(10000); + var result : []HttpHeader = undefined; + for (0..1000) |_| { + result = build_headers("api-key", .anthropic); + } + _ = result; +} + +bench "provider_calculate_retry_delay_latency" { + // Measure: cycles for retry delay calculation + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : usize = 0; + for (0..1000) |_| { + result = calculate_retry_delay(2, .exponential, 100); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/provider/schema.t27 b/apps/website/public/t27/specs/provider/schema.t27 new file mode 100644 index 0000000000..28cdc3bb89 --- /dev/null +++ b/apps/website/public/t27/specs/provider/schema.t27 @@ -0,0 +1,604 @@ +// SPDX-License-Identifier: Apache-2.0 +// provider/schema.t27 — Provider Message Types +// AI provider message structures and model types +// φ² + 1/φ² = 3 | TRINITY + +module provider-schema; + +// ============================================================================ +// Imports +// ============================================================================ + +use tritype-base::usize; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Anthropic API provider name +pub const PROVIDER_ANTHROPIC : [9]u8 = "anthropic"; + +/// OpenAI API provider name +pub const PROVIDER_OPENAI : [6]u8 = "openai"; + +/// Default provider +pub const PROVIDER_DEFAULT : [9]u8 = PROVIDER_ANTHROPIC; + +/// Default model for Anthropic +pub const MODEL_ANTHROPIC_DEFAULT : [18]u8 = "claude-sonnet-4-20250514"; + +/// Default model for OpenAI +pub const MODEL_OPENAI_DEFAULT : [6]u8 = "gpt-4"; + +/// Maximum tokens in request +pub const MAX_TOKENS : usize = 128000; + +/// Default temperature +pub const DEFAULT_TEMPERATURE : f64 = 0.7; + +/// Default max tokens +pub const DEFAULT_MAX_TOKENS : usize = 4096; + +/// Default top p value +pub const DEFAULT_TOP_P : f64 = 1.0; + +// ============================================================================ +// Types +// ============================================================================ + +/// Provider type +pub const ProviderType = enum(u8) { + anthropic = 0, + openai = 1, +}; + +/// Message role +pub const MessageRole = enum(u8) { + system = 0, + user = 1, + assistant = 2, + tool = 3, +}; + +/// Message content type +pub const ContentType = enum(u8) { + text = 0, + image = 1, + tool_use = 2, + tool_result = 3, +}; + +/// Message content +pub const MessageContent = struct { + type : ContentType, + text : []u8, + image_url : []u8, + tool_call_id : []u8, + tool_result : []u8, +}; + +/// Message +pub const Message = struct { + role : MessageRole, + content : []MessageContent, +}; + +/// Tool definition +pub const Tool = struct { + name : []u8, + description : []u8, + input_schema : []u8, +}; + +/// Tool call +pub const ToolCall = struct { + id : []u8, + name : []u8, + arguments : []u8, +}; + +/// Request parameters +pub const RequestParams = struct { + model : []u8, + messages : []Message, + max_tokens : usize, + temperature : f64, + top_p : f64, + tools : []Tool, +}; + +/// Streaming chunk type +pub const ChunkType = enum(u8) { + content = 0, + tool_call = 1, + done = 2, + error = 3, +}; + +/// Streaming chunk +pub const StreamingChunk = struct { + chunk_type : ChunkType, + delta : []u8, + tool_calls : []ToolCall, + finish_reason : []u8, + usage : Usage, +}; + +/// Token usage +pub const Usage = struct { + prompt_tokens : usize, + completion_tokens : usize, + total_tokens : usize, +}; + +/// Response +pub const Response = struct { + id : []u8, + model : []u8, + choices : []Choice, + usage : Usage, + finish_reason : []u8, +}; + +/// Choice +pub const Choice = struct { + message : Message, + finish_reason : []u8, +}; + +/// Stream response +pub const StreamResponse = struct { + id : []u8, + model : []u8, + chunks : []StreamingChunk, +}; + +/// Provider configuration +pub const ProviderConfig = struct { + provider_type : ProviderType, + api_key : []u8, + base_url : []u8, + model : []u8, + timeout_ms : usize, + max_retries : usize, +}; + +/// Provider capability +pub const ProviderCapability = enum(u8) { + chat_completion = 0, + streaming = 1, + function_calling = 2, + vision = 3, +}; + +/// Provider capabilities +pub const ProviderCapabilities = struct { + chat_completion : bool, + streaming : bool, + function_calling : bool, + vision : bool, +}; + +/// Error type +pub const ProviderError = struct { + code : i32, + message : []u8, + type : []u8, +}; + +/// Image content +pub const ImageContent = struct { + url : []u8, + detail : []u8, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create provider type from string +pub fn provider_type_from_string(s: []u8) ProviderType { + if (s == PROVIDER_ANTHROPIC) { + return .anthropic; + } else if (s == PROVIDER_OPENAI) { + return .openai; + } else { + return .anthropic; + } +} + +/// Get provider type string +pub fn provider_type_to_string(pt: ProviderType) []u8 { + return switch (pt) { + .anthropic => PROVIDER_ANTHROPIC, + .openai => PROVIDER_OPENAI, + }; +} + +/// Create message +pub fn message_create(role: MessageRole, text: []u8) Message { + return Message{ + .role = role, + .content = &[_]MessageContent{ + MessageContent{ + .type = .text, + .text = text, + .image_url = "", + .tool_call_id = "", + .tool_result = "", + }, + }, + }; +} + +/// Create system message +pub fn system_message_create(text: []u8) Message { + return message_create(.system, text); +} + +/// Create user message +pub fn user_message_create(text: []u8) Message { + return message_create(.user, text); +} + +/// Create tool definition +pub fn tool_create(name: []u8, description: []u8) Tool { + return Tool{ + .name = name, + .description = description, + .input_schema = "", + }; +} + +/// Create request params +pub fn request_params_create(model: []u8, messages: []Message) RequestParams { + return RequestParams{ + .model = model, + .messages = messages, + .max_tokens = DEFAULT_MAX_TOKENS, + .temperature = DEFAULT_TEMPERATURE, + .top_p = DEFAULT_TOP_P, + .tools = &[_]Tool{}, + }; +} + +/// Create provider config +pub fn provider_config_create(provider_type: ProviderType, api_key: []u8) ProviderConfig { + const base_url = if (provider_type == .anthropic) { + "https://api.anthropic.com"; + } else { + "https://api.openai.com"; + }; + return ProviderConfig{ + .provider_type = provider_type, + .api_key = api_key, + .base_url = base_url, + .model = if (provider_type == .anthropic) { MODEL_ANTHROPIC_DEFAULT } else { MODEL_OPENAI_DEFAULT }, + .timeout_ms = 30000, + .max_retries = 3, + }; +} + +/// Create usage +pub fn usage_create(prompt: usize, completion: usize) Usage { + return Usage{ + .prompt_tokens = prompt, + .completion_tokens = completion, + .total_tokens = prompt + completion, + }; +} + +/// Create streaming chunk +pub fn streaming_chunk_create(chunk_type: ChunkType, delta: []u8) StreamingChunk { + return StreamingChunk{ + .chunk_type = chunk_type, + .delta = delta, + .tool_calls = &[_]ToolCall{}, + .finish_reason = "", + .usage = usage_create(0, 0), + }; +} + +/// Create response +pub fn response_create(id: []u8, model: []u8, message: Message) Response { + return Response{ + .id = id, + .model = model, + .choices = &[_]Choice{ + Choice{ + .message = message, + .finish_reason = "stop", + }, + }, + .usage = usage_create(0, 0), + .finish_reason = "stop", + }; +} + +/// Get role string +pub fn role_to_string(role: MessageRole) []u8 { + return switch (role) { + .system => "system", + .user => "user", + .assistant => "assistant", + .tool => "tool", + }; +} + +/// Create provider error +pub fn provider_error_create(code: i32, message: []u8) ProviderError { + return ProviderError{ + .code = code, + .message = message, + .type = "ProviderError", + }; +} + +/// Check if provider is Anthropic +pub fn is_anthropic_provider(pt: ProviderType) bool { + return pt == .anthropic; +} + +/// Check if provider is OpenAI +pub fn is_openai_provider(pt: ProviderType) bool { + return pt == .openai; +} + +/// Check if response has error +pub fn response_has_error(resp: Response) bool { + return resp.choices.len == 0; +} + +/// Check if chunk is done +pub fn chunk_is_done(chunk: StreamingChunk) bool { + return chunk.chunk_type == .done; +} + +/// Get chunk type string +pub fn chunk_type_to_string(ct: ChunkType) []u8 { + return switch (ct) { + .content => "content", + .tool_call => "tool_call", + .done => "done", + .error => "error", + }; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "provider_provider_type_from_string" { + const pt = provider_type_from_string(PROVIDER_ANTHROPIC); + try std.testing.expect(pt == .anthropic); +} + +test "provider_provider_type_to_string" { + const str = provider_type_to_string(.openai); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 6)); +} + +test "provider_message_create" { + const msg = message_create(.user, "hello"); + try std.testing.expect(msg.role == .user); +} + +test "provider_system_message_create" { + const msg = system_message_create("system prompt"); + try std.testing.expect(msg.role == .system); +} + +test "provider_user_message_create" { + const msg = user_message_create("user input"); + try std.testing.expect(msg.role == .user); +} + +test "provider_tool_create" { + const tool = tool_create("test", "test tool"); + try std.testing.expectEqual(@as(usize, tool.name.len), @as(usize, 4)); +} + +test "provider_request_params_create" { + const params = request_params_create("model", &[_]Message{}); + try std.testing.expectEqual(@as(usize, params.model.len), @as(usize, 5)); +} + +test "provider_provider_config_create" { + const config = provider_config_create(.anthropic, "key"); + try std.testing.expect(config.provider_type == .anthropic); +} + +test "provider_usage_create" { + const usage = usage_create(100, 50); + try std.testing.expect(usage.total_tokens == 150); +} + +test "provider_streaming_chunk_create" { + const chunk = streaming_chunk_create(.content, "delta"); + try std.testing.expect(chunk.chunk_type == .content); +} + +test "provider_response_create" { + const resp = response_create("id", "model", user_message_create("test")); + try std.testing.expectEqual(@as(usize, resp.id.len), @as(usize, 2)); +} + +test "provider_role_to_string" { + const str = role_to_string(.assistant); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 9)); +} + +test "provider_provider_error_create" { + const err = provider_error_create(-1, "error"); + try std.testing.expect(err.code == -1); +} + +test "provider_is_anthropic_provider" { + try std.testing.expect(is_anthropic_provider(.anthropic)); +} + +test "provider_is_openai_provider" { + try std.testing.expect(is_openai_provider(.openai)); +} + +test "provider_response_has_error" { + const err_resp = response_create("", "", user_message_create("")); + try std.testing.expect(response_has_error(err_resp)); +} + +test "provider_chunk_is_done" { + const chunk = streaming_chunk_create(.done, ""); + try std.testing.expect(chunk_is_done(chunk)); +} + +test "provider_chunk_type_to_string" { + const str = chunk_type_to_string(.content); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 7)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant provider_type_in_range { + // ProviderType is in [0, 1] + @compileAssert(@as(u8, ProviderType.anthropic) == 0); + @compileAssert(@as(u8, ProviderType.openai) == 1); +} + +invariant message_role_in_range { + // MessageRole is in [0, 3] + @compileAssert(@as(u8, MessageRole.system) == 0); + @compileAssert(@as(u8, MessageRole.tool) == 3); +} + +invariant content_type_in_range { + // ContentType is in [0, 3] + @compileAssert(@as(u8, ContentType.text) == 0); +} + +invariant max_tokens_positive { + // MAX_TOKENS is positive + @compileAssert(MAX_TOKENS > 0); +} + +invariant default_temperature_valid { + // DEFAULT_TEMPERATURE is in [0, 2] + @compileAssert(DEFAULT_TEMPERATURE >= 0.0 and DEFAULT_TEMPERATURE <= 2.0); +} + +invariant default_max_tokens_valid { + // DEFAULT_MAX_TOKENS is positive + @compileAssert(DEFAULT_MAX_TOKENS > 0); +} + +invariant usage_totals_equal_parts { + // Usage total equals prompt + completion + @compileAssert(true); +} + +invariant request_params_has_model { + // RequestParams has non-empty model + @compileAssert(true); +} + +invariant response_has_id_or_model { + // Response has either id or model + @compileAssert(true); +} + +invariant chunk_type_in_range { + // ChunkType is in [0, 3] + @compileAssert(@as(u8, ChunkType.content) == 0); + @compileAssert(@as(u8, ChunkType.error) == 3); +} + +invariant tool_has_name { + // Tool has non-empty name + @compileAssert(true); +} + +invariant provider_config_has_api_key { + // ProviderConfig has non-empty api_key + @compileAssert(true); +} + +invariant provider_error_has_code { + // ProviderError has valid error code + @compileAssert(true); +} + +invariant message_has_role { + // Message has valid role + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "provider_message_create_latency" { + // Measure: cycles for message creation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : Message = undefined; + for (0..1000) |_| { + result = message_create(.user, "test"); + } + _ = result; +} + +bench "provider_request_params_create_latency" { + // Measure: cycles for request params creation + // Target: < 200 cycles + @setEvalBranchQuota(10000); + var result : RequestParams = undefined; + for (0..1000) |_| { + result = request_params_create("model", &[_]Message{}); + } + _ = result; +} + +bench "provider_provider_config_create_latency" { + // Measure: cycles for provider config creation + // Target: < 150 cycles + @setEvalBranchQuota(10000); + var result : ProviderConfig = undefined; + for (0..1000) |_| { + result = provider_config_create(.anthropic, "api-key"); + } + _ = result; +} + +bench "provider_streaming_chunk_create_latency" { + // Measure: cycles for streaming chunk creation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : StreamingChunk = undefined; + for (0..1000) |_| { + result = streaming_chunk_create(.content, "delta"); + } + _ = result; +} + +bench "provider_response_create_latency" { + // Measure: cycles for response creation + // Target: < 200 cycles + @setEvalBranchQuota(10000); + var result : Response = undefined; + for (0..1000) |_| { + result = response_create("id", "model", user_message_create("test")); + } + _ = result; +} + +bench "provider_role_to_string_latency" { + // Measure: cycles for role to string conversion + // Target: < 20 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = role_to_string(.assistant); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/provider/stream.t27 b/apps/website/public/t27/specs/provider/stream.t27 new file mode 100644 index 0000000000..e5d9d7c590 --- /dev/null +++ b/apps/website/public/t27/specs/provider/stream.t27 @@ -0,0 +1,611 @@ +// SPDX-License-Identifier: Apache-2.0 +// provider/stream.t27 — SSE/Streaming Response Handling +// Server-Sent Events and streaming response processing +// φ² + 1/φ² = 3 | TRINITY + +module provider-stream; + +// ============================================================================ +// Imports +// ============================================================================ + +use tritype-base::usize; +use provider-schema::StreamingChunk; +use provider-schema::ChunkType; +use provider-schema::ToolCall; +use provider-schema::usage_create; + +// ============================================================================ +// Constants +// ============================================================================ + +/// SSE event prefix +pub const SSE_EVENT_PREFIX : [7]u8 = "event: "; + +/// SSE data prefix +pub const SSE_DATA_PREFIX : [6]u8 = "data: "; + +/// SSE comment prefix +pub const SSE_COMMENT_PREFIX : [1]u8 = ":"; + +/// SSE message event type +pub const SSE_EVENT_MESSAGE : [7]u8 = "message"; + +/// SSE delta event type +pub const SSE_EVENT_DELTA : [6]u8 = "delta"; + +/// SSE done event type +pub const SSE_EVENT_DONE : [4]u8 = "done"; + +/// SSE error event type +pub const SSE_EVENT_ERROR : [5]u8 = "error"; + +/// Default buffer size for SSE +pub const SSE_BUFFER_SIZE : usize = 8192; + +/// Maximum chunk size in bytes +pub const MAX_CHUNK_SIZE : usize = 65536; + +/// Default stream timeout in milliseconds +pub const DEFAULT_STREAM_TIMEOUT_MS : usize = 60000; + +// ============================================================================ +// Types +// ============================================================================ + +/// SSE event type +pub const SseEventType = enum(u8) { + message = 0, + delta = 1, + done = 2, + error = 3, + comment = 4, +}; + +/// SSE event +pub const SseEvent = struct { + event_type : SseEventType, + data : []u8, + id : []u8, + retry : usize, +}; + +/// Stream state +pub const StreamState = enum(u8) { + idle = 0, + connecting = 1, + streaming = 2, + paused = 3, + error = 4, + closed = 5, +}; + +/// Stream error type +pub const StreamError = enum(u8) { + connection_failed = 0, + timeout = 1, + parse_error = 2, + server_error = 3, + client_closed = 4, +}; + +/// Stream configuration +pub const StreamConfig = struct { + timeout_ms : usize, + buffer_size : usize, + auto_reconnect : bool, + max_reconnect_attempts : usize, +}; + +/// Stream result +pub const StreamResult = struct { + success : bool, + chunks : []StreamingChunk, + error : StreamError, + error_message : []u8, +}; + +/// Buffer position +pub const BufferPosition = struct { + line_start : usize, + current : usize, + capacity : usize, +}; + +/// Parsed event +pub const ParsedEvent = struct { + success : bool, + event : SseEvent, + remaining : []u8, +}; + +/// Stream stats +pub const StreamStats = struct { + bytes_received : usize, + chunks_received : usize, + errors : usize, + start_time : usize, + end_time : usize, +}; + +/// Stream callback +pub const StreamCallback = enum(u8) { + on_chunk = 0, + on_done = 1, + on_error = 2, + on_connect = 3, + on_disconnect = 4, +}; + +/// Callback data +pub const CallbackData = struct { + callback_type : StreamCallback, + data : []u8, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create stream config +pub fn stream_config_default() StreamConfig { + return StreamConfig{ + .timeout_ms = DEFAULT_STREAM_TIMEOUT_MS, + .buffer_size = SSE_BUFFER_SIZE, + .auto_reconnect = false, + .max_reconnect_attempts = 3, + }; +} + +/// Create SSE event +pub fn sse_event_create(event_type: SseEventType, data: []u8) SseEvent { + return SseEvent{ + .event_type = event_type, + .data = data, + .id = "", + .retry = 0, + }; +} + +/// Create stream result +pub fn stream_result_create(chunks: []StreamingChunk) StreamResult { + return StreamResult{ + .success = true, + .chunks = chunks, + .error = .connection_failed, + .error_message = "", + }; +} + +/// Create stream result with error +pub fn stream_result_error(error: StreamError, message: []u8) StreamResult { + return StreamResult{ + .success = false, + .chunks = &[_]StreamingChunk{}, + .error = error, + .error_message = message, + }; +} + +/// Create buffer position +pub fn buffer_position_create(capacity: usize) BufferPosition { + return BufferPosition{ + .line_start = 0, + .current = 0, + .capacity = capacity, + }; +} + +/// Create parsed event +pub fn parsed_event_create(success: bool, event: SseEvent) ParsedEvent { + return ParsedEvent{ + .success = success, + .event = event, + .remaining = "", + }; +} + +/// Create stream stats +pub fn stream_stats_create() StreamStats { + return StreamStats{ + .bytes_received = 0, + .chunks_received = 0, + .errors = 0, + .start_time = 0, + .end_time = 0, + }; +} + +/// Create callback data +pub fn callback_data_create(callback_type: StreamCallback, data: []u8) CallbackData { + return CallbackData{ + .callback_type = callback_type, + .data = data, + }; +} + +/// Parse SSE line +pub fn parse_sse_line(line: []u8) ParsedEvent { + if (line.len == 0) { + return parsed_event_create(false, sse_event_create(.message, "")); + } + + if (line[0] == ':') { + return parsed_event_create(true, sse_event_create(.comment, "")); + } + + if (line.len > SSE_DATA_PREFIX.len and line[0..SSE_DATA_PREFIX.len] == SSE_DATA_PREFIX) { + const data = line[SSE_DATA_PREFIX.len..line.len]; + return parsed_event_create(true, sse_event_create(.delta, data)); + } + + if (line.len > SSE_EVENT_PREFIX.len and line[0..SSE_EVENT_PREFIX.len] == SSE_EVENT_PREFIX) { + const event_type_str = line[SSE_EVENT_PREFIX.len..line.len]; + const event_type = parse_event_type(event_type_str); + return parsed_event_create(true, sse_event_create(event_type, "")); + } + + return parsed_event_create(false, sse_event_create(.message, "")); +} + +/// Parse event type from string +pub fn parse_event_type(s: []u8) SseEventType { + if (s == SSE_EVENT_MESSAGE) { + return .message; + } else if (s == SSE_EVENT_DELTA) { + return .delta; + } else if (s == SSE_EVENT_DONE) { + return .done; + } else if (s == SSE_EVENT_ERROR) { + return .error; + } else { + return .message; + } +} + +/// Convert SSE event to streaming chunk +pub fn sse_event_to_chunk(event: SseEvent) StreamingChunk { + const chunk_type = if (event.event_type == .done) { + .done + } else if (event.event_type == .error) { + .error + } else { + .content + }; + return StreamingChunk{ + .chunk_type = chunk_type, + .delta = event.data, + .tool_calls = &[_]ToolCall{}, + .finish_reason = "", + .usage = usage_create(0, 0), + }; +} + +/// Check if line is SSE comment +pub fn is_sse_comment(line: []u8) bool { + return line.len > 0 and line[0] == ':'; +} + +/// Check if line is SSE data +pub fn is_sse_data(line: []u8) bool { + return line.len > SSE_DATA_PREFIX.len and line[0..SSE_DATA_PREFIX.len] == SSE_DATA_PREFIX; +} + +/// Check if line is SSE event +pub fn is_sse_event(line: []u8) bool { + return line.len > SSE_EVENT_PREFIX.len and line[0..SSE_EVENT_PREFIX.len] == SSE_EVENT_PREFIX; +} + +/// Get SSE event type string +pub fn sse_event_type_to_string(event_type: SseEventType) []u8 { + return switch (event_type) { + .message => SSE_EVENT_MESSAGE, + .delta => SSE_EVENT_DELTA, + .done => SSE_EVENT_DONE, + .error => SSE_EVENT_ERROR, + .comment => "comment", + }; +} + +/// Check if stream is active +pub fn stream_is_active(state: StreamState) bool { + return state == .streaming or state == .connecting; +} + +/// Check if stream has error +pub fn stream_has_error(state: StreamState) bool { + return state == .error; +} + +/// Get stream state string +pub fn stream_state_to_string(state: StreamState) []u8 { + return switch (state) { + .idle => "idle", + .connecting => "connecting", + .streaming => "streaming", + .paused => "paused", + .error => "error", + .closed => "closed", + }; +} + +/// Get stream error string +pub fn stream_error_to_string(error: StreamError) []u8 { + return switch (error) { + .connection_failed => "connection_failed", + .timeout => "timeout", + .parse_error => "parse_error", + .server_error => "server_error", + .client_closed => "client_closed", + }; +} + +/// Check if stream result is successful +pub fn is_stream_success(result: StreamResult) bool { + return result.success; +} + +/// Update stream stats +pub fn update_stream_stats(stats: StreamStats, bytes: usize) StreamStats { + return StreamStats{ + .bytes_received = stats.bytes_received + bytes, + .chunks_received = stats.chunks_received + 1, + .errors = stats.errors, + .start_time = stats.start_time, + .end_time = stats.end_time, + }; +} + +/// Increment stream stats errors +pub fn increment_stream_errors(stats: StreamStats) StreamStats { + return StreamStats{ + .bytes_received = stats.bytes_received, + .chunks_received = stats.chunks_received, + .errors = stats.errors + 1, + .start_time = stats.start_time, + .end_time = stats.end_time, + }; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "provider_stream_config_default" { + const config = stream_config_default(); + try std.testing.expect(config.timeout_ms == DEFAULT_STREAM_TIMEOUT_MS); +} + +test "provider_sse_event_create" { + const event = sse_event_create(.delta, "data"); + try std.testing.expect(event.event_type == .delta); +} + +test "provider_stream_result_create" { + const chunks = &[_]StreamingChunk{}; + const result = stream_result_create(chunks); + try std.testing.expect(result.success == true); +} + +test "provider_stream_result_error" { + const result = stream_result_error(.timeout, "timeout error"); + try std.testing.expect(result.success == false); +} + +test "provider_buffer_position_create" { + const pos = buffer_position_create(1024); + try std.testing.expect(pos.capacity == 1024); +} + +test "provider_parse_sse_line_data" { + const line = "data: {\"text\":\"hello\"}"; + const parsed = parse_sse_line(line); + try std.testing.expect(parsed.success == true); +} + +test "provider_parse_sse_line_event" { + const line = "event: message"; + const parsed = parse_sse_line(line); + try std.testing.expect(parsed.success == true); +} + +test "provider_parse_sse_line_comment" { + const line = ": this is a comment"; + const parsed = parse_sse_line(line); + try std.testing.expect(parsed.success == true); +} + +test "provider_is_sse_comment" { + try std.testing.expect(is_sse_comment(": comment")); +} + +test "provider_is_sse_data" { + try std.testing.expect(is_sse_data("data: value")); +} + +test "provider_is_sse_event" { + try std.testing.expect(is_sse_event("event: message")); +} + +test "provider_sse_event_type_to_string" { + const str = sse_event_type_to_string(.done); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 4)); +} + +test "provider_stream_is_active" { + try std.testing.expect(stream_is_active(.streaming)); +} + +test "provider_stream_has_error" { + try std.testing.expect(stream_has_error(.error)); +} + +test "provider_stream_state_to_string" { + const str = stream_state_to_string(.streaming); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 9)); +} + +test "provider_stream_error_to_string" { + const str = stream_error_to_string(.timeout); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 7)); +} + +test "provider_is_stream_success" { + const result = stream_result_create(&[_]StreamingChunk{}); + try std.testing.expect(is_stream_success(result)); +} + +test "provider_update_stream_stats" { + var stats = stream_stats_create(); + stats = update_stream_stats(stats, 100); + try std.testing.expect(stats.bytes_received == 100); +} + +test "provider_increment_stream_errors" { + var stats = stream_stats_create(); + stats = increment_stream_errors(stats); + try std.testing.expect(stats.errors == 1); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant sse_event_type_in_range { + // SseEventType is in [0, 4] + @compileAssert(@as(u8, SseEventType.message) == 0); + @compileAssert(@as(u8, SseEventType.comment) == 4); +} + +invariant stream_state_in_range { + // StreamState is in [0, 5] + @compileAssert(@as(u8, StreamState.idle) == 0); + @compileAssert(@as(u8, StreamState.closed) == 5); +} + +invariant stream_error_in_range { + // StreamError is in [0, 4] + @compileAssert(@as(u8, StreamError.connection_failed) == 0); + @compileAssert(@as(u8, StreamError.client_closed) == 4); +} + +invariant stream_config_valid { + // StreamConfig has valid timeout + @compileAssert(DEFAULT_STREAM_TIMEOUT_MS > 0); +} + +invariant stream_buffer_size_positive { + // SSE_BUFFER_SIZE is positive + @compileAssert(SSE_BUFFER_SIZE > 0); +} + +invariant max_chunk_size_positive { + // MAX_CHUNK_SIZE is positive + @compileAssert(MAX_CHUNK_SIZE > 0); +} + +invariant stream_stats_valid { + // StreamStats has valid fields + @compileAssert(true); +} + +invariant callback_type_in_range { + // StreamCallback is in [0, 4] + @compileAssert(@as(u8, StreamCallback.on_chunk) == 0); +} + +invariant parsed_event_valid { + // ParsedEvent has valid success flag + @compileAssert(true); +} + +invariant sse_event_valid { + // SseEvent has valid type + @compileAssert(true); +} + +invariant default_stream_timeout_positive { + // DEFAULT_STREAM_TIMEOUT_MS is positive + @compileAssert(DEFAULT_STREAM_TIMEOUT_MS > 0); +} + +invariant stream_is_active_exclusive_with_error { + // Stream cannot be active and error at same time + @compileAssert(!stream_is_active(.error)); +} + +invariant stream_is_result_success_matches_state { + // StreamResult success matches StreamState + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "provider_stream_config_default_latency" { + // Measure: cycles for stream config creation + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : StreamConfig = undefined; + for (0..1000) |_| { + result = stream_config_default(); + } + _ = result; +} + +bench "provider_sse_event_create_latency" { + // Measure: cycles for SSE event creation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : SseEvent = undefined; + for (0..1000) |_| { + result = sse_event_create(.delta, "data"); + } + _ = result; +} + +bench "provider_parse_sse_line_latency" { + // Measure: cycles for SSE line parsing + // Target: < 200 cycles + @setEvalBranchQuota(10000); + var result : ParsedEvent = undefined; + for (0..1000) |_| { + result = parse_sse_line("data: value"); + } + _ = result; +} + +bench "provider_stream_is_active_latency" { + // Measure: cycles for stream active check + // Target: < 20 cycles + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..1000) |_| { + result = stream_is_active(.streaming); + } + _ = result; +} + +bench "provider_stream_state_to_string_latency" { + // Measure: cycles for stream state to string + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = stream_state_to_string(.streaming); + } + _ = result; +} + +bench "provider_update_stream_stats_latency" { + // Measure: cycles for stats update + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : StreamStats = undefined; + for (0..1000) |_| { + var stats = stream_stats_create(); + result = update_stream_stats(stats, 100); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/provider/transform.t27 b/apps/website/public/t27/specs/provider/transform.t27 new file mode 100644 index 0000000000..03e95112da --- /dev/null +++ b/apps/website/public/t27/specs/provider/transform.t27 @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: Apache-2.0 +// provider/transform.t27 — Cross-Provider Message Transformations +// Message format transformations between different AI providers +// φ² + 1/φ² = 3 | TRINITY + +module provider-transform; + +// ============================================================================ +// Imports +// ============================================================================ + +use tritype-base::usize; +use provider-schema::Message; +use provider-schema::MessageRole; +use provider-schema::MessageContent; +use provider-schema::ContentType; +use provider-schema::Tool; +use provider-schema::ProviderType; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Maximum message history length +pub const MAX_HISTORY_LENGTH : usize = 100; + +/// Maximum content length per message +pub const MAX_CONTENT_LENGTH : usize = 100000; + +/// Default system prompt for transformations +pub const DEFAULT_SYSTEM_PROMPT : [17]u8 = "You are a helpful assistant."; + +// ============================================================================ +// Types +// ============================================================================ + +/// Transform direction +pub const TransformDirection = enum(u8) { + anthropic_to_openai = 0, + openai_to_anthropic = 1, + anthropic_to_anthropic = 2, + openai_to_openai = 3, +}; + +/// Transform result +pub const TransformResult = struct { + success : bool, + messages : []Message, + tools : []Tool, + error : []u8, +}; + +/// Message mapping +pub const MessageMapping = struct { + source_role : MessageRole, + target_role : MessageRole, + content_transform : ContentTransform, +}; + +/// Content transform +pub const ContentTransform = enum(u8) { + identity = 0, + escape_tool_calls = 1, + unescape_tool_calls = 2, + normalize_images = 3, +}; + +/// Tool mapping +pub const ToolMapping = struct { + source_name : []u8, + target_name : []u8, + parameter_transform : ParameterTransform, +}; + +/// Parameter transform +pub const ParameterTransform = enum(u8) { + identity = 0, + rename_field = 1, + convert_type = 2, +}; + +/// Transform options +pub const TransformOptions = struct { + preserve_system : bool, + preserve_tools : bool, + normalize_content : bool, +}; + +/// Batch transform result +pub const BatchTransformResult = struct { + results : []TransformResult, + total_success : usize, + total_failed : usize, +}; + +/// Transform error +pub const TransformError = struct { + code : i32, + message : []u8, + source_provider : ProviderType, + target_provider : ProviderType, +}; + +/// Transformed message +pub const TransformedMessage = struct { + original : Message, + transformed : Message, + applied_transforms : []ContentTransform, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create transform options +pub fn transform_options_default() TransformOptions { + return TransformOptions{ + .preserve_system = true, + .preserve_tools = true, + .normalize_content = true, + }; +} + +/// Create transform direction +pub fn transform_direction_create(source: ProviderType, target: ProviderType) TransformDirection { + if (source == .anthropic and target == .openai) { + return .anthropic_to_openai; + } else if (source == .openai and target == .anthropic) { + return .openai_to_anthropic; + } else { + return .anthropic_to_anthropic; + } +} + +/// Create transform result +pub fn transform_result_create(success: bool, messages: []Message) TransformResult { + return TransformResult{ + .success = success, + .messages = messages, + .tools = &[_]Tool{}, + .error = "", + }; +} + +/// Create transform result with error +pub fn transform_result_error(code: i32, message: []u8) TransformResult { + return TransformResult{ + .success = false, + .messages = &[_]Message{}, + .tools = &[_]Tool{}, + .error = message, + }; +} + +/// Create message mapping +pub fn message_mapping_create(source: MessageRole, target: MessageRole) MessageMapping { + return MessageMapping{ + .source_role = source, + .target_role = target, + .content_transform = .identity, + }; +} + +/// Create tool mapping +pub fn tool_mapping_create(source: []u8, target: []u8) ToolMapping { + return ToolMapping{ + .source_name = source, + .target_name = target, + .parameter_transform = .identity, + }; +} + +/// Create transform error +pub fn transform_error_create(code: i32, message: []u8, source: ProviderType, target: ProviderType) TransformError { + return TransformError{ + .code = code, + .message = message, + .source_provider = source, + .target_provider = target, + }; +} + +/// Create transformed message +pub fn transformed_message_create(original: Message, transformed: Message) TransformedMessage { + return TransformedMessage{ + .original = original, + .transformed = transformed, + .applied_transforms = &[_]ContentTransform{ .identity }, + }; +} + +/// Transform messages between providers +pub fn transform_messages(source: ProviderType, target: ProviderType, messages: []Message, options: TransformOptions) TransformResult { + const direction = transform_direction_create(source, target); + var result_messages : []Message = undefined; + var result_tools : []Tool = undefined; + + for (messages) |msg| { + const transformed = transform_single_message(direction, msg, options); + if (transformed.content.len > 0) { + result_messages = result_messages ++ &[_]Message{ transformed }; + } + } + + return TransformResult{ + .success = true, + .messages = result_messages, + .tools = result_tools, + .error = "", + }; +} + +/// Transform single message +pub fn transform_single_message(direction: TransformDirection, msg: Message, options: TransformOptions) Message { + const target_role = map_role(direction, msg.role); + var transformed_content : []MessageContent = undefined; + + for (msg.content) |content| { + const new_content = transform_content(direction, content, options); + transformed_content = transformed_content ++ &[_]MessageContent{ new_content }; + } + + return Message{ + .role = target_role, + .content = transformed_content, + }; +} + +/// Map role between providers +pub fn map_role(direction: TransformDirection, role: MessageRole) MessageRole { + return switch (direction) { + .anthropic_to_openai => map_role_anthropic_to_openai(role), + .openai_to_anthropic => map_role_openai_to_anthropic(role), + .anthropic_to_anthropic => role, + .openai_to_openai => role, + }; +} + +/// Map role from Anthropic to OpenAI +pub fn map_role_anthropic_to_openai(role: MessageRole) MessageRole { + return switch (role) { + .system => .system, + .user => .user, + .assistant => .assistant, + .tool => .assistant, + }; +} + +/// Map role from OpenAI to Anthropic +pub fn map_role_openai_to_anthropic(role: MessageRole) MessageRole { + return switch (role) { + .system => .system, + .user => .user, + .assistant => .assistant, + .tool => .tool, + }; +} + +/// Transform content based on direction +pub fn transform_content(direction: TransformDirection, content: MessageContent, options: TransformOptions) MessageContent { + if (!options.normalize_content) { + return content; + } + + return switch (direction) { + .anthropic_to_openai => normalize_for_openai(content), + .openai_to_anthropic => normalize_for_anthropic(content), + .anthropic_to_anthropic => content, + .openai_to_openai => content, + }; +} + +/// Normalize content for OpenAI +pub fn normalize_for_openai(content: MessageContent) MessageContent { + return switch (content.type) { + .text => content, + .image => normalize_image_for_openai(content), + .tool_use => convert_tool_to_openai(content), + .tool_result => content, + }; +} + +/// Normalize content for Anthropic +pub fn normalize_for_anthropic(content: MessageContent) MessageContent { + return switch (content.type) { + .text => content, + .image => normalize_image_for_anthropic(content), + .tool_use => content, + .tool_result => convert_tool_to_anthropic(content), + }; +} + +/// Normalize image for OpenAI +pub fn normalize_image_for_openai(content: MessageContent) MessageContent { + return content; +} + +/// Normalize image for Anthropic +pub fn normalize_image_for_anthropic(content: MessageContent) MessageContent { + return content; +} + +/// Convert tool call to OpenAI format +pub fn convert_tool_to_openai(content: MessageContent) MessageContent { + return content; +} + +/// Convert tool result to Anthropic format +pub fn convert_tool_to_anthropic(content: MessageContent) MessageContent { + return content; +} + +/// Transform tools between providers +pub fn transform_tools(source: ProviderType, target: ProviderType, tools: []Tool) []Tool { + if (source == target) { + return tools; + } + + var result : []Tool = undefined; + for (tools) |tool| { + const transformed = transform_single_tool(source, target, tool); + result = result ++ &[_]Tool{ transformed }; + } + return result; +} + +/// Transform single tool +pub fn transform_single_tool(source: ProviderType, target: ProviderType, tool: Tool) Tool { + return Tool{ + .name = tool.name, + .description = tool.description, + .input_schema = tool.input_schema, + }; +} + +/// Check if transformation is needed +pub fn needs_transformation(source: ProviderType, target: ProviderType) bool { + return source != target; +} + +/// Get transform direction string +pub fn transform_direction_to_string(dir: TransformDirection) []u8 { + return switch (dir) { + .anthropic_to_openai => "anthropic_to_openai", + .openai_to_anthropic => "openai_to_anthropic", + .anthropic_to_anthropic => "anthropic_to_anthropic", + .openai_to_openai => "openai_to_openai", + }; +} + +/// Check if transform result is successful +pub fn is_transform_success(result: TransformResult) bool { + return result.success; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "provider_transform_options_default" { + const opts = transform_options_default(); + try std.testing.expect(opts.preserve_system == true); +} + +test "provider_transform_direction_create" { + const dir = transform_direction_create(.anthropic, .openai); + try std.testing.expect(dir == .anthropic_to_openai); +} + +test "provider_transform_result_create" { + const result = transform_result_create(true, &[_]Message{}); + try std.testing.expect(result.success == true); +} + +test "provider_message_mapping_create" { + const mapping = message_mapping_create(.user, .user); + try std.testing.expect(mapping.target_role == .user); +} + +test "provider_tool_mapping_create" { + const mapping = tool_mapping_create("tool1", "tool2"); + try std.testing.expectEqual(@as(usize, mapping.target_name.len), @as(usize, 5)); +} + +test "provider_transform_error_create" { + const err = transform_error_create(-1, "error", .anthropic, .openai); + try std.testing.expect(err.code == -1); +} + +test "provider_map_role_anthropic_to_openai" { + const role = map_role_anthropic_to_openai(.user); + try std.testing.expect(role == .user); +} + +test "provider_map_role_openai_to_anthropic" { + const role = map_role_openai_to_anthropic(.assistant); + try std.testing.expect(role == .assistant); +} + +test "provider_needs_transformation" { + try std.testing.expect(needs_transformation(.anthropic, .openai)); +} + +test "provider_is_transform_success" { + const result = transform_result_create(true, &[_]Message{}); + try std.testing.expect(is_transform_success(result)); +} + +test "provider_transform_direction_to_string" { + const str = transform_direction_to_string(.anthropic_to_openai); + try std.testing.expectEqual(@as(usize, str.len), @as(usize, 17)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant transform_direction_in_range { + // TransformDirection is in [0, 3] + @compileAssert(@as(u8, TransformDirection.anthropic_to_openai) == 0); + @compileAssert(@as(u8, TransformDirection.openai_to_openai) == 3); +} + +invariant transform_result_valid { + // TransformResult has valid success flag + @compileAssert(true); +} + +invariant transform_options_valid { + // TransformOptions has valid flags + @compileAssert(true); +} + +invariant message_mapping_valid { + // MessageMapping has valid roles + @compileAssert(true); +} + +invariant tool_mapping_valid { + // ToolMapping has valid names + @compileAssert(true); +} + +invariant transform_error_valid { + // TransformError has valid fields + @compileAssert(true); +} + +invariant content_transform_in_range { + // ContentTransform is in [0, 3] + @compileAssert(@as(u8, ContentTransform.identity) == 0); + @compileAssert(@as(u8, ContentTransform.normalize_images) == 3); +} + +invariant max_history_length_positive { + // MAX_HISTORY_LENGTH is positive + @compileAssert(MAX_HISTORY_LENGTH > 0); +} + +invariant max_content_length_positive { + // MAX_CONTENT_LENGTH is positive + @compileAssert(MAX_CONTENT_LENGTH > 0); +} + +invariant preserve_system_implies_system_kept { + // preserve_system implies system messages are kept + @compileAssert(true); +} + +invariant preserve_tools_implies_tools_kept { + // preserve_tools implies tools are kept + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "provider_transform_options_default_latency" { + // Measure: cycles for transform options creation + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : TransformOptions = undefined; + for (0..1000) |_| { + result = transform_options_default(); + } + _ = result; +} + +bench "provider_transform_direction_create_latency" { + // Measure: cycles for transform direction creation + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : TransformDirection = undefined; + for (0..1000) |_| { + result = transform_direction_create(.anthropic, .openai); + } + _ = result; +} + +bench "provider_needs_transformation_latency" { + // Measure: cycles for transformation check + // Target: < 20 cycles + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..1000) |_| { + result = needs_transformation(.anthropic, .openai); + } + _ = result; +} + +bench "provider_map_role_latency" { + // Measure: cycles for role mapping + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : MessageRole = undefined; + for (0..1000) |_| { + result = map_role_openai_to_anthropic(.user); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/queen/brain_summaries.t27 b/apps/website/public/t27/specs/queen/brain_summaries.t27 new file mode 100644 index 0000000000..865339bcc6 --- /dev/null +++ b/apps/website/public/t27/specs/queen/brain_summaries.t27 @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/queen/brain_summaries.t27 +// Queen Brain Summaries Pipeline Specification +// Ring 061 - Episode summarization for Queen brain +// Defines how experience episodes are aggregated into summaries +// phi^2 + 1/phi^2 = 3 | TRINITY + +module BrainSummaries { + use queen::lotus; + + // ===================================================== + // 1. Summary Configuration + // ========================================================================= + + const MAX_EPISODES_PER_SUMMARY : usize = 50; + const SUMMARY_RETENTION_DAYS : usize = 90; + const MIN_CONFIDENCE_THRESHOLD : f64 = 0.5; + + // Summary types + const SUMMARY_TYPE_DAILY : u8 = 0; + const SUMMARY_TYPE_WEEKLY : u8 = 1; + const SUMMARY_TYPE_RING : u8 = 2; + const SUMMARY_TYPE_PHASE : u8 = 3; + + // ===================================================== + // 2. Summary Data Structures + // ========================================================================= + + // Brain summary record + struct BrainSummary { + id : usize, + summary_type : u8, + start_date : u64, + end_date : u64, + episodes_count : usize, + ring_range : [2]usize, + phase_number : u8, + + // Aggregate metrics + total_cycles : usize, + successful_cycles : usize, + failed_cycles : usize, + average_confidence : f64, + + // Domain metrics + domain_health : f64, + active_domains : usize, + sealed_domains : usize, + + // Learning metrics + new_patterns : usize, + updated_patterns : usize, + learning_confidence : f64, + + // System metrics + avg_cycle_time_ms : u64, + max_cycle_time_ms : u64, + min_cycle_time_ms : u64, + + // Quality signal + overall_quality : u8, + quality_reason : [256]u8, + } + + // Summary index (for efficient lookup) + struct SummaryIndex { + total_summaries : usize, + daily_summaries : usize, + weekly_summaries : usize, + ring_summaries : usize, + phase_summaries : usize, + last_summary_id : usize, + } + + // ===================================================== + // 3. Episode Aggregation + // ========================================================================= + + // aggregate_episodes() -> BrainSummary + // Aggregate episodes into a summary + fn aggregate_episodes(episodes: []lotus.Episode, summary_type: u8) -> BrainSummary { + var summary : BrainSummary = undefined; + var i : usize = 0; + + // Initialize counters + summary.episodes_count = episodes.len; + summary.total_cycles = episodes.len; + summary.successful_cycles = 0; + summary.failed_cycles = 0; + summary.average_confidence = 0.0; + summary.new_patterns = 0; + summary.updated_patterns = 0; + summary.avg_cycle_time_ms = 0; + summary.max_cycle_time_ms = 0; + summary.min_cycle_time_ms = 0xFFFFFFFFFFFFFFFF; + + // Aggregate from episodes + while (i < episodes.len) { + const episode = episodes[i]; + + // Count outcomes + if (episode.outcome == lotus.OUTCOME_SUCCESS) { + summary.successful_cycles = summary.successful_cycles + 1; + } else if (episode.outcome == lotus.OUTCOME_FAILURE) { + summary.failed_cycles = summary.failed_cycles + 1; + } + + // Sum confidence + summary.average_confidence = summary.average_confidence + episode.evaluation.confidence; + + // Track cycle times + const cycle_time = episode.result.execution_time_ms; + if (cycle_time > summary.max_cycle_time_ms) { + summary.max_cycle_time_ms = cycle_time; + } + if (cycle_time < summary.min_cycle_time_ms) { + summary.min_cycle_time_ms = cycle_time; + } + summary.avg_cycle_time_ms = summary.avg_cycle_time_ms + cycle_time; + + i = i + 1; + } + + // Compute averages + if (episodes.len > 0) { + summary.average_confidence = summary.average_confidence / @as(f64, @floatFromInt(episodes.len)); + summary.avg_cycle_time_ms = summary.avg_cycle_time_ms / @as(u64, @intCast(episodes.len)); + } + + // Determine overall quality + summary.overall_quality = determine_quality(summary); + + summary.summary_type = summary_type; + summary.id = 0; + + return summary; + } + + // ===================================================== + // 4. Quality Assessment + // ========================================================================= + + // determine_quality() -> u8 + // Determine overall quality from metrics + fn determine_quality(summary: BrainSummary) -> u8 { + const success_rate : f64; + if (summary.total_cycles > 0) { + success_rate = @as(f64, @floatFromInt(summary.successful_cycles)) / + @as(f64, @floatFromInt(summary.total_cycles)); + } else { + success_rate = 0.0; + } + + // Quality based on success rate and confidence + if (success_rate >= 0.9 and summary.average_confidence >= 0.8) { + return lotus.QUALITY_GOOD; + } else if (success_rate >= 0.7 and summary.average_confidence >= 0.6) { + return lotus.QUALITY_UNSTABLE; + } else { + return lotus.QUALITY_BAD; + } + } + + // ===================================================== + // 5. Summary Persistence + // ========================================================================= + + // save_summary() -> bool + // Save summary to persistent storage + fn save_summary(summary: BrainSummary, path: []u8) -> bool { + // Implementation: serialize and write to file + // Path pattern: .trinity/queen-brain/summaries/{type}_{id}.json + return true; + } + + // load_summary() -> BrainSummary + // Load summary from persistent storage + fn load_summary(summary_id: usize) -> BrainSummary { + var summary : BrainSummary = undefined; + // Implementation: read and deserialize from file + return summary; + } + + // ===================================================== + // 6. Summary Generation + // ========================================================================= + + // generate_daily_summary() -> BrainSummary + // Generate daily summary of episodes + fn generate_daily_summary(day_start: u64, day_end: u64) -> BrainSummary { + var episodes : [MAX_EPISODES_PER_SUMMARY]lotus.Episode; + var count : usize = 0; + + // Filter episodes by date range + // Implementation would read from .trinity/experience/episodes.jsonl + + var summary = aggregate_episodes(episodes[0..count], SUMMARY_TYPE_DAILY); + summary.start_date = day_start; + summary.end_date = day_end; + + return summary; + } + + // generate_ring_summary() -> BrainSummary + // Generate summary for a specific ring + fn generate_ring_summary(ring_number: usize) -> BrainSummary { + var episodes : [MAX_EPISODES_PER_SUMMARY]lotus.Episode; + var count : usize = 0; + + // Filter episodes by ring number + // Implementation would read from .trinity/experience/episodes.jsonl + + var summary = aggregate_episodes(episodes[0..count], SUMMARY_TYPE_RING); + summary.ring_range[0] = ring_number; + summary.ring_range[1] = ring_number; + + return summary; + } + + // generate_phase_summary() -> BrainSummary + // Generate summary for a phase + fn generate_phase_summary(phase_number: u8, ring_start: usize, ring_end: usize) -> BrainSummary { + var episodes : [MAX_EPISODES_PER_SUMMARY]lotus.Episode; + var count : usize = 0; + + // Filter episodes by phase and ring range + // Implementation would read from .trinity/experience/episodes.jsonl + + var summary = aggregate_episodes(episodes[0..count], SUMMARY_TYPE_PHASE); + summary.phase_number = phase_number; + summary.ring_range[0] = ring_start; + summary.ring_range[1] = ring_end; + + return summary; + } + + // ===================================================== + // 7. TDD - Tests + // ========================================================================= + + test aggregate_empty_episodes + var episodes : [0]lotus.Episode = undefined; + when result = aggregate_episodes(&episodes, SUMMARY_TYPE_DAILY) + then result.episodes_count == 0 + and result.total_cycles == 0 + and result.successful_cycles == 0 + and result.failed_cycles == 0 + + test aggregate_success_episodes + var episodes : [3]lotus.Episode = undefined; + // Initialize episodes with success outcomes + episodes[0].outcome = lotus.OUTCOME_SUCCESS; + episodes[0].evaluation.confidence = 0.9; + episodes[1].outcome = lotus.OUTCOME_SUCCESS; + episodes[1].evaluation.confidence = 0.8; + episodes[2].outcome = lotus.OUTCOME_SUCCESS; + episodes[2].evaluation.confidence = 0.85; + + when result = aggregate_episodes(&episodes, SUMMARY_TYPE_DAILY) + then result.successful_cycles == 3 + and result.failed_cycles == 0 + and result.average_confidence >= 0.8 + and result.overall_quality == lotus.QUALITY_GOOD + + test aggregate_mixed_episodes + var episodes : [4]lotus.Episode = undefined; + episodes[0].outcome = lotus.OUTCOME_SUCCESS; + episodes[0].evaluation.confidence = 0.9; + episodes[1].outcome = lotus.OUTCOME_FAILURE; + episodes[1].evaluation.confidence = 0.3; + episodes[2].outcome = lotus.OUTCOME_SUCCESS; + episodes[2].evaluation.confidence = 0.8; + episodes[3].outcome = lotus.OUTCOME_PARTIAL; + episodes[3].evaluation.confidence = 0.6; + + when result = aggregate_episodes(&episodes, SUMMARY_TYPE_DAILY) + then result.successful_cycles == 2 + and result.failed_cycles == 1 + and result.total_cycles == 4 + + test determine_quality_good + var summary : BrainSummary = undefined; + summary.successful_cycles = 9; + summary.total_cycles = 10; + summary.average_confidence = 0.85; + + when quality = determine_quality(summary) + then quality == lotus.QUALITY_GOOD + + test determine_quality_unstable + var summary : BrainSummary = undefined; + summary.successful_cycles = 7; + summary.total_cycles = 10; + summary.average_confidence = 0.65; + + when quality = determine_quality(summary) + then quality == lotus.QUALITY_UNSTABLE + + test determine_quality_bad + var summary : BrainSummary = undefined; + summary.successful_cycles = 5; + summary.total_cycles = 10; + summary.average_confidence = 0.5; + + when quality = determine_quality(summary) + then quality == lotus.QUALITY_BAD + + // ===================================================== + // 8. TDD - Invariants + // ========================================================================= + + invariant summary_totals_consistency + // Total cycles should equal sum of successful and failed + var episodes : [5]lotus.Episode = undefined; + episodes[0].outcome = lotus.OUTCOME_SUCCESS; + episodes[1].outcome = lotus.OUTCOME_SUCCESS; + episodes[2].outcome = lotus.OUTCOME_FAILURE; + episodes[3].outcome = lotus.OUTCOME_SUCCESS; + episodes[4].outcome = lotus.OUTCOME_PARTIAL; + + const summary = aggregate_episodes(&episodes, SUMMARY_TYPE_DAILY); + const reported_total = summary.successful_cycles + summary.failed_cycles; + // Note: partial outcomes are counted separately + assert summary.total_cycles >= reported_total + + invariant confidence_in_bounds + // Average confidence must be in [0, 1] + var episodes : [3]lotus.Episode = undefined; + episodes[0].evaluation.confidence = 0.5; + episodes[1].evaluation.confidence = 0.7; + episodes[2].evaluation.confidence = 0.9; + + const summary = aggregate_episodes(&episodes, SUMMARY_TYPE_DAILY); + assert summary.average_confidence >= 0.0 + assert summary.average_confidence <= 1.0 + + invariant success_rate_in_bounds + // Success rate cannot exceed 100% + var episodes : [10]lotus.Episode = undefined; + var i : usize = 0; + while (i < 10) { + episodes[i].outcome = lotus.OUTCOME_SUCCESS; + i = i + 1; + } + + const summary = aggregate_episodes(&episodes, SUMMARY_TYPE_DAILY); + assert summary.successful_cycles <= summary.total_cycles + + invariant cycle_time_sanity + // Max cycle time must be >= min cycle time + var episodes : [3]lotus.Episode = undefined; + episodes[0].result.execution_time_ms = 100; + episodes[1].result.execution_time_ms = 200; + episodes[2].result.execution_time_ms = 150; + + const summary = aggregate_episodes(&episodes, SUMMARY_TYPE_DAILY); + assert summary.max_cycle_time_ms >= summary.min_cycle_time_ms + + // ===================================================== + // 9. TDD - Benchmarks + // ========================================================================= + + bench aggregate_50_episodes + // Measure: cycles to aggregate 50 episodes + // Target: < 5000 cycles + var episodes : [50]lotus.Episode = undefined; + var i : usize = 0; + while (i < 50) { + episodes[i].outcome = lotus.OUTCOME_SUCCESS; + episodes[i].evaluation.confidence = 0.8; + episodes[i].result.execution_time_ms = 100; + i = i + 1; + } + @setEvalBranchQuota(10000); + var result : BrainSummary = undefined; + for (0..10) |_| { + result = aggregate_episodes(&episodes, SUMMARY_TYPE_DAILY); + } + _ = result; + + bench determine_quality_good_path + // Measure: cycles to determine quality for good case + // Target: < 500 cycles + var summary : BrainSummary = undefined; + summary.successful_cycles = 90; + summary.total_cycles = 100; + summary.average_confidence = 0.9; + @setEvalBranchQuota(10000); + var quality : u8 = 0; + for (0..100) |_| { + quality = determine_quality(summary); + } + _ = quality; +} diff --git a/apps/website/public/t27/specs/queen/lotus.t27 b/apps/website/public/t27/specs/queen/lotus.t27 new file mode 100644 index 0000000000..61ff769f8d --- /dev/null +++ b/apps/website/public/t27/specs/queen/lotus.t27 @@ -0,0 +1,803 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/queen/lotus.t27 +// Queen Lotus 6-Phase Orchestration Specification +// Self-improving agent orchestration with episode-based learning +// phi^2 + 1/phi^2 = 3 | TRINITY + +module QueenLotus { + // Import base types, HSLM, and runtime + use base::types; + use nn::hslm; + use compiler::runtime; + + // ===================================================== + // 1. Lotus Configuration + // ========================================================================= + + const NUM_PHASES : usize = 6; // 6 phases in Lotus cycle + const EPISODE_BUFFER_SIZE : usize = 100; // Max episodes in buffer + const POLICY_WINDOW_SIZE : usize = 10; // Window for policy evaluation + + // Phase definitions + const PHASE_OBSERVE : u8 = 0; // Observe current state + const PHASE_RECALL : u8 = 1; // Recall relevant episodes + const PHASE_EVALUATE : u8 = 2; // Evaluate situation quality + const PHASE_PLAN : u8 = 3; // Plan next actions + const PHASE_ACT : u8 = 4; // Execute actions + const PHASE_RECORD : u8 = 5; // Record episode outcome + + // Outcome types + const OUTCOME_UNKNOWN : u8 = 0; // Unknown/insufficient data + const OUTCOME_SUCCESS : u8 = 1; // Success + const OUTCOME_PARTIAL : u8 = 2; // Partial success + const OUTCOME_FAILURE : u8 = 3; // Failure (expected) + const OUTCOME_FATAL : u8 = 4; // Fatal/unexpected failure + + // Quality levels + const QUALITY_UNKNOWN : u8 = 0; // Unknown quality + const QUALITY_GOOD : u8 = 1; // Good quality + const QUALITY_UNSTABLE : u8 = 2; // Unstable quality + const QUALITY_BAD : u8 = 3; // Bad quality + + // Policy delta types + const DELTA_SCALE_UP : u8 = 0; // Scale up resources + const DELTA_SCALE_DOWN : u8 = 1; // Scale down resources + const DELTA_SET : u8 = 2; // Set parameter + const DELTA_WAIT : u8 = 3; // Wait/observe + + // ===================================================== + // 2. Lotus State + // ========================================================================= + + // Lotus runtime state + var current_phase : u8 = PHASE_OBSERVE; + var current_episode : usize = 0; + + // Episode buffer (circular buffer) + struct Episode { + id : usize, + timestamp : u64, + context : Context, + evaluation : Evaluation, + plan : Plan, + result : Action, + outcome : u8, + } + + var episode_buffer : [EPISODE_BUFFER_SIZE]Episode; + + // Policy state + var policy_state : [256]u8 = [0; 256]; + + // Evaluation window (last N episode indices) + var eval_window : [POLICY_WINDOW_SIZE]usize = [0; POLICY_WINDOW_SIZE]; + + // Phase timeouts + var phase_start_time : u64 = 0; + const PHASE_TIMEOUT_MS : u64 = 5000; // 5 second timeout per phase + + // ===================================================== + // 3. Data Structures + // ========================================================================= + + // Context: system state observation + struct Context { + active_issues : usize, + system_health : f64, + timestamp : u64, + } + + // Evaluation: quality assessment + struct Evaluation { + quality : u8, + success_count : u8, + partial_count : u8, + failure_count : u8, + confidence : f64, + } + + // Plan: action to take + struct Plan { + delta_type : u8, + target_resource : u8, + target_value : i32, + } + + // Action: executed action + struct Action { + delta_type : u8, + success : bool, + execution_time_ms : u64, + } + + // Cycle result + struct CycleResult { + context : Context, + evaluation : Evaluation, + plan : Plan, + action : Action, + outcome : u8, + total_time_ms : u64, + } + + // ===================================================== + // 4. Main Orchestration + // ========================================================================= + + // lotus_orchestrate() -> CycleResult + // Run complete 6-phase Lotus cycle + // Returns: context, evaluation, plan, result, outcome + fn lotus_orchestrate() -> CycleResult { + var result : CycleResult = undefined; + + // Phase 1: Observe + result.context = lotus_phase(PHASE_OBSERVE); + + // Phase 2: Recall + var recalled_episodes = lotus_phase(PHASE_RECALL); + + // Phase 3: Evaluate + result.evaluation = evaluate_quality(recalled_episodes); + + // Phase 4: Plan + result.plan = generate_plan(result.evaluation); + + // Phase 5: Act + result.action = execute_action(result.plan); + + // Phase 6: Record + result.outcome = record_episode(result); + + // Prepare result + result.total_time_ms = get_timestamp() - result.context.timestamp; + + return result; + } + + // lotus_phase(phase: u8) -> PhaseOutput + // Execute a single phase of the Lotus cycle + fn lotus_phase(phase: u8) -> PhaseOutput { + current_phase = phase; + phase_start_time = get_timestamp(); + + if (phase == PHASE_OBSERVE) { + return observe_state(); + } else if (phase == PHASE_RECALL) { + return recall_episodes(); + } else if (phase == PHASE_EVALUATE) { + return evaluate_quality_(); + } else if (phase == PHASE_PLAN) { + return generate_plan_(); + } else if (phase == PHASE_ACT) { + return execute_action_(); + } else if (phase == PHASE_RECORD) { + return record_episode_(); + } + + return PhaseOutput{ .success = false }; + } + + // ===================================================== + // 5. Phase: Observe + // ========================================================================= + + // PhaseOutput wrapper for observe_state + struct PhaseOutput { + success : bool, + data : [256]u8 = [0; 256], + } + + // observe_state() -> Context + // Gather current state information + fn observe_state() -> Context { + const active_issues = get_active_issues_count(); + const system_health = get_system_health(); + const timestamp = get_timestamp(); + + return Context{ + .active_issues = active_issues, + .system_health = system_health, + .timestamp = timestamp, + }; + } + + // get_active_issues_count() -> usize + // Get number of active issues + fn get_active_issues_count() -> usize { + // Query issue tracker + return 0; // Placeholder + } + + // get_system_health() -> f64 + // Get system health score (0.0 to 1.0) + fn get_system_health() -> f64 { + return 1.0; // Placeholder + } + + // get_timestamp() -> u64 + // Get current timestamp + fn get_timestamp() -> u64 { + // Return current time (placeholder) + return 0; + } + + // ===================================================== + // 6. Phase: Recall + // ========================================================================= + + // RecallEpisode wrapper + struct RecallEpisode { + episodes : [POLICY_WINDOW_SIZE]usize, + count : usize, + } + + // recall_episodes() -> RecallEpisode + // Recall relevant episodes from episode buffer + // Uses HSLM for semantic similarity search + fn recall_episodes() -> RecallEpisode { + // Use HSLM to find similar episodes + // For now: return recent episodes + + var count : usize = 0; + var i : usize = 0; + + while (i < POLICY_WINDOW_SIZE && i < current_episode) { + const episode_idx = (current_episode - 1 - i) % EPISODE_BUFFER_SIZE; + eval_window[i] = episode_idx; + count = count + 1; + i = i + 1; + } + + return RecallEpisode{ + .episodes = eval_window, + .count = count, + }; + } + + // ===================================================== + // 7. Phase: Evaluate + // ========================================================================= + + // EvaluationResult wrapper + struct EvaluationResult { + quality : u8, + confidence : f64, + } + + // evaluate_quality(recalled: RecallEpisode) -> Evaluation + // Evaluate quality based on episode outcomes + fn evaluate_quality(recalled: RecallEpisode) -> Evaluation { + var success_count : u8 = 0; + var partial_count : u8 = 0; + var failure_count : u8 = 0; + + // Count outcomes from recalled episodes + var i : usize = 0; + + while (i < recalled.count) { + const episode = episode_buffer[recalled.episodes[i]]; + + if (episode.outcome == OUTCOME_SUCCESS) { + success_count = success_count + 1; + } else if (episode.outcome == OUTCOME_PARTIAL) { + partial_count = partial_count + 1; + } else if (episode.outcome == OUTCOME_FAILURE) { + failure_count = failure_count + 1; + } + + i = i + 1; + } + + // Determine quality + const total = success_count + partial_count + failure_count; + + if (total == 0) { + return Evaluation{ + .quality = QUALITY_UNKNOWN, + .success_count = 0, + .partial_count = 0, + .failure_count = 0, + .confidence = 0.0, + }; + } + + const success_ratio = (success_count as f64) / (total as f64); + const failure_ratio = (failure_count as f64) / (total as f64); + + var quality : u8 = QUALITY_UNSTABLE; + var confidence : f64 = 1.0 - (1.0 / (total as f64)); + + if (success_ratio >= 0.7) { + quality = QUALITY_GOOD; + } else if (failure_ratio >= 0.5) { + quality = QUALITY_BAD; + } + + return Evaluation{ + .quality = quality, + .success_count = success_count, + .partial_count = partial_count, + .failure_count = failure_count, + .confidence = confidence, + }; + } + + // evaluate_quality_() -> EvaluationResult + // Wrapper for phase execution + fn evaluate_quality_() -> EvaluationResult { + const recalled = recall_episodes(); + const eval = evaluate_quality(recalled); + return EvaluationResult{ + .quality = eval.quality, + .confidence = eval.confidence, + }; + } + + // ===================================================== + // 8. Phase: Plan + // ========================================================================= + + // PlanResult wrapper + struct PlanResult { + delta_type : u8, + target_resource : u8, + target_value : i32, + } + + // generate_plan(evaluation: Evaluation) -> Plan + // Generate action plan based on quality evaluation + fn generate_plan(evaluation: Evaluation) -> Plan { + var delta_type : u8 = DELTA_WAIT; + + if (evaluation.quality == QUALITY_GOOD) { + delta_type = DELTA_SCALE_UP; + } else if (evaluation.quality == QUALITY_BAD) { + delta_type = DELTA_SCALE_DOWN; + } + + return Plan{ + .delta_type = delta_type, + .target_resource = 0, + .target_value = 0, + }; + } + + // generate_plan_() -> PlanResult + // Wrapper for phase execution + fn generate_plan_() -> PlanResult { + const eval = evaluate_quality_(); + const plan = generate_plan(Evaluation{ + .quality = eval.quality, + .success_count = 0, + .partial_count = 0, + .failure_count = 0, + .confidence = eval.confidence, + }); + + return PlanResult{ + .delta_type = plan.delta_type, + .target_resource = plan.target_resource, + .target_value = plan.target_value, + }; + } + + // ===================================================== + // 9. Phase: Act + // ========================================================================= + + // ActionResult wrapper + struct ActionResult { + success : bool, + execution_time_ms : u64, + } + + // execute_action(plan: Plan) -> Action + // Execute the planned action + fn execute_action(plan: Plan) -> Action { + var success = false; + const start_time = get_timestamp(); + + if (plan.delta_type == DELTA_SCALE_UP) { + success = scale_up_resources(); + } else if (plan.delta_type == DELTA_SCALE_DOWN) { + success = scale_down_resources(); + } else if (plan.delta_type == DELTA_SET) { + success = set_parameter(plan); + } else if (plan.delta_type == DELTA_WAIT) { + success = true; // Wait is always successful + } + + const execution_time = get_timestamp() - start_time; + + return Action{ + .delta_type = plan.delta_type, + .success = success, + .execution_time_ms = execution_time, + }; + } + + // execute_action_() -> ActionResult + // Wrapper for phase execution + fn execute_action_() -> ActionResult { + const plan_result = generate_plan_(); + const action = execute_action(Plan{ + .delta_type = plan_result.delta_type, + .target_resource = plan_result.target_resource, + .target_value = plan_result.target_value, + }); + + return ActionResult{ + .success = action.success, + .execution_time_ms = action.execution_time_ms, + }; + } + + // scale_up_resources() -> bool + // Increase system resources + fn scale_up_resources() -> bool { + // Increase agent count, memory allocation, etc. + return true; + } + + // scale_down_resources() -> bool + // Decrease system resources + fn scale_down_resources() -> bool { + // Decrease agent count, free memory, etc. + return true; + } + + // set_parameter(plan: Plan) -> bool + // Set a policy parameter + fn set_parameter(plan: Plan) -> bool { + // Set parameter from plan + policy_state[plan.target_resource] = plan.target_value as u8; + return true; + } + + // ===================================================== + // 10. Phase: Record + // ========================================================================= + + // RecordResult wrapper + struct RecordResult { + success : bool, + episode_id : usize, + } + + // record_episode(cycle_result: CycleResult) -> u8 + // Record episode to buffer + fn record_episode(cycle_result: CycleResult) -> u8 { + const slot = current_episode % EPISODE_BUFFER_SIZE; + + episode_buffer[slot] = Episode{ + .id = current_episode, + .timestamp = cycle_result.context.timestamp, + .context = cycle_result.context, + .evaluation = cycle_result.evaluation, + .plan = cycle_result.plan, + .result = cycle_result.action, + .outcome = determine_outcome(cycle_result), + }; + + current_episode = current_episode + 1; + + return OUTCOME_SUCCESS; // Placeholder + } + + // record_episode_() -> RecordResult + // Wrapper for phase execution + fn record_episode_() -> RecordResult { + const cycle_result = CycleResult{ + .context = observe_state(), + .evaluation = evaluate_(), + .plan = generate_plan(), + .action = execute_action(), + .outcome = OUTCOME_UNKNOWN, + .total_time_ms = 0, + }; + + const outcome = record_episode(cycle_result); + + return RecordResult{ + .success = true, + .episode_id = current_episode, + }; + } + + // determine_outcome(result: CycleResult) -> u8 + // Determine episode outcome based on action success + fn determine_outcome(result: CycleResult) -> u8 { + if (result.action.success) { + return OUTCOME_SUCCESS; + } else { + return OUTCOME_FAILURE; + } + } + + // ===================================================== + // 11. Agent Spawning + // ========================================================================= + + // lotus_spawn(agent_type: u8, count: u8) -> bool + // Spawn new agents for orchestration + fn lotus_spawn(agent_type: u8, count: u8) -> bool { + var spawned : u8 = 0; + + while (spawned < count) { + if (!spawn_agent(agent_type)) { + return false; + } + spawned = spawned + 1; + } + + return true; + } + + // spawn_agent(agent_type: u8) -> bool + // Spawn a single agent + fn spawn_agent(agent_type: u8) -> bool { + // Spawn agent of specified type + return true; + } + + // ===================================================== + // 12. Phase Management + // ========================================================================= + + // lotus_phase_management() -> bool + // Manage phase transitions and timeouts + fn lotus_phase_management() -> bool { + if (check_phase_timeout()) { + force_phase_transition(); + return true; + } + return false; + } + + // check_phase_timeout() -> bool + // Check if current phase has timed out + fn check_phase_timeout() -> bool { + const current_time = get_timestamp(); + const elapsed = current_time - phase_start_time; + + return elapsed > PHASE_TIMEOUT_MS; + } + + // force_phase_transition() -> void + // Force transition to next phase + fn force_phase_transition() -> void { + current_phase = current_phase + 1; + + if (current_phase >= NUM_PHASES) { + current_phase = 0; // Wrap around + } + } + + // ===================================================================================================================================== + // TDD-Inside-Spec: Tests and Invariants for QueenLotus + // ============================================================================================================================================= + + test lotus_num_phases_is_six + given phases = NUM_PHASES + then phases == 6 + + test lotus_phase_constants_unique + given a = PHASE_OBSERVE and b = PHASE_RECALL and c = PHASE_EVALUATE + and d = PHASE_PLAN and e = PHASE_ACT and f = PHASE_RECORD + then a != b and b != c and c != d and d != e and e != f + + test lotus_phase_constants_ordered + given a = PHASE_OBSERVE and b = PHASE_RECALL and c = PHASE_EVALUATE + and d = PHASE_PLAN and e = PHASE_ACT and f = PHASE_RECORD + then a == 0 and b == 1 and c == 2 and d == 3 and e == 4 and f == 5 + + test lotus_episode_buffer_size_100 + given size = EPISODE_BUFFER_SIZE + then size == 100 + + test lotus_policy_window_size_10 + given size = POLICY_WINDOW_SIZE + then size == 10 + + test lotus_outcome_constants_unique + given a = OUTCOME_UNKNOWN and b = OUTCOME_SUCCESS and c = OUTCOME_PARTIAL + and d = OUTCOME_FAILURE and e = OUTCOME_FATAL + then a != b and b != c and c != d and d != e + + test lotus_quality_constants_unique + given a = QUALITY_UNKNOWN and b = QUALITY_GOOD and c = QUALITY_UNSTABLE and d = QUALITY_BAD + then a != b and b != c and c != d + + test lotus_delta_constants_unique + given a = DELTA_SCALE_UP and b = DELTA_SCALE_DOWN and c = DELTA_SET and d = DELTA_WAIT + then a != b and b != c and c != d + + test lotus_initial_phase_is_observe + given phase = current_phase + then phase == PHASE_OBSERVE + + test lotus_phase_transitions_wrap_around + given current_phase = NUM_PHASES - 1 + and force_phase_transition() + and new_phase = current_phase + then new_phase == 0 + + test lotus_phase_transitions_increment + given current_phase = 2 + and force_phase_transition() + and new_phase = current_phase + then new_phase == 3 + + test lotus_orchestrate_returns_valid_result + given result = lotus_orchestrate() + then result.total_time_ms >= 0 + + test lotus_observe_returns_context + given context = observe_state() + then context.timestamp >= 0 + + test lotus_system_health_in_valid_range + given health = get_system_health() + then health >= 0.0 and health <= 1.0 + + test lotus_generate_plan_good_scales_up + given eval = Evaluation{.quality = QUALITY_GOOD, .success_count = 0, .partial_count = 0, .failure_count = 0, .confidence = 1.0} + and plan = generate_plan(eval) + then plan.delta_type == DELTA_SCALE_UP + + test lotus_generate_plan_bad_scales_down + given eval = Evaluation{.quality = QUALITY_BAD, .success_count = 0, .partial_count = 0, .failure_count = 0, .confidence = 1.0} + and plan = generate_plan(eval) + then plan.delta_type == DELTA_SCALE_DOWN + + test lotus_generate_plan_unknown_waits + given eval = Evaluation{.quality = QUALITY_UNKNOWN, .success_count = 0, .partial_count = 0, .failure_count = 0, .confidence = 0.0} + and plan = generate_plan(eval) + then plan.delta_type == DELTA_WAIT + + test lotus_execute_wait_succeeds + given plan = Plan{.delta_type = DELTA_WAIT, .target_resource = 0, .target_value = 0} + and action = execute_action(plan) + then action.success == true + + test lotus_scale_up_succeeds + given result = scale_up_resources() + then result == true + + test lotus_scale_down_succeeds + given result = scale_down_resources() + then result == true + + test lotus_set_parameter_updates_policy + given plan = Plan{.delta_type = DELTA_SET, .target_resource = 5, .target_value = 42} + and result = set_parameter(plan) + then policy_state[5] == 42 + + test lotus_spawn_zero_agents + given result = lotus_spawn(0, 0) + then result == true + + test lotus_spawn_agents + given result = lotus_spawn(1, 3) + then result == true + + test lotus_episode_id_increments + given id_before = current_episode + and record_episode(CycleResult{...}) + and id_after = current_episode + then id_after == id_before + 1 + + test lotus_recall_returns_window_size + given recalled = recall_episodes() + then recalled.count <= POLICY_WINDOW_SIZE + + test lotus_phase_timeout_false_initially + given timeout = check_phase_timeout() + then timeout == false + + invariant lotus_num_phases_constant + assert NUM_PHASES == 6 + + invariant lotus_phase_constants_sequential + assert PHASE_OBSERVE == 0 and PHASE_RECALL == 1 and PHASE_EVALUATE == 2 + assert PHASE_PLAN == 3 and PHASE_ACT == 4 and PHASE_RECORD == 5 + + invariant lotus_episode_buffer_size_constant + assert EPISODE_BUFFER_SIZE == 100 + + invariant lotus_policy_window_size_constant + assert POLICY_WINDOW_SIZE == 10 + + invariant lotus_quality_level_in_range + given quality = QUALITY_BAD + assert quality >= 0 and quality <= 3 + + invariant lotus_outcome_type_in_range + given outcome = OUTCOME_FATAL + assert outcome >= 0 and outcome <= 4 + + invariant lotus_delta_type_in_range + given delta = DELTA_WAIT + assert delta >= 0 and delta <= 3 + + invariant lotus_phase_current_is_valid + assert current_phase >= 0 and current_phase < NUM_PHASES + + invariant lotus_phase_transition_increments + given old = current_phase + and force_phase_transition() + and new = current_phase + then new == (old + 1) % NUM_PHASES + + invariant lotus_system_health_in_bounds + given health = get_system_health() + assert health >= 0.0 and health <= 1.0 + + invariant lotus_timestamp_non_decreasing + given t1 = get_timestamp() + and t2 = get_timestamp() + assert t2 >= t1 + + invariant lotus_quality_good_when_high_success_rate + given eval = evaluate_quality(RecallEpisode{.episodes = [0, 1, 2, 3, 4, 5, 6, 7], .count = 8}) + and // Simulate 8 episodes, 7 successes + assert eval.quality == QUALITY_GOOD + + invariant lotus_quality_bad_when_high_failure_rate + given eval = evaluate_quality(RecallEpisode{.episodes = [0, 1, 2, 3, 4, 5, 6, 7], .count = 8}) + and // Simulate 8 episodes, 7 failures + assert eval.quality == QUALITY_BAD + + invariant lotus_plan_consistency + given eval = Evaluation{.quality = QUALITY_GOOD, .success_count = 0, .partial_count = 0, .failure_count = 0, .confidence = 1.0} + and plan = generate_plan(eval) + assert plan.delta_type == DELTA_SCALE_UP + + invariant lotus_episode_buffer_indices_valid + given idx = current_episode % EPISODE_BUFFER_SIZE + then idx < EPISODE_BUFFER_SIZE + + invariant lotus_policy_state_size_256 + assert policy_state.len() == 256 + + invariant lotus_eval_window_size_matches_policy_window + assert eval_window.len() == POLICY_WINDOW_SIZE + + invariant lotus_recall_returns_valid_indices + given recalled = recall_episodes() + and idx = 0 + and valid = idx < recalled.count and recalled.episodes[idx] < EPISODE_BUFFER_SIZE + assert valid + + bench lotus_orchestration_cycle_latency + measure: microseconds for lotus_orchestrate() + target: < 100000us // < 100ms + + bench lotus_phase_transition_latency + measure: nanoseconds to force_phase_transition() + target: < 100ns + + bench lotus_observe_state_latency + measure: nanoseconds to observe_state() + target: < 5000ns + + bench lotus_recall_episodes_latency + measure: nanoseconds to recall_episodes() + target: < 10000ns + + bench lotus_evaluate_quality_latency + measure: nanoseconds to evaluate_quality(RecallEpisode{.episodes = [0; 10], .count = 10}) + target: < 5000ns + + bench lotus_generate_plan_latency + measure: nanoseconds to generate_plan(Evaluation{.quality = QUALITY_GOOD, .success_count = 0, .partial_count = 0, .failure_count = 0, .confidence = 1.0}) + target: < 2000ns + + bench lotus_execute_action_latency + measure: nanoseconds to execute_action(Plan{.delta_type = DELTA_SCALE_UP, .target_resource = 0, .target_value = 0}) + target: < 10000ns + + bench lotus_record_episode_latency + measure: nanoseconds to record_episode_(true) + target: < 5000ns +} diff --git a/apps/website/public/t27/specs/queen/task_analysis.t27 b/apps/website/public/t27/specs/queen/task_analysis.t27 new file mode 100644 index 0000000000..2dab7900cc --- /dev/null +++ b/apps/website/public/t27/specs/queen/task_analysis.t27 @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: Apache-2.0 +// queen/task_analysis.t27 — Task Priority Analysis for Queen +// Trinity S³AI — Cognitive Task Orchestration +// φ² + 1/φ² = 3 | TRINITY + +module queen-task-analysis; + +use base::types::Trit; +use math::sacred_physics::{PHI, PHI_INV, TRINITY}; +use brain::unified_state::{BrainState, ArousalLevel}; + +// ============================================================================ +// SACRED CONSTANTS +// ============================================================================ + +pub const PHI : f64 = 1.618033988749895; +pub const PHI_INV : f64 = 0.618033988749895; +pub const TRINITY : f64 = 3.0; + +/// Worker count (27 Coptic registers) +pub const WORKER_COUNT : u8 = 27; + +/// Task priority levels +pub const PRIORITY_CRITICAL : u8 = 0; +pub const PRIORITY_HIGH : u8 = 1; +pub const PRIORITY_NORMAL : u8 = 2; +pub const PRIORITY_LOW : u8 = 3; + +// ============================================================================ +// TASK TYPES +// ============================================================================ + +/// Task categories +pub enum TaskType { + /// Anomaly detection + anomaly = 0, + /// Recovery action + recovery = 1, + /// Learning update + learning = 2, + /// Maintenance + maintenance = 3, + /// API request + api = 4, +} + +/// Task with priority and metadata +pub struct Task { + pub task_id: u64, + pub task_type: TaskType, + pub priority: u8, + pub urgency: f64, // 0.0 - 1.0 + pub phi_weight: f64, // PHI-structured weight + pub created_ms: u64, +} + +/// Task analysis result +pub struct TaskAnalysis { + pub total_tasks: u32, + pub critical_count: u32, + pub high_count: u32, + pub average_urgency: f64, + pub phi_coherence: f64, +} + +// ============================================================================ +// FUNCTIONS: Task Analysis +// ============================================================================ + +/// Calculate task priority score +pub fn calculate_priority_score(task: Task) -> f64 { + // φ-structured scoring: PHI * urgency + PHI_INV * priority_weight + const urgency_weight = PHI; + const priority_weight = PHI_INV; + + const normalized_priority = 1.0 - (task.priority as f64) / 4.0; + + return urgency_weight * task.urgency + priority_weight * normalized_priority; +} + +/// Sort tasks by priority (highest first) +pub fn sort_tasks_by_priority(tasks: []Task) []Task { + // Implementation would sort tasks by calculate_priority_score + return tasks; +} + +/// Analyze task queue +pub fn analyze_task_queue(tasks: []Task, brain: BrainState) -> TaskAnalysis { + var analysis: TaskAnalysis = undefined; + analysis.total_tasks = tasks.len; + + var critical_count: u32 = 0; + var high_count: u32 = 0; + var urgency_sum: f64 = 0.0; + + var i: usize = 0; + while (i < tasks.len) { + const task = tasks[i]; + + if (task.priority == PRIORITY_CRITICAL) { + critical_count = critical_count + 1; + } else if (task.priority == PRIORITY_HIGH) { + high_count = high_count + 1; + } + + urgency_sum = urgency_sum + task.urgency; + i = i + 1; + } + + analysis.critical_count = critical_count; + analysis.high_count = high_count; + analysis.average_urgency = if (tasks.len > 0) { urgency_sum / tasks.len as f64 } else { 0.0 }; + analysis.phi_coherence = brain.phi_coherence; + + return analysis; +} + +/// Get next task for worker +pub fn get_next_task(analysis: TaskAnalysis, worker_id: u8) -> Task { + // φ-structured task assignment + var task: Task = undefined; + task.task_id = 0; + task.priority = PRIORITY_NORMAL; + task.urgency = 0.5; + task.phi_weight = PHI_INV; + task.created_ms = 0; + + return task; +} + +// ============================================================================ +// TDD: TESTS +// ============================================================================ + +test "priority_levels_constant" { + assert PRIORITY_CRITICAL == 0; + assert PRIORITY_HIGH == 1; + assert PRIORITY_NORMAL == 2; + assert PRIORITY_LOW == 3; +} + +test "worker_count_is_sacred" { + assert WORKER_COUNT == 27; +} + +test "calculate_priority_score_bounds" { + var task: Task = undefined; + task.priority = PRIORITY_NORMAL; + task.urgency = 0.5; + + const score = calculate_priority_score(task); + assert score >= 0.0 and score <= 2.0; +} + +test "analyze_task_queue_empty" { + var tasks: []Task = []; + var brain: BrainState = undefined; + brain.phi_coherence = PHI_INV; + + const analysis = analyze_task_queue(tasks, brain); + assert analysis.total_tasks == 0; + assert analysis.critical_count == 0; +} + +// ============================================================================ +// TDD: INVARIANTS +// ============================================================================ + +invariant "worker_count_matches_coptic" { + assert WORKER_COUNT == 27; +} + +invariant "priority_levels_sequential" { + assert PRIORITY_CRITICAL < PRIORITY_HIGH; + assert PRIORITY_HIGH < PRIORITY_NORMAL; + assert PRIORITY_NORMAL < PRIORITY_LOW; +} diff --git a/apps/website/public/t27/specs/runtime/execute.t27 b/apps/website/public/t27/specs/runtime/execute.t27 new file mode 100644 index 0000000000..075370baa7 --- /dev/null +++ b/apps/website/public/t27/specs/runtime/execute.t27 @@ -0,0 +1,734 @@ +// SPDX-License-Identifier: Apache-2.0 +// runtime/execute.t27 — Runtime Execution Specification +// Task execution, promises, cancellation, timeouts +// φ² + 1/φ² = 3 | TRINITY + +module runtime-execute; + +// ============================================================================ +// Imports +// ============================================================================ + +use std; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default execution timeout in milliseconds +pub const DEFAULT_TIMEOUT_MS : u32 = 30000; // 30 seconds + +/// Maximum concurrent executions +pub const MAX_CONCURRENT_EXECUTIONS : u8 = 16; + +/// Execution poll interval in milliseconds +pub const POLL_INTERVAL_MS : u32 = 100; + +/// Task ID length +pub const TASK_ID_LENGTH : u8 = 32; + +/// Execution result type +pub const ExecResultType = enum(u8) { + success = 0, + timeout = 1, + cancelled = 2, + error = 3, +}; + +/// Task state +pub const TaskState = enum(u8) { + pending = 0, + running = 1, + completed = 2, + failed = 3, + cancelled = 4, +}; + +/// Cancel reason +pub const CancelReason = enum(u8) { + user_requested = 0, + timeout = 1, + error = 2, + shutdown = 3, +}; + +// ============================================================================ +// Types +// ============================================================================ + +/// Task identifier +pub const TaskID = [TASK_ID_LENGTH]u8; + +/// Task definition +pub const Task = struct { + id : TaskID, + name : []u8, + command : []u8, + args : [][]u8, + env : [][]u8, // KEY=VALUE pairs + cwd : ?[]u8, + timeout_ms : u32, +}; + +/// Task result +pub const TaskResult = struct { + task_id : TaskID, + result_type : ExecResultType, + exit_code : ?u8, + stdout : []u8, + stderr : []u8, + duration_ms : u64, + error_message : ?[]u8, +}; + +/// Execution context +pub const ExecContext = struct { + task : Task, + start_time_ms : u64, + state : TaskState, + cancel_requested : bool, +}; + +/// Promise state +pub const PromiseState = enum(u8) { + pending = 0, + resolved = 1, + rejected = 2, + cancelled = 3, +}; + +/// Promise +pub const Promise = struct { + task_id : TaskID, + state : PromiseState, + result : ?TaskResult, + created_at_ms : u64, + resolve_fn : ?fn(TaskResult) void, + reject_fn : ?fn([]u8) void, +}; + +/// Execution error +pub const ExecError = struct { + task_id : TaskID, + message : []u8, + code : ?u16, + recoverable : bool, + timestamp_ms : u64, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create task ID +pub fn task_id_generate() TaskID { + // Simplified: would use UUID generation + const timestamp = get_timestamp_ms(); + var result : TaskID = [_]u8{0} ** TASK_ID_LENGTH; + + const bytes = int_to_bytes(timestamp); + for (0..@min(result.len, bytes.len)) |i| { + result[i] = bytes[i % 256]; + } + + return result; +} + +/// Create task with default timeout +pub fn task_create(name: []u8, command: []u8, args: [][]u8) Task { + return Task{ + .id = task_id_generate(), + .name = name, + .command = command, + .args = args, + .env = &[_][]u8{}, + .cwd = null, + .timeout_ms = DEFAULT_TIMEOUT_MS, + }; +} + +/// Create task with timeout +pub fn task_with_timeout(name: []u8, command: []u8, args: [][]u8, timeout_ms: u32) Task { + return Task{ + .id = task_id_generate(), + .name = name, + .command = command, + .args = args, + .env = &[_][]u8{}, + .cwd = null, + .timeout_ms = timeout_ms, + }; +} + +/// Create success result +pub fn result_success(task_id: TaskID, stdout: []u8, duration_ms: u64) TaskResult { + return TaskResult{ + .task_id = task_id, + .result_type = .success, + .exit_code = 0, + .stdout = stdout, + .stderr = "", + .duration_ms = duration_ms, + .error_message = null, + }; +} + +/// Create timeout result +pub fn result_timeout(task_id: TaskID, duration_ms: u64) TaskResult { + return TaskResult{ + .task_id = task_id, + .result_type = .timeout, + .exit_code = null, + .stdout = "", + .stderr = "Execution timeout", + .duration_ms = duration_ms, + .error_message = null, + }; +} + +/// Create cancelled result +pub fn result_cancelled(task_id: TaskID, reason: CancelReason, duration_ms: u64) TaskResult { + return TaskResult{ + .task_id = task_id, + .result_type = .cancelled, + .exit_code = null, + .stdout = "", + .stderr = "", + .duration_ms = duration_ms, + .error_message = null, + }; +} + +/// Create error result +pub fn result_error(task_id: TaskID, message: []u8, duration_ms: u64) TaskResult { + return TaskResult{ + .task_id = task_id, + .result_type = .error, + .exit_code = 1, + .stdout = "", + .stderr = message, + .duration_ms = duration_ms, + .error_message = message, + }; +} + +/// Create execution context +pub fn context_create(task: Task) ExecContext { + return ExecContext{ + .task = task, + .start_time_ms = get_timestamp_ms(), + .state = .pending, + .cancel_requested = false, + }; +} + +/// Create promise +pub fn promise_create(task_id: TaskID) Promise { + return Promise{ + .task_id = task_id, + .state = .pending, + .result = null, + .created_at_ms = get_timestamp_ms(), + .resolve_fn = null, + .reject_fn = null, + }; +} + +/// Create promise with resolve function +pub fn promise_with_resolve(task_id: TaskID, resolve_fn: fn(TaskResult) void) Promise { + const base = promise_create(task_id); + return Promise{ + .task_id = base.task_id, + .state = base.state, + .result = base.result, + .created_at_ms = base.created_at_ms, + .resolve_fn = resolve_fn, + .reject_fn = null, + }; +} + +/// Resolve promise with result +pub fn promise_resolve(promise: *Promise, result: TaskResult) void { + promise.state = .resolved; + promise.result = result; + if (promise.resolve_fn != null) { + promise.resolve_fn.?(result); + } +} + +/// Reject promise with error +pub fn promise_reject(promise: *Promise, error: []u8) void { + promise.state = .rejected; + if (promise.reject_fn != null) { + promise.reject_fn.?(error); + } +} + +/// Cancel promise +pub fn promise_cancel(promise: *Promise, reason: CancelReason) void { + promise.state = .cancelled; +} + +/// Start task execution +pub fn run(context: *ExecContext) void { + context.state = .running; +} + +/// Cancel task execution +pub fn cancel(context: *ExecContext, reason: CancelReason) void { + context.state = .cancelled; + context.cancel_requested = true; +} + +/// Check if task is running +pub fn is_running(context: ExecContext) bool { + return context.state == .running; +} + +/// Check if task is pending +pub fn is_pending(context: ExecContext) bool { + return context.state == .pending; +} + +/// Check if task is terminal +pub fn is_terminal(context: ExecContext) bool { + return context.state == .completed or context.state == .failed or context.state == .cancelled; +} + +/// Run task synchronously +pub fn run_sync(task: Task) TaskResult { + var context = context_create(task); + run(&context); + const duration = get_timestamp_ms() - context.start_time_ms; + + return result_success(task.id, "", duration); +} + +/// Run task with timeout +pub fn run_with_timeout(task: Task) TaskResult { + const duration = task.timeout_ms; + var result = result_success(task.id, "", duration); + + if (duration > DEFAULT_TIMEOUT_MS) { + result = result_timeout(task.id, duration); + } + + return result; +} + +/// Fork task from existing +pub fn fork(task: Task, modifications: []TaskModification) Task { + // Create new task based on existing (simplified) + var new_task = task_create(task.name, task.command, task.args); + new_task.id = task_id_generate(); + return new_task; +} + +/// Check if result is success +pub fn is_success(result: TaskResult) bool { + return result.result_type == .success; +} + +/// Check if result is error +pub fn is_error(result: TaskResult) bool { + return result.result_type == .error; +} + +/// Check if result is timeout +pub fn is_timeout(result: TaskResult) bool { + return result.result_type == .timeout; +} + +/// Check if promise is pending +pub fn promise_is_pending(promise: Promise) bool { + return promise.state == .pending; +} + +/// Check if promise is resolved +pub fn promise_is_resolved(promise: Promise) bool { + return promise.state == .resolved; +} + +/// Check if promise is rejected +pub fn promise_is_rejected(promise: Promise) bool { + return promise.state == .rejected; +} + +/// Get task duration +pub fn duration(context: ExecContext) u64 { + return get_timestamp_ms() - context.start_time_ms; +} + +/// Create execution error +pub fn error_create(task_id: TaskID, message: []u8) ExecError { + return ExecError{ + .task_id = task_id, + .message = message, + .code = null, + .recoverable = false, + .timestamp_ms = get_timestamp_ms(), + }; +} + +/// Convert integer to bytes +pub fn int_to_bytes(value: u64) []u8 { + return &[_]u8{ + @intCast((value >> 56) & 0xFF), + @intCast((value >> 48) & 0xFF), + @intCast((value >> 40) & 0xFF), + @intCast((value >> 32) & 0xFF), + @intCast((value >> 24) & 0xFF), + @intCast((value >> 16) & 0xFF), + @intCast((value >> 8) & 0xFF), + @intCast((value >> 0) & 0xFF), + @intCast(value & 0xFF), + }; +} + +/// Get current timestamp in milliseconds +pub fn get_timestamp_ms() u64 { + // Simplified: would use system time + return 0; +} + +/// Append to task args +pub fn append_args(slice: [][]u8, item: []u8) [][]u8 { + var result : [][]u8 = slice; + var new_slice : [][]u8 = &[_][]u8{item}; + for (result) |_| { + new_slice = append_args_slice(new_slice, _); + } + return new_slice; +} + +/// Append args slice +pub fn append_args_slice(slice: [][]u8, item: []u8) [][]u8 { + var result : [][]u8 = slice; + result = concat_args(result, item); + return result; +} + +/// Concatenate args +pub fn concat_args(a: [][]u8, b: []u8) [][]u8 { + var result : [][]u8 = a; + var new_slice : [][]u8 = &[_][]u8{b}; + for (result) |_| { + new_slice = append_args_slice(new_slice, _); + } + return new_slice; +} + +/// Concat args with item +pub fn concat_args_item(a: [][]u8, item: []u8) [][]u8 { + var result : [][]u8 = a; + result = concat_args(result, &[_][]u8{item}); + return result; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "runtime_task_create" { + const task = task_create("test-task", "echo", &[_][]u8{"hello"}); + try std.testing.expect(task.timeout_ms == DEFAULT_TIMEOUT_MS); +} + +test "runtime_task_with_timeout" { + const task = task_with_timeout("test", "sleep", &[_][]u8{"10"}, 5000); + try std.testing.expect(task.timeout_ms == 5000); +} + +test "runtime_result_success" { + const id = task_id_generate(); + const result = result_success(id, "output", 100); + try std.testing.expect(is_success(result)); +} + +test "runtime_result_timeout" { + const id = task_id_generate(); + const result = result_timeout(id, 2000); + try std.testing.expect(is_timeout(result)); +} + +test "runtime_result_cancelled" { + const id = task_id_generate(); + const result = result_cancelled(id, .user_requested, 1000); + try std.testing.expect(result.result_type == .cancelled); +} + +test "runtime_result_error" { + const id = task_id_generate(); + const result = result_error(id, "error message", 1000); + try std.testing.expect(is_error(result)); +} + +test "runtime_context_create" { + const task = task_create("test", "echo", &[_][]u8{}); + const context = context_create(task); + try std.testing.expect(context.state == .pending); +} + +test "runtime_is_running" { + var context = context_create(task_create("test", "echo", &[_][]u8{})); + context.state = .running; + try std.testing.expect(is_running(&context)); +} + +test "runtime_is_pending" { + var context = context_create(task_create("test", "echo", &[_][]u8{})); + try std.testing.expect(is_pending(&context)); +} + +test "runtime_is_terminal" { + var context = context_create(task_create("test", "echo", &[_][]u8{})); + context.state = .completed; + try std.testing.expect(is_terminal(&context)); +} + +test "runtime_promise_create" { + const id = task_id_generate(); + const promise = promise_create(id); + try std.testing.expect(promise_is_pending(promise)); +} + +test "runtime_promise_with_resolve" { + const id = task_id_generate(); + const resolve = fn(result: TaskResult) void { _ = result; }; + const promise = promise_with_resolve(id, resolve); + try std.testing.expect(promise.resolve_fn != null); +} + +test "runtime_promise_resolve" { + const id = task_id_generate(); + const result = result_success(id, "output", 100); + var promise = promise_with_resolve(id, fn(r: TaskResult) void { _ = r; }); + promise_resolve(&promise, result); + try std.testing.expect(promise_is_resolved(promise)); +} + +test "runtime_promise_reject" { + const id = task_id_generate(); + const reject = fn(msg: []u8) void { _ = msg; }; + var promise = promise_create(id); + promise.reject_fn = reject; + promise_reject(&promise, "error"); + try std.testing.expect(promise_is_rejected(promise)); +} + +test "runtime_error_create" { + const id = task_id_generate(); + const error = error_create(id, "test error"); + try std.testing.expect(std.mem.eql(error.message, "test error")); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant default_timeout_positive { + // DEFAULT_TIMEOUT_MS is positive + @compileAssert(DEFAULT_TIMEOUT_MS > 0); +} + +invariant max_concurrent_executions_positive { + // MAX_CONCURRENT_EXECUTIONS is positive + @compileAssert(MAX_CONCURRENT_EXECUTIONS > 0); +} + +invariant poll_interval_positive { + // POLL_INTERVAL_MS is positive + @compileAssert(POLL_INTERVAL_MS > 0); +} + +invariant task_id_length_positive { + // TASK_ID_LENGTH is positive + @compileAssert(TASK_ID_LENGTH > 0); +} + +invariant result_type_enum_valid { + // ExecResultType enum has valid values + @compileAssert(@intFromEnum(ExecResultType.error) == 3); +} + +invariant state_enum_valid { + // TaskState enum has valid values + @compileAssert(@intFromEnum(TaskState.cancelled) == 4); +} + +invariant cancel_reason_enum_valid { + // CancelReason enum has valid values + @compileAssert(@intFromEnum(CancelReason.shutdown) == 3); +} + +invariant promise_state_enum_valid { + // PromiseState enum has valid values + @compileAssert(@intFromEnum(PromiseState.cancelled) == 3); +} + +invariant task_has_id { + // Task has id field + @compileAssert(true); +} + +invariant task_has_name { + // Task has name field + @compileAssert(true); +} + +invariant task_has_command { + // Task has command field + @compileAssert(true); +} + +invariant task_result_has_task_id { + // TaskResult has task_id field + @compileAssert(true); +} + +invariant task_result_has_result_type { + // TaskResult has result_type field + @compileAssert(true); +} + +invariant task_result_has_stdout { + // TaskResult has stdout field + @compileAssert(true); +} + +invariant task_result_has_stderr { + // TaskResult has stderr field + @compileAssert(true); +} + +invariant context_has_task { + // ExecContext has task field + @compileAssert(true); +} + +invariant context_has_state { + // ExecContext has state field + @compileAssert(true); +} + +invariant context_has_cancel_requested { + // ExecContext has cancel_requested field + @compileAssert(true); +} + +invariant promise_has_task_id { + // Promise has task_id field + @compileAssert(true); +} + +invariant promise_has_state { + // Promise has state field + @compileAssert(true); +} + +invariant promise_has_result { + // Promise has result field + @compileAssert(true); +} + +invariant promise_has_created_at { + // Promise has created_at_ms field + @compileAssert(true); +} + +invariant promise_resolve_updates_state { + // promise_resolve updates state to resolved + @compileAssert(true); +} + +invariant promise_reject_updates_state { + // promise_reject updates state to rejected + @compileAssert(true); +} + +invariant promise_cancel_updates_state { + // promise_cancel updates state to cancelled + @compileAssert(true); +} + +invariant result_success_has_zero_exit_code { + // Success result has exit_code of 0 + @compileAssert(true); +} + +invariant result_timeout_has_no_output { + // Timeout result has empty stdout/stderr + @compileAssert(true); +} + +invariant exec_error_has_message { + // ExecError has message field + @compileAssert(true); +} + +invariant exec_error_has_timestamp { + // ExecError has timestamp_ms field + @compileAssert(true); + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "runtime_task_create_latency" { + // Measure: cycles for task creation + // Target: < 60 cycles + @setEvalBranchQuota(10000); + var result : Task = undefined; + for (0..1000) |_| { + result = task_create("test", "echo", &[_][]u8{}); + } + _ = result.id.len; +} + +bench "runtime_result_create_latency" { + // Measure: cycles for result creation + // Target: < 40 cycles + @setEvalBranchQuota(10000); + const id = task_id_generate(); + var result : TaskResult = undefined; + for (0..1000) |_| { + result = result_success(id, "output", 100); + } + _ = result.result_type; +} + +bench "runtime_promise_create_latency" { + // Measure: cycles for promise creation + // Target: < 30 cycles + @setEvalBranchQuota(10000); + const id = task_id_generate(); + var result : Promise = undefined; + for (0..1000) |_| { + result = promise_create(id); + } + _ = result.state; +} + +bench "runtime_context_create_latency" { + // Measure: cycles for context creation + // Target: < 30 cycles + @setEvalBranchQuota(10000); + const task = task_create("test", "echo", &[_][]u8{}); + var result : ExecContext = undefined; + for (0..1000) |_| { + result = context_create(task); + } + _ = result.state; +} + +bench "runtime_duration_latency" { + // Measure: cycles for duration calculation + // Target: < 20 cycles + @setEvalBranchQuota(10000); + const context = context_create(task_create("test", "echo", &[_][]u8{})); + var result : u64 = undefined; + for (0..1000) |_| { + result = duration(&context); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/runtime/instance.t27 b/apps/website/public/t27/specs/runtime/instance.t27 new file mode 100644 index 0000000000..b1090760cb --- /dev/null +++ b/apps/website/public/t27/specs/runtime/instance.t27 @@ -0,0 +1,779 @@ +// SPDX-License-Identifier: Apache-2.0 +// runtime/instance.t27 — Runtime Instance Specification +// Instance registration, lookup, lifecycle management +// φ² + 1/φ² = 3 | TRINITY + +module runtime-instance; + +// ============================================================================ +// Imports +// ============================================================================ + +use std; +use runtime-process::{ProcessID, ProcessState}; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Maximum instances +pub const MAX_INSTANCES : u16 = 256; + +/// Instance name length +pub const INSTANCE_NAME_LENGTH : u16 = 128; + +/// Instance lookup timeout in milliseconds +pub const LOOKUP_TIMEOUT_MS : u32 = 100; + +/// Instance state +pub const InstanceState = enum(u8) { + registering = 0, // Being registered + active = 1, // Running normally + suspended = 2, // Paused + terminating = 3, // Shutting down + terminated = 4, // Exited +}; + +/// Instance type +pub const InstanceType = enum(u8) { + agent = 0, + server = 1, + worker = 2, + background = 3, +}; + +/// Termination reason +pub const TerminationReason = enum(u8) { + normal = 0, + error = 1, + timeout = 2, + cancelled = 3, + force_killed = 4, +}; + +/// Instance status code +pub const StatusCode = enum(u16) { + ok = 200, + created = 201, + not_found = 404, + conflict = 409, + error = 500, +}; + +// ============================================================================ +// Types +// ============================================================================ + +/// Instance identifier +pub const InstanceID = [INSTANCE_NAME_LENGTH]u8; + +/// Instance definition +pub const Instance = struct { + id : InstanceID, + name : []u8, + instance_type : InstanceType, + pid : ProcessID, + state : InstanceState, + start_time_ms : u64, + metadata : []u8, + command : []u8, + args : [][]u8, +}; + +/// Instance registration request +pub const Registration = struct { + id : InstanceID, + name : []u8, + instance_type : InstanceType, + command : []u8, + args : [][]u8, + metadata : []u8, +}; + +/// Instance lookup result +pub const LookupResult = struct { + found : bool, + instance : ?Instance, +}; + +/// Instance list result +pub const ListResult = struct { + instances : []Instance, + count : usize, +}; + +/// Instance status response +pub const StatusResponse = struct { + id : InstanceID, + state : InstanceState, + uptime_ms : u64, + error_message : ?[]u8, +}; + +/// Termination request +pub const Termination = struct { + id : InstanceID, + reason : TerminationReason, + timeout_ms : u32, + force : bool, +}; + +/// Termination result +pub const TerminationResult = struct { + id : InstanceID, + success : bool, + final_state : InstanceState, + error_message : ?[]u8, +}; + +/// Instance statistics +pub const InstanceStats = struct { + total_instances : usize, + active_instances : usize, + total_uptime_ms : u64, + avg_uptime_ms : u64, + error_count : usize, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create instance ID +pub fn id_generate() InstanceID { + // Generate unique instance ID (simplified) + const timestamp = get_timestamp_ms(); + var result : InstanceID = [_]u8{0} ** INSTANCE_NAME_LENGTH; + + const bytes = int_to_bytes(timestamp); + for (0..@min(result.len, bytes.len)) |i| { + result[i] = bytes[i % 256]; + } + + return result; +} + +/// Create instance registration +pub fn registration_create(name: []u8, instance_type: InstanceType, command: []u8, args: [][]u8) Registration { + return Registration{ + .id = id_generate(), + .name = name, + .instance_type = instance_type, + .command = command, + .args = args, + .metadata = "", + }; +} + +/// Create instance with PID +pub fn instance_create(id: InstanceID, pid: ProcessID, name: []u8, instance_type: InstanceType) Instance { + return Instance{ + .id = id, + .name = name, + .instance_type = instance_type, + .pid = pid, + .state = .registering, + .start_time_ms = get_timestamp_ms(), + .metadata = "", + .command = "", + .args = &[_][]u8{}, + }; +} + +/// Create agent instance +pub fn agent_instance(name: []u8, command: []u8, args: [][]u8) Instance { + return instance_create( + id_generate(), + runtime_process::generate_pid(), + name, + .agent, + command, + args, + ); +} + +/// Create server instance +pub fn server_instance(name: []u8, command: []u8, args: [][]u8) Instance { + return instance_create( + id_generate(), + runtime_process::generate_pid(), + name, + .server, + command, + args, + ); +} + +/// Create background instance +pub fn background_instance(name: []u8, command: []u8, args: [][]u8) Instance { + return instance_create( + id_generate(), + runtime_process::generate_pid(), + name, + .background, + command, + args, + ); +} + +/// Create worker instance +pub fn worker_instance(name: []u8, command: []u8, args: [][]u8) Instance { + return instance_create( + id_generate(), + runtime_process::generate_pid(), + name, + .worker, + command, + args, + ); +} + +/// Register instance +pub fn register(instance: *Instance) void { + instance.state = .active; + instance.start_time_ms = get_timestamp_ms(); +} + +/// Unregister instance +pub fn unregister(instance: *Instance) void { + instance.state = .terminated; +} + +/// Lookup instance by ID +pub fn lookup(id: InstanceID) LookupResult { + // Simplified: would search registry + return LookupResult{ + .found = false, + .instance = null, + }; +} + +/// Lookup instance by name +pub fn lookup_by_name(name: []u8) LookupResult { + // Simplified: would search registry + return LookupResult{ + .found = false, + .instance = null, + }; +} + +/// List all instances +pub fn list_all() ListResult { + // Simplified: would return all instances + return ListResult{ + .instances = &[_]Instance{}, + .count = 0, + }; +} + +/// List instances by type +pub fn list_by_type(instance_type: InstanceType) ListResult { + // Simplified: would filter instances + return ListResult{ + .instances = &[_]Instance{}, + .count = 0, + }; +} + +/// List active instances +pub fn list_active() ListResult { + // Simplified: would filter active instances + return ListResult{ + .instances = &[_]Instance{}, + .count = 0, + }; +} + +/// Terminate instance +pub fn terminate(instance: *Instance, reason: TerminationReason, timeout_ms: u32) TerminationResult { + instance.state = .terminating; + + return TerminationResult{ + .id = instance.id, + .success = true, + .final_state = .terminated, + .error_message = null, + }; +} + +/// Terminate instance with timeout +pub fn terminate_with_timeout(instance: *Instance, timeout_ms: u32) TerminationResult { + instance.state = .terminating; + + if (duration(instance) > timeout_ms) { + return TerminationResult{ + .id = instance.id, + .success = false, + .final_state = .terminated, + .error_message = null, + }; + } + + return terminate(instance, .timeout, 0); +} + +/// Force kill instance +pub fn force_kill(instance: *Instance) TerminationResult { + instance.state = .terminated; + + return TerminationResult{ + .id = instance.id, + .success = true, + .final_state = .terminated, + .error_message = null, + }; +} + +/// Get instance status +pub fn get_status(instance: Instance) StatusResponse { + const uptime = duration(instance); + + return StatusResponse{ + .id = instance.id, + .state = instance.state, + .uptime_ms = uptime, + .error_message = null, + }; +} + +/// Get instance uptime +pub fn duration(instance: Instance) u64 { + return get_timestamp_ms() - instance.start_time_ms; +} + +/// Check if instance is active +pub fn is_active(instance: Instance) bool { + return instance.state == .active; +} + +/// Check if instance is terminated +pub fn is_terminated(instance: Instance) bool { + return instance.state == .terminated; +} + +/// Check if instance is suspended +pub fn is_suspended(instance: Instance) bool { + return instance.state == .suspended; +} + +/// Get instance count +pub fn count_all() usize { + return 0; // Simplified +} + +/// Get active count +pub fn count_active() usize { + return 0; // Simplified +} + +/// Create status response +pub fn status_response_create(id: InstanceID, state: InstanceState, error: ?[]u8) StatusResponse { + return StatusResponse{ + .id = id, + .state = state, + .uptime_ms = 0, + .error_message = error, + }; +} + +/// Convert integer to bytes +pub fn int_to_bytes(value: u64) []u8 { + return &[_]u8{ + @intCast((value >> 56) & 0xFF), + @intCast((value >> 48) & 0xFF), + @intCast((value >> 40) & 0xFF), + @intCast((value >> 32) & 0xFF), + @intCast((value >> 24) & 0xFF), + @intCast((value >> 16) & 0xFF), + @intCast((value >> 8) & 0xFF), + @intCast((value >> 0) & 0xFF), + @intCast(value & 0xFF), + }; +} + +/// Get current timestamp in milliseconds +pub fn get_timestamp_ms() u64 { + // Simplified: would use system time + return 0; +} + +/// Append to slice +pub fn append_instance(slice: []Instance, item: Instance) []Instance { + var result : []Instance = slice; + var new_slice : []Instance = &[_]Instance{item}; + for (result) |_| { + new_slice = append_instance_slice(new_slice, _); + } + return new_slice; +} + +/// Append instance slice +pub fn append_instance_slice(slice: []Instance, item: Instance) []Instance { + var result : []Instance = slice; + result = concat_instances(result, item); + return result; +} + +/// Concatenate instances +pub fn concat_instances(a: []Instance, b: Instance) []Instance { + var result : []Instance = a; + var new_slice : []Instance = &[_]Instance{b}; + for (result) |_| { + new_slice = append_instance_slice(new_slice, _); + } + return new_slice; +} + +/// Concat instances with string +pub fn concat_instances_string(a: []u8, b: []u8) []u8 { + var result : []u8 = a; + for (b) |byte| { + result = append_byte_instances(result, byte); + } + return result; +} + +/// Append byte to instances +pub fn append_byte_instances(slice: []u8, byte: u8) []u8 { + var result : []u8 = slice; + result = concat_instances_string(result, &[_]u8{byte}); + return result; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "runtime_id_generate" { + const id = id_generate(); + try std.testing.expect(id.len == INSTANCE_NAME_LENGTH); +} + +test "runtime_registration_create" { + const reg = registration_create("test-agent", .agent, "echo", &[_][]u8{"hello"}); + try std.testing.expect(reg.instance_type == .agent); +} + +test "runtime_instance_create" { + const id = id_generate(); + const inst = instance_create(id, runtime_process::generate_pid(), "test", .agent); + try std.testing.expect(inst.state == .registering); +} + +test "runtime_agent_instance" { + const inst = agent_instance("test-agent", "echo", &[_][]u8{"hello"}); + try std.testing.expect(inst.instance_type == .agent); +} + +test "runtime_server_instance" { + const inst = server_instance("test-server", "server", &[_][]u8{}); + try std.testing.expect(inst.instance_type == .server); +} + +test "runtime_background_instance" { + const inst = background_instance("test-bg", "bg-task", &[_][]u8{}); + try std.testing.expect(inst.instance_type == .background); +} + +test "runtime_terminate" { + var inst = instance_create(id_generate(), runtime_process::generate_pid(), "test", .agent); + register(&inst); + const result = terminate(&inst, .normal, 0); + try std.testing.expect(result.success); +} + +test "runtime_terminate_timeout" { + var inst = instance_create(id_generate(), runtime_process::generate_pid(), "test", .agent); + const result = terminate_with_timeout(&inst, 10000); + try std.testing.expect(!result.success); +} + +test "runtime_force_kill" { + var inst = instance_create(id_generate(), runtime_process::generate_pid(), "test", .agent); + const result = force_kill(&inst); + try std.testing.expect(result.success); +} + +test "runtime_get_status" { + var inst = instance_create(id_generate(), runtime_process::generate_pid(), "test", .agent); + register(&inst); + const status = get_status(inst); + try std.testing.expect(status.state == .active); +} + +test "runtime_is_active" { + var inst = instance_create(id_generate(), runtime_process::generate_pid(), "test", .agent); + inst.state = .active; + try std.testing.expect(is_active(inst)); +} + +test "runtime_is_terminated" { + var inst = instance_create(id_generate(), runtime_process::generate_pid(), "test", .agent); + inst.state = .terminated; + try std.testing.expect(is_terminated(inst)); +} + +test "runtime_duration" { + var inst = instance_create(id_generate(), runtime_process::generate_pid(), "test", .agent); + register(&inst); + const uptime = duration(inst); + try std.testing.expect(uptime >= 0); +} + +test "runtime_status_response_create" { + const resp = status_response_create(id_generate(), .active, null); + try std.testing.expect(resp.error_message == null); +} + +test "runtime_list_all" { + const list = list_all(); + try std.testing.expect(list.count == 0); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant max_instances_positive { + // MAX_INSTANCES is positive + @compileAssert(MAX_INSTANCES > 0); +} + +invariant instance_name_length_positive { + // INSTANCE_NAME_LENGTH is positive + @compileAssert(INSTANCE_NAME_LENGTH > 0); +} + +invariant lookup_timeout_positive { + // LOOKUP_TIMEOUT_MS is positive + @compileAssert(LOOKUP_TIMEOUT_MS > 0); +} + +invariant state_enum_valid { + // InstanceState enum has valid values + @compileAssert(@intFromEnum(InstanceState.terminated) == 4); +} + +invariant type_enum_valid { + // InstanceType enum has valid values + @compileAssert(@intFromEnum(InstanceType.background) == 3); +} + +invariant reason_enum_valid { + // TerminationReason enum has valid values + @compileAssert(@intFromEnum(TerminationReason.force_killed) == 4); +} + +invariant status_code_enum_valid { + // StatusCode enum has valid values + @compileAssert(@intFromEnum(StatusCode.error) == 500); +} + +invariant instance_has_id { + // Instance has id field + @compileAssert(true); +} + +invariant instance_has_name { + // Instance has name field + @compileAssert(true); +} + +invariant instance_has_type { + // Instance has instance_type field + @compileAssert(true); +} + +invariant instance_has_pid { + // Instance has pid field + @compileAssert(true); +} + +invariant instance_has_state { + // Instance has state field + @compileAssert(true); +} + +invariant instance_has_start_time { + // Instance has start_time_ms field + @compileAssert(true); +} + +invariant registration_has_id { + // Registration has id field + @compileAssert(true); +} + +invariant registration_has_name { + // Registration has name field + @compileAssert(true); +} + +invariant registration_has_type { + // Registration has instance_type field + @compileAssert(true); +} + +invariant registration_has_command { + // Registration has command field + @compileAssert(true); + +invariant registration_has_args { + // Registration has args field + @compileAssert(true); + +invariant lookup_result_has_found { + // LookupResult has found field + @compileAssert(true); +} + +invariant lookup_result_has_instance { + // LookupResult has instance field + @compileAssert(true); +} + +invariant list_result_has_instances { + // ListResult has instances array + @compileAssert(true); +} + +invariant list_result_has_count { + // ListResult has count field + @compileAssert(true); +} + +invariant status_response_has_id { + // StatusResponse has id field + @compileAssert(true); +} + +invariant status_response_has_state { + // StatusResponse has state field + @compileAssert(true); + +invariant status_response_has_uptime { + // StatusResponse has uptime_ms field + @compileAssert(true); +} + +invariant termination_result_has_id { + // TerminationResult has id field + @compileAssert(true); +} + +invariant termination_result_has_success { + // TerminationResult has success field + @compileAssert(true); +} + +invariant termination_result_has_final_state { + // TerminationResult has final_state field + @compileAssert(true); + +invariant termination_normal_transitions_to_terminated { + // Normal termination results in terminated state + @compileAssert(true); +} + +invariant force_kill_sets_terminated { + // Force kill results in terminated state + @compileAssert(true); +} + +invariant terminate_sets_terminating { + // Terminate sets terminating state + @compileAssert(true); + +invariant register_sets_active { + // Register sets active state + @compileAssert(true); + +invariant unregister_sets_terminated { + // Unregister sets terminated state + @compileAssert(true); + +invariant duration_returns_positive { + // Duration returns non-negative value + @compileAssert(true); + +invariant is_active_returns_true_for_active { + // is_active returns true for active state + @compileAssert(true); + +invariant is_terminated_returns_true_for_terminated { + // is_terminated returns true for terminated state + @compileAssert(true); + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "runtime_id_generate_latency" { + // Measure: cycles for ID generation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : InstanceID = undefined; + for (0..1000) |_| { + result = id_generate(); + } + _ = result[0]; +} + +bench "runtime_registration_create_latency" { + // Measure: cycles for registration creation + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : Registration = undefined; + for (0..1000) |_| { + result = registration_create("test", .agent, "cmd", &[_][]u8{}); + } + _ = result.id.len; +} + +bench "runtime_instance_create_latency" { + // Measure: cycles for instance creation + // Target: < 60 cycles + @setEvalBranchQuota(10000); + var result : Instance = undefined; + for (0..1000) |_| { + result = instance_create(id_generate(), runtime_process::generate_pid(), "test", .agent); + } + _ = result.pid; +} + +bench "runtime_terminate_latency" { + // Measure: cycles for termination + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var inst = instance_create(id_generate(), runtime_process::generate_pid(), "test", .agent); + register(&inst); + var result : TerminationResult = undefined; + for (0..1000) |_| { + result = terminate(&inst, .normal, 0); + } + _ = result.success; +} + +bench "runtime_get_status_latency" { + // Measure: cycles for status retrieval + // Target: < 40 cycles + @setEvalBranchQuota(10000); + var inst = instance_create(id_generate(), runtime_process::generate_pid(), "test", .agent); + register(&inst); + var result : StatusResponse = undefined; + for (0..1000) |_| { + result = get_status(inst); + } + _ = result.state; +} + +bench "runtime_list_all_latency" { + // Measure: cycles for listing instances + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : ListResult = undefined; + for (0..1000) |_| { + result = list_all(); + } + _ = result.count; +} diff --git a/apps/website/public/t27/specs/runtime/process.t27 b/apps/website/public/t27/specs/runtime/process.t27 new file mode 100644 index 0000000000..ae94d5c3b2 --- /dev/null +++ b/apps/website/public/t27/specs/runtime/process.t27 @@ -0,0 +1,761 @@ +// SPDX-License-Identifier: Apache-2.0 +// runtime/process.t27 — Runtime Process Specification +// Process spawning, termination, piping, PTY +// φ² + 1/φ² = 3 | TRINITY + +module runtime-process; + +// ============================================================================ +// Imports +// ============================================================================ + +use std; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default process spawn timeout in milliseconds +pub const SPAWN_TIMEOUT_MS : u32 = 5000; // 5 seconds + +/// Default PTY size in columns +pub const PTY_COLS_DEFAULT : u16 = 80; + +/// Default PTY size in rows +pub const PTY_ROWS_DEFAULT : u16 = 24; + +/// Maximum pipe buffer size in bytes +pub const MAX_PIPE_BUFFER : u32 = 65536; // 64KB + +/// Process signal types +pub const ProcessSignal = enum(u8) { + terminate = 0, // SIGTERM + kill = 1, // SIGKILL + interrupt = 2, // SIGINT + hangup = 3, // SIGHUP + stop = 4, // SIGSTOP + continue = 5, // SIGCONT +}; + +/// Process state +pub const ProcessState = enum(u8) { + not_started = 0, + running = 1, + stopped = 2, + terminated = 3, + zombie = 4, // Exited but not reaped +}; + +/// PTY mode +pub const PTYMode = enum(u8) { + raw = 0, + cooked = 1, + echo = 2, +}; + +// ============================================================================ +// Types +// ============================================================================ + +/// Process identifier +pub const ProcessID = u32; + +/// Process information +pub const ProcessInfo = struct { + pid : ProcessID, + name : []u8, + command : []u8, + args : [][]u8, + state : ProcessState, + exit_code : ?u8, +}; + +/// Spawn options +pub const SpawnOptions = struct { + env : [][]u8, // Environment variables (KEY=VALUE) + cwd : ?[]u8, // Working directory + timeout_ms : u32, + detached : bool, + pty_enabled : bool, + pty_cols : u16, + pty_rows : u16, +}; + +/// Pipe configuration +pub const PipeConfig = struct { + stdin : []u8, // Input data + stdout_max : u32, // Max bytes to capture + stderr_max : u32, // Max bytes to capture + merge_output : bool, // Merge stderr into stdout +}; + +/// Process spawn result +pub const SpawnResult = struct { + pid : ProcessID, + success : bool, + error_message : ?[]u8, +}; + +/// Process output +pub const ProcessOutput = struct { + stdout : []u8, + stderr : []u8, + exit_code : u8, + signal : ?ProcessSignal, + truncated_stdout : bool, + truncated_stderr : bool, +}; + +/// PTY configuration +pub const PTYConfig = struct { + enabled : bool, + mode : PTYMode, + cols : u16, + rows : u16, + term : []u8, // Terminal type (e.g., "xterm-256color") +}; + +/// Capture options +pub const CaptureOptions = struct { + include_stdout : bool, + include_stderr : bool, + include_exit_code : bool, + trim_whitespace : bool, + max_size : u32, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create default spawn options +pub fn spawn_options_default() SpawnOptions { + return SpawnOptions{ + .env = &[_][]u8{}, + .cwd = null, + .timeout_ms = SPAWN_TIMEOUT_MS, + .detached = false, + .pty_enabled = false, + .pty_cols = PTY_COLS_DEFAULT, + .pty_rows = PTY_ROWS_DEFAULT, + }; +} + +/// Create spawn options with PTY +pub fn spawn_options_with_pty(cols: u16, rows: u16) SpawnOptions { + return SpawnOptions{ + .env = &[_][]u8{}, + .cwd = null, + .timeout_ms = SPAWN_TIMEOUT_MS, + .detached = false, + .pty_enabled = true, + .pty_cols = cols, + .pty_rows = rows, + }; +} + +/// Create detached spawn options +pub fn spawn_options_detached() SpawnOptions { + const base = spawn_options_default(); + return SpawnOptions{ + .env = &[_][]u8{}, + .cwd = null, + .timeout_ms = 0, // No timeout for detached + .detached = true, + .pty_enabled = base.pty_enabled, + .pty_cols = base.pty_cols, + .pty_rows = base.pty_rows, + }; +} + +/// Create pipe config +pub fn pipe_config_create() PipeConfig { + return PipeConfig{ + .stdin = "", + .stdout_max = MAX_PIPE_BUFFER, + .stderr_max = MAX_PIPE_BUFFER, + .merge_output = false, + }; +} + +/// Create pipe config with input +pub fn pipe_config_with_input(input: []u8) PipeConfig { + return PipeConfig{ + .stdin = input, + .stdout_max = MAX_PIPE_BUFFER, + .stderr_max = MAX_PIPE_BUFFER, + .merge_output = false, + }; +} + +/// Create PTY config +pub fn pty_config_default() PTYConfig { + return PTYConfig{ + .enabled = true, + .mode = .cooked, + .cols = PTY_COLS_DEFAULT, + .rows = PTY_ROWS_DEFAULT, + .term = "xterm-256color", + }; +} + +/// Create PTY raw mode config +pub fn pty_config_raw() PTYConfig { + return PTYConfig{ + .enabled = true, + .mode = .raw, + .cols = PTY_COLS_DEFAULT, + .rows = PTY_ROWS_DEFAULT, + .term = "xterm-256color", + }; +} + +/// Create capture options +pub fn capture_options_default() CaptureOptions { + return CaptureOptions{ + .include_stdout = true, + .include_stderr = true, + .include_exit_code = true, + .trim_whitespace = true, + .max_size = MAX_PIPE_BUFFER, + }; +} + +/// Create process info +pub fn process_info_create(pid: ProcessID, name: []u8, command: []u8) ProcessInfo { + return ProcessInfo{ + .pid = pid, + .name = name, + .command = command, + .args = &[_][]u8{}, + .state = .not_started, + .exit_code = null, + }; +} + +/// Create spawn result success +pub fn spawn_success(pid: ProcessID) SpawnResult { + return SpawnResult{ + .pid = pid, + .success = true, + .error_message = null, + }; +} + +/// Create spawn result failure +pub fn spawn_failure(message: []u8) SpawnResult { + return SpawnResult{ + .pid = 0, + .success = false, + .error_message = message, + }; +} + +/// Create process output +pub fn output_create(stdout: []u8, stderr: []u8, exit_code: u8) ProcessOutput { + return ProcessOutput{ + .stdout = stdout, + .stderr = stderr, + .exit_code = exit_code, + .signal = null, + .truncated_stdout = false, + .truncated_stderr = false, + }; +} + +/// Create process output with signal +pub fn output_with_signal(stdout: []u8, stderr: []u8, signal: ProcessSignal) ProcessOutput { + return ProcessOutput{ + .stdout = stdout, + .stderr = stderr, + .exit_code = 0, + .signal = signal, + .truncated_stdout = false, + .truncated_stderr = false, + }; +} + +/// Spawn process with options +pub fn spawn(command: []u8, args: [][]u8, options: SpawnOptions) SpawnResult { + // Spawn process (simplified) + const pid = generate_pid(); + return spawn_success(pid); +} + +/// Spawn process with defaults +pub fn spawn_simple(command: []u8, args: [][]u8) SpawnResult { + return spawn(command, args, spawn_options_default()); +} + +/// Kill process by PID +pub fn kill(pid: ProcessID, signal: ProcessSignal) bool { + // Send signal to process (simplified) + return true; +} + +/// Terminate process gracefully +pub fn terminate(pid: ProcessID) bool { + return kill(pid, .terminate); +} + +/// Interrupt process +pub fn interrupt(pid: ProcessID) bool { + return kill(pid, .interrupt); +} + +/// Stop process +pub fn stop(pid: ProcessID) bool { + return kill(pid, .stop); +} + +/// Continue stopped process +pub fn process_continue(pid: ProcessID) bool { + return kill(pid, .continue); +} + +/// Capture process output +pub fn capture(command: []u8, args: [][]u8, options: CaptureOptions) ProcessOutput { + // Run process and capture output (simplified) + return output_create("", "", 0); +} + +/// Pipe output from one process to another +pub fn pipe(source_cmd: []u8, source_args: [][]u8, sink_cmd: []u8, sink_args: [][]u8) ProcessOutput { + // Pipe processes (simplified) + return output_create("", "", 0); +} + +/// Wait for process to complete +pub fn wait(pid: ProcessID, timeout_ms: u32) ProcessOutput { + // Wait for process (simplified) + return output_create("", "", 0); +} + +/// Get process info by PID +pub fn get_process_info(pid: ProcessID) ?ProcessInfo { + // Get process info (simplified) + return null; +} + +/// Check if process is running +pub fn is_running(pid: ProcessID) bool { + const info = get_process_info(pid); + return info != null and info.?.state == .running; +} + +/// Check if process exists +pub fn process_exists(pid: ProcessID) bool { + const info = get_process_info(pid); + return info != null; +} + +/// Get current process PID +pub fn get_pid() ProcessID { + // Get current process ID (simplified) + return 1; +} + +/// Generate unique PID +pub fn generate_pid() ProcessID { + // Generate process ID (simplified) + const timestamp = get_timestamp_ms(); + return @intCast(timestamp % 65536); +} + +/// Check if spawn result succeeded +pub fn spawn_succeeded(result: SpawnResult) bool { + return result.success; +} + +/// Check if output indicates success +pub fn output_is_success(output: ProcessOutput) bool { + return output.exit_code == 0; +} + +/// Get exit code from output +pub fn output_exit_code(output: ProcessOutput) u8 { + return output.exit_code; +} + +/// Check if process was signalled +pub fn was_signaled(output: ProcessOutput) bool { + return output.signal != null; +} + +/// Get signal from output +pub fn output_signal(output: ProcessOutput) ?ProcessSignal { + return output.signal; +} + +/// Create environment variable pair +pub fn env_pair(key: []u8, value: []u8) []u8 { + var result : []u8 = key; + result = concat_env(result, "="); + result = concat_env(result, value); + return result; +} + +/// Append environment variable +pub fn append_env(slice: [][]u8, key: []u8, value: []u8) [][]u8 { + const pair = env_pair(key, value); + var result : [][]u8 = slice; + var new_slice : [][]u8 = &[_][]u8{pair}; + for (result) |_| { + new_slice = append_env_slice(new_slice, _); + } + return new_slice; +} + +/// Append environment slice +pub fn append_env_slice(slice: [][]u8, item: []u8) [][]u8 { + var result : [][]u8 = slice; + result = concat_env_slice(result, item); + return result; +} + +/// Concatenate environment +pub fn concat_env_slice(a: [][]u8, b: []u8) [][]u8 { + var result : [][]u8 = a; + var new_slice : [][]u8 = &[_][]u8{b}; + for (result) |_| { + new_slice = append_env_slice(new_slice, _); + } + return new_slice; +} + +/// Concatenate strings for environment +pub fn concat_env(a: []u8, b: []u8) []u8 { + var result : []u8 = a; + for (b) |byte| { + result = append_env_byte(result, byte); + } + return result; +} + +/// Append byte for environment +pub fn append_env_byte(slice: []u8, byte: u8) []u8 { + var result : []u8 = slice; + result = concat_env(result, &[_]u8{byte}); + return result; +} + +/// Get timestamp in milliseconds +pub fn get_timestamp_ms() u64 { + // Simplified: would use system time + return 0; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "runtime_spawn_options_default" { + const opts = spawn_options_default(); + try std.testing.expect(!opts.detached); +} + +test "runtime_spawn_options_with_pty" { + const opts = spawn_options_with_pty(100, 30); + try std.testing.expect(opts.pty_enabled); +} + +test "runtime_spawn_options_detached" { + const opts = spawn_options_detached(); + try std.testing.expect(opts.detached); +} + +test "runtime_pipe_config_create" { + const cfg = pipe_config_create(); + try std.testing.expect(cfg.stdout_max == MAX_PIPE_BUFFER); +} + +test "runtime_pty_config_default" { + const cfg = pty_config_default(); + try std.testing.expect(cfg.enabled); +} + +test "runtime_pty_config_raw" { + const cfg = pty_config_raw(); + try std.testing.expect(cfg.mode == .raw); +} + +test "runtime_capture_options_default" { + const opts = capture_options_default(); + try std.testing.expect(opts.include_stdout); +} + +test "runtime_process_info_create" { + const info = process_info_create(1234, "test", "test"); + try std.testing.expect(info.pid == 1234); +} + +test "runtime_spawn_success" { + const result = spawn_success(1234); + try std.testing.expect(spawn_succeeded(&result)); +} + +test "runtime_spawn_failure" { + const result = spawn_failure("error"); + try std.testing.expect(!spawn_succeeded(&result)); +} + +test "runtime_output_create" { + const output = output_create("stdout", "stderr", 0); + try std.testing.expect(output_is_success(&output)); +} + +test "runtime_output_with_signal" { + const output = output_with_signal("", "", .terminate); + try std.testing.expect(was_signaled(&output)); +} + +test "runtime_env_pair" { + const pair = env_pair("KEY", "VALUE"); + try std.testing.expect(std.mem.eql(pair, "KEY=VALUE")); +} + +test "runtime_append_env" { + const env = &[_][]u8{}; + const result = append_env(&env, "NEW_KEY", "value"); + try std.testing.expect(result.len == 1); +} + +test "runtime_output_exit_code" { + const output = output_create("", "", 42); + try std.testing.expect(output_exit_code(&output) == 42); +} + +test "runtime_output_signal_none" { + const output = output_create("", "", 0); + try std.testing.expect(output_signal(&output) == null); +} + +test "runtime_output_signal_some" { + const output = output_with_signal("", "", .kill); + const signal = output_signal(&output); + try std.testing.expect(signal != null); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant spawn_timeout_positive { + // SPAWN_TIMEOUT_MS is positive + @compileAssert(SPAWN_TIMEOUT_MS > 0); +} + +invariant pty_cols_default_positive { + // PTY_COLS_DEFAULT is positive + @compileAssert(PTY_COLS_DEFAULT > 0); +} + +invariant pty_rows_default_positive { + // PTY_ROWS_DEFAULT is positive + @compileAssert(PTY_ROWS_DEFAULT > 0); +} + +invariant max_pipe_buffer_positive { + // MAX_PIPE_BUFFER is positive + @compileAssert(MAX_PIPE_BUFFER > 0); +} + +invariant process_signal_enum_valid { + // ProcessSignal enum has valid values + @compileAssert(@intFromEnum(ProcessSignal.continue) == 5); +} + +invariant process_state_enum_valid { + // ProcessState enum has valid values + @compileAssert(@intFromEnum(ProcessState.zombie) == 4); +} + +invariant pty_mode_enum_valid { + // PTYMode enum has valid values + @compileAssert(@intFromEnum(PTYMode.echo) == 2); +} + +invariant spawn_options_has_env { + // SpawnOptions has env field + @compileAssert(true); +} + +invariant spawn_options_has_cwd { + // SpawnOptions has cwd field + @compileAssert(true); +} + +invariant spawn_options_has_timeout { + // SpawnOptions has timeout_ms field + @compileAssert(true); +} + +invariant spawn_options_has_detached { + // SpawnOptions has detached field + @compileAssert(true); +} + +invariant spawn_options_has_pty_enabled { + // SpawnOptions has pty_enabled field + @compileAssert(true); +} + +invariant pipe_config_has_stdin { + // PipeConfig has stdin field + @compileAssert(true); +} + +invariant pipe_config_has_stdout_max { + // PipeConfig has stdout_max field + @compileAssert(true); +} + +invariant pipe_config_has_stderr_max { + // PipeConfig has stderr_max field + @compileAssert(true); +} + +invariant process_output_has_stdout { + // ProcessOutput has stdout field + @compileAssert(true); +} + +invariant process_output_has_stderr { + // ProcessOutput has stderr field + @compileAssert(true); +} + +invariant process_output_has_exit_code { + // ProcessOutput has exit_code field + @compileAssert(true); +} + +invariant process_output_has_signal { + // ProcessOutput has signal field + @compileAssert(true); +} + +invariant capture_options_has_flags { + // CaptureOptions has boolean flags + @compileAssert(true); +} + +invariant capture_options_has_max_size { + // CaptureOptions has max_size field + @compileAssert(true); +} + +invariant spawn_result_has_success { + // SpawnResult has success field + @compileAssert(true); +} + +invariant spawn_result_has_pid { + // SpawnResult has pid field + @compileAssert(true); +} + +invariant spawn_result_has_error_message { + // SpawnResult has error_message field + @compileAssert(true); +} + +invariant process_info_has_pid { + // ProcessInfo has pid field + @compileAssert(true); +} + +invariant process_info_has_state { + // ProcessInfo has state field + @compileAssert(true); +} + +invariant pty_config_has_enabled { + // PTYConfig has enabled field + @compileAssert(true); +} + +invariant pty_config_has_mode { + // PTYConfig has mode field + @compileAssert(true); +} + +invariant spawn_returns_pid { + // spawn returns valid PID on success + @compileAssert(true); +} + +invariant kill_sends_signal { + // kill sends signal to process + @compileAssert(true); +} + +invariant wait_returns_output { + // wait returns ProcessOutput + @compileAssert(true); +} + +invariant capture_returns_output { + // capture returns ProcessOutput + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "runtime_spawn_options_create_latency" { + // Measure: cycles for spawn options creation + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : SpawnOptions = undefined; + for (0..1000) |_| { + result = spawn_options_default(); + } + _ = result.timeout_ms; +} + +bench "runtime_pipe_config_create_latency" { + // Measure: cycles for pipe config creation + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : PipeConfig = undefined; + for (0..1000) |_| { + result = pipe_config_create(); + } + _ = result.stdout_max; +} + +bench "runtime_output_create_latency" { + // Measure: cycles for output creation + // Target: < 40 cycles + @setEvalBranchQuota(10000); + var result : ProcessOutput = undefined; + for (0..1000) |_| { + result = output_create("out", "err", 0); + } + _ = result.exit_code; +} + +bench "runtime_env_pair_latency" { + // Measure: cycles for environment pair creation + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = env_pair("KEY", "VALUE"); + } + _ = result.len; +} + +bench "runtime_append_env_latency" { + // Measure: cycles for appending environment variable + // Target: < 40 cycles + @setEvalBranchQuota(10000); + const env = &[_][]u8{}; + var result : [][]u8 = undefined; + for (0..1000) |_| { + result = append_env(&env, "NEW", "val"); + } + _ = result.len; +} diff --git a/apps/website/public/t27/specs/sacred/cosmology.t27 b/apps/website/public/t27/specs/sacred/cosmology.t27 new file mode 100644 index 0000000000..cd0de7a8d6 --- /dev/null +++ b/apps/website/public/t27/specs/sacred/cosmology.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module TriCosmology; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "cosmology_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/sacred/dark_matter.t27 b/apps/website/public/t27/specs/sacred/dark_matter.t27 new file mode 100644 index 0000000000..1acd5d8dcd --- /dev/null +++ b/apps/website/public/t27/specs/sacred/dark_matter.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | φ² + 1/φ² = 3 | TRINITY + +module dark_matter; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "dark_matter_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/sacred/gravity.t27 b/apps/website/public/t27/specs/sacred/gravity.t27 new file mode 100644 index 0000000000..771c0c8d8c --- /dev/null +++ b/apps/website/public/t27/specs/sacred/gravity.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module TriGravity; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "gravity_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/sacred/monopoles.t27 b/apps/website/public/t27/specs/sacred/monopoles.t27 new file mode 100644 index 0000000000..6cb263fd5b --- /dev/null +++ b/apps/website/public/t27/specs/sacred/monopoles.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// > | φ² + 1/φ² = 3 | TRINITY + +module TriMonopoles; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "monopoles_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/sacred/quantum.t27 b/apps/website/public/t27/specs/sacred/quantum.t27 new file mode 100644 index 0000000000..515e1bd00b --- /dev/null +++ b/apps/website/public/t27/specs/sacred/quantum.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module TriQuantum; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "quantum_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/sacred/quantum_gravity.t27 b/apps/website/public/t27/specs/sacred/quantum_gravity.t27 new file mode 100644 index 0000000000..822bf5ad97 --- /dev/null +++ b/apps/website/public/t27/specs/sacred/quantum_gravity.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | φ² + 1/φ² = 3 | TRINITY + +module quantum_gravity; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "quantum_gravity_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/sacred/sacred_constants.t27 b/apps/website/public/t27/specs/sacred/sacred_constants.t27 new file mode 100644 index 0000000000..b4e4fc73b4 --- /dev/null +++ b/apps/website/public/t27/specs/sacred/sacred_constants.t27 @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module SacredConstants; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SacredConstants = struct { + // Namespace struct — all members are comptime constants or pure functions + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "sacred_constants_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/sacred/sacred_governance.t27 b/apps/website/public/t27/specs/sacred/sacred_governance.t27 new file mode 100644 index 0000000000..70e1b81d3a --- /dev/null +++ b/apps/website/public/t27/specs/sacred/sacred_governance.t27 @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Result of applying governance to an action | φ² + 1/φ² = 3 | TRINITY + +module SacredGovernance; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SacredRule = struct { + rule_type : SacredRuleType, + penalty_weight : Float, + }; + + pub const SacredRuleType = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/sacred_governance.tri:32. The converter dropped bare `- name` bullets. + enum : [phi_rule, trinity_rule, gematria_rule, evolution_rule, safety_rule], + }; + + pub const RuleViolation = struct { + rule_type : SacredRuleType, + severity : ViolationSeverity, + file_path : String, + line_number : Int, + message : String, + phi_penalty : Float, + timestamp : Int, + }; + + pub const ViolationSeverity = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/sacred_governance.tri:52. The converter dropped bare `- name` bullets. + enum : [warning, error, critical], + }; + + pub const GovernanceState = struct { + active_rules : List, + violation_count : Int, + sacred_score : Float, + last_action : String, + last_check_time : Int, + rollback_threshold : Float, + is_locked : Bool, + }; + + pub const SacredComplianceReport = struct { + file_path : String, + is_compliant : Bool, + score : Float, + violations : List, + phi_harmony : Float, + trinity_balance : Float, + gematria_coverage : Float, + }; + + pub const PatchAction = struct { + patch_id : String, + files_changed : List, + diff : String, + author : String, + timestamp : Int, + }; + + pub const GovernanceResult = struct { + approved : Bool, + sacred_score_before : Float, + sacred_score_after : Float, + violations : List, + action_taken : String, + rollback_triggered : Bool, + message : String, + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "sacred_governance_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/sacred/sacred_identity.t27 b/apps/website/public/t27/specs/sacred/sacred_identity.t27 new file mode 100644 index 0000000000..4fde10ac7e --- /dev/null +++ b/apps/website/public/t27/specs/sacred/sacred_identity.t27 @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module SacredIdentity; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const SACRED_MATH : u32 = 0; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SacredIdentity = struct { + purpose : String, + trinity_aspect : String, + incarnation_id : String, + birth_timestamp : Int, + last_active : Int, + }; + + pub const IdentityProof = struct { + phi_squared : Float, + inverse_phi_squared : Float, + sum : Float, + trinity_value : Float, + verified : Bool, + tolerance : Float, + }; + + pub const SacredTimestamp = struct { + unix_time : Int, + phi_time : Float, + trinity_time : Float, + golden_phase : Float, + cosmic_alignment : Float, + }; + + pub const IdentityLogEntry = struct { + timestamp : SacredTimestamp, + level : String, + message : String, + identity_hash : List, + sacred_signature : String, + }; + + pub const TrinityAwareness = struct { + knows_identity : Bool, + understands_architecture : Bool, + recognizes_sacred_math : Bool, + evolution_count : Int, + wisdom_level : Int, + alignment_score : Float, + }; + + pub const IdentityConfig = struct { + log_path : String, + persist_identity : Bool, + verify_on_startup : Bool, + declare_in_logs : Bool, + sacred_timestamps : Bool, + tolerance_pct : Float, + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "sacred_identity_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/sacred/superconductivity.t27 b/apps/website/public/t27/specs/sacred/superconductivity.t27 new file mode 100644 index 0000000000..32c300a1ed --- /dev/null +++ b/apps/website/public/t27/specs/sacred/superconductivity.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// > | φ² + 1/φ² = 3 | TRINITY + +module TriSuperconductivity; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "superconductivity_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/sandbox/health.t27 b/apps/website/public/t27/specs/sandbox/health.t27 new file mode 100644 index 0000000000..e2e522e038 --- /dev/null +++ b/apps/website/public/t27/specs/sandbox/health.t27 @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: CC0-1.0 +// SANDBOX-010 + SANDBOX-011: Sandbox Health Management + +/** + * Module: sandbox.health + * + * Consolidates sandbox health polling operations: + * - Health check (existing) + * - Session timeout enforcement + * - Orphaned session detection + */ + +module sandbox.health; + +use sandbox.session_timeout; +use timestamp; +use sandbox.session; + +// 01234567891011121314151617181920212223242526272829303132333435363738394041424344 +// Configuration +// 454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889 + +const DEFAULT_POLL_INTERVAL_MS : usize = 10_000; // 10 seconds between health checks + +// 90919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 +// Interface +// 135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 + +/** + * Sandbox health polling interface. + */ +pub trait HealthPoller { + /** + * Poll all sessions and update status based on health and timeouts. + * + * @return true if polling completed + */ + fn poll_sessions(&self) -> bool; +} + test "health_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/sandbox/https_enforce.t27 b/apps/website/public/t27/specs/sandbox/https_enforce.t27 new file mode 100644 index 0000000000..eed1c81b87 --- /dev/null +++ b/apps/website/public/t27/specs/sandbox/https_enforce.t27 @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: CC0-1.0 +// SANDBOX-012: HTTPS Enforcement + +/** + * Module: sandbox.https_enforce + * + * Enforces HTTPS for all API requests. + * - Production: redirect HTTP to HTTPS (301 permanent redirect) + * - Local: allow HTTP for development + * - Uses X-Forwarded-Proto header when behind proxy + * + * This prevents mixed content and ensures all traffic is encrypted. + */ + +module sandbox.https_enforce; + +// ───────────────────────────────────────────── +// Configuration +// ───────────────────────────────────────────── + +const HTTPS_REDIRECT_STATUS : u16 = 301; +const HTTPS_SCHEME : &[u8; 5] = b"https"; +const HTTP_SCHEME : &[u8; 4] = b"http"; +const LOCAL_HOSTNAMES : [&[u8; 9]; 3] = [b"localhost", b"127.0.0.1", b"[::1]"]; + +// ───────────────────────────────────────────── +// Request Context +// ───────────────────────────────────────────── + +pub struct RequestContext { + forwarded_proto: Option<&[u8]>, + host: &[u8], + is_local: bool, +} + +// ───────────────────────────────────────────── +// Core Logic +// ───────────────────────────────────────────── + +/** + * Determine if a request should be redirected to HTTPS. + * + * Redirect rules: + * 1. Never redirect in local mode (localhost/127.0.0.1/::1) + * 2. Never redirect if X-Forwarded-Proto is "https" + * 3. Always redirect if X-Forwarded-Proto is "http" or missing + * + * @param ctx - Request context containing headers and local flag + * @return true if redirect to HTTPS is required + */ +fn should_redirect(ctx: &RequestContext) -> bool { + // Local mode: never redirect + if ctx.is_local { + return false; + } + + match ctx.forwarded_proto { + Some(proto) => { + // If explicitly set to https, no redirect needed + proto == HTTPS_SCHEME + }, + None => true, // No header = assume HTTP, redirect + } +} + +/** + * Build HTTPS redirect URL from original HTTP URL. + * + * @param original_url - Original HTTP URL + * @return HTTPS URL + */ +fn redirect_url(original_url: &[u8]) -> Vec { + let mut result = Vec::with_capacity(original_url.len() + 1); + let scheme_end = if original_url.len() > 7 && &original_url[..7] == b"http://" { + 7 + } else if original_url.len() > 8 && &original_url[..8] == b"https://" { + 8 // Already HTTPS, return as-is + } else { + 0 // No scheme prefix + }; + + if scheme_end > 0 { + result.extend_from_slice(HTTPS_SCHEME); + result.extend_from_slice(b"://"); + result.extend_from_slice(&original_url[scheme_end..]); + } else { + result.extend_from_slice(original_url); + } + result +} + +/** + * Check if a hostname indicates a local development environment. + * + * @param host - Hostname to check + * @return true if hostname is local + */ +fn is_local_hostname(host: &[u8]) -> bool { + LOCAL_HOSTNAMES.iter().any(|local| *local == host) +} + +// ───────────────────────────────────────────── +// Interface +// ───────────────────────────────────────────── + +/** + * HTTPS enforcement middleware interface. + */ +pub trait HttpsEnforcer { + /** + * Check if request should redirect and build redirect URL. + * + * @param ctx - Request context + * @return None if no redirect, Some(https_url) if redirect needed + */ + fn enforce(&self, ctx: &RequestContext) -> Option>; +} + +// ───────────────────────────────────────────── +// TDD Tests +// ───────────────────────────────────────────── + +.test { + use sandbox.https_enforce; + + fn make_ctx(forwarded_proto: Option<&[u8]>, host: &[u8]) -> RequestContext { + RequestContext { + forwarded_proto, + host, + is_local: is_local_hostname(host), + } + } + + // Test: HTTPS requests are NOT redirected + test "https_not_redirected" { + input: { + ctx: make_ctx(Some(HTTPS_SCHEME), b"api.t27.dev"), + }; + expected: false; + description: "Requests with X-Forwarded-Proto: https should not be redirected"; + } + + // Test: HTTP requests in production ARE redirected + test "http_redirected_in_prod" { + input: { + ctx: make_ctx(Some(HTTP_SCHEME), b"api.t27.dev"), + }; + expected: true; + description: "Requests with X-Forwarded-Proto: http should be redirected to HTTPS"; + } + + // Test: Missing proto header triggers redirect + test "no_proto_header_redirected" { + input: { + ctx: make_ctx(None, b"api.t27.dev"), + }; + expected: true; + description: "Requests without X-Forwarded-Proto should be redirected"; + } + + // Test: Local mode never redirects + test "local_mode_never_redirects" { + input: { + ctx: make_ctx(Some(HTTP_SCHEME), b"localhost"), + }; + expected: false; + description: "Requests to localhost should never be redirected"; + } + + // Test: Redirect URL construction is correct + test "redirect_url_correct" { + input: { + original_url: b"http://api.t27.dev/health", + }; + expected: b"https://api.t27.dev/health"; + description: "HTTP URL should be converted to HTTPS URL"; + } + + // Test: HTTPS URL remains unchanged + test "https_url_unchanged" { + input: { + original_url: b"https://api.t27.dev/health", + }; + expected: b"https://api.t27.dev/health"; + description: "Already HTTPS URL should remain unchanged"; + } + + // Test: 127.0.0.1 is considered local + test "127_0_0_1_is_local" { + input: { + ctx: make_ctx(Some(HTTP_SCHEME), b"127.0.0.1"), + }; + expected: false; + description: "Requests to 127.0.0.1 should never be redirected"; + } + + // Test: IPv6 localhost is considered local + test "ipv6_localhost_is_local" { + input: { + ctx: make_ctx(Some(HTTP_SCHEME), b"[::1]"), + }; + expected: false; + description: "Requests to [::1] should never be redirected"; + } + + // Test: Port is preserved in redirect + test "redirect_preserves_port" { + input: { + original_url: b"http://api.t27.dev:8080/health", + }; + expected: b"https://api.t27.dev:8080/health"; + description: "Port number should be preserved in HTTPS redirect"; + } + + // Test: Query string is preserved in redirect + test "redirect_preserves_query" { + input: { + original_url: b"http://api.t27.dev/health?debug=true", + }; + expected: b"https://api.t27.dev/health?debug=true"; + description: "Query string should be preserved in HTTPS redirect"; + } +} diff --git a/apps/website/public/t27/specs/sandbox/modules.t27 b/apps/website/public/t27/specs/sandbox/modules.t27 new file mode 100644 index 0000000000..0bcd4c7f28 --- /dev/null +++ b/apps/website/public/t27/specs/sandbox/modules.t27 @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: CC0-1.0 + +/** + * Sandbox Modules Registry + * + * Defines all sandbox-related business logic modules + * that can be referenced by specs and used by the compiler. + */ + +module sandbox.modules; + +use sandbox.session_timeout; + +// 01234567891011121314151617181920212223242526272829303132333435363738394041424344 +// Session Timeout Module +// 454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889 + +pub use session_timeout; + test "modules_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/sandbox/orphan_detection.t27 b/apps/website/public/t27/specs/sandbox/orphan_detection.t27 new file mode 100644 index 0000000000..f3ed590b70 --- /dev/null +++ b/apps/website/public/t27/specs/sandbox/orphan_detection.t27 @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: CC0-1.0 +// SANDBOX-011: Orphaned Session Detection + +/** + * Module: sandbox.orphan_detection + * + * Detects sessions with no associated Railway resources (orphaned). + * Orphaned sessions are those where: + * 1. Session exists in database + * 2. No corresponding Railway deployment/service exists + * 3. Session status is still "active" or "starting" + * + * Orphaned sessions should be flagged for cleanup to prevent + * resource waste and ensure accurate slot accounting. + */ + +module sandbox.orphan_detection; + +import timestamp; +import sandbox.session; + +// ───────────────────────────────────────────── +// Session Type Definition (shared with session_timeout) +// ───────────────────────────────────────────── + +/** + * Common Session type for sandbox management. + */ +pub struct Session { + id: [u8; 32], + name: [u8; 64], + status: SessionStatus, + created_at: Timestamp, + updated_at: Timestamp, + railway_id: Option<[u8; 64]>, // Railway deployment/service ID +} + +/** + * Timestamp type for session tracking. + */ +pub struct Timestamp { + ms: u64, +} + +/** + * Session status enum. + */ +pub enum SessionStatus { + Starting = "starting", + Active = "active", + Failed = "failed", + Terminating = "terminating", + Deleted = "deleted", +} + +/** + * Optional type for railway_id field. + */ +pub enum Option { + Some(T), + None, +} + +// ───────────────────────────────────────────── +// Configuration +// ───────────────────────────────────────────── + +const ORPHANED_THRESHOLD_MINUTES : u64 = 15; // Sessions without railway_id for 15+ min are orphans + +/** + * Check if a session is orphaned. + * + * A session is considered orphaned if: + * 1. It has no railway_id (None or empty) + * 2. Its status is Starting or Active (not Failed/Terminating/Deleted) + * 3. It was created more than ORPHANED_THRESHOLD_MINUTES ago + * + * @param session - The session to check + * @param current_time_ms - Current timestamp in milliseconds + * @return true if session is orphaned + */ +fn is_session_orphaned( + session: &Session, + current_time_ms: u64, +) -> bool { + // Must have status that should have a railway resource + if session.status != SessionStatus.Starting && session.status != SessionStatus.Active { + return false; + } + + // Must have no railway_id (None) or empty + match session.railway_id { + Option::Some(id) if !id.is_empty() => return false, + _ => {}, + } + + // Must be old enough to be considered orphaned + let age_ms = current_time_ms - session.created_at.ms; + let threshold_ms = ORPHANED_THRESHOLD_MINUTES * 60_000; + age_ms >= threshold_ms +} + +/** + * Find all orphaned sessions in a list. + * + * @param sessions - List of sessions to check + * @param current_time_ms - Current timestamp in milliseconds + * @return Vector of orphaned session IDs + */ +fn detect_orphaned_sessions( + sessions: &[Session], + current_time_ms: u64, +) -> Vec<[u8; 32]> { + let mut orphans = Vec::new(); + for session in sessions { + if is_session_orphaned(session, current_time_ms) { + orphans.push(session.id); + } + } + orphans +} + +// ───────────────────────────────────────────── +// Interface +// ───────────────────────────────────────────── + +/** + * Orphan detection interface for sandbox health service. + */ +pub trait OrphanDetector { + /** + * Scan all sessions and return orphaned ones. + * + * @param current_time_ms - Current timestamp in milliseconds + * @return List of orphaned session IDs + */ + fn scan_orphans(&self, current_time_ms: u64) -> Vec<[u8; 32]>; + + /** + * Check if a specific session is orphaned. + * + * @param session - The session to check + * @return true if session is orphaned + */ + fn is_orphan(&self, session: &Session) -> bool; +} + +// ───────────────────────────────────────────── +// TDD Tests +// ───────────────────────────────────────────── + +.test { + use sandbox.orphan_detection; + + // Test: Active session without railway_id for > 15 minutes is orphaned + test "orphaned_active_session_no_railway_id" { + input: { + session: Session { + id: [0x01, 0x00, 0x00, 0x00, 0x00, 0x01], + name: [0x74, 0x65, 0x73, 0x74, 0x6e, 0x73, 0x73, 0x74, 0x74, 0x00], + status: SessionStatus.Active, + created_at: Timestamp { ms: 4_000_000_000 }, // 16 minutes ago + updated_at: Timestamp { ms: 4_000_000_000 }, + railway_id: Option::None, + }, + current_time_ms: 4_960_000_000, // Now + }; + expected: true; + description: "Active session without railway_id for >15 min should be orphaned"; + } + + // Test: Active session with railway_id is NOT orphaned + test "active_session_with_railway_id_not_orphaned" { + input: { + session: Session { + id: [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + name: [0x74, 0x65, 0x73, 0x74, 0x6e, 0x73, 0x73, 0x74, 0x74, 0x00], + status: SessionStatus.Active, + created_at: Timestamp { ms: 4_000_000_000 }, + updated_at: Timestamp { ms: 4_000_000_000 }, + railway_id: Option::Some([0x72, 0x77, 0x00]), // Has railway_id + }, + current_time_ms: 4_960_000_000, + }; + expected: false; + description: "Active session with railway_id should not be orphaned"; + } + + // Test: Failed session is NOT orphaned (even without railway_id) + test "failed_session_not_orphaned" { + input: { + session: Session { + id: [0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + name: [0x74, 0x65, 0x73, 0x74, 0x6e, 0x73, 0x73, 0x74, 0x74, 0x00], + status: SessionStatus.Failed, + created_at: Timestamp { ms: 4_000_000_000 }, + updated_at: Timestamp { ms: 4_000_000_000 }, + railway_id: Option::None, + }, + current_time_ms: 4_960_000_000, + }; + expected: false; + description: "Failed sessions should not be orphaned (handled separately)"; + } + + // Test: Terminating session is NOT orphaned + test "terminating_session_not_orphaned" { + input: { + session: Session { + id: [0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + name: [0x74, 0x65, 0x73, 0x74, 0x6e, 0x73, 0x73, 0x74, 0x74, 0x00], + status: SessionStatus.Terminating, + created_at: Timestamp { ms: 4_000_000_000 }, + updated_at: Timestamp { ms: 4_000_000_000 }, + railway_id: Option::None, + }, + current_time_ms: 4_960_000_000, + }; + expected: false; + description: "Terminating sessions should not be orphaned (already in cleanup)"; + } + + // Test: Deleted session is NOT orphaned + test "deleted_session_not_orphaned" { + input: { + session: Session { + id: [0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + name: [0x74, 0x65, 0x73, 0x74, 0x6e, 0x73, 0x73, 0x74, 0x74, 0x00], + status: SessionStatus.Deleted, + created_at: Timestamp { ms: 4_000_000_000 }, + updated_at: Timestamp { ms: 4_000_000_000 }, + railway_id: Option::None, + }, + current_time_ms: 4_960_000_000, + }; + expected: false; + description: "Deleted sessions should not be orphaned (already cleaned up)"; + } + + // Test: Starting session < 15 minutes without railway_id is NOT orphaned (grace period) + test "starting_session_grace_period_not_orphaned" { + input: { + session: Session { + id: [0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + name: [0x74, 0x65, 0x73, 0x74, 0x6e, 0x73, 0x73, 0x74, 0x74, 0x00], + status: SessionStatus.Starting, + created_at: Timestamp { ms: 4_800_000_000 }, // 10 minutes ago + updated_at: Timestamp { ms: 4_800_000_000 }, + railway_id: Option::None, + }, + current_time_ms: 5_400_000_000, // Now + }; + expected: false; + description: "Starting session within 15 min grace period should not be orphaned"; + } +} diff --git a/apps/website/public/t27/specs/sandbox/session_timeout.t27 b/apps/website/public/t27/specs/sandbox/session_timeout.t27 new file mode 100644 index 0000000000..0c741dde3e --- /dev/null +++ b/apps/website/public/t27/specs/sandbox/session_timeout.t27 @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: CC0-1.0 +// SANDBOX-010: Session Timeout Enforcement + +/** + * Module: sandbox.session_timeout + * + * Enforces maximum session lifetime for Railway sandbox resources. + * Sessions exceeding max duration are automatically terminated + * to prevent resource waste and slot exhaustion. + */ + +module sandbox.session_timeout; + +import timestamp; + +// --------------------------------------------- +// Session Type Definition +// --------------------------------------------- + +/** + * Common Session type for sandbox management. + * Shared between .t27 specifications and TypeScript backend. + */ +pub struct Session { + id: [u8; 32], + name: [u8; 64], + status: SessionStatus, + created_at: Timestamp, + updated_at: Timestamp, +} + +/** + * Timestamp type for session tracking. + */ +pub struct Timestamp { + ms: u64, +} + +/** + * Session status enum matching TypeScript/PostgreSQL conventions. + */ +pub enum SessionStatus { + Starting = "starting", + Active = "active", + Failed = "failed", + Terminating = "terminating", + Deleted = "deleted", +} + +// --------------------------------------------- +// Configuration +// --------------------------------------------- + +const DEFAULT_MAX_SESSION_DURATION_MS : u64 = 3_600_000; // 1 hour + +/** + * Check if a session has exceeded its maximum allowed duration. + * + * @param session - The session to check + * @param max_duration_ms - Maximum allowed duration in milliseconds + * @return true if session should be terminated + */ +fn should_terminate_session( + session: &Session, + max_duration_ms: u64, +) -> bool { + if session.status != SessionStatus.Active { + return false; + } + + let elapsed = timestamp.now_ms() - session.created_at.ms; + elapsed > max_duration_ms +} + +// --------------------------------------------- +// Interface +// --------------------------------------------- + +/** + * Session timeout check interface for sandbox health service. + */ +pub trait TimeoutChecker { + /** + * Check if a session exceeds max duration and mark for termination. + * + * @param session - The session to evaluate + * @return true if termination is required + */ + fn check_timeout(&self, session: &Session) -> bool; +} + +// --------------------------------------------- +// TDD Tests +// --------------------------------------------- + +.test { + use sandbox.session_timeout; + + // Test: Session exceeds max duration should be terminated + test "terminate_session_exceeded_duration" { + input: { + session: Session { + id: [0x01, 0x00, 0x00, 0x00, 0x00, 0x01], + name: [0x74, 0x65, 0x73, 0x74, 0x6e, 0x73, 0x73, 0x74, 0x74, 0x00], + status: SessionStatus.Active, + created_at: Timestamp { ms: 4_000_000_000 }, + updated_at: Timestamp { ms: 4_000_000_000 }, + }, + max_duration_ms: 3_600_000, + }; + expected: true; + description: "Session older than max duration should be marked for termination"; + } + + // Test: Session within max duration should NOT be terminated + test "session_within_max_duration" { + input: { + session: Session { + id: [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + name: [0x74, 0x65, 0x73, 0x74, 0x6e, 0x73, 0x73, 0x74, 0x74, 0x00], + status: SessionStatus.Active, + created_at: Timestamp { ms: 2_000_000_000 }, + updated_at: Timestamp { ms: 2_000_000_000 }, + }, + max_duration_ms: 3_600_000, + }; + expected: false; + description: "Session within max duration should not be terminated"; + } + + // Test: Non-active session should NOT be terminated + test "non_active_session_ignored" { + input: { + session: Session { + id: [0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + name: [0x74, 0x65, 0x73, 0x74, 0x6e, 0x73, 0x73, 0x74, 0x74, 0x00], + status: SessionStatus.Starting, + created_at: Timestamp { ms: 2_000_000_000 }, + updated_at: Timestamp { ms: 2_000_000_000 }, + }, + max_duration_ms: 3_600_000, + }; + expected: false; + description: "Non-active sessions should not be terminated even if old"; + } + + // Test: Failed session should NOT be terminated + test "failed_session_ignored" { + input: { + session: Session { + id: [0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + name: [0x74, 0x65, 0x73, 0x74, 0x6e, 0x73, 0x73, 0x74, 0x74, 0x00], + status: SessionStatus.Failed, + created_at: Timestamp { ms: 2_000_000_000 }, + updated_at: Timestamp { ms: 2_000_000_000 }, + }, + max_duration_ms: 3_600_000, + }; + expected: false; + description: "Failed sessions should not be terminated"; + } + + // Test: Deleted session should NOT be terminated + test "deleted_session_ignored" { + input: { + session: Session { + id: [0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + name: [0x74, 0x65, 0x73, 0x74, 0x6e, 0x73, 0x73, 0x74, 0x74, 0x00], + status: SessionStatus.Deleted, + created_at: Timestamp { ms: 2_000_000_000 }, + updated_at: Timestamp { ms: 2_000_000_000 }, + }, + max_duration_ms: 3_600_000, + }; + expected: false; + description: "Deleted sessions should not be terminated"; + } + + // Test: Terminating session should NOT be terminated (already in process) + test "terminating_session_ignored" { + input: { + session: Session { + id: [0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], + name: [0x74, 0x65, 0x73, 0x74, 0x6e, 0x73, 0x73, 0x74, 0x74, 0x00], + status: SessionStatus.Terminating, + created_at: Timestamp { ms: 2_000_000_000 }, + updated_at: Timestamp { ms: 2_000_000_000 }, + }, + max_duration_ms: 3_600_000, + }; + expected: false; + description: "Terminating sessions should not be terminated (already in process)"; + } +} diff --git a/apps/website/public/t27/specs/server/agent-runner.t27 b/apps/website/public/t27/specs/server/agent-runner.t27 new file mode 100644 index 0000000000..9a96e9fcbe --- /dev/null +++ b/apps/website/public/t27/specs/server/agent-runner.t27 @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/server/agent-runner.t27 +// Agent Runner Specification +// Constitutional Law #4: De-Zig-fication - .t27 is source of truth +// Constitutional Law #5: De-Zig Strict - no new Rust business logic + +module AgentRunner { + use base::types; + + // ==================================================================== + // Agent Report + // ==================================================================== + + struct AgentReport { + turns: u32, + total_input_tokens: u64, + total_output_tokens: u64, + tool_call_count: u32, + file_mod_count: u32, + task_completed: bool, + summary: str, + duration_seconds: f64, + } + + // ==================================================================== + // Tool System + // ==================================================================== + + struct ToolResult { + output: str, + is_complete: bool, + duration_ms: u64, + success: bool, + } + + enum StopReason { + EndTurn = 0, + ToolUse = 1, + MaxTokens = 2, + Unknown = 3, + } + + // ==================================================================== + // Agent State + // ==================================================================== + + struct AgentState { + turn: u32, + max_turns: u32, + total_input_tokens: u64, + total_output_tokens: u64, + tool_call_counter: u32, + task_completed: bool, + completion_summary: str, + } + + // ==================================================================== + // Agent Constants + // ==================================================================== + + const MAX_TURNS: u32 = 100; + const MAX_TOOLS_CALLED: u32 = 256; + const MAX_FILES_MODIFIED: u32 = 256; + const DEFAULT_TIMEOUT_SECONDS: u32 = 120; + const MAX_TOKEN_SUMMARY_LENGTH: u32 = 200; + + // ==================================================================== + // Agent Initialization + // ==================================================================== + + fn agent_state_init(max_turns: u32) -> AgentState { + var state = AgentState{}; + state.turn = 0; + state.max_turns = max_turns; + state.total_input_tokens = 0; + state.total_output_tokens = 0; + state.tool_call_counter = 0; + state.task_completed = false; + return state; + } + + fn agent_report_init() -> AgentReport { + var report = AgentReport{}; + report.turns = 0; + report.total_input_tokens = 0; + report.total_output_tokens = 0; + report.tool_call_count = 0; + report.file_mod_count = 0; + report.task_completed = false; + report.duration_seconds = 0.0; + return report; + } + + // ==================================================================== + // Tool Execution + // ==================================================================== + + fn track_tool_call(state: AgentState, tool_name: str) -> AgentState { + var new_state = state; + new_state.tool_call_counter = state.tool_call_counter + 1; + return new_state; + } + + fn check_task_complete(result: ToolResult) -> bool { + return result.is_complete; + } + + // ==================================================================== + // Stop Reason Helpers + // ==================================================================== + + fn stop_reason_from_string(reason: str) -> StopReason { + if reason == "end_turn" { + return StopReason::EndTurn; + } + if reason == "tool_use" { + return StopReason::ToolUse; + } + if reason == "max_tokens" { + return StopReason::MaxTokens; + } + return StopReason::Unknown; + } + + fn stop_reason_to_string(reason: StopReason) -> str { + if reason == StopReason::EndTurn { + return "end_turn"; + } + if reason == StopReason::ToolUse { + return "tool_use"; + } + if reason == StopReason::MaxTokens { + return "max_tokens"; + } + return "unknown"; + } + + // ==================================================================== + // Token Tracking + // ==================================================================== + + fn add_tokens(state: AgentState, input_tokens: u64, output_tokens: u64) -> AgentState { + var new_state = state; + new_state.total_input_tokens = state.total_input_tokens + input_tokens; + new_state.total_output_tokens = state.total_output_tokens + output_tokens; + return new_state; + } + + fn total_tokens(state: AgentState) -> u64 { + return state.total_input_tokens + state.total_output_tokens; + } + + // ==================================================================== + // String Helpers + // ==================================================================== + + fn truncate_string(s: str, max: u32) -> str { + return s; + } + + // ==================================================================== + // Turn Management + // ==================================================================== + + fn next_turn(state: AgentState) -> AgentState { + var new_state = state; + new_state.turn = state.turn + 1; + return new_state; + } + + fn should_continue(state: AgentState) -> bool { + return state.turn < state.max_turns; + } + + // ==================================================================== + // Report Building + // ==================================================================== + + fn build_report(state: AgentState, duration_seconds: f64) -> AgentReport { + var report = agent_report_init(); + report.turns = state.turn; + report.total_input_tokens = state.total_input_tokens; + report.total_output_tokens = state.total_output_tokens; + report.task_completed = state.task_completed; + report.summary = state.completion_summary; + report.duration_seconds = duration_seconds; + return report; + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "agent_state_init" { + var state = agent_state_init(10); + assert(state.turn == 0); + assert(state.max_turns == 10); + assert(state.total_input_tokens == 0); + assert(state.total_output_tokens == 0); + assert(state.task_completed == false); + } + + test "agent_report_init" { + var report = agent_report_init(); + assert(report.turns == 0); + assert(report.task_completed == false); + assert(report.duration_seconds == 0.0); + } + + test "stop_reason_conversion" { + assert(stop_reason_from_string("end_turn") == StopReason::EndTurn); + assert(stop_reason_from_string("tool_use") == StopReason::ToolUse); + assert(stop_reason_from_string("max_tokens") == StopReason::MaxTokens); + assert(stop_reason_from_string("unknown") == StopReason::Unknown); + } + + test "token_tracking" { + var state = agent_state_init(10); + state = add_tokens(state, 100, 50); + assert(state.total_input_tokens == 100); + assert(state.total_output_tokens == 50); + assert(total_tokens(state) == 150); + } + + test "turn_management" { + var state = agent_state_init(10); + assert(should_continue(state) == true); + state = next_turn(state); + assert(state.turn == 1); + } + + test "constants" { + assert(MAX_TURNS == 100); + assert(MAX_TOOLS_CALLED == 256); + assert(MAX_FILES_MODIFIED == 256); + assert(DEFAULT_TIMEOUT_SECONDS == 120); + assert(MAX_TOKEN_SUMMARY_LENGTH == 200); + } + + // ==================================================================== + // Invariants + // ==================================================================== + + invariant "turns_non_negative" { + var state = agent_state_init(10); + assert(state.turn >= 0); + } +} diff --git a/apps/website/public/t27/specs/server/api.t27 b/apps/website/public/t27/specs/server/api.t27 new file mode 100644 index 0000000000..72b4fb85b3 --- /dev/null +++ b/apps/website/public/t27/specs/server/api.t27 @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/server/api.t27 +// API Client Types Specification +// Constitutional Law #4: De-Zig-fication - .t27 is source of truth +// Constitutional Law #5: De-Zig Strict - no new Rust business logic + +module Api { + use base::types; + + // ==================================================================== + // Message Types + // ==================================================================== + + enum MessageRole { + User = 0, + Assistant = 1, + System = 2, + } + + enum ContentBlockType { + Text = 0, + ToolUse = 1, + ToolResult = 2, + Thinking = 3, + } + + struct Message { + role: MessageRole, + content: MessageContent, + } + + // ==================================================================== + // Content Block + // ==================================================================== + + struct TextBlock { + block_type: ContentBlockType, + text: str, + } + + struct ToolUseBlock { + block_type: ContentBlockType, + id: str, + name: str, + input: str, + } + + struct ToolResultBlock { + block_type: ContentBlockType, + tool_use_id: str, + content: str, + } + + struct ThinkingBlock { + block_type: ContentBlockType, + thinking: str, + } + + // ==================================================================== + // API Response + // ==================================================================== + + struct Usage { + input_tokens: u32, + output_tokens: u32, + } + + struct ApiResponse { + id: str, + model: str, + stop_reason: str, + usage: Usage, + } + + // ==================================================================== + // API Constants + // ==================================================================== + + const DEFAULT_TIMEOUT_SECONDS: u32 = 30; + const DEFAULT_MAX_TOKENS: u32 = 8192; + const DEFAULT_BASE_URL: str = "https://api.z.ai/api/anthropic"; + const DEFAULT_MODEL: str = "claude-sonnet-4-5-20250514"; + + // ==================================================================== + // Helper Functions + // ==================================================================== + + fn get_block_type_name(block_type: ContentBlockType) -> str { + if block_type == ContentBlockType::Text { + return "text"; + } + if block_type == ContentBlockType::ToolUse { + return "tool_use"; + } + if block_type == ContentBlockType::ToolResult { + return "tool_result"; + } + if block_type == ContentBlockType::Thinking { + return "thinking"; + } + return "unknown"; + } + + fn get_role_name(role: MessageRole) -> str { + if role == MessageRole::User { + return "user"; + } + if role == MessageRole::Assistant { + return "assistant"; + } + if role == MessageRole::System { + return "system"; + } + return "unknown"; + } + + fn total_usage(usage: Usage) -> u32 { + return usage.input_tokens + usage.output_tokens; + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "block_type_names" { + assert(get_block_type_name(ContentBlockType::Text) == "text"); + assert(get_block_type_name(ContentBlockType::ToolUse) == "tool_use"); + assert(get_block_type_name(ContentBlockType::ToolResult) == "tool_result"); + assert(get_block_type_name(ContentBlockType::Thinking) == "thinking"); + } + + test "role_names" { + assert(get_role_name(MessageRole::User) == "user"); + assert(get_role_name(MessageRole::Assistant) == "assistant"); + assert(get_role_name(MessageRole::System) == "system"); + } + + test "total_usage" { + var usage = Usage{ input_tokens = 100, output_tokens = 50 }; + assert(total_usage(usage) == 150); + } + + test "constants" { + assert(DEFAULT_TIMEOUT_SECONDS == 30); + assert(DEFAULT_MAX_TOKENS == 8192); + } +} diff --git a/apps/website/public/t27/specs/server/http.t27 b/apps/website/public/t27/specs/server/http.t27 new file mode 100644 index 0000000000..a5f1b990a8 --- /dev/null +++ b/apps/website/public/t27/specs/server/http.t27 @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: Apache-2.0 +// http.t27 — HTTP Server Specification +// HTTP listener, request/response handling, middleware +// φ² + 1/φ² = 3 | TRINITY + +module server-http; + +// ============================================================================ +// Imports +// ============================================================================ + +use std; +use lsp-schema::Position; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default HTTP port +pub const DEFAULT_PORT : u16 = 8080; + +/// Maximum concurrent connections +pub const MAX_CONNECTIONS : u16 = 100; + +/// Request timeout in seconds +pub const REQUEST_TIMEOUT : u16 = 30; + +/// HTTP GET method +pub const METHOD_GET : [3]u8 = "GET"; + +/// HTTP POST method +pub const METHOD_POST : [4]u8 = "POST"; + +/// HTTP PUT method +pub const METHOD_PUT : [3]u8 = "PUT"; + +/// HTTP DELETE method +pub const METHOD_DELETE : [6]u8 = "DELETE"; + +/// HTTP 200 OK +pub const STATUS_OK : u16 = 200; + +/// HTTP 404 Not Found +pub const STATUS_NOT_FOUND : u16 = 404; + +/// HTTP 500 Internal Server Error +pub const STATUS_ERROR : u16 = 500; + +/// Content-Type header for JSON +pub const CONTENT_TYPE_JSON : [16]u8 = "application/json"; + +/// Content-Type header for SSE +pub const CONTENT_TYPE_SSE : [20]u8 = "text/event-stream"; + +// ============================================================================ +// Types +// ============================================================================ + +/// HTTP method type +pub const HttpMethod = enum(u8) { + get = 0, + post = 1, + put = 2, + delete = 3, +}; + +/// HTTP status code with reason +pub const HttpStatus = struct { + code : u16, + reason : []u8, +}; + +/// HTTP request headers +pub const HttpHeaders = struct { + content_type : []u8, + content_length : usize, + user_agent : []u8, +}; + +/// HTTP request +pub const HttpRequest = struct { + method : HttpMethod, + path : []u8, + headers : HttpHeaders, + body : []u8, +}; + +/// HTTP response +pub const HttpResponse = struct { + status : HttpStatus, + headers : HttpHeaders, + body : []u8, +}; + +/// Server configuration +pub const ServerConfig = struct { + host : []u8, + port : u16, + max_connections : u16, + timeout_sec : u16, +}; + +/// Middleware context +pub const MiddlewareContext = struct { + request : HttpRequest, + response : ?*HttpResponse, + metadata : []u8, +}; + +/// Middleware function signature +pub const Middleware = fn(MiddlewareContext) bool; + +/// Server state +pub const ServerState = enum(u8) { + stopped = 0, + starting = 1, + running = 2, + stopping = 3, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create default server configuration +pub fn config_default() ServerConfig { + return ServerConfig{ + .host = "127.0.0.1", + .port = DEFAULT_PORT, + .max_connections = MAX_CONNECTIONS, + .timeout_sec = REQUEST_TIMEOUT, + }; +} + +/// Create HTTP 200 OK response +pub fn response_ok(body: []u8) HttpResponse { + return HttpResponse{ + .status = HttpStatus{ .code = STATUS_OK, .reason = "OK" }, + .headers = HttpHeaders{ + .content_type = CONTENT_TYPE_JSON, + .content_length = body.len, + .user_agent = "", + }, + .body = body, + }; +} + +/// Create HTTP 404 Not Found response +pub fn response_not_found() HttpResponse { + return HttpResponse{ + .status = HttpStatus{ .code = STATUS_NOT_FOUND, .reason = "Not Found" }, + .headers = HttpHeaders{ .content_type = CONTENT_TYPE_JSON, .content_length = 0, .user_agent = "" }, + .body = "[]", + }; +} + +/// Create HTTP 500 Internal Server Error response +pub fn response_error(message: []u8) HttpResponse { + return HttpResponse{ + .status = HttpStatus{ .code = STATUS_ERROR, .reason = "Internal Server Error" }, + .headers = HttpHeaders{ .content_type = CONTENT_TYPE_JSON, .content_length = message.len, .user_agent = "" }, + .body = message, + }; +} + +/// Create empty HTTP request +pub fn request_empty() HttpRequest { + return HttpRequest{ + .method = .get, + .path = "/", + .headers = HttpHeaders{ .content_type = "", .content_length = 0, .user_agent = "" }, + .body = "", + }; +} + +/// Create middleware context from request +pub fn middleware_context_create(req: HttpRequest) MiddlewareContext { + return MiddlewareContext{ + .request = req, + .response = null, + .metadata = "", + }; +} + +/// Check if status is success (2xx) +pub fn status_is_success(status: HttpStatus) bool { + return status.code >= 200 and status.code < 300; +} + +/// Check if status is client error (4xx) +pub fn status_is_client_error(status: HttpStatus) bool { + return status.code >= 400 and status.code < 500; +} + +/// Check if status is server error (5xx) +pub fn status_is_server_error(status: HttpStatus) bool { + return status.code >= 500 and status.code < 600; +} + +/// Create HTTP GET request for path +pub fn request_get(path: []u8) HttpRequest { + return HttpRequest{ + .method = .get, + .path = path, + .headers = HttpHeaders{ .content_type = "", .content_length = 0, .user_agent = "" }, + .body = "", + }; +} + +/// Create HTTP POST request with body +pub fn request_post(path: []u8, body: []u8) HttpRequest { + return HttpRequest{ + .method = .post, + .path = path, + .headers = HttpHeaders{ + .content_type = CONTENT_TYPE_JSON, + .content_length = body.len, + .user_agent = "", + }, + .body = body, + }; +} + +/// Get method as string +pub fn method_to_string(method: HttpMethod) []u8 { + return switch (method) { + .get => METHOD_GET, + .post => METHOD_POST, + .put => METHOD_PUT, + .delete => METHOD_DELETE, + }; +} + +/// Check if content type is JSON +pub fn is_json_content(content_type: []u8) bool { + return std.mem.eql(content_type, CONTENT_TYPE_JSON); +} + +/// Check if content type is SSE +pub fn is_sse_content(content_type: []u8) bool { + return std.mem.eql(content_type, CONTENT_TYPE_SSE); +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "http_config_default_has_defaults" { + const cfg = config_default(); + try std.testing.expect(std.mem.eql(cfg.host, "127.0.0.1")); + try std.testing.expect(cfg.port == DEFAULT_PORT); + try std.testing.expect(cfg.max_connections == MAX_CONNECTIONS); +} + +test "http_response_ok_has_status_200" { + const resp = response_ok("test"); + try std.testing.expect(resp.status.code == STATUS_OK); +} + +test "http_response_not_found_has_status_404" { + const resp = response_not_found(); + try std.testing.expect(resp.status.code == STATUS_NOT_FOUND); +} + +test "http_response_error_has_status_500" { + const resp = response_error("error"); + try std.testing.expect(resp.status.code == STATUS_ERROR); +} + +test "http_request_get_has_get_method" { + const req = request_get("/test"); + try std.testing.expect(req.method == .get); + try std.testing.expect(std.mem.eql(req.path, "/test")); +} + +test "http_request_post_has_body" { + const body = "{ \"test\": true }"; + const req = request_post("/api", body); + try std.testing.expect(req.method == .post); + try std.testing.expect(req.body.len == body.len); +} + +test "http_status_200_is_success" { + const status = HttpStatus{ .code = 200, .reason = "OK" }; + try std.testing.expect(status_is_success(status)); +} + +test "http_status_404_is_client_error" { + const status = HttpStatus{ .code = 404, .reason = "Not Found" }; + try std.testing.expect(status_is_client_error(status)); +} + +test "http_status_500_is_server_error" { + const status = HttpStatus{ .code = 500, .reason = "Internal Server Error" }; + try std.testing.expect(status_is_server_error(status)); +} + +test "http_method_to_string" { + try std.testing.expectEqual(@as(usize, method_to_string(.get).len), @as(usize, 3)); + try std.testing.expect(std.mem.eql(method_to_string(.post), METHOD_POST)); +} + +test "http_is_json_content" { + try std.testing.expect(is_json_content(CONTENT_TYPE_JSON)); + try std.testing.expect(!is_json_content(CONTENT_TYPE_SSE)); +} + +test "http_is_sse_content" { + try std.testing.expect(is_sse_content(CONTENT_TYPE_SSE)); + try std.testing.expect(!is_sse_content(CONTENT_TYPE_JSON)); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant port_range_valid { + // Port must be in valid range [1, 65535] + @compileAssert(DEFAULT_PORT < 65536); +} + +invariant max_connections_positive { + // Max connections must be positive + @compileAssert(MAX_CONNECTIONS > 0); +} + +invariant timeout_positive { + // Request timeout must be positive + @compileAssert(REQUEST_TIMEOUT > 0); +} + +invariant status_code_valid_range { + // Status codes are in [100, 599] + @compileAssert(STATUS_OK >= 100 and STATUS_OK < 600); + @compileAssert(STATUS_NOT_FOUND >= 100 and STATUS_NOT_FOUND < 600); + @compileAssert(STATUS_ERROR >= 100 and STATUS_ERROR < 600); +} + +invariant method_enum_valid { + // HttpMethod enum has 4 values + @compileAssert(@intFromEnum(HttpMethod.delete) == 3); +} + +invariant state_enum_valid { + // ServerState enum has 4 values + @compileAssert(@intFromEnum(ServerState.stopping) == 3); +} + +invariant status_success_for_2xx { + // status_is_success returns true for 2xx codes + @compileAssert(true); +} + +invariant status_client_error_for_4xx { + // status_is_client_error returns true for 4xx codes + @compileAssert(true); +} + +invariant status_server_error_for_5xx { + // status_is_server_error returns true for 5xx codes + @compileAssert(true); +} + +invariant request_body_length_matches_header { + // Content-length header matches body length + @compileAssert(true); +} + +invariant response_body_length_matches_header { + // Content-length header matches body length + @compileAssert(true); +} + +invariant middleware_returns_bool { + // Middleware functions return boolean continuation flag + @compileAssert(true); +} + +invariant config_host_is_string { + // ServerConfig host is string + @compileAssert(true); +} + +invariant request_method_is_enum { + // HttpRequest method is HttpMethod enum + @compileAssert(true); +} + +invariant response_status_is_struct { + // HttpResponse status is HttpStatus struct + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "http_response_create_latency" { + // Measure: cycles for response creation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : HttpResponse = undefined; + for (0..1000) |_| { + result = response_ok("test"); + } + _ = result; +} + +bench "http_request_create_latency" { + // Measure: cycles for request creation + // Target: < 80 cycles + @setEvalBranchQuota(10000); + var result : HttpRequest = undefined; + for (0..1000) |_| { + result = request_get("/test"); + } + _ = result; +} + +bench "http_status_check_latency" { + // Measure: cycles for status classification + // Target: < 20 cycles + @setEvalBranchQuota(10000); + const status = HttpStatus{ .code = 200, .reason = "OK" }; + var result : bool = false; + for (0..1000) |_| { + result = status_is_success(status); + } + _ = result; +} + +bench "http_method_to_string_latency" { + // Measure: cycles for method string conversion + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = method_to_string(.post); + } + _ = result; +} + +bench "http_content_type_check_latency" { + // Measure: cycles for content type check + // Target: < 40 cycles + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..1000) |_| { + result = is_json_content(CONTENT_TYPE_JSON); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/server/mdns.t27 b/apps/website/public/t27/specs/server/mdns.t27 new file mode 100644 index 0000000000..e075591d6d --- /dev/null +++ b/apps/website/public/t27/specs/server/mdns.t27 @@ -0,0 +1,566 @@ +// SPDX-License-Identifier: Apache-2.0 +// mdns.t27 — mDNS Specification +// Multicast DNS service discovery, announcement, resolution +// φ² + 1/φ² = 3 | TRINITY + +module server-mdns; + +// ============================================================================ +// Imports +// ============================================================================ + +use std; + +// ============================================================================ +// Constants +// ============================================================================ + +/// mDNS port number +pub const MDNS_PORT : u16 = 5353; + +/// mDNS IPv4 multicast address +pub const MDNS_IPV4 : [15]u8 = "224.0.0.251"; + +/// mDNS IPv6 multicast address +pub const MDNS_IPV6 : [39]u8 = "ff02::fb"; + +/// Default mDNS service type for HTTP +pub const SERVICE_HTTP : [9]u8 = "_http._tcp"; + +/// Default mDNS service type for LSP +pub const SERVICE_LSP : [8]u8 = "_lsp._tcp"; + +/// mDNS protocol version +pub const PROTOCOL_VERSION : u8 = 1; + +/// Query interval in milliseconds +pub const QUERY_INTERVAL_MS : u32 = 60000; // 60 seconds + +/// Announcement TTL in seconds +pub const ANNOUNCEMENT_TTL : u32 = 120; // 2 minutes + +/// Query timeout in milliseconds +pub const QUERY_TIMEOUT_MS : u32 = 5000; // 5 seconds + +/// PTR query type +pub const QTYPE_PTR : u16 = 12; + +/// SRV query type +pub const QTYPE_SRV : u16 = 33; + +/// TXT query type +pub const QTYPE_TXT : u16 = 16; + +/// mDNS response class +pub const CLASS_IN : u16 = 1; + +// ============================================================================ +// Types +// ============================================================================ + +/// mDNS record type +pub const RecordType = enum(u8) { + ptr = 0, // Pointer record + srv = 1, // Service record + txt = 2, // Text record + a = 3, // Address record +}; + +/// mDNS query type +pub const QueryType = enum(u8) { + ptr = 0, // Pointer query + srv = 1, // Service query + txt = 2, // Text query +}; + +/// mDNS service record +pub const ServiceRecord = struct { + name : []u8, + type : []u8, // _service._proto + domain : []u8, // .local + port : u16, + priority : u16, + weight : u16, + txt_data : []u8, +}; + +/// mDNS PTR record +pub const PTRRecord = struct { + name : []u8, + ptr_target : []u8, +}; + +/// mDNS TXT record +pub const TXTRecord = struct { + name : []u8, + data : []u8, +}; + +/// mDNS query +pub const Query = struct { + qtype : QueryType, + name : []u8, +}; + +/// mDNS response +pub const Response = struct { + qtype : QueryType, + records : []ServiceRecord, +}; + +/// mDNS discovery state +pub const DiscoveryState = enum(u8) { + idle = 0, + announcing = 1, + querying = 2, + discovered = 3, + error = 4, +}; + +/// mDNS resolver +pub const Resolver = struct { + services : []ServiceRecord, + last_query_time_ms : u64, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create service record +pub fn service_create(name: []u8, type: []u8, port: u16) ServiceRecord { + return ServiceRecord{ + .name = name, + .type = type, + .domain = "local", + .port = port, + .priority = 0, // Default priority + .weight = 0, // Default weight + .txt_data = "", + }; +} + +/// Create service record with TXT data +pub fn service_create_with_txt(name: []u8, type: []u8, port: u16, txt: []u8) ServiceRecord { + const base = service_create(name, type, port); + return ServiceRecord{ + .name = name, + .type = type, + .domain = "local", + .port = port, + .priority = 0, + .weight = 0, + .txt_data = txt, + }; +} + +/// Create PTR record +pub fn ptr_create(name: []u8, target: []u8) PTRRecord { + return PTRRecord{ + .name = name, + .ptr_target = target, + }; +} + +/// Create TXT record +pub fn txt_create(name: []u8, data: []u8) TXTRecord { + return TXTRecord{ + .name = name, + .data = data, + }; +} + +/// Create query for service +pub fn query_srv(service_name: []u8) Query { + return Query{ + .qtype = .srv, + .name = service_name, + }; +} + +/// Create PTR query +pub fn query_ptr() Query { + return Query{ + .qtype = .ptr, + .name = "_services._dns-sd._udp", + }; +} + +/// Create TXT query +pub fn query_txt(service_name: []u8) Query { + return Query{ + .qtype = .txt, + .name = service_name, + }; +} + +/// Create new resolver +pub fn resolver_new() Resolver { + return Resolver{ + .services = &[_]ServiceRecord{}, + .last_query_time_ms = 0, + }; +} + +/// Add service to resolver +pub fn resolver_add_service(resolver: *Resolver, service: ServiceRecord) void { + resolver.services = append(resolver.services, service); +} + +/// Find service by name +pub fn resolver_find_service(resolver: Resolver, name: []u8) ?ServiceRecord { + for (resolver.services) |service| { + if (std.mem.eql(service.name, name)) { + return service; + } + } + return null; +} + +/// Find services by type +pub fn resolver_find_by_type(resolver: Resolver, type: []u8) []ServiceRecord { + var result : []ServiceRecord = &[_]ServiceRecord{}; + for (resolver.services) |service| { + if (std.mem.eql(service.type, type)) { + result = append(result, service); + } + } + return result; +} + +/// Get service count +pub fn resolver_count(resolver: Resolver) usize { + return resolver.services.len; +} + +/// Format service as mDNS name +pub fn service_format_name(service: ServiceRecord) []u8 { + return concat(service.name, ".", service.type); +} + +/// Format service as full domain name +pub fn service_format_fqdn(service: ServiceRecord) []u8 { + const local_name = service_format_name(service); + return concat(local_name, ".local"); +} + +/// Check if record is PTR type +pub fn is_ptr_record(qtype: QueryType) bool { + return qtype == .ptr; +} + +/// Check if record is SRV type +pub fn is_srv_record(qtype: QueryType) bool { + return qtype == .srv; +} + +/// Check if record is TXT type +pub fn is_txt_record(qtype: QueryType) bool { + return qtype == .txt; +} + +/// Append to slice +pub fn append(slice: []ServiceRecord, item: ServiceRecord) []ServiceRecord { + var result : []ServiceRecord = slice; + var new_slice : []ServiceRecord = &[_]ServiceRecord{item}; + for (result) |_| { + new_slice = append(new_slice, _); + } + return new_slice; +} + +/// Concatenate two strings +pub fn concat(a: []u8, b: []u8) []u8 { + var result : []u8 = a; + for (b) |byte| { + result = append_bytes(result, byte); + } + return result; +} + +/// Append byte to string +pub fn append_bytes(slice: []u8, byte: u8) []u8 { + var result : []u8 = slice; + result = concat(result, &[_]u8{byte}); + return result; +} + +/// Update last query time +pub fn resolver_update_time(resolver: *Resolver) void { + resolver.last_query_time_ms = get_timestamp_ms(); +} + +/// Check if cache is stale +pub fn resolver_cache_stale(resolver: Resolver, ttl_ms: u32) bool { + const elapsed = get_timestamp_ms() - resolver.last_query_time_ms; + return elapsed > ttl_ms; +} + +/// Get current timestamp in milliseconds +pub fn get_timestamp_ms() u64 { + // Simplified: would use system time in implementation + return 0; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "mdns_service_create_defaults" { + const service = service_create("my-service", "_http._tcp", 8080); + try std.testing.expect(service.port == 8080); + try std.testing.expect(std.mem.eql(service.type, "_http._tcp")); +} + +test "mdns_service_create_with_txt" { + const service = service_create_with_txt("my-service", "_http._tcp", 8080, "key=value"); + try std.testing.expect(service.txt_data.len > 0); +} + +test "mdns_ptr_create" { + const ptr = ptr_create("_http._tcp.local", "my-service._http._tcp"); + try std.testing.expect(std.mem.eql(ptr.ptr_target, "my-service._http._tcp")); +} + +test "mdns_txt_create" { + const txt = txt_create("my-service._http._tcp", "info"); + try std.testing.expect(std.mem.eql(txt.data, "info")); +} + +test "mdns_query_srv" { + const query = query_srv("my-service._http._tcp"); + try std.testing.expect(query.qtype == .srv); +} + +test "mdns_query_ptr" { + const query = query_ptr(); + try std.testing.expect(query.qtype == .ptr); +} + +test "mdns_query_txt" { + const query = query_txt("my-service._http._tcp"); + try std.testing.expect(query.qtype == .txt); +} + +test "mdns_resolver_new_empty" { + const resolver = resolver_new(); + try std.testing.expect(resolver_count(resolver) == 0); +} + +test "mdns_resolver_add_increments" { + var resolver = resolver_new(); + const service = service_create("service1", "_http._tcp", 8080); + resolver_add_service(&resolver, service); + try std.testing.expect(resolver_count(&resolver) == 1); +} + +test "mdns_resolver_find_service" { + var resolver = resolver_new(); + const service = service_create("service1", "_http._tcp", 8080); + resolver_add_service(&resolver, service); + const found = resolver_find_service(resolver, "service1"); + try std.testing.expect(found != null); +} + +test "mdns_resolver_find_not_found" { + var resolver = resolver_new(); + const service = service_create("service1", "_http._tcp", 8080); + resolver_add_service(&resolver, service); + const found = resolver_find_service(resolver, "service2"); + try std.testing.expect(found == null); +} + +test "mdns_is_ptr_record" { + try std.testing.expect(is_ptr_record(.ptr)); + try std.testing.expect(!is_ptr_record(.srv)); +} + +test "mdns_is_srv_record" { + try std.testing.expect(is_srv_record(.srv)); + try std.testing.expect(!is_srv_record(.txt)); +} + +test "mdns_service_format_name" { + const service = service_create("my-service", "_http._tcp", 8080); + const name = service_format_name(service); + try std.testing.expect(std.mem.eql(name, "my-service._http._tcp")); +} + +test "mdns_service_format_fqdn" { + const service = service_create("my-service", "_http._tcp", 8080); + const fqdn = service_format_fqdn(service); + try std.testing.expect(std.mem.eql(fqdn, "my-service._http._tcp.local")); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant mdns_port_valid { + // MDNS_PORT is valid UDP port + @compileAssert(MDNS_PORT > 1023 and MDNS_PORT < 65536); +} + +invariant mdns_ipv4_is_multicast { + // MDNS_IPV4 is valid multicast address + @compileAssert(MDNS_IPV4.len > 0); +} + +invariant mdns_ipv6_is_multicast { + // MDNS_IPV6 is valid multicast address + @compileAssert(MDNS_IPV6.len > 0); +} + +invariant service_http_is_string { + // SERVICE_HTTP is valid service type + @compileAssert(SERVICE_HTTP.len > 0); +} + +invariant service_lsp_is_string { + // SERVICE_LSP is valid service type + @compileAssert(SERVICE_LSP.len > 0); +} + +invariant query_interval_positive { + // QUERY_INTERVAL_MS is positive + @compileAssert(QUERY_INTERVAL_MS > 0); +} + +invariant announcement_ttl_positive { + // ANNOUNCEMENT_TTL is positive + @compileAssert(ANNOUNCEMENT_TTL > 0); +} + +invariant query_timeout_positive { + // QUERY_TIMEOUT_MS is positive + @compileAssert(QUERY_TIMEOUT_MS > 0); +} + +invariant record_type_enum_valid { + // RecordType enum has valid values + @compileAssert(@intFromEnum(RecordType.a) == 3); +} + +invariant query_type_enum_valid { + // QueryType enum has valid values + @compileAssert(@intFromEnum(QueryType.txt) == 2); +} + +invariant discovery_state_enum_valid { + // DiscoveryState enum has valid values + @compileAssert(@intFromEnum(DiscoveryState.error) == 4); +} + +invariant service_record_complete { + // ServiceRecord has all required fields + @compileAssert(true); +} + +invariant service_has_name { + // ServiceRecord name is string + @compileAssert(true); +} + +invariant service_has_type { + // ServiceRecord type is string + @compileAssert(true); +} + +invariant service_has_port { + // ServiceRecord port is valid port number + @compileAssert(true); +} + +invariant resolver_has_services { + // Resolver has services array + @compileAssert(true); +} + +invariant service_format_includes_domain { + // service_format_fqdn includes .local suffix + @compileAssert(true); +} + +invariant resolver_find_returns_null_when_empty { + // resolver_find_service returns null when service not found + @compileAssert(true); +} + +invariant query_has_type { + // Query has qtype field + @compileAssert(true); +} + +invariant query_has_name { + // Query has name field + @compileAssert(true); +} + +invariant response_has_records { + // Response has records array + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "mdns_service_create_latency" { + // Measure: cycles for service record creation + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : ServiceRecord = undefined; + for (0..1000) |_| { + result = service_create("test-service", "_http._tcp", 8080); + } + _ = result.port; +} + +bench "mdns_service_format_latency" { + // Measure: cycles for service name formatting + // Target: < 40 cycles + @setEvalBranchQuota(10000); + const service = service_create("test", "_http._tcp", 8080); + var result : []u8 = undefined; + for (0..1000) |_| { + result = service_format_name(service); + } + _ = result.len; +} + +bench "mdns_resolver_find_latency" { + // Measure: cycles for service lookup + // Target: < 100 cycles per service + @setEvalBranchQuota(10000); + var resolver = resolver_new(); + const service = service_create("target", "_http._tcp", 8080); + resolver_add_service(&resolver, service); + var result : ?ServiceRecord = undefined; + for (0..1000) |_| { + result = resolver_find_service(resolver, "target"); + } + _ = result != null; +} + +bench "mdns_query_create_latency" { + // Measure: cycles for query creation + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : Query = undefined; + for (0..1000) |_| { + result = query_srv("test._http._tcp"); + } + _ = result.qtype; +} + +bench "mdns_resolver_add_latency" { + // Measure: cycles for adding service + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var resolver = resolver_new(); + const service = service_create("test", "_http._tcp", 8080); + for (0..1000) |_| { + resolver_add_service(&resolver, service); + } + _ = resolver.services.len; +} diff --git a/apps/website/public/t27/specs/server/project.t27 b/apps/website/public/t27/specs/server/project.t27 new file mode 100644 index 0000000000..d7b495e4bc --- /dev/null +++ b/apps/website/public/t27/specs/server/project.t27 @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/server/project.t27 +// Project Management Specification +// Constitutional Law #4: De-Zig-fication - .t27 is source of truth +// Constitutional Law #5: De-Zig Strict - no new Rust business logic + +module Project { + use base::types; + + // ==================================================================== + // Project Structure + // ==================================================================== + + struct Project { + id: str, + name: str, + path: str, + created_at: u64, + updated_at: u64, + file_count: u32, + current: bool, + } + + struct FileEntry { + path: str, + content: str, + modified: bool, + } + + // ==================================================================== + // Project Manager + // ==================================================================== + + struct ProjectManager { + projects: [32]Project, + project_count: u32, + current_project_id: str, + } + + fn project_manager_init() -> ProjectManager { + return ProjectManager{ + projects = [Project{}; 32], + project_count = 0, + current_project_id = "", + }; + } + + // ==================================================================== + // Project Operations + // ==================================================================== + + fn create_project(mgr: *ProjectManager, name: str, path: str) -> Project { + const id = "proj-" ++ name + var project = Project{ + id = id, + name = name, + path = path, + created_at = 0, + updated_at = 0, + file_count = 0, + current = true, + } + if (mgr.project_count < 32) { + mgr.projects[mgr.project_count] = project + mgr.project_count = mgr.project_count + 1 + mgr.current_project_id = id + } + return project + } + + fn get_project(mgr: ProjectManager, id: str) -> Project { + var i: u32 = 0 + while (i < mgr.project_count) { + if (mgr.projects[i].id == id) { + return mgr.projects[i] + } + i = i + 1 + } + return Project{id = "", name = "", path = "", created_at = 0, updated_at = 0, file_count = 0, current = false} + } + + fn get_current_project(mgr: ProjectManager) -> Project { + return get_project(mgr, mgr.current_project_id) + } + + fn set_current_project(mgr: *ProjectManager, id: str) -> bool { + var found: bool = false + var i: u32 = 0 + while (i < mgr.project_count) { + mgr.projects[i].current = (mgr.projects[i].id == id) + if (mgr.projects[i].id == id) { + found = true + mgr.current_project_id = id + } + i = i + 1 + } + return found + } + + fn list_projects(mgr: ProjectManager) -> [32]Project { + return mgr.projects + } + + fn delete_project(mgr: *ProjectManager, id: str) -> bool { + var found: bool = false + var i: u32 = 0 + while (i < mgr.project_count) { + if (mgr.projects[i].id == id) { + found = true + } + if (found and i < mgr.project_count - 1) { + mgr.projects[i] = mgr.projects[i + 1] + } + i = i + 1 + } + if (found) { + mgr.project_count = mgr.project_count - 1 + if (mgr.current_project_id == id) { + mgr.current_project_id = "" + } + } + return found + } + + // ==================================================================== + // File Operations + // ==================================================================== + + fn read_file(path: str) -> str { + return "" + } + + fn write_file(path: str, content: str) -> bool { + return true + } + + fn file_exists(path: str) -> bool { + return false + } + + fn list_files(dir: str) -> [64]str { + return [""; 64] + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "create_project" { + var mgr = project_manager_init() + const project = create_project(&mgr, "test-project", "/workspace/test") + assert(project.id == "proj-test-project") + assert(project.name == "test-project") + assert(project.current == true) + } + + test "get_project" { + var mgr = project_manager_init() + const created = create_project(&mgr, "my-project", "/path") + const retrieved = get_project(mgr, created.id) + assert(retrieved.id == created.id) + } + + test "set_current_project" { + var mgr = project_manager_init() + const p1 = create_project(&mgr, "p1", "/p1") + const p2 = create_project(&mgr, "p2", "/p2") + const result = set_current_project(&mgr, p1.id) + assert(result == true) + assert(mgr.current_project_id == p1.id) + } + + test "list_projects" { + var mgr = project_manager_init() + create_project(&mgr, "proj1", "/1") + create_project(&mgr, "proj2", "/2") + assert(mgr.project_count == 2) + } + + test "delete_project" { + var mgr = project_manager_init() + const project = create_project(&mgr, "to-delete", "/del") + const result = delete_project(&mgr, project.id) + assert(result == true) + assert(mgr.project_count == 0) + } + + test "get_current_project" { + var mgr = project_manager_init() + const project = create_project(&mgr, "current", "/cur") + const current = get_current_project(mgr) + assert(current.id == project.id) + } + + test "project_manager_init" { + const mgr = project_manager_init() + assert(mgr.project_count == 0) + assert(mgr.current_project_id == "") + } + + test "file_operations" { + const exists = file_exists("/nonexistent") + assert(exists == false) + } +} diff --git a/apps/website/public/t27/specs/server/provider.t27 b/apps/website/public/t27/specs/server/provider.t27 new file mode 100644 index 0000000000..0bba419933 --- /dev/null +++ b/apps/website/public/t27/specs/server/provider.t27 @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/server/provider.t27 +// LLM Provider Configuration Specification +// Constitutional Law #4: De-Zig-fication - .t27 is source of truth +// Constitutional Law #5: De-Zig Strict - no new Rust business logic + +module Provider { + use base::types; + + // ==================================================================== + // Provider Types + // ==================================================================== + + enum ProviderType { + OpenAI = 0, + Anthropic = 1, + Google = 2, + Custom = 3, + } + + // ==================================================================== + // Provider Configuration + // ==================================================================== + + struct Provider { + id: str, + name: str, + provider_type: ProviderType, + base_url: str, + api_key: str, + enabled: bool, + default_model: str, + } + + struct Model { + id: str, + provider_id: str, + name: str, + context_window: u32, + max_output_tokens: u32, + supports_vision: bool, + supports_streaming: bool, + } + + // ==================================================================== + // Provider Manager + // ==================================================================== + + struct ProviderManager { + providers: [16]Provider, + provider_count: u32, + default_provider_id: str, + } + + fn provider_manager_init() -> ProviderManager { + return ProviderManager{ + providers = [Provider{}; 16], + provider_count = 0, + default_provider_id = "", + }; + } + + // ==================================================================== + // Provider Operations + // ==================================================================== + + fn add_provider(mgr: *ProviderManager, provider: Provider) -> bool { + if (mgr.provider_count >= 16) { + return false + } + mgr.providers[mgr.provider_count] = provider + mgr.provider_count = mgr.provider_count + 1 + if (mgr.default_provider_id == "") { + mgr.default_provider_id = provider.id + } + return true + } + + fn get_provider(mgr: ProviderManager, id: str) -> Provider { + var i: u32 = 0 + while (i < mgr.provider_count) { + if (mgr.providers[i].id == id) { + return mgr.providers[i] + } + i = i + 1 + } + return Provider{id = "", name = "", provider_type = ProviderType::Custom, base_url = "", api_key = "", enabled = false, default_model = ""} + } + + fn get_default_provider(mgr: ProviderManager) -> Provider { + return get_provider(mgr, mgr.default_provider_id) + } + + fn set_default_provider(mgr: *ProviderManager, id: str) -> bool { + var i: u32 = 0 + while (i < mgr.provider_count) { + if (mgr.providers[i].id == id) { + mgr.default_provider_id = id + return true + } + i = i + 1 + } + return false + } + + fn list_providers(mgr: ProviderManager) -> [16]Provider { + return mgr.providers + } + + fn remove_provider(mgr: *ProviderManager, id: str) -> bool { + var found: bool = false + var i: u32 = 0 + while (i < mgr.provider_count) { + if (mgr.providers[i].id == id) { + found = true + } + if (found and i < mgr.provider_count - 1) { + mgr.providers[i] = mgr.providers[i + 1] + } + i = i + 1 + } + if (found) { + mgr.provider_count = mgr.provider_count - 1 + if (mgr.default_provider_id == id) { + mgr.default_provider_id = "" + } + } + return found + } + + fn enable_provider(mgr: *ProviderManager, id: str, enabled: bool) -> bool { + var i: u32 = 0 + while (i < mgr.provider_count) { + if (mgr.providers[i].id == id) { + mgr.providers[i].enabled = enabled + return true + } + i = i + 1 + } + return false + } + + // ==================================================================== + // Default Providers + // ==================================================================== + + fn create_openai_provider(api_key: str) -> Provider { + return Provider{ + id = "openai", + name = "OpenAI", + provider_type = ProviderType::OpenAI, + base_url = "https://api.openai.com/v1", + api_key = api_key, + enabled = true, + default_model = "gpt-4o", + } + } + + fn create_anthropic_provider(api_key: str) -> Provider { + return Provider{ + id = "anthropic", + name = "Anthropic", + provider_type = ProviderType::Anthropic, + base_url = "https://api.anthropic.com", + api_key = api_key, + enabled = true, + default_model = "claude-3-5-sonnet-20241022", + } + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "provider_type_values" { + assert(ProviderType::OpenAI == 0) + assert(ProviderType::Anthropic == 1) + assert(ProviderType::Google == 2) + assert(ProviderType::Custom == 3) + } + + test "add_provider" { + var mgr = provider_manager_init() + const provider = create_openai_provider("sk-key") + const result = add_provider(&mgr, provider) + assert(result == true) + assert(mgr.provider_count == 1) + } + + test "get_provider" { + var mgr = provider_manager_init() + const provider = create_openai_provider("sk-key") + add_provider(&mgr, provider) + const retrieved = get_provider(mgr, "openai") + assert(retrieved.id == "openai") + assert(retrieved.name == "OpenAI") + } + + test "get_default_provider" { + var mgr = provider_manager_init() + const provider = create_openai_provider("sk-key") + add_provider(&mgr, provider) + const default = get_default_provider(mgr) + assert(default.id == "openai") + } + + test "set_default_provider" { + var mgr = provider_manager_init() + const p1 = create_openai_provider("key1") + const p2 = create_anthropic_provider("key2") + add_provider(&mgr, p1) + add_provider(&mgr, p2) + const result = set_default_provider(&mgr, "anthropic") + assert(result == true) + assert(mgr.default_provider_id == "anthropic") + } + + test "remove_provider" { + var mgr = provider_manager_init() + const provider = create_openai_provider("sk-key") + add_provider(&mgr, provider) + const result = remove_provider(&mgr, "openai") + assert(result == true) + assert(mgr.provider_count == 0) + } + + test "enable_provider" { + var mgr = provider_manager_init() + const provider = create_openai_provider("sk-key") + add_provider(&mgr, provider) + const result = enable_provider(&mgr, "openai", false) + assert(result == true) + const retrieved = get_provider(mgr, "openai") + assert(retrieved.enabled == false) + } + + test "create_openai_provider" { + const provider = create_openai_provider("my-key") + assert(provider.id == "openai") + assert(provider.provider_type == ProviderType::OpenAI) + assert(provider.default_model == "gpt-4o") + } + + test "create_anthropic_provider" { + const provider = create_anthropic_provider("my-key") + assert(provider.id == "anthropic") + assert(provider.provider_type == ProviderType::Anthropic) + assert(provider.default_model == "claude-3-5-sonnet-20241022") + } + + test "provider_manager_init" { + const mgr = provider_manager_init() + assert(mgr.provider_count == 0) + assert(mgr.default_provider_id == "") + } +} diff --git a/apps/website/public/t27/specs/server/router.t27 b/apps/website/public/t27/specs/server/router.t27 new file mode 100644 index 0000000000..0a231c410d --- /dev/null +++ b/apps/website/public/t27/specs/server/router.t27 @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: Apache-2.0 +// router.t27 — HTTP Router Specification +// URL routing, pattern matching, parameter extraction +// φ² + 1/φ² = 3 | TRINITY + +module server-router; + +// ============================================================================ +// Imports +// ============================================================================ + +use std; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Maximum route depth +pub const MAX_ROUTE_DEPTH : u8 = 10; + +/// Maximum URL path length +pub const MAX_PATH_LENGTH : u16 = 4096; + +/// Root path segment +pub const PATH_ROOT : []u8 = "/"; + +/// Route parameter wildcard +pub const PARAM_WILDCARD : u8 = 42; // '*' + +/// Path separator +pub const PATH_SEPARATOR : u8 = 47; // '/' + +// ============================================================================ +// Types +// ============================================================================ + +/// HTTP method for routing +pub const RouteMethod = enum(u8) { + get = 0, + post = 1, + put = 2, + delete = 3, + patch = 4, + options = 5, + any = 6, // Match any method +}; + +/// Route handler signature +pub const Handler = fn([]u8) HttpResponse; + +/// Route parameter +pub const RouteParam = struct { + key : []u8, + value : []u8, +}; + +/// HTTP response (forward declaration) +pub const HttpResponse = struct { + status_code : u16, + body : []u8, +}; + +/// Route definition +pub const Route = struct { + method : RouteMethod, + path : []u8, + handler : Handler, + params : []RouteParam, +}; + +/// Route match result +pub const RouteMatch = struct { + route : *Route, + params : []RouteParam, + matched : bool, +}; + +/// Router state +pub const Router = struct { + routes : []Route, + fallback : ?Handler, +}; + +/// URL path segments +pub const PathSegments = struct { + segments : [][]u8, + count : usize, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create new router +pub fn router_new() Router { + return Router{ + .routes = &[_]Route{}, + .fallback = null, + }; +} + +/// Add route to router +pub fn route_add(router: *Router, method: RouteMethod, path: []u8, handler: Handler) bool { + const route = Route{ + .method = method, + .path = path, + .handler = handler, + .params = &[_]RouteParam{}, + }; + // Append to routes (simplified) + return true; +} + +/// Match request to route +pub fn route_match(router: *Router, method: RouteMethod, path: []u8) RouteMatch { + const segments = path_split(path); + var best_match : *Route = null; + var best_params : []RouteParam = &[_]RouteParam{}; + + for (router.routes) |route| { + if (route.method != method and route.method != .any) { + continue; + } + + const result = match_route(route.path, segments); + if (result.matched) { + best_match = @ptrFrom(*route); + best_params = result.params; + } + } + + return RouteMatch{ + .route = best_match, + .params = best_params, + .matched = best_match != null, + }; +} + +/// Split path into segments +pub fn path_split(path: []u8) PathSegments { + if (path.len == 0 or path[0] != PATH_SEPARATOR) { + return PathSegments{ .segments = &[_][]u8{}, .count = 0 }; + } + + const without_root = path[1..path.len]; + var segments : [][]u8 = &[_][]u8{}; + var start : usize = 0; + + for (0..without_root.len) |i| { + if (without_root[i] == PATH_SEPARATOR or i == without_root.len - 1) { + if (i > start) { + const segment = without_root[start..i]; + segments = append(segments, segment); + } + start = i + 1; + } + } + + return PathSegments{ .segments = segments, .count = segments.len }; +} + +/// Match route pattern against path segments +pub fn match_route(pattern: []u8, segments: PathSegments) MatchResult { + const pattern_segments = path_split(pattern); + + if (pattern_segments.count != segments.count) { + // Check for wildcard match + if (pattern_has_wildcard(pattern)) { + return match_wildcard(pattern_segments, segments); + } + return MatchResult{ .matched = false, .params = &[_]RouteParam{} }; + } + + var params : []RouteParam = &[_]RouteParam{}; + var matched : bool = true; + + for (0..pattern_segments.count) |i| { + const pattern_seg = pattern_segments.segments[i]; + const path_seg = segments.segments[i]; + + if (is_param(pattern_seg)) { + const param_name = param_name_extract(pattern_seg); + const param = RouteParam{ .key = param_name, .value = path_seg }; + params = append(params, param); + } else if (!std.mem.eql(pattern_seg, path_seg)) { + matched = false; + break; + } + } + + return MatchResult{ .matched = matched, .params = params }; +} + +/// Check if pattern has wildcard +pub fn pattern_has_wildcard(pattern: []u8) bool { + return std.mem.indexOfScalar(pattern, PARAM_WILDCARD) < pattern.len; +} + +/// Match wildcard pattern against segments +pub fn match_wildcard(pattern: PathSegments, segments: PathSegments) MatchResult { + const wildcard_idx = find_wildcard_index(pattern.segments); + if (wildcard_idx == null) { + return MatchResult{ .matched = false, .params = &[_]RouteParam{} }; + } + + // Prefix must match + var params : []RouteParam = &[_]RouteParam{}; + var matched : bool = true; + + for (0..wildcard_idx) |i| { + if (!std.mem.eql(pattern.segments[i], segments.segments[i])) { + matched = false; + break; + } + } + + if (matched and segments.count > pattern.count - 1) { + const wildcard_value = join_segments(wildcard_idx + 1, segments); + const param = RouteParam{ .key = "*", .value = wildcard_value }; + params = append(params, param); + } + + return MatchResult{ .matched = matched, .params = params }; +} + +/// Find wildcard index in pattern +pub fn find_wildcard_index(segments: [][]u8) ?usize { + for (0..segments.len) |i| { + if (segments[i].len == 1 and segments[i][0] == PARAM_WILDCARD) { + return i; + } + } + return null; +} + +/// Check if segment is parameter (starts with :) +pub fn is_param(segment: []u8) bool { + return segment.len > 0 and segment[0] == 58; // ':' +} + +/// Extract parameter name from segment +pub fn param_name_extract(segment: []u8) []u8 { + if (segment.len <= 1) { + return ""; + } + return segment[1..segment.len]; +} + +/// Join segments starting from index +pub fn join_segments(start: usize, segments: PathSegments) []u8 { + var result : []u8 = PATH_ROOT; + + for (start..segments.count) |i| { + result = concat(result, segments.segments[i]); + result = concat(result, &[_]u8{PATH_SEPARATOR}); + } + + return result; +} + +/// Concatenate two byte slices +pub fn concat(a: []u8, b: []u8) []u8 { + var result : []u8 = a; + for (b) |byte| { + result = append(result, byte); + } + return result; +} + +/// Append to slice +pub fn append(slice: []u8, item: []u8) []u8 { + var result : []u8 = slice; + for (0..item.len) |i| { + result = concat(result, &[_]u8{item[i]}); + } + return result; +} + +/// Extract parameter value by key +pub fn param_get(params: []RouteParam, key: []u8) []u8 { + for (params) |param| { + if (std.mem.eql(param.key, key)) { + return param.value; + } + } + return ""; +} + +/// Check if path matches pattern +pub fn path_matches(path: []u8, pattern: []u8) bool { + if (std.mem.eql(pattern, "*")) { + return true; + } + if (pattern_has_wildcard(pattern)) { + return prefix_match(path, pattern_without_wildcard(pattern)); + } + return std.mem.eql(path, pattern); +} + +/// Remove wildcard from pattern +pub fn pattern_without_wildcard(pattern: []u8) []u8 { + const wildcard_idx = std.mem.indexOfScalar(pattern, PARAM_WILDCARD); + if (wildcard_idx == pattern.len - 1) { + return pattern[0..wildcard_idx]; + } + return pattern; +} + +/// Check prefix match +pub fn prefix_match(path: []u8, prefix: []u8) bool { + if (prefix.len > path.len) { + return false; + } + for (0..prefix.len) |i| { + if (path[i] != prefix[i]) { + return false; + } + } + return true; +} + +/// Set fallback handler +pub fn router_set_fallback(router: *Router, handler: Handler) void { + router.fallback = handler; +} + +/// Get route count +pub fn router_count(router: *Router) usize { + return router.routes.len; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "router_new_empty" { + const router = router_new(); + try std.testing.expect(router_count(&router) == 0); +} + +test "path_split_root" { + const result = path_split("/"); + try std.testing.expect(result.count == 0); +} + +test "path_split_simple" { + const result = path_split("/api/users"); + try std.testing.expect(result.count == 2); +} + +test "path_split_complex" { + const result = path_split("/api/v1/users/123"); + try std.testing.expect(result.count == 4); +} + +test "is_param_detects_colon" { + try std.testing.expect(is_param(":id")); + try std.testing.expect(!is_param("id")); +} + +test "is_param_detects_not_empty" { + try std.testing.expect(!is_param("")); + try std.testing.expect(!is_param(":")); +} + +test "param_name_extracts_id" { + const result = param_name_extract(":id"); + try std.testing.expect(std.mem.eql(result, "id")); +} + +test "param_name_empty_for_colon_only" { + const result = param_name_extract(":"); + try std.testing.expect(result.len == 0); +} + +test "path_matches_exact" { + try std.testing.expect(path_matches("/users", "/users")); +} + +test "path_matches_wildcard" { + try std.testing.expect(path_matches("/any/path", "/any/*")); +} + +test "path_matches_wildcard_all" { + try std.testing.expect(path_matches("/anything", "*")); +} + +test "prefix_match_true" { + try std.testing.expect(prefix_match("/api/v1/users", "/api/v1")); +} + +test "prefix_match_false_different" { + try std.testing.expect(!prefix_match("/api/v2", "/api/v1")); +} + +test "prefix_match_false_shorter" { + try std.testing.expect(!prefix_match("/api", "/api/v1")); +} + +test "pattern_has_wildcard_true" { + try std.testing.expect(pattern_has_wildcard("/api/*")); +} + +test "pattern_has_wildcard_false" { + try std.testing.expect(!pattern_has_wildcard("/api/users")); +} + +test "param_get_finds_value" { + const params = &[_]RouteParam{ .key = "id", .value = "123" }; + try std.testing.expect(std.mem.eql(param_get(params, "id"), "123")); +} + +test "param_get_empty_not_found" { + const params = &[_]RouteParam{ .key = "name", .value = "test" }; + try std.testing.expect(param_get(params, "id").len == 0); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant route_method_enum_valid { + // RouteMethod enum has valid values + @compileAssert(@intFromEnum(RouteMethod.any) == 6); +} + +invariant max_route_depth_positive { + // MAX_ROUTE_DEPTH is positive + @compileAssert(MAX_ROUTE_DEPTH > 0); +} + +invariant max_path_length_positive { + // MAX_PATH_LENGTH is positive + @compileAssert(MAX_PATH_LENGTH > 0); +} + +invariant param_wildcard_is_asterisk { + // PARAM_WILDCARD is ASCII '*' + @compileAssert(PARAM_WILDCARD == 42); +} + +invariant path_separator_is_slash { + // PATH_SEPARATOR is ASCII '/' + @compileAssert(PATH_SEPARATOR == 47); +} + +invariant path_root_is_slash { + // PATH_ROOT is single slash + @compileAssert(PATH_ROOT.len == 1 and PATH_ROOT[0] == 47); +} + +invariant route_has_method { + // All routes have method defined + @compileAssert(true); +} + +invariant route_has_path { + // All routes have path defined + @compileAssert(true); +} + +invariant route_has_handler { + // All routes have handler defined + @compileAssert(true); +} + +invariant match_result_has_matched_flag { + // RouteMatch has matched boolean + @compileAssert(true); +} + +invariant match_result_has_params { + // RouteMatch has params array + @compileAssert(true); +} + +invariant router_has_routes { + // Router has routes array + @compileAssert(true); +} + +invariant path_segments_preserves_order { + // path_split maintains segment order + @compileAssert(true); +} + +invariant param_key_is_string { + // RouteParam key is string + @compileAssert(true); +} + +invariant param_value_is_string { + // RouteParam value is string + @compileAssert(true); +} + +invariant handler_is_function { + // Handler is function type + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "router_path_split_latency" { + // Measure: cycles for path splitting + // Target: < 100 cycles + @setEvalBranchQuota(10000); + const path = "/api/v1/users/123/posts/456"; + var result : PathSegments = undefined; + for (0..1000) |_| { + result = path_split(path); + } + _ = result.count; +} + +bench "router_match_latency" { + // Measure: cycles for route matching + // Target: < 200 cycles + @setEvalBranchQuota(10000); + const router = router_new(); + const segments = path_split("/api/users"); + var result : RouteMatch = undefined; + for (0..1000) |_| { + result = route_match(&router, .get, "/api/users"); + } + _ = result.matched; +} + +bench "router_param_get_latency" { + // Measure: cycles for parameter lookup + // Target: < 50 cycles + @setEvalBranchQuota(10000); + const params = &[_]RouteParam{ .key = "id", .value = "123" }; + var result : []u8 = undefined; + for (0..1000) |_| { + result = param_get(params, "id"); + } + _ = result.len; +} + +bench "router_path_matches_latency" { + // Measure: cycles for path matching + // Target: < 80 cycles + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..1000) |_| { + result = path_matches("/api/users", "/api/users"); + } + _ = result; +} + +bench "router_prefix_match_latency" { + // Measure: cycles for prefix matching + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : bool = false; + for (0..1000) |_| { + result = prefix_match("/api/v1/users", "/api/v1"); + } + _ = result; +} + +bench "router_param_name_extract_latency" { + // Measure: cycles for parameter name extraction + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = param_name_extract(":userId"); + } + _ = result.len; +} diff --git a/apps/website/public/t27/specs/server/routes.t27 b/apps/website/public/t27/specs/server/routes.t27 new file mode 100644 index 0000000000..df40b7ce63 --- /dev/null +++ b/apps/website/public/t27/specs/server/routes.t27 @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/server/routes.t27 +// T27 Server Routes Specification +// Constitutional Law #4: De-Zig-fication + +module Routes { + use base::types; + + const MAX_ROUTES: u32 = 64; + + enum HttpMethod { + GET = 0, + POST = 1, + PUT = 2, + PATCH = 3, + DELETE = 4, + } + + struct Route { + path: str, + method: HttpMethod, + handler_name: str, + } + + test "http_method_values" { + assert(HttpMethod::GET == 0) + assert(HttpMethod::POST == 1) + assert(HttpMethod::PUT == 2) + assert(HttpMethod::DELETE == 4) + } + + test "constants" { + assert(MAX_ROUTES == 64) + } +} diff --git a/apps/website/public/t27/specs/server/session.t27 b/apps/website/public/t27/specs/server/session.t27 new file mode 100644 index 0000000000..6de258ead3 --- /dev/null +++ b/apps/website/public/t27/specs/server/session.t27 @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/server/session.t27 +// Session Management Specification +// Constitutional Law #4: De-Zig-fication - .t27 is source of truth +// Constitutional Law #5: De-Zig Strict - no new Rust business logic + +module Session { + use base::types; + + // ==================================================================== + // Session States + // ==================================================================== + + enum SessionState { + Pending = 0, + Active = 1, + Completed = 2, + Failed = 3, + } + + // ==================================================================== + // Message Types + // ==================================================================== + + enum MessageRole { + User = 0, + Assistant = 1, + System = 2, + Tool = 3, + } + + // ==================================================================== + // Session Structure + // ==================================================================== + + struct Session { + id: str, + state: SessionState, + created_at: u64, + updated_at: u64, + message_count: u32, + model: str, + provider: str, + } + + struct Message { + id: str, + role: MessageRole, + content: str, + timestamp: u64, + tool_calls: [8]ToolCall, + tool_call_id: str, + } + + struct ToolCall { + name: str, + arguments: str, + } + + // ==================================================================== + // Session Manager + // ==================================================================== + + struct SessionManager { + sessions: [256]Session, + session_count: u32, + active_session_id: str, + } + + fn session_manager_init() -> SessionManager { + return SessionManager{ + sessions = [Session{}; 256], + session_count = 0, + active_session_id = "", + }; + } + + // ==================================================================== + // Session Operations + // ==================================================================== + + fn create_session(mgr: *SessionManager, model: str, provider: str) -> Session { + const id = generate_session_id(mgr.session_count) + var session = Session{ + id = id, + state = SessionState::Active, + created_at = 0, + updated_at = 0, + message_count = 0, + model = model, + provider = provider, + } + if (mgr.session_count < 256) { + mgr.sessions[mgr.session_count] = session + mgr.session_count = mgr.session_count + 1 + mgr.active_session_id = id + } + return session + } + + fn get_session(mgr: SessionManager, id: str) -> Session { + var i: u32 = 0 + while (i < mgr.session_count) { + if (mgr.sessions[i].id == id) { + return mgr.sessions[i] + } + i = i + 1 + } + return Session{id = "", state = SessionState::Pending, created_at = 0, updated_at = 0, message_count = 0, model = "", provider = ""} + } + + fn update_session(mgr: *SessionManager, session: Session) -> bool { + var i: u32 = 0 + while (i < mgr.session_count) { + if (mgr.sessions[i].id == session.id) { + mgr.sessions[i] = session + return true + } + i = i + 1 + } + return false + } + + fn delete_session(mgr: *SessionManager, id: str) -> bool { + var found: bool = false + var i: u32 = 0 + while (i < mgr.session_count) { + if (mgr.sessions[i].id == id) { + found = true + } + if (found and i < mgr.session_count - 1) { + mgr.sessions[i] = mgr.sessions[i + 1] + } + i = i + 1 + } + if (found) { + mgr.session_count = mgr.session_count - 1 + } + return found + } + + fn list_sessions(mgr: SessionManager) -> [256]Session { + return mgr.sessions + } + + // ==================================================================== + // Message Operations + // ==================================================================== + + fn create_message(role: MessageRole, content: str) -> Message { + return Message{ + id = "msg-000", + role = role, + content = content, + timestamp = 0, + tool_calls = [ToolCall{}; 8], + tool_call_id = "", + }; + } + + fn add_message(session: *Session, msg: Message) -> void { + session.message_count = session.message_count + 1 + session.updated_at = session.created_at + session.message_count + } + + // ==================================================================== + // Utilities + // ==================================================================== + + fn generate_session_id(counter: u32) -> str { + return "ses_default" + } + + fn session_to_json(session: Session) -> str { + return "{\"id\": \"session\", \"state\": 1}" + } + + fn message_to_json(msg: Message) -> str { + return "{\"id\": \"message\", \"role\": 0}" + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "session_state_values" { + assert(SessionState::Pending == 0) + assert(SessionState::Active == 1) + assert(SessionState::Completed == 2) + assert(SessionState::Failed == 3) + } + + test "message_role_values" { + assert(MessageRole::User == 0) + assert(MessageRole::Assistant == 1) + assert(MessageRole::System == 2) + assert(MessageRole::Tool == 3) + } + + test "create_session" { + var mgr = session_manager_init() + const session = create_session(&mgr, "gpt-4o", "openai") + assert(session.id == "ses_default") + assert(session.state == SessionState::Active) + assert(session.model == "gpt-4o") + assert(session.provider == "openai") + } + + test "get_session" { + var mgr = session_manager_init() + const created = create_session(&mgr, "claude-3", "anthropic") + const retrieved = get_session(mgr, "ses_default") + assert(retrieved.id == created.id) + } + + test "update_session" { + var mgr = session_manager_init() + const session = create_session(&mgr, "model", "provider") + var updated = session + updated.state = SessionState::Completed + const result = update_session(&mgr, updated) + assert(result == true) + } + + test "delete_session" { + var mgr = session_manager_init() + const session = create_session(&mgr, "model", "provider") + const result = delete_session(&mgr, session.id) + assert(result == true) + assert(mgr.session_count == 0) + } + + test "session_manager_init" { + const mgr = session_manager_init() + assert(mgr.session_count == 0) + assert(mgr.active_session_id == "") + } + + test "create_message" { + const msg = create_message(MessageRole::User, "Hello") + assert(msg.role == MessageRole::User) + assert(msg.content == "Hello") + } + + test "add_message" { + var session = Session{id = "test", state = SessionState::Active, created_at = 0, updated_at = 0, message_count = 0, model = "", provider = ""} + const msg = create_message(MessageRole::User, "Test") + add_message(&session, msg) + assert(session.message_count == 1) + } + + test "session_to_json" { + const session = Session{id = "s1", state = SessionState::Active, created_at = 100, updated_at = 200, message_count = 5, model = "m1", provider = "p1"} + const json = session_to_json(session) + assert(json != "") + } + + test "message_to_json" { + const msg = Message{id = "m1", role = MessageRole::Assistant, content = "Hi", timestamp = 100, tool_calls = [ToolCall{}; 8], tool_call_id = ""} + const json = message_to_json(msg) + assert(json != "") + } + + test "session_count_increment" { + var mgr = session_manager_init() + assert(mgr.session_count == 0) + create_session(&mgr, "model1", "provider1") + assert(mgr.session_count == 1) + create_session(&mgr, "model2", "provider2") + assert(mgr.session_count == 2) + } +} diff --git a/apps/website/public/t27/specs/server/sse.t27 b/apps/website/public/t27/specs/server/sse.t27 new file mode 100644 index 0000000000..cc17a38d14 --- /dev/null +++ b/apps/website/public/t27/specs/server/sse.t27 @@ -0,0 +1,562 @@ +// SPDX-License-Identifier: Apache-2.0 +// sse.t27 — Server-Sent Events Specification +// SSE connections, event streaming, reconnection handling +// φ² + 1/φ² = 3 | TRINITY + +module server-sse; + +// ============================================================================ +// Imports +// ============================================================================ + +use std; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default SSE heartbeat interval in milliseconds +pub const HEARTBEAT_INTERVAL_MS : u32 = 30000; // 30 seconds + +/// Default SSE retry delay in milliseconds +pub const RETRY_DELAY_MS : u32 = 1000; // 1 second + +/// Maximum retry attempts +pub const MAX_RETRIES : u8 = 10; + +/// SSE event prefix +pub const EVENT_PREFIX : [6]u8 = "event:"; + +/// SSE data prefix +pub const DATA_PREFIX : [5]u8 = "data:"; + +/// SSE comment prefix +pub const COMMENT_PREFIX : [8]u8 = "comment:"; + +/// SSE ID field +pub const ID_FIELD : [3]u8 = "id:"; + +/// SSE retry field +pub const RETRY_FIELD : [6]u8 = "retry:"; + +/// SSE event type +pub const SSEEventType = enum(u8) { + message = 0, + status = 1, + error = 2, + keepalive = 3, +}; + +/// SSE connection state +pub const SSEState = enum(u8) { + disconnected = 0, + connecting = 1, + connected = 2, + reconnecting = 3, + closed = 4, +}; + +/// SSE close codes +pub const SSECloseCode = enum(u16) { + normal = 1000, + going_away = 1001, + protocol_error = 1002, + internal_error = 1011, +}; + +// ============================================================================ +// Types +// ============================================================================ + +/// SSE event +pub const SSEEvent = struct { + id : []u8, + event_type : []u8, + data : []u8, + retry : ?u32, +}; + +/// SSE connection +pub const SSEConnection = struct { + state : SSEState, + last_event_id : []u8, + retry_count : u8, + retry_delay_ms : u32, + url : []u8, +}; + +/// SSE client +pub const SSEClient = struct { + connection : SSEConnection, + on_message : fn(SSEEvent) void, + on_error : fn([]u8) void, + on_open : fn() void, + on_close : fn(SSECloseCode, []u8) void, +}; + +/// SSE server +pub const SEServer = struct { + clients : []SSEClient, + heartbeat_interval_ms : u32, +}; + +/// SSE heartbeat event +pub const SSEHeartbeat = struct { + comment : []u8, + timestamp_ms : u64, +}; +// ============================================================================ +// Functions +// ============================================================================ + +/// Create new SSE connection +pub fn connection_new(url: []u8) SSEConnection { + return SSEConnection{ + .state = .disconnected, + .last_event_id = "", + .retry_count = 0, + .retry_delay_ms = RETRY_DELAY_MS, + .url = url, + }; +} + +/// Create new SSE client +pub fn client_new(url: []u8) SSEClient { + return SSEClient{ + .connection = connection_new(url), + .on_message = fn(e: SSEEvent) void { }, + .on_error = fn(msg: []u8) void { }, + .on_open = fn() void { }, + .on_close = fn(code: SSECloseCode, msg: []u8) void { }, + }; +} + +/// Create SSE event +pub fn event_create(event_type: []u8, data: []u8) SSEEvent { + return SSEEvent{ + .id = "", + .event_type = event_type, + .data = data, + .retry = null, + }; +} + +/// Create SSE event with ID +pub fn event_create_with_id(id: []u8, event_type: []u8, data: []u8) SSEEvent { + return SSEEvent{ + .id = id, + .event_type = event_type, + .data = data, + .retry = null, + }; +} + +/// Create SSE heartbeat event +pub fn heartbeat_create(comment: []u8) SSEHeartbeat { + return SSEHeartbeat{ + .comment = comment, + .timestamp_ms = 0, // Would be set at send time + }; +} + +/// Check if connection is active +pub fn connection_is_active(conn: SSEConnection) bool { + return conn.state == .connected; +} + +/// Check if connection can reconnect +pub fn connection_can_reconnect(conn: SSEConnection) bool { + return conn.retry_count < MAX_RETRIES and conn.state == .closed; +} + +/// Increment retry count +pub fn connection_retry_inc(conn: *SSEConnection) void { + conn.retry_count += 1; + conn.retry_delay_ms *= 2; // Exponential backoff +} + +/// Reset retry count +pub fn connection_retry_reset(conn: *SSEConnection) void { + conn.retry_count = 0; + conn.retry_delay_ms = RETRY_DELAY_MS; +} + +/// Get next retry delay +pub fn connection_retry_delay(conn: SSEConnection) u32 { + return conn.retry_delay_ms; +} + +/// Set connection state +pub fn connection_set_state(conn: *SSEConnection, state: SSEState) void { + conn.state = state; +} + +/// Update last event ID +pub fn connection_update_event_id(conn: *SSEConnection, event_id: []u8) void { + conn.last_event_id = event_id; +} + +/// Format SSE event as string +pub fn event_format(event: SSEEvent) []u8 { + var result : []u8 = EVENT_PREFIX; + result = concat(result, " "); + result = concat(result, event.event_type); + result = concat(result, "\n"); + result = concat(result, DATA_PREFIX); + result = concat(result, " "); + result = concat(result, event.data); + + if (event.id.len > 0) { + result = concat(result, "\n"); + result = concat(result, ID_FIELD); + result = concat(result, " "); + result = concat(result, event.id); + } + + if (event.retry != null) { + result = concat(result, "\n"); + result = concat(result, RETRY_FIELD); + result = concat(result, " "); + const retry_val: u32 = @intCast(event.retry.?); + result = concat(result, int_to_string(retry_val)); + } + + return concat(result, "\n\n"); +} + +/// Format SSE heartbeat as string +pub fn heartbeat_format(heartbeat: SSEHeartbeat) []u8 { + var result : []u8 = COMMENT_PREFIX; + result = concat(result, " "); + result = concat(result, heartbeat.comment); + result = concat(result, "\n\n"); + return result; +} + +/// Concatenate two strings +pub fn concat(a: []u8, b: []u8) []u8 { + var result : []u8 = a; + for (b) |byte| { + result = append(result, byte); + } + return result; +} + +/// Append to slice +pub fn append(slice: []u8, byte: u8) []u8 { + var result : []u8 = slice; + result = concat(result, &[_]u8{byte}); + return result; +} + +/// Convert integer to string (decimal) +pub fn int_to_string(value: u32) []u8 { + if (value == 0) { + return "0"; + } + + var result : []u8 = ""; + var remaining : u32 = value; + + while (remaining > 0) { + const digit = remaining % 10; + const char: u8 = @intCast(48 + digit); // '0' = 48 + result = concat(&[_]u8{char}, result); + remaining /= 10; + } + + return result; +} + +/// Add client to server +pub fn server_add_client(server: *SEServer, client: SSEClient) void { + server.clients = append(server.clients, client); +} + +/// Remove client from server +pub fn server_remove_client(server: *SEServer, client_index: usize) void { + // Remove at index (simplified) +} + +/// Send event to all clients +pub fn server_broadcast(server: *SEServer, event: SSEEvent) void { + const formatted = event_format(event); + for (server.clients) |client| { + // Send formatted event to client + } +} + +/// Send heartbeat to all clients +pub fn server_heartbeat(server: *SEServer) void { + const heartbeat = heartbeat_create("keepalive"); + const formatted = heartbeat_format(heartbeat); + for (server.clients) |client| { + // Send heartbeat to client + } +} + +/// Get client count +pub fn server_client_count(server: SEServer) usize { + return server.clients.len; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "sse_connection_new_disconnected" { + const conn = connection_new("http://localhost/events"); + try std.testing.expect(conn.state == .disconnected); + try std.testing.expect(conn.retry_count == 0); +} + +test "sse_client_new_has_connection" { + const client = client_new("http://localhost/events"); + try std.testing.expect(client.connection.state == .disconnected); +} + +test "sse_event_create_has_data" { + const event = event_create("message", "hello"); + try std.testing.expect(std.mem.eql(event.data, "hello")); +} + +test "sse_event_create_with_id" { + const event = event_create_with_id("123", "message", "hello"); + try std.testing.expect(std.mem.eql(event.id, "123")); +} + +test "sse_heartbeat_create" { + const heartbeat = heartbeat_create("keepalive"); + try std.testing.expect(std.mem.eql(heartbeat.comment, "keepalive")); +} + +test "sse_connection_is_active_true" { + var conn = connection_new("http://localhost/events"); + conn.state = .connected; + try std.testing.expect(connection_is_active(conn)); +} + +test "sse_connection_is_active_false" { + var conn = connection_new("http://localhost/events"); + conn.state = .connecting; + try std.testing.expect(!connection_is_active(conn)); +} + +test "sse_connection_can_reconnect_within_limit" { + var conn = connection_new("http://localhost/events"); + conn.state = .closed; + conn.retry_count = 5; + try std.testing.expect(connection_can_reconnect(conn)); +} + +test "sse_connection_can_reconnect_exhausted" { + var conn = connection_new("http://localhost/events"); + conn.state = .closed; + conn.retry_count = MAX_RETRIES; + try std.testing.expect(!connection_can_reconnect(conn)); +} + +test "sse_connection_retry_increments" { + var conn = connection_new("http://localhost/events"); + connection_retry_inc(&conn); + try std.testing.expect(conn.retry_count == 1); + try std.testing.expect(conn.retry_delay_ms == RETRY_DELAY_MS * 2); +} + +test "sse_connection_retry_reset" { + var conn = connection_new("http://localhost/events"); + conn.retry_count = 10; + connection_retry_reset(&conn); + try std.testing.expect(conn.retry_count == 0); + try std.testing.expect(conn.retry_delay_ms == RETRY_DELAY_MS); +} + +test "sse_event_format_basic" { + const event = event_create("message", "hello"); + const formatted = event_format(event); + try std.testing.expect(std.mem.indexOf(formatted, EVENT_PREFIX) < formatted.len); +} + +test "sse_heartbeat_format" { + const heartbeat = heartbeat_create("ping"); + const formatted = heartbeat_format(heartbeat); + try std.testing.expect(std.mem.indexOf(formatted, COMMENT_PREFIX) < formatted.len); +} + +test "sse_int_to_string_zero" { + const result = int_to_string(0); + try std.testing.expect(std.mem.eql(result, "0")); +} + +test "sse_int_to_string_positive" { + const result = int_to_string(42); + try std.testing.expect(std.mem.eql(result, "42")); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant heartbeat_interval_positive { + // HEARTBEAT_INTERVAL_MS is positive + @compileAssert(HEARTBEAT_INTERVAL_MS > 0); +} + +invariant retry_delay_positive { + // RETRY_DELAY_MS is positive + @compileAssert(RETRY_DELAY_MS > 0); +} + +invariant max_retries_positive { + // MAX_RETRIES is positive + @compileAssert(MAX_RETRIES > 0); +} + +invariant event_prefix_is_string { + // EVENT_PREFIX is valid SSE prefix + @compileAssert(EVENT_PREFIX.len > 0); +} + +invariant data_prefix_is_string { + // DATA_PREFIX is valid SSE prefix + @compileAssert(DATA_PREFIX.len > 0); +} + +invariant sse_state_enum_valid { + // SSEState enum has valid values + @compileAssert(@intFromEnum(SSEState.closed) == 4); +} + +invariant sse_event_type_enum_valid { + // SSEEventType enum has valid values + @compileAssert(@intFromEnum(SSEEventType.keepalive) == 3); +} + +invariant connection_has_state { + // SSEConnection has state field + @compileAssert(true); +} + +invariant connection_has_retry_count { + // SSEConnection has retry_count field + @compileAssert(true); +} + +invariant connection_has_url { + // SSEConnection has URL field + @compileAssert(true); +} + +invariant event_has_data { + // SSEEvent has data field + @compileAssert(true); +} + +invariant event_has_type { + // SSEEvent has event_type field + @compileAssert(true); +} + +invariant event_format_contains_prefixes { + // event_format output contains all required prefixes + @compileAssert(true); +} + +invariant heartbeat_format_contains_comment { + // heartbeat_format output contains comment prefix + @compileAssert(true); +} + +invariant server_has_clients { + // SEServer has clients array + @compileAssert(true); +} + +invariant server_has_heartbeat_interval { + // SEServer has heartbeat_interval_ms field + @compileAssert(true); +} + +invariant connection_retry_backoff_doubles { + // Each retry doubles the delay + @compileAssert(true); +} + +invariant connection_retry_reset_clears { + // Reset clears retry count and delay + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "sse_event_create_latency" { + // Measure: cycles for event creation + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : SSEEvent = undefined; + for (0..1000) |_| { + result = event_create("message", "test"); + } + _ = result.data.len; +} + +bench "sse_event_format_latency" { + // Measure: cycles for event formatting + // Target: < 150 cycles + @setEvalBranchQuota(10000); + const event = event_create("message", "test data"); + var result : []u8 = undefined; + for (0..1000) |_| { + result = event_format(event); + } + _ = result.len; +} + +bench "sse_connection_new_latency" { + // Measure: cycles for connection creation + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : SSEConnection = undefined; + for (0..1000) |_| { + result = connection_new("http://localhost/events"); + } + _ = result.state; +} + +bench "sse_connection_check_latency" { + // Measure: cycles for connection state check + // Target: < 10 cycles + @setEvalBranchQuota(10000); + const conn = connection_new("http://localhost/events"); + conn.state = .connected; + var result : bool = false; + for (0..1000) |_| { + result = connection_is_active(conn); + } + _ = result; +} + +bench "sse_heartbeat_format_latency" { + // Measure: cycles for heartbeat formatting + // Target: < 50 cycles + @setEvalBranchQuota(10000); + const heartbeat = heartbeat_create("keepalive"); + var result : []u8 = undefined; + for (0..1000) |_| { + result = heartbeat_format(heartbeat); + } + _ = result.len; +} + +bench "sse_int_to_string_latency" { + // Measure: cycles for int to string conversion + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : []u8 = undefined; + for (0..1000) |_| { + result = int_to_string(42); + } + _ = result.len; +} diff --git a/apps/website/public/t27/specs/server/vm.t27 b/apps/website/public/t27/specs/server/vm.t27 new file mode 100644 index 0000000000..1aa704e6d0 --- /dev/null +++ b/apps/website/public/t27/specs/server/vm.t27 @@ -0,0 +1,592 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/server/vm.t27 +// VSA VM - Ternary Virtual Machine for Hyperdimensional Computing +// phi^2 + 1/phi^2 = 3 | TRINITY + +module VM { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; + use numeric::gf16; + use vsa::vsa_core; + use ternary::hybrid_arithmetic; + + // ======================================================================== + // 1. VSA Opcodes + // ======================================================================== + // + // Virtual machine opcodes for VSA operations: + // - Vector operations: load, store, const, random + // - VSA operations: bind, unbind, bundle, similarity + // - Arithmetic: add, neg, mul + // - Control: mov, pack, unpack, cmp + // - Permute operations for sequence encoding + // ======================================================================== + + pub const VSAOpcode = enum(u8) { + // Vector operations + v_load, + v_store, + v_const, + v_random, + + // VSA operations + v_bind, + v_unbind, + v_bundle2, + v_bundle3, + + // Similarity operations + v_dot, + v_cosine, + v_hamming, + + // Arithmetic + v_add, + v_neg, + v_mul, + + // Control + v_mov, + v_pack, + v_unpack, + v_cmp, + + // Permute + v_permute, + v_ipermute, + v_seq, + + // Special + nop, + halt, + }; + + // ======================================================================== + // 2. VM Registers + // ======================================================================== + // + // VSA Registers provide: + // - 4 vector registers (v0-v3) for HybridBigInt values + // - 2 scalar registers (s0, s1) for i64 results + // - 4 float registers (f0-f3) for f64 similarity results + // - Program counter (pc) for instruction tracking + // - Condition codes (cc_zero, cc_neg, cc_pos) for comparisons + // ======================================================================== + + pub const VSARegisters = struct { + // Vector registers + v0: hybrid_arithmetic::HybridBigInt, + v1: hybrid_arithmetic::HybridBigInt, + v2: hybrid_arithmetic::HybridBigInt, + v3: hybrid_arithmetic::HybridBigInt, + + // Scalar registers + s0: i64, + s1: i64, + + // Float registers + f0: gf16, + f1: gf16, + f2: gf16, + f3: gf16, + + // Program counter + pc: u32, + + // Condition codes + cc_zero: bool, + cc_neg: bool, + cc_pos: bool, + }; + + // ======================================================================== + // 3. VSA Instruction + // ======================================================================== + + pub const VSAInstruction = struct { + opcode: VSAOpcode, + dst: u8, + src1: u8, + src2: u8, + imm: i64, + }; + + // ======================================================================== + // 4. VSA Virtual Machine + // ======================================================================== + // + // The VSA VM executes VSA instructions on HybridBigInt values. + // Supports JIT compilation for accelerated operations. + // Tracks cycle count and memory usage. + // ======================================================================== + + pub const VSAVM = struct { + registers: VSARegisters, + halted: bool, + cycle_count: u64, + jit_enabled: bool, + }; + + // vm_create() -> VSAVM + // Create new VM with default registers + // + // Complexity: O(1) + // ======================================================================== + pub fn vm_create() VSAVM { + const zero_hv = hybrid_arithmetic::HybridBigInt::zero(); + var result : VSAVM = undefined; + result.registers.v0 = zero_hv; + result.registers.v1 = zero_hv; + result.registers.v2 = zero_hv; + result.registers.v3 = zero_hv; + result.registers.s0 = 0; + result.registers.s1 = 0; + result.registers.f0 = gf16::from_f64(0.0); + result.registers.f1 = gf16::from_f64(0.0); + result.registers.f2 = gf16::from_f64(0.0); + result.registers.f3 = gf16::from_f64(0.0); + result.registers.pc = 0; + result.registers.cc_zero = false; + result.registers.cc_neg = false; + result.registers.cc_pos = false; + result.halted = false; + result.cycle_count = 0; + result.jit_enabled = true; + return result; + } + + // vm_halt(vm: VSAVM) -> void + // Halt the VM + // + // Complexity: O(1) + // ======================================================================== + pub fn vm_halt(vm: VSAVM) VSAVM { + var result = vm; + result.halted = true; + return result; + } + + // vm_step(vm: VSAVM, instruction: VSAInstruction) -> VSAVM + // Execute one instruction + // + // Complexity: O(1) for most operations + // ======================================================================== + pub fn vm_step(vm: VSAVM, instruction: VSAInstruction) VSAVM { + var result = vm; + result.cycle_count += 1; + + switch (instruction.opcode) { + .v_load => { + // Load from immediate to dst + result.registers.v0 = hybrid_arithmetic::create_unpacked( + [_]i8{ 1 }, + 1, + hybrid_arithmetic::unpacked_mode + ); + }, + .v_store => { + // Store dst to memory (not implemented in spec) + result.cycle_count += 1; + }, + .v_bind => { + const src1_vec = vm_get_vreg(result, instruction.src1); + const src2_vec = vm_get_vreg(result, instruction.src2); + const binded = vsa_core::bind(src1_vec, src2_vec); + result = vm_set_vreg(result, instruction.dst, binded); + }, + .v_unbind => { + const src1_vec = vm_get_vreg(result, instruction.src1); + const src2_vec = vm_get_vreg(result, instruction.src2); + const unbound = vsa_core::unbind(src1_vec, src2_vec); + result = vm_set_vreg(result, instruction.dst, unbound); + }, + .v_bundle2 => { + const src1_vec = vm_get_vreg(result, instruction.src1); + const src2_vec = vm_get_vreg(result, instruction.src2); + const bundled = vsa_core::bundle2(src1_vec, src2_vec); + result = vm_set_vreg(result, instruction.dst, bundled); + }, + .v_bundle3 => { + const src1_vec = vm_get_vreg(result, instruction.src1); + const src2_vec = vm_get_vreg(result, instruction.src2); + const src3_vec = vm_get_vreg(result, instruction.dst); + const bundled = vsa_core::bundle3(src1_vec, src2_vec, src3_vec); + result = vm_set_vreg(result, instruction.dst, bundled); + }, + .v_dot => { + const src1_vec = vm_get_vreg(result, instruction.src1); + const src2_vec = vm_get_vreg(result, instruction.src2); + const result_scalar = vsa_core::dot_product(src1_vec, src2_vec); + result.registers.s0 = result_scalar; + }, + .v_cosine => { + const src1_vec = vm_get_vreg(result, instruction.src1); + const src2_vec = vm_get_vreg(result, instruction.src2); + const result_scalar = vsa_core::cosine_similarity(src1_vec, src2_vec); + result.registers.f0 = result_scalar; + }, + .v_hamming => { + const src1_vec = vm_get_vreg(result, instruction.src1); + const src2_vec = vm_get_vreg(result, instruction.src2); + const result_scalar = vsa_core::hamming_distance(src1_vec, src2_vec); + result.registers.s1 = @as(i64, @intCast(result_scalar)); + }, + .v_add => { + const src1_vec = vm_get_vreg(result, instruction.src1); + const src2_vec = vm_get_vreg(result, instruction.src2); + const added = hybrid_arithmetic::add(src1_vec, src2_vec); + result = vm_set_vreg(result, instruction.dst, added); + }, + v_neg => { + const src1_vec = vm_get_vreg(result, instruction.src1); + const negated = src1_vec.negate(); + result = vm_set_vreg(result, instruction.dst, negated); + }, + v_mul => { + const src1_vec = vm_get_vreg(result, instruction.src1); + const src2_vec = vm_get_vreg(result, instruction.src2); + const multiplied = vsa_core::mul(src1_vec, src2_vec); + result = vm_set_vreg(result, instruction.dst, multiplied); + }, + .v_mov => { + const src_vec = vm_get_vreg(result, instruction.src1); + result = vm_set_vreg(result, instruction.dst, src_vec); + }, + v_pack => { + const dst_vec = vm_get_vreg(result, instruction.dst); + const packed = dst_vec.pack(); + result = vm_set_vreg(result, instruction.dst, packed); + }, + v_unpack => { + const dst_vec = vm_get_vreg(result, instruction.dst); + const unpacked = dst_vec.unpack(); + result = vm_set_vreg(result, instruction.dst, unpacked); + }, + .v_cmp => { + const src1_vec = vm_get_vreg(result, instruction.src1); + const src2_vec = vm_get_vreg(result, instruction.src2); + const distance = vsa_core::hamming_distance(src1_vec, src2_vec); + result.registers.cc_zero = (distance == 0); + // Update neg/pos based on comparison + // Implementation-specific + }, + .v_permute => { + const src_vec = vm_get_vreg(result, instruction.src1); + const permuted = vsa_core::permute(src_vec, instruction.imm); + result = vm_set_vreg(result, instruction.dst, permuted); + }, + .v_ipermute => { + const src_vec = vm_get_vreg(result, instruction.src1); + const permuted = vsa_core::inverse_permute(src_vec, instruction.imm); + result = vm_set_vreg(result, instruction.dst, permuted); + }, + .nop => { + result.cycle_count += 1; + }, + .halt => { + result.halted = true; + }, + } + + return result; + } + + // ======================================================================== + // 5. Helper Functions + // ======================================================================== + + // vm_get_vreg(vm: VSAVM, reg: u8) -> hybrid_arithmetic::HybridBigInt + // Get vector register by index + // + // Complexity: O(1) + // ======================================================================== + pub fn vm_get_vreg(vm: VSAVM, reg: u8) hybrid_arithmetic::HybridBigInt { + return switch (reg) { + 0 => vm.registers.v0, + 1 => vm.registers.v1, + 2 => vm.registers.v2, + 3 => vm.registers.v3, + else => hybrid_arithmetic::HybridBigInt::zero(), + }; + } + + // vm_set_vreg(vm: VSAVM, reg: u8, value: hybrid_arithmetic::HybridBigInt) -> VSAVM + // Set vector register by index + // + // Complexity: O(1) + // ======================================================================== + pub fn vm_set_vreg(vm: VSAVM, reg: u8, value: hybrid_arithmetic::HybridBigInt) VSAVM { + var result = vm; + switch (reg) { + 0 => { result.registers.v0 = value; }, + 1 => { result.registers.v1 = value; }, + 2 => { result.registers.v2 = value; }, + 3 => { result.registers.v3 = value; }, + else => {}, + } + return result; + } + + // ======================================================================== + // TDD - Tests + // ======================================================================== + + test "vm_create_has_zero_registers" + given vm = vm_create() + then vm.registers.v0.data.get_dimension() == 0 + + test "vm_create_pc_is_zero" + given vm = vm_create() + then vm.registers.pc == 0 + + test "vm_create_not_halted" + given vm = vm_create() + then vm.halted == false + + test "vm_halt_sets_halted" + given vm = vm_create() + when halted = vm_halt(vm) + then halted.halted == true + + test "vm_step_nop_increments_cycle" + given vm = vm_create() + and inst = VSAInstruction{ .opcode = .nop } + when result = vm_step(vm, inst) + then result.cycle_count == 1 + + test "vm_step_halt_halts" + given vm = vm_create() + and inst = VSAInstruction{ .opcode = .halt } + when result = vm_step(vm, inst) + then result.halted == true + + test "vm_step_mov_copies_register" + given vm = vm_create() + and src = hybrid_arithmetic::create_unpacked([_]i8{ 1, 0 }, 2) + and vm_with_src = vm_set_vreg(vm, 0, src) + and inst = VSAInstruction{ .opcode = .v_mov, .src1 = 0, .dst = 1 } + when result = vm_step(vm_with_src, inst) + then result.registers.v1.data == src.data + + test "vm_step_add_adds_vectors" + given a = hybrid_arithmetic::create_unpacked([_]i8{ 1 }, 1) + and b = hybrid_arithmetic::create_unpacked([_]i8{ 1 }, 1) + and vm_with_a = vm_set_vreg(vm_create(), 0, a) + and vm_with_b = vm_set_vreg(vm_with_a, 1, b) + and inst = VSAInstruction{ .opcode = .v_add, .src1 = 0, .src2 = 1, .dst = 2 } + when result = vm_step(vm_with_b, inst) + then result.registers.v2.data.get_dimension() > 0 + + test "vm_step_neg_flips_sign" + given a = hybrid_arithmetic::create_unpacked([_]i8{ 1 }, 1) + and vm_with_a = vm_set_vreg(vm_create(), 0, a) + and inst = VSAInstruction{ .opcode = v_neg, .src1 = 0, .dst = 1 } + when result = vm_step(vm_with_a, inst) + then result.registers.v1.data.get(0) == .neg + + test "vm_step_dot_product_stores_result" + given a = hybrid_arithmetic::create_unpacked([_]i8{ 1 }, 1) + and b = hybrid_arithmetic::create_unpacked([_]i8{ 1 }, 1) + and vm_with_a = vm_set_vreg(vm_create(), 0, a) + and vm_with_b = vm_set_vreg(vm_with_a, 1, b) + and inst = VSAInstruction{ .opcode = .v_dot, .src1 = 0, .src2 = 1 } + when result = vm_step(vm_with_b, inst) + then result.registers.s0 > 0 + + test "vm_get_vreg_0_returns_v0" + given vm = vm_create() + when result = vm_get_vreg(vm, 0) + then result.data == vm.registers.v0.data + + test "vm_get_vreg_1_returns_v1" + given vm = vm_create() + when result = vm_get_vreg(vm, 1) + then result.data == vm.registers.v1.data + + test "vm_get_vreg_invalid_returns_zero" + given vm = vm_create() + when result = vm_get_vreg(vm, 5) + then result.data.get_dimension() == 0 + + test "vm_set_vreg_0_sets_v0" + given vm = vm_create() + and vec = hybrid_arithmetic::create_unpacked([_]i8{ 1 }, 1) + when result = vm_set_vreg(vm, 0, vec) + then result.registers.v0.data == vec.data + + test "vm_set_vreg_1_sets_v1" + given vm = vm_create() + and vec = hybrid_arithmetic::create_unpacked([_]i8{ 1 }, 1) + when result = vm_set_vreg(vm, 1, vec) + then result.registers.v1.data == vec.data + + test "vm_step_permute_shifts" + given a = hybrid_arithmetic::create_unpacked([_]i8{ 0, 1, 0 }, 3) + and vm_with_a = vm_set_vreg(vm_create(), 0, a) + and inst = VSAInstruction{ .opcode = .v_permute, .src1 = 0, .imm = 1, .dst = 1 } + when result = vm_step(vm_with_a, inst) + then result.cycle_count == 1 + + test "vm_step_ipermute_shifts_back" + given a = hybrid_arithmetic::create_unpacked([_]i8{ 0, 1, 0 }, 3) + and vm_with_a = vm_set_vreg(vm_create(), 0, a) + and inst = VSAInstruction{ .opcode = .v_ipermute, .src1 = 0, .imm = 1, .dst = 1 } + when result = vm_step(vm_with_a, inst) + then result.cycle_count == 1 + + test "vm_step_bind_associates" + given a = hybrid_arithmetic::create_unpacked([_]i8{ 1 }, 1) + and b = hybrid_arithmetic::create_unpacked([_]i8{ 0 }, 1) + and vm_with_a = vm_set_vreg(vm_set_vreg(vm_create(), 0, a), 1, b) + and inst = VSAInstruction{ .opcode = .v_bind, .src1 = 0, .src2 = 1, .dst = 2 } + when result = vm_step(vm_with_a, inst) + then result.cycle_count == 1 + + test "vm_step_unbind_reverses_bind" + given a = hybrid_arithmetic::create_unpacked([_]i8{ 1 }, 1) + and b = hybrid_arithmetic::create_unpacked([_]i8{ 0 }, 1) + and vm_with_a = vm_set_vreg(vm_set_vreg(vm_create(), 0, a), 1, b) + and bind_inst = VSAInstruction{ .opcode = .v_bind, .src1 = 0, .src2 = 1, .dst = 2 } + and bound = vm_step(vm_with_a, bind_inst) + and unbind_inst = VSAInstruction{ .opcode = .v_unbind, .src1 = 2, .src2 = 1, .dst = 3 } + when result = vm_step(bound, unbind_inst) + then result.cycle_count == 2 + + test "vm_step_bundle2_combines" + given a = hybrid_arithmetic::create_unpacked([_]i8{ 1 }, 1) + and b = hybrid_arithmetic::create_unpacked([_]i8{ 0 }, 1) + and vm_with_a = vm_set_vreg(vm_set_vreg(vm_create(), 0, a), 1, b) + and inst = VSAInstruction{ .opcode = .v_bundle2, .src1 = 0, .src2 = 1, .dst = 2 } + when result = vm_step(vm_with_a, inst) + then result.cycle_count == 1 + + // ======================================================================== + // TDD - Invariants + // ======================================================================== + + invariant vm_step_preserves_cycle_count + // Each step increments cycle_count by 1 + const vm = vm_create(); + const inst = VSAInstruction{ .opcode = .nop }; + const result = vm_step(vm, inst); + assert result.cycle_count == vm.cycle_count + 1; + + invariant vm_halt_sets_halted_flag + // Halt instruction sets halted flag + const vm = vm_create(); + const inst = VSAInstruction{ .opcode = .halt }; + const result = vm_halt(vm); + assert result.halted == true; + + invariant vm_halt_prevents_execution + // Halted VM doesn't execute instructions + const vm = vm_create(); + const inst = VSAInstruction{ .opcode = .nop }; + const halted = vm_halt(vm); + const result = vm_step(halted, inst); + assert result.halted == true; + + invariant vm_registers_initialized_to_zero + // All registers initialized to zero + const vm = vm_create(); + assert vm.registers.v0.data.get_dimension() == 0; + assert vm.registers.v1.data.get_dimension() == 0; + assert vm.registers.v2.data.get_dimension() == 0; + assert vm.registers.v3.data.get_dimension() == 0; + assert vm.registers.s0 == 0; + assert vm.registers.s1 == 0; + + invariant vm_get_vreg_valid_range + // Valid register indices return valid registers + const vm = vm_create(); + const r0 = vm_get_vreg(vm, 0); + const r1 = vm_get_vreg(vm, 1); + const r2 = vm_get_vreg(vm, 2); + const r3 = vm_get_vreg(vm, 3); + assert r0.data == vm.registers.v0.data; + assert r1.data == vm.registers.v1.data; + assert r2.data == vm.registers.v2.data; + assert r3.data == vm.registers.v3.data; + + invariant vm_get_vreg_invalid_returns_zero + // Invalid register index returns zero vector + const vm = vm_create(); + const r_invalid = vm_get_vreg(vm, 5); + assert r_invalid.data.get_dimension() == 0; + + // ======================================================================== + // TDD - Benchmarks + // ======================================================================== + + bench "vm_create_latency" + // Measure: cycles for VM creation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : VSAVM = undefined; + for (0..1000) |_| { + result = vm_create(); + } + _ = result; + + bench "vm_step_nop_latency" + // Measure: cycles for NOP instruction + // Target: < 10 cycles + @setEvalBranchQuota(10000); + const vm = vm_create(); + const inst = VSAInstruction{ .opcode = .nop }; + var result : VSAVM = undefined; + for (0..1000) |_| { + result = vm_step(vm, inst); + } + _ = result; + + bench "vm_step_mov_latency" + // Measure: cycles for MOV instruction + // Target: < 50 cycles + @setEvalBranchQuota(10000); + const vm = vm_create(); + const inst = VSAInstruction{ .opcode = .v_mov, .src1 = 0, .dst = 1 }; + var result : VSAVM = undefined; + for (0..1000) |_| { + result = vm_step(vm, inst); + } + _ = result; + + bench "vm_step_add_latency" + // Measure: cycles for ADD instruction + // Target: < 200 cycles (vector addition) + @setEvalBranchQuota(10000); + const vm = vm_create(); + const inst = VSAInstruction{ .opcode = .v_add, .src1 = 0, .src2 = 1, .dst = 2 }; + var result : VSAVM = undefined; + for (0..1000) |_| { + result = vm_step(vm, inst); + } + _ = result; + + bench "vm_step_dot_latency" + // Measure: cycles for DOT instruction + // Target: < 200 cycles (dot product) + @setEvalBranchQuota(10000); + const vm = vm_create(); + const inst = VSAInstruction{ .opcode = .v_dot, .src1 = 0, .src2 = 1 }; + var result : VSAVM = undefined; + for (0..1000) |_| { + result = vm_step(vm, inst); + } + _ = result; + + bench "vm_step_bind_latency" + // Measure: cycles for BIND instruction + // Target: < 200 cycles + @setEvalBranchQuota(10000); + const vm = vm_create(); + const inst = VSAInstruction{ .opcode = .v_bind, .src1 = 0, .src2 = 1, .dst = 2 }; + var result : VSAVM = undefined; + for (0..1000) |_| { + result = vm_step(vm, inst); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/shell/environment.t27 b/apps/website/public/t27/specs/shell/environment.t27 new file mode 100644 index 0000000000..fbb66f9a2e --- /dev/null +++ b/apps/website/public/t27/specs/shell/environment.t27 @@ -0,0 +1,303 @@ +// specs/shell/environment.t27 +// Shell Environment Operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module ShellEnvironment { + use base::types; + use shell::schema; + + // ==================================================================== + // Environment Operations + // ==================================================================== + + // get retrieves an environment variable + fn get(name: str) -> Result { + // Implementation: Get environment variable value + } + + // set sets an environment variable + fn set(name: str, value: str) -> Result { + // Implementation: Set environment variable + } + + // unset removes an environment variable + fn unset(name: str) -> Result { + // Implementation: Remove environment variable + } + + // list returns all environment variables + fn list() -> Result<[EnvVar], ShellError> { + // Implementation: Get all environment variables + } + + // expand expands environment variables in a string + fn expand(input: str) -> Result { + // Implementation: Expand $VAR and ${VAR} patterns + } + + // ==================================================================== + // Path Operations + // ==================================================================== + + // PATH is the PATH environment variable name + const PATH: str = "PATH"; + + // get_path returns the PATH environment variable + fn get_path() -> Result { + // Implementation: Get PATH value + } + + // set_path sets the PATH environment variable + fn set_path(value: str) -> Result { + // Implementation: Set PATH value + } + + // path_append appends a directory to PATH + fn path_append(directory: str, position: str?) -> Result { + // Implementation: Append to PATH (position: "front" or "back") + } + + // path_remove removes a directory from PATH + fn path_remove(directory: str) -> Result { + // Implementation: Remove from PATH + } + + // path_list returns all directories in PATH + fn path_list() -> Result<[str], ShellError> { + // Implementation: Split PATH into directories + } + + // path_which finds the executable in PATH + fn path_which(executable: str) -> Result { + // Implementation: Find executable in PATH + } + + // ==================================================================== + // Shell Selection Operations + // ==================================================================== + + // detect detects the current shell + fn detect() -> Result { + // Implementation: Detect current shell from env + } + + // find finds a shell by name + fn find(name: str) -> Result { + // Implementation: Search for shell in system + } + + // select selects the best available shell + fn select(preferred: str?) -> Result { + // Implementation: Select shell, preferring preferred + } + + // fallback returns the fallback shell for the platform + fn fallback(platform: Platform) -> Result { + // Implementation: Return platform-specific fallback + } + + // ==================================================================== + // Home Directory Operations + // ==================================================================== + + // HOME is the HOME environment variable name + const HOME: str = "HOME"; + + // get_home returns the home directory + fn get_home() -> Result { + // Implementation: Get HOME directory + } + + // USER is the USER environment variable name + const USER: str = "USER"; + + // get_user returns the current user + fn get_user() -> Result { + // Implementation: Get current user name + } + + // ==================================================================== + // Platform Detection + // ==================================================================== + + // platform returns the current platform + fn platform() -> Platform { + // Implementation: Detect current platform + } + + // is_windows checks if running on Windows + fn is_windows() -> bool { + // Implementation: Check for Windows + } + + // is_darwin checks if running on macOS + fn is_darwin() -> bool { + // Implementation: Check for macOS + } + + // is_linux checks if running on Linux + fn is_linux() -> bool { + // Implementation: Check for Linux + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "constants_values" { + assert(PATH == "PATH"); + assert(HOME == "HOME"); + assert(USER == "USER"); + } + + test "env_var_creation" { + var env = EnvVar { + name = "TEST_VAR", + value = "test_value", + }; + assert(env.name == "TEST_VAR"); + assert(env.value == "test_value"); + } + + test "platform_values" { + assert(Platform::Darwin as u32 == 0); + assert(Platform::Linux as u32 == 1); + assert(Platform::Windows as u32 == 2); + } + + test "shell_properties_full" { + var props = ShellProperties { + path = "/bin/bash", + name = "bash", + shellType = ShellType::Bash, + isLogin = true, + isPosix = true, + isBlacklisted = false, + }; + assert(props.path == "/bin/bash"); + assert(props.shellType == ShellType::Bash); + } + + test "shell_properties_minimal" { + var props = ShellProperties { + path = "/usr/bin/zsh", + name = "zsh", + shellType = ShellType::Zsh, + isLogin = false, + isPosix = true, + isBlacklisted = false, + }; + assert(!props.isLogin); + assert(props.isPosix); + } + + test "shell_properties_blacklisted" { + var props = ShellProperties { + path = "/usr/local/bin/fish", + name = "fish", + shellType = ShellType::Fish, + isLogin = true, + isPosix = false, + isBlacklisted = true, + }; + assert(props.isBlacklisted); + assert(!props.isPosix); + } + + test "shell_error_values" { + assert(ShellError::NotFound as u32 == 0); + assert(ShellError::PermissionDenied as u32 == 1); + assert(ShellError::Timeout as u32 == 2); + assert(ShellError::Signal as u32 == 3); + assert(ShellError::InvalidCommand as u32 == 4); + assert(ShellError::ProcessError as u32 == 5); + assert(ShellError::EnvironmentError as u32 == 6); + } + + test "shell_type_values" { + assert(ShellType::Bash as u32 == 0); + assert(ShellType::Zsh as u32 == 1); + assert(ShellType::Fish as u32 == 2); + assert(ShellType::Dash as u32 == 3); + assert(ShellType::Sh as u32 == 4); + assert(ShellType::Pwsh as u32 == 5); + assert(ShellType::PowerShell as u32 == 6); + assert(ShellType::Cmd as u32 == 7); + assert(ShellType::Unknown as u32 == 8); + } + + test "process_status_values" { + assert(ProcessStatus::Running as u32 == 0); + assert(ProcessStatus::Exited as u32 == 1); + assert(ProcessStatus::Signaled as u32 == 2); + assert(ProcessStatus::Stopped as u32 == 3); + } + + test "shell_type_posix" { + assert(ShellType::Bash.is_posix); + assert(ShellType::Zsh.is_posix); + assert(ShellType::Dash.is_posix); + assert(ShellType::Sh.is_posix); + } + + test "shell_type_non_posix" { + assert(!ShellType::Fish.is_posix); + assert(!ShellType::Pwsh.is_posix); + assert(!ShellType::PowerShell.is_posix); + assert(!ShellType::Cmd.is_posix); + } + + test "shell_type_login" { + assert(ShellType::Bash.is_login); + assert(ShellType::Zsh.is_login); + assert(ShellType::Fish.is_login); + assert(ShellType::Dash.is_login); + assert(ShellType::Sh.is_login); + } + + test "shell_type_non_login" { + assert(!ShellType::Pwsh.is_login); + assert(!ShellType::PowerShell.is_login); + assert(!ShellType::Cmd.is_login); + } + + test "shell_type_blacklisted" { + assert(ShellType::Fish.is_blacklisted); + } + + test "shell_type_not_blacklisted" { + assert(!ShellType::Bash.is_blacklisted); + assert(!ShellType::Zsh.is_blacklisted); + assert(!ShellType::Sh.is_blacklisted); + } + + test "shell_properties_powershell" { + var props = ShellProperties { + path = "C:\\Windows\\System32\\powershell.exe", + name = "powershell", + shellType = ShellType::PowerShell, + isLogin = false, + isPosix = false, + isBlacklisted = false, + }; + assert(props.shellType == ShellType::PowerShell); + assert(!props.isPosix); + } + + test "env_var_empty_value" { + var env = EnvVar { + name = "EMPTY", + value = "", + }; + assert(env.name == "EMPTY"); + assert(env.value == ""); + } + + test "env_var_with_spaces" { + var env = EnvVar { + name = "WITH_SPACES", + value = "value with spaces", + }; + assert(env.value == "value with spaces"); + } +} diff --git a/apps/website/public/t27/specs/shell/process.t27 b/apps/website/public/t27/specs/shell/process.t27 new file mode 100644 index 0000000000..d5759dc172 --- /dev/null +++ b/apps/website/public/t27/specs/shell/process.t27 @@ -0,0 +1,304 @@ +// specs/shell/process.t27 +// Shell Process Operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module ShellProcess { + use base::types; + use shell::schema; + + // ==================================================================== + // Process ID Type + // ==================================================================== + + // ProcessID is a branded string representing a process identifier + struct ProcessID(str); + + // ==================================================================== + // Process Operations + // ==================================================================== + + // spawn spawns a new process + fn spawn(command: str, args: [str], opts: ProcessOptions) -> Result { + // Implementation: Spawn process and return ID + } + + // spawn_shell spawns a shell command + fn spawn_shell(command: str, opts: ProcessOptions) -> Result { + // Implementation: Execute command in shell and return result + } + + // kill terminates a process + fn kill(pid: ProcessID, signal: str?) -> Result { + // Implementation: Send signal to process + } + + // kill_tree terminates a process tree + fn kill_tree(pid: ProcessID) -> Result { + // Implementation: Terminate process and all children + } + + // wait waits for a process to complete + fn wait(pid: ProcessID) -> Result { + // Implementation: Wait for process and return result + } + + // poll checks process status without blocking + fn poll(pid: ProcessID) -> Result { + // Implementation: Get current process info + } + + // is_alive checks if a process is still running + fn is_alive(pid: ProcessID) -> Result { + // Implementation: Check if process is alive + } + + // ==================================================================== + // Process Pool Operations + // ==================================================================== + + // Pool represents a pool of processes + struct Pool { + processes: [ProcessID], + maxSize: u32, + } + + // pool_create creates a new process pool + fn pool_create(maxSize: u32) -> Result { + // Implementation: Create process pool + } + + // pool_spawn spawns a process in the pool + fn pool_spawn(pool: Pool, command: str, args: [str], opts: ProcessOptions) -> Result { + // Implementation: Spawn in pool with size check + } + + // pool_wait_all waits for all processes in pool to complete + fn pool_wait_all(pool: Pool) -> Result<[ProcessResult], ShellError> { + // Implementation: Wait for all pool processes + } + + // pool_cleanup removes completed processes from pool + fn pool_cleanup(pool: Pool) -> Result { + // Implementation: Clean up completed processes + } + + // ==================================================================== + // Signal Operations + // ==================================================================== + + // Signal represents a signal that can be sent to a process + enum Signal { + SIGTERM = 0, + SIGKILL = 1, + SIGINT = 2, + SIGHUP = 3, + SIGSTOP = 4, + SIGCONT = 5, + } + + // send_signal sends a signal to a process + fn send_signal(pid: ProcessID, signal: Signal) -> Result { + // Implementation: Send signal to process + } + + // signal_name returns the name of a signal + fn signal_name(signal: Signal) -> str { + // Implementation: Return signal name + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "process_id_creation" { + var pid = ProcessID("12345"); + assert(pid.0 == "12345"); + } + + test "pool_creation" { + var pool = Pool { + processes = [], + maxSize = 10, + }; + assert(pool.maxSize == 10); + assert(pool.processes.len == 0); + } + + test "pool_with_processes" { + var pool = Pool { + processes = [ + ProcessID("1"), + ProcessID("2"), + ProcessID("3"), + ], + maxSize = 10, + }; + assert(pool.processes.len == 3); + } + + test "pool_full" { + var pool = Pool { + processes = [ + ProcessID("1"), + ProcessID("2"), + ProcessID("3"), + ProcessID("4"), + ProcessID("5"), + ], + maxSize = 5, + }; + assert(pool.processes.len == pool.maxSize); + } + + test "signal_values" { + assert(Signal::SIGTERM as u32 == 0); + assert(Signal::SIGKILL as u32 == 1); + assert(Signal::SIGINT as u32 == 2); + assert(Signal::SIGHUP as u32 == 3); + assert(Signal::SIGSTOP as u32 == 4); + assert(Signal::SIGCONT as u32 == 5); + } + + test "process_options_no_capture" { + var opts = ProcessOptions { + cwd = null, + env = null, + timeout = null, + stdin = null, + captureOutput = false, + abort = null, + }; + assert(!opts.captureOutput); + } + + test "process_options_with_env" { + var env: [str: str] = { "TEST": "value" }; + var opts = ProcessOptions { + cwd = "/test", + env = env, + timeout = 1000, + stdin = "test input", + captureOutput = true, + abort = false, + }; + assert(opts.env?.["TEST"] == "value"); + assert(opts.cwd == "/test"); + } + + test "process_info_running" { + var info = ProcessInfo { + pid = 1000, + command = "sleep 10", + status = ProcessStatus::Running, + exitCode = null, + signal = null, + startTime = 1234567890, + endTime = null, + }; + assert(info.status == ProcessStatus::Running); + assert(info.exitCode == null); + } + + test "process_info_signaled" { + var info = ProcessInfo { + pid = 1001, + command = "long-task", + status = ProcessStatus::Signaled, + exitCode = null, + signal = 9, + startTime = 1234567890, + endTime = 1234567950, + }; + assert(info.status == ProcessStatus::Signaled); + assert(info.signal == 9); + } + + test "process_info_stopped" { + var info = ProcessInfo { + pid = 1002, + command = "pause-task", + status = ProcessStatus::Stopped, + exitCode = null, + signal = 19, + startTime = 1234567890, + endTime = null, + }; + assert(info.status == ProcessStatus::Stopped); + assert(info.signal == 19); + } + + test "process_result_zero_duration" { + var result = ProcessResult { + exitCode = 0, + stdout: "quick", + stderr: "", + success = true, + timedOut = false, + duration = 0, + }; + assert(result.duration == 0); + } + + test "process_result_large_duration" { + var result = ProcessResult { + exitCode = 0, + stdout: "slow output", + stderr: "", + success = true, + timedOut = false, + duration = 10000, + }; + assert(result.duration == 10000); + } + + test "process_result_with_stderr" { + var result = ProcessResult { + exitCode = 1, + stdout: "some output", + stderr: "error message", + success = false, + timedOut = false, + duration = 500, + }; + assert(!result.success); + assert(result.stderr == "error message"); + } + + test "process_id_numeric" { + var pid = ProcessID("99999"); + assert(pid.0 == "99999"); + } + + test "pool_empty" { + var pool = Pool { + processes = [], + maxSize = 5, + }; + assert(pool.processes.len == 0); + assert(pool.maxSize == 5); + } + + test "process_options_no_stdin" { + var opts = ProcessOptions { + cwd = null, + env = null, + timeout = null, + stdin = null, + captureOutput = true, + abort = null, + }; + assert(opts.stdin == null); + } + + test "process_options_no_timeout" { + var opts = ProcessOptions { + cwd = null, + env = null, + timeout = null, + stdin = null, + captureOutput = true, + abort = null, + }; + assert(opts.timeout == null); + } +} diff --git a/apps/website/public/t27/specs/shell/schema.t27 b/apps/website/public/t27/specs/shell/schema.t27 new file mode 100644 index 0000000000..073d147421 --- /dev/null +++ b/apps/website/public/t27/specs/shell/schema.t27 @@ -0,0 +1,405 @@ +// specs/shell/schema.t27 +// Shell Types Specification +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Shell { + use base::types; + + // ==================================================================== + // Shell Types + // ==================================================================== + + // ShellType represents the type of shell + enum ShellType { + Bash = 0, + Zsh = 1, + Fish = 2, + Dash = 3, + Sh = 4, + Pwsh = 5, + PowerShell = 6, + Cmd = 7, + Unknown = 8, + } + + // Platform represents the operating system platform + enum Platform { + Darwin = 0, + Linux = 1, + Windows = 2, + } + + // ==================================================================== + // Process Types + // ==================================================================== + + // ProcessStatus represents the status of a process + enum ProcessStatus { + Running = 0, + Exited = 1, + Signaled = 2, + Stopped = 3, + } + + // ProcessInfo represents information about a process + struct ProcessInfo { + pid: u32, // Process ID + command: str, // Command that started the process + status: ProcessStatus, // Current status + exitCode: i32?, // Exit code if exited + signal: u32?, // Signal that terminated process + startTime: u64, // When the process started + endTime: u64?, // When the process ended + } + + // ProcessOptions represents options for spawning a process + struct ProcessOptions { + cwd: str?, // Working directory + env: [str: str]?, // Environment variables + timeout: u64?, // Timeout in milliseconds + stdin: str?, // Input to send to stdin + captureOutput: bool, // Whether to capture stdout/stderr + abort: bool?, // Whether abort signal should terminate + } + + // ProcessResult represents the result of a process + struct ProcessResult { + exitCode: i32, // Exit code + stdout: str, // Standard output + stderr: str, // Standard error + success: bool, // Whether the process succeeded + timedOut: bool, // Whether the process timed out + duration: u64, // Duration in milliseconds + } + + // ==================================================================== + // Shell Properties + // ==================================================================== + + // ShellProperties represents properties of a shell + struct ShellProperties { + path: str, // Full path to the shell + name: str, // Shell name + shellType: ShellType, // Type of shell + isLogin: bool, // Whether it's a login shell + isPosix: bool, // Whether it's POSIX compliant + isBlacklisted: bool, // Whether it's blacklisted + } + + // ==================================================================== + // Environment Types + // ==================================================================== + + // EnvVar represents an environment variable + struct EnvVar { + name: str, // Variable name + value: str, // Variable value + } + + // ==================================================================== + // Constants + // ==================================================================== + + const SIGKILL_TIMEOUT_MS: u32 = 200; + const SHELL_ENV_VAR: str = "SHELL"; + + // ==================================================================== + // Error Types + // ==================================================================== + + // ShellError represents errors in shell operations + enum ShellError { + NotFound = 0, + PermissionDenied = 1, + Timeout = 2, + Signal = 3, + InvalidCommand = 4, + ProcessError = 5, + EnvironmentError = 6, + } + + // ==================================================================== + // Helper Functions + // ==================================================================== + + // is_posix checks if a shell type is POSIX compliant + fn is_posix(shellType: ShellType) -> bool { + return shellType == ShellType::Bash + || shellType == ShellType::Zsh + || shellType == ShellType::Dash + || shellType == ShellType::Sh; + } + + // is_login checks if a shell type is typically a login shell + fn is_login(shellType: ShellType) -> bool { + return shellType == ShellType::Bash + || shellType == ShellType::Zsh + || shellType == ShellType::Fish + || shellType == ShellType::Dash + || shellType == ShellType::Sh; + } + + // is_blacklisted checks if a shell is blacklisted + fn is_blacklisted(shellType: ShellType) -> bool { + return shellType == ShellType::Fish || shellType == ShellType::Nu; + } + + // is_success checks if a process result indicates success + fn is_success(result: ProcessResult) -> bool { + return result.exitCode == 0 && !result.timedOut; + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "shell_type_values" { + assert(ShellType::Bash as u32 == 0); + assert(ShellType::Zsh as u32 == 1); + assert(ShellType::Fish as u32 == 2); + assert(ShellType::Dash as u32 == 3); + assert(ShellType::Sh as u32 == 4); + assert(ShellType::Pwsh as u32 == 5); + assert(ShellType::PowerShell as u32 == 6); + assert(ShellType::Cmd as u32 == 7); + assert(ShellType::Unknown as u32 == 8); + } + + test "platform_values" { + assert(Platform::Darwin as u32 == 0); + assert(Platform::Linux as u32 == 1); + assert(Platform::Windows as u32 == 2); + } + + test "process_status_values" { + assert(ProcessStatus::Running as u32 == 0); + assert(ProcessStatus::Exited as u32 == 1); + assert(ProcessStatus::Signaled as u32 == 2); + assert(ProcessStatus::Stopped as u32 == 3); + } + + test "shell_error_values" { + assert(ShellError::NotFound as u32 == 0); + assert(ShellError::PermissionDenied as u32 == 1); + assert(ShellError::Timeout as u32 == 2); + assert(ShellError::Signal as u32 == 3); + assert(ShellError::InvalidCommand as u32 == 4); + assert(ShellError::ProcessError as u32 == 5); + assert(ShellError::EnvironmentError as u32 == 6); + } + + test "process_info_creation" { + var info = ProcessInfo { + pid = 12345, + command = "ls -la", + status = ProcessStatus::Running, + exitCode = null, + signal = null, + startTime = 1234567890, + endTime = null, + }; + assert(info.pid == 12345); + assert(info.command == "ls -la"); + assert(info.status == ProcessStatus::Running); + } + + test "process_info_exited" { + var info = ProcessInfo { + pid = 12345, + command = "ls -la", + status = ProcessStatus::Exited, + exitCode = 0, + signal = null, + startTime = 1234567890, + endTime = 1234567900, + }; + assert(info.status == ProcessStatus::Exited); + assert(info.exitCode == 0); + assert(info.endTime == 1234567900); + } + + test "process_options_default" { + var opts = ProcessOptions { + cwd = null, + env = null, + timeout = null, + stdin = null, + captureOutput = true, + abort = null, + }; + assert(opts.captureOutput); + assert(opts.cwd == null); + } + + test "process_options_full" { + var env: [str: str] = { "PATH": "/usr/bin", "HOME": "/home/user" }; + var opts = ProcessOptions { + cwd = "/tmp", + env = env, + timeout = 5000, + stdin = "input", + captureOutput = false, + abort = true, + }; + assert(opts.cwd == "/tmp"); + assert(opts.timeout == 5000); + assert(opts.stdin == "input"); + } + + test "process_result_success" { + var result = ProcessResult { + exitCode = 0, + stdout = "output", + stderr = "", + success = true, + timedOut = false, + duration = 100, + }; + assert(result.success); + assert(!result.timedOut); + assert(result.exitCode == 0); + } + + test "process_result_failure" { + var result = ProcessResult { + exitCode = 1, + stdout = "", + stderr = "error", + success = false, + timedOut = false, + duration = 50, + }; + assert(!result.success); + assert(result.exitCode == 1); + } + + test "process_result_timeout" { + var result = ProcessResult { + exitCode = 124, + stdout = "", + stderr = "timeout", + success = false, + timedOut = true, + duration = 5000, + }; + assert(result.timedOut); + assert(result.exitCode == 124); + } + + test "shell_properties_bash" { + var props = ShellProperties { + path = "/bin/bash", + name = "bash", + shellType = ShellType::Bash, + isLogin = true, + isPosix = true, + isBlacklisted = false, + }; + assert(props.shellType == ShellType::Bash); + assert(props.isPosix); + assert(props.isLogin); + assert(!props.isBlacklisted); + } + + test "shell_properties_fish" { + var props = ShellProperties { + path = "/usr/local/bin/fish", + name = "fish", + shellType = ShellType::Fish, + isLogin = true, + isPosix = false, + isBlacklisted = true, + }; + assert(props.shellType == ShellType::Fish); + assert(!props.isPosix); + assert(props.isBlacklisted); + } + + test "env_var_creation" { + var envVar = EnvVar { + name = "PATH", + value = "/usr/bin:/bin", + }; + assert(envVar.name == "PATH"); + assert(envVar.value == "/usr/bin:/bin"); + } + + test "constants_values" { + assert(SIGKILL_TIMEOUT_MS == 200); + assert(SHELL_ENV_VAR == "SHELL"); + } + + test "is_posix_true" { + assert(is_posix(ShellType::Bash)); + assert(is_posix(ShellType::Zsh)); + assert(is_posix(ShellType::Dash)); + assert(is_posix(ShellType::Sh)); + } + + test "is_posix_false" { + assert(!is_posix(ShellType::Fish)); + assert(!is_posix(ShellType::Pwsh)); + assert(!is_posix(ShellType::PowerShell)); + assert(!is_posix(ShellType::Cmd)); + } + + test "is_login_true" { + assert(is_login(ShellType::Bash)); + assert(is_login(ShellType::Zsh)); + assert(is_login(ShellType::Fish)); + assert(is_login(ShellType::Dash)); + assert(is_login(ShellType::Sh)); + } + + test "is_login_false" { + assert(!is_login(ShellType::Pwsh)); + assert(!is_login(ShellType::PowerShell)); + assert(!is_login(ShellType::Cmd)); + } + + test "is_blacklisted_true" { + assert(is_blacklisted(ShellType::Fish)); + } + + test "is_blacklisted_false" { + assert(!is_blacklisted(ShellType::Bash)); + assert(!is_blacklisted(ShellType::Zsh)); + assert(!is_blacklisted(ShellType::Sh)); + } + + test "is_success_true" { + var result = ProcessResult { + exitCode = 0, + stdout = "", + stderr = "", + success = true, + timedOut = false, + duration = 10, + }; + assert(is_success(result)); + } + + test "is_success_false_exit_code" { + var result = ProcessResult { + exitCode = 1, + stdout = "", + stderr = "", + success = false, + timedOut = false, + duration = 10, + }; + assert(!is_success(result)); + } + + test "is_success_false_timeout" { + var result = ProcessResult { + exitCode = 0, + stdout = "", + stderr = "", + success = false, + timedOut = true, + duration = 5000, + }; + assert(!is_success(result)); + } +} diff --git a/apps/website/public/t27/specs/storage/kv.t27 b/apps/website/public/t27/specs/storage/kv.t27 new file mode 100644 index 0000000000..8cb8b604d6 --- /dev/null +++ b/apps/website/public/t27/specs/storage/kv.t27 @@ -0,0 +1,158 @@ +// specs/storage/kv.t27 +// Key-Value Storage Operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module StorageKv { + use base::types; + use storage::schema; + + // ==================================================================== + // KV Operations + // ==================================================================== + + // read retrieves a value from storage by key + // Returns Error if the key does not exist + fn read(key: [str]) -> Result { + // Implementation: Acquire read lock, read file, parse JSON + // Returns NotFoundError if key does not exist + } + + // write stores a value at the given key + // Overwrites existing values + fn write(key: [str], value: T) -> Result { + // Implementation: Acquire write lock, serialize to JSON, write file + } + + // update applies a function to an existing value + // Returns NotFoundError if key does not exist + fn update(key: [str], fn: (T) -> void) -> Result { + // Implementation: Acquire write lock, read, apply function, write back + } + + // remove deletes a value from storage + // No-op if key does not exist + fn remove(key: [str]) -> Result { + // Implementation: Acquire write lock, delete file + } + + // list returns all keys with the given prefix + // Returns empty list if prefix does not exist + fn list(prefix: [str]) -> Result<[[str]], StorageError> { + // Implementation: Glob files matching prefix pattern + } + + // exists checks if a key exists in storage + fn exists(key: [str]) -> Result { + // Implementation: Check file existence + } + + // ==================================================================== + // Bulk Operations + // ==================================================================== + + // batch_write writes multiple values atomically + fn batch_write(entries: [(key: [str], value: T)]) -> Result { + // Implementation: Write all entries in a transaction + } + + // batch_read reads multiple values efficiently + fn batch_read(keys: [[str]]) -> Result<[T?], StorageError> { + // Implementation: Read all keys, return null for missing keys + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "read_missing_key_returns_error" { + // Reading a non-existent key should return NotFoundError + var result = read(["nonexistent", "key"]); + assert(result.is_err()); + } + + test "write_and_read_roundtrip" { + // Writing a value and reading it back should return the same value + var key = ["test", "roundtrip"]; + var value = "hello world"; + _ = write(key, value); + var result = read(key); + assert(result.is_ok()); + assert(result.unwrap() == value); + } + + test "write_overwrites_existing" { + // Writing to an existing key should overwrite the value + var key = ["test", "overwrite"]; + _ = write(key, "first"); + _ = write(key, "second"); + var result = read(key); + assert(result.is_ok()); + assert(result.unwrap() == "second"); + } + + test "update_applies_function" { + // Update should apply function to existing value + var key = ["test", "update"]; + _ = write(key, 10); + _ = update(key, |x| x + 1); + var result = read(key); + assert(result.is_ok()); + assert(result.unwrap() == 11); + } + + test "update_missing_key_returns_error" { + // Updating a non-existent key should return NotFoundError + var result = update(["missing"], |x| x + 1); + assert(result.is_err()); + } + + test "remove_deletes_key" { + // Remove should delete the key from storage + var key = ["test", "remove"]; + _ = write(key, "value"); + _ = remove(key); + var result = read(key); + assert(result.is_err()); + } + + test "remove_nonexistent_is_noop" { + // Removing a non-existent key should not error + var result = remove(["does", "not", "exist"]); + assert(result.is_ok()); + } + + test "list_returns_matching_keys" { + // List should return all keys with the given prefix + var prefix = ["test", "list"]; + _ = write(["test", "list", "a"], "value a"); + _ = write(["test", "list", "b"], "value b"); + _ = write(["test", "other", "c"], "value c"); + var result = list(prefix); + assert(result.is_ok()); + var keys = result.unwrap(); + assert(keys.len == 2); + } + + test "list_nonexistent_prefix_returns_empty" { + // List on a non-existent prefix should return empty list + var result = list(["nonexistent"]); + assert(result.is_ok()); + assert(result.unwrap().len == 0); + } + + test "exists_returns_true_for_existing_key" { + // exists should return true for existing keys + var key = ["test", "exists"]; + _ = write(key, "value"); + var result = exists(key); + assert(result.is_ok()); + assert(result.unwrap() == true); + } + + test "exists_returns_false_for_missing_key" { + // exists should return false for missing keys + var result = exists(["does", "not", "exist"]); + assert(result.is_ok()); + assert(result.unwrap() == false); + } +} diff --git a/apps/website/public/t27/specs/storage/lock.t27 b/apps/website/public/t27/specs/storage/lock.t27 new file mode 100644 index 0000000000..bc36be3d76 --- /dev/null +++ b/apps/website/public/t27/specs/storage/lock.t27 @@ -0,0 +1,178 @@ +// specs/storage/lock.t27 +// Locking Primitives for Storage Operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module StorageLock { + use base::types; + use storage::schema; + + // ==================================================================== + // Lock State + // ==================================================================== + + // LockState represents the state of a lock holder + struct LockState { + key: str, + mode: LockMode, + owner: str, + acquired_at: u64, + expires_at: u64, + } + + // ==================================================================== + // Lock Operations + // ==================================================================== + + // acquire_read attempts to acquire a read lock on the given key + // Multiple read locks can be held simultaneously + // Returns false if lock cannot be acquired immediately + fn acquire_read(key: str) -> Result { + // Implementation: Try to acquire read lock, return true if acquired + } + + // acquire_write attempts to acquire a write lock on the given key + // Only one write lock can be held at a time + // Returns false if lock cannot be acquired immediately + fn acquire_write(key: str) -> Result { + // Implementation: Try to acquire write lock, return true if acquired + } + + // release releases a lock on the given key + fn release(key: str) -> Result { + // Implementation: Release the lock held by current owner + } + + // try_lock attempts to acquire a lock with a timeout + // Returns false if lock cannot be acquired within timeout + fn try_lock(key: str, mode: LockMode, timeout_ms: u64) -> Result { + // Implementation: Retry lock acquisition until timeout + } + + // is_locked checks if a key is currently locked + fn is_locked(key: str) -> Result { + // Implementation: Check if lock exists for the key + } + + // get_lock_state returns the current lock state for a key + fn get_lock_state(key: str) -> Result { + // Implementation: Return lock state or null if not locked + } + + // wait_for_read waits until a read lock can be acquired + fn wait_for_read(key: str) -> Result { + // Implementation: Block until read lock is available + } + + // wait_for_write waits until a write lock can be acquired + fn wait_for_write(key: str) -> Result { + // Implementation: Block until write lock is available + } + + // ==================================================================== + // Lock Management + // ==================================================================== + + // cleanup_expired removes expired locks + fn cleanup_expired() -> Result { + // Implementation: Remove all locks past their expiry time + } + + // get_all_locks returns all active locks + fn get_all_locks() -> Result<[LockState], StorageError> { + // Implementation: Return list of all currently held locks + } + + // force_release forces release of a lock (use with caution) + fn force_release(key: str) -> Result { + // Implementation: Release lock regardless of owner + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "acquire_read_returns_true" { + // Acquiring a read lock on an unlocked key should succeed + var result = acquire_read("test_key"); + assert(result.is_ok()); + assert(result.unwrap() == true); + _ = release("test_key"); + } + + test "acquire_write_returns_true" { + // Acquiring a write lock on an unlocked key should succeed + var result = acquire_write("test_key"); + assert(result.is_ok()); + assert(result.unwrap() == true); + _ = release("test_key"); + } + + test "multiple_read_locks_allowed" { + // Multiple read locks should be allowed on the same key + var result1 = acquire_read("test_key"); + var result2 = acquire_read("test_key"); + assert(result1.is_ok()); + assert(result2.is_ok()); + assert(result1.unwrap() == true); + assert(result2.unwrap() == true); + _ = release("test_key"); + _ = release("test_key"); + } + + test "write_lock_excludes_read" { + // A write lock should prevent acquiring a read lock + _ = acquire_write("test_key"); + var result = acquire_read("test_key"); + assert(result.is_ok()); + assert(result.unwrap() == false); + _ = release("test_key"); + } + + test "write_lock_excludes_write" { + // A write lock should prevent acquiring another write lock + _ = acquire_write("test_key"); + var result = acquire_write("test_key"); + assert(result.is_ok()); + assert(result.unwrap() == false); + _ = release("test_key"); + } + + test "read_lock_excludes_write" { + // A read lock should prevent acquiring a write lock + _ = acquire_read("test_key"); + var result = acquire_write("test_key"); + assert(result.is_ok()); + assert(result.unwrap() == false); + _ = release("test_key"); + } + + test "release_allows_reacquisition" { + // Releasing a lock should allow reacquiring + _ = acquire_write("test_key"); + _ = release("test_key"); + var result = acquire_write("test_key"); + assert(result.is_ok()); + assert(result.unwrap() == true); + _ = release("test_key"); + } + + test "is_locked_returns_correct_state" { + // is_locked should return true when locked + _ = acquire_write("test_key"); + var result1 = is_locked("test_key"); + assert(result1.is_ok()); + assert(result1.unwrap() == true); + _ = release("test_key"); + var result2 = is_locked("test_key"); + assert(result2.is_ok()); + assert(result2.unwrap() == false); + } + + test "try_lock_with_timeout" { + // try_lock should acquire lock within timeout + var result = try_lock("test_key", LockMode::Write, 1000); + assert(result.is_ok()); + assert(result.unwrap() == true); + _ = release("test_key"); + } +} diff --git a/apps/website/public/t27/specs/storage/migrate.t27 b/apps/website/public/t27/specs/storage/migrate.t27 new file mode 100644 index 0000000000..7bdf75993f --- /dev/null +++ b/apps/website/public/t27/specs/storage/migrate.t27 @@ -0,0 +1,192 @@ +// specs/storage/migrate.t27 +// Data Migration Operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module StorageMigrate { + use base::types; + use storage::schema; + + // ==================================================================== + // Migration Types + // ==================================================================== + + // MigrationStep represents a single migration step + struct MigrationStep { + version: u32, + name: str, + up: (str) -> Result, + down: (str) -> Result, + } + + // MigrationStatus represents the current migration state + struct MigrationStatus { + current_version: u32, + target_version: u32, + is_migrating: bool, + last_migration_at: u64?, + } + + // ==================================================================== + // Migration Operations + // ==================================================================== + + // version returns the current migration version + fn version() -> Result { + // Implementation: Read migration marker file, return version + } + + // set_version updates the current migration version + fn set_version(v: u32) -> Result { + // Implementation: Write version to migration marker file + } + + // apply runs pending migrations up to the latest version + fn apply(dir: str) -> Result { + // Implementation: Get current version, run all pending migrations + } + + // apply_to runs migrations up to a specific target version + fn apply_to(dir: str, target: u32) -> Result { + // Implementation: Run migrations from current to target version + } + + // rollback rolls back to a previous version + fn rollback(dir: str, target: u32) -> Result { + // Implementation: Run down migrations from current to target + } + + // status returns the current migration status + fn status(dir: str) -> Result { + // Implementation: Return migration status information + } + + // register adds a new migration step + fn register(step: MigrationStep) -> Result { + // Implementation: Add migration to registry + } + + // list_available returns all available migrations + fn list_available() -> Result<[MigrationStep], StorageError> { + // Implementation: Return all registered migrations + } + + // list_pending returns migrations that have not been applied + fn list_pending() -> Result<[MigrationStep], StorageError> { + // Implementation: Compare current version with available migrations + } + + // ==================================================================== + // Built-in Migrations + // ==================================================================== + + // Migration 1: Migrate from legacy project storage + fn migrate_1_legacy_projects(dir: str) -> Result { + // Migrates sessions and messages from ../project/*/storage/ + // to unified storage directory structure + } + + // Migration 2: Extract session diffs + fn migrate_2_session_diffs(dir: str) -> Result { + // Extracts diff arrays from session summaries + // into dedicated session_diff entries + } + + // No-op rollback, shared by every migration that declares one. + // + // Both steps wrote `down = |dir| { Result::ok(()) } // No rollback`. + // Zig has NO CLOSURES -- not a lambda, not an anonymous fn -- so a closure + // in value position is a parse error and a wall over the whole file. A + // named function is the only form the language has, and it is what the + // comment already said this was. + // + // Naming it once rather than twice is not a liberty: the two closures were + // character-identical, and a shared no-op reads as deliberate where two + // copies read as an oversight. + fn migration_no_rollback(dir: str) -> Result { + return Result::ok(()); + } + + // ==================================================================== + // Migration Registry + // ==================================================================== + + const MIGRATIONS: [MigrationStep] = [ + MigrationStep { + version = 1, + name = "legacy_projects", + up = migrate_1_legacy_projects, + down = migration_no_rollback, // No rollback + }, + MigrationStep { + version = 2, + name = "session_diffs", + up = migrate_2_session_diffs, + down = migration_no_rollback, // No rollback + }, + ]; + + // ==================================================================== + // Tests + // ==================================================================== + + test "version_returns_default_zero" { + // When no migration marker exists, version should return 0 + var result = version(); + assert(result.is_ok()); + // Default to 0 if marker doesn't exist + } + + test "set_version_persists_version" { + // Setting a version should persist it + _ = set_version(2); + var result = version(); + assert(result.is_ok()); + assert(result.unwrap() == 2); + _ = set_version(0); // Reset for tests + } + + test "list_available_returns_all_migrations" { + // list_available should return all registered migrations + var result = list_available(); + assert(result.is_ok()); + var migrations = result.unwrap(); + assert(migrations.len >= 2); + } + + test "migrations_have_correct_versions" { + // All migrations should have sequential versions starting at 1 + var result = list_available(); + assert(result.is_ok()); + var migrations = result.unwrap(); + if (migrations.len > 0) { + assert(migrations[0].version == 1); + } + if (migrations.len > 1) { + assert(migrations[1].version == 2); + } + } + + test "apply_migrates_to_latest" { + // apply should run all pending migrations + _ = set_version(0); + var result = apply("/tmp/storage"); + // Should succeed if migrations are valid + assert(result.is_ok()); + } + + test "rollback_migrates_down" { + // rollback should migrate down to target version + _ = set_version(2); + var result = rollback("/tmp/storage", 1); + // Should succeed if rollback is supported + assert(result.is_ok()); + } + + test "status_returns_current_state" { + // status should return the current migration state + var result = status("/tmp/storage"); + assert(result.is_ok()); + var status = result.unwrap(); + assert(status.current_version <= status.target_version); + } +} diff --git a/apps/website/public/t27/specs/storage/schema.t27 b/apps/website/public/t27/specs/storage/schema.t27 new file mode 100644 index 0000000000..f9c1fa8359 --- /dev/null +++ b/apps/website/public/t27/specs/storage/schema.t27 @@ -0,0 +1,145 @@ +// specs/storage/schema.t27 +// Storage Types Specification +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Storage { + use base::types; + + // ==================================================================== + // Storage Key Types + // ==================================================================== + + // StorageKey represents a hierarchical key for storage operations + // Each key is a path of string segments + struct StorageKey { + path: [str], + } + + // StorageEntry represents a stored value with metadata + struct StorageEntry { + key: StorageKey, + value: str, + timestamp: u64, + version: u32, + } + + // ==================================================================== + // Lock Types + // ==================================================================== + + // LockMode defines the type of lock + enum LockMode { + Read = 0, + Write = 1, + } + + // Lock represents an active lock on a resource + struct Lock { + key: str, + mode: LockMode, + acquired_at: u64, + } + + // ==================================================================== + // Migration Types + // ==================================================================== + + // Migration represents a data migration step + struct Migration { + version: u32, + name: str, + description: str, + } + + // MigrationResult represents the result of a migration + enum MigrationResult { + Success = 0, + Failed = 1, + Skipped = 2, + } + + // ==================================================================== + // Error Types + // ==================================================================== + + // StorageError represents a storage operation error + struct StorageError { + message: str, + code: str, + } + + // NotFoundError is raised when a key does not exist + struct NotFoundError { + message: str, + key: StorageKey, + } + + // LockError is raised when lock acquisition fails + struct LockError { + message: str, + key: str, + mode: LockMode, + } + + // ==================================================================== + // Constants + // ==================================================================== + + const DEFAULT_LOCK_TIMEOUT_MS: u64 = 5000; + const MAX_RETRIES: u32 = 3; + const CURRENT_SCHEMA_VERSION: u32 = 2; + + // ==================================================================== + // Helper Functions + // ==================================================================== + + // is_not_found checks if an error is a NotFoundError + fn is_not_found(err: StorageError) -> bool { + return err.code == "NOT_FOUND"; + } + + // is_lock_error checks if an error is a LockError + fn is_lock_error(err: StorageError) -> bool { + return err.code == "LOCK_ERROR"; + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "storage_key_creation" { + var key = StorageKey { + path = ["session", "abc123"], + }; + assert(key.path.len == 2); + assert(key.path[0] == "session"); + assert(key.path[1] == "abc123"); + } + + test "lock_mode_values" { + var read_lock = LockMode::Read; + var write_lock = LockMode::Write; + assert(read_lock as u32 == 0); + assert(write_lock as u32 == 1); + } + + test "is_not_found_detection" { + var err = StorageError { + message = "Key not found", + code = "NOT_FOUND", + }; + assert(is_not_found(err)); + + var other_err = StorageError { + message = "Permission denied", + code = "PERMISSION_ERROR", + }; + assert(!is_not_found(other_err)); + } + + test "constants_values" { + assert(DEFAULT_LOCK_TIMEOUT_MS == 5000); + assert(MAX_RETRIES == 3); + assert(CURRENT_SCHEMA_VERSION == 2); + } +} diff --git a/apps/website/public/t27/specs/sync/index.t27 b/apps/website/public/t27/specs/sync/index.t27 new file mode 100644 index 0000000000..79e4680ed9 --- /dev/null +++ b/apps/website/public/t27/specs/sync/index.t27 @@ -0,0 +1,778 @@ +// SPDX-License-Identifier: Apache-2.0 +// sync/index.t27 — Sync Index Specification +// Sync operations, checkpointing, delta management +// φ² + 1/φ² = 3 | TRINITY + +module sync-index; + +// ============================================================================ +// Imports +// ============================================================================ + +use std; +use sync-schema::{SyncID, SyncOp, SyncCheckpoint, SyncState, SyncDelta}; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Index version +pub const INDEX_VERSION : u16 = 1; + +/// Maximum index entries +pub const MAX_INDEX_ENTRIES : u16 = 65535; + +/// Index block size in bytes +pub const INDEX_BLOCK_SIZE : u16 = 4096; + +/// Delta compression threshold +pub const DELTA_COMPRESSION_THRESHOLD : u16 = 256; + +/// Checkpoint retention count +pub const CHECKPOINT_RETENTION : u8 = 5; + +/// Index entry type +pub const IndexEntryType = enum(u8) { + data = 0, // Regular data entry + delta = 1, // Compressed delta + tombstone = 2, // Deleted entry marker + metadata = 3, // Index metadata +}; + +/// Index operation status +pub const IndexStatus = enum(u8) { + ok = 0, // Operation succeeded + conflict = 1, // Version conflict + not_found = 2, // Key not found + error = 3, // Generic error +}; + +// ============================================================================ +// Types +// ============================================================================ + +/// Index entry +pub const IndexEntry = struct { + key : []u8, + version : u64, + value : []u8, + entry_type : IndexEntryType, + checksum : [32]u8, // SHA-256 as hex string + size_bytes : usize, +}; + +/// Index metadata +pub const IndexMetadata = struct { + version : u16, + entry_count : usize, + last_checkpoint : SyncID, + created_at_ms : u64, + updated_at_ms : u64, +}; + +/// Index delta operation +pub const DeltaOp = struct { + op_type : u8, // 0=add, 1=remove, 2=modify + key : []u8, + value : ?[]u8, + old_version : u64, + new_version : u64, +}; + +/// Index block +pub const IndexBlock = struct { + entries : []IndexEntry, + block_index : u32, + checksum : [32]u8, +}; + +/// Index state +pub const IndexState = struct { + metadata : IndexMetadata, + current_version : u64, + blocks : []IndexBlock, + checkpoints : []SyncCheckpoint, +}; + +/// Replay position +pub const ReplayPosition = struct { + checkpoint_id : SyncID, + operation_index : u32, + timestamp_ms : u64, +}; + +/// Replay result +pub const ReplayResult = struct { + applied_count : usize, + skipped_count : usize, + conflicts : usize, + final_state : []u8, +}; + +/// Delta generation result +pub const DeltaResult = struct { + delta : []DeltaOp, + base_version : u64, + target_version : u64, + size_bytes : usize, +}; + +/// Index query +pub const IndexQuery = struct { + key_pattern : []u8, + min_version : ?u64, + max_version : ?u64, + limit : ?u16, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +/// Create new index state +pub fn index_state_new() IndexState { + return IndexState{ + .metadata = IndexMetadata{ + .version = INDEX_VERSION, + .entry_count = 0, + .last_checkpoint = "", + .created_at_ms = get_timestamp_ms(), + .updated_at_ms = get_timestamp_ms(), + }, + .current_version = 0, + .blocks = &[_]IndexBlock{}, + .checkpoints = &[_]SyncCheckpoint{}, + }; +} + +/// Create index entry +pub fn entry_create(key: []u8, version: u64, value: []u8) IndexEntry { + return IndexEntry{ + .key = key, + .version = version, + .value = value, + .entry_type = .data, + .checksum = "", // Would be computed + .size_bytes = value.len, + }; +} + +/// Create tombstone entry +pub fn entry_tombstone(key: []u8, version: u64) IndexEntry { + return IndexEntry{ + .key = key, + .version = version, + .value = "", + .entry_type = .tombstone, + .checksum = "tombstone", + .size_bytes = 0, + }; +} + +/// Create metadata entry +pub fn entry_metadata_create(entry_count: usize, last_checkpoint: SyncID) IndexEntry { + return IndexEntry{ + .key = "_metadata", + .version = 0, + .value = "", + .entry_type = .metadata, + .checksum = "metadata", + .size_bytes = 0, + }; +} + +/// Create delta operation (add) +pub fn delta_add(key: []u8, value: []u8, old_version: u64, new_version: u64) DeltaOp { + return DeltaOp{ + .op_type = 0, + .key = key, + .value = value, + .old_version = old_version, + .new_version = new_version, + }; +} + +/// Create delta operation (remove) +pub fn delta_remove(key: []u8, old_version: u64, new_version: u64) DeltaOp { + return DeltaOp{ + .op_type = 1, + .key = key, + .value = null, + .old_version = old_version, + .new_version = new_version, + }; +} + +/// Create delta operation (modify) +pub fn delta_modify(key: []u8, old_value: []u8, new_value: []u8, old_version: u64, new_version: u64) DeltaOp { + return DeltaOp{ + .op_type = 2, + .key = key, + .value = new_value, + .old_version = old_version, + .new_version = new_version, + }; +} + +/// Create index block +pub fn block_create(entries: []IndexEntry, block_index: u32) IndexBlock { + return IndexBlock{ + .entries = entries, + .block_index = block_index, + .checksum = "", // Would be computed + }; +} + +/// Create replay position +pub fn replay_position_create(checkpoint_id: SyncID, operation_index: u32) ReplayPosition { + return ReplayPosition{ + .checkpoint_id = checkpoint_id, + .operation_index = operation_index, + .timestamp_ms = get_timestamp_ms(), + }; +} + +/// Create query +pub fn query_create(key_pattern: []u8) IndexQuery { + return IndexQuery{ + .key_pattern = key_pattern, + .min_version = null, + .max_version = null, + .limit = null, + }; +} + +/// Create query with version range +pub fn query_with_version_range(key_pattern: []u8, min_v: u64, max_v: u64) IndexQuery { + return IndexQuery{ + .key_pattern = key_pattern, + .min_version = min_v, + .max_version = max_v, + .limit = null, + }; +} + +/// Create query with limit +pub fn query_with_limit(key_pattern: []u8, limit: u16) IndexQuery { + return IndexQuery{ + .key_pattern = key_pattern, + .min_version = null, + .max_version = null, + .limit = limit, + }; +} + +/// Add entry to index +pub fn index_add(state: *IndexState, entry: IndexEntry) IndexStatus { + state.current_version += 1; + state.metadata.entry_count += 1; + state.metadata.updated_at_ms = get_timestamp_ms(); + return .ok; +} + +/// Remove entry from index +pub fn index_remove(state: *IndexState, key: []u8) IndexStatus { + // Find and remove entry (simplified) + state.metadata.entry_count = max(0, state.metadata.entry_count - 1); + state.metadata.updated_at_ms = get_timestamp_ms(); + return .ok; +} + +/// Get entry from index +pub fn index_get(state: IndexState, key: []u8) ?IndexEntry { + // Find entry (simplified) + return null; +} + +/// Query index +pub fn index_query(state: IndexState, query: IndexQuery) []IndexEntry { + var result : []IndexEntry = &[_]IndexEntry{}; + const limit = query.limit.? MAX_INDEX_ENTRIES; + + for (0..min(result.len, limit)) |_| { + // Would match and add to result + } + + return result; +} + +/// Create checkpoint +pub fn checkpoint_create(state: *IndexState) SyncCheckpoint { + const checkpoint = SyncCheckpoint{ + .sync_id = sync_id_generate(), + .operation_index = @intCast(state.metadata.entry_count), + .state_snapshot = "", + .timestamp_ms = get_timestamp_ms(), + }; + + state.checkpoints = trim_checkpoints(append_checkpoints(state.checkpoints, checkpoint)); + + state.metadata.last_checkpoint = checkpoint.sync_id; + state.metadata.updated_at_ms = get_timestamp_ms(); + + return checkpoint; +} + +/// Generate delta between versions +pub fn delta_generate(state: IndexState, base_version: u64, target_version: u64) DeltaResult { + var operations : []DeltaOp = &[_]DeltaOp{}; + + // Simplified: would compute actual delta + const test_op = delta_add("test", "value", base_version, target_version); + operations = append_deltas(operations, test_op); + + return DeltaResult{ + .delta = operations, + .base_version = base_version, + .target_version = target_version, + .size_bytes = 0, + }; +} + +/// Replay delta to current state +pub fn delta_replay(state: *IndexState, delta: SyncDelta) ReplayResult { + var applied : usize = 0; + var skipped : usize = 0; + var conflicts : usize = 0; + + for (delta.operations, 0..) |op, i| { + // Apply operation (simplified) + applied += 1; + } + + return ReplayResult{ + .applied_count = applied, + .skipped_count = skipped, + .conflicts = conflicts, + .final_state = "", + }; +} + +/// Replay from checkpoint +pub fn checkpoint_replay(state: IndexState, checkpoint_id: SyncID) ReplayResult { + // Find checkpoint and replay operations + var applied : usize = 0; + var skipped : usize = 0; + var conflicts : usize = 0; + + for (state.checkpoints) |checkpoint| { + if (std.mem.eql(checkpoint.sync_id, checkpoint_id)) { + for (0..checkpoint.operation_index) |_| { + applied += 1; + } + } + } + + return ReplayResult{ + .applied_count = applied, + .skipped_count = skipped, + .conflicts = conflicts, + .final_state = "", + }; +} + +/// Trim old checkpoints +pub fn trim_checkpoints(checkpoints: []SyncCheckpoint, new_checkpoint: SyncCheckpoint) []SyncCheckpoint { + if (checkpoints.len < CHECKPOINT_RETENTION) { + return checkpoints; + } + + const start_idx = checkpoints.len - CHECKPOINT_RETENTION + 1; + return append_checkpoints(checkpoints[0..start_idx], new_checkpoint); +} + +/// Append checkpoint to array +pub fn append_checkpoints(checkpoints: []SyncCheckpoint, checkpoint: SyncCheckpoint) []SyncCheckpoint { + var result : []SyncCheckpoint = checkpoints; + var new_slice : []SyncCheckpoint = &[_]SyncCheckpoint{checkpoint}; + for (result) |_| { + new_slice = append_checkpoints_slice(new_slice, _); + } + return new_slice; +} + +/// Append checkpoints slice +pub fn append_checkpoints_slice(slice: []SyncCheckpoint, item: SyncCheckpoint) []SyncCheckpoint { + var result : []SyncCheckpoint = slice; + var new_slice : []SyncCheckpoint = &[_]SyncCheckpoint{item}; + for (result) |_| { + new_slice = append_checkpoints_slice(new_slice, _); + } + return new_slice; +} + +/// Append delta operations +pub fn append_deltas(slice: []DeltaOp, item: DeltaOp) []DeltaOp { + var result : []DeltaOp = slice; + var new_slice : []DeltaOp = &[_]DeltaOp{item}; + for (result) |_| { + new_slice = append_delta_slice(new_slice, _); + } + return new_slice; +} + +/// Append delta operation +pub fn append_delta_slice(slice: []DeltaOp, item: DeltaOp) []DeltaOp { + var result : []DeltaOp = slice; + result = concat_bytes(result, &[_]u8{@intCast(item.op_type)}); + return result; +} + +/// Append bytes +pub fn concat_bytes(slice: []u8, item: u8) []u8 { + var result : []u8 = slice; + result = append_byte(result, item); + return result; +} + +/// Append byte +pub fn append_byte(slice: []u8, byte: u8) []u8 { + var result : []u8 = slice; + result = concat_bytes(result, byte); + return result; +} + +/// Get checkpoint count +pub fn checkpoint_count(checkpoints: []SyncCheckpoint) usize { + return checkpoints.len; +} + +/// Get block count +pub fn block_count(state: IndexState) usize { + return state.blocks.len; +} + +/// Get entry count +pub fn entry_count(state: IndexState) usize { + return state.metadata.entry_count; +} + +/// Generate sync ID +pub fn sync_id_generate() SyncID { + const timestamp = get_timestamp_ms(); + var result : SyncID = [_]u8{0} ** 32; + + const bytes = int_to_bytes(timestamp); + for (0..@min(result.len, bytes.len)) |i| { + result[i] = bytes[i % 256]; + } + + return result; +} + +/// Convert integer to bytes +pub fn int_to_bytes(value: u64) []u8 { + return &[_]u8{ + @intCast((value >> 56) & 0xFF), + @intCast((value >> 48) & 0xFF), + @intCast((value >> 40) & 0xFF), + @intCast((value >> 32) & 0xFF), + @intCast((value >> 24) & 0xFF), + @intCast((value >> 16) & 0xFF), + @intCast((value >> 8) & 0xFF), + @intCast((value >> 0) & 0xFF), + @intCast(value & 0xFF), + }; +} + +/// Get timestamp in milliseconds +pub fn get_timestamp_ms() u64 { + // Simplified: would use system time + return 0; +} + +/// Get minimum of two values +pub fn min(a: usize, b: usize) usize { + return if (a < b) a else b; +} + +/// Calculate checksum +pub fn checksum_compute(data: []u8) [32]u8 { + // Simplified: would compute SHA-256 + return [_]u8{0} ** 32; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "sync_index_state_new" { + const state = index_state_new(); + try std.testing.expect(state.metadata.version == INDEX_VERSION); +} + +test "sync_entry_create" { + const entry = entry_create("key", 1, "value"); + try std.testing.expect(std.mem.eql(entry.key, "key")); + try std.testing.expect(entry.version == 1); +} + +test "sync_entry_tombstone" { + const entry = entry_tombstone("key", 1); + try std.testing.expect(entry.entry_type == .tombstone); +} + +test "sync_delta_add" { + const op = delta_add("key", "value", 0, 1); + try std.testing.expect(op.op_type == 0); +} + +test "sync_delta_remove" { + const op = delta_remove("key", 0, 1); + try std.testing.expect(op.op_type == 1); +} + +test "sync_delta_modify" { + const op = delta_modify("key", "old", "new", 0, 1); + try std.testing.expect(op.op_type == 2); +} + +test "sync_block_create" { + const entry = entry_create("key", 1, "value"); + const block = block_create(&[_]IndexEntry{entry}, 0); + try std.testing.expect(block.block_index == 0); +} + +test "sync_query_create" { + const query = query_create("test"); + try std.testing.expect(std.mem.eql(query.key_pattern, "test")); +} + +test "sync_query_with_version_range" { + const query = query_with_version_range("test", 10, 100); + try std.testing.expect(query.min_version.? == 10); +} + +test "sync_replay_position_create" { + const pos = replay_position_create(sync_id_generate(), 10); + try std.testing.expect(pos.operation_index == 10); +} + +test "sync_checkpoint_create" { + const state = index_state_new(); + const checkpoint = checkpoint_create(&state); + try std.testing.expect(checkpoint_count(state.checkpoints) > 0); +} + +test "sync_trim_checkpoints" { + var checkpoints : []SyncCheckpoint = &[_]SyncCheckpoint{}; + for (0..CHECKPOINT_RETENTION + 2) |i| { + checkpoints = append_checkpoints(checkpoints, SyncCheckpoint{ + .sync_id = sync_id_generate(), + .operation_index = @intCast(i), + .state_snapshot = "", + .timestamp_ms = get_timestamp_ms(), + }); + } + const trimmed = trim_checkpoints(checkpoints, checkpoints[checkpoints.len - 1]); + try std.testing.expect(checkpoint_count(trimmed) == CHECKPOINT_RETENTION); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant index_version_positive { + // INDEX_VERSION is positive + @compileAssert(INDEX_VERSION > 0); +} + +invariant max_index_entries_positive { + // MAX_INDEX_ENTRIES is positive + @compileAssert(MAX_INDEX_ENTRIES > 0); +} + +invariant index_block_size_positive { + // INDEX_BLOCK_SIZE is positive + @compileAssert(INDEX_BLOCK_SIZE > 0); +} + +invariant delta_compression_threshold_positive { + // DELTA_COMPRESSION_THRESHOLD is positive + @compileAssert(DELTA_COMPRESSION_THRESHOLD > 0); +} + +invariant checkpoint_retention_positive { + // CHECKPOINT_RETENTION is positive + @compileAssert(CHECKPOINT_RETENTION > 0); +} + +invariant entry_type_enum_valid { + // IndexEntryType enum has valid values + @compileAssert(@intFromEnum(IndexEntryType.metadata) == 3); +} + +invariant index_status_enum_valid { + // IndexStatus enum has valid values + @compileAssert(@intFromEnum(IndexStatus.error) == 3); +} + +invariant index_entry_has_key { + // IndexEntry has key field + @compileAssert(true); +} + +invariant index_entry_has_version { + // IndexEntry has version field + @compileAssert(true); +} + +invariant index_entry_has_value { + // IndexEntry has value field + @compileAssert(true); +} + +invariant index_entry_has_type { + // IndexEntry has entry_type field + @compileAssert(true); +} + +invariant delta_op_has_type { + // DeltaOp has op_type field + @compileAssert(true); +} + +invariant delta_op_has_key { + // DeltaOp has key field + @compileAssert(true); +} + +invariant delta_op_has_version_fields { + // DeltaOp has old_version and new_version + @compileAssert(true); +} + +invariant replay_result_has_counts { + // ReplayResult has count fields + @compileAssert(true); +} + +invariant replay_result_has_final_state { + // ReplayResult has final_state field + @compileAssert(true); +} + +invariant delta_result_has_delta { + // DeltaResult has delta field + @compileAssert(true); +} + +invariant delta_result_has_versions { + // DeltaResult has base_version and target_version + @compileAssert(true); +} + +invariant index_state_has_metadata { + // IndexState has metadata field + @compileAssert(true); +} + +invariant index_state_has_current_version { + // IndexState has current_version field + @compileAssert(true); +} + +invariant index_state_has_blocks { + // IndexState has blocks array + @compileAssert(true); +} + +invariant index_state_has_checkpoints { + // IndexState has checkpoints array + @compileAssert(true); +} + +invariant checkpoint_add_increments_count { + // Adding checkpoint increments entry_count + @compileAssert(true); +} + +invariant checkpoint_trim_retains_limit { + // trim_checkpoints retains exactly CHECKPOINT_RETENTION checkpoints + @compileAssert(true); +} + +invariant query_has_key_pattern { + // IndexQuery has key_pattern field + @compileAssert(true); +} + +invariant replay_applied_plus_skipped_equals_total { + // Applied + skipped operations equals total operations + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "sync_index_add_latency" { + // Measure: cycles for index add operation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var state = index_state_new(); + const entry = entry_create("key", 1, "value"); + var result : IndexStatus = undefined; + for (0..1000) |_| { + result = index_add(&state, entry); + } + _ = result; +} + +bench "sync_index_get_latency" { + // Measure: cycles for index get operation + // Target: < 80 cycles + @setEvalBranchQuota(10000); + var state = index_state_new(); + var result : ?IndexEntry = undefined; + for (0..1000) |_| { + result = index_get(&state, "key"); + } + _ = result != null; +} + +bench "sync_checkpoint_create_latency" { + // Measure: cycles for checkpoint creation + // Target: < 150 cycles + @setEvalBranchQuota(10000); + var state = index_state_new(); + var result : SyncCheckpoint = undefined; + for (0..1000) |_| { + result = checkpoint_create(&state); + } + _ = result.operation_index; +} + +bench "sync_delta_generate_latency" { + // Measure: cycles for delta generation + // Target: < 200 cycles + @setEvalBranchQuota(10000); + var state = index_state_new(); + var result : DeltaResult = undefined; + for (0..1000) |_| { + result = delta_generate(state, 0, 1); + } + _ = result.delta.len; +} + +bench "sync_entry_create_latency" { + // Measure: cycles for entry creation + // Target: < 50 cycles + @setEvalBranchQuota(10000); + var result : IndexEntry = undefined; + for (0..1000) |_| { + result = entry_create("key", 1, "value"); + } + _ = result.version; +} + +bench "sync_query_create_latency" { + // Measure: cycles for query creation + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : IndexQuery = undefined; + for (0..1000) |_| { + result = query_create("test"); + } + _ = result.key_pattern.len; +} diff --git a/apps/website/public/t27/specs/sync/schema.t27 b/apps/website/public/t27/specs/sync/schema.t27 new file mode 100644 index 0000000000..95fb229a8b --- /dev/null +++ b/apps/website/public/t27/specs/sync/schema.t27 @@ -0,0 +1,721 @@ +// SPDX-License-Identifier: Apache-2.0 +// sync/schema.t27 — Sync Schema Specification +// Sync ID, state, event types for change synchronization +// φ² + 1/φ² = 3 | TRINITY + +module sync-schema; + +// ============================================================================ +// Imports +// ============================================================================ + +use std; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Maximum sync ID length +pub const SYNC_ID_MAX_LEN : u8 = 64; + +/// Default sync timeout in milliseconds +pub const SYNC_TIMEOUT_MS : u32 = 30000; // 30 seconds + +/// Maximum concurrent sync operations +pub const MAX_CONCURRENT_SYNCS : u8 = 10; + +/// Sync state pending duration in milliseconds +pub const STATE_PENDING_TIMEOUT_MS : u32 = 5000; + +/// Sync checkpoint interval in operations +pub const CHECKPOINT_INTERVAL : u16 = 100; + +// ============================================================================ +// Types +// ============================================================================ + +/// Sync identifier (128-bit hash) +pub const SyncID = [SYNC_ID_MAX_LEN]u8; + +/// Sync operation type +pub const SyncOpType = enum(u8) { + insert = 0, // Insert new data + update = 1, // Update existing data + delete = 2, // Delete data + merge = 3, // Merge changes + reconcile = 4, // Resolve conflicts +}; + +/// Sync state +pub const SyncState = enum(u8) { + idle = 0, // No active sync + pending = 1, // Sync requested, not started + syncing = 2, // Sync in progress + complete = 3, // Sync finished successfully + failed = 4, // Sync failed + conflict = 5, // Conflict detected +}; + +/// Sync event +pub const SyncEvent = struct { + sync_id : SyncID, + op_type : SyncOpType, + state : SyncState, + timestamp_ms : u64, + metadata : []u8, +}; + +/// Sync operation +pub const SyncOp = struct { + op_type : SyncOpType, + key : []u8, + value : []u8, // Empty for delete + expected_version : u64, + actual_version : ?u64, +}; + +/// Sync conflict +pub const SyncConflict = struct { + sync_id : SyncID, + key : []u8, + local_value : []u8, + remote_value : []u8, + resolved : bool, +}; + +/// Sync checkpoint +pub const SyncCheckpoint = struct { + sync_id : SyncID, + operation_index : u16, + state_snapshot : []u8, // Serialized state + timestamp_ms : u64, +}; + +/// Sync session +pub const SyncSession = struct { + id : SyncID, + start_time_ms : u64, + end_time_ms : ?u64, + operations : []SyncOp, + state : SyncState, + errors : []u8, +}; + +/// Sync delta +pub const SyncDelta = struct { + sync_id : SyncID, + operations : []SyncOp, + base_version : u64, + target_version : u64, +}; + +/// Sync statistics +pub const SyncStats = struct { + total_operations : usize, + successful_operations : usize, + failed_operations : usize, + conflicts : usize, + duration_ms : u64, +}; +// ============================================================================ +// Functions +// ============================================================================ + +/// Generate new sync ID +pub fn sync_id_generate() SyncID { + // Simplified: would use hash generation in implementation + const timestamp = get_timestamp_ms(); + var result : SyncID = [_]u8{0} ** SYNC_ID_MAX_LEN; + + const bytes = int_to_bytes(timestamp); + for (0..@min(result.len, bytes.len)) |i| { + result[i] = bytes[i % 256]; + } + + return result; +} + +/// Check if sync ID is valid +pub fn sync_id_valid(id: SyncID) bool { + var has_non_zero : bool = false; + for (id) |byte| { + if (byte != 0) { + has_non_zero = true; + break; + } + } + return has_non_zero; +} + +/// Create sync operation +pub fn op_create(op_type: SyncOpType, key: []u8, value: []u8) SyncOp { + return SyncOp{ + .op_type = op_type, + .key = key, + .value = value, + .expected_version = 0, + .actual_version = null, + }; +} + +/// Create sync operation with version +pub fn op_create_with_version(op_type: SyncOpType, key: []u8, value: []u8, version: u64) SyncOp { + return SyncOp{ + .op_type = op_type, + .key = key, + .value = value, + .expected_version = version, + .actual_version = null, + }; +} + +/// Create sync event +pub fn event_create(sync_id: SyncID, op_type: SyncOpType, state: SyncState) SyncEvent { + return SyncEvent{ + .sync_id = sync_id, + .op_type = op_type, + .state = state, + .timestamp_ms = get_timestamp_ms(), + .metadata = "", + }; +} + +/// Create sync event with metadata +pub fn event_create_with_metadata(sync_id: SyncID, op_type: SyncOpType, state: SyncState, meta: []u8) SyncEvent { + return SyncEvent{ + .sync_id = sync_id, + .op_type = op_type, + .state = state, + .timestamp_ms = get_timestamp_ms(), + .metadata = meta, + }; +} + +/// Create sync conflict +pub fn conflict_create(sync_id: SyncID, key: []u8, local: []u8, remote: []u8) SyncConflict { + return SyncConflict{ + .sync_id = sync_id, + .key = key, + .local_value = local, + .remote_value = remote, + .resolved = false, + }; +} + +/// Create sync checkpoint +pub fn checkpoint_create(sync_id: SyncID, index: u16, state: []u8) SyncCheckpoint { + return SyncCheckpoint{ + .sync_id = sync_id, + .operation_index = index, + .state_snapshot = state, + .timestamp_ms = get_timestamp_ms(), + }; +} + +/// Create new sync session +pub fn session_create(id: SyncID) SyncSession { + return SyncSession{ + .id = id, + .start_time_ms = get_timestamp_ms(), + .end_time_ms = null, + .operations = &[_]SyncOp{}, + .state = .pending, + .errors = "", + }; +} + +/// Create sync statistics +pub fn stats_create(total: usize, successful: usize, failed: usize, conflicts: usize) SyncStats { + return SyncStats{ + .total_operations = total, + .successful_operations = successful, + .failed_operations = failed, + .conflicts = conflicts, + .duration_ms = 0, + }; +} + +/// Check if operation is insert +pub fn op_is_insert(op: SyncOp) bool { + return op.op_type == .insert; +} + +/// Check if operation is update +pub fn op_is_update(op: SyncOp) bool { + return op.op_type == .update; +} + +/// Check if operation is delete +pub fn op_is_delete(op: SyncOp) bool { + return op.op_type == .delete; +} + +/// Check if operation is merge +pub fn op_is_merge(op: SyncOp) bool { + return op.op_type == .merge; +} + +/// Check if state is terminal +pub fn state_is_terminal(state: SyncState) bool { + return state == .complete or state == .failed or state == .conflict; +} + +/// Check if state is active +pub fn state_is_active(state: SyncState) bool { + return state == .pending or state == .syncing; +} + +/// Resolve conflict with local value +pub fn conflict_resolve_local(conflict: *SyncConflict) void { + conflict.remote_value = conflict.local_value; + conflict.resolved = true; +} + +/// Resolve conflict with remote value +pub fn conflict_resolve_remote(conflict: *SyncConflict) void { + conflict.local_value = conflict.remote_value; + conflict.resolved = true; +} + +/// Mark conflict as unresolved +pub fn conflict_unresolved(conflict: *SyncConflict) void { + conflict.resolved = false; +} + +/// Update session state +pub fn session_set_state(session: *SyncSession, state: SyncState) void { + session.state = state; +} + +/// Complete session with timestamp +pub fn session_complete(session: *SyncSession) void { + session.end_time_ms = get_timestamp_ms(); + session.state = .complete; +} + +/// Fail session with error +pub fn session_fail(session: *SyncSession, error: []u8) void { + session.end_time_ms = get_timestamp_ms(); + session.state = .failed; + session.errors = append(session.errors, error); +} + +/// Add operation to session +pub fn session_add_op(session: *SyncSession, op: SyncOp) void { + session.operations = append(session.operations, op); +} + +/// Get operation count +pub fn session_op_count(session: SyncSession) usize { + return session.operations.len; +} + +/// Calculate stats from session +pub fn session_stats(session: SyncSession) SyncStats { + var successful : usize = 0; + var failed : usize = 0; + + for (session.operations) |op| { + if (op.actual_version != null) { + successful += 1; + } else { + failed += 1; + } + } + + const duration = session.end_time_ms.? - session.start_time_ms; + + return SyncStats{ + .total_operations = session.operations.len, + .successful_operations = successful, + .failed_operations = failed, + .conflicts = 0, + .duration_ms = duration, + }; +} + +/// Append byte slice to session errors +pub fn append_errors(errors: []u8, error: []u8) []u8 { + var result : []u8 = errors; + const sep = "\n"; + result = concat(result, error); + result = concat(result, sep); + return result; +} + +/// Concatenate byte slices +pub fn concat(a: []u8, b: []u8) []u8 { + var result : []u8 = a; + for (b) |byte| { + result = append_byte(result, byte); + } + return result; +} + +/// Append byte to slice +pub fn append_byte(slice: []u8, byte: u8) []u8 { + var result : []u8 = slice; + result = concat(result, &[_]u8{byte}); + return result; +} + +/// Append operation to slice +pub fn append(slice: []SyncOp, item: SyncOp) []SyncOp { + var result : []SyncOp = slice; + var new_slice : []SyncOp = &[_]SyncOp{item}; + for (result) |_| { + new_slice = append_ops(new_slice, _); + } + return new_slice; +} + +/// Append operations to slice +pub fn append_ops(slice: []SyncOp, items: []SyncOp) []SyncOp { + var result : []SyncOp = slice; + for (items) |item| { + result = append(result, item); + } + return result; +} + +/// Convert integer to bytes (big-endian) +pub fn int_to_bytes(value: u64) []u8 { + return &[_]u8{ + @intCast((value >> 56) & 0xFF), + @intCast((value >> 48) & 0xFF), + @intCast((value >> 40) & 0xFF), + @intCast((value >> 32) & 0xFF), + @intCast((value >> 24) & 0xFF), + @intCast((value >> 16) & 0xFF), + @intCast((value >> 8) & 0xFF), + @intCast((value >> 0) & 0xFF), + @intCast(value & 0xFF), + }; +} + +/// Get minimum of two values +pub fn min(a: usize, b: usize) usize { + return if (a < b) a else b; +} + +/// Get current timestamp in milliseconds +pub fn get_timestamp_ms() u64 { + // Simplified: would use system time + return 0; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "sync_op_create_insert" { + const op = op_create(.insert, "key", "value"); + try std.testing.expect(op_is_insert(op)); +} + +test "sync_op_create_update" { + const op = op_create(.update, "key", "value"); + try std.testing.expect(op_is_update(op)); +} + +test "sync_op_create_delete" { + const op = op_create(.delete, "key", ""); + try std.testing.expect(op_is_delete(op)); +} + +test "sync_op_create_merge" { + const op = op_create(.merge, "key", "value"); + try std.testing.expect(op_is_merge(op)); +} + +test "sync_event_create" { + const id = sync_id_generate(); + const event = event_create(id, .insert, .syncing); + try std.testing.expect(state_is_active(event.state)); +} + +test "sync_event_complete" { + const id = sync_id_generate(); + const event = event_create(id, .insert, .complete); + try std.testing.expect(state_is_terminal(event.state)); +} + +test "sync_conflict_create" { + const id = sync_id_generate(); + const conflict = conflict_create(id, "key", "local", "remote"); + try std.testing.expect(!conflict.resolved); +} + +test "sync_session_create" { + const id = sync_id_generate(); + const session = session_create(id); + try std.testing.expect(session.state == .pending); +} + +test "sync_session_complete" { + const id = sync_id_generate(); + var session = session_create(id); + session_complete(&session); + try std.testing.expect(session.state == .complete); +} + +test "sync_session_fail" { + const id = sync_id_generate(); + var session = session_create(id); + session_fail(&session, "error"); + try std.testing.expect(session.state == .failed); +} + +test "sync_conflict_resolve_local" { + const id = sync_id_generate(); + var conflict = conflict_create(id, "key", "local", "remote"); + conflict_resolve_local(&conflict); + try std.testing.expect(std.mem.eql(conflict.remote_value, "local")); + try std.testing.expect(conflict.resolved); +} + +test "sync_conflict_resolve_remote" { + const id = sync_id_generate(); + var conflict = conflict_create(id, "key", "local", "remote"); + conflict_resolve_remote(&conflict); + try std.testing.expect(std.mem.eql(conflict.local_value, "remote")); + try std.testing.expect(conflict.resolved); +} + +test "sync_session_add_op" { + const id = sync_id_generate(); + var session = session_create(id); + const op = op_create(.insert, "key", "value"); + session_add_op(&session, op); + try std.testing.expect(session_op_count(session) == 1); +} + +test "sync_stats_calculate" { + const id = sync_id_generate(); + var session = session_create(id); + session_complete(&session); + const stats = session_stats(&session); + try std.testing.expect(stats.total_operations == 0); +} + +test "sync_stats_partial" { + const id = sync_id_generate(); + var session = session_create(id); + const op1 = op_create(.insert, "k1", "v1"); + const op2 = op_create(.insert, "k2", "v2"); + session_add_op(&session, op1); + session_add_op(&session, op2); + session_complete(&session); + const stats = session_stats(&session); + try std.testing.expect(stats.successful_operations == 2); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant sync_id_max_len_positive { + // SYNC_ID_MAX_LEN is positive + @compileAssert(SYNC_ID_MAX_LEN > 0); +} + +invariant sync_timeout_positive { + // SYNC_TIMEOUT_MS is positive + @compileAssert(SYNC_TIMEOUT_MS > 0); +} + +invariant max_concurrent_syncs_positive { + // MAX_CONCURRENT_SYNCS is positive + @compileAssert(MAX_CONCURRENT_SYNCS > 0); +} + +invariant op_type_enum_valid { + // SyncOpType enum has valid values + @compileAssert(@intFromEnum(SyncOpType.reconcile) == 5); +} + +invariant sync_state_enum_valid { + // SyncState enum has valid values + @compileAssert(@intFromEnum(SyncState.conflict) == 5); +} + +invariant sync_event_has_sync_id { + // SyncEvent has sync_id field + @compileAssert(true); +} + +invariant sync_event_has_op_type { + // SyncEvent has op_type field + @compileAssert(true); +} + +invariant sync_event_has_state { + // SyncEvent has state field + @compileAssert(true); +} + +invariant sync_event_has_timestamp { + // SyncEvent has timestamp_ms field + @compileAssert(true); +} + +invariant sync_op_has_key { + // SyncOp has key field + @compileAssert(true); +} + +invariant sync_op_has_value { + // SyncOp has value field + @compileAssert(true); +} + +invariant sync_op_has_expected_version { + // SyncOp has expected_version field + @compileAssert(true); +} + +invariant sync_conflict_has_key { + // SyncConflict has key field + @compileAssert(true); +} + +invariant sync_conflict_has_both_values { + // SyncConflict has local_value and remote_value + @compileAssert(true); +} + +invariant sync_conflict_has_resolved { + // SyncConflict has resolved boolean + @compileAssert(true); +} + +invariant sync_session_has_id { + // SyncSession has id field + @compileAssert(true); +} + +invariant sync_session_has_start_time { + // SyncSession has start_time_ms field + @compileAssert(true); +} + +invariant sync_session_has_operations { + // SyncSession has operations array + @compileAssert(true); +} + +invariant sync_session_has_state { + // SyncSession has state field + @compileAssert(true); +} + +invariant sync_stats_counts { + // SyncStats has count fields + @compileAssert(true); +} + +invariant sync_stats_has_duration { + // SyncStats has duration_ms field + @compileAssert(true); +} + +invariant state_terminal_is_complete_or_failed { + // state_is_terminal returns true for complete or failed + @compileAssert(true); +} + +invariant state_active_is_pending_or_syncing { + // state_is_active returns true for pending or syncing + @compileAssert(true); +} + +invariant checkpoint_has_sync_id { + // SyncCheckpoint has sync_id field + @compileAssert(true); +} + +invariant checkpoint_has_operation_index { + // SyncCheckpoint has operation_index field + @compileAssert(true); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "sync_id_generate_latency" { + // Measure: cycles for sync ID generation + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var result : SyncID = undefined; + for (0..1000) |_| { + result = sync_id_generate(); + } + _ = result[0]; +} + +bench "sync_op_create_latency" { + // Measure: cycles for operation creation + // Target: < 30 cycles + @setEvalBranchQuota(10000); + var result : SyncOp = undefined; + for (0..1000) |_| { + result = op_create(.insert, "key", "value"); + } + _ = result.op_type; +} + +bench "sync_event_create_latency" { + // Measure: cycles for event creation + // Target: < 50 cycles + @setEvalBranchQuota(10000); + const id = sync_id_generate(); + var result : SyncEvent = undefined; + for (0..1000) |_| { + result = event_create(id, .insert, .syncing); + } + _ = result.state; +} + +bench "sync_session_stats_latency" { + // Measure: cycles for stats calculation + // Target: < 200 cycles + @setEvalBranchQuota(10000); + const id = sync_id_generate(); + var session = session_create(id); + const op = op_create(.insert, "key", "value"); + session_add_op(&session, op); + session_complete(&session); + var result : SyncStats = undefined; + for (0..1000) |_| { + result = session_stats(&session); + } + _ = result.total_operations; +} + +bench "sync_conflict_create_latency" { + // Measure: cycles for conflict creation + // Target: < 40 cycles + @setEvalBranchQuota(10000); + const id = sync_id_generate(); + var result : SyncConflict = undefined; + for (0..1000) |_| { + result = conflict_create(id, "key", "local", "remote"); + } + _ = result.resolved; +} + +bench "sync_session_add_op_latency" { + // Measure: cycles for adding operation + // Target: < 30 cycles + @setEvalBranchQuota(10000); + const id = sync_id_generate(); + var session = session_create(id); + const op = op_create(.insert, "key", "value"); + var result : usize = undefined; + for (0..1000) |_| { + session_add_op(&session, op); + result = session_op_count(session); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/ternary/bigint.t27 b/apps/website/public/t27/specs/ternary/bigint.t27 new file mode 100644 index 0000000000..19983ce583 --- /dev/null +++ b/apps/website/public/t27/specs/ternary/bigint.t27 @@ -0,0 +1,1440 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ternary/bigint.t27 +// TVC BigInt - Balanced Ternary Arbitrary Precision Arithmetic +// 01234 567891011: V = n 12 3^k 13 14^m 15 16^p 17 e^q +// phi^2 + 1/phi^2 = 3 | TRINITY +// +// Balanced Ternary representation: +// - Each trit has value {-1, 0, +1} +// - Number = Sigma(trit[i] * 3^i) for i = 0..n-1 +// - No separate sign bit needed (inherent in representation) +// - Rounding is simpler (truncation = rounding to nearest) +// +// Supports: +// - Arbitrary precision integers up to 3^256 ~= 10^122 +// - SIMD-optimized operations (32 trits in parallel) +// - Karatsuba multiplication O(n^1.585) +// - Newton-Raphson division for large numbers + +module TernaryBigInt; + +// ============================================================================ +// Imports +// ============================================================================ + +use tritype-base::Trit; +use tritype-base::trit_negate; +use tritype-base::trit_multiply; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Maximum trits for BigInt (supports numbers up to 3^256 ~= 10^122) +pub const MAX_TRITS : usize = 256; + +/// Trit constants for convenience +pub const TRIT_NEG : Trit = .neg; +pub const TRIT_ZERO : Trit = .zero; +pub const TRIT_POS : Trit = .pos; + +/// SIMD chunk size (32 trits = 256 bits = AVX2) +pub const SIMD_CHUNK_SIZE : usize = 32; + +/// Number of SIMD chunks in BigInt (256 / 32 = 8) +pub const SIMD_CHUNKS : usize = MAX_TRITS / SIMD_CHUNK_SIZE; + +/// Karatsuba threshold: use simple multiplication for smaller numbers +pub const KARATSUBA_THRESHOLD : usize = 32; + +/// SIMD threshold: use SIMD for numbers with >= 64 trits +pub const SIMD_THRESHOLD : usize = 64; + +/// Long division threshold for using i64 native division +pub const I64_DIV_THRESHOLD : usize = 40; + +// ============================================================================ +// Types +// ============================================================================ + +/// Division result type +pub const DivResult = struct { + q : BigInt, // quotient + r : BigInt, // remainder +}; + +/// Balanced Ternary BigInt +/// Stores number as array of trits (least significant first) +pub const BigInt = struct { + /// Trits array (LST first) + trits : [MAX_TRITS]Trit, + /// Number of significant trits + len : usize, + + const Self = @This(); + + // ======================================================================== + // Constructors + // ======================================================================== + + /// Create zero BigInt + pub fn zero() Self { + return Self{ + .trits = [_]Trit{TRIT_ZERO} ** MAX_TRITS, + .len = 1, + }; + } + + /// Create BigInt from i64 value + /// Algorithm: repeated division by 3 with balanced ternary remainder + /// Complexity: O(log_3(|value|)) + pub fn from_i64(value: i64) Self { + var result = zero(); + if (value == 0) { + return result; + } + + var v = value; + var i : usize = 0; + + while (v != 0 and i < MAX_TRITS) { + // Get remainder in range -1..1 + var rem = @mod(v, @as(i64, 3)); + if (rem == 2) { + rem = -1; + v = @divFloor(v - rem, 3); + } else if (rem == -2) { + rem = 1; + v = @divFloor(v - rem, 3); + } else { + v = @divFloor(v, 3); + } + + result.trits[i] = @intCast(rem); + i = i + 1; + } + + result.len = if (i == 0) { 1 } else { i }; + return result.normalize(); + } + + // ======================================================================== + // Conversion + // ======================================================================== + + /// Convert BigInt to i64 (may overflow for large numbers) + /// Algorithm: Sigma(trit[i] * 3^i) + /// Complexity: O(len) + pub fn to_i64(self: Self) i64 { + var result : i64 = 0; + var power : i64 = 1; + + var i : usize = 0; + while (i < self.len) { + result += @as(i64, self.trits[i]) * power; + power *= 3; + i = i + 1; + } + + return result; + } + + // ======================================================================== + // Properties + // ======================================================================== + + /// Remove leading zero trits + /// Ensures canonical representation + /// Complexity: O(len) worst case, O(1) typical + pub fn normalize(self: *Self) void { + while (self.len > 1 and self.trits[self.len - 1] == TRIT_ZERO) { + self.len -= 1; + } + } + + /// Check if BigInt is zero + /// Returns: true if only trit is zero + pub fn is_zero(self: Self) bool { + return self.len == 1 and self.trits[0] == TRIT_ZERO; + } + + /// Check if BigInt is negative + /// In balanced ternary, sign is determined by most significant trit + /// Returns: true if MST < 0 + pub fn is_negative(self: Self) bool { + return self.trits[self.len - 1] == TRIT_NEG; + } + + /// Check if BigInt is positive + /// Returns: true if MST > 0 + pub fn is_positive(self: Self) bool { + return self.trits[self.len - 1] == TRIT_POS; + } + + /// Get sign of BigInt + /// Returns: -1 if negative, 0 if zero, +1 if positive + pub fn signum(self: Self) i8 { + if (self.is_zero()) { + return 0; + } else if (self.is_negative()) { + return -1; + } else { + return 1; + } + } + + // ======================================================================== + // Unary Operations + // ======================================================================== + + /// Negate BigInt (flip all trits) + /// In balanced ternary: -x = flip all trits of x + /// Complexity: O(len) + pub fn negate(self: Self) Self { + var result = self; + var i : usize = 0; + while (i < result.len) { + result.trits[i] = trit_negate(result.trits[i]); + i = i + 1; + } + return result; + } + + /// Absolute value + /// Returns: self if positive, negate(self) if negative + /// Complexity: O(len) worst case, O(1) for positive + pub fn abs(self: Self) Self { + if (self.is_negative()) { + return self.negate(); + } + return self; + } + + // ======================================================================== + // Addition + // ======================================================================== + + /// Add two BigInts + /// Uses SIMD for large numbers, scalar for small + /// Algorithm: trit-wise addition with carry propagation + /// Complexity: O(max(len(a), len(b))) + pub fn add(a: Self, b: Self) Self { + // Use SIMD for larger numbers + if (a.len >= SIMD_THRESHOLD or b.len >= SIMD_THRESHOLD) { + return a.add_simd(b); + } + return a.add_scalar(b); + } + + /// Scalar addition (trit-by-trit with carry) + /// Complexity: O(max(len(a), len(b))) + fn add_scalar(a: Self, b: Self) Self { + var result = zero(); + var carry : i8 = 0; + + const max_len = @max(a.len, b.len); + + var i : usize = 0; + while (i < max_len + 1) { + if (i >= MAX_TRITS) { + break; + } + + const a_trit : i8 = if (i < a.len) { @intCast(a.trits[i]) } else { 0 }; + const b_trit : i8 = if (i < b.len) { @intCast(b.trits[i]) } else { 0 }; + + var sum : i8 = a_trit + b_trit + carry; + carry = 0; + + // Normalize to balanced ternary (-1, 0, +1) + while (sum > 1) { + sum -= 3; + carry += 1; + } + while (sum < -1) { + sum += 3; + carry -= 1; + } + + result.trits[i] = @intCast(sum); + result.len = i + 1; + i = i + 1; + } + + result.normalize(); + return result; + } + + /// SIMD-optimized addition (32 trits at a time) + /// Complexity: O(max(len(a), len(b)) / 32 + carry_propagation) + fn add_simd(a: Self, b: Self) Self { + var result = zero(); + const max_len = @max(a.len, b.len); + const num_chunks = (max_len + SIMD_CHUNK_SIZE - 1) / SIMD_CHUNK_SIZE; + + // First pass: parallel add without carry propagation + var chunk : usize = 0; + while (chunk < num_chunks) { + const offset = chunk * SIMD_CHUNK_SIZE; + + var i : usize = 0; + while (i < SIMD_CHUNK_SIZE) { + const idx = offset + i; + const a_trit : i8 = if (idx < a.len) { @intCast(a.trits[idx]) } else { 0 }; + const b_trit : i8 = if (idx < b.len) { @intCast(b.trits[idx]) } else { 0 }; + result.trits[idx] = @intCast(a_trit + b_trit); + i = i + 1; + } + chunk = chunk + 1; + } + + // Second pass: sequential carry propagation + var carry : i8 = 0; + var i : usize = 0; + while (i < max_len + 1) { + if (i >= MAX_TRITS) { + break; + } + + var val : i16 = @as(i16, result.trits[i]) + carry; + carry = 0; + + while (val > 1) { + val -= 3; + carry += 1; + } + while (val < -1) { + val += 3; + carry -= 1; + } + + result.trits[i] = @intCast(val); + i = i + 1; + } + + result.len = max_len + 1; + result.normalize(); + return result; + } + + // ======================================================================== + // Subtraction + // ======================================================================== + + /// Subtract: a - b = a + (-b) + /// Uses negation and addition + /// Complexity: O(max(len(a), len(b))) + pub fn sub(a: Self, b: Self) Self { + const neg_b = b.negate(); + return a.add(neg_b); + } + + // ======================================================================== + // Multiplication + // ======================================================================== + + /// Multiply two BigInts + /// Uses Karatsuba for large numbers, simple for small + /// Complexity: O(n^1.585) for Karatsuba, O(n^2) for simple + pub fn mul(a: Self, b: Self) Self { + if (a.len <= KARATSUBA_THRESHOLD or b.len <= KARATSUBA_THRESHOLD) { + return a.mul_simple(b); + } + return a.mul_karatsuba(b); + } + + /// Grade school multiplication + /// For each trit in a, multiply with b and add with offset + /// Complexity: O(len(a) * len(b)) + fn mul_simple(a: Self, b: Self) Self { + var result = zero(); + + var i : usize = 0; + while (i < a.len) { + if (a.trits[i] == TRIT_ZERO) { + i = i + 1; + continue; + } + + var partial = zero(); + var carry : i8 = 0; + + var j : usize = 0; + while (j < b.len) { + if (i + j >= MAX_TRITS) { + break; + } + + var prod : i16 = @as(i16, @intCast(a.trits[i])) * @as(i16, @intCast(b.trits[j])) + carry; + carry = 0; + + while (prod > 1) { + prod -= 3; + carry += 1; + } + while (prod < -1) { + prod += 3; + carry -= 1; + } + + partial.trits[i + j] = @intCast(prod); + if (i + j + 1 > partial.len) { + partial.len = i + j + 1; + } + j = j + 1; + } + + // Handle final carry + if (carry != 0 and i + b.len < MAX_TRITS) { + partial.trits[i + b.len] = @intCast(carry); + if (i + b.len + 1 > partial.len) { + partial.len = i + b.len + 1; + } + } + + result = result.add(partial); + i = i + 1; + } + + result.normalize(); + return result; + } + + /// Karatsuba multiplication for large numbers + /// Recursively splits numbers: a = a1*3^m + a0, b = b1*3^m + b0 + /// Uses 3 multiplications: z0 = a0*b0, z2 = a1*b1, z1 = (a0+a1)*(b0+b1) - z0 - z2 + /// Complexity: O(n^log2(3)) ~= O(n^1.585) + fn mul_karatsuba(a: Self, b: Self) Self { + // Base case: use simple multiplication + if (a.len <= KARATSUBA_THRESHOLD or b.len <= KARATSUBA_THRESHOLD) { + return a.mul_simple(b); + } + + // Split at midpoint + const m = @max(a.len, b.len) / 2; + + // a = a1 * 3^m + a0 + var a0 = zero(); + var a1 = zero(); + + var i : usize = 0; + while (i < @min(m, a.len)) { + a0.trits[i] = a.trits[i]; + i = i + 1; + } + a0.len = @min(m, a.len); + a0.normalize(); + + if (a.len > m) { + var j : usize = 0; + while (m + j < a.len) { + a1.trits[j] = a.trits[m + j]; + j = j + 1; + } + a1.len = a.len - m; + a1.normalize(); + } + + // b = b1 * 3^m + b0 + var b0 = zero(); + var b1 = zero(); + + i = 0; + while (i < @min(m, b.len)) { + b0.trits[i] = b.trits[i]; + i = i + 1; + } + b0.len = @min(m, b.len); + b0.normalize(); + + if (b.len > m) { + var j : usize = 0; + while (m + j < b.len) { + b1.trits[j] = b.trits[m + j]; + j = j + 1; + } + b1.len = b.len - m; + b1.normalize(); + } + + // Karatsuba: 3 multiplications + const z0 = a0.mul_karatsuba(b0); + const z2 = a1.mul_karatsuba(b1); + + const a_sum = a0.add(a1); + const b_sum = b0.add(b1); + var z1 = a_sum.mul_karatsuba(b_sum); + z1 = z1.sub(z0); + z1 = z1.sub(z2); + + // Result = z0 + z1 * 3^m + z2 * 3^(2m) + var result = z0; + + // Add z1 * 3^m + const z1_shifted = z1.shift_left(m); + result = result.add(z1_shifted); + + // Add z2 * 3^(2m) + const z2_shifted = z2.shift_left(2 * m); + result = result.add(z2_shifted); + + result.normalize(); + return result; + } + + // ======================================================================== + // Comparison + // ======================================================================== + + /// Compare absolute values + /// Returns: -1 if |a| < |b|, 0 if |a| == |b|, +1 if |a| > |b| + pub fn compare_abs(a: Self, b: Self) i8 { + const a_abs = a.abs(); + const b_abs = b.abs(); + + if (a_abs.len != b_abs.len) { + return if (a_abs.len < b_abs.len) { -1 } else { 1 }; + } + + // Compare from most significant trit + var i = a_abs.len; + while (i > 0) { + i -= 1; + if (a_abs.trits[i] != b_abs.trits[i]) { + return if (a_abs.trits[i] < b_abs.trits[i]) { -1 } else { 1 }; + } + } + + return 0; + } + + /// Compare two BigInts + /// Returns: -1 if a < b, 0 if a == b, +1 if a > b + /// Complexity: O(min(len(a), len(b))) + pub fn compare(a: Self, b: Self) i8 { + // Compare signs first + const a_sign = a.signum(); + const b_sign = b.signum(); + + if (a_sign < b_sign) { + return -1; + } else if (a_sign > b_sign) { + return 1; + } + + // Same sign: compare absolute values + const cmp = a.compare_abs(b); + + // If both negative, reverse comparison + if (a_sign < 0) { + return -cmp; + } + + return cmp; + } + + // ======================================================================== + // Division + // ======================================================================== + + /// Division with remainder + /// Returns (q, r) such that a = q * b + r, |r| < |b| + /// Uses Newton-Raphson for large numbers, long division for small + pub fn div_rem(a: Self, b: Self) DivResult { + if (b.is_zero()) { + // Division by zero: return zeros + return DivResult{ .q = zero(), .r = zero() }; + } + + // For small numbers, use native division via i64 + if (a.len <= I64_DIV_THRESHOLD and b.len <= I64_DIV_THRESHOLD) { + const a_val = a.to_i64(); + const b_val = b.to_i64(); + + if (b_val == 0) { + return DivResult{ .q = zero(), .r = zero() }; + } + + const q_val = @divTrunc(a_val, b_val); + const r_val = @rem(a_val, b_val); + + return DivResult{ + .q = from_i64(q_val), + .r = from_i64(r_val), + }; + } + + // For larger numbers, use long division + return a.div_rem_long(b); + } + + /// Long division for large numbers + /// Algorithm: shift and subtract method + /// Complexity: O(len(a) * len(b)) + fn div_rem_long(a: Self, b: Self) DivResult { + const cmp = a.compare_abs(b); + if (cmp < 0) { + // |a| < |b|: quotient = 0, remainder = a + return DivResult{ .q = zero(), .r = a }; + } + if (cmp == 0) { + // |a| == |b| + if (a.is_negative() == b.is_negative()) { + return DivResult{ .q = from_i64(1), .r = zero() }; + } else { + return DivResult{ .q = from_i64(-1), .r = zero() }; + } + } + + // Determine result sign + const result_neg = a.is_negative() != b.is_negative(); + + // Work with absolute values + var remainder = a.abs(); + const divisor = b.abs(); + var quotient = zero(); + + // Find scale (shift to align MSB) + var scale : usize = 0; + if (remainder.len > divisor.len) { + scale = remainder.len - divisor.len; + } + + // Long division + var pos : usize = scale + 1; + while (pos > 0) { + pos -= 1; + + // Shift divisor to current position + const shifted_divisor = divisor.shift_left(pos); + + // Find quotient trit + var q_trit : i8 = 0; + + // Try +1 + if (!remainder.is_negative() and remainder.compare_abs(shifted_divisor) >= 0) { + const test_sub = remainder.sub(shifted_divisor); + if (test_sub.abs().compare_abs(remainder.abs()) <= 0) { + q_trit = 1; + remainder = test_sub; + } + } + + // Try -1 + if (q_trit == 0 and remainder.is_negative()) { + const test_add = remainder.add(shifted_divisor); + if (test_add.abs().compare_abs(remainder.abs()) < 0) { + q_trit = -1; + remainder = test_add; + } + } + + quotient.trits[pos] = @intCast(q_trit); + if (pos >= quotient.len and q_trit != 0) { + quotient.len = pos + 1; + } + } + + quotient.normalize(); + remainder.normalize(); + + // Adjust signs + if (result_neg) { + quotient = quotient.negate(); + } + if (a.is_negative() and !remainder.is_zero()) { + remainder = remainder.negate(); + } + + return DivResult{ .q = quotient, .r = remainder }; + } + + /// Division (quotient only) + pub fn div(a: Self, b: Self) Self { + return a.div_rem(b).q; + } + + /// Modulo (remainder only) + pub fn mod(a: Self, b: Self) Self { + return a.div_rem(b).r; + } + + // ======================================================================== + // Shift Operations + // ======================================================================== + + /// Shift left by n trits (multiply by 3^n) + /// Complexity: O(len + n) + pub fn shift_left(self: Self, n: usize) Self { + if (n == 0) { + return self; + } + + var result = zero(); + var i : usize = 0; + while (i < self.len) { + if (i + n < MAX_TRITS) { + result.trits[i + n] = self.trits[i]; + } + i = i + 1; + } + result.len = @min(self.len + n, MAX_TRITS); + result.normalize(); + return result; + } + + /// Shift right by n trits (divide by 3^n, truncate) + /// Complexity: O(len - n) + pub fn shift_right(self: Self, n: usize) Self { + if (n >= self.len) { + return zero(); + } + + var result = zero(); + var i = n; + while (i < self.len) { + result.trits[i - n] = self.trits[i]; + i = i + 1; + } + result.len = self.len - n; + result.normalize(); + return result; + } + + // ======================================================================== + // Newton-Raphson Division (for very large numbers) + // ======================================================================== + + /// Newton-Raphson reciprocal approximation + /// Computes approximation of 3^precision / b + /// Iteration: x_{n+1} = x_n * (2 - b * x_n / 3^precision) + /// Complexity: O(log(precision) * mul_cost) + pub fn newton_reciprocal(b: Self, precision: usize) Self { + if (b.is_zero()) { + return zero(); + } + + const b_abs = b.abs(); + + // Initial guess: 3^(precision - b.len + 1) + var x = zero(); + const initial_pos = if (precision > b_abs.len) { + precision - b_abs.len + 1 + } else { + 1 + }; + if (initial_pos < MAX_TRITS) { + x.trits[initial_pos] = TRIT_POS; + x.len = initial_pos + 1; + } else { + x.trits[0] = TRIT_POS; + x.len = 1; + } + + const two = from_i64(2); + const max_iterations : usize = 10; + + var iter : usize = 0; + while (iter < max_iterations) { + // x = x * (2 - b * x / 3^precision) + const bx = b_abs.mul(x); + const two_scaled = two.shift_left(precision); + const diff = two_scaled.sub(bx); + const x_new = x.mul(diff).shift_right(precision); + + // Check convergence + if (x_new.compare(x) == 0) { + x = x_new; + break; + } + + x = x_new; + iter = iter + 1; + } + + if (b.is_negative()) { + return x.negate(); + } + return x; + } + + /// Fast division using Newton-Raphson for very large numbers + /// Computes a / b using reciprocal approximation + /// Complexity: O(mul_cost * log(precision)) + pub fn div_newton(a: Self, b: Self) DivResult { + if (b.is_zero()) { + return DivResult{ .q = zero(), .r = zero() }; + } + + // For small numbers, use regular division + if (a.len <= I64_DIV_THRESHOLD and b.len <= I64_DIV_THRESHOLD) { + return a.div_rem(b); + } + + // Compute precision needed + const precision = @max(a.len, b.len) + 10; + + // Get reciprocal of b + const recip = b.newton_reciprocal(precision); + + // Compute a * recip / 3^precision + const product = a.mul(recip); + var quotient = product.shift_right(precision); + + // Compute remainder: r = a - q * b + const qb = quotient.mul(b); + var remainder = a.sub(qb); + + // Adjust if remainder is out of range + while (!remainder.is_zero() and remainder.abs().compare_abs(b.abs()) >= 0) { + if (remainder.is_negative() == b.is_negative()) { + remainder = remainder.sub(b); + quotient = quotient.add(from_i64(1)); + } else { + remainder = remainder.add(b); + quotient = quotient.sub(from_i64(1)); + } + } + + return DivResult{ .q = quotient, .r = remainder }; + } +}; + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "big_int_zero_creation" { + // Verify zero() creates valid zero BigInt + const z = BigInt.zero(); + try std.testing.expect(z.is_zero() == true); + try std.testing.expect(z.len == 1); + try std.testing.expect(z.trits[0] == TRIT_ZERO); +} + +test "big_int_from_i64_zero" { + // Verify from_i64(0) creates zero + const z = BigInt.from_i64(0); + try std.testing.expect(z.is_zero() == true); +} + +test "big_int_from_i64_positive" { + // Verify from_i64 creates correct positive BigInt + const a = BigInt.from_i64(42); + const b = BigInt.from_i64(100); + const c = BigInt.from_i64(1); + try std.testing.expectEqual(@as(i64, 42), a.to_i64()); + try std.testing.expectEqual(@as(i64, 100), b.to_i64()); + try std.testing.expectEqual(@as(i64, 1), c.to_i64()); +} + +test "big_int_from_i64_negative" { + // Verify from_i64 creates correct negative BigInt + const a = BigInt.from_i64(-42); + const b = BigInt.from_i64(-100); + const c = BigInt.from_i64(-1); + try std.testing.expectEqual(@as(i64, -42), a.to_i64()); + try std.testing.expectEqual(@as(i64, -100), b.to_i64()); + try std.testing.expectEqual(@as(i64, -1), c.to_i64()); +} + +test "big_int_to_i64_roundtrip" { + // Verify to_i64(from_i64(x)) == x for various values + const values = [_]i64{ 0, 1, -1, 2, -2, 10, -10, 100, -100, 1000, -1000, 12345, -12345 }; + for (values) |val| { + const big = BigInt.from_i64(val); + const back = big.to_i64(); + try std.testing.expectEqual(val, back); + } +} + +test "big_int_normalize" { + // Verify normalize removes leading zeros + var a = BigInt.zero(); + a.trits[0] = TRIT_POS; + a.trits[1] = TRIT_ZERO; + a.trits[2] = TRIT_ZERO; + a.trits[3] = TRIT_ZERO; + a.len = 4; + a.normalize(); + try std.testing.expect(a.len == 1); + try std.testing.expect(a.trits[0] == TRIT_POS); +} + +test "big_int_is_negative" { + // Verify is_negative works correctly + const pos = BigInt.from_i64(42); + const neg = BigInt.from_i64(-42); + const z = BigInt.zero(); + try std.testing.expect(pos.is_negative() == false); + try std.testing.expect(neg.is_negative() == true); + try std.testing.expect(z.is_negative() == false); +} + +test "big_int_is_positive" { + // Verify is_positive works correctly + const pos = BigInt.from_i64(42); + const neg = BigInt.from_i64(-42); + const z = BigInt.zero(); + try std.testing.expect(pos.is_positive() == true); + try std.testing.expect(neg.is_positive() == false); + try std.testing.expect(z.is_positive() == false); +} + +test "big_int_signum" { + // Verify signum returns correct sign + const pos = BigInt.from_i64(42); + const neg = BigInt.from_i64(-42); + const z = BigInt.zero(); + try std.testing.expectEqual(@as(i8, 1), pos.signum()); + try std.testing.expectEqual(@as(i8, -1), neg.signum()); + try std.testing.expectEqual(@as(i8, 0), z.signum()); +} + +test "big_int_negate" { + // Verify negate flips sign + const a = BigInt.from_i64(42); + const b = BigInt.from_i64(-42); + const c = BigInt.zero(); + const neg_a = a.negate(); + const neg_b = b.negate(); + const neg_c = c.negate(); + try std.testing.expectEqual(@as(i64, -42), neg_a.to_i64()); + try std.testing.expectEqual(@as(i64, 42), neg_b.to_i64()); + try std.testing.expectEqual(@as(i64, 0), neg_c.to_i64()); +} + +test "big_int_abs" { + // Verify abs returns absolute value + const a = BigInt.from_i64(42); + const b = BigInt.from_i64(-42); + const c = BigInt.zero(); + try std.testing.expectEqual(@as(i64, 42), a.abs().to_i64()); + try std.testing.expectEqual(@as(i64, 42), b.abs().to_i64()); + try std.testing.expectEqual(@as(i64, 0), c.abs().to_i64()); +} + +test "big_int_add_positive" { + // Verify addition of positive numbers + const a = BigInt.from_i64(123); + const b = BigInt.from_i64(456); + const sum = a.add(b); + try std.testing.expectEqual(@as(i64, 579), sum.to_i64()); +} + +test "big_int_add_negative" { + // Verify addition with negative numbers + const a = BigInt.from_i64(-100); + const b = BigInt.from_i64(50); + const sum = a.add(b); + try std.testing.expectEqual(@as(i64, -50), sum.to_i64()); +} + +test "big_int_add_zero" { + // Verify adding zero is identity + const a = BigInt.from_i64(42); + const z = BigInt.zero(); + const sum1 = a.add(z); + const sum2 = z.add(a); + try std.testing.expectEqual(@as(i64, 42), sum1.to_i64()); + try std.testing.expectEqual(@as(i64, 42), sum2.to_i64()); +} + +test "big_int_add_commutative" { + // Verify addition is commutative: a + b = b + a + const a = BigInt.from_i64(123); + const b = BigInt.from_i64(456); + const sum1 = a.add(b); + const sum2 = b.add(a); + try std.testing.expect(sum1.compare(sum2) == 0); +} + +test "big_int_sub" { + // Verify subtraction + const a = BigInt.from_i64(1000); + const b = BigInt.from_i64(300); + const diff = a.sub(b); + try std.testing.expectEqual(@as(i64, 700), diff.to_i64()); +} + +test "big_int_sub_negative" { + // Verify subtraction giving negative result + const a = BigInt.from_i64(100); + const b = BigInt.from_i64(300); + const diff = a.sub(b); + try std.testing.expectEqual(@as(i64, -200), diff.to_i64()); +} + +test "big_int_mul_simple_positive" { + // Verify simple multiplication + const a = BigInt.from_i64(12); + const b = BigInt.from_i64(34); + const prod = a.mul_simple(b); + try std.testing.expectEqual(@as(i64, 408), prod.to_i64()); +} + +test "big_int_mul_simple_negative" { + // Verify multiplication with negative + const a = BigInt.from_i64(-7); + const b = BigInt.from_i64(8); + const prod = a.mul_simple(b); + try std.testing.expectEqual(@as(i64, -56), prod.to_i64()); +} + +test "big_int_mul_zero" { + // Verify multiplication by zero + const a = BigInt.from_i64(12345); + const z = BigInt.zero(); + const prod = a.mul(z); + try std.testing.expect(prod.is_zero() == true); +} + +test "big_int_mul_karatsuba" { + // Verify Karatsuba multiplication + const a = BigInt.from_i64(12345); + const b = BigInt.from_i64(67890); + const prod = a.mul_karatsuba(b); + try std.testing.expectEqual(@as(i64, 838102050), prod.to_i64()); +} + +test "big_int_mul_consistency" { + // Verify mul_simple and mul_karatsuba give same result + const a = BigInt.from_i64(12345); + const b = BigInt.from_i64(67890); + const prod_simple = a.mul_simple(b); + const prod_karat = a.mul_karatsuba(b); + try std.testing.expect(prod_simple.compare(prod_karat) == 0); +} + +test "big_int_compare_equal" { + // Verify compare for equal numbers + const a = BigInt.from_i64(42); + const b = BigInt.from_i64(42); + try std.testing.expect(a.compare(b) == 0); +} + +test "big_int_compare_less" { + // Verify compare for a < b + const a = BigInt.from_i64(42); + const b = BigInt.from_i64(100); + try std.testing.expect(a.compare(b) == -1); +} + +test "big_int_compare_greater" { + // Verify compare for a > b + const a = BigInt.from_i64(100); + const b = BigInt.from_i64(42); + try std.testing.expect(a.compare(b) == 1); +} + +test "big_int_compare_negative" { + // Verify compare with negative numbers + const a = BigInt.from_i64(-100); + const b = BigInt.from_i64(-42); + try std.testing.expect(a.compare(b) == -1); +} + +test "big_int_div_exact" { + // Verify exact division + const a = BigInt.from_i64(81); + const b = BigInt.from_i64(9); + const result = a.div_rem(b); + try std.testing.expectEqual(@as(i64, 9), result.q.to_i64()); + try std.testing.expect(result.r.is_zero() == true); +} + +test "big_int_div_with_remainder" { + // Verify division with remainder + const a = BigInt.from_i64(10); + const b = BigInt.from_i64(3); + const result = a.div_rem(b); + try std.testing.expectEqual(@as(i64, 3), result.q.to_i64()); + try std.testing.expectEqual(@as(i64, 1), result.r.to_i64()); +} + +test "big_int_div_negative" { + // Verify division with negative numbers + const a = BigInt.from_i64(-100); + const b = BigInt.from_i64(7); + const result = a.div_rem(b); + try std.testing.expectEqual(@as(i64, -14), result.q.to_i64()); + try std.testing.expectEqual(@as(i64, -2), result.r.to_i64()); +} + +test "big_int_div_by_negative" { + // Verify division by negative divisor + const a = BigInt.from_i64(100); + const b = BigInt.from_i64(-7); + const result = a.div_rem(b); + try std.testing.expectEqual(@as(i64, -14), result.q.to_i64()); + try std.testing.expectEqual(@as(i64, 2), result.r.to_i64()); +} + +test "big_int_shift_left" { + // Verify shift left (multiply by 3^n) + const a = BigInt.from_i64(10); + const shifted = a.shift_left(2); + try std.testing.expectEqual(@as(i64, 90), shifted.to_i64()); // 10 * 9 = 90 +} + +test "big_int_shift_right" { + // Verify shift right (divide by 3^n) + const a = BigInt.from_i64(27); + const shifted = a.shift_right(1); + try std.testing.expectEqual(@as(i64, 9), shifted.to_i64()); // 27 / 3 = 9 +} + +test "big_int_shift_identity" { + // Verify shift by zero is identity + const a = BigInt.from_i64(42); + const shifted = a.shift_left(0); + try std.testing.expect(shifted.compare(a) == 0); +} + +test "big_int_newton_div" { + // Verify Newton-Raphson division + const a = BigInt.from_i64(1000000); + const b = BigInt.from_i64(1234); + const result = a.div_newton(b); + try std.testing.expectEqual(@as(i64, 810), result.q.to_i64()); + try std.testing.expectEqual(@as(i64, 460), result.r.to_i64()); +} + +test "big_int_newton_div_consistency" { + // Verify Newton division gives same result as regular division + const a = BigInt.from_i64(1000000); + const b = BigInt.from_i64(1234); + const result_newton = a.div_newton(b); + const result_regular = a.div_rem(b); + try std.testing.expect(result_newton.q.compare(result_regular.q) == 0); + try std.testing.expect(result_newton.r.compare(result_regular.r) == 0); +} + +test "big_int_large_add" { + // Verify addition of larger numbers + const a = BigInt.from_i64(1000000000); + const b = BigInt.from_i64(999999999); + const sum = a.add(b); + try std.testing.expectEqual(@as(i64, 1999999999), sum.to_i64()); +} + +test "big_int_large_mul" { + // Verify multiplication of larger numbers + const a = BigInt.from_i64(123456); + const b = BigInt.from_i64(789012); + const prod = a.mul(b); + try std.testing.expectEqual(@as(i64, 97406197072), prod.to_i64()); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant big_int_zero_is_neutral_add { + // Zero is neutral for addition: a + 0 = a + const values = [_]i64{ 1, -1, 42, -42, 100, -100 }; + inline for (values) |val| { + const a = BigInt.from_i64(val); + const z = BigInt.zero(); + @compileAssert(a.add(z).compare(a) == 0); + @compileAssert(z.add(a).compare(a) == 0); + } +} + +invariant big_int_zero_is_annihilator_mul { + // Zero is annihilator for multiplication: a * 0 = 0 + const values = [_]i64{ 1, -1, 42, -42, 100, -100 }; + inline for (values) |val| { + const a = BigInt.from_i64(val); + const z = BigInt.zero(); + @compileAssert(a.mul(z).is_zero() == true); + } +} + +invariant big_int_add_commutative { + // Addition is commutative: a + b = b + a + const vals_a = [_]i64{ 1, 2, 10, 100 }; + const vals_b = [_]i64{ 1, 2, 10, 100 }; + inline for (vals_a) |a_val| { + inline for (vals_b) |b_val| { + const a = BigInt.from_i64(a_val); + const b = BigInt.from_i64(b_val); + @compileAssert(a.add(b).compare(b.add(a)) == 0); + } + } +} + +invariant big_int_add_associative { + // Addition is associative: (a + b) + c = a + (b + c) + const a = BigInt.from_i64(12); + const b = BigInt.from_i64(34); + const c = BigInt.from_i64(56); + @compileAssert(a.add(b).add(c).compare(a.add(b.add(c))) == 0); +} + +invariant big_int_mul_commutative { + // Multiplication is commutative: a * b = b * a + const vals_a = [_]i64{ 1, 2, 10 }; + const vals_b = [_]i64{ 1, 2, 10 }; + inline for (vals_a) |a_val| { + inline for (vals_b) |b_val| { + const a = BigInt.from_i64(a_val); + const b = BigInt.from_i64(b_val); + @compileAssert(a.mul(b).compare(b.mul(a)) == 0); + } + } +} + +invariant big_int_mul_associative { + // Multiplication is associative: (a * b) * c = a * (b * c) + const a = BigInt.from_i64(3); + const b = BigInt.from_i64(5); + const c = BigInt.from_i64(7); + @compileAssert(a.mul(b).mul(c).compare(a.mul(b.mul(c))) == 0); +} + +invariant big_int_distributive { + // Multiplication distributes over addition: a * (b + c) = a*b + a*c + const a = BigInt.from_i64(3); + const b = BigInt.from_i64(5); + const c = BigInt.from_i64(7); + @compileAssert(a.mul(b.add(c)).compare(a.mul(b).add(a.mul(c))) == 0); +} + +invariant big_int_negate_twice { + // Double negation: -(-a) = a + const values = [_]i64{ 1, -1, 42, -42, 100 }; + inline for (values) |val| { + const a = BigInt.from_i64(val); + @compileAssert(a.negate().negate().compare(a) == 0); + } +} + +invariant big_int_abs_idempotent { + // Absolute value is idempotent: | |a| | = |a| + const values = [_]i64{ 1, -1, 42, -42, 100, -100 }; + inline for (values) |val| { + const a = BigInt.from_i64(val); + @compileAssert(a.abs().abs().compare(a.abs()) == 0); + } +} + +invariant big_int_sub_equals_add_negate { + // Subtraction equals addition of negation: a - b = a + (-b) + const vals_a = [_]i64{ 10, 100 }; + const vals_b = [_]i64{ 3, 7 }; + inline for (vals_a) |a_val| { + inline for (vals_b) |b_val| { + const a = BigInt.from_i64(a_val); + const b = BigInt.from_i64(b_val); + @compileAssert(a.sub(b).compare(a.add(b.negate())) == 0); + } + } +} + +invariant big_int_division_invariant { + // Division invariant: a = q * b + r, |r| < |b| + const vals_a = [_]i64{ 10, 100, 1000 }; + const vals_b = [_]i64{ 3, 7, 13 }; + inline for (vals_a) |a_val| { + inline for (vals_b) |b_val| { + const a = BigInt.from_i64(a_val); + const b = BigInt.from_i64(b_val); + const result = a.div_rem(b); + const reconstructed = result.q.mul(b).add(result.r); + @compileAssert(reconstructed.compare(a) == 0); + @compileAssert(result.r.compare_abs(b) < 0 or result.r.is_zero()); + } + } +} + +invariant big_int_compare_transitive { + // Comparison is transitive: a < b and b < c implies a < c + const a = BigInt.from_i64(1); + const b = BigInt.from_i64(10); + const c = BigInt.from_i64(100); + @compileAssert(a.compare(b) < 0); + @compileAssert(b.compare(c) < 0); + @compileAssert(a.compare(c) < 0); +} + +invariant big_int_compare_total_ordering { + // Every pair is comparable + const vals_a = [_]i64{ 1, -1, 42, -42 }; + const vals_b = [_]i64{ 1, -1, 42, -42 }; + inline for (vals_a) |a_val| { + inline for (vals_b) |b_val| { + const a = BigInt.from_i64(a_val); + const b = BigInt.from_i64(b_val); + const cmp = a.compare(b); + @compileAssert(cmp == -1 or cmp == 0 or cmp == 1); + } + } +} + +invariant big_int_shift_consistency { + // Shift left then right returns original (with truncation) + const values = [_]i64{ 10, 100, 1000 }; + inline for (values) |val| { + const a = BigInt.from_i64(val); + const shifted = a.shift_left(2); + const restored = shifted.shift_right(2); + @compileAssert(restored.compare(a) == 0); + } +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "big_int_add_latency" { + // Measure: cycles for BigInt addition + // Target: < 1000 cycles for 64-trit numbers + @setEvalBranchQuota(10000); + var result : BigInt = undefined; + const a = BigInt.from_i64(12345); + const b = BigInt.from_i64(6789); + for (0..1000) |_| { + result = a.add(b); + } + _ = result; +} + +bench "big_int_sub_latency" { + // Measure: cycles for BigInt subtraction + // Target: < 1000 cycles for 64-trit numbers + @setEvalBranchQuota(10000); + var result : BigInt = undefined; + const a = BigInt.from_i64(12345); + const b = BigInt.from_i64(6789); + for (0..1000) |_| { + result = a.sub(b); + } + _ = result; +} + +bench "big_int_mul_simple_latency" { + // Measure: cycles for simple multiplication + // Target: < 5000 cycles for 32-trit numbers + @setEvalBranchQuota(10000); + var result : BigInt = undefined; + const a = BigInt.from_i64(12345); + const b = BigInt.from_i64(6789); + for (0..100) |_| { + result = a.mul_simple(b); + } + _ = result; +} + +bench "big_int_mul_karatsuba_latency" { + // Measure: cycles for Karatsuba multiplication + // Target: < 3000 cycles for 64-trit numbers + @setEvalBranchQuota(10000); + var result : BigInt = undefined; + const a = BigInt.from_i64(123456789); + const b = BigInt.from_i64(987654321); + for (0..100) |_| { + result = a.mul_karatsuba(b); + } + _ = result; +} + +bench "big_int_div_latency" { + // Measure: cycles for division + // Target: < 10000 cycles for 64-trit numbers + @setEvalBranchQuota(10000); + var result : BigInt = undefined; + const a = BigInt.from_i64(1000000); + const b = BigInt.from_i64(1234); + for (0..100) |_| { + result = a.div(b); + } + _ = result; +} + +bench "big_int_shift_latency" { + // Measure: cycles for shift operations + // Target: < 500 cycles + @setEvalBranchQuota(10000); + var result : BigInt = undefined; + const a = BigInt.from_i64(12345); + for (0..1000) |_| { + result = a.shift_left(10); + _ = result.shift_right(10); + } + _ = result; +} + +bench "big_int_normalize_latency" { + // Measure: cycles for normalization + // Target: < 100 cycles + @setEvalBranchQuota(10000); + var a = BigInt.from_i64(12345); + a.trits[10] = TRIT_ZERO; + a.trits[11] = TRIT_ZERO; + a.len = 12; + for (0..1000) |_| { + a.normalize(); + } + _ = a; +} + +bench "big_int_compare_latency" { + // Measure: cycles for comparison + // Target: < 500 cycles + @setEvalBranchQuota(10000); + var cmp : i8 = 0; + const a = BigInt.from_i64(12345); + const b = BigInt.from_i64(6789); + for (0..1000) |_| { + cmp = a.compare(b); + } + _ = cmp; +} + +bench "big_int_negate_latency" { + // Measure: cycles for negation + // Target: < 500 cycles for 64-trit numbers + @setEvalBranchQuota(10000); + var result : BigInt = undefined; + const a = BigInt.from_i64(12345); + for (0..1000) |_| { + result = a.negate(); + } + _ = result; +} + +bench "big_int_abs_latency" { + // Measure: cycles for absolute value + // Target: < 500 cycles + @setEvalBranchQuota(10000); + var result : BigInt = undefined; + const a = BigInt.from_i64(-12345); + for (0..1000) |_| { + result = a.abs(); + } + _ = result; +} + +bench "big_int_to_i64_latency" { + // Measure: cycles for to_i64 conversion + // Target: < 1000 cycles + @setEvalBranchQuota(10000); + var result : i64 = 0; + const a = BigInt.from_i64(12345); + for (0..1000) |_| { + result = a.to_i64(); + } + _ = result; +} + +bench "big_int_from_i64_latency" { + // Measure: cycles for from_i64 conversion + // Target: < 1000 cycles + @setEvalBranchQuota(10000); + var result : BigInt = undefined; + for (0..1000) |i| { + result = BigInt.from_i64(@intCast(i)); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/ternary/hybrid_arithmetic.t27 b/apps/website/public/t27/specs/ternary/hybrid_arithmetic.t27 new file mode 100644 index 0000000000..2f588f12b4 --- /dev/null +++ b/apps/website/public/t27/specs/ternary/hybrid_arithmetic.t27 @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: Apache-2.0 +// Module: Hybrid Arithmetic - Packed Storage with Unpacked Computation +// phi^2 + 1/phi^2 = 3 | TRINITY + +module HybridArithmetic { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; // Trit enum + use base::ops; // trit operations + use numeric::gf16; // GF16 for numeric scalars + + // ======================================================================== + // 1. Storage Mode Enumeration + // ======================================================================== + + // StorageMode: dual-mode storage for optimal performance + // packed_mode: storage as 5-trit-per-byte (memory efficient) + // unpacked_mode: storage as individual trits (compute efficient) + pub const StorageMode = enum(u8) { + packed_mode, + unpacked_mode, + }; + + // ======================================================================== + // 2. Hybrid BigInt Type + // ======================================================================== + + // HybridBigInt: dual-mode big integer for ternary computation + // Stores data in both packed and unpacked forms + // Uses packed_mode for storage, unpacked_mode for SIMD computation + pub struct HybridBigInt { + packed_data : []u8, // Packed trits (5 per byte) + unpacked_data : []i8, // Unpacked trits (-1, 0, +1) + mode : StorageMode, // Current storage mode + sign : i8, // Sign: -1, 0, +1 + trit_count : u16, // Number of trits (not bytes) + } + + // ======================================================================== + // 3. SIMD Vector Types (platform-agnostic) + // ======================================================================== + + // Vec32i8: logical vector of 32 signed 8-bit integers + // Used for SIMD-accelerated trit operations + // Platform-specific: XMM/YMM registers, implementation detail omitted + pub struct Vec32i8 { + data : [32]i8, + }; + + // Vec32i16: logical vector of 32 signed 16-bit integers + // Used for SIMD-accelerated carry propagation + // Platform-specific: implementation detail omitted + pub struct Vec32i16 { + data : [16]i16, + }; + + // ======================================================================== + // 4. Hybrid BigInt Creation + // ======================================================================== + + // zero() -> HybridBigInt + // Create zero HybridBigInt + // Returns HybridBigInt with sign=0, trit_count=0, mode=packed_mode + // Complexity: O(1) + pub fn zero() -> HybridBigInt; + + // ensureUnpacked(bigint: &HybridBigInt) + // Ensure HybridBigInt is in unpacked mode for computation + // If mode is packed_mode, unpack to unpacked_data and set mode + // This is a no-op if already in unpacked_mode + // Complexity: O(n) where n = trit_count + pub fn ensureUnpacked(bigint: &HybridBigInt); + + // ======================================================================== + // 5. Arithmetic Operations + // ======================================================================== + + // add(a: &HybridBigInt, b: &HybridBigInt) -> HybridBigInt + // Add two HybridBigInt values + // Returns sum in unpacked_mode for further computation + // Complexity: O(n) where n = max(trit_count of a, b) + pub fn add(a: &HybridBigInt, b: &HybridBigInt) -> HybridBigInt; + + // addSimd(a: &HybridBigInt, b: &HybridBigInt) -> HybridBigInt + // Add two HybridBigInt values using SIMD acceleration + // Uses Vec32i8 for parallel trit addition + // Returns sum in unpacked_mode + // Platform-specific: uses XMM/YMM registers for parallel ops + // Complexity: O(n/32) where n = max(trit_count of a, b) + pub fn addSimd(a: &HybridBigInt, b: &HybridBigInt) -> HybridBigInt; + + // sub(a: &HybridBigInt, b: &HybridBigInt) -> HybridBigInt + // Subtract b from a using HybridBigInt arithmetic + // Returns difference in unpacked_mode + // Complexity: O(n) where n = max(trit_count of a, b) + pub fn sub(a: &HybridBigInt, b: &HybridBigInt) -> HybridBigInt; + + // mul(a: &HybridBigInt, b: &HybridBigInt) -> HybridBigInt + // Multiply two HybridBigInt values + // Returns product in unpacked_mode + // Complexity: O(n*m) where n,m are trit_counts + pub fn mul(a: &HybridBigInt, b: &HybridBigInt) -> HybridBigInt; + + // dotProduct(a: &HybridBigInt, b: &HybridBigInt) -> i32 + // Compute dot product of two HybridBigInt values + // Returns sum of element-wise products + // Used for similarity metrics in ternary vector spaces + // Complexity: O(n) where n = min(trit_count of a, b) + pub fn dotProduct(a: &HybridBigInt, b: &HybridBigInt) -> i32; + + // ======================================================================== + // 6. Mode Transition Functions + // ======================================================================== + + // pack(bigint: &HybridBigInt) + // Transition from unpacked_mode to packed_mode + // Encodes unpacked_data to packed_data + // Sets mode to packed_mode after packing + // This is a no-op if already in packed_mode + // Complexity: O(n) where n = trit_count + pub fn pack(bigint: &HybridBigInt); + + // unpack(bigint: &HybridBigInt) + // Transition from packed_mode to unpacked_mode + // Decodes packed_data to unpacked_data + // Sets mode to unpacked_mode after unpacking + // This is a no-op if already in unpacked_mode + // Complexity: O(n) where n = trit_count + pub fn unpack(bigint: &HybridBigInt); + + // ======================================================================== + // 7. Helper Functions + // ======================================================================== + + // createPacked(trits: []i8, count: u16) -> HybridBigInt + // Create HybridBigInt in packed_mode from trits + // Encodes trits to packed_data + // Complexity: O(n) where n = count + pub fn createPacked(trits: []i8, count: u16) -> HybridBigInt; + + // createUnpacked(trits: []i8, count: u16) -> HybridBigInt + // Create HybridBigInt in unpacked_mode from trits + // Copies trits to unpacked_data + // Complexity: O(n) where n = count + pub fn createUnpacked(trits: []i8, count: u16) -> HybridBigInt; + + // ======================================================================== + // TDD - Tests + // ======================================================================== + + test hybrid_zero_is_zero + // Verify: zero() returns zero HybridBigInt + given result = zero() + then result.sign == 0 and result.trit_count == 0 + + test hybrid_zero_mode_is_packed + // Verify: zero() returns HybridBigInt in packed_mode + given result = zero() + then result.mode == packed_mode + + test hybrid_ensureUnpacked_on_packed + // Verify: ensureUnpacked() transitions packed to unpacked + given packed = createPacked([1, 0, -1], 3) + when ensureUnpacked(packed) + then packed.mode == unpacked_mode + + test hybrid_ensureUnpacked_noop_when_unpacked + // Verify: ensureUnpacked() is no-op when already unpacked + given unpacked = createUnpacked([1, 0, -1], 3) + and initial_mode = unpacked.mode + when ensureUnpacked(unpacked) + then unpacked.mode == initial_mode + + test hybrid_add_zero_identity + // Verify: adding zero returns same value + given a = createUnpacked([1, 0, -1], 3) + and zero_val = zero() + when result = add(a, zero_val) + then result.trit_count == 3 + + test hybrid_add_commutative + // Verify: a + b = b + a + given a = createUnpacked([1, 0], 2) + and b = createUnpacked([0, 1], 2) + when ab = add(a, b) + and ba = add(b, a) + then ab.trit_count == ba.trit_count + + test hybrid_sub_zero_identity + // Verify: subtracting zero returns same value + given a = createUnpacked([1, 0, -1], 3) + and zero_val = zero() + when result = sub(a, zero_val) + then result.trit_count == 3 + + test hybrid_sub_from_zero_negates + // Verify: 0 - a = -a + given a = createUnpacked([1, 0, 1], 3) + and zero_val = zero() + when result = sub(zero_val, a) + then result.sign == -a.sign + + test hybrid_mul_by_zero_returns_zero + // Verify: multiplying by zero returns zero + given a = createUnpacked([1, 0, 1], 3) + and zero_val = zero() + when result = mul(a, zero_val) + then result.trit_count == 0 + + test hybrid_mul_by_one_returns_same + // Verify: multiplying by one returns same value + given a = createUnpacked([1, 0, 1], 3) + and one = createUnpacked([1], 1) + when result = mul(a, one) + then result.trit_count == a.trit_count + + test hybrid_addSimd_matches_add + // Verify: SIMD addition matches scalar addition + given a = createUnpacked([1, 0, -1, 1, 0], 5) + and b = createUnpacked([0, 1, 1, 0, 1], 5) + when scalar_result = add(a, b) + and simd_result = addSimd(a, b) + then scalar_result.trit_count == simd_result.trit_count + + test hybrid_dotProduct_positive + // Verify: dot product of identical vectors is positive + given a = createUnpacked([1, 1, 1], 3) + and b = createUnpacked([1, 1, 1], 3) + when result = dotProduct(a, b) + then result > 0 + + test hybrid_dotProduct_orthogonal + // Verify: dot product of orthogonal vectors is zero + given a = createUnpacked([1, 0, 0], 3) + and b = createUnpacked([0, 1, 0], 3) + when result = dotProduct(a, b) + then result == 0 + + test hybrid_dotProduct_negative + // Verify: dot product of opposite vectors is negative + given a = createUnpacked([1, 1, 1], 3) + and b = createUnpacked([-1, -1, -1], 3) + when result = dotProduct(a, b) + then result < 0 + + test hybrid_pack_transition + // Verify: pack() transitions to packed_mode + given unpacked = createUnpacked([1, 0, -1], 3) + when pack(unpacked) + then unpacked.mode == packed_mode + + test hybrid_unpack_transition + // Verify: unpack() transitions to unpacked_mode + given packed = createPacked([1, 0, -1], 3) + when unpack(packed) + then packed.mode == unpacked_mode + + test hybrid_pack_roundtrip + // Verify: pack then unpack preserves data + given original = createUnpacked([1, 0, -1, 1], 4) + when pack(original) + and unpack(original) + then original.trit_count == 4 + + test hybrid_createPacked_valid_trits + // Verify: createPacked() encodes trits correctly + given result = createPacked([1, 0, -1], 3) + then result.mode == packed_mode and result.trit_count == 3 + + test hybrid_createUnpacked_valid_trits + // Verify: createUnpacked() copies trits correctly + given result = createUnpacked([1, 0, -1], 3) + then result.mode == unpacked_mode and result.trit_count == 3 + + test hybrid_add_overflow_detection + // Verify: addition overflow is handled + given a = createUnpacked([1, 1, 1, 1, 1], 5) + and b = createUnpacked([1, 1, 1, 1, 1], 5) + when result = add(a, b) + then result.trit_count >= 5 + + test hybrid_mul_commutative + // Verify: a * b = b * a + given a = createUnpacked([1, 0], 2) + and b = createUnpacked([0, 1], 2) + when ab = mul(a, b) + and ba = mul(b, a) + then ab.trit_count == ba.trit_count + + test hybrid_mul_distributive + // Verify: a * (b + c) = a*b + a*c + given a = createUnpacked([1], 1) + and b = createUnpacked([1, 0], 2) + and c = createUnpacked([0, 1], 2) + and bc = add(b, c) + and lhs = mul(a, bc) + and ab = mul(a, b) + and ac = mul(a, c) + and rhs = add(ab, ac) + then lhs.trit_count == rhs.trit_count + + test hybrid_empty_bigint_is_zero + // Verify: empty HybridBigInt is zero + given empty = createUnpacked([] as []i8, 0) + then empty.trit_count == 0 and empty.sign == 0 + + test hybrid_sign_preserved + // Verify: sign is preserved through operations + given a = createUnpacked([1, 1], 2) + and b = createUnpacked([1], 1) + when result = mul(a, b) + then result.sign == 1 + + // ======================================================================== + // TDD - Invariants + // ======================================================================== + + invariant hybrid_add_identity + // Verify: adding zero returns same value + // This is verified by hybrid_add_zero_identity test + assert true; + + invariant hybrid_mul_commutative + // Verify: multiplication is commutative + // This is verified by hybrid_mul_commutative test + assert true; + + invariant hybrid_mul_distributive + // Verify: multiplication distributes over addition + // This is verified by hybrid_mul_distributive test + assert true; + + invariant hybrid_add_commutative + // Verify: addition is commutative + // This is verified by hybrid_add_commutative test + assert true; + + invariant pack_unpack_roundtrip + // Verify: pack then unpack preserves data + // This is verified by hybrid_pack_roundtrip test + assert true; + + invariant zero_is_additive_identity + // Verify: zero is additive identity + const zero_val = zero(); + assert zero_val.sign == 0 and zero_val.trit_count == 0; + + invariant zero_is_multiplicative_zero + // Verify: zero multiplied by anything is zero + const zero_val = zero(); + assert zero_val.trit_count == 0; + + invariant storage_mode_enum_valid + // Verify: StorageMode enum has exactly 2 values + // packed_mode = 0, unpacked_mode = 1 + assert packed_mode != unpacked_mode; + + invariant vec32i8_size_is_32 + // Verify: Vec32i8 has exactly 32 elements + // Platform-specific: implementation may vary + assert true; + + invariant vec32i16_size_is_16 + // Verify: Vec32i16 has exactly 16 elements + // Platform-specific: implementation may vary + assert true; + + invariant dotProduct_linear + // Verify: dot product is linear + // dot(a, b+c) = dot(a,b) + dot(a,c) + // This is verified by tests + assert true; + + invariant dotProduct_symmetric + // Verify: dot product is symmetric + // dot(a, b) = dot(b, a) + // This is verified by tests + assert true; + + invariant dotProduct_positive_for_same + // Verify: dot product of identical vectors is positive + // dot(a, a) > 0 for non-zero a + // This is verified by tests + assert true; + + invariant sign_is_trit_value + // Verify: sign field contains valid trit value + // sign must be -1, 0, or +1 + // This is enforced by type system + assert true; + + invariant trit_count_non_negative + // Verify: trit_count is always >= 0 + // This is enforced by type system + assert true; + + // ======================================================================== + // TDD - Benchmarks + // ======================================================================== + + bench hybrid_add_latency + // Measure: cycles for add() operation + // Target: < 100 cycles for 10-trit numbers + @setEvalBranchQuota(10000); + var a = createUnpacked([1, 1, 1, 1, 1], 5); + var b = createUnpacked([1, 0, -1, 1, 0], 5); + var result = add(a, b); + _ = result; + + bench hybrid_addSimd_latency + // Measure: cycles for addSimd() operation + // Target: < 50 cycles for 160-trit numbers (5x32 SIMD) + @setEvalBranchQuota(10000); + var a = createUnpacked([1] * 32, 32); + var b = createUnpacked([0] * 32, 32); + var result = addSimd(a, b); + _ = result; + + bench hybrid_sub_latency + // Measure: cycles for sub() operation + // Target: < 100 cycles for 10-trit numbers + @setEvalBranchQuota(10000); + var a = createUnpacked([1, 1, 1, 1, 1], 5); + var b = createUnpacked([1, 0, -1, 1, 0], 5); + var result = sub(a, b); + _ = result; + + bench hybrid_mul_latency + // Measure: cycles for mul() operation + // Target: < 500 cycles for 5-trit numbers + @setEvalBranchQuota(10000); + var a = createUnpacked([1, 1, 1], 3); + var b = createUnpacked([1, 0, 1], 3); + var result = mul(a, b); + _ = result; + + bench hybrid_dotProduct_latency + // Measure: cycles for dotProduct() operation + // Target: < 200 cycles for 32-trit vectors + @setEvalBranchQuota(10000); + var a = createUnpacked([1] * 32, 32); + var b = createUnpacked([1] * 32, 32); + var result = dotProduct(a, b); + _ = result; + + bench hybrid_pack_latency + // Measure: cycles for pack() operation + // Target: < 50 cycles for 32 trits + @setEvalBranchQuota(10000); + var unpacked = createUnpacked([1, 0, -1, 1] * 8, 32); + pack(unpacked); + _ = unpacked; + + bench hybrid_unpack_latency + // Measure: cycles for unpack() operation + // Target: < 50 cycles for 32 trits + @setEvalBranchQuota(10000); + var packed = createPacked([1, 0, -1, 1] * 8, 32); + unpack(packed); + _ = packed; + + bench hybrid_zero_latency + // Measure: cycles for zero() creation + // Target: < 10 cycles + @setEvalBranchQuota(10000); + var result = zero(); + _ = result; + + bench hybrid_ensureUnpacked_latency + // Measure: cycles for ensureUnpacked() operation + // Target: < 50 cycles for 32 trits + @setEvalBranchQuota(10000); + var packed = createPacked([1] * 32, 32); + ensureUnpacked(packed); + _ = packed; + + bench hybrid_createPacked_latency + // Measure: cycles for createPacked() operation + // Target: < 100 cycles for 32 trits + @setEvalBranchQuota(10000); + var result = createPacked([1] * 32, 32); + _ = result; + + bench hybrid_createUnpacked_latency + // Measure: cycles for createUnpacked() operation + // Target: < 50 cycles for 32 trits + @setEvalBranchQuota(10000); + var result = createUnpacked([1] * 32, 32); + _ = result; +} diff --git a/apps/website/public/t27/specs/ternary/hybrid_bigint.t27 b/apps/website/public/t27/specs/ternary/hybrid_bigint.t27 new file mode 100644 index 0000000000..19ce4ed4d0 --- /dev/null +++ b/apps/website/public/t27/specs/ternary/hybrid_bigint.t27 @@ -0,0 +1,1064 @@ +// HybridBigInt: Optimal Memory/Speed Trade-off +// Uses packed storage (4.5x memory savings) with unpacked computation +// SIMD-accelerated operations for high performance +// +// Author: Dmitrii Vasilev +// SPDX-License-Identifier: Apache-2.0 + +module hybrid_bigint; + +import numeric::gf16; +import tritype-base::Trit; +import ternary::bigint; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Maximum number of trits (3^10) +pub const MAX_TRITS: usize = 59049; + +/// Number of trits packed per byte (5 trits per byte) +pub const TRITS_PER_BYTE: usize = 5; + +/// Maximum packed bytes required +pub const MAX_PACKED_BYTES: usize = (MAX_TRITS + TRITS_PER_BYTE - 1) / TRITS_PER_BYTE; + +/// SIMD width for parallel trit operations (32 trits at once) +pub const SIMD_WIDTH: usize = 32; + +/// Number of SIMD chunks for MAX_TRITS +pub const SIMD_CHUNKS: usize = MAX_TRITS / SIMD_WIDTH; + +/// Memory efficiency factor (unpacked vs packed) +pub const MEMORY_EFFICIENCY: gf16 = 5.0; + +// ============================================================================ +// Types +// ============================================================================ + +/// Storage mode for HybridBigInt +/// Packed mode: 5 trits per byte, memory efficient +/// Unpacked mode: 1 trit per byte, compute efficient +pub const StorageMode = enum(u8) { + packed_mode, + unpacked_mode, +}; + +/// SIMD vector for 32 trits +pub type Vec32 = [SIMD_WIDTH]Trit; + +/// HybridBigInt combines packed storage with unpacked computation +/// - Stores in packed format for memory efficiency (4.5x savings) +/// - Unpacks lazily for computation +/// - Re-packs after computation if dirty +/// - Uses SIMD for parallel trit operations +pub struct HybridBigInt { + /// Packed storage (always valid, 5 trits per byte) + packed_data: [MAX_PACKED_BYTES]u8, + /// Unpacked cache (null when packed, allocated on heap when needed) + unpacked_cache: Option<[MAX_TRITS]Trit>, + /// Current storage mode + mode: StorageMode, + /// Number of significant trits + trit_len: usize, + /// Dirty flag: unpacked cache modified, needs re-pack + dirty: bool, +} + +// ============================================================================ +// SIMD Operations +// ============================================================================ + +/// SIMD add 32 trits in parallel with carry propagation +/// Returns: (sum_vector, carry_vector) +pub fn simd_add(a: Vec32, b: Vec32) -> (Vec32, Vec32) { + let mut sum: Vec32 = [TRIT_ZERO; SIMD_WIDTH]; + let mut carry: Vec32 = [TRIT_ZERO; SIMD_WIDTH]; + + for i in 0..SIMD_WIDTH { + let ai = a[i]; + let bi = b[i]; + let s: i16 = (ai as i16) + (bi as i16); + + if s > 1 { + sum[i] = TRIT_NEG; + carry[i] = TRIT_POS; + } else if s < -1 { + sum[i] = TRIT_POS; + carry[i] = TRIT_NEG; + } else { + sum[i] = s as Trit; + carry[i] = TRIT_ZERO; + } + } + + (sum, carry) +} + +/// SIMD negate 32 trits +pub fn simd_negate(v: Vec32) -> Vec32 { + let mut result: Vec32 = [TRIT_ZERO; SIMD_WIDTH]; + for i in 0..SIMD_WIDTH { + result[i] = -v[i]; + } + result +} + +/// SIMD dot product of 32 trits (returns scalar) +pub fn simd_dot_product(a: Vec32, b: Vec32) -> i32 { + let mut total: i32 = 0; + for i in 0..SIMD_WIDTH { + total += (a[i] as i32) * (b[i] as i32); + } + total +} + +/// SIMD check if all zeros +pub fn simd_is_zero(v: Vec32) -> bool { + for i in 0..SIMD_WIDTH { + if v[i] != TRIT_ZERO { + return false; + } + } + true +} + +// ============================================================================ +// HybridBigInt Methods +// ============================================================================ + +impl HybridBigInt { + /// Create zero value (minimal stack usage) + pub fn zero() -> HybridBigInt { + HybridBigInt { + packed_data: [0u8; MAX_PACKED_BYTES], + unpacked_cache: None, + mode: StorageMode::packed_mode, + trit_len: 1, + dirty: false, + } + } + + /// Ensure unpacked cache is allocated and valid + pub fn ensure_unpacked(&mut self) { + if self.mode == StorageMode::unpacked_mode { + if let Some(ref cache) = self.unpacked_cache { + return; + } + } + + // Allocate unpacked cache + let mut cache = [TRIT_ZERO; MAX_TRITS]; + + // Unpack from packed_data to cache + let num_packs = (self.trit_len + TRITS_PER_BYTE - 1) / TRITS_PER_BYTE; + for pack_idx in 0..num_packs { + let pack_byte = self.packed_data[pack_idx]; + let base = pack_idx * TRITS_PER_BYTE; + + // Decode 5 trits from pack byte (using base-3 encoding) + let mut value = pack_byte as u32; + for j in 0..TRITS_PER_BYTE { + let pos = base + j; + if pos < MAX_TRITS && pos < self.trit_len { + // Decode balanced ternary from packed representation + let trit_val = ((value % 3) as i8) - 1; + cache[pos] = trit_val; + value /= 3; + } + } + } + + self.unpacked_cache = Some(cache); + self.mode = StorageMode::unpacked_mode; + } + + /// Pack the unpacked cache back to packed storage + pub fn pack(&mut self) { + if !self.dirty && self.mode == StorageMode::packed_mode { + return; + } + + self.ensure_unpacked(); + + if let Some(ref cache) = self.unpacked_cache { + let num_packs = (self.trit_len + TRITS_PER_BYTE - 1) / TRITS_PER_BYTE; + for pack_idx in 0..num_packs { + let base = pack_idx * TRITS_PER_BYTE; + let mut pack_value: u32 = 0; + + // Encode 5 trits to pack byte + for j in 0..TRITS_PER_BYTE { + let pos = base + j; + if pos < self.trit_len { + let trit = cache[pos] + 1; // Convert to 0,1,2 + pack_value += (trit as u32) * (3u32.pow(j as u32)); + } + } + + self.packed_data[pack_idx] = (pack_value % 256) as u8; + } + } + + self.mode = StorageMode::packed_mode; + self.dirty = false; + } + + /// Memory usage in bytes (packed) + pub fn memory_usage(&self) -> usize { + (self.trit_len + TRITS_PER_BYTE - 1) / TRITS_PER_BYTE + } + + /// Create from i64 + pub fn from_i64(value: i64) -> HybridBigInt { + let mut result = HybridBigInt::zero(); + if value == 0 { + return result; + } + + result.ensure_unpacked(); + if let Some(ref mut cache) = result.unpacked_cache { + let mut v = value; + let mut pos: usize = 0; + + while v != 0 && pos < MAX_TRITS { + let mut rem = v % 3; + if rem == 2 { + rem = -1; + v += 1; // Adjust for balanced ternary + } else if rem == -2 { + rem = 1; + v -= 1; + } + cache[pos] = rem as Trit; + v /= 3; + pos += 1; + } + + result.trit_len = if pos == 0 { 1 } else { pos }; + } + + result.mode = StorageMode::unpacked_mode; + result.dirty = true; + result + } + + /// Convert to i64 + pub fn to_i64(&self) -> i64 { + let mut result: i64 = 0; + let mut power: i64 = 1; + + let num_trits = self.trit_len.min(40); // i64 can hold ~40 trits + for i in 0..num_trits { + let trit = if self.mode == StorageMode::unpacked_mode { + self.unpacked_cache.as_ref().map_or(TRIT_ZERO, |c| c[i]) + } else { + // Need to unpack first (simplified: assume unpacked for this example) + TRIT_ZERO + }; + result += (trit as i64) * power; + power *= 3; + } + + result + } + + /// Get trit at position (auto-unpacks if needed) + pub fn get_trit(&mut self, pos: usize) -> Trit { + if pos >= self.trit_len { + return TRIT_ZERO; + } + self.ensure_unpacked(); + self.unpacked_cache.as_ref().map_or(TRIT_ZERO, |c| c[pos]) + } + + /// Set trit at position (marks dirty) + pub fn set_trit(&mut self, pos: usize, value: Trit) { + if pos >= MAX_TRITS { + return; + } + self.ensure_unpacked(); + if let Some(ref mut cache) = self.unpacked_cache { + cache[pos] = value; + self.dirty = true; + if pos >= self.trit_len && value != TRIT_ZERO { + self.trit_len = pos + 1; + } + } + } + + /// Check if zero + pub fn is_zero(&mut self) -> bool { + if self.trit_len == 1 { + return self.get_trit(0) == TRIT_ZERO; + } + false + } + + /// Check if negative + pub fn is_negative(&mut self) -> bool { + if self.trit_len == 0 { + return false; + } + self.get_trit(self.trit_len - 1) < 0 + } + + /// Negate + pub fn negate(&mut self) -> HybridBigInt { + let mut result = HybridBigInt::zero(); + result.ensure_unpacked(); + self.ensure_unpacked(); + + if let (Some(ref mut dst), Some(ref src)) = + (&mut result.unpacked_cache, &self.unpacked_cache) + { + for i in 0..self.trit_len { + dst[i] = -src[i]; + } + } + + result.trit_len = self.trit_len; + result.mode = StorageMode::unpacked_mode; + result.dirty = true; + result + } + + /// Add two HybridBigInts (uses unpacked for speed) + pub fn add(&mut self, other: &mut HybridBigInt) -> HybridBigInt { + self.ensure_unpacked(); + other.ensure_unpacked(); + + let mut result = HybridBigInt::zero(); + result.ensure_unpacked(); + + let max_len = self.trit_len.max(other.trit_len); + let mut carry: Trit = TRIT_ZERO; + + if let (Some(ref mut dst), Some(ref src_a), Some(ref src_b)) = + (&mut result.unpacked_cache, &self.unpacked_cache, &other.unpacked_cache) + { + for i in 0..=max_len { + if i >= MAX_TRITS { + break; + } + + let a_trit: i16 = if i < self.trit_len { src_a[i] as i16 } else { 0 }; + let b_trit: i16 = if i < other.trit_len { src_b[i] as i16 } else { 0 }; + + let mut sum = a_trit + b_trit + (carry as i16); + carry = TRIT_ZERO; + + while sum > 1 { + sum -= 3; + carry = TRIT_POS; + } + while sum < -1 { + sum += 3; + carry = TRIT_NEG; + } + + dst[i] = sum as Trit; + } + } + + result.trit_len = (max_len + 1).min(MAX_TRITS); + result.normalize(); + result + } + + /// SIMD-accelerated add (32 trits at a time) + pub fn add_simd(&mut self, other: &mut HybridBigInt) -> HybridBigInt { + self.ensure_unpacked(); + other.ensure_unpacked(); + + let mut result = HybridBigInt::zero(); + result.ensure_unpacked(); + + let max_len = self.trit_len.max(other.trit_len); + let num_chunks = (max_len + SIMD_WIDTH - 1) / SIMD_WIDTH; + + // Phase 1: SIMD parallel addition + let mut carries: [[Trit; SIMD_WIDTH]; SIMD_CHUNKS + 1] = [[TRIT_ZERO; SIMD_WIDTH]; SIMD_CHUNKS + 1]; + + if let (Some(ref mut dst), Some(ref src_a), Some(ref src_b)) = + (&mut result.unpacked_cache, &self.unpacked_cache, &other.unpacked_cache) + { + for chunk in 0..num_chunks { + let base = chunk * SIMD_WIDTH; + + let mut a_vec: Vec32 = [TRIT_ZERO; SIMD_WIDTH]; + let mut b_vec: Vec32 = [TRIT_ZERO; SIMD_WIDTH]; + + for i in 0..SIMD_WIDTH { + let idx = base + i; + a_vec[i] = if idx < self.trit_len { src_a[idx] } else { TRIT_ZERO }; + b_vec[i] = if idx < other.trit_len { src_b[idx] } else { TRIT_ZERO }; + } + + let (sum_vec, carry_vec) = simd_add(a_vec, b_vec); + + for i in 0..SIMD_WIDTH { + let idx = base + i; + if idx < MAX_TRITS { + dst[idx] = sum_vec[i]; + } + carries[chunk][i] = carry_vec[i]; + } + } + + // Phase 2: Sequential carry propagation + let mut carry: Trit = TRIT_ZERO; + for i in 0..=max_len { + if i >= MAX_TRITS { + break; + } + + let chunk = i / SIMD_WIDTH; + let offset = i % SIMD_WIDTH; + + let mut val: i16 = dst[i] as i16; + + // Add carry from SIMD + if i > 0 { + let prev_chunk = (i - 1) / SIMD_WIDTH; + let prev_offset = (i - 1) % SIMD_WIDTH; + if prev_chunk < num_chunks { + val += carries[prev_chunk][prev_offset] as i16; + } + } + + val += carry as i16; + carry = TRIT_ZERO; + + while val > 1 { + val -= 3; + carry = TRIT_POS; + } + while val < -1 { + val += 3; + carry = TRIT_NEG; + } + + dst[i] = val as Trit; + } + } + + result.trit_len = (max_len + 1).min(MAX_TRITS); + result.normalize(); + result + } + + /// Subtract + pub fn sub(&mut self, other: &mut HybridBigInt) -> HybridBigInt { + let mut neg_other = other.negate(); + self.add(&mut neg_other) + } + + /// Multiply two HybridBigInts + pub fn mul(&mut self, other: &mut HybridBigInt) -> HybridBigInt { + self.ensure_unpacked(); + other.ensure_unpacked(); + + let mut result = HybridBigInt::zero(); + result.ensure_unpacked(); + + if let (Some(ref mut dst), Some(ref src_a), Some(ref src_b)) = + (&mut result.unpacked_cache, &self.unpacked_cache, &other.unpacked_cache) + { + for i in 0..self.trit_len { + let a_trit = src_a[i]; + if a_trit == TRIT_ZERO { + continue; + } + + let mut carry: Trit = TRIT_ZERO; + for j in 0..other.trit_len { + if i + j >= MAX_TRITS { + break; + } + + let mut prod: i16 = (a_trit as i16) * (src_b[j] as i16); + prod += dst[i + j] as i16; + prod += carry as i16; + carry = TRIT_ZERO; + + while prod > 1 { + prod -= 3; + carry = TRIT_POS; + } + while prod < -1 { + prod += 3; + carry = TRIT_NEG; + } + + dst[i + j] = prod as Trit; + } + + if carry != TRIT_ZERO && i + other.trit_len < MAX_TRITS { + dst[i + other.trit_len] = carry; + } + } + } + + result.trit_len = (self.trit_len + other.trit_len).min(MAX_TRITS); + result.normalize(); + result + } + + /// SIMD dot product (for VSA similarity) + pub fn dot_product(&mut self, other: &mut HybridBigInt) -> i32 { + self.ensure_unpacked(); + other.ensure_unpacked(); + + let mut total: i32 = 0; + let min_len = self.trit_len.min(other.trit_len); + let num_chunks = min_len / SIMD_WIDTH; + + if let (Some(ref src_a), Some(ref src_b)) = + (&self.unpacked_cache, &other.unpacked_cache) + { + // SIMD chunks + for chunk in 0..num_chunks { + let base = chunk * SIMD_WIDTH; + + let mut a_vec: Vec32 = [TRIT_ZERO; SIMD_WIDTH]; + let mut b_vec: Vec32 = [TRIT_ZERO; SIMD_WIDTH]; + + for i in 0..SIMD_WIDTH { + a_vec[i] = src_a[base + i]; + b_vec[i] = src_b[base + i]; + } + + total += simd_dot_product(a_vec, b_vec); + } + + // Remainder (scalar) + let remainder_start = num_chunks * SIMD_WIDTH; + for i in remainder_start..min_len { + total += (src_a[i] as i32) * (src_b[i] as i32); + } + } + + total + } + + /// Normalize: remove leading zeros + pub fn normalize(&mut self) { + self.ensure_unpacked(); + if let Some(ref cache) = self.unpacked_cache { + while self.trit_len > 1 && cache[self.trit_len - 1] == TRIT_ZERO { + self.trit_len -= 1; + } + self.dirty = true; + } + } + + /// Convert from BigInt + pub fn from_bigint(big: &BigInt) -> HybridBigInt { + let mut result = HybridBigInt::zero(); + result.ensure_unpacked(); + + if let Some(ref mut cache) = result.unpacked_cache { + for i in 0..big.len { + if i < MAX_TRITS { + cache[i] = big.trits[i]; + } + } + } + + result.trit_len = big.len.min(MAX_TRITS); + result.mode = StorageMode::unpacked_mode; + result.dirty = true; + result + } + + /// Convert to BigInt + pub fn to_bigint(&mut self) -> BigInt { + self.ensure_unpacked(); + let mut result = BigInt::zero(); + + if let Some(ref cache) = self.unpacked_cache { + for i in 0..self.trit_len { + if i < 256 { + result.trits[i] = cache[i]; + } + } + } + + result.len = self.trit_len.min(256); + result + } +} + +// ============================================================================ +// Tests +// ============================================================================ + +test "zero creates empty value" { + let zero = HybridBigInt::zero(); + assert!(zero.is_zero()); + assert_eq!(zero.trit_len, 1); + assert_eq!(zero.memory_usage(), 1); +} + +test "from_i64 and to_i64 roundtrip" { + let values = [0i64, 1, -1, 10, -10, 100, -100, 12345, -12345, 123456789]; + for val in values { + let mut hybrid = HybridBigInt::from_i64(val); + let back = hybrid.to_i64(); + assert_eq!(val, back); + } +} + +test "from_i64 positive values" { + let mut h = HybridBigInt::from_i64(10); + assert_eq!(h.to_i64(), 10); + assert_eq!(h.trit_len, 4); // 10 in balanced ternary: 1 0 -1 +} + +test "from_i64 negative values" { + let mut h = HybridBigInt::from_i64(-10); + assert_eq!(h.to_i64(), -10); + assert!(h.is_negative()); +} + +test "get_trit and set_trit" { + let mut h = HybridBigInt::zero(); + h.set_trit(0, TRIT_POS); + h.set_trit(1, TRIT_NEG); + h.set_trit(2, TRIT_ZERO); + + assert_eq!(h.get_trit(0), TRIT_POS); + assert_eq!(h.get_trit(1), TRIT_NEG); + assert_eq!(h.get_trit(2), TRIT_ZERO); +} + +test "addition basic" { + let mut a = HybridBigInt::from_i64(123); + let mut b = HybridBigInt::from_i64(456); + let mut sum = a.add(&mut b); + assert_eq!(sum.to_i64(), 579); +} + +test "addition with negative" { + let mut a = HybridBigInt::from_i64(100); + let mut b = HybridBigInt::from_i64(-50); + let mut sum = a.add(&mut b); + assert_eq!(sum.to_i64(), 50); +} + +test "subtraction basic" { + let mut a = HybridBigInt::from_i64(500); + let mut b = HybridBigInt::from_i64(200); + let mut diff = a.sub(&mut b); + assert_eq!(diff.to_i64(), 300); +} + +test "multiplication basic" { + let mut a = HybridBigInt::from_i64(12); + let mut b = HybridBigInt::from_i64(34); + let mut prod = a.mul(&mut b); + assert_eq!(prod.to_i64(), 408); +} + +test "multiplication negative" { + let mut a = HybridBigInt::from_i64(-12); + let mut b = HybridBigInt::from_i64(34); + let mut prod = a.mul(&mut b); + assert_eq!(prod.to_i64(), -408); +} + +test "negate" { + let mut h = HybridBigInt::from_i64(123); + let neg = h.negate(); + assert_eq!(neg.to_i64(), -123); +} + +test "negate twice returns original" { + let mut h = HybridBigInt::from_i64(456); + let neg1 = h.negate(); + let neg2 = neg1.negate(); + assert_eq!(neg2.to_i64(), 456); +} + +test "pack_unpack_roundtrip" { + let mut hybrid = HybridBigInt::from_i64(12345); + let val1 = hybrid.to_i64(); + + // Force pack + hybrid.pack(); + assert_eq!(hybrid.mode, StorageMode::packed_mode); + + // Force unpack via get_trit + let _ = hybrid.get_trit(0); + assert_eq!(hybrid.mode, StorageMode::unpacked_mode); + + let val2 = hybrid.to_i64(); + assert_eq!(val1, val2); +} + +test "memory_efficiency" { + let mut hybrid = HybridBigInt::from_i64(123456789); + hybrid.pack(); + let mem = hybrid.memory_usage(); + // Should use significantly less memory than trit_len + assert!(mem < 50); +} + +test "add_simd_matches_scalar" { + let cases = [(123i64, 456), (-100, 200), (12345, 67890)]; + + for (a_val, b_val) in cases { + let mut a1 = HybridBigInt::from_i64(a_val); + let mut b1 = HybridBigInt::from_i64(b_val); + + let mut a2 = HybridBigInt::from_i64(a_val); + let mut b2 = HybridBigInt::from_i64(b_val); + + let sum_scalar = a1.add(&mut b1); + let sum_simd = a2.add_simd(&mut b2); + + assert_eq!(sum_scalar.to_i64(), sum_simd.to_i64()); + } +} + +test "dot_product" { + let mut a = HybridBigInt::from_i64(12345); + let mut b = HybridBigInt::from_i64(12345); + + let dot = a.dot_product(&mut b); + // dot product of identical vectors should be positive + assert!(dot > 0); +} + +test "dot_product_different" { + let mut a = HybridBigInt::from_i64(100); + let mut b = HybridBigInt::from_i64(-100); + + let dot = a.dot_product(&mut b); + // dot product of opposites should be negative + assert!(dot < 0); +} + +test "normalize_removes_leading_zeros" { + let mut h = HybridBigInt::zero(); + h.set_trit(0, TRIT_POS); + h.set_trit(1, TRIT_NEG); + h.set_trit(5, TRIT_ZERO); // Leading zero + h.trit_len = 6; + + h.normalize(); + assert_eq!(h.trit_len, 2); +} + +test "is_zero detection" { + let mut h = HybridBigInt::zero(); + assert!(h.is_zero()); + + h.set_trit(0, TRIT_POS); + assert!(!h.is_zero()); +} + +test "is_negative detection" { + let mut pos = HybridBigInt::from_i64(100); + assert!(!pos.is_negative()); + + let mut neg = HybridBigInt::from_i64(-100); + assert!(neg.is_negative()); +} + +test "ensure_unpacked allocates_cache" { + let mut h = HybridBigInt::zero(); + assert!(h.unpacked_cache.is_none()); + + h.ensure_unpacked(); + assert!(h.unpacked_cache.is_some()); + assert_eq!(h.mode, StorageMode::unpacked_mode); +} + +test "pack_sets_mode_and_clears_dirty" { + let mut h = HybridBigInt::from_i64(12345); + h.set_trit(10, TRIT_POS); // Make dirty + + h.pack(); + assert_eq!(h.mode, StorageMode::packed_mode); + assert!(!h.dirty); +} + +test "add_large_numbers" { + let mut a = HybridBigInt::from_i64(123456789); + let mut b = HybridBigInt::from_i64(987654321); + let mut sum = a.add(&mut b); + assert_eq!(sum.to_i64(), 1111111110); +} + +test "subtraction_result_negative" { + let mut a = HybridBigInt::from_i64(100); + let mut b = HybridBigInt::from_i64(200); + let mut diff = a.sub(&mut b); + assert_eq!(diff.to_i64(), -100); + assert!(diff.is_negative()); +} + +test "multiplication_by_zero" { + let mut a = HybridBigInt::from_i64(12345); + let mut b = HybridBigInt::from_i64(0); + let mut prod = a.mul(&mut b); + assert!(prod.is_zero()); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +invariant "zero value has trit_len = 1" { + let zero = HybridBigInt::zero(); + assert_eq!(zero.trit_len, 1); +} + +invariant "zero value is packed mode" { + let zero = HybridBigInt::zero(); + assert_eq!(zero.mode, StorageMode::packed_mode); +} + +invariant "zero value is not dirty" { + let zero = HybridBigInt::zero(); + assert!(!zero.dirty); +} + +invariant "memory_efficiency_factor" { + let mut h = HybridBigInt::from_i64(1000); + h.pack(); + let packed_bytes = h.memory_usage() as gf16; + let unpacked_bytes = h.trit_len as gf16; + // Packed should use at most 1/5th the space + assert!(packed_bytes <= unpacked_bytes / MEMORY_EFFICIENCY + 1.0); +} + +invariant "add_commutative" { + let mut a = HybridBigInt::from_i64(123); + let mut b = HybridBigInt::from_i64(456); + + let mut a2 = HybridBigInt::from_i64(123); + let mut b2 = HybridBigInt::from_i64(456); + + let ab = a.add(&mut b); + let ba = b2.add(&mut a2); + + assert_eq!(ab.to_i64(), ba.to_i64()); +} + +invariant "add_identity" { + let mut a = HybridBigInt::from_i64(123); + let mut zero = HybridBigInt::zero(); + + let result = a.add(&mut zero); + assert_eq!(result.to_i64(), 123); +} + +invariant "mul_identity" { + let mut a = HybridBigInt::from_i64(123); + let mut one = HybridBigInt::from_i64(1); + + let result = a.mul(&mut one); + assert_eq!(result.to_i64(), 123); +} + +invariant "mul_commutative" { + let mut a = HybridBigInt::from_i64(12); + let mut b = HybridBigInt::from_i64(34); + + let mut a2 = HybridBigInt::from_i64(12); + let mut b2 = HybridBigInt::from_i64(34); + + let ab = a.mul(&mut b); + let ba = b2.mul(&mut a2); + + assert_eq!(ab.to_i64(), ba.to_i64()); +} + +invariant "negate_twice_is_identity" { + let mut h = HybridBigInt::from_i64(456); + let neg1 = h.negate(); + let neg2 = neg1.negate(); + assert_eq!(neg2.to_i64(), 456); +} + +invariant "sub_equals_add_negate" { + let mut a1 = HybridBigInt::from_i64(500); + let mut b1 = HybridBigInt::from_i64(200); + + let mut a2 = HybridBigInt::from_i64(500); + let mut b2 = HybridBigInt::from_i64(200); + + let diff = a1.sub(&mut b1); + let mut neg_b = b2.negate(); + let sum = a2.add(&mut neg_b); + + assert_eq!(diff.to_i64(), sum.to_i64()); +} + +invariant "add_simd_matches_scalar" { + let mut a = HybridBigInt::from_i64(12345); + let mut b = HybridBigInt::from_i64(67890); + + let mut a2 = HybridBigInt::from_i64(12345); + let mut b2 = HybridBigInt::from_i64(67890); + + let scalar = a.add(&mut b); + let simd = a2.add_simd(&mut b2); + + assert_eq!(scalar.to_i64(), simd.to_i64()); +} + +invariant "dot_product_symmetric" { + let mut a = HybridBigInt::from_i64(12345); + let mut b = HybridBigInt::from_i64(67890); + + let mut a2 = HybridBigInt::from_i64(12345); + let mut b2 = HybridBigInt::from_i64(67890); + + let ab = a.dot_product(&mut b); + let ba = b2.dot_product(&mut a2); + + assert_eq!(ab, ba); +} + +invariant "dot_product_reflexive" { + let mut a = HybridBigInt::from_i64(12345); + let dot = a.dot_product(&mut a); + // Dot product with self is always >= 0 + assert!(dot >= 0); +} + +invariant "normalize_preserves_value" { + let mut h = HybridBigInt::from_i64(12345); + let val_before = h.to_i64(); + h.normalize(); + let val_after = h.to_i64(); + assert_eq!(val_before, val_after); +} + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench "hybrid_bigint_from_i64" { + let iterations = 10000; + let mut total: i64 = 0; + + for _ in 0..iterations { + let h = HybridBigInt::from_i64(123456789); + total += h.to_i64(); + } + + // Prevent optimization + assert!(total > 0); +} + +bench "hybrid_bigint_to_i64" { + let iterations = 10000; + let mut h = HybridBigInt::from_i64(123456789); + + for _ in 0..iterations { + let _ = h.to_i64(); + } +} + +bench "hybrid_bigint_add" { + let iterations = 10000; + let mut a = HybridBigInt::from_i64(123456789); + let mut b = HybridBigInt::from_i64(987654321); + + for _ in 0..iterations { + let _ = a.add(&mut b); + } +} + +bench "hybrid_bigint_add_simd" { + let iterations = 10000; + let mut a = HybridBigInt::from_i64(123456789); + let mut b = HybridBigInt::from_i64(987654321); + + for _ in 0..iterations { + let _ = a.add_simd(&mut b); + } +} + +bench "hybrid_bigint_sub" { + let iterations = 10000; + let mut a = HybridBigInt::from_i64(987654321); + let mut b = HybridBigInt::from_i64(123456789); + + for _ in 0..iterations { + let _ = a.sub(&mut b); + } +} + +bench "hybrid_bigint_mul" { + let iterations = 1000; + let mut a = HybridBigInt::from_i64(12345); + let mut b = HybridBigInt::from_i64(6789); + + for _ in 0..iterations { + let _ = a.mul(&mut b); + } +} + +bench "hybrid_bigint_dot_product" { + let iterations = 10000; + let mut a = HybridBigInt::from_i64(123456789); + let mut b = HybridBigInt::from_i64(987654321); + + for _ in 0..iterations { + let _ = a.dot_product(&mut b); + } +} + +bench "hybrid_bigint_pack" { + let iterations = 10000; + let mut h = HybridBigInt::from_i64(123456789); + + for _ in 0..iterations { + h.pack(); + } +} + +bench "hybrid_bigint_ensure_unpacked" { + let iterations = 10000; + let mut h = HybridBigInt::zero(); + + for _ in 0..iterations { + h.ensure_unpacked(); + } +} + +bench "hybrid_bigint_negate" { + let iterations = 10000; + let mut h = HybridBigInt::from_i64(123456789); + + for _ in 0..iterations { + let _ = h.negate(); + } +} + +bench "simd_add_performance" { + let iterations = 100000; + let a: Vec32 = [1; SIMD_WIDTH]; + let b: Vec32 = [1; SIMD_WIDTH]; + + for _ in 0..iterations { + let _ = simd_add(a, b); + } +} + +bench "simd_dot_product_performance" { + let iterations = 100000; + let a: Vec32 = [1; SIMD_WIDTH]; + let b: Vec32 = [1; SIMD_WIDTH]; + + for _ in 0..iterations { + let _ = simd_dot_product(a, b); + } +} diff --git a/apps/website/public/t27/specs/ternary/packed_trit.t27 b/apps/website/public/t27/specs/ternary/packed_trit.t27 new file mode 100644 index 0000000000..0cfa0991f5 --- /dev/null +++ b/apps/website/public/t27/specs/ternary/packed_trit.t27 @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: Apache-2.0 +// Module: Packed Trit Encoding (5 trits per byte) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module PackedTrit { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; // Trit enum for trit values + use base::ops; // trit_min, trit_max for operations + use numeric::gf16; // GF16 for numeric scalars + + // ======================================================================== + // 1. Trit Values and Encoding Constants + // ======================================================================== + + // Balanced ternary trit values + pub const TRIT_NEG : i8 = -1; + pub const TRIT_ZERO : i8 = 0; + pub const TRIT_POS : i8 = 1; + + // 5-trit-per-byte encoding (distinct from PackedTrit's 8-trit encoding) + pub const TRITS_PER_BYTE : u8 = 5; + pub const MAX_PACKED_BYTES : u16 = 2400; + pub const MAX_TRITS : u16 = 12000; + + // Encoding values: uses 2 bits per trit (0b00=0, 0b01=+1, 0b10=-1) + pub const ENCODED_NEG : u8 = 2; + pub const ENCODED_ZERO : u8 = 0; + pub const ENCODED_POS : u8 = 1; + pub const TRIT_MASK : u8 = 0x03; + + // ======================================================================== + // 2. Result Types + // ======================================================================== + + pub struct PackResult { + bytes : []u8, + count : u16, + valid : bool, + } + + pub struct UnpackResult { + trits : []i8, + count : u16, + valid : bool, + } + + pub struct ArithResult { + value : PackedBigInt, + overflow : bool, + } + + // ======================================================================== + // 3. PackedBigInt Type + // ======================================================================== + + pub struct PackedBigInt { + data : []u8, + sign : i8, + magnitude : u16, + } + + // ======================================================================== + // 4. Encoding Functions + // ======================================================================== + + // encodePack(trits: []i8, count: u16) -> PackResult + // Pack trits into bytes using 5-trit-per-byte encoding + // Algorithm: encode 5 trits into one byte using 2 bits per trit + // Encoding: -1 -> 0b10, 0 -> 0b00, +1 -> 0b01 + // Returns valid=true if all trits are valid (-1, 0, +1) + // Returns valid=false if any trit is invalid + // Complexity: O(n) where n = count + pub fn encodePack(trits: []i8, count: u16) -> PackResult; + + // decodePack(bytes: []u8, byte_count: u16) -> UnpackResult + // Unpack bytes to trits using 5-trit-per-byte encoding + // Algorithm: decode 5 trits from each byte using 2-bit trit encoding + // Returns count trits (last byte may have unused trits) + // Complexity: O(n) where n = byte_count + pub fn decodePack(bytes: []u8, byte_count: u16) -> UnpackResult; + + // ======================================================================== + // 5. PackedBigInt Arithmetic + // ======================================================================== + + // packed_add(a: PackedBigInt, b: PackedBigInt) -> ArithResult + // Add two PackedBigInt values + // Returns ArithResult with sum and overflow flag + // Overflow occurs if magnitude exceeds MAX_TRITS + // Complexity: O(n) where n = max(magnitude of a, magnitude of b) + pub fn packed_add(a: PackedBigInt, b: PackedBigInt) -> ArithResult; + + // packed_sub(a: PackedBigInt, b: PackedBigInt) -> ArithResult + // Subtract b from a using PackedBigInt arithmetic + // Returns ArithResult with difference and overflow flag + // Overflow occurs on underflow (result < 0) + // Complexity: O(n) where n = max(magnitude of a, magnitude of b) + pub fn packed_sub(a: PackedBigInt, b: PackedBigInt) -> ArithResult; + + // packed_mul(a: PackedBigInt, b: PackedBigInt) -> ArithResult + // Multiply two PackedBigInt values + // Returns ArithResult with product and overflow flag + // Overflow occurs if magnitude exceeds MAX_TRITS + // Complexity: O(n*m) where n,m are magnitudes + pub fn packed_mul(a: PackedBigInt, b: PackedBigInt) -> ArithResult; + + // packed_from_i64(value: i64) -> PackedBigInt + // Convert i64 to PackedBigInt + // Returns PackedBigInt with sign and magnitude + // Handles overflow by clamping to MAX_TRITS + // Complexity: O(log3(|value|)) to compute magnitude + pub fn packed_from_i64(value: i64) -> PackedBigInt; + + // packed_to_i64(bigint: PackedBigInt) -> i64 + // Convert PackedBigInt to i64 + // Returns signed integer value + // Returns 0 if magnitude is 0 + // Complexity: O(n) where n = magnitude + pub fn packed_to_i64(bigint: PackedBigInt) -> i64; + + // ======================================================================== + // 6. Utility Functions + // ======================================================================== + + // trit_valid(trit: i8) -> bool + // Check if trit value is valid (-1, 0, or +1) + // Returns true if trit is in valid range + // Complexity: O(1) + pub fn trit_valid(trit: i8) -> bool { + return trit == TRIT_NEG or trit == TRIT_ZERO or trit == TRIT_POS; + } + + // normalize_trit(trit: i8) -> i8 + // Normalize trit to valid range + // Returns closest valid trit value + // Used for error recovery in arithmetic operations + // Complexity: O(1) + pub fn normalize_trit(trit: i8) -> i8 { + if (trit < -1) { + return TRIT_NEG; + } else if (trit > 1) { + return TRIT_POS; + } else if (trit == 0) { + return TRIT_ZERO; + } else { + return if (trit > 0) TRIT_POS else TRIT_NEG; + } + } + + // count_trits(bytes: []u8) -> u16 + // Count the number of trits in packed bytes + // Each byte contains 5 trits, so result = byte_count * 5 + // Complexity: O(1) + pub fn count_trits(bytes: []u8) -> u16 { + const byte_count = bytes.len() as u16; + return byte_count * TRITS_PER_BYTE; + } + + // ======================================================================== + // TDD - Tests + // ======================================================================== + + test encode_pack_single_trit + // Verify: single positive trit encodes correctly + given trits = [TRIT_POS] + and count = 1 + when result = encodePack(trits, count) + then result.valid and result.count == 1 + + test encode_pack_multiple_trits + // Verify: multiple trits encode correctly + given trits = [TRIT_NEG, TRIT_ZERO, TRIT_POS, TRIT_NEG, TRIT_ZERO] + and count = 5 + when result = encodePack(trits, count) + then result.valid and result.count == 2 + + test encode_pack_invalid_trit + // Verify: invalid trit value causes valid=false + given trits = [2] + and count = 1 + when result = encodePack(trits, count) + then !result.valid and result.count == 0 + + test encode_pack_empty + // Verify: empty input returns empty result + given trits = [] as []i8 + and count = 0 + when result = encodePack(trits, count) + then result.valid and result.count == 0 + + test encode_pack_max_trits + // Verify: maximum trit count works + given count = MAX_TRITS + and byte_count = count / TRITS_PER_BYTE + when result = encodePack(trits, count) + then result.valid and result.bytes.len() == byte_count + + test decode_pack_roundtrip + // Verify: encode then decode returns original trits + given original = [TRIT_NEG, TRIT_ZERO, TRIT_POS, TRIT_NEG, TRIT_ZERO] + and encoded = encodePack(original, 5) + when decoded = decodePack(encoded.bytes, encoded.count) + then decoded.valid and decoded.count == 5 + + test decode_pack_roundtrip_all_values + // Verify: all trit values roundtrip correctly + given original = [TRIT_NEG, TRIT_NEG, TRIT_NEG, TRIT_NEG, TRIT_NEG] + and encoded = encodePack(original, 5) + when decoded = decodePack(encoded.bytes, encoded.count) + then decoded.valid and decoded.trits[0] == TRIT_NEG and decoded.trits[1] == TRIT_NEG + + test packed_add_zero_identity + // Verify: adding zero returns same value + given a = packed_from_i64(42) + and zero = packed_from_i64(0) + when result = packed_add(a, zero) + then !result.overflow and packed_to_i64(result.value) == 42 + + test packed_sub_zero_identity + // Verify: subtracting zero returns same value + given a = packed_from_i64(42) + and zero = packed_from_i64(0) + when result = packed_sub(a, zero) + then !result.overflow and packed_to_i64(result.value) == 42 + + test packed_from_i64_positive + // Verify: positive i64 converts correctly + given result = packed_from_i64(42) + when value = packed_to_i64(result) + then value == 42 and result.sign == TRIT_POS + + test packed_from_i64_negative + // Verify: negative i64 converts correctly + given result = packed_from_i64(-42) + when value = packed_to_i64(result) + then value == -42 and result.sign == TRIT_NEG + + test packed_from_i64_zero + // Verify: zero i64 converts to zero + given result = packed_from_i64(0) + when value = packed_to_i64(result) + then value == 0 and result.magnitude == 0 + + test packed_mul_by_zero + // Verify: multiplying by zero returns zero + given a = packed_from_i64(42) + and zero = packed_from_i64(0) + when result = packed_mul(a, zero) + then !result.overflow and packed_to_i64(result.value) == 0 + + test packed_mul_by_one + // Verify: multiplying by one returns same value + given a = packed_from_i64(42) + and one = packed_from_i64(1) + when result = packed_mul(a, one) + then !result.overflow and packed_to_i64(result.value) == 42 + + test trit_valid_all_values + // Verify: all valid trit values pass validation + when trits = [TRIT_NEG, TRIT_ZERO, TRIT_POS] + for (trits) |t| { + assert trit_valid(t) == true; + } + + test normalize_trit_neg_to_neg + // Verify: negative value normalizes to negative + given result = normalize_trit(-2) + then result == TRIT_NEG + + test normalize_trit_large_pos_to_pos + // Verify: large positive normalizes to positive + given result = normalize_trit(100) + then result == TRIT_POS + + test normalize_trit_zero_to_zero + // Verify: zero normalizes to zero + given result = normalize_trit(0) + then result == TRIT_ZERO + + test count_trits_empty + // Verify: empty byte array returns zero trit count + given bytes = [] as []u8 + when count = count_trits(bytes) + then count == 0 + + test count_trits_single_byte + // Verify: one byte contains 5 trits + given bytes = [0x00] as []u8 + when count = count_trits(bytes) + then count == 5 + + test count_trits_multiple_bytes + // Verify: multiple bytes contain correct trit count + given bytes = [0x00, 0x00, 0x00] as []u8 + when count = count_trits(bytes) + then count == 15 + + // ======================================================================== + // TDD - Invariants + // ======================================================================== + + invariant trit_values_in_range + // Verify: trit constants are in valid range [-1, 0, +1] + assert TRIT_NEG >= -1 and TRIT_NEG <= 1; + assert TRIT_ZERO >= -1 and TRIT_ZERO <= 1; + assert TRIT_POS >= -1 and TRIT_POS <= 1; + + invariant encoding_values_valid + // Verify: encoding values fit in 2-bit range + assert ENCODED_NEG >= 0 and ENCODED_NEG < 4; + assert ENCODED_ZERO >= 0 and ENCODED_ZERO < 4; + assert ENCODED_POS >= 0 and ENCODED_POS < 4; + + invariant trit_mask_correct + // Verify: TRIT_MASK correctly masks 2 bits + assert TRIT_MASK == 0x03; + + invariant max_trits_equals_max_bytes_times_five + // Verify: MAX_TRITS = MAX_PACKED_BYTES * TRITS_PER_BYTE + assert MAX_TRITS == MAX_PACKED_BYTES * TRITS_PER_BYTE; + + invariant encode_decode_roundtrip + // Verify: encoding then decoding returns original value + // This is verified by decode_pack_roundtrip test + assert true; + + invariant add_zero_identity + // Verify: adding zero returns same value + // This is verified by packed_add_zero_identity test + assert true; + + invariant sub_zero_identity + // Verify: subtracting zero returns same value + // This is verified by packed_sub_zero_identity test + assert true; + + invariant mul_by_zero_returns_zero + // Verify: multiplying by zero returns zero + // This is verified by packed_mul_by_zero test + assert true; + + invariant mul_by_one_returns_same + // Verify: multiplying by one returns same value + // This is verified by packed_mul_by_one test + assert true; + + invariant from_i64_to_i64_roundtrip + // Verify: i64 -> PackedBigInt -> i64 returns original + // This is verified by packed_from_i64_* tests + assert true; + + // ======================================================================== + // TDD - Benchmarks + // ======================================================================== + + bench encode_pack_throughput + // Measure: operations/second for encoding + // Target: encode 10000 trits in < 1ms + @setEvalBranchQuota(10000); + var trits = [_]i8{ TRIT_POS, TRIT_NEG, TRIT_ZERO }; + var result = encodePack(trits, 3); + _ = result; + + bench decode_pack_throughput + // Measure: operations/second for decoding + // Target: decode 10000 trits in < 1ms + @setEvalBranchQuota(10000); + var bytes = [_]u8{ 0x00 }; + var result = decodePack(bytes, 1); + _ = result; + + bench packed_add_latency + // Measure: cycles for packed addition + // Target: < 100 cycles for 10-trit numbers + @setEvalBranchQuota(10000); + var a = packed_from_i64(12345); + var b = packed_from_i64(54321); + var result = packed_add(a, b); + _ = result; + + bench packed_sub_latency + // Measure: cycles for packed subtraction + // Target: < 100 cycles for 10-trit numbers + @setEvalBranchQuota(10000); + var a = packed_from_i64(54321); + var b = packed_from_i64(12345); + var result = packed_sub(a, b); + _ = result; + + bench packed_mul_latency + // Measure: cycles for packed multiplication + // Target: < 1000 cycles for 5-trit numbers + @setEvalBranchQuota(10000); + var a = packed_from_i64(1234); + var b = packed_from_i64(5678); + var result = packed_mul(a, b); + _ = result; + + bench packed_from_i64_latency + // Measure: cycles for i64 to PackedBigInt conversion + // Target: < 50 cycles for 32-bit numbers + @setEvalBranchQuota(10000); + var result = packed_from_i64(2147483647); + _ = result; + + bench packed_to_i64_latency + // Measure: cycles for PackedBigInt to i64 conversion + // Target: < 50 cycles for 32-bit numbers + @setEvalBranchQuota(10000); + var big = packed_from_i64(2147483647); + var result = packed_to_i64(big); + _ = result; + + bench trit_valid_latency + // Measure: cycles for trit validation + // Target: < 5 cycles + @setEvalBranchQuota(10000); + var result = trit_valid(1); + _ = result; + + bench normalize_trit_latency + // Measure: cycles for trit normalization + // Target: < 10 cycles + @setEvalBranchQuota(10000); + var result = normalize_trit(100); + _ = result; +} diff --git a/apps/website/public/t27/specs/test_framework/core.t27 b/apps/website/public/t27/specs/test_framework/core.t27 new file mode 100644 index 0000000000..c704224050 --- /dev/null +++ b/apps/website/public/t27/specs/test_framework/core.t27 @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27 Math/Physics Test Framework - Core +// +// Ring 050: Test framework core per T27-MATH-PHYSICS-TEST-FRAMEWORK-SPEC.md +// Provides fundamental testing constructs for scientific computing + +module core; + +// S Core testing enums and structures + +/// Test verdict/tier classification +enum Verdict { + CLEAN, + FAIL, + PARTIAL, + SKIP +} + +/// Test tolerance tier (numeric/physics precision levels) +enum ToleranceTier { + EXACT, // Mathematical identity (e.g., phi^2 = phi + 1) + WITHIN_UNCERTAINTY, // Within experimental uncertainty (CODATA) + EMPIRICAL_FIT, // Empirical formula, good accuracy + APPROXIMATION, // Approximation > experimental uncertainty + FALSIFIED_AS_EXACT, // Cannot claim as exact vs experiment + CONJECTURAL, // Hypothesis, insufficient verification + UNTTESTED // Not yet checked quantitatively +} + +/// Engineering status for toolchain tests +enum EngineeringStatus { + proved, // Theorem or machine-checked proof in-repo + tested, // Automated test / conformance fails if violated + empirical, // Observed in practice; not formal proof + conjectural, // Open or partial + deprecated // Superseded; history only +} + +/// Test result container +struct TestResult { + name: string, + verdict: Verdict, + tolerance: ToleranceTier, + engineering: EngineeringStatus, + claim_id: Option, // Link to RESEARCH_CLAIMS.md + input: any, + expected: any, + actual: any, + delta: Option, + message: Option +} + +/// Sequential gate pipeline with short-circuit +/// Pattern: parse -> type_check -> semantic -> numeric_stability -> physics_constraint -> audit +struct GatePipeline { + parse_ok: bool, + type_check_ok: bool, + semantic_ok: bool, + numeric_stability_ok: bool, + physics_constraint_ok: bool, + audit_ok: bool +} + +impl GatePipeline { + // Sequential evaluation - if gate N fails, gates N+1... are not evaluated + fn run_sequential(self) -> bool { + self.parse_ok && + (if self.parse_ok { self.type_check_ok } else { false }) && + (if self.parse_ok && self.type_check_ok { self.semantic_ok } else { false }) && + (if self.parse_ok && self.type_check_ok && self.semantic_ok { self.numeric_stability_ok } else { false }) && + (if self.parse_ok && self.type_check_ok && self.semantic_ok && self.numeric_stability_ok { self.physics_constraint_ok } else { false }) && + (if self.parse_ok && self.type_check_ok && self.semantic_ok && self.numeric_stability_ok && self.physics_constraint_ok { self.audit_ok } else { false }) + } + + fn verdict(self) -> Verdict { + if self.run_sequential() { Verdict::CLEAN } else { Verdict::FAIL } + } +} + +// S Core testing utilities + +/// Property-based testing: for all x in domain, property holds +fn for_all(domain: fn() -> T, property: fn(T) -> bool, iterations: u32) -> TestResult { + let mut failures = 0u32; + let mut last_failure = None; + + for i in 0..iterations { + let input = domain(); + if !property(input) { + failures += 1; + last_failure = Some(input); + } + } + + let verdict = if failures == 0 { Verdict::CLEAN } else { Verdict::FAIL }; + let message = if failures > 0 { + Some(format!("{} failures in {} iterations (last failure: {:?})", failures, iterations, last_failure)) + } else { None }; + + TestResult { + name: format!("for_all_{} iterations", iterations), + verdict, + tolerance: ToleranceTier::EXACT, + engineering: EngineeringStatus::tested, + claim_id: None, + input: iterations, + expected: 0u32, + actual: failures, + delta: None, + message + } +} + +/// Metamorphic testing: if input(x) produces y, then input(f(x)) should produce g(y) +fn metamorphic(input: fn(T) -> R, transform: fn(T) -> T, relation: fn(R, R) -> bool, test_cases: Vec) -> Vec { + test_cases.iter().map(|x| { + let y1 = input(*x); + let x_transformed = transform(*x); + let y2 = input(x_transformed); + + let relation_holds = relation(y1, y2); + let verdict = if relation_holds { Verdict::CLEAN } else { Verdict::FAIL }; + + TestResult { + name: format!("metamorphic_{:?}", x), + verdict, + tolerance: ToleranceTier::EXACT, + engineering: EngineeringStatus::tested, + claim_id: None, + input: *x, + expected: y1, + actual: y2, + delta: None, + message: if !relation_holds { Some("Metamorphic relation violated") } else { None } + } + }).collect() +} + +/// Differential testing: compare two implementations for equality within tolerance +fn differential(impl1: fn(T) -> T, impl2: fn(T) -> T, tolerance: f64, test_cases: Vec) -> Vec { + test_cases.iter().map(|x| { + let result1 = impl1(*x); + let result2 = impl2(*x); + + // For numeric types, check relative difference + let delta = match (result1, result2) { + (f64::INF(a), f64::INF(b)) => 0.0, + (f64::INF(a), b) => f64::INFINITY, + (a, f64::INF(b)) => f64::INFINITY, + (a, b) => (a - b).abs() / a.abs().max(b.abs()).max(1e-15) + }; + + let verdict = if delta <= tolerance { Verdict::CLEAN } else { Verdict::FAIL }; + let tol_tier = if tolerance == 0.0 { ToleranceTier::EXACT } else { ToleranceTier::WITHIN_UNCERTAINTY }; + + TestResult { + name: format!("differential_{:?}", x), + verdict, + tolerance: tol_tier, + engineering: EngineeringStatus::empirical, + claim_id: None, + input: *x, + expected: result1, + actual: result2, + delta: Some(delta), + message: if delta > tolerance { Some(format!("Delta {} exceeds tolerance {}", delta, tolerance)) } else { None } + } + }).collect() +} + +// S Test runners and reporting + +/// TAP (Test Anything Protocol) output generator +struct TapReporter { + results: Vec +} + +impl TapReporter { + fn new(results: Vec) -> TapReporter { + TapReporter { results } + } + + fn generate_tap(self) -> string { + let total = self.results.len(); + let mut output = format!("1..{}\n", total); + + for (i, result) in self.results.iter().enumerate() { + let status = match result.verdict { + Verdict::CLEAN => "ok", + Verdict::PARTIAL => "ok # TODO", + Verdict::SKIP => "ok # SKIP", + Verdict::FAIL => "not ok" + }; + + output.push_str(&format!("{} {} - {}\n", status, i + 1, result.name)); + + if result.verdict == Verdict::FAIL { + if let Some(ref msg) = result.message { + output.push_str(&format!(" ---\n {}\n ...\n", msg)); + } + } + } + + output + } +} + +/// JSON test report generator +struct JsonReporter { + results: Vec +} + +impl JsonReporter { + fn new(results: Vec) -> JsonReporter { + JsonReporter { results } + } + + fn generate_json(self) -> string { + // Simplified JSON generation + let total = self.results.len(); + let passed = self.results.iter().filter(|r| r.verdict == Verdict::CLEAN).count(); + let failed = self.results.iter().filter(|r| r.verdict == Verdict::FAIL).count(); + let skipped = self.results.iter().filter(|r| r.verdict == Verdict::SKIP).count(); + + format!(r#"{{ + "total": {}, + "passed": {}, + "failed": {}, + "skipped": {}, + "results": [] +}}"#, total, passed, failed, skipped) + } +} + +// S Test invariants and properties + +/// Sequential gate invariant - enforces strict ordering +invariant gate_pipeline_sequential(gate: GatePipeline) -> bool { + gate.run_sequential() == (gate.parse_ok && gate.type_check_ok && gate.semantic_ok && + gate.numeric_stability_ok && gate.physics_constraint_ok && gate.audit_ok) +} + +/// GoldenFloat identity property: for any GF number, round-trip conversion preserves value +invariant gf16_roundtrip_preservation(value: f64) -> bool { + // This would call actual GF16 encode/decode functions + // For now, this is a placeholder showing the invariant structure + let encoded = gf16_encode_f32(value as f32); + let decoded = gf16_decode_to_f32(encoded); + (decoded - value as f32).abs() < 1e-6 +} + +/// Phi algebraic identity: phi^2 = phi + 1 (exact mathematical identity) +invariant phi_squared_identity(phi: f64) -> bool { + (phi * phi - phi - 1.0).abs() < 1e-15 // Exact within floating point precision +} + +// S Test entry point +fn run_test_suite() -> Vec { + let mut results = Vec::new(); + + // Example: Test GF4 exhaustive properties + // This would generate all 16 GF4 values and test commutativity, associativity, etc. + let gf4_exhaustive_results = test_gf4_exhaustive(); + results.extend(gf4_exhaustive_results); + + // Example: Test phi identities + let phi = 1.618033988749895; // Golden ratio + results.push(TestResult { + name: "phi_squared_identity".to_string(), + verdict: if phi_squared_identity(phi) { Verdict::CLEAN } else { Verdict::FAIL }, + tolerance: ToleranceTier::EXACT, + engineering: EngineeringStatus::proved, + claim_id: Some("C-phi-001".to_string()), + input: phi, + expected: phi + 1.0, + actual: phi * phi, + delta: Some((phi * phi - phi - 1.0).abs()), + message: None + }); + + results +} + +// Test helpers (would be expanded based on actual GF implementations) +fn test_gf4_exhaustive() -> Vec { + // Placeholder for GF4 exhaustive testing + // In reality, this would enumerate all 16 GF4 values and test all binary operations + vec![] +} + +fn gf16_encode_f32(value: f32) -> u16 { + // Placeholder for actual GF16 encoding + 0u16 +} + +fn gf16_decode_to_f32(encoded: u16) -> f32 { + // Placeholder for actual GF16 decoding + 0.0f32 +} + +test { + let suite_results = run_test_suite(); + + // Check that all tests pass + let failed_count = suite_results.iter().filter(|r| r.verdict == Verdict::FAIL).count(); + assert!(failed_count == 0, "Test suite had {} failures", failed_count); + + // Generate TAP output + let tap_reporter = TapReporter::new(suite_results.clone()); + let tap_output = tap_reporter.generate_tap(); + println!("TAP Output:\n{}", tap_output); + + // Generate JSON output + let json_reporter = JsonReporter::new(suite_results); + let json_output = json_reporter.generate_json(); + println!("JSON Output:\n{}", json_output); +} \ No newline at end of file diff --git a/apps/website/public/t27/specs/test_framework/graph_drift_detection.t27 b/apps/website/public/t27/specs/test_framework/graph_drift_detection.t27 new file mode 100644 index 0000000000..cd0664e551 --- /dev/null +++ b/apps/website/public/t27/specs/test_framework/graph_drift_detection.t27 @@ -0,0 +1,650 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/test_framework/graph_drift_detection.t27 +// Ring 054 -- Graph Drift Detection for Structural Change Detection +// Monitors structural changes in mathematical and physics specifications over time + +module GraphDriftDetection { + use test_framework::core::{TestResult, Verdict, ToleranceTier, EngineeringStatus}; + use std::collections::{HashMap, HashSet}; + + // ----------------------------------------------------- + // 1. Drift Detection Configuration + // ----------------------------------------------------- + + // Graph node representing a mathematical/physics entity + struct GraphNode { + id: string; + name: string; + type: NodeType; + claim_id: string; + tolerance_tier: ToleranceTier; + value: f64; + formula: string; + dependencies: [string]; + metadata: HashMap; + } + + // Graph edge representing relationships between nodes + struct GraphEdge { + from: string; + to: string; + relation: EdgeRelation; + weight: f64; + claim_id: string; + } + + // Structural graph for drift detection + struct DriftGraph { + nodes: HashMap; + edges: [GraphEdge]; + version: string; + timestamp: u64; + hash: string; + } + + // Drift detection configuration + struct DriftConfig { + threshold: f64; + window_size: u32; + baseline_version: string; + claim_id: string; + tolerance_tier: ToleranceTier; + check_semantic_drift: bool; + check_structural_drift: bool; + check_performance_drift: bool; + } + + // Node types + enum NodeType { + CONSTANT, + FUNCTION, + THEOREM, + INVARIANT, + TEST, + BENCHMARK + } + + // Edge relations + enum EdgeRelation { + DEPENDS_ON, + IMPLEMENTS, + VERIFIES, + OPTIMIZES, + TRANSFORMS + } + + // Default drift detection configuration + fn default_drift_config() -> DriftConfig { + return DriftConfig{ + threshold: 0.1, + window_size: 10, + baseline_version: "v1.0.0", + claim_id: "C-meta-001", + tolerance_tier: ToleranceTier::WITHIN_UNCERTAINTY, + check_semantic_drift: true, + check_structural_drift: true, + check_performance_drift: true, + }; + } + + // ----------------------------------------------------- + // 2. Graph Construction and Parsing + // ----------------------------------------------------- + + // Parse T27 spec file and extract graph structure + fn parse_t27_spec_to_graph(file_path: string) -> DriftGraph { + const content = read_file(file_path); + let mut nodes = HashMap::new(); + let mut edges = []; + let mut current_module = ""; + + for line in content.lines() { + if line.starts_with("module ") { + current_module = extract_module_name(line); + } else if line.starts_with("const ") { + const node = parse_constant(line, current_module); + nodes.insert(node.id, node); + } else if line.starts_with("fn ") { + const node = parse_function(line, current_module); + nodes.insert(node.id, node); + } else if line.starts_with("test ") { + const node = parse_test(line, current_module); + nodes.insert(node.id, node); + } else if line.starts_with("invariant ") { + const node = parse_invariant(line, current_module); + nodes.insert(node.id, node); + } else if line.starts_with("bench ") { + const node = parse_benchmark(line, current_module); + nodes.insert(node.id, node); + } + } + + // Extract dependencies and build edges + edges = extract_dependencies(nodes); + + return DriftGraph{ + nodes: nodes, + edges: edges, + version: extract_version(content), + timestamp: get_current_timestamp(), + hash: compute_graph_hash(nodes, edges), + }; + } + + // Parse constant definition + fn parse_constant(line: string, module: string) -> GraphNode { + const parts = line.split_whitespace(); + const name = parts[1]; // Skip "const" + const value_expr = extract_value_expression(line); + const claim_id = extract_claim_id(line); + const tolerance_tier = extract_tolerance_tier(line); + + return GraphNode{ + id: format!("{}::{}", module, name), + name: name, + type: NodeType::CONSTANT, + claim_id: claim_id, + tolerance_tier: tolerance_tier, + value: evaluate_expression(value_expr), + formula: value_expr, + dependencies: extract_dependencies_from_expression(value_expr), + metadata: HashMap::new(), + }; + } + + // Parse function definition + fn parse_function(line: string, module: string) -> GraphNode { + const name = extract_function_name(line); + const signature = extract_function_signature(line); + const claim_id = extract_claim_id(line); + const tolerance_tier = extract_tolerance_tier(line); + + return GraphNode{ + id: format!("{}::{}", module, name), + name: name, + type: NodeType::FUNCTION, + claim_id: claim_id, + tolerance_tier: tolerance_tier, + value: 0.0, // Functions don't have scalar values + formula: signature, + dependencies: extract_dependencies_from_signature(signature), + metadata: HashMap::new(), + }; + } + + // Parse test definition + fn parse_test(line: string, module: string) -> GraphNode { + const name = extract_test_name(line); + const claim_id = extract_claim_id(line); + const tolerance_tier = extract_tolerance_tier(line); + + return GraphNode{ + id: format!("{}::{}", module, name), + name: name, + type: NodeType::TEST, + claim_id: claim_id, + tolerance_tier: tolerance_tier, + value: 0.0, + formula: format!("test_{}", name), + dependencies: extract_test_dependencies(line), + metadata: HashMap::new(), + }; + } + + // ----------------------------------------------------- + // 3. Drift Detection Algorithms + // ----------------------------------------------------- + + // Drift detection result + struct DriftResult { + has_drift: bool; + semantic_drift: f64; + structural_drift: f64; + performance_drift: f64; + details: string; + changes: [DriftChange]; + } + + // Individual drift change + struct DriftChange { + change_type: ChangeType; + node_id: string; + before: string; + after: string; + severity: f64; + description: string; + } + + // Change types + enum ChangeType { + NODE_ADDED, + NODE_REMOVED, + NODE_MODIFIED, + EDGE_ADDED, + EDGE_REMOVED, + VALUE_CHANGED, + FORMULA_CHANGED, + CLAIM_CHANGED, + TOLERANCE_CHANGED + } + + // Compare two graphs and detect drift + fn detect_graph_drift(baseline: DriftGraph, current: DriftGraph, config: DriftConfig) -> DriftResult { + let mut changes = []; + let mut semantic_drift = 0.0; + let mut structural_drift = 0.0; + let mut performance_drift = 0.0; + + if config.check_semantic_drift { + semantic_drift = calculate_semantic_drift(baseline, current); + } + + if config.check_structural_drift { + structural_drift = calculate_structural_drift(baseline, current, &mut changes); + } + + if config.check_performance_drift { + performance_drift = calculate_performance_drift(baseline, current); + } + + const total_drift = (semantic_drift + structural_drift + performance_drift) / 3.0; + const has_drift = total_drift > config.threshold; + + return DriftResult{ + has_drift: has_drift, + semantic_drift: semantic_drift, + structural_drift: structural_drift, + performance_drift: performance_drift, + details: generate_drift_details(total_drift, config), + changes: changes, + }; + } + + // Calculate semantic drift (changes in meaning/behavior) + fn calculate_semantic_drift(baseline: DriftGraph, current: DriftGraph) -> f64 { + let mut drift_score = 0.0; + let mut total_comparisons = 0; + + for (node_id, baseline_node) in baseline.nodes { + if let Some(current_node) = current.nodes.get(node_id) { + // Compare values for constants + if baseline_node.type == NodeType::CONSTANT && current_node.type == NodeType::CONSTANT { + const value_diff = abs(baseline_node.value - current_node.value); + const relative_diff = value_diff / abs(baseline_node.value); + drift_score += relative_diff; + total_comparisons += 1; + } + + // Compare formulas + if baseline_node.formula != current_node.formula { + drift_score += 0.5; // Medium drift for formula changes + total_comparisons += 1; + } + + // Compare claim IDs + if baseline_node.claim_id != current_node.claim_id { + drift_score += 0.3; // Low drift for claim changes + total_comparisons += 1; + } + + // Compare tolerance tiers + if baseline_node.tolerance_tier != current_node.tolerance_tier { + drift_score += 0.2; // Low drift for tolerance changes + total_comparisons += 1; + } + } + } + + return if total_comparisons > 0 { drift_score / total_comparisons } else { 0.0 }; + } + + // Calculate structural drift (changes in graph structure) + fn calculate_structural_drift(baseline: DriftGraph, current: DriftGraph, changes: &mut [DriftChange]) -> f64 { + let mut drift_score = 0.0; + let mut total_elements = baseline.nodes.length() + baseline.edges.length(); + + // Check for added nodes + for (node_id, current_node) in current.nodes { + if !baseline.nodes.contains_key(node_id) { + drift_score += 1.0; + changes.push(DriftChange{ + change_type: ChangeType::NODE_ADDED, + node_id: node_id, + before: "", + after: format!("{:?}", current_node), + severity: 0.8, + description: format!("New node added: {}", node_id), + }); + } + } + + // Check for removed nodes + for (node_id, baseline_node) in baseline.nodes { + if !current.nodes.contains_key(node_id) { + drift_score += 1.0; + changes.push(DriftChange{ + change_type: ChangeType::NODE_REMOVED, + node_id: node_id, + before: format!("{:?}", baseline_node), + after: "", + severity: 0.9, + description: format!("Node removed: {}", node_id), + }); + } + } + + // Check for modified nodes + for (node_id, baseline_node) in baseline.nodes { + if let Some(current_node) = current.nodes.get(node_id) { + if baseline_node != current_node { + drift_score += 0.5; + changes.push(DriftChange{ + change_type: ChangeType::NODE_MODIFIED, + node_id: node_id, + before: format!("{:?}", baseline_node), + after: format!("{:?}", current_node), + severity: 0.6, + description: format!("Node modified: {}", node_id), + }); + } + } + } + + return if total_elements > 0 { drift_score / total_elements } else { 0.0 }; + } + + // Calculate performance drift (changes in performance characteristics) + fn calculate_performance_drift(baseline: DriftGraph, current: DriftGraph) -> f64 { + // This would integrate with benchmark results + // For now, return a placeholder + return 0.0; + } + + // ----------------------------------------------------- + // 4. Drift Monitoring and Alerting + // ----------------------------------------------------- + + // Drift monitoring system + struct DriftMonitor { + baseline_graph: DriftGraph; + config: DriftConfig; + history: [DriftResult]; + alert_threshold: f64; + } + + // Create new drift monitor + fn create_drift_monitor(baseline_graph: DriftGraph, config: DriftConfig) -> DriftMonitor { + return DriftMonitor{ + baseline_graph: baseline_graph, + config: config, + history: [], + alert_threshold: config.threshold * 1.5, // Alert at 1.5x threshold + }; + } + + // Monitor current graph for drift + fn monitor_drift(monitor: &mut DriftMonitor, current_graph: DriftGraph) -> DriftResult { + const result = detect_graph_drift(monitor.baseline_graph, current_graph, monitor.config); + monitor.history.push(result.clone()); + + // Check if alert should be triggered + if result.has_drift && result.semantic_drift > monitor.alert_threshold { + trigger_drift_alert(result); + } + + return result; + } + + // Trigger drift alert + fn trigger_drift_alert(result: DriftResult) { + const alert_message = format!( + "DRIFT ALERT: Semantic drift detected (score: {:.4})\nChanges: {}\nDetails: {}", + result.semantic_drift, + result.changes.length(), + result.details + ); + + // Log the alert + log_error("drift_detection", &alert_message); + + // Could send to external monitoring system + // send_alert_to_monitoring_system(alert_message); + } + + // ----------------------------------------------------- + // 5. Integration with Test Framework + // ----------------------------------------------------- + + // Run drift detection as part of test suite + fn run_drift_detection_tests() -> [TestResult] { + const config = default_drift_config(); + let mut results = []; + + // Load baseline graph + const baseline_path = "specs/math/sacred_physics.t27"; + const baseline_graph = parse_t27_spec_to_graph(baseline_path); + + // Load current graph (could be from different version/branch) + const current_path = "specs/math/sacred_physics_current.t27"; + if file_exists(current_path) { + const current_graph = parse_t27_spec_to_graph(current_path); + const drift_result = detect_graph_drift(baseline_graph, current_graph, config); + + results.push(TestResult{ + verdict: if drift_result.has_drift { Verdict::FAIL } else { Verdict::PASS }, + claim_id: config.claim_id, + tolerance_tier: config.tolerance_tier, + details: drift_result.details, + engineering_status: if drift_result.has_drift { + EngineeringStatus::NEEDS_INVESTIGATION + } else { + EngineeringStatus::READY + }, + drift_changes: drift_result.changes, + }); + } else { + results.push(TestResult{ + verdict: Verdict::PASS, + claim_id: config.claim_id, + tolerance_tier: config.tolerance_tier, + details: "No current version available for drift comparison", + engineering_status: EngineeringStatus::READY, + }); + } + + return results; + } + + // Benchmark: Drift detection performance + bench drift_detection_performance + measure: milliseconds to run run_drift_detection_tests() + target: < 1000ms + + // ----------------------------------------------------- + // 6. TDD-Inside-Spec: Drift Detection Tests + // ----------------------------------------------------- + + test drift_config_creation + given config = default_drift_config() + then config.threshold == 0.1 + and config.claim_id == "C-meta-001" + and config.check_semantic_drift == true + + test graph_parsing_basic + given graph = parse_t27_spec_to_graph("specs/math/sacred_physics.t27") + then graph.nodes.length() > 0 + and graph.version.length() > 0 + and graph.hash.length() > 0 + + test drift_detection_no_changes + given baseline = create_test_graph() + and current = baseline.clone() + and config = default_drift_config() + and result = detect_graph_drift(baseline, current, config) + then result.has_drift == false + and result.semantic_drift == 0.0 + + test drift_detection_value_change + given baseline = create_test_graph() + and mut current = baseline.clone() + and node = current.nodes.get("test::PHI").unwrap() + and node.value = 1.7 // Changed from golden ratio + and config = default_drift_config() + and result = detect_graph_drift(baseline, current, config) + then result.has_drift == true + and result.semantic_drift > 0.0 + + test drift_detection_node_addition + given baseline = create_test_graph() + and mut current = baseline.clone() + and new_node = create_test_node() + and current.nodes.insert(new_node.id, new_node) + and config = default_drift_config() + and result = detect_graph_drift(baseline, current, config) + then result.has_drift == true + and result.structural_drift > 0.0 + + test drift_monitoring_integration + given baseline = create_test_graph() + and config = default_drift_config() + and mut monitor = create_drift_monitor(baseline, config) + and current = create_modified_test_graph() + and result = monitor_drift(&mut monitor, current) + then monitor.history.length() == 1 + and result.semantic_drift > 0.0 + + test drift_detection_suite + given results = run_drift_detection_tests() + then results.length() >= 1 + and all_results_have_claim_ids(results) + + invariant drift_config_valid + assert default_drift_config().threshold > 0.0 + and default_drift_config().window_size > 0 + and default_drift_config().claim_id.length() > 0 + + invariant graph_structure_consistent + given graph = create_test_graph() + assert graph.nodes.length() > 0 + and graph.hash.length() > 0 + and graph.timestamp > 0 +} + +// Helper functions (would be implemented in actual codebase) +fn extract_module_name(line: string) -> string { + const parts = line.split_whitespace(); + return parts[1].trim_end_matches("{"); +} + +fn extract_value_expression(line: string) -> string { + const start = line.find(":").unwrap_or(0) + 1; + const end = line.find(";").unwrap_or(line.length()); + return line[start..end].trim(); +} + +fn extract_claim_id(line: string) -> string { + if line.contains("Claim:") { + const start = line.find("Claim:").unwrap(); + const end = line.find("(", start).unwrap_or(line.length()); + return line[start + 6..end].trim(); + } + return "C-phi-001"; // Default claim +} + +fn extract_tolerance_tier(line: string) -> ToleranceTier { + if line.contains("tolerance:") { + const start = line.find("tolerance:").unwrap(); + const end = line.find(",", start).unwrap_or(line.length()); + const tier_str = line[start + 10..end].trim(); + return parse_tolerance_tier(tier_str); + } + return ToleranceTier::WITHIN_UNCERTAINTY; // Default tier +} + +fn evaluate_expression(expr: string) -> f64 { + // Implementation would evaluate mathematical expressions + return 1.618; // Placeholder for golden ratio +} + +fn extract_dependencies_from_expression(expr: string) -> [string] { + // Implementation would extract variable dependencies + return []; +} + +fn extract_dependencies_from_signature(sig: string) -> [string] { + // Implementation would extract function dependencies + return []; +} + +fn extract_test_dependencies(line: string) -> [string] { + // Implementation would extract test dependencies + return []; +} + +fn extract_version(content: string) -> string { + // Implementation would extract version from file + return "v1.0.0"; +} + +fn get_current_timestamp() -> u64 { + // Implementation would get current timestamp + return 1642694400; // Placeholder +} + +fn compute_graph_hash(nodes: HashMap, edges: [GraphEdge]) -> string { + // Implementation would compute hash of graph structure + return "abc123"; +} + +fn generate_drift_details(drift_score: f64, config: DriftConfig) -> string { + return format!("Drift score: {:.4}, threshold: {:.4}", drift_score, config.threshold); +} + +fn all_results_have_claim_ids(results: [TestResult]) -> bool { + // Implementation would verify all results have claim IDs + return true; +} + +fn create_test_graph() -> DriftGraph { + // Implementation would create test graph for unit tests + return DriftGraph{ + nodes: HashMap::new(), + edges: [], + version: "test", + timestamp: 1, + hash: "test", + }; +} + +fn create_test_node() -> GraphNode { + // Implementation would create test node + return GraphNode{ + id: "test::node", + name: "test_node", + type: NodeType::CONSTANT, + claim_id: "C-test", + tolerance_tier: ToleranceTier::EXACT, + value: 1.0, + formula: "1.0", + dependencies: [], + metadata: HashMap::new(), + }; +} + +fn create_modified_test_graph() -> DriftGraph { + // Implementation would create modified test graph + return create_test_graph(); +} + +fn log_error(category: string, message: string) { + // Implementation would log error message +} + +fn parse_tolerance_tier(tier_str: string) -> ToleranceTier { + match tier_str { + "EXACT" => ToleranceTier::EXACT, + "CONJECTURAL" => ToleranceTier::CONJECTURAL, + "WITHIN_UNCERTAINTY" => ToleranceTier::WITHIN_UNCERTAINTY, + _ => ToleranceTier::WITHIN_UNCERTAINTY, + } +} \ No newline at end of file diff --git a/apps/website/public/t27/specs/test_framework/property_test_template.t27 b/apps/website/public/t27/specs/test_framework/property_test_template.t27 new file mode 100644 index 0000000000..9579b0e913 --- /dev/null +++ b/apps/website/public/t27/specs/test_framework/property_test_template.t27 @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/test_framework/property_test_template.t27 +// Ring 052 -- Property-Based Testing Template for GoldenFloat +// Standardized PBT patterns following T27-MATH-PHYSICS-TEST-FRAMEWORK-SPEC.md + +module PBTTemplate { + use test_framework::core::{TestResult, Verdict, ToleranceTier, EngineeringStatus}; + use numeric::golden_float::{GF16, GF32}; + + // ----------------------------------------------------- + // 1. PBT Configuration and Generators + // ----------------------------------------------------- + + // Standard test configuration for property-based tests + struct PropertyTestConfig { + num_tests : u32; + max_shrinks : u32; + seed : u64; + tolerance : f64; + claim_id : string; + tolerance_tier: ToleranceTier; + } + + // Default configuration for GoldenFloat PBT + fn default_gf_config() -> PropertyTestConfig { + return PropertyTestConfig{ + num_tests = 1000, + max_shrinks = 100, + seed = 42, + tolerance = 1e-6, + claim_id = "C-gf-001", + tolerance_tier = ToleranceTier::WITHIN_UNCERTAINTY, + }; + } + + // ----------------------------------------------------- + // 2. GoldenFloat Generators + // ----------------------------------------------------- + + // Generate valid GF16 values with coverage of edge cases + fn generate_gf16(rng: &mut Random) -> GF16 { + const patterns = [ + 0x0000, // +0.0 + 0x8000, // -0.0 + 0x7C00, // +Inf + 0xFC00, // -Inf + 0x7E00, // +NaN (quiet) + 0xFE00, // -NaN (quiet) + ]; + + if rng.next_u32() % 100 < 5 { // 5% edge cases + const idx = rng.next_u32() % patterns.length() as u32; + return GF16::from_bits(patterns[idx as usize]); + } + + // Generate normal numbers with full range coverage + const bits = rng.next_u16(); + return GF16::from_bits(bits); + } + + // Generate valid GF32 values with coverage of edge cases + fn generate_gf32(rng: &mut Random) -> GF32 { + const patterns = [ + 0x00000000, // +0.0 + 0x80000000, // -0.0 + 0x7F800000, // +Inf + 0xFF800000, // -Inf + 0x7FC00000, // +NaN (quiet) + 0xFFC00000, // -NaN (quiet) + ]; + + if rng.next_u32() % 100 < 5 { // 5% edge cases + const idx = rng.next_u32() % patterns.length() as u32; + return GF32::from_bits(patterns[idx as usize]); + } + + // Generate normal numbers with full range coverage + const bits = rng.next_u32(); + return GF32::from_bits(bits); + } + + // Generate pairs of GF16 values for binary operations + fn generate_gf16_pair(rng: &mut Random) -> (GF16, GF16) { + return (generate_gf16(rng), generate_gf16(rng)); + } + + // Generate triples of GF16 values for ternary operations + fn generate_gf16_triple(rng: &mut Random) -> (GF16, GF16, GF16) { + return (generate_gf16(rng), generate_gf16(rng), generate_gf16(rng)); + } + + // ----------------------------------------------------- + // 3. Property Test Executors + // ----------------------------------------------------- + + // Universal property: for all valid inputs, property holds + fn for_all(config: PropertyTestConfig, generator: fn(&mut Random) -> T, property: F) -> TestResult + where F: Fn(T) -> bool + { + let mut rng = Random::new(config.seed); + let mut failures = 0; + let mut counterexamples = []; + + for i in 0..config.num_tests { + const input = generator(&mut rng); + if !property(input) { + failures += 1; + if counterexamples.length() < 10 { + counterexamples.push(input); + } + } + } + + if failures == 0 { + return TestResult{ + verdict: Verdict::PASS, + claim_id: config.claim_id, + tolerance_tier: config.tolerance_tier, + details: "Property holds for all generated inputs", + engineering_status: EngineeringStatus::READY, + }; + } else { + return TestResult{ + verdict: Verdict::FAIL, + claim_id: config.claim_id, + tolerance_tier: config.tolerance_tier, + details: format!("Property failed for {} inputs", failures), + engineering_status: EngineeringStatus::NEEDS_INVESTIGATION, + counterexamples: counterexamples, + }; + } + } + + // Metamorphic property: if f(x) = y, then f'(x) = g(y) + fn metamorphic(config: PropertyTestConfig, generator: fn(&mut Random) -> T, + f: F, g: G, h: H) -> TestResult + where F: Fn(T) -> U, G: Fn(U) -> V, H: Fn(T) -> V + { + let mut rng = Random::new(config.seed); + let mut failures = 0; + let mut counterexamples = []; + + for i in 0..config.num_tests { + const input = generator(&mut rng); + const y = f(input); + const g_y = g(y); + const h_x = h(input); + + if !approx_equal(g_y, h_x, config.tolerance) { + failures += 1; + if counterexamples.length() < 10 { + counterexamples.push((input, y, g_y, h_x)); + } + } + } + + if failures == 0 { + return TestResult{ + verdict: Verdict::PASS, + claim_id: config.claim_id, + tolerance_tier: config.tolerance_tier, + details: "Metamorphic property holds for all inputs", + engineering_status: EngineeringStatus::READY, + }; + } else { + return TestResult{ + verdict: Verdict::FAIL, + claim_id: config.claim_id, + tolerance_tier: config.tolerance_tier, + details: format!("Metamorphic property failed for {} inputs", failures), + engineering_status: EngineeringStatus::NEEDS_INVESTIGATION, + counterexamples: counterexamples, + }; + } + } + + // Differential property: f(x) ~= g(x) within tolerance + fn differential(config: PropertyTestConfig, generator: fn(&mut Random) -> T, + f: F, g: G) -> TestResult + where F: Fn(T) -> U, G: Fn(T) -> U + { + let mut rng = Random::new(config.seed); + let mut failures = 0; + let mut max_diff = 0.0; + let mut counterexamples = []; + + for i in 0..config.num_tests { + const input = generator(&mut rng); + const f_result = f(input); + const g_result = g(input); + const diff = abs(f_result - g_result); + + if diff > max_diff { + max_diff = diff; + } + + if diff > config.tolerance { + failures += 1; + if counterexamples.length() < 10 { + counterexamples.push((input, f_result, g_result, diff)); + } + } + } + + if failures == 0 { + return TestResult{ + verdict: Verdict::PASS, + claim_id: config.claim_id, + tolerance_tier: config.tolerance_tier, + details: format!("Differential property holds (max diff: {})", max_diff), + engineering_status: EngineeringStatus::READY, + }; + } else { + return TestResult{ + verdict: Verdict::FAIL, + claim_id: config.claim_id, + tolerance_tier: config.tolerance_tier, + details: format!("Differential property failed for {} inputs (max diff: {})", failures, max_diff), + engineering_status: EngineeringStatus::NEEDS_INVESTIGATION, + counterexamples: counterexamples, + }; + } + } + + // ----------------------------------------------------- + // 4. GoldenFloat-Specific Property Tests + // ----------------------------------------------------- + + // Property: GF16 addition is commutative + // Claim: C-gf-001 (GoldenFloat meets stated effective accuracy) + property gf16_addition_commutative(config: PropertyTestConfig) -> TestResult { + return for_all(config, generate_gf16_pair, |(a, b)| { + const sum1 = a + b; + const sum2 = b + a; + return sum1 == sum2; + }); + } + + // Property: GF16 addition is associative + // Claim: C-gf-001 (GoldenFloat meets stated effective accuracy) + property gf16_addition_associative(config: PropertyTestConfig) -> TestResult { + return for_all(config, generate_gf16_triple, |(a, b, c)| { + const sum1 = (a + b) + c; + const sum2 = a + (b + c); + return sum1 == sum2; + }); + } + + // Property: GF16 multiplication is commutative + // Claim: C-gf-001 (GoldenFloat meets stated effective accuracy) + property gf16_multiplication_commutative(config: PropertyTestConfig) -> TestResult { + return for_all(config, generate_gf16_pair, |(a, b)| { + const product1 = a * b; + const product2 = b * a; + return product1 == product2; + }); + } + + // Property: GF16 multiplication is associative + // Claim: C-gf-001 (GoldenFloat meets stated effective accuracy) + property gf16_multiplication_associative(config: PropertyTestConfig) -> TestResult { + return for_all(config, generate_gf16_triple, |(a, b, c)| { + const product1 = (a * b) * c; + const product2 = a * (b * c); + return product1 == product2; + }); + } + + // Property: GF16 distributive law: a * (b + c) = a*b + a*c + // Claim: C-gf-001 (GoldenFloat meets stated effective accuracy) + property gf16_distributive_law(config: PropertyTestConfig) -> TestResult { + return for_all(config, generate_gf16_triple, |(a, b, c)| { + const left = a * (b + c); + const right = (a * b) + (a * c); + return left == right; + }); + } + + // Metamorphic: GF16 to f32 conversion should be invertible + // Claim: C-gf-001 (GoldenFloat meets stated effective accuracy) + property gf16_to_f32_invertible(config: PropertyTestConfig) -> TestResult { + return metamorphic(config, generate_gf16, + |x| x.to_f32(), + |f32_val| f32_val.to_gf16(), + |x| x // Identity + ); + } + + // Differential: GF16 vs IEEE fp16 operations + // Claim: C-gf-001 (GoldenFloat meets stated effective accuracy) + property gf16_vs_ieee_fp16(config: PropertyTestConfig) -> TestResult { + return differential(config, generate_gf16_pair, + |(a, b)| (a + b).to_f64(), // GF16 addition + |(a, b)| { // IEEE fp16 addition + const a_f16 = a.to_ieee_fp16(); + const b_f16 = b.to_ieee_fp16(); + const sum_f16 = a_f16 + b_f16; + return sum_f16.to_f64(); + } + ); + } + + // Property: GF16 precision bounds + // Claim: C-gf-001 (GoldenFloat meets stated effective accuracy) + property gf16_precision_bounds(config: PropertyTestConfig) -> TestResult { + return for_all(config, generate_gf16, |x| { + if x.is_nan() || x.is_infinite() { + return true; // Skip NaN/Inf + } + + const abs_value = abs(x.to_f64()); + if abs_value == 0.0 { + return true; // Skip zero + } + + // Check that GF16 precision is within claimed bounds + const next_representable = x.next_after(x.to_f64() * 1.001); + const diff = abs(next_representable - x.to_f64()); + + // GF16 should have at least 3 decimal digits of precision + return diff / abs_value <= 1e-3; + }); + } + + // ----------------------------------------------------- + // 5. Test Orchestration + // ----------------------------------------------------- + + // Standard GoldenFloat property test suite + fn goldenfloat_property_test_suite() -> [TestResult] { + const config = default_gf_config(); + const strict_config = PropertyTestConfig{ + num_tests: 5000, + max_shrinks: 200, + tolerance: 1e-9, + ..config + }; + + return [ + gf16_addition_commutative(config), + gf16_addition_associative(strict_config), + gf16_multiplication_commutative(config), + gf16_multiplication_associative(strict_config), + gf16_distributive_law(strict_config), + gf16_to_f32_invertible(config), + gf16_vs_ieee_fp16(config), + gf16_precision_bounds(config), + ]; + } + + // Benchmark: Property test execution time + bench goldenfloat_property_test_performance + measure: milliseconds to execute goldenfloat_property_test_suite() + target: < 5000ms + + // TDD-Inside-Spec: Verification tests for property test framework + // =========================================================================================== + + test property_test_config_creation + given config = default_gf_config() + then config.num_tests == 1000 + and config.claim_id == "C-gf-001" + and config.tolerance_tier == ToleranceTier::WITHIN_UNCERTAINTY + + test gf16_generator_coverage + given rng = Random::new(42) + and samples = [generate_gf16(&mut rng) for _ in 0..100] + then has_edge_cases(samples) + and has_normal_numbers(samples) + + test for_all_passes_when_property_holds + given config = default_gf_config() + and result = for_all(config, |_| 0, |_| true) + then result.verdict == Verdict::PASS + and result.engineering_status == EngineeringStatus::READY + + test for_all_fails_when_property_fails + given config = default_gf_config() + and result = for_all(config, |_| 0, |_| false) + then result.verdict == Verdict::FAIL + and result.engineering_status == EngineeringStatus::NEEDS_INVESTIGATION + + test metamorphic_property_passes_when_relations_hold + given config = default_gf_config() + and result = metamorphic(config, |x| x, |x| x, |x| x, |x| x) + then result.verdict == Verdict::PASS + + test differential_property_passes_within_tolerance + given config = default_gf_config() + and result = differential(config, |x| x, |x| x) + then result.verdict == Verdict::PASS + + invariant property_test_config_valid + assert default_gf_config().num_tests > 0 + and default_gf_config().max_shrinks > 0 + and default_gf_config().tolerance > 0.0 + + invariant goldenfloat_test_suite_complete + given suite = goldenfloat_property_test_suite() + assert suite.length() >= 8 + and all_tests_have_claim_ids(suite) + + invariant gf16_property_test_coverage + assert has_commutativity_tests() + and has_associativity_tests() + and has_distributivity_tests() + and has_precision_tests() +} + +// Helper functions (would be implemented in actual codebase) +fn approx_equal(a: f64, b: f64, tolerance: f64) -> bool { + return abs(a - b) <= tolerance; +} + +fn has_edge_cases(samples: [GF16]) -> bool { + // Implementation would check for presence of edge cases + return true; +} + +fn has_normal_numbers(samples: [GF16]) -> bool { + // Implementation would check for presence of normal numbers + return true; +} + +fn all_tests_have_claim_ids(suite: [TestResult]) -> bool { + // Implementation would verify all tests have claim IDs + return true; +} + +fn has_commutativity_tests() -> bool { return true; } +fn has_associativity_tests() -> bool { return true; } +fn has_distributivity_tests() -> bool { return true; } +fn has_precision_tests() -> bool { return true; } \ No newline at end of file diff --git a/apps/website/public/t27/specs/test_framework/runner.t27 b/apps/website/public/t27/specs/test_framework/runner.t27 new file mode 100644 index 0000000000..09eb8d565b --- /dev/null +++ b/apps/website/public/t27/specs/test_framework/runner.t27 @@ -0,0 +1,618 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27 Math/Physics Test Framework - Runner +// +// Ring 050: Test runner implementation per T27-MATH-PHYSICS-TEST-FRAMEWORK-SPEC.md +// Entry point for `tri test ` execution + +module runner; + +use core::{TestResult, Verdict, ToleranceTier, EngineeringStatus, GatePipeline, for_all, metamorphic, differential, TapReporter, JsonReporter}; + +/// Test runner configuration +struct TestRunnerConfig { + /// Maximum number of iterations for property-based tests + max_iterations: u32, + /// Default tolerance for numeric comparisons + default_tolerance: f64, + /// Whether to generate detailed reports + verbose: bool, + /// Output format (tap, json, both) + output_format: string +} + +impl TestRunnerConfig { + fn default() -> TestRunnerConfig { + TestRunnerConfig { + max_iterations: 10000, + default_tolerance: 1e-12, + verbose: false, + output_format: "tap".to_string() + } + } + + fn with_iterations(self, iterations: u32) -> TestRunnerConfig { + TestRunnerConfig { max_iterations: iterations, ..self } + } + + fn with_tolerance(self, tolerance: f64) -> TestRunnerConfig { + TestRunnerConfig { default_tolerance: tolerance, ..self } + } + + fn with_verbose(self, verbose: bool) -> TestRunnerConfig { + TestRunnerConfig { verbose, ..self } + } + + fn with_format(self, format: string) -> TestRunnerConfig { + TestRunnerConfig { output_format: format, ..self } + } +} + +/// Test runner state +struct TestRunner { + config: TestRunnerConfig, + results: Vec, + start_time: f64 // Using f64 for timestamp +} + +impl TestRunner { + fn new(config: TestRunnerConfig) -> TestRunner { + TestRunner { + config, + results: Vec::new(), + start_time: current_timestamp() + } + } + + /// Execute a single test specification + fn run_test_spec(&mut self, spec_path: string) -> Result, string> { + // Parse the .t27 specification + let spec_content = read_file(spec_path)?; + let spec = parse_t27_spec(spec_content)?; + + // Execute all test blocks in the spec + let mut test_results = Vec::new(); + + for test_block in spec.test_blocks { + let result = self.execute_test_block(test_block)?; + test_results.push(result); + } + + Ok(test_results) + } + + /// Execute a single test block + fn execute_test_block(&mut self, test_block: TestBlock) -> Result { + match test_block.kind { + TestKind::Unit => self.execute_unit_test(test_block), + TestKind::Property => self.execute_property_test(test_block), + TestKind::Invariant => self.execute_invariant_test(test_block), + TestKind::Bench => self.execute_benchmark_test(test_block), + TestKind::GatePipeline => self.execute_gate_pipeline_test(test_block) + } + } + + /// Execute unit test + fn execute_unit_test(&mut self, test_block: TestBlock) -> Result { + let start = current_timestamp(); + + // Execute the test logic + let result = test_block.test_fn(); + let duration = current_timestamp() - start; + + Ok(TestResult { + name: test_block.name, + verdict: if result { Verdict::CLEAN } else { Verdict::FAIL }, + tolerance: test_block.tolerance_tier.unwrap_or(ToleranceTier::EXACT), + engineering: EngineeringStatus::tested, + claim_id: test_block.claim_id, + input: test_block.input, + expected: test_block.expected, + actual: result, + delta: None, + message: if result { None } else { Some("Unit test failed".to_string()) } + }) + } + + /// Execute property-based test + fn execute_property_test(&mut self, test_block: TestBlock) -> Result { + let iterations = test_block.iterations.unwrap_or(self.config.max_iterations); + let domain = test_block.domain_fn.unwrap_or(|| default_domain()); + let property = test_block.property_fn.unwrap_or(default_property()); + + let result = for_all(domain, property, iterations); + + Ok(result) + } + + /// Execute invariant test + fn execute_invariant_test(&mut self, test_block: TestBlock) -> Result { + // Invariant tests are always property tests with exact tolerance + let mut invariant_block = test_block; + invariant_block.tolerance_tier = Some(ToleranceTier::EXACT); + self.execute_property_test(invariant_block) + } + + /// Execute benchmark test + fn execute_benchmark_test(&mut self, test_block: TestBlock) -> Result { + let start = current_timestamp(); + + // Run the benchmark operation multiple times + let iterations = test_block.iterations.unwrap_or(1000); + for _ in 0..iterations { + let _ = test_block.test_fn(); + } + + let duration = current_timestamp() - start; + let avg_time = duration / iterations as f64; + + Ok(TestResult { + name: format!("{}_bench", test_block.name), + verdict: Verdict::CLEAN, // Benchmarks don't fail unless error + tolerance: ToleranceTier::EMPIRICAL_FIT, + engineering: EngineeringStatus::empirical, + claim_id: test_block.claim_id, + input: iterations, + expected: 0.0, // Expected execution time + actual: avg_time, + delta: None, + message: Some(format!("Average execution time: {:.6}s", avg_time)) + }) + } + + /// Execute gate pipeline test + fn execute_gate_pipeline_test(&mut self, test_block: TestBlock) -> Result { + let pipeline = test_block.pipeline_fn.unwrap_or(default_pipeline())(); + let verdict = pipeline.verdict(); + + Ok(TestResult { + name: format!("{}_pipeline", test_block.name), + verdict, + tolerance: ToleranceTier::EXACT, + engineering: EngineeringStatus::tested, + claim_id: test_block.claim_id, + input: test_block.input, + expected: GatePipeline::all_true(), + actual: pipeline, + delta: None, + message: if verdict == Verdict::FAIL { + Some("Gate pipeline failed - check which gates are false".to_string()) + } else { None } + }) + } + + /// Generate test reports + fn generate_reports(&self) -> string { + let mut output = String::new(); + + if self.config.output_format.contains("tap") || self.config.output_format.contains("both") { + let tap_reporter = TapReporter::new(self.results.clone()); + output.push_str("=== TAP Report ===\n"); + output.push_str(&tap_reporter.generate_tap()); + output.push_str("\n"); + } + + if self.config.output_format.contains("json") || self.config.output_format.contains("both") { + let json_reporter = JsonReporter::new(self.results.clone()); + output.push_str("=== JSON Report ===\n"); + output.push_str(&json_reporter.generate_json()); + output.push_str("\n"); + } + + output + } + + /// Print summary statistics + fn print_summary(&self) { + let total = self.results.len(); + let passed = self.results.iter().filter(|r| r.verdict == Verdict::CLEAN).count(); + let failed = self.results.iter().filter(|r| r.verdict == Verdict::FAIL).count(); + let skipped = self.results.iter().filter(|r| r.verdict == Verdict::SKIP).count(); + let partial = self.results.iter().filter(|r| r.verdict == Verdict::PARTIAL).count(); + + println!("=== Test Summary ==="); + println!("Total tests: {}", total); + println!("Passed: {}", passed); + println!("Failed: {}", failed); + println!("Skipped: {}", skipped); + println!("Partial: {}", partial); + + if failed > 0 { + println!("\n=== Failed Tests ==="); + for result in &self.results { + if result.verdict == Verdict::FAIL { + println!("0 {}", result.name); + if let Some(ref msg) = result.message { + println!(" {}", msg); + } + } + } + } + + if self.config.verbose { + println!("\n=== All Results ==="); + for result in &self.results { + let status = match result.verdict { + Verdict::CLEAN => "1", + Verdict::FAIL => "2", + Verdict::SKIP => "3", + Verdict::PARTIAL => "~" + }; + println!("{} {} ({})", status, result.name, result.engineering); + } + } + } + + /// Exit with appropriate code based on test results + fn exit_with_code(&self) -> ! { + let failed = self.results.iter().filter(|r| r.verdict == Verdict::FAIL).count(); + + if failed > 0 { + std::process::exit(1); + } else { + std::process::exit(0); + } + } +} + +// 4 Test specification parser + +/// Parsed .t27 test specification +struct T27Spec { + name: string, + version: string, + test_blocks: Vec +} + +/// Individual test block within a specification +struct TestBlock { + name: string, + kind: TestKind, + tolerance_tier: Option, + claim_id: Option, + input: any, + expected: any, + iterations: Option, + test_fn: Option bool>, + domain_fn: Option any>, + property_fn: Option bool>, + pipeline_fn: Option GatePipeline> +} + +/// Test kind classification +enum TestKind { + Unit, // Simple unit test + Property, // Property-based test + Invariant, // Mathematical invariant + Bench, // Performance benchmark + GatePipeline // Sequential gate pipeline +} + +// 5 Helper functions + +fn read_file(path: string) -> Result { + // Placeholder for file reading + Ok("".to_string()) +} + +fn parse_t27_spec(content: string) -> Result { + // Placeholder for .t27 specification parsing + Ok(T27Spec { + name: "test_spec".to_string(), + version: "1.0".to_string(), + test_blocks: Vec::new() + }) +} + +fn current_timestamp() -> f64 { + // Placeholder for timestamp + 0.0 +} + +fn default_domain() -> fn() -> any { + || 0u32 // Default domain +} + +fn default_property() -> fn(any) -> bool { + |_x| true // Default property (always true) +} + +fn default_pipeline() -> fn() -> GatePipeline { + || GatePipeline::all_true() +} + +// 6 Main entry point + +/// Execute test specification with default configuration +pub fn run_test_spec_file(spec_path: string) -> Result<(), string> { + let config = TestRunnerConfig::default(); + run_test_spec_file_with_config(spec_path, config) +} + +/// Execute test specification with custom configuration +pub fn run_test_spec_file_with_config(spec_path: string, config: TestRunnerConfig) -> Result<(), string> { + let mut runner = TestRunner::new(config); + + // Run the test specification + let results = runner.run_test_spec(spec_path)?; + runner.results = results; + + // Generate reports + let reports = runner.generate_reports(); + if runner.config.verbose { + println!("{}", reports); + } + + // Print summary + runner.print_summary(); + + // Exit with appropriate code + runner.exit_with_code(); +} + +/// Command-line interface for test runner +pub fn main(args: Vec) -> Result<(), string> { + let mut config = TestRunnerConfig::default(); + let mut spec_files = Vec::new(); + + // Parse command line arguments + let mut i = 0; + while i < args.len() { + match args[i].as_str() { + "--iterations" | "-i" => { + if i + 1 < args.len() { + config.max_iterations = args[i + 1].parse::().map_err(|_| "Invalid iteration count".to_string())?; + i += 1; + } + } + "--tolerance" | "-t" => { + if i + 1 < args.len() { + config.default_tolerance = args[i + 1].parse::().map_err(|_| "Invalid tolerance".to_string())?; + i += 1; + } + } + "--verbose" | "-v" => { + config.verbose = true; + } + "--format" | "-f" => { + if i + 1 < args.len() { + config.output_format = args[i + 1].clone(); + i += 1; + } + } + "--help" | "-h" => { + print_help(); + return Ok(()); + } + _ => { + if !args[i].starts_with("-") { + spec_files.push(args[i].clone()); + } + } + } + i += 1; + } + + if spec_files.is_empty() { + return Err("No test specification files provided".to_string()); + } + + // Run all specified test files + let mut overall_success = true; + for spec_file in spec_files { + if let Err(err) = run_test_spec_file_with_config(spec_file, config.clone()) { + eprintln!("Error running {}: {}", spec_file, err); + overall_success = false; + } + } + + if !overall_success { + std::process::exit(1); + } + + Ok(()) +} + +fn print_help() { + println!("t27 Test Runner - Ring 050"); + println!(""); + println!("USAGE: tri test [OPTIONS] ..."); + println!(""); + println!("OPTIONS:"); + println!(" -i, --iterations N Maximum iterations for property tests (default: 10000)"); + println!(" -t, --tolerance T Default tolerance for comparisons (default: 1e-12)"); + println!(" -v, --verbose Enable verbose output"); + println!(" -f, --format FMT Output format: tap, json, both (default: tap)"); + println!(" -h, --help Show this help message"); + println!(""); + println!("EXAMPLES:"); + println!(" tri test specs/math/constants.t27"); + println!(" tri test --format json --verbose specs/nn/attention.t27"); + println!(" tri test --iterations 100000 specs/test_framework/core.t27"); +} + +test runner_config_default { + let config = TestRunnerConfig::default(); + assert config.max_iterations == 10000; + assert config.default_tolerance == 1e-12; + assert config.verbose == false; + assert config.output_format == "tap"; +} + +test runner_config_with_iterations { + let config = TestRunnerConfig::default().with_iterations(5000); + assert config.max_iterations == 5000; +} + +test runner_config_with_tolerance { + let config = TestRunnerConfig::default().with_tolerance(1e-6); + assert config.default_tolerance == 1e-6; +} + +test runner_config_with_verbose { + let config = TestRunnerConfig::default().with_verbose(true); + assert config.verbose == true; +} + +test runner_config_with_format { + let config = TestRunnerConfig::default().with_format("json"); + assert config.output_format == "json"; +} + +test runner_config_chain_builders { + let config = TestRunnerConfig::default() + .with_iterations(999) + .with_tolerance(0.001) + .with_verbose(true) + .with_format("both"); + assert config.max_iterations == 999; + assert config.default_tolerance == 0.001; + assert config.verbose == true; + assert config.output_format == "both"; +} + +test runner_new_initializes_empty { + let config = TestRunnerConfig::default(); + let runner = TestRunner::new(config); + assert runner.results.len() == 0; +} + +test runner_new_preserves_config { + let config = TestRunnerConfig::default().with_verbose(true); + let runner = TestRunner::new(config); + assert runner.config.verbose == true; +} + +test runner_execute_unit_test_clean { + let config = TestRunnerConfig::default(); + let mut runner = TestRunner::new(config); + let test_block = TestBlock { + name: "unit_pass", + kind: TestKind::Unit, + test_fn: || true, + ..TestBlock::default() + }; + let result = runner.execute_unit_test(test_block); + assert result.is_ok(); + assert result.unwrap().verdict == Verdict::CLEAN; +} + +test runner_execute_unit_test_fail { + let config = TestRunnerConfig::default(); + let mut runner = TestRunner::new(config); + let test_block = TestBlock { + name: "unit_fail", + kind: TestKind::Unit, + test_fn: || false, + ..TestBlock::default() + }; + let result = runner.execute_unit_test(test_block); + assert result.is_ok(); + assert result.unwrap().verdict == Verdict::FAIL; +} + +test runner_benchmark_returns_clean { + let config = TestRunnerConfig::default(); + let mut runner = TestRunner::new(config); + let test_block = TestBlock { + name: "bench_basic", + kind: TestKind::Bench, + iterations: Some(100), + test_fn: || true, + ..TestBlock::default() + }; + let result = runner.execute_benchmark_test(test_block); + assert result.is_ok(); + assert result.unwrap().verdict == Verdict::CLEAN; +} + +test runner_invariant_uses_exact_tolerance { + let config = TestRunnerConfig::default(); + let mut runner = TestRunner::new(config); + let test_block = TestBlock { + name: "inv_test", + kind: TestKind::Invariant, + tolerance_tier: None, + ..TestBlock::default() + }; + let result = runner.execute_invariant_test(test_block); + assert result.is_ok(); +} + +test runner_generate_reports_tap { + let config = TestRunnerConfig::default().with_format("tap"); + let runner = TestRunner::new(config); + let output = runner.generate_reports(); + assert output.contains("TAP Report"); +} + +test runner_generate_reports_json { + let config = TestRunnerConfig::default().with_format("json"); + let runner = TestRunner::new(config); + let output = runner.generate_reports(); + assert output.contains("JSON Report"); +} + +test runner_generate_reports_both { + let config = TestRunnerConfig::default().with_format("both"); + let runner = TestRunner::new(config); + let output = runner.generate_reports(); + assert output.contains("TAP Report"); + assert output.contains("JSON Report"); +} + +test runner_spec_parse_returns_ok { + let result = parse_t27_spec("".to_string()); + assert result.is_ok(); +} + +test runner_spec_parse_has_empty_blocks { + let spec = parse_t27_spec("".to_string()).unwrap(); + assert spec.test_blocks.len() == 0; +} + +test runner_read_file_returns_ok { + let result = read_file("nonexistent.t27".to_string()); + assert result.is_ok(); +} + +invariant runner_config_max_iterations_positive { + let config = TestRunnerConfig::default(); + assert config.max_iterations > 0; +} + +invariant runner_config_tolerance_positive { + let config = TestRunnerConfig::default(); + assert config.default_tolerance > 0.0; +} + +invariant runner_results_start_empty { + let config = TestRunnerConfig::default(); + let runner = TestRunner::new(config); + assert runner.results.len() == 0; +} + +invariant runner_start_time_non_negative { + let config = TestRunnerConfig::default(); + let runner = TestRunner::new(config); + assert runner.start_time >= 0.0; +} + +bench runner_config_default_latency { + measure: nanoseconds to create default TestRunnerConfig + target: < 100ns +} + +bench runner_new_latency { + measure: nanoseconds to create TestRunner + target: < 500ns +} + +bench runner_generate_reports_latency { + measure: nanoseconds to generate empty reports + target: < 1000ns +} + +bench runner_execute_unit_test_latency { + measure: nanoseconds to execute a trivial unit test + target: < 2000ns +} \ No newline at end of file diff --git a/apps/website/public/t27/specs/test_framework/verilog_bench_harness.t27 b/apps/website/public/t27/specs/test_framework/verilog_bench_harness.t27 new file mode 100644 index 0000000000..440e3d0ca0 --- /dev/null +++ b/apps/website/public/t27/specs/test_framework/verilog_bench_harness.t27 @@ -0,0 +1,460 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/test_framework/verilog_bench_harness.t27 +// Ring 053 -- Verilog Bench Harness for Hardware-in-the-Loop Testing +// Integration framework for testing GoldenFloat operations against Verilog RTL + +module VerilogBenchHarness { + use test_framework::core::{TestResult, Verdict, ToleranceTier, EngineeringStatus}; + use numeric::golden_float::{GF16, GF32}; + + // ----------------------------------------------------- + // 1. Verilog Integration Configuration + // ----------------------------------------------------- + + // Verilog simulation configuration + struct VerilogConfig { + simulator : string; // "iverilog", "vcs", "questa", "xcelium" + top_module : string; + rtl_files : [string]; + test_files : [string]; + includes : [string]; + defines : [(string, string)]; + timeout_ms : u32; + wave_dump : bool; + coverage : bool; + claim_id : string; + tolerance_tier: ToleranceTier; + } + + // Default Verilog configuration for GoldenFloat testing + fn default_verilog_config() -> VerilogConfig { + return VerilogConfig{ + simulator: "iverilog", + top_module: "gf16_testbench", + rtl_files: [ + "rtl/gf16_add.v", + "rtl/gf16_mul.v", + "rtl/gf16_convert.v", + "rtl/gf16_top.v" + ], + test_files: [ + "rtl/tests/gf16_basic_tests.v", + "rtl/tests/gf16_edge_cases.v", + "rtl/tests/gf16_random_vectors.v" + ], + includes: [ + "rtl/includes/gf16_defines.vh", + "rtl/includes/tb_defines.vh" + ], + defines: [ + ("SIMULATION", "1"), + ("GF16_TESTING", "1"), + ("WAVE_DUMP", "1") + ], + timeout_ms: 30000, + wave_dump: true, + coverage: true, + claim_id: "C-gf-001", + tolerance_tier: ToleranceTier::WITHIN_UNCERTAINTY, + }; + } + + // ----------------------------------------------------- + // 2. Test Vector Generation + // ----------------------------------------------------- + + // Test vector for Verilog simulation + struct VerilogTestVector { + name: string; + inputs: [f64]; + expected_outputs: [f64]; + tolerance: f64; + description: string; + } + + // Generate comprehensive test vectors for GF16 operations + fn generate_gf16_test_vectors() -> [VerilogTestVector] { + return [ + // Basic operations + VerilogTestVector{ + name: "gf16_add_positive", + inputs: [1.0, 2.0], + expected_outputs: [3.0], + tolerance: 1e-6, + description: "Addition of two positive GF16 numbers" + }, + VerilogTestVector{ + name: "gf16_add_negative", + inputs: [-1.5, -2.5], + expected_outputs: [-4.0], + tolerance: 1e-6, + description: "Addition of two negative GF16 numbers" + }, + VerilogTestVector{ + name: "gf16_add_mixed_signs", + inputs: [3.0, -1.5], + expected_outputs: [1.5], + tolerance: 1e-6, + description: "Addition of positive and negative GF16 numbers" + }, + + // Edge cases + VerilogTestVector{ + name: "gf16_add_zero", + inputs: [0.0, 1.5], + expected_outputs: [1.5], + tolerance: 1e-6, + description: "Addition with zero" + }, + VerilogTestVector{ + name: "gf16_add_infinity", + inputs: [1.0, f64::INFINITY], + expected_outputs: [f64::INFINITY], + tolerance: 1e-6, + description: "Addition with infinity" + }, + VerilogTestVector{ + name: "gf16_add_nan", + inputs: [1.0, f64::NAN], + expected_outputs: [f64::NAN], + tolerance: 1e-6, + description: "Addition with NaN" + }, + + // Multiplication + VerilogTestVector{ + name: "gf16_mul_basic", + inputs: [2.0, 3.0], + expected_outputs: [6.0], + tolerance: 1e-6, + description: "Basic multiplication" + }, + VerilogTestVector{ + name: "gf16_mul_fractional", + inputs: [0.5, 0.25], + expected_outputs: [0.125], + tolerance: 1e-6, + description: "Multiplication of fractions" + }, + + // Conversion tests + VerilogTestVector{ + name: "gf16_to_f32_conversion", + inputs: [1.5], + expected_outputs: [1.5], + tolerance: 1e-6, + description: "GF16 to F32 conversion" + }, + + // Precision boundary tests + VerilogTestVector{ + name: "gf16_precision_boundary", + inputs: [1.00390625], // Smallest GF16 fraction + expected_outputs: [1.00390625], + tolerance: 1e-6, + description: "Precision boundary test" + } + ]; + } + + // Generate random test vectors for comprehensive coverage + fn generate_random_gf16_vectors(count: u32, seed: u64) -> [VerilogTestVector] { + let mut rng = Random::new(seed); + let mut vectors = []; + + for i in 0..count { + const a = rng.next_f64() * 10.0 - 5.0; // Range: -5.0 to 5.0 + const b = rng.next_f64() * 10.0 - 5.0; + const sum = a + b; + const product = a * b; + + vectors.push(VerilogTestVector{ + name: format!("random_gf16_op_{}", i), + inputs: [a, b], + expected_outputs: [sum, product], + tolerance: 1e-4, // Looser tolerance for random values + description: format!("Random GF16 operations test {}", i) + }); + } + + return vectors; + } + + // ----------------------------------------------------- + // 3. Verilog Test Execution + // ----------------------------------------------------- + + // Verilog simulation result + struct VerilogSimulationResult { + success: bool; + output: string; + error: string; + wave_file: string; + coverage_report: string; + execution_time_ms: u32; + } + + // Execute Verilog simulation with test vectors + fn run_verilog_simulation(config: VerilogConfig, test_vectors: [VerilogTestVector]) -> VerilogSimulationResult { + // Generate Verilog testbench file + const testbench_content = generate_verilog_testbench(config, test_vectors); + const testbench_file = "rtl/tests/generated_gf16_testbench.v"; + write_file(testbench_file, testbench_content); + + // Build simulation command based on simulator + const cmd = build_simulation_command(config, testbench_file); + + // Execute simulation + const start_time = get_current_time_ms(); + const result = execute_command(cmd, config.timeout_ms); + const execution_time = get_current_time_ms() - start_time; + + return VerilogSimulationResult{ + success: result.exit_code == 0, + output: result.stdout, + error: result.stderr, + wave_file: if config.wave_dump { "sim_output.vcd" } else { "" }, + coverage_report: if config.coverage { "coverage_report.xml" } else { "" }, + execution_time_ms: execution_time, + }; + } + + // Parse Verilog simulation output + fn parse_verilog_output(output: string, test_vectors: [VerilogTestVector]) -> [TestResult] { + let mut results = []; + let mut current_test = ""; + let mut test_passed = true; + let mut differences = []; + + for line in output.lines() { + if line.contains("TEST_START:") { + current_test = line.split(":")[1].trim(); + test_passed = true; + differences = []; + } else if line.contains("TEST_PASS:") { + const test_name = line.split(":")[1].trim(); + const vector = find_test_vector(test_vectors, test_name); + if vector != null { + results.push(TestResult{ + verdict: Verdict::PASS, + claim_id: vector.claim_id, + tolerance_tier: vector.tolerance_tier, + details: format!("Verilog simulation passed: {}", test_name), + engineering_status: EngineeringStatus::READY, + }); + } + } else if line.contains("TEST_FAIL:") { + const test_name = line.split(":")[1].trim(); + const vector = find_test_vector(test_vectors, test_name); + if vector != null { + results.push(TestResult{ + verdict: Verdict::FAIL, + claim_id: vector.claim_id, + tolerance_tier: vector.tolerance_tier, + details: format!("Verilog simulation failed: {}", test_name), + engineering_status: EngineeringStatus::NEEDS_INVESTIGATION, + differences: differences, + }); + } + } else if line.contains("DIFFERENCE:") { + const diff_info = line.split(":")[1].trim(); + differences.push(diff_info); + } + } + + return results; + } + + // ----------------------------------------------------- + // 4. Hardware-Software Co-verification + // ----------------------------------------------------- + + // Compare software and hardware results + fn compare_sw_hw_results(sw_results: [TestResult], hw_results: [TestResult]) -> TestResult { + let mut mismatches = 0; + let mut matches = 0; + + for sw_result in sw_results { + const hw_result = find_matching_result(hw_results, sw_result.name); + if hw_result != null { + if sw_result.verdict == hw_result.verdict { + matches += 1; + } else { + mismatches += 1; + } + } + } + + if mismatches == 0 { + return TestResult{ + verdict: Verdict::PASS, + claim_id: "C-gf-001", + tolerance_tier: ToleranceTier::WITHIN_UNCERTAINTY, + details: format!("Software-Hardware co-verification passed: {} tests match", matches), + engineering_status: EngineeringStatus::READY, + }; + } else { + return TestResult{ + verdict: Verdict::FAIL, + claim_id: "C-gf-001", + tolerance_tier: ToleranceTier::WITHIN_UNCERTAINTY, + details: format!("Software-Hardware co-verification failed: {} mismatches out of {}", + mismatches, matches + mismatches), + engineering_status: EngineeringStatus::NEEDS_INVESTIGATION, + }; + } + } + + // ----------------------------------------------------- + // 5. Comprehensive Test Orchestration + // ----------------------------------------------------- + + // Full GoldenFloat hardware verification flow + fn goldenfloat_hardware_verification() -> [TestResult] { + const config = default_verilog_config(); + + // Generate test vectors + const basic_vectors = generate_gf16_test_vectors(); + const random_vectors = generate_random_gf16_vectors(1000, 42); + const all_vectors = basic_vectors.concat(random_vectors); + + // Run Verilog simulation + const sim_result = run_verilog_simulation(config, all_vectors); + + if !sim_result.success { + return [TestResult{ + verdict: Verdict::FAIL, + claim_id: "C-gf-001", + tolerance_tier: ToleranceTier::WITHIN_UNCERTAINTY, + details: format!("Verilog simulation failed: {}", sim_result.error), + engineering_status: EngineeringStatus::BLOCKED, + }]; + } + + // Parse simulation output + const hw_results = parse_verilog_output(sim_result.output, all_vectors); + + // Run software tests for comparison + const sw_results = run_software_goldenfloat_tests(all_vectors); + + // Compare software and hardware results + const co_verification_result = compare_sw_hw_results(sw_results, hw_results); + + // Generate coverage report + const coverage_result = generate_coverage_report(sim_result.coverage_report); + + return [co_verification_result, coverage_result].concat(hw_results); + } + + // Benchmark: Verilog simulation performance + bench verilog_simulation_performance + measure: milliseconds to run goldenfloat_hardware_verification() + target: < 60000ms + + // ----------------------------------------------------- + // 6. TDD-Inside-Spec: Verilog Harness Tests + // ----------------------------------------------------- + + test verilog_config_creation + given config = default_verilog_config() + then config.simulator == "iverilog" + and config.top_module == "gf16_testbench" + and config.claim_id == "C-gf-001" + + test test_vector_generation + given vectors = generate_gf16_test_vectors() + then vectors.length() >= 10 + and has_basic_operation_tests(vectors) + and has_edge_case_tests(vectors) + + test random_vector_generation + given vectors = generate_random_gf16_vectors(100, 42) + then vectors.length() == 100 + and all_vectors_have_inputs(vectors) + + test verilog_simulation_execution + given config = default_verilog_config() + and vectors = generate_gf16_test_vectors() + and result = run_verilog_simulation(config, vectors) + then result.execution_time_ms > 0 + and (result.success || result.error.length() > 0) + + test software_hardware_comparison + given sw_results = [TestResult{verdict: Verdict::PASS, claim_id: "C-gf-001", ...}] + and hw_results = [TestResult{verdict: Verdict::PASS, claim_id: "C-gf-001", ...}] + and result = compare_sw_hw_results(sw_results, hw_results) + then result.verdict == Verdict::PASS + + test hardware_verification_flow + given results = goldenfloat_hardware_verification() + then results.length() >= 1 + and has_claim_ids(results) + + invariant verilog_config_valid + assert default_verilog_config().rtl_files.length() > 0 + and default_verilog_config().test_files.length() > 0 + and default_verilog_config().timeout_ms > 0 + + invariant test_vectors_complete + given vectors = generate_gf16_test_vectors() + assert has_addition_tests(vectors) + and has_multiplication_tests(vectors) + and has_conversion_tests(vectors) + and has_edge_case_tests(vectors) +} + +// Helper functions (would be implemented in actual codebase) +fn generate_verilog_testbench(config: VerilogConfig, vectors: [VerilogTestVector]) -> string { + // Implementation would generate Verilog testbench code + return "// Generated Verilog testbench for GoldenFloat testing"; +} + +fn build_simulation_command(config: VerilogConfig, testbench_file: string) -> string { + // Implementation would build simulator-specific command + return "iverilog -o sim_output -I rtl/includes rtl/tests/generated_gf16_testbench.v"; +} + +fn execute_command(cmd: string, timeout_ms: u32) -> CommandResult { + // Implementation would execute command and capture output + return CommandResult{exit_code: 0, stdout: "", stderr: ""}; +} + +fn find_test_vector(vectors: [VerilogTestVector], name: string) -> VerilogTestVector? { + // Implementation would find test vector by name + return null; +} + +fn find_matching_result(results: [TestResult], name: string) -> TestResult? { + // Implementation would find matching test result + return null; +} + +fn run_software_goldenfloat_tests(vectors: [VerilogTestVector]) -> [TestResult] { + // Implementation would run software GoldenFloat tests + return []; +} + +fn generate_coverage_report(coverage_file: string) -> TestResult { + // Implementation would generate coverage report + return TestResult{ + verdict: Verdict::PASS, + claim_id: "C-gf-001", + tolerance_tier: ToleranceTier::WITHIN_UNCERTAINTY, + details: "Coverage report generated", + engineering_status: EngineeringStatus::READY, + }; +} + +fn has_basic_operation_tests(vectors: [VerilogTestVector]) -> bool { return true; } +fn has_edge_case_tests(vectors: [VerilogTestVector]) -> bool { return true; } +fn all_vectors_have_inputs(vectors: [VerilogTestVector]) -> bool { return true; } +fn has_claim_ids(results: [TestResult]) -> bool { return true; } +fn has_addition_tests(vectors: [VerilogTestVector]) -> bool { return true; } +fn has_multiplication_tests(vectors: [VerilogTestVector]) -> bool { return true; } +fn has_conversion_tests(vectors: [VerilogTestVector]) -> bool { return true; } + +struct CommandResult { + exit_code: i32; + stdout: string; + stderr: string; +} \ No newline at end of file diff --git a/apps/website/public/t27/specs/tools/registry.t27 b/apps/website/public/t27/specs/tools/registry.t27 new file mode 100644 index 0000000000..314402b657 --- /dev/null +++ b/apps/website/public/t27/specs/tools/registry.t27 @@ -0,0 +1,522 @@ +// specs/tools/registry.t27 +// Tools Registry Operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module ToolsRegistry { + use base::types; + use tools::schema; + + // ==================================================================== + // Registry Operations + // ==================================================================== + + // create creates a new tool registry + fn create() -> ToolRegistry { + // Implementation: Create empty registry + } + + // register registers a tool in the registry + fn register(registry: ToolRegistry, tool: ToolDefinition) -> Result { + // Implementation: Register tool + } + + // unregister removes a tool from the registry + fn unregister(registry: ToolRegistry, toolID: ToolID) -> Result { + // Implementation: Unregister and return tool + } + + // get retrieves a tool by ID + fn get(registry: ToolRegistry, toolID: ToolID) -> Result { + // Implementation: Get tool definition + } + + // has checks if a tool is registered + fn has(registry: ToolRegistry, toolID: ToolID) -> bool { + // Implementation: Check if tool exists + } + + // list returns all registered tool IDs + fn list(registry: ToolRegistry) -> [ToolID] { + // Implementation: List all tool IDs + } + + // list_by_category returns tools by category + fn list_by_category(registry: ToolRegistry, category: ToolCategory) -> [ToolID] { + // Implementation: Filter tools by category + } + + // list_allowed returns tools that are allowed + fn list_allowed(registry: ToolRegistry) -> [ToolID] { + // Implementation: Filter allowed tools + } + + // list_dangerous returns tools that are dangerous + fn list_dangerous(registry: ToolRegistry) -> [ToolID] { + // Implementation: Filter dangerous tools + } + + // ==================================================================== + // Permission Operations + // ==================================================================== + + // set_permission sets the permission for a tool + fn set_permission(registry: ToolRegistry, toolID: ToolID, permission: ToolPermission) -> Result { + // Implementation: Set tool permission + } + + // get_permission gets the permission for a tool + fn get_permission(registry: ToolRegistry, toolID: ToolID) -> Result { + // Implementation: Get tool permission + } + + // allow allows a tool + fn allow(registry: ToolRegistry, toolID: ToolID) -> Result { + // Implementation: Set permission to Allowed + } + + // deny denies a tool + fn deny(registry: ToolRegistry, toolID: ToolID) -> Result { + // Implementation: Set permission to Denied + } + + // ask sets a tool to require permission + fn ask(registry: ToolRegistry, toolID: ToolID) -> Result { + // Implementation: Set permission to Ask + } + + // ==================================================================== + // Custom Tool Operations + // ==================================================================== + + // register_custom registers a custom tool + fn register_custom(registry: ToolRegistry, tool: ToolDefinition) -> Result { + // Implementation: Register as custom tool + } + + // list_custom returns all custom tools + fn list_custom(registry: ToolRegistry) -> [ToolID] { + // Implementation: List custom tool IDs + } + + // unregister_custom removes a custom tool + fn unregister_custom(registry: ToolRegistry, toolID: ToolID) -> Result { + // Implementation: Unregister custom tool + } + + // ==================================================================== + // Bulk Operations + // ==================================================================== + + // register_all registers multiple tools at once + fn register_all(registry: ToolRegistry, tools: [ToolDefinition]) -> Result { + // Implementation: Register all tools, return count + } + + // set_all_permissions sets permissions for multiple tools + fn set_all_permissions(registry: ToolRegistry, permissions: [ToolID: ToolPermission]) -> Result { + // Implementation: Set permissions for all tools + } + + // clear removes all tools from registry + fn clear(registry: ToolRegistry) -> void { + // Implementation: Clear all tools + } + + // ==================================================================== + // Query Operations + // ==================================================================== + + // find_by_description finds tools by description pattern + fn find_by_description(registry: ToolRegistry, pattern: str) -> [ToolID] { + // Implementation: Search tools by description + } + + // count returns the number of registered tools + fn count(registry: ToolRegistry) -> u32 { + // Implementation: Count tools + } + + // is_empty checks if the registry is empty + fn is_empty(registry: ToolRegistry) -> bool { + // Implementation: Check if empty + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "tool_id_creation" { + var id = ToolID("bash"); + assert(id.0 == "bash"); + } + + test "call_id_creation" { + var id = CallID("call-abc"); + assert(id.0 == "call-abc"); + } + + test "registry_creation" { + var registry = create(); + assert(is_empty(registry)); + assert(count(registry) == 0); + } + + test "registry_with_tools" { + var registry = create(); + var tools: [ToolID: ToolDefinition] = { + ToolID("bash"): ToolDefinition { + id = ToolID("bash"), + description = "Execute bash command", + parameters = ToolParameters { + properties = {}, + required: [], + }, + metadata = ToolMetadata { + category = ToolCategory::Process, + permission = ToolPermission::Ask, + dangerous = true, + experimental = false, + truncated = null, + outputPath = null, + }, + }, + }; + registry.tools = tools; + assert(!is_empty(registry)); + assert(count(registry) == 1); + } + + test "tool_category_values" { + assert(ToolCategory::File as u32 == 0); + assert(ToolCategory::Search as u32 == 1); + assert(ToolCategory::Process as u32 == 2); + assert(ToolCategory::System as u32 == 3); + assert(ToolCategory::Network as u32 == 4); + assert(ToolCategory::Session as u32 == 5); + assert(ToolCategory::Custom as u32 == 6); + } + + test "tool_permission_values" { + assert(ToolPermission::Allowed as u32 == 0); + assert(ToolPermission::Ask as u32 == 1); + assert(ToolPermission::Denied as u32 == 2); + } + + test "tool_error_values" { + assert(ToolError::NotFound as u32 == 0); + assert(ToolError::PermissionDenied as u32 == 1); + assert(ToolError::InvalidParameters as u32 == 2); + assert(ToolError::ExecutionFailed as u32 == 3); + assert(ToolError::Timeout as u32 == 4); + assert(ToolError::Aborted as u32 == 5); + assert(ToolError::NotDefined as u32 == 6); + } + + test "tool_definition_bash" { + var def = ToolDefinition { + id = ToolID("bash"), + description = "Execute bash command", + parameters = ToolParameters { + properties = {}, + required: [], + }, + metadata = ToolMetadata { + category = ToolCategory::Process, + permission = ToolPermission::Ask, + dangerous = true, + experimental = false, + truncated = null, + outputPath = null, + }, + }; + assert(def.id.0 == "bash"); + assert(def.metadata.dangerous); + assert(def.metadata.category == ToolCategory::Process); + } + + test "tool_definition_read" { + var def = ToolDefinition { + id = ToolID("read"), + description = "Read file contents", + parameters = ToolParameters { + properties = {}, + required: [], + }, + metadata = ToolMetadata { + category = ToolCategory::File, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = null, + outputPath = null, + }, + }; + assert(def.id.0 == "read"); + assert(!def.metadata.dangerous); + assert(def.metadata.category == ToolCategory::File); + } + + test "tool_metadata_allowed" { + var metadata = ToolMetadata { + category = ToolCategory::File, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(is_allowed(metadata)); + assert(!requires_permission(metadata)); + } + + test "tool_metadata_ask" { + var metadata = ToolMetadata { + category = ToolCategory::System, + permission = ToolPermission::Ask, + dangerous = true, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(!is_allowed(metadata)); + assert(requires_permission(metadata)); + assert(is_dangerous(metadata)); + } + + test "tool_metadata_denied" { + var metadata = ToolMetadata { + category = ToolCategory::System, + permission = ToolPermission::Denied, + dangerous = true, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(!is_allowed(metadata)); + assert(!requires_permission(metadata)); + assert(metadata.permission == ToolPermission::Denied); + } + + test "tool_parameters_empty" { + var params = ToolParameters { + properties = {}, + required: [], + }; + assert(params.properties.len == 0); + assert(params.required.len == 0); + } + + test "tool_parameters_with_required" { + var props: [str: ParameterSchema] = { + "file": ParameterSchema { + type = "string", + description = "File path", + required = true, + default = null, + enum = null, + }, + }; + var params = ToolParameters { + properties = props, + required: ["file"], + }; + assert(params.properties["file"].required); + assert(params.required[0] == "file"); + } + + test "validation_error_creation" { + var error = ValidationError { + parameter = "file", + message = "File not found", + code = "NOT_FOUND", + }; + assert(error.parameter == "file"); + assert(error.code == "NOT_FOUND"); + } + + test "validation_result_valid" { + var result = ValidationResult { + valid = true, + errors: [], + }; + assert(result.valid); + } + + test "validation_result_invalid" { + var errors = [ + ValidationError { + parameter = "limit", + message = "Must be positive", + code = "INVALID_VALUE", + }, + ]; + var result = ValidationResult { + valid = false, + errors = errors, + }; + assert(!result.valid); + assert(result.errors.len == 1); + } + + test "tool_call_creation" { + var call = ToolCall { + toolID = ToolID("read"), + callID = CallID("call-xyz"), + parameters = { "file": "test.txt" }, + timestamp = 1234567890, + }; + assert(call.toolID.0 == "read"); + assert(call.callID.0 == "call-xyz"); + } + + test "constants_values" { + assert(MAX_OUTPUT_LENGTH == 100000); + assert(DEFAULT_TIMEOUT_MS == 120000); + } + + test "is_dangerous_true" { + var metadata = ToolMetadata { + category = ToolCategory::System, + permission = ToolPermission::Ask, + dangerous = true, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(is_dangerous(metadata)); + } + + test "is_dangerous_false" { + var metadata = ToolMetadata { + category = ToolCategory::File, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(!is_dangerous(metadata)); + } + + test "requires_permission_true" { + var metadata = ToolMetadata { + category = ToolCategory::System, + permission = ToolPermission::Ask, + dangerous = true, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(requires_permission(metadata)); + } + + test "requires_permission_false" { + var metadata = ToolMetadata { + category = ToolCategory::File, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(!requires_permission(metadata)); + } + + test "is_allowed_true" { + var metadata = ToolMetadata { + category = ToolCategory::File, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(is_allowed(metadata)); + } + + test "is_allowed_false" { + var metadata = ToolMetadata { + category = ToolCategory::System, + permission = ToolPermission::Denied, + dangerous = true, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(!is_allowed(metadata)); + } + + test "tool_metadata_truncated" { + var metadata = ToolMetadata { + category = ToolCategory::Search, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = true, + outputPath = "/tmp/output.txt", + }; + assert(metadata.truncated == true); + assert(metadata.outputPath == "/tmp/output.txt"); + } + + test "tool_metadata_experimental" { + var metadata = ToolMetadata { + category = ToolCategory::Custom, + permission = ToolPermission::Ask, + dangerous = false, + experimental = true, + truncated = null, + outputPath = null, + }; + assert(metadata.experimental); + } + + test "tool_result_creation" { + var metadata = ToolMetadata { + category = ToolCategory::File, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = null, + outputPath = null, + }; + var result = ToolResult { + toolID = ToolID("read"), + callID = CallID("call-1"), + success = true, + title = "File read", + output = "content", + metadata = metadata, + error = null, + truncated = false, + duration = 50, + }; + assert(result.success); + assert(result.duration == 50); + } + + test "tool_context_creation" { + var ctx = ToolContext { + sessionID = SessionSchema::SessionID("sess-1"), + messageID = SessionSchema::MessageID("msg-1"), + callID = CallID("call-1"), + agent = "claude", + abort = false, + extra = null, + }; + assert(ctx.agent == "claude"); + assert(!ctx.abort); + } + + test "parameter_schema_creation" { + var param = ParameterSchema { + type = "string", + description = "Path", + required = true, + default = null, + enum = null, + }; + assert(param.type == "string"); + assert(param.required); + } +} diff --git a/apps/website/public/t27/specs/tools/schema.t27 b/apps/website/public/t27/specs/tools/schema.t27 new file mode 100644 index 0000000000..6bfdbc55d1 --- /dev/null +++ b/apps/website/public/t27/specs/tools/schema.t27 @@ -0,0 +1,522 @@ +// specs/tools/schema.t27 +// Tools Types Specification +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Tools { + use base::types; + use session::schema as SessionSchema; + + // ==================================================================== + // Tool ID Type + // ==================================================================== + + // ToolID is a branded string representing a tool identifier + struct ToolID(str); + + // CallID is a branded string representing a tool call identifier + struct CallID(str); + + // ==================================================================== + // Tool Types + // ==================================================================== + + // ToolCategory represents the category of a tool + enum ToolCategory { + File = 0, // File operations + Search = 1, // Search operations + Process = 2, // Process execution + System = 3, // System operations + Network = 4, // Network operations + Session = 5, // Session operations + Custom = 6, // Custom tools + } + + // ToolPermission represents the permission level for a tool + enum ToolPermission { + Allowed = 0, // Tool can be used freely + Ask = 1, // User must approve each use + Denied = 2, // Tool is disabled + } + + // ==================================================================== + // Tool Definition + // ==================================================================== + + // ParameterSchema represents the schema for tool parameters + struct ParameterSchema { + type: str, // Parameter type (string, number, boolean, etc.) + description: str?, // Parameter description + required: bool, // Whether parameter is required + default: any?, // Default value + enum: [any]?, // Enum values if applicable + } + + // ToolParameters represents the parameters schema for a tool + struct ToolParameters { + properties: [str: ParameterSchema], + required: [str], + } + + // ToolMetadata represents metadata about a tool + struct ToolMetadata { + category: ToolCategory, + permission: ToolPermission, + dangerous: bool, // Whether tool is potentially dangerous + experimental: bool, // Whether tool is experimental + truncated: bool?, // Whether output was truncated + outputPath: str?, // Path to truncated output + } + + // ToolDefinition represents the definition of a tool + struct ToolDefinition { + id: ToolID, + description: str, + parameters: ToolParameters, + metadata: ToolMetadata, + } + + // ==================================================================== + // Tool Execution + // ==================================================================== + + // ToolContext represents the context for tool execution + struct ToolContext { + sessionID: SessionSchema::SessionID, + messageID: SessionSchema::MessageID, + callID: CallID?, + agent: str, // Agent name + abort: bool, // Whether operation was aborted + extra: any?, // Extra context data + } + + // ToolResult represents the result of a tool execution + struct ToolResult { + toolID: ToolID, + callID: CallID, + success: bool, + title: str, // Result title + output: str, // Result output + metadata: ToolMetadata, + error: str?, // Error message if failed + truncated: bool, // Whether output was truncated + duration: u64, // Execution duration in ms + } + + // ToolCall represents a tool call request + struct ToolCall { + toolID: ToolID, + callID: CallID, + parameters: any, // Tool parameters + timestamp: u64, // Call timestamp + } + + // ==================================================================== + // Validation + // ==================================================================== + + // ValidationError represents a parameter validation error + struct ValidationError { + parameter: str, // Parameter name + message: str, // Error message + code: str, // Error code + } + + // ValidationResult represents the result of parameter validation + struct ValidationResult { + valid: bool, + errors: [ValidationError], + } + + // ==================================================================== + // Tool Registry Types + // ==================================================================== + + // ToolRegistry represents a registry of available tools + struct ToolRegistry { + tools: [ToolID: ToolDefinition], + permissions: [ToolID: ToolPermission], + custom: [ToolID], // Custom tool IDs + } + + // ==================================================================== + // Error Types + // ==================================================================== + + // ToolError represents errors in tool operations + enum ToolError { + NotFound = 0, + PermissionDenied = 1, + InvalidParameters = 2, + ExecutionFailed = 3, + Timeout = 4, + Aborted = 5, + NotDefined = 6, + } + + // ==================================================================== + // Constants + // ==================================================================== + + const MAX_OUTPUT_LENGTH: u32 = 100000; + const DEFAULT_TIMEOUT_MS: u32 = 120000; + + // ==================================================================== + // Helper Functions + // ==================================================================== + + // is_dangerous checks if a tool is dangerous + fn is_dangerous(metadata: ToolMetadata) -> bool { + return metadata.dangerous; + } + + // requires_permission checks if a tool requires user permission + fn requires_permission(metadata: ToolMetadata) -> bool { + return metadata.permission == ToolPermission::Ask; + } + + // is_allowed checks if a tool is allowed + fn is_allowed(metadata: ToolMetadata) -> bool { + return metadata.permission == ToolPermission::Allowed; + } + + // ==================================================================== + // Tests + // ==================================================================== + + test "tool_id_creation" { + var id = ToolID("read_file"); + assert(id.0 == "read_file"); + } + + test "call_id_creation" { + var id = CallID("call-123"); + assert(id.0 == "call-123"); + } + + test "tool_category_values" { + assert(ToolCategory::File as u32 == 0); + assert(ToolCategory::Search as u32 == 1); + assert(ToolCategory::Process as u32 == 2); + assert(ToolCategory::System as u32 == 3); + assert(ToolCategory::Network as u32 == 4); + assert(ToolCategory::Session as u32 == 5); + assert(ToolCategory::Custom as u32 == 6); + } + + test "tool_permission_values" { + assert(ToolPermission::Allowed as u32 == 0); + assert(ToolPermission::Ask as u32 == 1); + assert(ToolPermission::Denied as u32 == 2); + } + + test "tool_error_values" { + assert(ToolError::NotFound as u32 == 0); + assert(ToolError::PermissionDenied as u32 == 1); + assert(ToolError::InvalidParameters as u32 == 2); + assert(ToolError::ExecutionFailed as u32 == 3); + assert(ToolError::Timeout as u32 == 4); + assert(ToolError::Aborted as u32 == 5); + assert(ToolError::NotDefined as u32 == 6); + } + + test "parameter_schema_creation" { + var param = ParameterSchema { + type = "string", + description = "File path", + required = true, + default = null, + enum = null, + }; + assert(param.type == "string"); + assert(param.required); + } + + test "parameter_schema_with_default" { + var param = ParameterSchema { + type = "number", + description = "Limit", + required = false, + default = 10, + enum = null, + }; + assert(param.default == 10); + assert(!param.required); + } + + test "parameter_schema_with_enum" { + var param = ParameterSchema { + type = "string", + description = "Mode", + required = true, + default = null, + enum: ["read", "write", "append"], + }; + assert(param.enum?.len == 3); + } + + test "tool_parameters_creation" { + var props: [str: ParameterSchema] = { + "file": ParameterSchema { + type = "string", + description = "File path", + required = true, + default = null, + enum = null, + }, + }; + var params = ToolParameters { + properties = props, + required: ["file"], + }; + assert(params.properties["file"].required); + } + + test "tool_metadata_safe" { + var metadata = ToolMetadata { + category = ToolCategory::File, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(!metadata.dangerous); + assert(metadata.permission == ToolPermission::Allowed); + } + + test "tool_metadata_dangerous" { + var metadata = ToolMetadata { + category = ToolCategory::System, + permission = ToolPermission::Ask, + dangerous = true, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(metadata.dangerous); + assert(metadata.permission == ToolPermission::Ask); + } + + test "tool_definition_creation" { + var props: [str: ParameterSchema] = {}; + var params = ToolParameters { + properties = props, + required: [], + }; + var metadata = ToolMetadata { + category = ToolCategory::File, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = null, + outputPath = null, + }; + var def = ToolDefinition { + id = ToolID("read"), + description = "Read a file", + parameters = params, + metadata = metadata, + }; + assert(def.id.0 == "read"); + assert(def.description == "Read a file"); + } + + test "tool_context_creation" { + var ctx = ToolContext { + sessionID = SessionSchema::SessionID("sess-1"), + messageID = SessionSchema::MessageID("msg-1"), + callID = CallID("call-1"), + agent = "claude", + abort = false, + extra = null, + }; + assert(ctx.sessionID.0 == "sess-1"); + assert(ctx.callID?.0 == "call-1"); + } + + test "tool_result_success" { + var metadata = ToolMetadata { + category = ToolCategory::File, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = null, + outputPath = null, + }; + var result = ToolResult { + toolID = ToolID("read"), + callID = CallID("call-1"), + success = true, + title = "File read", + output = "file content", + metadata = metadata, + error = null, + truncated = false, + duration = 100, + }; + assert(result.success); + assert(!result.truncated); + } + + test "tool_result_failure" { + var metadata = ToolMetadata { + category = ToolCategory::File, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = null, + outputPath = null, + }; + var result = ToolResult { + toolID = ToolID("read"), + callID = CallID("call-1"), + success = false, + title = "Failed", + output = "", + metadata = metadata, + error = "File not found", + truncated = false, + duration = 50, + }; + assert(!result.success); + assert(result.error == "File not found"); + } + + test "validation_result_valid" { + var result = ValidationResult { + valid = true, + errors: [], + }; + assert(result.valid); + assert(result.errors.len == 0); + } + + test "validation_result_invalid" { + var errors = [ + ValidationError { + parameter = "file", + message = "Required", + code = "REQUIRED", + }, + ]; + var result = ValidationResult { + valid = false, + errors = errors, + }; + assert(!result.valid); + assert(result.errors.len == 1); + } + + test "tool_call_creation" { + var call = ToolCall { + toolID = ToolID("read"), + callID = CallID("call-1"), + parameters = { "file": "test.txt" }, + timestamp = 1234567890, + }; + assert(call.toolID.0 == "read"); + assert(call.parameters["file"] == "test.txt"); + } + + test "constants_values" { + assert(MAX_OUTPUT_LENGTH == 100000); + assert(DEFAULT_TIMEOUT_MS == 120000); + } + + test "is_dangerous_true" { + var metadata = ToolMetadata { + category = ToolCategory::System, + permission = ToolPermission::Ask, + dangerous = true, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(is_dangerous(metadata)); + } + + test "is_dangerous_false" { + var metadata = ToolMetadata { + category = ToolCategory::File, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(!is_dangerous(metadata)); + } + + test "requires_permission_true" { + var metadata = ToolMetadata { + category = ToolCategory::System, + permission = ToolPermission::Ask, + dangerous = true, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(requires_permission(metadata)); + } + + test "requires_permission_false" { + var metadata = ToolMetadata { + category = ToolCategory::File, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(!requires_permission(metadata)); + } + + test "is_allowed_true" { + var metadata = ToolMetadata { + category = ToolCategory::File, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(is_allowed(metadata)); + } + + test "is_allowed_false" { + var metadata = ToolMetadata { + category = ToolCategory::System, + permission = ToolPermission::Denied, + dangerous = true, + experimental = false, + truncated = null, + outputPath = null, + }; + assert(!is_allowed(metadata)); + } + + test "tool_metadata_truncated" { + var metadata = ToolMetadata { + category = ToolCategory::Search, + permission = ToolPermission::Allowed, + dangerous = false, + experimental = false, + truncated = true, + outputPath = "/tmp/output.txt", + }; + assert(metadata.truncated == true); + assert(metadata.outputPath == "/tmp/output.txt"); + } + + test "tool_metadata_experimental" { + var metadata = ToolMetadata { + category = ToolCategory::Custom, + permission = ToolPermission::Ask, + dangerous = false, + experimental = true, + truncated = null, + outputPath = null, + }; + assert(metadata.experimental); + } +} diff --git a/apps/website/public/t27/specs/tools/tri_to_t27_converter.t27 b/apps/website/public/t27/specs/tools/tri_to_t27_converter.t27 new file mode 100644 index 0000000000..6f1b9ac68e --- /dev/null +++ b/apps/website/public/t27/specs/tools/tri_to_t27_converter.t27 @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/tools/tri_to_t27_converter.t27 +// Converts .tri (YAML-like) specs to .t27 (Zig-like TDD) format | φ² + 1/φ² = 3 | TRINITY + +module TriToT27Converter; + use base::types; + use io::file; + + // ═══════════════════════════════════════════════════════════ + // 1. Constants + // ═══════════════════════════════════════════════════════════ + + const SPDX_HEADER : []const u8 = "// SPDX-License-Identifier: Apache-2.0\n"; + const TRINITY_FOOTER : []const u8 = "// φ² + 1/φ² = 3 | TRINITY\n"; + const VERSION : u32 = 1_0000; // 1.0.0 + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const TriSpec = struct { + name : []const u8, + version : []const u8, + description : []const u8, + types : []TriType, + constants : []TriConstant, + functions : []TriFunction, + behaviors : []Behavior, + constraints : []Constraint, + }; + + pub const TriType = struct { + name : []const u8, + fields : []TriField, + is_pub : bool, + }; + + pub const TriField = struct { + name : []const u8, + field_type : []const u8, + is_optional : bool, + }; + + pub const TriConstant = struct { + name : []const u8, + value : []const u8, + const_type : []const u8, + }; + + pub const TriFunction = struct { + name : []const u8, + params : []FunctionParam, + return_type : []const u8, + description : []const u8, + }; + + pub const FunctionParam = struct { + name : []const u8, + param_type : []const u8, + }; + + pub const Behavior = struct { + name : []const u8, + description : []const u8, + }; + + pub const Constraint = struct { + name : []const u8, + description : []const u8, + }; + + pub const Route = struct { + source : []const u8, + target : []const u8, + }; + + pub const MigrationStats = struct { + total_files : u32, + converted_files : u32, + failed_files : u32, + skipped_files : u32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // parse_tri_file(content: []const u8) → TriSpec + // Parses a .tri file content and returns the structured specification. + fn parse_tri_file(content: []const u8) -> TriSpec { + // Line-based parsing handling sections: + // - name: module name + // - types: struct definitions with fields + // - constants: constant declarations + // - functions: function signatures + // - behaviors: test case descriptions + // - constraints: invariant requirements + } + + // parse_type_line(line: []const u8) → TriField + // Parses a single type field line (e.g., "is_v6 : bool"). + fn parse_type_line(line: []const u8) -> TriField { + } + + // convert_type(tri_type: []const u8) → []const u8 + // Converts .tri type syntax to .t27 syntax. + // - "[16]u8" remains "[16]u8" + // - "?IpAddress" becomes "?IpAddress" + // - "Option(T)" becomes "?T" + fn convert_type(tri_type: []const u8) -> []const u8 { + } + + // generate_t27(spec: TriSpec) → []const u8 + // Generates complete .t27 file content from TriSpec. + fn generate_t27(spec: TriSpec) -> []const u8 { + // Output structure: + // 1. Header (SPDX + module declaration) + // 2. Use statements + // 3. Constants section + // 4. Types section + // 5. Core Functions section + // 6. TDD Tests section (from behaviors) + // 7. TDD Invariants section (from constraints) + } + + // route_file(source_path: []const u8) → []const u8 + // Determines the target path for a .tri file based on routing table. + fn route_file(source_path: []const u8) -> []const u8 { + // Routing table mapping: + // algo/relu.tri → ml/activation/relu_activation.t27 + // algo/dense.tri → ml/layers/dense_layer.t27 + // algo/lstm.tri → ml/recurrent/lstm_cell.t27 + // algo/multi_head_attn.tri → ml/transformer/multi_head_attention.t27 + // algo/sgd.tri → ml/optimizer/sgd.t27 + // algo/adam.tri → ml/optimizer/adam.t27 + // algo/mse_loss.tri → ml/loss/mse_loss.t27 + // algo/dqn.tri → ml/rl/dqn.t27 + // tri/tri_list.tri → tri/collections/list.t27 + // tri/tri_map.tri → tri/collections/map.t27 + // tri/tri_set.tri → tri/collections/set.t27 + // tri/tri_queue.tri → tri/collections/queue.t27 + // tri/tri_stack.tri → tri/collections/stack.t27 + // tri/tri_avl_tree.tri → tri/trees/avl_tree.t27 + // tri/tri_b_tree.tri → tri/trees/b_tree.t27 + // tri/tri_rb_tree.tri → tri/trees/red_black_tree.t27 + // tri/tri_quick_sort.tri → tri/sort/quick_sort.t27 + // tri/tri_merge_sort.tri → tri/sort/merge_sort.t27 + // tri/tri_sha256.tri → tri/crypto/sha256.t27 + // tri/tri_http.tri → tri/net/http.t27 + // tri/tri_fs.tri → tri/io/fs.t27 + } + + // write_output(path: []const u8, content: []const u8) → void + // Writes converted content to the target file. + fn write_output(path: []const u8, content: []const u8) -> void { + // Creates parent directories if they don't exist + // Writes content with UTF-8 encoding + } + + // run_migration(source_dir: []const u8, target_dir: []const u8) → MigrationStats + // Main entry point for bulk conversion. + fn run_migration(source_dir: []const u8, target_dir: []const u8) -> MigrationStats { + // 1. Scan source directory for .tri files + // 2. For each .tri file: + // a. Read content + // b. Parse using parse_tri_file + // c. Generate .t27 using generate_t27 + // d. Determine target path using route_file + // e. Write using write_output + // 3. Return statistics + } + + // ═══════════════════════════════════════════════════════════ + // 4. Utility Functions + // ═══════════════════════════════════════════════════════════ + + // snake_to_pascal_case(name: []const u8) → []const u8 + // Converts snake_case to PascalCase for module names. + fn snake_to_pascal_case(name: []const u8) -> []const u8 { + // "tri_list" → "TriList" + // "dense_layer" → "DenseLayer" + } + + // sanitize_description(desc: []const u8) → []const u8 + // Removes or escapes characters that could break .t27 syntax. + fn sanitize_description(desc: []const u8) -> []const u8 { + } + + // is_valid_t27_syntax(content: []const u8) → bool + // Validates that generated content follows .t27 syntax rules. + fn is_valid_t27_syntax(content: []const u8) -> bool { + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests + // ═══════════════════════════════════════════════════════════ + + test parse_tri_file_parses_name + given content = "name: test_algo\nversion: \"1.0\"" + when spec = parse_tri_file(content) + then spec.name == "test_algo" + + test parse_tri_file_parses_version + given content = "name: test\nversion: \"1.2.3\"" + when spec = parse_tri_file(content) + then spec.version == "1.2.3" + + test parse_tri_file_parses_types + given content = "name: test\ntypes:\n - name: MyType\n fields:\n - name: field1\n type: u32" + when spec = parse_tri_file(content) + then spec.types.len == 1 + and spec.types[0].name == "MyType" + + test parse_tri_file_parses_constants + given content = "name: test\nconstants:\n ZERO: 0.0" + when spec = parse_tri_file(content) + then spec.constants.len == 1 + and spec.constants[0].name == "ZERO" + + test parse_tri_file_parses_functions + given content = "name: test\nfunctions:\n - name: forward\n params: [input: []f32]\n return: []f32" + when spec = parse_tri_file(content) + then spec.functions.len == 1 + and spec.functions[0].name == "forward" + + test convert_type_preserves_array_syntax + given input = "[16]u8" + when result = convert_type(input) + then result == "[16]u8" + + test convert_type_preserves_optional_syntax + given input = "?IpAddress" + when result = convert_type(input) + then result == "?IpAddress" + + test convert_type_converts_option_syntax + given input = "Option(T)" + when result = convert_type(input) + then result == "?T" + + test snake_to_pascal_case_basic + given input = "tri_list" + when result = snake_to_pascal_case(input) + then result == "TriList" + + test snake_to_pascal_case_multi_word + given input = "dense_layer" + when result = snake_to_pascal_case(input) + then result == "DenseLayer" + + test generate_t27_creates_valid_structure + given spec = TriSpec{ .name = "TestModule", .version = "1.0.0", .description = "Test module", .types = [], .constants = [], .functions = [], .behaviors = [], .constraints = [] } + when content = generate_t27(spec) + then contains_module_declaration(content) + and contains_spdx_header(content) + and contains_trinity_footer(content) + + test route_file_maps_algo_relu + given input = "algo/relu.tri" + when result = route_file(input) + then result == "ml/activation/relu_activation.t27" + + test route_file maps_algo_dense + given input = "algo/dense.tri" + when result = route_file(input) + then result == "ml/layers/dense_layer.t27" + + test route_file_maps_tri_list + given input = "tri/tri_list.tri" + when result = route_file(input) + then result == "tri/collections/list.t27" + + test route_file_maps_tri_avl_tree + given input = "tri/tri_avl_tree.tri" + when result = route_file(input) + then result == "tri/trees/avl_tree.t27" + + test route_file_maps_tri_sha256 + given input = "tri/tri_sha256.tri" + when result = route_file(input) + then result == "tri/crypto/sha256.t27" + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants + // ═══════════════════════════════════════════════════════════ + + invariant converter_output_is_ascii + given content = any_tri_spec() + when t27 = generate_t27(content) + then all_bytes_are_ascii(t27) + + invariant converter_output_has_module_decl + given content = valid_tri_spec() + when t27 = generate_t27(content) + then contains_module_declaration(t27) + + invariant converter_output_has_spdx_header + given content = valid_tri_spec() + when t27 = generate_t27(content) + then contains_spdx_header(t27) + + invariant converter_preserves_all_functions + given content = tri_with_n_functions(10) + when t27 = generate_t27(content) + then function_count(t27) == 10 + + invariant converter_preserves_all_types + given content = tri_with_n_types(5) + when t27 = generate_t27(content) + then type_count(t27) == 5 + + invariant converter_includes_tests_from_behaviors + given content = tri_with_behaviors(3) + when t27 = generate_t27(content) + then test_count(t27) == 3 + + invariant converter_includes_invariants_from_constraints + given content = tri_with_constraints(2) + when t27 = generate_t27(content) + then invariant_count(t27) == 2 + + invariant converter_output_no_unsafe_without_comment + given content = safe_tri_spec() + when t27 = generate_t27(content) + then contains_no_unsafe(t27) + or unsafe_has_safety_comment(t27) + + invariant route_output_is_valid_path + given input = any_valid_tri_filename() + when result = route_file(input) + then path_components_valid(result) + and extension_is_t27(result) + + invariant migration_stats_are_consistent + given stats = run_migration("/source", "/target") + then stats.total_files == stats.converted_files + stats.failed_files + stats.skipped_files + + // ═══════════════════════════════════════════════════════════ + // TDD: Benchmarks + // ═══════════════════════════════════════════════════════════ + + bench parse_tri_file_small + given input = small_tri_spec() // ~10 lines + when result = parse_tri_file(input) + then elapsed_time_ms < 1 + + bench parse_tri_file_medium + given input = medium_tri_spec() // ~100 lines + when result = parse_tri_file(input) + then elapsed_time_ms < 5 + + bench parse_tri_file_large + given input = large_tri_spec() // ~500 lines + when result = parse_tri_file(input) + then elapsed_time_ms < 25 + + bench generate_t27_small + given input = small_tri_spec() + when result = generate_t27(parse_tri_file(input)) + then elapsed_time_ms < 1 + + bench generate_t27_medium + given input = medium_tri_spec() + when result = generate_t27(parse_tri_file(input)) + then elapsed_time_ms < 5 + + bench convert_type + given input = "[16]u8" + when result = convert_type(input) + then elapsed_time_ns < 100 + + bench route_file + given input = "algo/relu.tri" + when result = route_file(input) + then elapsed_time_ns < 100 + + // ═══════════════════════════════════════════════════════════ + // Implementation Notes + // ═══════════════════════════════════════════════════════════ + // + // Bootstrap Implementation: + // - This specification defines the contract for the converter + // - Bootstrap implementation is in tools/converter/ (Rust) + // - The Rust tool MUST conform to this .t27 specification + // + // Parsing Strategy: + // - Use line-by-line parsing (not pure YAML parser) + // - Handle indentation-sensitive field declarations + // - Support both YAML-style (- name:) and direct (name:) syntax + // + // Type Mapping (.tri → .t27): + // - "[N]type" → "[N]type" (arrays) + // - "?Type" → "?Type" (optionals) + // - "Option(T)" → "?T" (optionals) + // - "[]" → "[]const" (slices) + // + // Section Mapping (.tri → .t27): + // - name: xxx → module Xxx; + // - types: → pub const Xxx = struct { ... }; + // - constants: → const XXX: type = value; + // - functions: → fn xxx(params) -> ret { ... } + // - behaviors: → test xxx_... (TDD) + // - constraints: → invariant xxx_... (TDD) + // + // ═══════════════════════════════════════════════════════════ diff --git a/apps/website/public/t27/specs/tri/agent/agent_run.t27 b/apps/website/public/t27/specs/tri/agent/agent_run.t27 new file mode 100644 index 0000000000..60c8ba6305 --- /dev/null +++ b/apps/website/public/t27/specs/tri/agent/agent_run.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | φ² + 1/φ² = 3 | TRINITY + +module AgentRun; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "agent_run_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/agent/agents.t27 b/apps/website/public/t27/specs/tri/agent/agents.t27 new file mode 100644 index 0000000000..b5de193bc7 --- /dev/null +++ b/apps/website/public/t27/specs/tri/agent/agents.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | φ² + 1/φ² = 3 | TRINITY + +module agents; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "agents_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/agent/autonomous_lifecycle.t27 b/apps/website/public/t27/specs/tri/agent/autonomous_lifecycle.t27 new file mode 100644 index 0000000000..1a2fb82898 --- /dev/null +++ b/apps/website/public/t27/specs/tri/agent/autonomous_lifecycle.t27 @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Computes sacred bond between two agents | φ² + 1/φ² = 3 | TRINITY + +module TriAutonomousLifecycle; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const LifecycleState = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/autonomous_lifecycle.tri:27. The converter dropped bare `- name` bullets. + enum : [ideation, specification, generation, validation, deployment, monitoring, healing, stable], + }; + + pub const LifecycleEvent = struct { + event_id : String, + from_state : LifecycleState, + to_state : LifecycleState, + timestamp : i64, + pas_score : Float, // φ-weighted priority + trigger : String, + }; + + pub const AutonomousAgent = struct { + agent_id : String, + state : LifecycleState, + current_task : Option, + task_history : List, + performance_metrics : PerformanceMetrics, + sacred_rating : Float, + }; + + pub const Task = struct { + task_id : String, + task_type : TaskType, + spec_file : Option, + priority : Float, + dependencies : List, + status : TaskStatus, + }; + + pub const TaskType = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/autonomous_lifecycle.tri:70. The converter dropped bare `- name` bullets. + enum : [create_spec, generate_code, run_tests, deploy, monitor, heal], + }; + + pub const TaskStatus = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/autonomous_lifecycle.tri:79. The converter dropped bare `- name` bullets. + enum : [pending, in_progress, completed, failed, retrying], + }; + + pub const PerformanceMetrics = struct { + tasks_completed : Int, + tasks_failed : Int, + avg_task_time_ms : Float, + pas_score_avg : Float, + uptime_percentage : Float, + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "autonomous_lifecycle_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/agent/autonomous_universe.t27 b/apps/website/public/t27/specs/tri/agent/autonomous_universe.t27 new file mode 100644 index 0000000000..db375e34f3 --- /dev/null +++ b/apps/website/public/t27/specs/tri/agent/autonomous_universe.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module AutonomousUniverse; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "autonomous_universe_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/agent/eternal_monitor.t27 b/apps/website/public/t27/specs/tri/agent/eternal_monitor.t27 new file mode 100644 index 0000000000..94f09c020a --- /dev/null +++ b/apps/website/public/t27/specs/tri/agent/eternal_monitor.t27 @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Component health state | φ² + 1/φ² = 3 | TRINITY + +module EternalMonitor; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Severity = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/eternal_monitor.tri:16. The converter dropped bare `- name` bullets. + enum : [info, warning, critical, fatal], + }; + + pub const HealthStatus = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/eternal_monitor.tri:24. The converter dropped bare `- name` bullets. + enum : [healthy, degraded, failed, unknown], + }; + + pub const Config = struct { + interval_ms : u64, + max_alerts : usize, + auto_heal : bool, + log_file : ?[]const u8, + }; + + pub const Alert = struct { + timestamp : i64, + component : []const u8, + severity : Severity, + message : []const u8, + resolved : bool, + }; + + pub const Metrics = struct { + uptime_s : u64, + check_count : u64, + alert_count : u64, + heal_attempts : u64, + heal_successes : u64, + }; + + pub const SystemComponent = struct { + status : HealthStatus, + last_check : i64, + last_alert : "?i64", + consecutive_failures : u32, + }; + + pub const EternalMonitor = struct { + allocator : "std.mem.Allocator", + config : Config, + components : "ArrayList(SystemComponent)", + alerts : "ArrayList(Alert)", + metrics : Metrics, + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "eternal_monitor_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/agent/experience_hooks.t27 b/apps/website/public/t27/specs/tri/agent/experience_hooks.t27 new file mode 100644 index 0000000000..5a4e027cf3 --- /dev/null +++ b/apps/website/public/t27/specs/tri/agent/experience_hooks.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | φ² + 1/φ² = 3 | TRINITY + +module experience_hooks; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "experience_hooks_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/agent/faculty_board.t27 b/apps/website/public/t27/specs/tri/agent/faculty_board.t27 new file mode 100644 index 0000000000..5a0262ab93 --- /dev/null +++ b/apps/website/public/t27/specs/tri/agent/faculty_board.t27 @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Current agent state | φ² + 1/φ² = 3 | TRINITY + +module FacultyBoard; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Lang = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/faculty_board.tri:20. The converter dropped bare `- name` bullets. + enum : [ru, en], + }; + + pub const FacultySnapshot = struct { + faculty_count : u32, + active_agents : u32, + compile_rate : f64, + dirty_files : u32, + build_broken : bool, + timestamp : i64, + agents : []AgentStatus, + }; + + pub const AgentStatus = struct { + agent : "Agent", + status : "AgentStatusKind", + wake_count : u32, + last_seen : i64, + }; + + pub const Agent = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/faculty_board.tri:44. The converter dropped bare `- name` bullets. + enum : [ralph, scholar, mu, oracle, swarm, linter], + }; + + pub const AgentStatusKind = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/faculty_board.tri:54. The converter dropped bare `- name` bullets. + enum : [up, down, frozen, missing], + }; + + pub const FacultyDelta = struct { + compile_delta : f64, + dirty_delta : i32, + active_delta : i32, + faculty_delta : i32, + prev_timestamp : i64, + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "faculty_board_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/agent/governance_agent.t27 b/apps/website/public/t27/specs/tri/agent/governance_agent.t27 new file mode 100644 index 0000000000..805860e9d2 --- /dev/null +++ b/apps/website/public/t27/specs/tri/agent/governance_agent.t27 @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// String | φ² + 1/φ² = 3 | TRINITY + +module GovernanceAgent; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const GovernanceAgent = struct { + identity : String, // "GOVERNANCEAGENT of Sacred Intelligence" + sacred_score : Float, // 0-1 scale + generation : Int, + total_violations : Int, + total_enforcements : Int, + last_check_timestamp : Int64, + }; + + pub const SacredRule = struct { + weight : Float, // φ-based weight + penalty_multiplier : Float, + enabled : Bool, + }; + + pub const Violation = struct { + rule : String, // Which sacred rule was violated + file_path : String, + line_number : Int, + severity : String, // critical, warning, info + penalty : Float, // φ-based penalty score + timestamp : Int64, + commit_hash : String, + auto_rollback : Bool, + resolved : Bool, + }; + + pub const SacredScore = struct { + phi_harmony : Float, // Cosine similarity to φ + trinity_balance : Float, // Ternary balance (-1, 0, +1) + gematria_compliance : Float, // Sacred name usage + evolution_fitness : Float, // Fitness improvement + test_safety : Float, // Tests passing + overall_score : Float, // Weighted average 0-1 + timestamp : Int64, + }; + + pub const PatchRequest = struct { + patch_id : String, + author : String, + files : List, + pre_score : Float, + post_score : Float, + delta : Float, + status : String, // pending, approved, rejected, rolledBack + approver : String, + timestamp : Int64, + }; + + pub const PreCommitState = struct { + enabled : Bool, + block_on_violation : Bool, + auto_rollback_threshold : Float, + allowed_overrides : List, + last_check_result : String, + }; + + pub const GovernanceWidget = struct { + current_score : Float, + trend : String, // improving, stable, declining + violations_today : Int, + enforcements_today : Int, + pending_patches : Int, + last_update : Int64, + }; + + pub const AuditEntry = struct { + id : String, + timestamp : Int64, + action : String, // check, enforce, rollback, approve + rule : String, + outcome : String, // passed, failed, warning + details : String, + sacred_score_before : Float, + sacred_score_after : Float, + agent_identity : String, + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "governance_agent_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/agent/handoff.t27 b/apps/website/public/t27/specs/tri/agent/handoff.t27 new file mode 100644 index 0000000000..54bd1c0bb2 --- /dev/null +++ b/apps/website/public/t27/specs/tri/agent/handoff.t27 @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module Handoff; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const PlannerOutput = struct { + issue_number : UInt32, + subtasks : []const []const u8, + files : []const []const u8, + approach : String, + spec_path : String, + timestamp : Int64, + cost_tokens_in : UInt64, + cost_tokens_out : UInt64, + cost_usd : Float64, + }; + + pub const CoderOutput = struct { + issue_number : UInt32, + branch : String, + files_modified : []const []const u8, + commits : []const []const u8, + lines_added : UInt32, + lines_removed : UInt32, + timestamp : Int64, + cost_tokens_in : UInt64, + cost_tokens_out : UInt64, + cost_usd : Float64, + }; + + pub const ReviewerVerdict = struct { + issue_number : UInt32, + approved : Bool, + feedback : []const []const u8, + iteration : UInt8, + max_iterations : UInt8, + files_reviewed : []const []const u8, + timestamp : Int64, + cost_tokens_in : UInt64, + cost_tokens_out : UInt64, + cost_usd : Float64, + }; + + pub const TesterReport = struct { + issue_number : UInt32, + tests_passed : UInt32, + tests_total : UInt32, + benchmarks : []const []const u8, + regressions : []const []const u8, + timestamp : Int64, + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "handoff_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/agent/memory.t27 b/apps/website/public/t27/specs/tri/agent/memory.t27 new file mode 100644 index 0000000000..a60e331dbb --- /dev/null +++ b/apps/website/public/t27/specs/tri/agent/memory.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | φ² + 1/φ² = 3 | TRINITY + +module memory; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "memory_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/agent/swarm_agents.t27 b/apps/website/public/t27/specs/tri/agent/swarm_agents.t27 new file mode 100644 index 0000000000..be45fba4c3 --- /dev/null +++ b/apps/website/public/t27/specs/tri/agent/swarm_agents.t27 @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// String | φ² + 1/φ² = 3 | TRINITY + +module SwarmAgents; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const AgentType = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/swarm_agents.tri:31. The converter dropped bare `- name` bullets. + enum : [architect, codex, evolver, oracle, guardian, herald], + }; + + pub const AgentStatus = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/swarm_agents.tri:40. The converter dropped bare `- name` bullets. + enum : [idle, active, thinking, waiting_consensus, blocked], + }; + + pub const Agent = struct { + id : String, + agent_type : AgentType, + status : AgentStatus, + sacred_role : String, + phi_score : Float, + task_queue : List, + completed_tasks : List, + sacred_declaration : String, + }; + + pub const Task = struct { + task_id : String, + priority : Float, // φ-weighted 0-1 + assigned_to : Option, + status : TaskStatus, + result : Option, + sacred_formula : Option, + }; + + pub const TaskStatus = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/swarm_agents.tri:69. The converter dropped bare `- name` bullets. + enum : [pending, assigned, in_progress, completed, failed, awaiting_consensus], + }; + + pub const SwarmState = struct { + agents : List, + coordination_mode : CoordinationMode, + phi_harmony_score : Float, // 0-1, target ≥0.95 + consensus_threshold : Float, // Default 0.95 + active_tasks : List, + completed_tasks : List, + iteration_count : Int, + sacred_bond : Float, + }; + + pub const CoordinationMode = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/swarm_agents.tri:89. The converter dropped bare `- name` bullets. + enum : [parallel, consensus, hierarchical, sacred_circle], + }; + + pub const ConsensusProposal = struct { + proposal_id : String, + agent_type : AgentType, + proposal_text : String, + phi_weight : Float, + votes : List, + consensus_score : Float, + status : ConsensusStatus, + }; + + pub const Vote = struct { + agent_id : String, + agent_type : AgentType, + approve : Bool, + phi_influence : Float, + rationale : String, + }; + + pub const ConsensusStatus = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/swarm_agents.tri:114. The converter dropped bare `- name` bullets. + enum : [pending, approved, rejected, tie], + }; + + pub const SwarmHarmonyMetrics = struct { + cosine_similarity : Float, // VSA cosine similarity + consensus_rate : Float, // Approved / Total + task_success_rate : Float, // Completed / Total + phi_alignment : Float, // How aligned with sacred math + overall_harmony : Float, // Combined score 0-1 + }; + + pub const AgentCommunication = struct { + from_agent_id : String, + to_agent_id : String, + message_type : MessageType, + content : String, + phi_signature : Float, + timestamp : Int, + }; + + pub const MessageType = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/swarm_agents.tri:138. The converter dropped bare `- name` bullets. + enum : [task_request, task_result, consensus_request, consensus_vote, broadcast, alert], + }; + + pub const SacredPattern = struct { + pattern_id : String, + discovered_by : AgentType, + phi_ratio : Float, + trinity_aligned : Bool, + formula_string : String, + gematria_value : Int, + confidence : Float, + }; + + pub const TaskResult = struct { + agent_id : String, + task_id : String, + result : String, + phi_score : Float, + }; + + pub const SacredFormula = struct { + module : Math.sacredFormula, + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "swarm_agents_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/collections/array.t27 b/apps/website/public/t27/specs/tri/collections/array.t27 new file mode 100644 index 0000000000..48b177ea86 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/array.t27 @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// ArrayView provides zero-copy access | φ² + 1/φ² = 3 | TRINITY + +module TriArray; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const ArrayView(T) = struct { + ptr : [*]T, + len : usize, + }; + + pub const SliceRange = struct { + start : usize, + end : usize, + step : i64, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // slice(arr: []const T, start: usize, end: usize) → []const T + fn slice(arr: []const T, start: usize, end: usize) -> []const T { + // TODO: Implement from .tri spec + } + + // slice_from(arr: []const T, start: usize) → []const T + fn slice_from(arr: []const T, start: usize) -> []const T { + // TODO: Implement from .tri spec + } + + // first(arr: []const T) → T + fn first(arr: []const T) -> T { + // TODO: Implement from .tri spec + } + + // last(arr: []const T) → T + fn last(arr: []const T) -> T { + // TODO: Implement from .tri spec + } + + // is_empty(arr: []const T) → bool + fn is_empty(arr: []const T) -> bool { + // TODO: Implement from .tri spec + } + + // contains(arr: []const T, item: T) → bool + fn contains(arr: []const T, item: T) -> bool { + // TODO: Implement from .tri spec + } + + // index_of(arr: []const T, item: T) → ?usize + fn index_of(arr: []const T, item: T) -> ?usize { + // TODO: Implement from .tri spec + } + + // reverse(allocator: std.mem.Allocator, arr: []const T) → []T + fn reverse(allocator: std.mem.Allocator, arr: []const T) -> []T { + // TODO: Implement from .tri spec + } + + // concat(allocator: std.mem.Allocator, a: []const T, b: []const T) → []T + fn concat(allocator: std.mem.Allocator, a: []const T, b: []const T) -> []T { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test array_view_reads_straight_through_to_the_backing_storage + // Verify: the view is zero-copy — it holds a pointer, not a duplicate, so + // reads through it land on the original array's bytes + given data = [_]u8{ 10, 20, 30 } + when view = ArrayView(u8){ .ptr = @constCast(&data), .len = data.len } + and first_element = view.ptr[0] + and last_element = view.ptr[view.len - 1] + then view.len == 3 and first_element == 10 and last_element == 30 + + test an_empty_array_view_has_zero_length + // Verify: the boundary case — a view of nothing reports len 0 + given data = [_]u8{} + when view = ArrayView(u8){ .ptr = @constCast(&data), .len = 0 } + then view.len == 0 + + test array_view_is_one_type_per_element_type + // Verify: ArrayView is generic over T, so two instantiations at the same + // element type are the same type and different element types are not + given same = ArrayView(u8) == ArrayView(u8) + and different = ArrayView(u8) == ArrayView(u16) + then same and different == false + + test slice_range_start_is_inclusive_and_end_is_exclusive + // Verify: the field pair matches Zig's own slice syntax, so a range of + // 1..4 over five elements selects three of them starting at index 1 + given data = [_]u8{ 0, 1, 2, 3, 4 } + and range = SliceRange{ .start = 1, .end = 4, .step = 1 } + when window = data[range.start..range.end] + and window_matches = std.mem.eql(u8, window, &[_]u8{ 1, 2, 3 }) + then window.len == 3 and window_matches + + test an_empty_slice_range_has_equal_bounds + // Verify: start == end selects nothing, which is how is_empty distinguishes + // an empty view from a one-element one + given data = [_]u8{ 0, 1, 2 } + and range = SliceRange{ .start = 2, .end = 2, .step = 1 } + when window = data[range.start..range.end] + then window.len == 0 + + test slice_range_step_is_signed_so_it_can_run_backwards + // Verify: step is i64, not usize — a negative step is representable, which + // is what reverse traversal needs + given forward = SliceRange{ .start = 0, .end = 3, .step = 1 } + and backward = SliceRange{ .start = 0, .end = 3, .step = -1 } + then forward.step == 1 and backward.step == -1 and backward.step < 0 + diff --git a/apps/website/public/t27/specs/tri/collections/bitmap.t27 b/apps/website/public/t27/specs/tri/collections/bitmap.t27 new file mode 100644 index 0000000000..bec7298736 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/bitmap.t27 @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Uses usize words for efficiency | φ² + 1/φ² = 3 | TRINITY + +module TriBitmap; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Bitmap = struct { + bits : []usize, + capacity : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(capacity: usize, allocator: std.mem.Allocator) → !Bitmap + fn init(capacity: usize, allocator: std.mem.Allocator) -> !Bitmap { + // TODO: Implement from .tri spec + } + + // get(bitmap: Bitmap, index: usize) → bool + fn get(bitmap: Bitmap, index: usize) -> bool { + // TODO: Implement from .tri spec + } + + // set(bitmap: *Bitmap, index: usize) → void + fn set(bitmap: *Bitmap, index: usize) -> void { + // TODO: Implement from .tri spec + } + + // clear(bitmap: *Bitmap, index: usize) → void + fn clear(bitmap: *Bitmap, index: usize) -> void { + // TODO: Implement from .tri spec + } + + // flip(bitmap: *Bitmap, index: usize) → void + fn flip(bitmap: *Bitmap, index: usize) -> void { + // TODO: Implement from .tri spec + } + + // set_all(bitmap: *Bitmap) → void + fn set_all(bitmap: *Bitmap) -> void { + // TODO: Implement from .tri spec + } + + // clear_all(bitmap: *Bitmap) → void + fn clear_all(bitmap: *Bitmap) -> void { + // TODO: Implement from .tri spec + } + + // count(bitmap: Bitmap) → usize + fn count(bitmap: Bitmap) -> usize { + // TODO: Implement from .tri spec + } + + // find_first(bitmap: Bitmap) → ?usize + fn find_first(bitmap: Bitmap) -> ?usize { + // TODO: Implement from .tri spec + } + + // find_last(bitmap: Bitmap) → ?usize + fn find_last(bitmap: Bitmap) -> ?usize { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above is an unimplemented stub: each one emits + // `@panic("not yet implemented")`, so calling any of them aborts the test + // binary rather than failing a test. The assertions below are therefore + // about the representation the module actually declares. + + test bits_are_packed_into_usize_words + // "Uses usize words for efficiency" -- the backing store is machine + // words, not bytes and not bools. + then @FieldType(Bitmap, "bits") == []usize + and @bitSizeOf(usize) >= 32 + + test capacity_is_a_bit_count_not_a_word_count + // capacity counts bits, so a bitmap needs ceil(capacity / bits-per-word) + // words -- 1024 bits is 16 words on a 64-bit target, not 1024. + given words_for_1024 = (1024 + @bitSizeOf(usize) - 1) / @bitSizeOf(usize) + then @FieldType(Bitmap, "capacity") == usize + and words_for_1024 * @bitSizeOf(usize) >= 1024 + and words_for_1024 < 1024 + + test bitmap_is_a_slice_plus_a_length + // No allocator and no word count are stored: the slice carries its own + // length, and capacity is the only other state. + then @typeInfo(Bitmap).@"struct".fields.len == 2 + and @hasField(Bitmap, "allocator") == false + diff --git a/apps/website/public/t27/specs/tri/collections/bitset.t27 b/apps/website/public/t27/specs/tri/collections/bitset.t27 new file mode 100644 index 0000000000..6bfbf7badf --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/bitset.t27 @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Free bitset | φ² + 1/φ² = 3 | TRINITY + +module TriBitset; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Bitset = struct { + data : []usize, + size : usize, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(allocator: std.mem.Allocator, bit_count: usize) → Bitset + fn init(allocator: std.mem.Allocator, bit_count: usize) -> Bitset { + // TODO: Implement from .tri spec + } + + // set(bs: *Bitset, index: usize) → void + fn set(bs: *Bitset, index: usize) -> void { + // TODO: Implement from .tri spec + } + + // clear(bs: *Bitset, index: usize) → void + fn clear(bs: *Bitset, index: usize) -> void { + // TODO: Implement from .tri spec + } + + // test(bs: *Bitset, index: usize) → bool + fn test(bs: *Bitset, index: usize) -> bool { + // TODO: Implement from .tri spec + } + + // union(a: *Bitset, b: *Bitset, allocator: std.mem.Allocator) → Bitset + fn union(a: *Bitset, b: *Bitset, allocator: std.mem.Allocator) -> Bitset { + // TODO: Implement from .tri spec + } + + // intersect(a: *Bitset, b: *Bitset, allocator: std.mem.Allocator) → Bitset + fn intersect(a: *Bitset, b: *Bitset, allocator: std.mem.Allocator) -> Bitset { + // TODO: Implement from .tri spec + } + + // deinit(bs: *Bitset) → void + fn deinit(bs: *Bitset) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test init_basic_case + given input = default_input() + when result = init(input) + then result != undefined + + test set_basic_case + given input = default_input() + when result = set(input) + then result != undefined + + test clear_basic_case + given input = default_input() + when result = clear(input) + then result != undefined + + test test_basic_case + given input = default_input() + when result = test(input) + then result != undefined + + test union_basic_case + given input = default_input() + when result = union(input) + then result != undefined + + test intersect_basic_case + given input = default_input() + when result = intersect(input) + then result != undefined + + test deinit_basic_case + given input = default_input() + when result = deinit(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/collections/bitvector.t27 b/apps/website/public/t27/specs/tri/collections/bitvector.t27 new file mode 100644 index 0000000000..a4e0c6df2f --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/bitvector.t27 @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Grows as needed | φ² + 1/φ² = 3 | TRINITY + +module TriBitvector; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const BitVector = struct { + bits : []usize, + length : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // empty() → BitVector + fn empty() -> BitVector { + // TODO: Implement from .tri spec + } + + // with_capacity(bits: usize, allocator: std.mem.Allocator) → !BitVector + fn with_capacity(bits: usize, allocator: std.mem.Allocator) -> !BitVector { + // TODO: Implement from .tri spec + } + + // push(bv: *BitVector, bit: bool, allocator: std.mem.Allocator) → !void + fn push(bv: *BitVector, bit: bool, allocator: std.mem.Allocator) -> !void { + // TODO: Implement from .tri spec + } + + // pop(bv: *BitVector) → ?bool + fn pop(bv: *BitVector) -> ?bool { + // TODO: Implement from .tri spec + } + + // get(bv: BitVector, index: usize) → bool + fn get(bv: BitVector, index: usize) -> bool { + // TODO: Implement from .tri spec + } + + // set(bv: *BitVector, index: usize, value: bool) → void + fn set(bv: *BitVector, index: usize, value: bool) -> void { + // TODO: Implement from .tri spec + } + + // len(bv: BitVector) → usize + fn len(bv: BitVector) -> usize { + // TODO: Implement from .tri spec + } + + // append(bv: *BitVector, other: BitVector, allocator: std.mem.Allocator) → !void + fn append(bv: *BitVector, other: BitVector, allocator: std.mem.Allocator) -> !void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test empty_vector_owns_no_words_and_holds_no_bits + // Verify: the boundary -- an empty BitVector has both an empty backing + // slice and a zero bit count + given bv = BitVector{ .bits = &[_]usize{}, .length = 0 } + then bv.bits.len == 0 and bv.length == 0 + + test word_capacity_covers_the_bit_length + // Verify: `bits` is measured in machine words and `length` in bits, so + // the backing slice must span at least `length` bits + given words = [_]usize{ 0, 0 } + and bv = BitVector{ .bits = @constCast(&words), .length = 65 } + when capacity = bv.bits.len * @bitSizeOf(usize) + then capacity >= bv.length and bv.length > @bitSizeOf(usize) + + test bit_index_maps_to_word_and_offset + // Verify: the addressing get/set must implement -- bit i lives in word + // i / @bitSizeOf(usize) at offset i % @bitSizeOf(usize), counting from + // the least significant bit. Word 1 holds 0b10, so bit + // @bitSizeOf(usize) + 1 reads back set and bit @bitSizeOf(usize) does not. + given words = [_]usize{ 0, 2 } + and bv = BitVector{ .bits = @constCast(&words), .length = @bitSizeOf(usize) + 2 } + and idx = @bitSizeOf(usize) + 1 + when word = bv.bits[idx / @bitSizeOf(usize)] + and bit = (word >> (idx % @bitSizeOf(usize))) & 1 + and neighbour = word & 1 + then bit == 1 and neighbour == 0 + diff --git a/apps/website/public/t27/specs/tri/collections/btree.t27 b/apps/website/public/t27/specs/tri/collections/btree.t27 new file mode 100644 index 0000000000..c0c62b17a2 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/btree.t27 @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Self-balancing tree | φ² + 1/φ² = 3 | TRINITY + +module TriBtree; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const BTree(K, V) = struct { + root : BTreeNode(K, V), + order : usize, + }; + + pub const BTreeNode(K, V) = struct { + keys : []K, + values : []V, + children : []BTreeNode(K, V), + leaf : bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(order: usize, allocator: std.mem.Allocator) → !BTree(T) + fn init(order: usize, allocator: std.mem.Allocator) -> !BTree(T) { + // TODO: Implement from .tri spec + } + + // insert(tree: *BTree(T), key: K, value: V, allocator: std.mem.Allocator) → !void + fn insert(tree: *BTree(T), key: K, value: V, allocator: std.mem.Allocator) -> !void { + // TODO: Implement from .tri spec + } + + // search(tree: BTree(T), key: K) → ?V + fn search(tree: BTree(T), key: K) -> ?V { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // init, insert and search are all unimplemented stubs: each one emits + // `@panic("not yet implemented")`, so calling any of them aborts the test + // binary rather than failing a test. (Their signatures are also truncated + // to the first parameter, and they spell the type `BTree(T)` with one + // argument where the declaration takes two.) The assertions below are + // about the representation the module actually declares. + + test the_tree_is_generic_over_a_key_and_a_value_type + // Both BTree and BTreeNode are comptime functions of two types: this + // is a key/value map, not a set of bare keys. + then @typeInfo(@TypeOf(BTree)).@"fn".params.len == 2 + and @typeInfo(@TypeOf(BTreeNode)).@"fn".params.len == 2 + and @TypeOf(BTree) != type + + test keys_and_values_are_parallel_slices_in_a_node + // Slot i of `keys` pairs with slot i of `values`, so a lookup finds + // the index once and reads the payload from the same position. + then @typeInfo(BTreeNode(u32, f32)).@"struct".fields.len == 4 + and @FieldType(BTreeNode(u32, f32), "keys") == []u32 + and @FieldType(BTreeNode(u32, f32), "values") == []f32 + and @FieldType(BTreeNode(u32, f32), "leaf") == bool + + test children_are_a_slice_of_the_node_type_itself + // The recursion goes through a slice, which is a pointer plus a length + // -- that is what keeps the node type finite in size. + given children = @FieldType(BTreeNode(u32, f32), "children") + then @typeInfo(children).pointer.child == BTreeNode(u32, f32) + and @typeInfo(children).pointer.size == .slice + + test the_root_is_a_node_by_value_so_an_empty_tree_still_has_one + // root is not optional and not a pointer: a BTree always contains a + // node, and `order` is carried at runtime rather than as a comptime + // parameter, so init(order) sets it on an already-shaped value. + then @FieldType(BTree(u32, f32), "root") == BTreeNode(u32, f32) + and @FieldType(BTree(u32, f32), "order") == usize + and @sizeOf(BTree(u32, f32)) >= @sizeOf(BTreeNode(u32, f32)) + diff --git a/apps/website/public/t27/specs/tri/collections/circular_buffer.t27 b/apps/website/public/t27/specs/tri/collections/circular_buffer.t27 new file mode 100644 index 0000000000..6b1cc1b17c --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/circular_buffer.t27 @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Free buffer | φ² + 1/φ² = 3 | TRINITY + +module TriCircularBuffer; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const CircularBuffer = struct { + data : []i64, + head : usize, + tail : usize, + capacity : usize, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(allocator: std.mem.Allocator, capacity: usize) → CircularBuffer + fn init(allocator: std.mem.Allocator, capacity: usize) -> CircularBuffer { + // TODO: Implement from .tri spec + } + + // write(buf: *CircularBuffer, value: i64) → !void + fn write(buf: *CircularBuffer, value: i64) -> !void { + // TODO: Implement from .tri spec + } + + // read(buf: *CircularBuffer) → i64 + fn read(buf: *CircularBuffer) -> i64 { + // TODO: Implement from .tri spec + } + + // is_empty(buf: *CircularBuffer) → bool + fn is_empty(buf: *CircularBuffer) -> bool { + // TODO: Implement from .tri spec + } + + // deinit(buf: *CircularBuffer) → void + fn deinit(buf: *CircularBuffer) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above has no body yet, so it panics when called; and + // write(buf) / read(buf) carry no value in or out, so no write/read round + // trip can be stated until those signatures grow one. What can be stated + // is the shape of the CircularBuffer value itself. + + test circular_buffer_empty_state_has_head_meeting_tail + given empty = CircularBuffer{.data=&[_]i64{},.head=0,.tail=0,.capacity=0,.allocator=std.testing.allocator} + then empty.head == empty.tail + and empty.capacity == 0 + and empty.data.len == 0 + + test circular_buffer_capacity_matches_its_backing_array + given buf = CircularBuffer{.data=@constCast(&[_]i64{7,8,9,10}),.head=3,.tail=1,.capacity=4,.allocator=std.testing.allocator} + then buf.capacity == buf.data.len + and buf.head != buf.tail + and buf.data[buf.tail] == 8 + + test circular_buffer_indices_stay_inside_the_capacity + // head and tail are ring positions, so both are < capacity in either + // order -- head above tail is just as legal as head below it + given wrapped = CircularBuffer{.data=@constCast(&[_]i64{1,2,3,4}),.head=3,.tail=1,.capacity=4,.allocator=std.testing.allocator} + then wrapped.head > wrapped.tail + and wrapped.head < wrapped.capacity + and wrapped.tail < wrapped.capacity + diff --git a/apps/website/public/t27/specs/tri/collections/context.t27 b/apps/website/public/t27/specs/tri/collections/context.t27 new file mode 100644 index 0000000000..8cb0e30c5c --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/context.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module TriContext; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "context_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/collections/deque.t27 b/apps/website/public/t27/specs/tri/collections/deque.t27 new file mode 100644 index 0000000000..ba500e5f5b --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/deque.t27 @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Free deque | φ² + 1/φ² = 3 | TRINITY + +module TriDeque; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Deque = struct { + data : []i64, + front : usize, + back : usize, + size : usize, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(allocator: std.mem.Allocator) → Deque + fn init(allocator: std.mem.Allocator) -> Deque { + // TODO: Implement from .tri spec + } + + // push_front(deque: *Deque, value: i64) → !void + fn push_front(deque: *Deque, value: i64) -> !void { + // TODO: Implement from .tri spec + } + + // push_back(deque: *Deque, value: i64) → !void + fn push_back(deque: *Deque, value: i64) -> !void { + // TODO: Implement from .tri spec + } + + // pop_front(deque: *Deque) → i64 + fn pop_front(deque: *Deque) -> i64 { + // TODO: Implement from .tri spec + } + + // pop_back(deque: *Deque) → i64 + fn pop_back(deque: *Deque) -> i64 { + // TODO: Implement from .tri spec + } + + // deinit(deque: *Deque) → void + fn deinit(deque: *Deque) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above has no body yet, so it panics when called; and + // push_front(deque) / pop_front(deque) take no element and return none, + // so no push/pop round trip can be stated until those signatures grow an + // element. What can be stated is the shape of the Deque value itself. + + test deque_empty_state_is_self_consistent + given empty = Deque{.data=&[_]i64{},.front=0,.back=0,.size=0,.allocator=std.testing.allocator} + then empty.size == 0 + and empty.front == empty.back + and empty.data.len == 0 + + test deque_holds_its_elements_and_its_two_ends_apart + given d = Deque{.data=@constCast(&[_]i64{10,20,30,40}),.front=1,.back=3,.size=3,.allocator=std.testing.allocator} + then d.data.len == 4 + and d.data[d.front] == 20 + and d.data[d.back] == 40 + and d.size <= d.data.len + diff --git a/apps/website/public/t27/specs/tri/collections/either.t27 b/apps/website/public/t27/specs/tri/collections/either.t27 new file mode 100644 index 0000000000..3509ef595d --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/either.t27 @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Either represents tagged union | φ² + 1/φ² = 3 | TRINITY + +module TriEither; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Either(L, R) = struct { + is_left : bool, + left : L, + right : R, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // left(value: L) → Either(L, R) + fn left(value: L) -> Either(L, R) { + // TODO: Implement from .tri spec + } + + // right(value: R) → Either(L, R) + fn right(value: R) -> Either(L, R) { + // TODO: Implement from .tri spec + } + + // is_left(either: Either(L, R)) → bool + fn is_left(either: Either(L, R)) -> bool { + // TODO: Implement from .tri spec + } + + // is_right(either: Either(L, R)) → bool + fn is_right(either: Either(L, R)) -> bool { + // TODO: Implement from .tri spec + } + + // unwrap(either: Either(L, R), "default_left: L, "default_right: R) → ?T + fn unwrap(either: Either(L, R), "default_left: L, "default_right: R) -> ?T { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // The five constructors and accessors have empty bodies, so no value can + // be wrapped or unwrapped yet. What the module has decided is how the two + // arms are laid out. + + test the_tag_is_a_plain_bool + given e = Either(i64, f64) + and tag_type = @FieldType(e, "is_left") + then tag_type == bool + + test either_stores_both_arms_side_by_side + // this is a struct, not a tagged union: the L and R payloads are both + // resident and only is_left says which one is meaningful, so the type + // costs at least the sum of the two arms rather than the larger one + given total_size = @sizeOf(Either(i64, f64)) + and both_arms = @sizeOf(i64) + @sizeOf(f64) + then total_size >= both_arms + + test the_two_arms_may_have_different_types + given e = Either(i64, f64) + and left_type = @FieldType(e, "left") + and right_type = @FieldType(e, "right") + then left_type == i64 and right_type == f64 + diff --git a/apps/website/public/t27/specs/tri/collections/interval.t27 b/apps/website/public/t27/specs/tri/collections/interval.t27 new file mode 100644 index 0000000000..e358caaee9 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/interval.t27 @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Range operations | φ² + 1/φ² = 3 | TRINITY + +module TriInterval; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Interval = struct { + start : i64, + end : i64, + inclusive : bool, + }; + + pub const IntervalSet = struct { + intervals : []Interval, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // create(start: i64, end: i64) → Interval + fn create(start: i64, end: i64) -> Interval { + // TODO: Implement from .tri spec + } + + // overlaps(a: Interval, b: Interval) → bool + fn overlaps(a: Interval, b: Interval) -> bool { + // TODO: Implement from .tri spec + } + + // union(a: IntervalSet, b: IntervalSet, allocator: std.mem.Allocator) → !IntervalSet + fn union(a: IntervalSet, b: IntervalSet, allocator: std.mem.Allocator) -> !IntervalSet { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // create/overlaps/union are unimplemented stubs returning void, so no range + // algebra can be exercised yet. What the module does declare -- the signed + // endpoints, the inclusive flag, and a set of intervals -- is tested here. + + test inclusive_flag_distinguishes_closed_from_half_open + // Verify: the same endpoint pair denotes [0,10] or [0,10) depending + // only on this flag + given closed = Interval{.start=0,.end=10,.inclusive=true} + and half_open = Interval{.start=0,.end=10,.inclusive=false} + then closed.start == half_open.start and closed.end == half_open.end and closed.inclusive != half_open.inclusive + + test interval_width_is_the_endpoint_difference + // Verify: [3,11) spans 8 units + given iv = Interval{.start=3,.end=11,.inclusive=false} + and width = iv.end - iv.start + then width == 8 + + test degenerate_interval_is_a_single_point + // Verify: start == end, the boundary case, is representable and holds + // exactly one point when closed + given point = Interval{.start=5,.end=5,.inclusive=true} + then point.start == point.end and point.inclusive + + test endpoints_are_signed_so_an_interval_may_straddle_zero + // Verify: i64 endpoints, not usize + given straddling = Interval{.start=-4,.end=4,.inclusive=true} + and width = straddling.end - straddling.start + then straddling.start < 0 and width == 8 + + test empty_interval_set_holds_no_intervals + // Verify: the empty set is the identity for union, and is representable + given empty = IntervalSet{.intervals=@constCast(&[_]Interval{})} + then empty.intervals.len == 0 + + test interval_set_preserves_order_and_arity + // Verify: IntervalSet is a plain slice, so two disjoint ranges stay two + // ranges in the order given + given set = IntervalSet{.intervals=@constCast(&[_]Interval{Interval{.start=0,.end=2,.inclusive=false}, Interval{.start=5,.end=9,.inclusive=false}})} + then set.intervals.len == 2 and set.intervals[0].end < set.intervals[1].start + diff --git a/apps/website/public/t27/specs/tri/collections/linked_list.t27 b/apps/website/public/t27/specs/tri/collections/linked_list.t27 new file mode 100644 index 0000000000..24ff663026 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/linked_list.t27 @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Free all nodes | φ² + 1/φ² = 3 | TRINITY + +module TriLinkedList; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const ListNode = struct { + value : T, + prev : ?ListNode, + next : ?ListNode, + }; + + pub const LinkedList = struct { + head : ?ListNode, + tail : ?ListNode, + length : usize, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init() → LinkedList + fn init() -> LinkedList { + // TODO: Implement from .tri spec + } + + // append(list: *LinkedList, value: i64) → !void + fn append(list: *LinkedList, value: i64) -> !void { + // TODO: Implement from .tri spec + } + + // prepend(list: *LinkedList, value: i64) → !void + fn prepend(list: *LinkedList, value: i64) -> !void { + // TODO: Implement from .tri spec + } + + // remove(list: *LinkedList, value: i64) → bool + fn remove(list: *LinkedList, value: i64) -> bool { + // TODO: Implement from .tri spec + } + + // deinit(list: *LinkedList) → void + fn deinit(list: *LinkedList) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test init_basic_case + given input = default_input() + when result = init(input) + then result != undefined + + test append_basic_case + given input = default_input() + when result = append(input) + then result != undefined + + test prepend_basic_case + given input = default_input() + when result = prepend(input) + then result != undefined + + test remove_basic_case + given input = default_input() + when result = remove(input) + then result != undefined + + test deinit_basic_case + given input = default_input() + when result = deinit(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/collections/list.t27 b/apps/website/public/t27/specs/tri/collections/list.t27 new file mode 100644 index 0000000000..e0d061869b --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/list.t27 @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Lists are immutable — operations return new lists | φ² + 1/φ² = 3 | TRINITY + +module TriList; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const List(T) = struct { + is_empty : bool, + head : T, + tail : "?*List(T)", + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // empty() → List(void) + fn empty() -> List(void) { + // TODO: Implement from .tri spec + } + + // cons(head: T) → void + fn cons(head: T) -> void { + // TODO: Implement from .tri spec + } + + // head(list: List(T)) → void + fn head(list: List(T)) -> void { + // TODO: Implement from .tri spec + } + + // tail(list: List(T)) → void + fn tail(list: List(T)) -> void { + // TODO: Implement from .tri spec + } + + // map(list: List(T)) → void + fn map(list: List(T)) -> void { + // TODO: Implement from .tri spec + } + + // filter(list: List(T)) → void + fn filter(list: List(T)) -> void { + // TODO: Implement from .tri spec + } + + // fold(list: List(T)) → void + fn fold(list: List(T)) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test empty_basic_case + given input = default_input() + when result = empty(input) + then result != undefined + + test cons_basic_case + given input = default_input() + when result = cons(input) + then result != undefined + + test head_basic_case + given input = default_input() + when result = head(input) + then result != undefined + + test tail_basic_case + given input = default_input() + when result = tail(input) + then result != undefined + + test map_basic_case + given input = default_input() + when result = map(input) + then result != undefined + + test filter_basic_case + given input = default_input() + when result = filter(input) + then result != undefined + + test fold_basic_case + given input = default_input() + when result = fold(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/collections/lockfree_stack.t27 b/apps/website/public/t27/specs/tri/collections/lockfree_stack.t27 new file mode 100644 index 0000000000..3d799ade9b --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/lockfree_stack.t27 @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Pop value (CAS-based) | φ² + 1/φ² = 3 | TRINITY + +module TriLockfreeStack; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const LFNode = struct { + value : i64, + next : ?*LFNode, + }; + + pub const LockFreeStack = struct { + head : ?*LFNode, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init() → LockFreeStack + fn init() -> LockFreeStack { + // TODO: Implement from .tri spec + } + + // push(s: *LockFreeStack, value: i64, allocator: std.mem.Allocator) → !void + fn push(s: *LockFreeStack, value: i64, allocator: std.mem.Allocator) -> !void { + // TODO: Implement from .tri spec + } + + // pop(s: *LockFreeStack, allocator: std.mem.Allocator) → i64 + fn pop(s: *LockFreeStack, allocator: std.mem.Allocator) -> i64 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // init, push and pop are still `TODO: Implement from .tri spec` and compile + // to `@panic`, so none of them is called here -- and there is no CAS to + // exercise until they exist. The linked shape is real: an empty stack and + // the last node of a chain are both `null` head/next, which is what makes + // the CAS loop's terminating case a plain null check. + + // The empty stack: head is null, and that is the only representation of + // empty -- there is no sentinel node. + test empty_stack_head_is_null + given s = LockFreeStack{ .head = null } + then s.head == null + + // A node holds an i64 payload; the tail of the chain is null-terminated, + // not self-referential. + test tail_node_next_is_null + given n = LFNode{ .value = 42, .next = null } + then n.value == 42 + and n.next == null + + // The payload is signed: a negative value is a value, not an error code. + test node_value_is_signed + given n = LFNode{ .value = -1, .next = null } + then n.value == -1 + and n.value < 0 + diff --git a/apps/website/public/t27/specs/tri/collections/lru.t27 b/apps/website/public/t27/specs/tri/collections/lru.t27 new file mode 100644 index 0000000000..1adac580dc --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/lru.t27 @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Evicts least recently used | φ² + 1/φ² = 3 | TRINITY + +module TriLru; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const LRU(K, V) = struct { + capacity : usize, + entries : std.HashMap(K, V), + access_list : []K, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(capacity: usize, allocator: std.mem.Allocator) → !LRU(K, V) + fn init(capacity: usize, allocator: std.mem.Allocator) -> !LRU(K, V) { + // TODO: Implement from .tri spec + } + + // get(cache: *LRU(K, V), key: K, allocator: std.mem.Allocator) → ?V + fn get(cache: *LRU(K, V), key: K, allocator: std.mem.Allocator) -> ?V { + // TODO: Implement from .tri spec + } + + // put(cache: *LRU(K, V), key: K, value: V, allocator: std.mem.Allocator) → !void + fn put(cache: *LRU(K, V), key: K, value: V, allocator: std.mem.Allocator) -> !void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test init_basic_case + given input = default_input() + when result = init(input) + then result != undefined + + test get_basic_case + given input = default_input() + when result = get(input) + then result != undefined + + test put_basic_case + given input = default_input() + when result = put(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/collections/lru_cache.t27 b/apps/website/public/t27/specs/tri/collections/lru_cache.t27 new file mode 100644 index 0000000000..ac6c0c65a6 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/lru_cache.t27 @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// When at capacity, remove least recently used item | φ² + 1/φ² = 3 | TRINITY + +module TriLruCache; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const LRUCache = struct { + generic : K, V, + capacity : usize, + size : usize, + head : *Node, + tail : *Node, + map : HashMap(K, *Node), + }; + + pub const Node = struct { + generic : K, V, + key : K, + value : V, + prev : *Node, + next : *Node, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(capacity: usize) → LRUCache + fn init(capacity: usize) -> LRUCache { + // TODO: Implement from .tri spec + } + + // get(cache: *LRUCache, key: K) → ?V + fn get(cache: *LRUCache, key: K) -> ?V { + // TODO: Implement from .tri spec + } + + // put(cache: *LRUCache, key: K, value: V) → void + fn put(cache: *LRUCache, key: K, value: V) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test init_basic_case + given input = default_input() + when result = init(input) + then result != undefined + + test get_basic_case + given input = default_input() + when result = get(input) + then result != undefined + + test put_basic_case + given input = default_input() + when result = put(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/collections/map.t27 b/apps/website/public/t27/specs/tri/collections/map.t27 new file mode 100644 index 0000000000..b15b49cfe7 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/map.t27 @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Operations return new maps | φ² + 1/φ² = 3 | TRINITY + +module TriMap; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Map(K, V) = struct { + keys : []K, + values : []V, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // empty() → Map(K, V) + fn empty() -> Map(K, V) { + // TODO: Implement from .tri spec + } + + // singleton(key: K, value: V) → Map(K, V) + fn singleton(key: K, value: V) -> Map(K, V) { + // TODO: Implement from .tri spec + } + + // get(map: Map(K, V), key: K) → Option(V) + fn get(map: Map(K, V), key: K) -> Option(V) { + // TODO: Implement from .tri spec + } + + // set(map: Map(K, V), key: K, value: V) → Map(K, V) + fn set(map: Map(K, V), key: K, value: V) -> Map(K, V) { + // TODO: Implement from .tri spec + } + + // keys(map: Map(K, V)) → []K + fn keys(map: Map(K, V)) -> []K { + // TODO: Implement from .tri spec + } + + // values(map: Map(K, V)) → []V + fn values(map: Map(K, V)) -> []V { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // All six operations have empty bodies, so no lookup or insertion can be + // exercised here. The representation is the part the module has decided, + // and these pin it. + + test map_keeps_keys_and_values_in_two_parallel_slices + given m = Map(u8, i64) + and keys_type = @FieldType(m, "keys") + and values_type = @FieldType(m, "values") + then keys_type == []u8 and values_type == []i64 + + test map_stores_no_shared_length + // the struct is exactly two slices, so nothing in the type forces + // keys.len == values.len -- callers carry that invariant themselves + given total_size = @sizeOf(Map(u8, i64)) + and slice_size = @sizeOf([]u8) + then total_size == 2 * slice_size + diff --git a/apps/website/public/t27/specs/tri/collections/maybe.t27 b/apps/website/public/t27/specs/tri/collections/maybe.t27 new file mode 100644 index 0000000000..5ffcb8b389 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/maybe.t27 @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Satisfies monad laws | φ² + 1/φ² = 3 | TRINITY + +module TriMaybe; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Maybe(T) = struct { + computed : bool, + value : T, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // pure(value: T) → Maybe(T) + fn pure(value: T) -> Maybe(T) { + // TODO: Implement from .tri spec + } + + // bind(maybe: Maybe(T), fn: fn(T) -> Maybe(U)) → Maybe(U) + fn bind(maybe: Maybe(T), fn: fn(T) -> Maybe(U)) -> Maybe(U) { + // TODO: Implement from .tri spec + } + + // map(maybe: Maybe(T), fn: fn(T) -> U) → Maybe(U) + fn map(maybe: Maybe(T), fn: fn(T) -> U) -> Maybe(U) { + // TODO: Implement from .tri spec + } + + // join(nested: Maybe(Maybe(T))) → Maybe(T) + fn join(nested: Maybe(Maybe(T))) -> Maybe(T) { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // pure, bind, map and join are all unimplemented stubs: each one emits + // `@panic("not yet implemented")`, so calling any of them aborts the test + // binary rather than failing a test. The monad laws cannot be exercised + // until they have bodies; what is checkable is the shape they operate on. + + test maybe_is_a_flag_beside_a_value_not_a_tagged_union + // Two fields: the payload is always materialised, the flag says + // whether it means anything. A Maybe over nothing is just the flag. + then @typeInfo(Maybe(u32)).@"struct".fields.len == 2 + and @FieldType(Maybe(u32), "computed") == bool + and @FieldType(Maybe(u32), "value") == u32 + and @sizeOf(Maybe(void)) == 1 + + test maybe_does_not_get_the_null_pointer_optimization + // Zig folds the absent case of ?*u32 into the null pointer itself; + // Maybe keeps a separate flag, so it is strictly the wider encoding. + then @sizeOf(?*u32) == @sizeOf(*u32) + and @sizeOf(Maybe(*u32)) > @sizeOf(*u32) + + test join_flattens_two_layers_of_the_same_shape + // join takes Maybe(Maybe(T)): the outer value slot is itself a Maybe, + // so flattening is combining two `computed` flags into one. + given nested = Maybe(Maybe(u32)){ .computed = true, .value = Maybe(u32){ .computed = true, .value = 7 } } + then @FieldType(Maybe(Maybe(u32)), "value") == Maybe(u32) + and nested.computed == true + and nested.value.computed == true + and nested.value.value == 7 + + test an_absent_maybe_still_carries_a_value_slot + // computed = false does not erase the payload -- the field is always + // there, which is why bind must consult the flag before the value. + given absent = Maybe(u32){ .computed = false, .value = 0 } + then absent.computed == false + and @sizeOf(Maybe(u32)) >= @sizeOf(u32) + diff --git a/apps/website/public/t27/specs/tri/collections/namespace.t27 b/apps/website/public/t27/specs/tri/collections/namespace.t27 new file mode 100644 index 0000000000..fc859b14c5 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/namespace.t27 @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | φ² + 1/φ² = 3 | TRINITY + +module TriNamespace; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Namespace = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/tri_namespace.tri:7. The converter dropped bare + // `- name` bullets, leaving `kind : Enum, variants : ,` with no names. + enum : [core, dev, forge, agent, mcp, system], + }; + + // A TAGGED UNION, and its payloads were not lost either. Recovered from + // trinity-fpga specs/tri/tri_namespace.tri:17-25, where `variants:` carries + // a nested map per case rather than bare names -- the converter dropped the + // nesting and left `namespaced : ,` `flat : ,` with no types at all. + // + // An enum rule must NOT fire here: `namespaced` and `flat` carry data, and + // folding them to bare tags would delete it. + pub const ParsedCommandNamespaced = struct { + namespace : Namespace, + // an empty string means "list the namespace" + command : []const u8, + }; + + pub const ParsedCommandFlat = struct { + command : []const u8, + }; + + pub const ParsedCommand = union(enum) { + namespaced : ParsedCommandNamespaced, + flat : ParsedCommandFlat, + help : void, + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "namespace_smoke_test" { + expect(true) + } + + test namespace_lists_the_six_command_groups + // Recovered from trinity-fpga specs/tri/tri_namespace.tri:7. The + // upstream spec's own behaviors say Namespace.toString returns the + // lowercase variant name, so the spelling below is load-bearing. + then @typeInfo(Namespace).@"enum".fields.len == 6 + and std.mem.eql(u8, @typeInfo(Namespace).@"enum".fields[0].name, "core") + and std.mem.eql(u8, @typeInfo(Namespace).@"enum".fields[5].name, "system") diff --git a/apps/website/public/t27/specs/tri/collections/option.t27 b/apps/website/public/t27/specs/tri/collections/option.t27 new file mode 100644 index 0000000000..a9e00333f6 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/option.t27 @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Eliminates null pointer issues | φ² + 1/φ² = 3 | TRINITY + +module TriOption; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Option(T) = struct { + is_some : bool, + value : T, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // some(value: T) → Option(T) + fn some(value: T) -> Option(T) { + // TODO: Implement from .tri spec + } + + // none() → Option(void) + fn none() -> Option(void) { + // TODO: Implement from .tri spec + } + + // unwrap_or(opt: Option(T), default: T) → T + fn unwrap_or(opt: Option(T), default: T) -> T { + // TODO: Implement from .tri spec + } + + // is_some(opt: Option(T)) → bool + fn is_some(opt: Option(T)) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // some, none, unwrap_or and is_some are still `TODO: Implement from .tri + // spec` and compile to `@panic`, so none of them is called here. The + // testable content today is the Option(T) shape itself: the discriminant + // and the payload are separate fields, which is the whole point of the + // type -- a T can be absent without a null pointer standing in for it. + + // The present case carries both the flag and the payload. + test option_some_shape + given o = Option(i64){ .is_some = true, .value = 7 } + then o.is_some == true + and o.value == 7 + + // The absent case is the flag alone; the payload slot still exists and is + // a normal value of T, never a null pointer. + test option_none_shape + given o = Option(i64){ .is_some = false, .value = 0 } + then o.is_some == false + and o.value == 0 + + // The discriminant is independent of the payload: the same value of T + // appears in both cases, and only the flag tells them apart. + test option_flag_alone_distinguishes_the_cases + given present = Option(i64){ .is_some = true, .value = 0 } + and absent = Option(i64){ .is_some = false, .value = 0 } + then present.value == absent.value + and present.is_some != absent.is_some + + // Option is generic over T, not fixed to one payload type. + test option_is_generic_over_the_payload + given flag = Option(bool){ .is_some = true, .value = false } + then flag.is_some == true + and flag.value == false + diff --git a/apps/website/public/t27/specs/tri/collections/priority_queue.t27 b/apps/website/public/t27/specs/tri/collections/priority_queue.t27 new file mode 100644 index 0000000000..0cc17f9342 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/priority_queue.t27 @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Free queue | φ² + 1/φ² = 3 | TRINITY + +module TriPriorityQueue; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const PriorityQueue = struct { + data : []i64, + size : usize, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(allocator: std.mem.Allocator) → PriorityQueue + fn init(allocator: std.mem.Allocator) -> PriorityQueue { + // TODO: Implement from .tri spec + } + + // enqueue(pq: *PriorityQueue, value: i64) → !void + fn enqueue(pq: *PriorityQueue, value: i64) -> !void { + // TODO: Implement from .tri spec + } + + // dequeue(pq: *PriorityQueue) → i64 + fn dequeue(pq: *PriorityQueue) -> i64 { + // TODO: Implement from .tri spec + } + + // peek(pq: *PriorityQueue) → i64 + fn peek(pq: *PriorityQueue) -> i64 { + // TODO: Implement from .tri spec + } + + // is_empty(pq: *PriorityQueue) → bool + fn is_empty(pq: *PriorityQueue) -> bool { + // TODO: Implement from .tri spec + } + + // deinit(pq: *PriorityQueue) → void + fn deinit(pq: *PriorityQueue) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above is an unimplemented stub emitting + // `@panic("not yet implemented")`, so no heap ordering can be exercised. + // What the module states is the layout, and that is what is asserted. + + test priorities_are_signed + // []i64, not []u64: a negative priority is representable, so callers + // may order by cost as well as by rank. + then @FieldType(PriorityQueue, "data") == []i64 + + test size_is_tracked_separately_from_the_buffer + // data.len is the allocated capacity; size is the number of live + // elements. Two distinct numbers, hence the separate field. + then @hasField(PriorityQueue, "size") + and @FieldType(PriorityQueue, "size") == usize + + test the_queue_owns_its_allocator + // init takes an allocator and the struct stores it, which is why + // deinit exists at all. + then @FieldType(PriorityQueue, "allocator") == std.mem.Allocator + and @typeInfo(PriorityQueue).@"struct".fields.len == 3 + diff --git a/apps/website/public/t27/specs/tri/collections/queue.t27 b/apps/website/public/t27/specs/tri/collections/queue.t27 new file mode 100644 index 0000000000..4cc38af867 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/queue.t27 @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// First-in-first-out ordering | φ² + 1/φ² = 3 | TRINITY + +module TriQueue; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Queue(T) = struct { + front : []T, + back : []T, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // empty() → Queue(T) + fn empty() -> Queue(T) { + // TODO: Implement from .tri spec + } + + // enqueue(queue: Queue(T)) → void + fn enqueue(queue: Queue(T)) -> void { + // TODO: Implement from .tri spec + } + + // dequeue(queue: Queue(T)) → void + fn dequeue(queue: Queue(T)) -> void { + // TODO: Implement from .tri spec + } + + // peek(queue: Queue(T)) → void + fn peek(queue: Queue(T)) -> void { + // TODO: Implement from .tri spec + } + + // is_empty(queue: Queue(T)) → void + fn is_empty(queue: Queue(T)) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // empty/enqueue/dequeue/peek/is_empty are unimplemented stubs -- each emits + // `@panic("not yet implemented")`, so no FIFO behaviour can be exercised. + // Queue(T) itself is a real type constructor, and that is testable. + + test queue_substitutes_its_element_type + // The parameter reaches both fields: Queue(i64) stores i64, not T. + then @FieldType(Queue(i64), "front") == []i64 + and @FieldType(Queue(i64), "back") == []i64 + + test distinct_element_types_give_distinct_queues + // A generic that ignored T would collapse these to one type. + then Queue(i64) != Queue(u8) + and Queue(i64) == Queue(i64) + + test queue_is_the_two_stack_shape + // front and back only: the amortised FIFO built from two LIFOs. + then @typeInfo(Queue(u8)).@"struct".fields.len == 2 + and @hasField(Queue(u8), "front") and @hasField(Queue(u8), "back") + diff --git a/apps/website/public/t27/specs/tri/collections/result.t27 b/apps/website/public/t27/specs/tri/collections/result.t27 new file mode 100644 index 0000000000..bdc720bacd --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/result.t27 @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Forces checking error case | φ² + 1/φ² = 3 | TRINITY + +module TriResult; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Result(T, E) = struct { + is_ok : bool, + value : T, + error : E, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // ok(value: T) → Result(T, E) + fn ok(value: T) -> Result(T, E) { + // TODO: Implement from .tri spec + } + + // err(error: E) → Result(T, E) + fn err(error: E) -> Result(T, E) { + // TODO: Implement from .tri spec + } + + // unwrap_or(result: Result(T, E), default: T) → T + fn unwrap_or(result: Result(T, E), default: T) -> T { + // TODO: Implement from .tri spec + } + + // is_error(result: Result(T, E)) → bool + fn is_error(result: Result(T, E)) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // ok/err/unwrap_or/is_error are unimplemented stubs returning void, so no + // construct-then-unwrap round trip can be exercised yet. What the module + // does declare -- a struct generic over both the value and the error type -- + // is tested here. + + test ok_shaped_result_carries_its_value + // Verify: is_ok is the discriminant and value holds the payload + given r = Result(i32, u8){.is_ok=true,.value=7,.@"error"=0} + then r.is_ok and r.value == 7 + + test err_shaped_result_carries_its_error + // Verify: the error side is reachable under the same field names + given r = Result(i32, u8){.is_ok=false,.value=0,.@"error"=42} + then r.is_ok == false and r.@"error" == 42 + + test both_payloads_are_always_present + // Verify: this Result is a product, not a sum -- value and error are + // both storage-resident regardless of is_ok, so is_ok is the only thing + // that "forces checking the error case" + given r = Result(i32, u8){.is_ok=true,.value=7,.@"error"=42} + then r.value == 7 and r.@"error" == 42 + + test result_is_generic_over_both_parameters + // Verify: T and E are independent, so instantiations differing in + // either one are distinct types + then (Result(i32, u8) != Result(bool, u8)) and (Result(i32, u8) != Result(i32, i64)) + + test error_field_name_survives_being_a_zig_keyword + // Verify: the declared field is named "error", which the emitter must + // escape rather than rename + then (@hasField(Result(i32, u8), "error")) and (@hasField(Result(i32, u8), "is_ok")) + diff --git a/apps/website/public/t27/specs/tri/collections/ring_buffer.t27 b/apps/website/public/t27/specs/tri/collections/ring_buffer.t27 new file mode 100644 index 0000000000..6a09385fa9 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/ring_buffer.t27 @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Wraps around when full | φ² + 1/φ² = 3 | TRINITY + +module TriRing; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Ring(T) = struct { + buffer : []T, + head : usize, + tail : usize, + capacity : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // new(capacity: usize) → Ring(T) + fn new(capacity: usize) -> Ring(T) { + // TODO: Implement from .tri spec + } + + // push(ring: Ring(T), value: T) → bool + fn push(ring: Ring(T), value: T) -> bool { + // TODO: Implement from .tri spec + } + + // pop(ring: Ring(T)) → Option(T) + fn pop(ring: Ring(T)) -> Option(T) { + // TODO: Implement from .tri spec + } + + // is_empty(ring: Ring(T)) → bool + fn is_empty(ring: Ring(T)) -> bool { + // TODO: Implement from .tri spec + } + + // is_full(ring: Ring(T)) → bool + fn is_full(ring: Ring(T)) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test a_fresh_ring_has_head_and_tail_at_the_same_slot + // Verify: the empty case — head == tail is the emptiness test, and the + // backing storage is exactly capacity slots wide + given storage = [_]u8{ 0, 0, 0, 0 } + when ring = Ring(u8){ .buffer = @constCast(&storage), .head = 0, .tail = 0, .capacity = 4 } + then ring.head == ring.tail and ring.buffer.len == ring.capacity + + test the_slot_after_the_last_one_is_the_first_one + // Verify: the wrap rule — indices advance modulo capacity, so a tail + // sitting on the final slot moves to slot 0, not past the end + given storage = [_]u8{ 0, 0, 0, 0 } + and ring = Ring(u8){ .buffer = @constCast(&storage), .head = 0, .tail = 3, .capacity = 4 } + when wrapped = (ring.tail + 1) % ring.capacity + then wrapped == 0 and wrapped < ring.buffer.len + + test occupancy_is_tail_minus_head_modulo_capacity + // Verify: the count survives a wrap — head 3, tail 1 in a 4-slot ring + // holds two items (slots 3 and 0), not a negative number + given storage = [_]u8{ 0, 0, 0, 0 } + and ring = Ring(u8){ .buffer = @constCast(&storage), .head = 3, .tail = 1, .capacity = 4 } + when used = (ring.tail + ring.capacity - ring.head) % ring.capacity + then used == 2 and used < ring.capacity + + test occupancy_of_an_unwrapped_ring_is_the_plain_difference + // Verify: head 1, tail 3 holds slots 1 and 2 — the same two items, + // reached without the modulo doing any work + given storage = [_]u8{ 0, 0, 0, 0 } + and ring = Ring(u8){ .buffer = @constCast(&storage), .head = 1, .tail = 3, .capacity = 4 } + when used = (ring.tail + ring.capacity - ring.head) % ring.capacity + then used == 2 + + test a_full_ring_and_an_empty_ring_both_show_head_equal_to_tail + // Verify: the classic ambiguity — this layout carries no separate count, + // so capacity slots of data would be indistinguishable from zero slots. + // A correct push must therefore stop at capacity - 1 usable entries. + given storage = [_]u8{ 0, 0, 0, 0 } + and ring = Ring(u8){ .buffer = @constCast(&storage), .head = 0, .tail = 0, .capacity = 4 } + when used = (ring.tail + ring.capacity - ring.head) % ring.capacity + and max_distinguishable = ring.capacity - 1 + then used == 0 and max_distinguishable == 3 + + test ring_is_one_type_per_element_type + // Verify: Ring is generic over T, so two instantiations at the same + // element type are the same type and different element types are not + given same = Ring(u8) == Ring(u8) + and different = Ring(u8) == Ring(u32) + then same and different == false + diff --git a/apps/website/public/t27/specs/tri/collections/set.t27 b/apps/website/public/t27/specs/tri/collections/set.t27 new file mode 100644 index 0000000000..2ac48acaa8 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/set.t27 @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// No duplicates | φ² + 1/φ² = 3 | TRINITY + +module TriSet; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const HashSet(T) = struct { + items : std.HashMap(T, void), + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(allocator: std.mem.Allocator) → !HashSet(T) + fn init(allocator: std.mem.Allocator) -> !HashSet(T) { + // TODO: Implement from .tri spec + } + + // add(set: *HashSet(T), item: T, allocator: std.mem.Allocator) → !void + fn add(set: *HashSet(T), item: T, allocator: std.mem.Allocator) -> !void { + // TODO: Implement from .tri spec + } + + // contains(set: HashSet(T), item: T) → bool + fn contains(set: HashSet(T), item: T) -> bool { + // TODO: Implement from .tri spec + } + + // union(a: HashSet(T), b: HashSet(T), allocator: std.mem.Allocator) → !HashSet(T) + fn union(a: HashSet(T), b: HashSet(T), allocator: std.mem.Allocator) -> !HashSet(T) { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test init_basic_case + given input = default_input() + when result = init(input) + then result != undefined + + test add_basic_case + given input = default_input() + when result = add(input) + then result != undefined + + test contains_basic_case + given input = default_input() + when result = contains(input) + then result != undefined + + test union_basic_case + given input = default_input() + when result = union(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/collections/skip_list.t27 b/apps/website/public/t27/specs/tri/collections/skip_list.t27 new file mode 100644 index 0000000000..6957ab9e42 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/skip_list.t27 @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Random level selection | φ² + 1/φ² = 3 | TRINITY + +module TriSkipList; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SkipNode(T) = struct { + value : T, + forward : []?SkipNode(T), + level : usize, + }; + + pub const SkipList(T) = struct { + head : SkipNode(T), + max_level : usize, + level : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(max_level: usize, allocator: std.mem.Allocator) → !SkipList(T) + fn init(max_level: usize, allocator: std.mem.Allocator) -> !SkipList(T) { + // TODO: Implement from .tri spec + } + + // insert(list: *SkipList(T), value: T, allocator: std.mem.Allocator) → !void + fn insert(list: *SkipList(T), value: T, allocator: std.mem.Allocator) -> !void { + // TODO: Implement from .tri spec + } + + // search(list: SkipList(T), value: T) → bool + fn search(list: SkipList(T), value: T) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // init, insert and search have empty bodies, so the random level selection + // in the header cannot be exercised. These pin the node shape it will use. + + test forward_links_are_optional_nodes_held_by_value + // a level-k node carries k forward slots, each either a node or null + given node = SkipNode(i64) + and forward_type = @FieldType(node, "forward") + then forward_type == []?SkipNode(i64) + + test a_list_holds_its_head_node_inline + // head is a node by value, not a pointer, so an empty list still owns + // one node's worth of storage + given list = SkipList(i64) + and head_type = @FieldType(list, "head") + then head_type == SkipNode(i64) + + test levels_are_unsigned + given node_level_type = @FieldType(SkipNode(i64), "level") + and list_max_level_type = @FieldType(SkipList(i64), "max_level") + then node_level_type == usize and list_max_level_type == usize + diff --git a/apps/website/public/t27/specs/tri/collections/stack.t27 b/apps/website/public/t27/specs/tri/collections/stack.t27 new file mode 100644 index 0000000000..2ea310dac2 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/stack.t27 @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Last-in-first-out ordering | φ² + 1/φ² = 3 | TRINITY + +module TriStack; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Stack(T) = struct { + items : []T, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // empty() → Stack(T) + fn empty() -> Stack(T) { + // TODO: Implement from .tri spec + } + + // push(stack: Stack(T)) → void + fn push(stack: Stack(T)) -> void { + // TODO: Implement from .tri spec + } + + // pop(stack: Stack(T)) → void + fn pop(stack: Stack(T)) -> void { + // TODO: Implement from .tri spec + } + + // peek(stack: Stack(T)) → void + fn peek(stack: Stack(T)) -> void { + // TODO: Implement from .tri spec + } + + // is_empty(stack: Stack(T)) → void + fn is_empty(stack: Stack(T)) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test empty_stack_holds_no_items + // Verify: the boundary is_empty tests for -- an empty stack's backing + // slice has length zero + given s = Stack(u32){ .items = &[_]u32{} } + then s.items.len == 0 + + test top_of_stack_is_the_highest_index + // Verify: last-in-first-out ordering. Items enter at the end of the + // slice, so peek reads items[len - 1] and 30 -- pushed last -- is on top + given xs = [_]u32{ 10, 20, 30 } + and s = Stack(u32){ .items = @constCast(&xs) } + then s.items[s.items.len - 1] == 30 and s.items[0] == 10 + + test stack_is_generic_over_the_element_type + // Verify: Stack(T) stores T, so Stack(u8) and Stack(u32) are two + // distinct types rather than one erased container + given a = Stack(u8){ .items = &[_]u8{} } + when element_type = @TypeOf(a.items) + and distinct = Stack(u8) != Stack(u32) + then distinct and element_type == []u8 + diff --git a/apps/website/public/t27/specs/tri/collections/state.t27 b/apps/website/public/t27/specs/tri/collections/state.t27 new file mode 100644 index 0000000000..c99c22fa5e --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/state.t27 @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// State is a monad | φ² + 1/φ² = 3 | TRINITY + +module TriState; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const State(S, T) = struct { + run : fn(S) -> (S, T), + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // pure(value: T) → State(S, T) + fn pure(value: T) -> State(S, T) { + // TODO: Implement from .tri spec + } + + // get() → State(S, S) + fn get() -> State(S, S) { + // TODO: Implement from .tri spec + } + + // put(state: S) → State(S, void) + fn put(state: S) -> State(S, void) { + // TODO: Implement from .tri spec + } + + // modify(fn: fn(S) -> S) → State(S, void) + fn modify(fn: fn(S) -> S) -> State(S, void) { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test pure_basic_case + given input = default_input() + when result = pure(input) + then result != undefined + + test get_basic_case + given input = default_input() + when result = get(input) + then result != undefined + + test put_basic_case + given input = default_input() + when result = put(input) + then result != undefined + + test modify_basic_case + given input = default_input() + when result = modify(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/collections/tuple.t27 b/apps/website/public/t27/specs/tri/collections/tuple.t27 new file mode 100644 index 0000000000..926f347587 --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/tuple.t27 @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Combines multiple types | φ² + 1/φ² = 3 | TRINITY + +module TriTuple; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Tuple2(A, B) = struct { + first : A, + second : B, + }; + + pub const Tuple3(A, B, C) = struct { + first : A, + second : B, + third : C, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // pair(a: A, b: B) → Tuple2(A, B) + fn pair(a: A, b: B) -> Tuple2(A, B) { + // TODO: Implement from .tri spec + } + + // triple(a: A, b: B, c: C) → Tuple3(A, B, C) + fn triple(a: A, b: B, c: C) -> Tuple3(A, B, C) { + // TODO: Implement from .tri spec + } + + // fst(pair: Tuple2(A, B)) → A + fn fst(pair: Tuple2(A, B)) -> A { + // TODO: Implement from .tri spec + } + + // snd(pair: Tuple2(A, B)) → B + fn snd(pair: Tuple2(A, B)) -> B { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test pair_basic_case + given input = default_input() + when result = pair(input) + then result != undefined + + test triple_basic_case + given input = default_input() + when result = triple(input) + then result != undefined + + test fst_basic_case + given input = default_input() + when result = fst(input) + then result != undefined + + test snd_basic_case + given input = default_input() + when result = snd(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/collections/variant.t27 b/apps/website/public/t27/specs/tri/collections/variant.t27 new file mode 100644 index 0000000000..bcbd3e0cfe --- /dev/null +++ b/apps/website/public/t27/specs/tri/collections/variant.t27 @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// One of many types | φ² + 1/φ² = 3 | TRINITY + +module TriVariant; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Variant(T) = struct { + tag : []const u8, + value : T, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // make(tag: []const u8, value: T) → Variant(T) + fn make(tag: []const u8, value: T) -> Variant(T) { + // TODO: Implement from .tri spec + } + + // get_tag(variant: Variant(T)) → []const u8 + fn get_tag(variant: Variant(T)) -> []const u8 { + // TODO: Implement from .tri spec + } + + // match(variant: Variant(T), handlers: Map) → T + fn match(variant: Variant(T), handlers: Map) -> T { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // make/get_tag/match are unimplemented stubs -- each emits + // `@panic("not yet implemented")`, so tagging and dispatch cannot be + // exercised. Variant(T) is a real type constructor, and that is testable. + + test payload_type_follows_the_parameter + then @FieldType(Variant(u8), "value") == u8 + and @FieldType(Variant(bool), "value") == bool + + test tag_is_a_string_whatever_the_payload + // The tag is not derived from T: it is the same []const u8 either way. + then @FieldType(Variant(u8), "tag") == []const u8 + and @FieldType(Variant(bool), "tag") == []const u8 + + test distinct_payloads_give_distinct_variants + then Variant(u8) != Variant(bool) + and Variant(u8) == Variant(u8) + + test variant_is_tag_plus_payload_only + // "One of many types" is carried by a runtime string, not a Zig union: + // there is no compile-time exhaustiveness available here. + then @typeInfo(Variant(u8)).@"struct".fields.len == 2 + diff --git a/apps/website/public/t27/specs/tri/crypto/base32.t27 b/apps/website/public/t27/specs/tri/crypto/base32.t27 new file mode 100644 index 0000000000..cd24da3eb3 --- /dev/null +++ b/apps/website/public/t27/specs/tri/crypto/base32.t27 @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// RFC 4648 compliant | φ² + 1/φ² = 3 | TRINITY + +module TriBase32; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Base32 = struct { + alphabet : []const u8, + padding : bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // standard() → Base32 + fn standard() -> Base32 { + // TODO: Implement from .tri spec + } + + // encode(codec: Base32, input: []const u8, allocator: std.mem.Allocator) → ![]const u8 + fn encode(codec: Base32, input: []const u8, allocator: std.mem.Allocator) -> ![]const u8 { + // TODO: Implement from .tri spec + } + + // decode(codec: Base32, input: []const u8, allocator: std.mem.Allocator) → ![]const u8 + fn decode(codec: Base32, input: []const u8, allocator: std.mem.Allocator) -> ![]const u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test standard_alphabet_holds_thirty_two_symbols + // Verify: RFC 4648 section 6 — 5 bits per symbol means exactly 32 symbols, + // and the standard encoding pads to a multiple of eight characters + given codec = Base32{ .alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", .padding = true } + then codec.alphabet.len == 32 and codec.padding + + test alphabet_is_the_letters_then_the_digits_two_through_seven + // Verify: the 26 uppercase letters fill indices 0..25 and 2-7 fill 26..31 + given alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" + when letters_match = std.mem.eql(u8, alphabet[0..26], "ABCDEFGHIJKLMNOPQRSTUVWXYZ") + and digits_match = std.mem.eql(u8, alphabet[26..32], "234567") + then letters_match and digits_match + + test alphabet_omits_the_digits_that_look_like_letters + // Verify: RFC 4648 section 6 leaves out 0, 1, 8 and 9 so that a symbol + // cannot be confused with O, I, B or g when read aloud or by hand + given alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" + when zero_at = std.mem.indexOf(u8, alphabet, "0") + and one_at = std.mem.indexOf(u8, alphabet, "1") + and eight_at = std.mem.indexOf(u8, alphabet, "8") + and nine_at = std.mem.indexOf(u8, alphabet, "9") + then zero_at == null and one_at == null and eight_at == null and nine_at == null + + test alphabet_is_narrower_than_base64 + // Verify: base32 trades density for case-insensitivity — 5 bits a symbol + // against base64's 6, so the same 40-bit group needs 8 symbols not 7 + given base32_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" + when group_symbols = 40 / 5 + then base32_alphabet.len == 32 and group_symbols == 8 + + test encoded_length_is_eight_characters_per_five_bytes + // Verify: with padding on, n input bytes become 8 * ceil(n/5) characters — + // 5 bytes give 8, and 6 bytes give 16 because the last group is padded + given five = 8 * ((5 + 4) / 5) + and six = 8 * ((6 + 4) / 5) + then five == 8 and six == 16 + diff --git a/apps/website/public/t27/specs/tri/crypto/base64.t27 b/apps/website/public/t27/specs/tri/crypto/base64.t27 new file mode 100644 index 0000000000..1803469434 --- /dev/null +++ b/apps/website/public/t27/specs/tri/crypto/base64.t27 @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// RFC 4648 compliant | φ² + 1/φ² = 3 | TRINITY + +module TriBase64; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Base64 = struct { + alphabet : []const u8, + padding : bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // standard() → Base64 + fn standard() -> Base64 { + // TODO: Implement from .tri spec + } + + // url_safe() → Base64 + fn url_safe() -> Base64 { + // TODO: Implement from .tri spec + } + + // encode(codec: Base64, input: []const u8, allocator: std.mem.Allocator) → ![]const u8 + fn encode(codec: Base64, input: []const u8, allocator: std.mem.Allocator) -> ![]const u8 { + // TODO: Implement from .tri spec + } + + // decode(codec: Base64, input: []const u8, allocator: std.mem.Allocator) → ![]const u8 + fn decode(codec: Base64, input: []const u8, allocator: std.mem.Allocator) -> ![]const u8 { + // TODO: Implement from .tri spec + } + + // encoded_length(codec: Base64, input_len: usize) → usize + fn encoded_length(codec: Base64, input_len: usize) -> usize { + // TODO: Implement from .tri spec + } + + // decoded_length(input: []const u8) → !usize + fn decoded_length(input: []const u8) -> !usize { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test standard_alphabet_holds_sixty_four_symbols + // Verify: RFC 4648 section 4 — 6 bits per symbol means exactly 64 symbols, + // and the standard encoding pads to a multiple of four characters + given codec = Base64{ .alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", .padding = true } + then codec.alphabet.len == 64 and codec.padding + + test url_safe_alphabet_holds_sixty_four_symbols + // Verify: RFC 4648 section 5 is the same size and drops the padding, so + // the result is safe in a URL path or query without percent-escaping + given codec = Base64{ .alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", .padding = false } + then codec.alphabet.len == 64 and codec.padding == false + + test the_two_alphabets_agree_on_their_first_sixty_two_symbols + // Verify: RFC 4648 section 5 substitutes only indices 62 and 63 + given standard_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + and url_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + when prefixes_agree = std.mem.eql(u8, standard_alphabet[0..62], url_alphabet[0..62]) + and tails_agree = std.mem.eql(u8, standard_alphabet[62..64], url_alphabet[62..64]) + then prefixes_agree and tails_agree == false + + test url_safe_alphabet_avoids_the_reserved_url_characters + // Verify: neither + nor / survives into the URL-safe alphabet + given url_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + when plus_at = std.mem.indexOf(u8, url_alphabet, "+") + and slash_at = std.mem.indexOf(u8, url_alphabet, "/") + then plus_at == null and slash_at == null + + test encoded_length_is_four_characters_per_three_bytes + // Verify: with padding on, n input bytes become 4 * ceil(n/3) characters — + // 3 bytes give 4, and 4 bytes give 8 because the last group is padded + given three = 4 * ((3 + 2) / 3) + and four = 4 * ((4 + 2) / 3) + then three == 4 and four == 8 + + test decoded_length_undoes_encoded_length_on_whole_groups + // Verify: a padded stream of 4k characters carries 3k bytes, so the two + // length functions are inverse on inputs that are a multiple of three + given encoded = 4 * ((9 + 2) / 3) + when decoded = encoded / 4 * 3 + then encoded == 12 and decoded == 9 + diff --git a/apps/website/public/t27/specs/tri/crypto/crypto.t27 b/apps/website/public/t27/specs/tri/crypto/crypto.t27 new file mode 100644 index 0000000000..6b6fcc8364 --- /dev/null +++ b/apps/website/public/t27/specs/tri/crypto/crypto.t27 @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Cryptographic operations | φ² + 1/φ² = 3 | TRINITY + +module TriCrypto; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const KeyPair = struct { + public_key : []u8, + private_key : []u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // generate_key_pair(allocator: std.mem.Allocator) → !KeyPair + fn generate_key_pair(allocator: std.mem.Allocator) -> !KeyPair { + // TODO: Implement from .tri spec + } + + // sha256(data: []const u8, allocator: std.mem.Allocator) → ![]u8 + fn sha256(data: []const u8, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // hmac(key: []const u8, message: []const u8, allocator: std.mem.Allocator) → ![]u8 + fn hmac(key: []const u8, message: []const u8, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test generate_key_pair_basic_case + given input = default_input() + when result = generate_key_pair(input) + then result != undefined + + test sha256_basic_case + given input = default_input() + when result = sha256(input) + then result != undefined + + test hmac_basic_case + given input = default_input() + when result = hmac(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/crypto/ecc.t27 b/apps/website/public/t27/specs/tri/crypto/ecc.t27 new file mode 100644 index 0000000000..5ab6e9f24b --- /dev/null +++ b/apps/website/public/t27/specs/tri/crypto/ecc.t27 @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Check if point satisfies curve equation | φ² + 1/φ² = 3 | TRINITY + +module TriEcc; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const ECPoint = struct { + x : f64, + y : f64, + is_infinity : bool, + }; + + pub const EllipticCurve = struct { + a : f64, + b : f64, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // add(curve: *EllipticCurve, p: ECPoint, q: ECPoint) → ECPoint + fn add(curve: *EllipticCurve, p: ECPoint, q: ECPoint) -> ECPoint { + // TODO: Implement from .tri spec + } + + // multiply(curve: *EllipticCurve, p: ECPoint, k: u64) → ECPoint + fn multiply(curve: *EllipticCurve, p: ECPoint, k: u64) -> ECPoint { + // TODO: Implement from .tri spec + } + + // is_on_curve(curve: *EllipticCurve, p: ECPoint) → bool + fn is_on_curve(curve: *EllipticCurve, p: ECPoint) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // add/multiply/is_on_curve are unimplemented stubs returning void, so they + // cannot be called. The curve equation they are built around is asserted + // directly against the declared fields instead: the reference curve is + // y^2 = x^3 + 1 (a = 0, b = 1), on which (2, 3) lies because 9 = 8 + 1. + + test reference_point_satisfies_the_curve_equation + // Verify: this is the condition is_on_curve must decide -- the residual + // y^2 - (x^3 + a*x + b) vanishes for a point on the curve + given curve = EllipticCurve{.a=0.0,.b=1.0} + and p = ECPoint{.x=2.0,.y=3.0,.is_infinity=false} + and residual = p.y * p.y - (p.x * p.x * p.x + curve.a * p.x + curve.b) + then @abs(residual) < 1e-12 + + test point_off_the_curve_leaves_a_nonzero_residual + // Verify: the negative case -- (2, 4) gives 16 rather than 9, so the + // residual is 7 and the point is rejected + given curve = EllipticCurve{.a=0.0,.b=1.0} + and p = ECPoint{.x=2.0,.y=4.0,.is_infinity=false} + and residual = p.y * p.y - (p.x * p.x * p.x + curve.a * p.x + curve.b) + then @abs(residual - 7.0) < 1e-12 + + test negated_point_is_also_on_the_curve + // Verify: the curve is symmetric about the x-axis, so (2, -3) satisfies + // it as well -- this is the additive inverse used by add + given curve = EllipticCurve{.a=0.0,.b=1.0} + and p = ECPoint{.x=2.0,.y=-3.0,.is_infinity=false} + and residual = p.y * p.y - (p.x * p.x * p.x + curve.a * p.x + curve.b) + then @abs(residual) < 1e-12 + + test reference_curve_is_nonsingular + // Verify: the discriminant condition 4a^3 + 27b^2 != 0 holds, so the + // curve has no cusp or self-intersection and the group law is defined + given curve = EllipticCurve{.a=0.0,.b=1.0} + and disc = 4.0 * curve.a * curve.a * curve.a + 27.0 * curve.b * curve.b + then @abs(disc - 27.0) < 1e-12 + + test point_at_infinity_is_flagged_rather_than_coordinate_encoded + // Verify: the group identity is carried by is_infinity, not by a + // sentinel pair of coordinates + given identity = ECPoint{.x=0.0,.y=0.0,.is_infinity=true} + then identity.is_infinity + diff --git a/apps/website/public/t27/specs/tri/crypto/hex.t27 b/apps/website/public/t27/specs/tri/crypto/hex.t27 new file mode 100644 index 0000000000..8cfcab6f22 --- /dev/null +++ b/apps/website/public/t27/specs/tri/crypto/hex.t27 @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Every byte becomes 2 chars | φ² + 1/φ² = 3 | TRINITY + +module TriHex; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Hex = struct { + uppercase : bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // lower_case() → Hex + fn lower_case() -> Hex { + // TODO: Implement from .tri spec + } + + // upper_case() → Hex + fn upper_case() -> Hex { + // TODO: Implement from .tri spec + } + + // encode(codec: Hex, input: []const u8, allocator: std.mem.Allocator) → ![]const u8 + fn encode(codec: Hex, input: []const u8, allocator: std.mem.Allocator) -> ![]const u8 { + // TODO: Implement from .tri spec + } + + // decode(input: []const u8, allocator: std.mem.Allocator) → ![]const u8 + fn decode(input: []const u8, allocator: std.mem.Allocator) -> ![]const u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // lower_case(), upper_case(), encode() and decode() have no body yet, so + // they panic when called; encode(codec) also takes no bytes and returns + // none, so the "every byte becomes 2 chars" claim and the + // decode(encode(x)) == x round trip cannot be stated yet. The Hex codec + // value is what this module states. + + test hex_codec_records_its_letter_case + given lower = Hex{.uppercase=false} + and upper = Hex{.uppercase=true} + then !lower.uppercase + and upper.uppercase + + test hex_the_two_codecs_are_distinguishable + // one bool is the whole difference between the two codecs the module + // names, so it has to actually differ between them + given lower = Hex{.uppercase=false} + and upper = Hex{.uppercase=true} + then lower.uppercase != upper.uppercase + diff --git a/apps/website/public/t27/specs/tri/crypto/hmac.t27 b/apps/website/public/t27/specs/tri/crypto/hmac.t27 new file mode 100644 index 0000000000..36904aa679 --- /dev/null +++ b/apps/website/public/t27/specs/tri/crypto/hmac.t27 @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Key padded to 64 bytes with ipad/opad | φ² + 1/φ² = 3 | TRINITY + +module TriHmac; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const HMAC = struct { + opad : [64]u8, + inner : SHA256, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(key: []const u8) → HMAC + fn init(key: []const u8) -> HMAC { + // TODO: Implement from .tri spec + } + + // update(hmac: *HMAC, data: []const u8) → void + fn update(hmac: *HMAC, data: []const u8) -> void { + // TODO: Implement from .tri spec + } + + // final(hmac: *HMAC) → [32]u8 + fn final(hmac: *HMAC) -> [32]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test init_basic_case + given input = default_input() + when result = init(input) + then result != undefined + + test update_basic_case + given input = default_input() + when result = update(input) + then result != undefined + + test final_basic_case + given input = default_input() + when result = final(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/crypto/reed_solomon.t27 b/apps/website/public/t27/specs/tri/crypto/reed_solomon.t27 new file mode 100644 index 0000000000..6b8b779640 --- /dev/null +++ b/apps/website/public/t27/specs/tri/crypto/reed_solomon.t27 @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Can recover from up to parity_shards/2 erasures | φ² + 1/φ² = 3 | TRINITY + +module TriReedSolomon; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const RSCode = struct { + data_shards : usize, + parity_shards : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // encode(data: []const u8, parity_count: usize, allocator: std.mem.Allocator) → ![]u8 + fn encode(data: []const u8, parity_count: usize, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // decode(shards: []const ?u8, allocator: std.mem.Allocator) → ![]u8 + fn decode(shards: []const ?u8, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test encode_basic_case + given input = default_input() + when result = encode(input) + then result != undefined + + test decode_basic_case + given input = default_input() + when result = decode(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/crypto/rsa.t27 b/apps/website/public/t27/specs/tri/crypto/rsa.t27 new file mode 100644 index 0000000000..cc891d02f7 --- /dev/null +++ b/apps/website/public/t27/specs/tri/crypto/rsa.t27 @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Fast exponentiation mod n | φ² + 1/φ² = 3 | TRINITY + +module TriRsa; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const RSAKeyPair = struct { + public_e : u64, + public_n : u64, + private_d : u64, + private_n : u64, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // generate(allocator: std.mem.Allocator, bit_size: usize) → RSAKeyPair + fn generate(allocator: std.mem.Allocator, bit_size: usize) -> RSAKeyPair { + // TODO: Implement from .tri spec + } + + // encrypt(message: u64, e: u64, n: u64) → u64 + fn encrypt(message: u64, e: u64, n: u64) -> u64 { + // TODO: Implement from .tri spec + } + + // decrypt(ciphertext: u64, d: u64, n: u64) → u64 + fn decrypt(ciphertext: u64, d: u64, n: u64) -> u64 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above is still `TODO: Implement`, so each one compiles + // to `@panic("not yet implemented")`: calling one aborts the test binary. + // What this module does state is the shape of an RSA key pair, and that + // shape carries a real arithmetic obligation, which these tests check on + // a concrete key. + + test key_pair_holds_four_u64_words + given e = @FieldType(RSAKeyPair, "public_e") + then e == u64 + and @FieldType(RSAKeyPair, "public_n") == u64 + and @FieldType(RSAKeyPair, "private_d") == u64 + and @FieldType(RSAKeyPair, "private_n") == u64 + + // The textbook key from p = 61, q = 53: n = pq = 3233, and the totient + // (p-1)(q-1) = 3120. Both halves of a key pair share the same modulus, + // and the exponents are inverses mod the totient -- 17 * 2753 = 46801 = + // 15 * 3120 + 1 -- which is exactly what makes decrypt undo encrypt. + test both_halves_of_a_key_pair_share_one_modulus + given key = RSAKeyPair{ .public_e = 17, .public_n = 3233, .private_d = 2753, .private_n = 3233 } + then key.public_n == key.private_n + and key.public_n == 61 * 53 + + test public_and_private_exponents_are_inverses_mod_the_totient + given key = RSAKeyPair{ .public_e = 17, .public_n = 3233, .private_d = 2753, .private_n = 3233 } + and totient = (61 - 1) * (53 - 1) + then totient == 3120 + and (key.public_e * key.private_d) % totient == 1 + + test generate_takes_an_allocator_and_returns_nothing + given f = generate + then @TypeOf(f) == fn (std.mem.Allocator) void + + // encrypt is given a message with no key, and decrypt a ciphertext with + // no key. Neither can do the modular exponentiation the header names. + // These record the signatures as they stand. + test encrypt_and_decrypt_take_one_u64_and_return_nothing + given f = encrypt + then @TypeOf(f) == fn (u64) void + and @TypeOf(decrypt) == fn (u64) void + diff --git a/apps/website/public/t27/specs/tri/crypto/sha256.t27 b/apps/website/public/t27/specs/tri/crypto/sha256.t27 new file mode 100644 index 0000000000..bf366d84f1 --- /dev/null +++ b/apps/website/public/t27/specs/tri/crypto/sha256.t27 @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Merkle-Damgard construction with 64-byte blocks | φ² + 1/φ² = 3 | TRINITY + +module TriSha256; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SHA256 = struct { + state : [8]u32, + buffer : [64]u8, + count : u64, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init() → SHA256 + fn init() -> SHA256 { + // TODO: Implement from .tri spec + } + + // update(sha: *SHA256, data: []const u8) → void + fn update(sha: *SHA256, data: []const u8) -> void { + // TODO: Implement from .tri spec + } + + // final(sha: *SHA256) → [32]u8 + fn final(sha: *SHA256) -> [32]u8 { + // TODO: Implement from .tri spec + } + + // hash(data: []const u8) → [32]u8 + fn hash(data: []const u8) -> [32]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // init, update, final and hash have no bodies, so no digest can be + // computed here yet -- not even the empty-string vector. What the module + // does fix is the shape of the Merkle-Damgard state, and those three + // widths are the ones SHA-256 requires. + + test state_is_the_256_bit_digest + // eight 32-bit working variables a..h = 256 bits of chaining value + given state_bits = @bitSizeOf(@FieldType(SHA256, "state")) + then state_bits == 256 + + test buffer_is_exactly_one_512_bit_block + given buffer_bytes = @sizeOf(@FieldType(SHA256, "buffer")) + then buffer_bytes == 64 and buffer_bytes * 8 == 512 + + test length_counter_matches_the_64_bit_padding_field + // the final block encodes the message length in 64 bits, so the + // running counter has to be that wide + given count_bits = @bitSizeOf(@FieldType(SHA256, "count")) + then count_bits == 64 + diff --git a/apps/website/public/t27/specs/tri/encoding/bson.t27 b/apps/website/public/t27/specs/tri/encoding/bson.t27 new file mode 100644 index 0000000000..cf2881575b --- /dev/null +++ b/apps/website/public/t27/specs/tri/encoding/bson.t27 @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Binary representation | φ² + 1/φ² = 3 | TRINITY + +module TriBson; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const BsonValue = struct { + enum : [Double, String, Document, Array, Binary, ObjectId, Boolean, DateTime, Null, Int32, Int64], + }; + + pub const BsonDocument = struct { + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // parse(data: []const u8, allocator: std.mem.Allocator) → !BsonDocument + fn parse(data: []const u8, allocator: std.mem.Allocator) -> !BsonDocument { + // TODO: Implement from .tri spec + } + + // serialize(doc: BsonDocument, allocator: std.mem.Allocator) → ![]u8 + fn serialize(doc: BsonDocument, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // parse() and serialize() have no body yet, so they panic when called and + // the serialize(parse(x)) == x round trip cannot be stated. What the + // module states is its two types. + // + // Note on BsonValue: the `enum : [...]` line now lowers to a real enum, so + // the eleven BSON kinds are mutually exclusive tags. Both the count and the + // tag-ness hold. What is still missing is the payload. + + test bson_value_declares_the_eleven_bson_value_kinds + // Double, String, Document, Array, Binary, ObjectId, Boolean, + // DateTime, Null, Int32, Int64 + given kinds = std.meta.fields(BsonValue).len + then kinds == 11 + + test bson_value_kinds_are_distinct_tags_but_still_carry_no_payload + // The eleven kinds are alternatives, not simultaneous fields: a value + // is exactly one of them and can tell Double from Int64 at runtime. + // The discriminant is a u4 -- the narrowest that holds eleven tags -- + // so a BsonValue is one byte of tag and nothing else. It is a bare + // enum, not a tagged union, so a real BSON value's double, string or + // document still has nowhere to live. + given first = @intFromEnum(BsonValue.Double) + and last = @intFromEnum(BsonValue.Int64) + and tag = @typeInfo(BsonValue).@"enum".tag_type + then first == 0 and last == 10 + and tag == u4 + and @sizeOf(BsonValue) == 1 + + test bson_document_is_still_an_empty_placeholder + given doc = BsonDocument{} + then std.meta.fields(@TypeOf(doc)).len == 0 + diff --git a/apps/website/public/t27/specs/tri/encoding/csv.t27 b/apps/website/public/t27/specs/tri/encoding/csv.t27 new file mode 100644 index 0000000000..f58caca7f0 --- /dev/null +++ b/apps/website/public/t27/specs/tri/encoding/csv.t27 @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// RFC 4180 compliant | φ² + 1/φ² = 3 | TRINITY + +module TriCsv; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const CsvRow = struct { + cells : [][]const u8, + }; + + pub const CsvDocument = struct { + headers : []CsvRow, + rows : []CsvRow, + delimiter : "u8", + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // parse(text: []const u8) → void + fn parse(text: []const u8) -> void { + // TODO: Implement from .tri spec + } + + // get(doc: CsvDocument) → void + fn get(doc: CsvDocument) -> void { + // TODO: Implement from .tri spec + } + + // set(doc: *CsvDocument) → void + fn set(doc: *CsvDocument) -> void { + // TODO: Implement from .tri spec + } + + // serialize(doc: CsvDocument) → void + fn serialize(doc: CsvDocument) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // parse/get/set/serialize are unimplemented stubs -- each emits + // `@panic("not yet implemented")`, so no parse/serialize round trip can be + // exercised. The declared document shape is what remains testable. + + test the_delimiter_is_one_byte_and_is_configurable + // RFC 4180 names the comma, but the field exists so TSV and + // semicolon-separated input can use the same parser. + then @FieldType(CsvDocument, "delimiter") == u8 + + test a_header_line_is_just_another_row + // headers and rows are the same element type, so serialize() need not + // special-case the first line. + then @FieldType(CsvDocument, "headers") == @FieldType(CsvDocument, "rows") + and @FieldType(CsvDocument, "rows") == []CsvRow + + test a_row_can_hold_at_least_one_cell + // FAILING ON PURPOSE. CsvRow is declared with no fields at all, so it + // is a zero-sized type: a parsed row cannot carry a single cell, and + // headers/rows are slices of nothing. The spec needs a cells field. + then @sizeOf(CsvRow) > 0 + diff --git a/apps/website/public/t27/specs/tri/encoding/html.t27 b/apps/website/public/t27/specs/tri/encoding/html.t27 new file mode 100644 index 0000000000..15acb4c5ce --- /dev/null +++ b/apps/website/public/t27/specs/tri/encoding/html.t27 @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// HTML5 subset | φ² + 1/φ² = 3 | TRINITY + +module TriHtml; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const HtmlNode = struct { + tag : []const u8, + attributes : [td.StringHashMap([]Const u8), + children : []HtmlNode, + inner_text : []const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // parse(html: []const u8, allocator: std.mem.Allocator) → !HtmlNode + fn parse(html: []const u8, allocator: std.mem.Allocator) -> !HtmlNode { + // TODO: Implement from .tri spec + } + + // query_selector(node: HtmlNode, selector: []const u8) → ?HtmlNode + fn query_selector(node: HtmlNode, selector: []const u8) -> ?HtmlNode { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test parse_basic_case + given input = default_input() + when result = parse(input) + then result != undefined + + test query_selector_basic_case + given input = default_input() + when result = query_selector(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/encoding/json.t27 b/apps/website/public/t27/specs/tri/encoding/json.t27 new file mode 100644 index 0000000000..ed0328e9df --- /dev/null +++ b/apps/website/public/t27/specs/tri/encoding/json.t27 @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// RFC 8259 compliant | φ² + 1/φ² = 3 | TRINITY + +module TriJson; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const JsonValue = struct { + type : JsonType, + data : *JsonValueData, + }; + + pub const JsonType = struct { + enum : [Null, Bool, Number, String, Array, Object], + }; + + pub const JsonArray = struct { + items : []JsonValue, + }; + + pub const JsonObject = struct { + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // parse(text: []const u8, allocator: std.mem.Allocator) → !JsonValue + fn parse(text: []const u8, allocator: std.mem.Allocator) -> !JsonValue { + // TODO: Implement from .tri spec + } + + // stringify(value: JsonValue, allocator: std.mem.Allocator) → ![]u8 + fn stringify(value: JsonValue, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // get(obj: JsonObject, key: []const u8) → ?JsonValue + fn get(obj: JsonObject, key: []const u8) -> ?JsonValue { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test parse_basic_case + given input = default_input() + when result = parse(input) + then result != undefined + + test stringify_basic_case + given input = default_input() + when result = stringify(input) + then result != undefined + + test get_basic_case + given input = default_input() + when result = get(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/encoding/markup.t27 b/apps/website/public/t27/specs/tri/encoding/markup.t27 new file mode 100644 index 0000000000..a10214a4ec --- /dev/null +++ b/apps/website/public/t27/specs/tri/encoding/markup.t27 @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Common markdown subset | φ² + 1/φ² = 3 | TRINITY + +module TriMarkup; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const MarkdownNode = struct { + type : []const u8, + content : []const u8, + children : []MarkdownNode, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // parse(markdown: []const u8, allocator: std.mem.Allocator) → ![]MarkdownNode + fn parse(markdown: []const u8, allocator: std.mem.Allocator) -> ![]MarkdownNode { + // TODO: Implement from .tri spec + } + + // to_html(nodes: []MarkdownNode, allocator: std.mem.Allocator) → ![]u8 + fn to_html(nodes: []MarkdownNode, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // parse and to_html have empty bodies, so no markdown can be parsed and + // no round trip can be checked. These pin the node shape both will use. + + test children_are_nodes_held_by_value + // the tree nests directly rather than through pointers, so a node owns + // its whole subtree + given children_type = @FieldType(MarkdownNode, "children") + then children_type == []MarkdownNode + + test the_node_tag_and_its_text_are_borrowed_byte_slices + // both are const slices, so a node points into the source markdown + // instead of copying it + given type_field = @FieldType(MarkdownNode, "type") + and content_field = @FieldType(MarkdownNode, "content") + then type_field == []const u8 and content_field == []const u8 + diff --git a/apps/website/public/t27/specs/tri/encoding/mime.t27 b/apps/website/public/t27/specs/tri/encoding/mime.t27 new file mode 100644 index 0000000000..eb4bcdd4b9 --- /dev/null +++ b/apps/website/public/t27/specs/tri/encoding/mime.t27 @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// RFC 5322 compliant | φ² + 1/φ² = 3 | TRINITY + +module TriMime; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Email = struct { + from : []const u8, + to : [][]const u8, + subject : []const u8, + body : []const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // parse(raw: []const u8, allocator: std.mem.Allocator) → !Email + fn parse(raw: []const u8, allocator: std.mem.Allocator) -> !Email { + // TODO: Implement from .tri spec + } + + // format(email: Email, allocator: std.mem.Allocator) → ![]u8 + fn format(email: Email, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // parse and format are unimplemented stubs -- calling either aborts the + // test binary with `@panic("not yet implemented")`. What the module does + // state is the shape of an RFC 5322 message, so that is what is asserted. + + test email_carries_the_four_declared_headers + then @typeInfo(Email).@"struct".fields.len == 4 + and @hasField(Email, "from") and @hasField(Email, "to") + and @hasField(Email, "subject") and @hasField(Email, "body") + + test one_sender_many_recipients + // RFC 5322: From is a single mailbox here, To is a list. + then @FieldType(Email, "from") == []const u8 + and @FieldType(Email, "to") == [][]const u8 + + test body_is_raw_bytes_not_decoded_parts + // No MIME part tree is declared: the body is one flat byte slice. + then @FieldType(Email, "body") == []const u8 + and @hasField(Email, "parts") == false + diff --git a/apps/website/public/t27/specs/tri/encoding/msgpack.t27 b/apps/website/public/t27/specs/tri/encoding/msgpack.t27 new file mode 100644 index 0000000000..5120cf33cf --- /dev/null +++ b/apps/website/public/t27/specs/tri/encoding/msgpack.t27 @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Compact binary format | φ² + 1/φ² = 3 | TRINITY + +module TriMsgpack; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const MsgPackType = struct { + enum : [Nil, Bool, Int, Uint, Float, Str, Bin, Array, Map], + }; + + pub const MsgPackValue = struct { + type : MsgPackType, + int_value : i64, + uint_value : u64, + float_value : f64, + str_value : []const u8, + bin_value : []const u8, + array_value : []MsgPackValue, + map_value : std.StringHashMap(MsgPackValue), + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // encode(value: MsgPackValue, allocator: std.mem.Allocator) → ![]u8 + fn encode(value: MsgPackValue, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // decode(data: []const u8, allocator: std.mem.Allocator) → !MsgPackValue + fn decode(data: []const u8, allocator: std.mem.Allocator) -> !MsgPackValue { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test msgpack_type_names_the_nine_families + // Verify: MessagePack sorts every value into nil, bool, int, uint, float, + // str, bin, array or map — nine families and no tenth + given family_count = @typeInfo(MsgPackType).@"enum".fields.len + and has_nil = @hasField(MsgPackType, "Nil") + and has_map = @hasField(MsgPackType, "Map") + then family_count == 9 and has_nil and has_map + + test signed_and_unsigned_integers_are_separate_families + // Verify: MessagePack has distinct int and uint tags, so the payload keeps + // an i64 and a u64 side by side rather than one signed field + given signed_type = @FieldType(MsgPackValue, "int_value") + and unsigned_type = @FieldType(MsgPackValue, "uint_value") + then signed_type == i64 and unsigned_type == u64 + + test str_and_bin_are_separate_families + // Verify: MessagePack split str (UTF-8 text) from bin (opaque bytes) in + // the 2013 spec revision, so the value carries both slices + given has_str = @hasField(MsgPackValue, "str_value") + and has_bin = @hasField(MsgPackValue, "bin_value") + and str_type = @FieldType(MsgPackValue, "str_value") + and bin_type = @FieldType(MsgPackValue, "bin_value") + then has_str and has_bin and str_type == bin_type + + test arrays_and_maps_nest_the_value_type_inside_itself + // Verify: array_value holds MsgPackValue elements, which is what makes the + // format recursive — a document is a value, not a flat record + given element_type = @FieldType(MsgPackValue, "array_value") + and expected = []MsgPackValue + then element_type == expected + + test positive_fixint_covers_zero_through_127 + // Verify: the single-byte positive fixint tag 0x00..0x7f encodes 128 + // distinct values, so any int in 0..127 costs exactly one byte + given low = 0x00 + and high = 0x7f + when span = high - low + 1 + then span == 128 + + test negative_fixint_covers_minus_32_through_minus_1 + // Verify: the single-byte negative fixint tag 0xe0..0xff encodes 32 + // distinct values, mapping onto -32..-1 + given low = 0xe0 + and high = 0xff + when span = high - low + 1 + then span == 32 + diff --git a/apps/website/public/t27/specs/tri/encoding/xml.t27 b/apps/website/public/t27/specs/tri/encoding/xml.t27 new file mode 100644 index 0000000000..c9c1b13bb8 --- /dev/null +++ b/apps/website/public/t27/specs/tri/encoding/xml.t27 @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Simplified XML parser | φ² + 1/φ² = 3 | TRINITY + +module TriXml; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const XmlNode = struct { + tag : []const u8, + attributes : [td.StringHashMap([]Const u8), + children : []XmlNode, + text : []const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // parse(text: []const u8, allocator: std.mem.Allocator) → !XmlNode + fn parse(text: []const u8, allocator: std.mem.Allocator) -> !XmlNode { + // TODO: Implement from .tri spec + } + + // format(node: XmlNode, allocator: std.mem.Allocator) → ![]u8 + fn format(node: XmlNode, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test parse_basic_case + given input = default_input() + when result = parse(input) + then result != undefined + + test format_basic_case + given input = default_input() + when result = format(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/graph/bellman_ford.t27 b/apps/website/public/t27/specs/tri/graph/bellman_ford.t27 new file mode 100644 index 0000000000..698a67e8f9 --- /dev/null +++ b/apps/website/public/t27/specs/tri/graph/bellman_ford.t27 @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Find shortest paths, detect negative cycles | φ² + 1/φ² = 3 | TRINITY + +module TriBellmanFord; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Edge = struct { + from : usize, + to : usize, + weight : i64, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // shortest_path(edges: []Edge, vertex_count: usize, start: usize, allocator: std.mem.Allocator) → []i64 + fn shortest_path(edges: []Edge, vertex_count: usize, start: usize, allocator: std.mem.Allocator) -> []i64 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test shortest_path_basic_case + given input = default_input() + when result = shortest_path(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/graph/dijkstra.t27 b/apps/website/public/t27/specs/tri/graph/dijkstra.t27 new file mode 100644 index 0000000000..20b9f994b9 --- /dev/null +++ b/apps/website/public/t27/specs/tri/graph/dijkstra.t27 @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Find shortest paths from start to all vertices | φ² + 1/φ² = 3 | TRINITY + +module TriDijkstra; + use base::types; + use math::constants; + use TriGraphBfs::Graph; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const DijkstraResult = struct { + distance : []f64, + parent : []?usize, + allocator : "std.mem.Allocator", + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // shortest_path(graph: *Graph) → void + fn shortest_path(graph: *Graph) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test shortest_path_basic_case + given input = default_input() + when result = shortest_path(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/graph/disjoint_set.t27 b/apps/website/public/t27/specs/tri/graph/disjoint_set.t27 new file mode 100644 index 0000000000..3eaa7ea9a0 --- /dev/null +++ b/apps/website/public/t27/specs/tri/graph/disjoint_set.t27 @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Attach shorter tree under taller tree root | φ² + 1/φ² = 3 | TRINITY + +module TriDisjointSet; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const DisjointSet = struct { + parent : []usize, + rank : []usize, + count : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(size: usize) → DisjointSet + fn init(size: usize) -> DisjointSet { + // TODO: Implement from .tri spec + } + + // find(ds: *DisjointSet, x: usize) → usize + fn find(ds: *DisjointSet, x: usize) -> usize { + // TODO: Implement from .tri spec + } + + // union(ds: *DisjointSet, x: usize, y: usize) → void + fn union(ds: *DisjointSet, x: usize, y: usize) -> void { + // TODO: Implement from .tri spec + } + + // connected(ds: *const DisjointSet, x: usize, y: usize) → bool + fn connected(ds: *const DisjointSet, x: usize, y: usize) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // init, find, union and connected are all unimplemented stubs: each one + // emits `@panic("not yet implemented")`, so calling any of them aborts the + // test binary rather than failing a test. (Their signatures are also + // truncated to the first parameter -- `find(ds: *DisjointSet)` has nowhere + // to receive the element to look up.) The assertions below build the + // forest by hand and check the representation the module declares. + + test parent_and_rank_are_parallel_arrays_over_the_same_elements + // One slot per element in each: index i is element i in both. No + // allocator is stored, so init(size) borrows its backing memory. + then @typeInfo(DisjointSet).@"struct".fields.len == 3 + and @FieldType(DisjointSet, "parent") == []usize + and @FieldType(DisjointSet, "rank") == @FieldType(DisjointSet, "parent") + and @hasField(DisjointSet, "allocator") == false + + test a_freshly_initialised_forest_is_every_element_its_own_root + // What init(size) has to produce: parent[i] == i, every rank zero, and + // count equal to the element count because nothing is joined yet. + // Built by hand here, since init is a stub. + given parent = [_]usize{ 0, 1, 2, 3 } + given rank = [_]usize{ 0, 0, 0, 0 } + given ds = DisjointSet{ .parent = @constCast(&parent), .rank = @constCast(&rank), .count = 4 } + then ds.parent[0] == 0 + and ds.parent[3] == 3 + and ds.rank[3] == 0 + and ds.count == ds.parent.len + + test one_union_drops_the_component_count_by_exactly_one + // Attaching element 1 under root 0 leaves three components, not four, + // and the taller root's rank goes up while the absorbed one keeps its. + given parent = [_]usize{ 0, 0, 2, 3 } + given rank = [_]usize{ 1, 0, 0, 0 } + given ds = DisjointSet{ .parent = @constCast(&parent), .rank = @constCast(&rank), .count = 3 } + then ds.parent[1] == 0 + and ds.rank[0] == 1 + and ds.count == ds.parent.len - 1 + and ds.count >= 1 + + test rank_is_bounded_by_the_log_of_the_element_count + // "Attach shorter tree under taller tree root": a root of rank r has + // at least 2^r members underneath it, so a million-element forest can + // never push a rank past 20 -- a usize rank is never the limit. + given members = 1 << 20 + then std.math.log2_int(usize, members) == 20 + and members > 1000000 + diff --git a/apps/website/public/t27/specs/tri/graph/graph.t27 b/apps/website/public/t27/specs/tri/graph/graph.t27 new file mode 100644 index 0000000000..8c17f0cdd0 --- /dev/null +++ b/apps/website/public/t27/specs/tri/graph/graph.t27 @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Adjacency list representation | φ² + 1/φ² = 3 | TRINITY + +module TriGraph; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Graph(T) = struct { + nodes : std.HashMap(T, []T), + directed : "bool", + }; + + pub const GraphPath(T) = struct { + nodes : []T, + cost : "f64", + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // empty(directed: bool) → void + fn empty(directed: bool) -> void { + // TODO: Implement from .tri spec + } + + // add_node(graph: *Graph(T)) → void + fn add_node(graph: *Graph(T)) -> void { + // TODO: Implement from .tri spec + } + + // add_edge(graph: *Graph(T)) → void + fn add_edge(graph: *Graph(T)) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test empty_basic_case + given input = default_input() + when result = empty(input) + then result != undefined + + test add_node_basic_case + given input = default_input() + when result = add_node(input) + then result != undefined + + test add_edge_basic_case + given input = default_input() + when result = add_edge(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/graph/graph_bfs.t27 b/apps/website/public/t27/specs/tri/graph/graph_bfs.t27 new file mode 100644 index 0000000000..b4241a6f4b --- /dev/null +++ b/apps/website/public/t27/specs/tri/graph/graph_bfs.t27 @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Free graph memory | φ² + 1/φ² = 3 | TRINITY + +module TriGraphBfs; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Graph = struct { + adj : [][]usize, + allocator : std.mem.Allocator, + }; + + pub const BFSResult = struct { + order : []usize, + distance : []usize, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(allocator: std.mem.Allocator, vertex_count: usize) → Graph + fn init(allocator: std.mem.Allocator, vertex_count: usize) -> Graph { + // TODO: Implement from .tri spec + } + + // add_edge(graph: *Graph, from: usize, to: usize) → void + fn add_edge(graph: *Graph, from: usize, to: usize) -> void { + // TODO: Implement from .tri spec + } + + // traverse(graph: *Graph, start: usize, allocator: std.mem.Allocator) → BFSResult + fn traverse(graph: *Graph, start: usize, allocator: std.mem.Allocator) -> BFSResult { + // TODO: Implement from .tri spec + } + + // deinit(graph: *Graph) → void + fn deinit(graph: *Graph) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test adjacency_has_one_row_per_vertex + // Verify: adj is indexed by vertex id, so a three-vertex graph has three + // rows even when a vertex has no neighbours + given row0 = [_]usize{ 1 } + and row1 = [_]usize{ 0, 2 } + and row2 = [_]usize{ 1 } + and rows = [_][]usize{ @constCast(&row0), @constCast(&row1), @constCast(&row2) } + when graph = Graph{ .adj = @constCast(&rows), .allocator = std.testing.allocator } + then graph.adj.len == 3 + + test an_undirected_edge_appears_in_both_endpoints_rows + // Verify: add_edge on an undirected graph must write twice — the path + // 0 - 1 - 2 puts 1 in row 0 and 0 in row 1 + given row0 = [_]usize{ 1 } + and row1 = [_]usize{ 0, 2 } + and row2 = [_]usize{ 1 } + and rows = [_][]usize{ @constCast(&row0), @constCast(&row1), @constCast(&row2) } + when graph = Graph{ .adj = @constCast(&rows), .allocator = std.testing.allocator } + and zero_sees_one = graph.adj[0][0] + and one_sees_zero = graph.adj[1][0] + and one_sees_two = graph.adj[1][1] + and two_sees_one = graph.adj[2][0] + then zero_sees_one == 1 and one_sees_zero == 0 and one_sees_two == 2 and two_sees_one == 1 + + test the_degree_sum_is_twice_the_edge_count + // Verify: the handshake lemma on the path 0 - 1 - 2 — two edges, so the + // row lengths add up to four + given row0 = [_]usize{ 1 } + and row1 = [_]usize{ 0, 2 } + and row2 = [_]usize{ 1 } + and rows = [_][]usize{ @constCast(&row0), @constCast(&row1), @constCast(&row2) } + when graph = Graph{ .adj = @constCast(&rows), .allocator = std.testing.allocator } + and degree_sum = graph.adj[0].len + graph.adj[1].len + graph.adj[2].len + then degree_sum == 4 + + test an_isolated_vertex_has_an_empty_row + // Verify: the boundary case — a vertex with no edges still occupies a row, + // and that row has length 0 rather than being absent + given rows = [_][]usize{ &.{} } + when graph = Graph{ .adj = @constCast(&rows), .allocator = std.testing.allocator } + and degree = graph.adj[0].len + then graph.adj.len == 1 and degree == 0 + + test bfs_result_holds_one_distance_per_vertex_and_starts_at_zero + // Verify: order and distance are parallel arrays over the visited set, so + // they are the same length, and the source sits at distance 0 + given order = [_]usize{ 0, 1, 2 } + and distance = [_]usize{ 0, 1, 2 } + when result = BFSResult{ .order = @constCast(&order), .distance = @constCast(&distance), .allocator = std.testing.allocator } + and source_distance = result.distance[0] + then result.order.len == result.distance.len and source_distance == 0 + + test bfs_distances_never_jump_by_more_than_one + // Verify: BFS explores by layer, so along the visit order the distance + // rises by at most 1 at each step — this is the property traverse() must + // produce on the path 0 - 1 - 2 + given distance = [_]usize{ 0, 1, 2 } + when step1 = distance[1] - distance[0] + and step2 = distance[2] - distance[1] + then step1 <= 1 and step2 <= 1 + diff --git a/apps/website/public/t27/specs/tri/graph/graph_dfs.t27 b/apps/website/public/t27/specs/tri/graph/graph_dfs.t27 new file mode 100644 index 0000000000..007376d453 --- /dev/null +++ b/apps/website/public/t27/specs/tri/graph/graph_dfs.t27 @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// DFS from start vertex | φ² + 1/φ² = 3 | TRINITY + +module TriGraphDfs; + use base::types; + use math::constants; + use TriGraphBfs::Graph; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const DFSResult = struct { + preorder : []usize, + postorder : []usize, + allocator : "std.mem.Allocator", + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // traverse(graph: *Graph) → void + fn traverse(graph: *Graph) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test traverse_basic_case + given input = default_input() + when result = traverse(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/graph/prims_mst.t27 b/apps/website/public/t27/specs/tri/graph/prims_mst.t27 new file mode 100644 index 0000000000..c5287c1d1c --- /dev/null +++ b/apps/website/public/t27/specs/tri/graph/prims_mst.t27 @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Find MST using Prim's algorithm | φ² + 1/φ² = 3 | TRINITY + +module TriPrimsMst; + use base::types; + use math::constants; + use TriGraphBfs::Graph; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const MSTResult = struct { + edges : []Edge, + total_weight : "i64", + allocator : "std.mem.Allocator", + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // mst(graph: *Graph) → void + fn mst(graph: *Graph) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test mst_basic_case + given input = default_input() + when result = mst(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/graph/topological_sort.t27 b/apps/website/public/t27/specs/tri/graph/topological_sort.t27 new file mode 100644 index 0000000000..e914e74070 --- /dev/null +++ b/apps/website/public/t27/specs/tri/graph/topological_sort.t27 @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// If u -> v is edge, u appears before v in order | φ² + 1/φ² = 3 | TRINITY + +module TriTopological; + use base::types; + use math::constants; + use TriGraphBfs::Graph; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const TopologicalSort = struct { + order : []usize, + has_cycle : "bool", + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // sort(graph: *const Graph) → void + fn sort(graph: *const Graph) -> void { + // TODO: Implement from .tri spec + } + + // is_valid(result: TopologicalSort) → void + fn is_valid(result: TopologicalSort) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test sort_basic_case + given input = default_input() + when result = sort(input) + then result != undefined + + test is_valid_basic_case + given input = default_input() + when result = is_valid(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/io/compress.t27 b/apps/website/public/t27/specs/tri/io/compress.t27 new file mode 100644 index 0000000000..874b747376 --- /dev/null +++ b/apps/website/public/t27/specs/tri/io/compress.t27 @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Lossless compression | φ² + 1/φ² = 3 | TRINITY + +module TriCompress; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Compressed = struct { + data : []u8, + original_len : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // compress(input: []const u8, allocator: std.mem.Allocator) → !Compressed + fn compress(input: []const u8, allocator: std.mem.Allocator) -> !Compressed { + // TODO: Implement from .tri spec + } + + // decompress(compressed: Compressed, allocator: std.mem.Allocator) → ![]u8 + fn decompress(compressed: Compressed, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test original_len_is_the_uncompressed_size_not_the_payload_size + // Verify: the field that makes decompress possible -- original_len + // records how many bytes to restore, and for a record that actually + // compressed it is larger than data.len + given payload = [_]u8{ 0, 1, 2, 3 } + and c = Compressed{ .data = @constCast(&payload), .original_len = 10 } + then c.data.len == 4 and c.original_len == 10 and c.original_len > c.data.len + + test empty_input_gives_an_empty_record + // Verify: the boundary -- nothing to compress means no payload and a + // recorded original length of zero + given c = Compressed{ .data = &[_]u8{}, .original_len = 0 } + then c.data.len == 0 and c.original_len == 0 + + test incompressible_input_may_be_no_smaller_than_the_original + // Verify: lossless compression cannot shrink every input, so the type + // must also represent a record whose payload is not smaller than the + // original -- original_len is not an upper bound on data.len + given payload = [_]u8{ 7, 7, 7, 7 } + and c = Compressed{ .data = @constCast(&payload), .original_len = 4 } + then c.data.len == c.original_len + diff --git a/apps/website/public/t27/specs/tri/io/filesystem.t27 b/apps/website/public/t27/specs/tri/io/filesystem.t27 new file mode 100644 index 0000000000..4bd0873313 --- /dev/null +++ b/apps/website/public/t27/specs/tri/io/filesystem.t27 @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Results don't end with separator (except root) | φ² + 1/φ² = 3 | TRINITY + +module TriFilesystem; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const PathError = struct { + enum : [invalid_path, not_found, not_a_directory, not_a_file, permission_denied], + }; + + pub const FileInfo = struct { + path : []const u8, + size : u64, + is_dir : bool, + is_file : bool, + modified : u64, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // join(allocator: std.mem.Allocator, parts: [][]const u8) → ![]u8 + fn join(allocator: std.mem.Allocator, parts: [][]const u8) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // basename(path: []const u8) → []const u8 + fn basename(path: []const u8) -> []const u8 { + // TODO: Implement from .tri spec + } + + // dirname(path: []const u8) → []const u8 + fn dirname(path: []const u8) -> []const u8 { + // TODO: Implement from .tri spec + } + + // ext(path: []const u8) → []const u8 + fn ext(path: []const u8) -> []const u8 { + // TODO: Implement from .tri spec + } + + // has_ext(path: []const u8, ext: []const u8) → bool + fn has_ext(path: []const u8, ext: []const u8) -> bool { + // TODO: Implement from .tri spec + } + + // is_absolute(path: []const u8) → bool + fn is_absolute(path: []const u8) -> bool { + // TODO: Implement from .tri spec + } + + // normalize(allocator: std.mem.Allocator, path: []const u8) → ![]u8 + fn normalize(allocator: std.mem.Allocator, path: []const u8) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // All seven path functions are unimplemented stubs returning void, so no + // join/basename/dirname round trip can be asserted yet. FileInfo, the one + // well-formed declaration in this module, is tested here. + // + // PathError used to be untestable: its body was `enum : ,` and the type + // emitted as a struct with no variants. The five names have been recovered + // from the upstream spec this file was converted from (trinity-fpga + // specs/tri/tri_filesystem.tri:7) and are pinned below. + + test directory_entry_is_not_also_a_file + // Verify: FileInfo has the declared field names, and the two kind flags + // are mutually exclusive for a directory + given info = FileInfo{.path="/usr/local",.size=4096,.is_dir=true,.is_file=false,.modified=0} + then info.is_dir and info.is_file == false + + test regular_file_entry_is_not_a_directory + // Verify: the same exclusion the other way round + given info = FileInfo{.path="/usr/local/bin/t27c",.size=1024,.is_dir=false,.is_file=true,.modified=1700000000} + then info.is_file and info.is_dir == false + + test size_field_reaches_past_four_gigabytes + // Verify: the declared u64 size holds a file a u32 could not address + given info = FileInfo{.path="/big.bin",.size=5000000000,.is_dir=false,.is_file=true,.modified=0} + then info.size > 4294967295 + + test absolute_path_is_stored_verbatim + // Verify: the path field keeps the bytes it was built from, leading + // separator included + given info = FileInfo{.path="/usr/local/bin/t27c",.size=0,.is_dir=false,.is_file=true,.modified=0} + and stored = std.mem.eql(u8, info.path, "/usr/local/bin/t27c") + then stored + + test empty_size_is_representable_for_an_empty_file + // Verify: zero, the boundary value, is a legal size + given info = FileInfo{.path="/empty",.size=0,.is_dir=false,.is_file=true,.modified=0} + then info.size == 0 and info.is_file + + + test path_error_lists_the_failures_a_path_operation_can_have + // Recovered from trinity-fpga specs/tri/tri_filesystem.tri:7. The + // not_a_directory / not_a_file pair is what makes is_dir and is_file on + // FileInfo checkable rather than merely declared. + then @typeInfo(PathError).@"enum".fields.len == 5 + and std.mem.eql(u8, @typeInfo(PathError).@"enum".fields[0].name, "invalid_path") + and std.mem.eql(u8, @typeInfo(PathError).@"enum".fields[4].name, "permission_denied") diff --git a/apps/website/public/t27/specs/tri/io/fs.t27 b/apps/website/public/t27/specs/tri/io/fs.t27 new file mode 100644 index 0000000000..a2e9837320 --- /dev/null +++ b/apps/website/public/t27/specs/tri/io/fs.t27 @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Platform-aware paths | φ² + 1/φ² = 3 | TRINITY + +module TriFs; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Path = struct { + parts : [][]const u8, + absolute : bool, + }; + + pub const FileInfo = struct { + size : u64, + is_dir : bool, + is_file : bool, + modified : Instant, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // join(base: Path, suffix: Path, allocator: std.mem.Allocator) → !Path + fn join(base: Path, suffix: Path, allocator: std.mem.Allocator) -> !Path { + // TODO: Implement from .tri spec + } + + // basename(path: Path) → []const u8 + fn basename(path: Path) -> []const u8 { + // TODO: Implement from .tri spec + } + + // dirname(path: Path) → []const u8 + fn dirname(path: Path) -> []const u8 { + // TODO: Implement from .tri spec + } + + // extension(path: Path) → ?[]const u8 + fn extension(path: Path) -> ?[]const u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test join_basic_case + given input = default_input() + when result = join(input) + then result != undefined + + test basename_basic_case + given input = default_input() + when result = basename(input) + then result != undefined + + test dirname_basic_case + given input = default_input() + when result = dirname(input) + then result != undefined + + test extension_basic_case + given input = default_input() + when result = extension(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/io/io.t27 b/apps/website/public/t27/specs/tri/io/io.t27 new file mode 100644 index 0000000000..f31788dc8f --- /dev/null +++ b/apps/website/public/t27/specs/tri/io/io.t27 @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Tag effects for type safety | φ² + 1/φ² = 3 | TRINITY + +module TriIo; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const IO(T) = struct { + performed : bool, + value : T, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // pure(value: T) → IO(T) + fn pure(value: T) -> IO(T) { + // TODO: Implement from .tri spec + } + + // map(io: IO(T), fn: fn(T) -> U) → IO(U) + fn map(io: IO(T), fn: fn(T) -> U) -> IO(U) { + // TODO: Implement from .tri spec + } + + // bind(io: IO(T), fn: fn(T) -> IO(U)) → IO(U) + fn bind(io: IO(T), fn: fn(T) -> IO(U)) -> IO(U) { + // TODO: Implement from .tri spec + } + + // perform(io: IO(T)) → T + fn perform(io: IO(T)) -> T { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test a_pure_value_is_not_yet_performed + // Verify: the tag that makes the effect type useful -- pure() wraps a + // value without running anything, so performed is still false + given io = IO(u32){ .performed = false, .value = 7 } + then io.performed == false and io.value == 7 + + test performed_flag_is_the_only_difference_after_perform + // Verify: perform() flips the tag and leaves the payload alone -- two + // IO values over the same payload differ only in `performed` + given pending = IO(u32){ .performed = false, .value = 1 } + and done = IO(u32){ .performed = true, .value = 1 } + then pending.value == done.value and pending.performed != done.performed + + test io_is_generic_over_the_payload_type + // Verify: IO(T) threads T into `value`, so IO(bool) and IO(u32) are + // distinct types rather than one erased box + given io = IO(bool){ .performed = true, .value = true } + when payload_type = @TypeOf(io.value) + and distinct = IO(bool) != IO(u32) + then distinct and payload_type == bool + diff --git a/apps/website/public/t27/specs/tri/io/reader.t27 b/apps/website/public/t27/specs/tri/io/reader.t27 new file mode 100644 index 0000000000..f0a4c393ff --- /dev/null +++ b/apps/website/public/t27/specs/tri/io/reader.t27 @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Reader enables implicit environment passing | φ² + 1/φ² = 3 | TRINITY + +module TriReader; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Reader(R, T) = struct { + run : fn(R) -> T, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // pure(value: T) → Reader(R, T) + fn pure(value: T) -> Reader(R, T) { + // TODO: Implement from .tri spec + } + + // ask() → Reader(R, R) + fn ask() -> Reader(R, R) { + // TODO: Implement from .tri spec + } + + // asks(fn: fn(R) -> T) → Reader(R, T) + fn asks(fn: fn(R) -> T) -> Reader(R, T) { + // TODO: Implement from .tri spec + } + + // local(fn: fn(R) -> R, reader: Reader(R, T)) → Reader(R, T) + fn local(fn: fn(R) -> R, reader: Reader(R, T)) -> Reader(R, T) { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test pure_basic_case + given input = default_input() + when result = pure(input) + then result != undefined + + test ask_basic_case + given input = default_input() + when result = ask(input) + then result != undefined + + test asks_basic_case + given input = default_input() + when result = asks(input) + then result != undefined + + test local_basic_case + given input = default_input() + when result = local(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/io/writer.t27 b/apps/website/public/t27/specs/tri/io/writer.t27 new file mode 100644 index 0000000000..c706fa9779 --- /dev/null +++ b/apps/website/public/t27/specs/tri/io/writer.t27 @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Output type must be a monoid | φ² + 1/φ² = 3 | TRINITY + +module TriWriter; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Writer(W, T) = struct { + value : T, + output : W, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // pure(value: T) → Writer(W, T) + fn pure(value: T) -> Writer(W, T) { + // TODO: Implement from .tri spec + } + + // tell(output: W) → Writer(W, void) + fn tell(output: W) -> Writer(W, void) { + // TODO: Implement from .tri spec + } + + // listen(writer: Writer(W, T)) → Writer(W, struct { T, W }) + fn listen(writer: Writer(W, T)) -> Writer(W, struct { T, W }) { + // TODO: Implement from .tri spec + } + + // censor(fn: fn(W) -> W, writer: Writer(W, T)) → Writer(W, T) + fn censor(fn: fn(W) -> W, writer: Writer(W, T)) -> Writer(W, T) { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test pure_basic_case + given input = default_input() + when result = pure(input) + then result != undefined + + test tell_basic_case + given input = default_input() + when result = tell(input) + then result != undefined + + test listen_basic_case + given input = default_input() + when result = listen(input) + then result != undefined + + test censor_basic_case + given input = default_input() + when result = censor(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/io/zip.t27 b/apps/website/public/t27/specs/tri/io/zip.t27 new file mode 100644 index 0000000000..652ba6814d --- /dev/null +++ b/apps/website/public/t27/specs/tri/io/zip.t27 @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Immutable tree traversal | φ² + 1/φ² = 3 | TRINITY + +module TriZipper; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Zipper(T) = struct { + focus : T, + left : "List(T)", + right : "List(T)", + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // current(zipper: Zipper(T)) → void + fn current(zipper: Zipper(T)) -> void { + // TODO: Implement from .tri spec + } + + // go_down(zipper: Zipper(T)) → void + fn go_down(zipper: Zipper(T)) -> void { + // TODO: Implement from .tri spec + } + + // go_up(zipper: Zipper(T)) → void + fn go_up(zipper: Zipper(T)) -> void { + // TODO: Implement from .tri spec + } + + // go_left(zipper: Zipper(T)) → void + fn go_left(zipper: Zipper(T)) -> void { + // TODO: Implement from .tri spec + } + + // go_right(zipper: Zipper(T)) → void + fn go_right(zipper: Zipper(T)) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test current_basic_case + given input = default_input() + when result = current(input) + then result != undefined + + test go_down_basic_case + given input = default_input() + when result = go_down(input) + then result != undefined + + test go_up_basic_case + given input = default_input() + when result = go_up(input) + then result != undefined + + test go_left_basic_case + given input = default_input() + when result = go_left(input) + then result != undefined + + test go_right_basic_case + given input = default_input() + when result = go_right(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/math/bezier.t27 b/apps/website/public/t27/specs/tri/math/bezier.t27 new file mode 100644 index 0000000000..603ee70c0b --- /dev/null +++ b/apps/website/public/t27/specs/tri/math/bezier.t27 @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// De Casteljau algorithm for stable evaluation | φ² + 1/φ² = 3 | TRINITY + +module TriBezier; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Point = struct { + x : f64, + y : f64, + }; + + pub const BezierCurve = struct { + control : []Point, + degree : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // evaluate(curve: *const BezierCurve, t: f64) → Point + fn evaluate(curve: *const BezierCurve, t: f64) -> Point { + // TODO: Implement from .tri spec + } + + // derivative(curve: *const BezierCurve) → BezierCurve + fn derivative(curve: *const BezierCurve) -> BezierCurve { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // evaluate and derivative are unimplemented stubs returning void, so they + // cannot be called. The relations they must respect -- the control-point + // count, endpoint interpolation, and the first de Casteljau step -- are + // asserted against the declared fields instead. + + test degree_one_curve_has_two_control_points + // Verify: a degree-n curve carries n+1 control points + given curve = BezierCurve{.control=@constCast(&[_]Point{Point{.x=0.0,.y=0.0},Point{.x=4.0,.y=2.0}}),.degree=1} + then curve.control.len == 2 and curve.control.len == curve.degree + 1 + + test degree_two_curve_has_three_control_points + // Verify: the same relation one degree up + given curve = BezierCurve{.control=@constCast(&[_]Point{Point{.x=0.0,.y=0.0},Point{.x=1.0,.y=2.0},Point{.x=2.0,.y=0.0}}),.degree=2} + then curve.control.len == 3 and curve.control.len == curve.degree + 1 + + test curve_endpoints_are_its_first_and_last_control_points + // Verify: the endpoint interpolation property B(0) = P0, B(1) = Pn that + // evaluate must preserve, stated over the control points it reads + given curve = BezierCurve{.control=@constCast(&[_]Point{Point{.x=0.0,.y=0.0},Point{.x=1.0,.y=2.0},Point{.x=2.0,.y=0.0}}),.degree=2} + and first = curve.control[0] + and last = curve.control[curve.degree] + then @abs(last.x - 2.0) < 1e-12 and first.x < last.x + + test linear_curve_midpoint_is_the_average_of_its_control_points + // Verify: the de Casteljau step at t = 0.5 on a degree-1 curve, which is + // the value evaluate must return for this input + given p0 = Point{.x=0.0,.y=0.0} + and p1 = Point{.x=4.0,.y=2.0} + and mid_x = (p0.x + p1.x) / 2.0 + and mid_y = (p0.y + p1.y) / 2.0 + and x_ok = @abs(mid_x - 2.0) < 1e-12 + and y_ok = @abs(mid_y - 1.0) < 1e-12 + then x_ok and y_ok + diff --git a/apps/website/public/t27/specs/tri/math/constants.t27 b/apps/website/public/t27/specs/tri/math/constants.t27 new file mode 100644 index 0000000000..2245892216 --- /dev/null +++ b/apps/website/public/t27/specs/tri/math/constants.t27 @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// All constants are comptime-known | φ² + 1/φ² = 3 | TRINITY + +module TriConstants; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SystemLimits = struct { + max_path_len : usize, + max_line_len : usize, + max_args : usize, + max_env_vars : usize, + }; + + pub const SacredConstants = struct { + phi : f64, + pi : f64, + e : f64, + sqrt2 : f64, + sqrt3 : f64, + golden_ratio : f64, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // max_path_len() → usize + fn max_path_len() -> usize { + // TODO: Implement from .tri spec + } + + // max_line_len() → usize + fn max_line_len() -> usize { + // TODO: Implement from .tri spec + } + + // max_args() → usize + fn max_args() -> usize { + // TODO: Implement from .tri spec + } + + // max_env_vars() → usize + fn max_env_vars() -> usize { + // TODO: Implement from .tri spec + } + + // get_p_h_i() → f64 + fn get_p_h_i() -> f64 { + // TODO: Implement from .tri spec + } + + // get_p_i() → f64 + fn get_p_i() -> f64 { + // TODO: Implement from .tri spec + } + + // get_e() → f64 + fn get_e() -> f64 { + // TODO: Implement from .tri spec + } + + // get_s_q_r_t2() → f64 + fn get_s_q_r_t2() -> f64 { + // TODO: Implement from .tri spec + } + + // get_s_q_r_t3() → f64 + fn get_s_q_r_t3() -> f64 { + // TODO: Implement from .tri spec + } + + // get_golden_ratio() → f64 + fn get_golden_ratio() -> f64 { + // TODO: Implement from .tri spec + } + + // get_system_limits() → SystemLimits + fn get_system_limits() -> SystemLimits { + // TODO: Implement from .tri spec + } + + // get_sacred_constants() → SacredConstants + fn get_sacred_constants() -> SacredConstants { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every one of the twelve accessors above has an empty body, so no value + // can be read out of this module yet and no test here can name one. What + // the module does commit to is the width of the fields it stores them in: + // SacredConstants declares f64, and the identities below hold to 1e-12 in + // f64 and would not in f32. + + test declared_f64_width_carries_the_sacred_identity + // phi^2 + 1/phi^2 = 3 + given c = SacredConstants{ .phi = 1.61803398874989484820458683436563811772, .pi = 3.14159265358979323846264338327950288, .e = 2.7182818284590452353602874713526625, .sqrt2 = 1.41421356237309504880168872420969808, .sqrt3 = 1.73205080756887729352744634150587237, .golden_ratio = 1.61803398874989484820458683436563811772 } + and phi_sq = c.phi * c.phi + and sum = phi_sq + 1.0 / phi_sq + then @abs(sum - 3.0) < 1e-12 + + test declared_f64_width_carries_the_golden_ratio_fixed_point + // phi^2 = phi + 1 + given c = SacredConstants{ .phi = 1.61803398874989484820458683436563811772, .pi = 3.14159265358979323846264338327950288, .e = 2.7182818284590452353602874713526625, .sqrt2 = 1.41421356237309504880168872420969808, .sqrt3 = 1.73205080756887729352744634150587237, .golden_ratio = 1.61803398874989484820458683436563811772 } + then @abs(c.phi * c.phi - (c.phi + 1.0)) < 1e-12 + + test declared_f64_width_carries_the_surds + given c = SacredConstants{ .phi = 1.61803398874989484820458683436563811772, .pi = 3.14159265358979323846264338327950288, .e = 2.7182818284590452353602874713526625, .sqrt2 = 1.41421356237309504880168872420969808, .sqrt3 = 1.73205080756887729352744634150587237, .golden_ratio = 1.61803398874989484820458683436563811772 } + and sqrt2_error = @abs(c.sqrt2 * c.sqrt2 - 2.0) + and sqrt3_error = @abs(c.sqrt3 * c.sqrt3 - 3.0) + then sqrt2_error < 1e-12 and sqrt3_error < 1e-12 + + test system_limits_are_declared_unsigned_and_pointer_wide + // all four limits are usize, so none of them can be negative and each + // can address the whole span the platform can + given limits_are_usize = @FieldType(SystemLimits, "max_path_len") == usize + and args_are_usize = @FieldType(SystemLimits, "max_args") == usize + then limits_are_usize and args_are_usize + diff --git a/apps/website/public/t27/specs/tri/math/math.t27 b/apps/website/public/t27/specs/tri/math/math.t27 new file mode 100644 index 0000000000..da4e056431 --- /dev/null +++ b/apps/website/public/t27/specs/tri/math/math.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module TriMath; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "math_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/math/matrix.t27 b/apps/website/public/t27/specs/tri/math/matrix.t27 new file mode 100644 index 0000000000..0912c41865 --- /dev/null +++ b/apps/website/public/t27/specs/tri/math/matrix.t27 @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Free matrix | φ² + 1/φ² = 3 | TRINITY + +module TriMatrix; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Matrix = struct { + data : []f64, + rows : usize, + cols : usize, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(allocator: std.mem.Allocator, rows: usize, cols: usize) → Matrix + fn init(allocator: std.mem.Allocator, rows: usize, cols: usize) -> Matrix { + // TODO: Implement from .tri spec + } + + // get(m: *Matrix, row: usize, col: usize) → f64 + fn get(m: *Matrix, row: usize, col: usize) -> f64 { + // TODO: Implement from .tri spec + } + + // set(m: *Matrix, row: usize, col: usize, value: f64) → void + fn set(m: *Matrix, row: usize, col: usize, value: f64) -> void { + // TODO: Implement from .tri spec + } + + // multiply(a: *Matrix, b: *Matrix, allocator: std.mem.Allocator) → Matrix + fn multiply(a: *Matrix, b: *Matrix, allocator: std.mem.Allocator) -> Matrix { + // TODO: Implement from .tri spec + } + + // transpose(m: *Matrix, allocator: std.mem.Allocator) → Matrix + fn transpose(m: *Matrix, allocator: std.mem.Allocator) -> Matrix { + // TODO: Implement from .tri spec + } + + // identity(allocator: std.mem.Allocator, size: usize) → Matrix + fn identity(allocator: std.mem.Allocator, size: usize) -> Matrix { + // TODO: Implement from .tri spec + } + + // deinit(m: *Matrix) → void + fn deinit(m: *Matrix) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above is still `TODO: Implement`, so each one compiles + // to `@panic("not yet implemented")`: calling one aborts the test binary. + // What this module does state is the layout of Matrix and the signature + // of each operation, and that is what these tests hold to. + + test matrix_is_a_flat_f64_buffer_with_usize_dimensions + given cells = @FieldType(Matrix, "data") + then cells == []f64 + and @FieldType(Matrix, "rows") == usize + and @FieldType(Matrix, "cols") == usize + + test matrix_carries_its_own_allocator + given a = @FieldType(Matrix, "allocator") + then a == std.mem.Allocator + + test init_and_identity_take_an_allocator_and_return_nothing + given f = init + then @TypeOf(f) == fn (std.mem.Allocator) void + and @TypeOf(identity) == fn (std.mem.Allocator) void + + test get_set_transpose_and_deinit_take_one_matrix_pointer + given f = get + then @TypeOf(f) == fn (*Matrix) void + and @TypeOf(set) == fn (*Matrix) void + and @TypeOf(transpose) == fn (*Matrix) void + and @TypeOf(deinit) == fn (*Matrix) void + + // multiply is declared with ONE operand. A product needs two, so this + // records the signature as it stands rather than the one it wants. + test multiply_is_declared_with_a_single_operand + given f = multiply + then @TypeOf(f) == fn (*Matrix) void + diff --git a/apps/website/public/t27/specs/tri/math/measurement.t27 b/apps/website/public/t27/specs/tri/math/measurement.t27 new file mode 100644 index 0000000000..175e263ac7 --- /dev/null +++ b/apps/website/public/t27/specs/tri/math/measurement.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module TriMeasurement; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "measurement_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/math/polynomial.t27 b/apps/website/public/t27/specs/tri/math/polynomial.t27 new file mode 100644 index 0000000000..859167dea5 --- /dev/null +++ b/apps/website/public/t27/specs/tri/math/polynomial.t27 @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Free polynomial | φ² + 1/φ² = 3 | TRINITY + +module TriPolynomial; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Polynomial = struct { + coeffs : []f64, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(allocator: std.mem.Allocator, coeffs: []f64) → Polynomial + fn init(allocator: std.mem.Allocator, coeffs: []f64) -> Polynomial { + // TODO: Implement from .tri spec + } + + // eval(p: *Polynomial, x: f64) → f64 + fn eval(p: *Polynomial, x: f64) -> f64 { + // TODO: Implement from .tri spec + } + + // add(a: *Polynomial, b: *Polynomial, allocator: std.mem.Allocator) → Polynomial + fn add(a: *Polynomial, b: *Polynomial, allocator: std.mem.Allocator) -> Polynomial { + // TODO: Implement from .tri spec + } + + // multiply(a: *Polynomial, b: *Polynomial, allocator: std.mem.Allocator) → Polynomial + fn multiply(a: *Polynomial, b: *Polynomial, allocator: std.mem.Allocator) -> Polynomial { + // TODO: Implement from .tri spec + } + + // derivative(p: *Polynomial, allocator: std.mem.Allocator) → Polynomial + fn derivative(p: *Polynomial, allocator: std.mem.Allocator) -> Polynomial { + // TODO: Implement from .tri spec + } + + // deinit(p: *Polynomial) → void + fn deinit(p: *Polynomial) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test zero_polynomial_has_no_coefficients + // Verify: the boundary -- the zero polynomial carries an empty + // coefficient slice, which is what init() must hand back before any + // term is added + given p = Polynomial{ .coeffs = &[_]f64{}, .allocator = std.testing.allocator } + then p.coeffs.len == 0 + + test coefficients_are_stored_in_ascending_degree + // Verify: coeffs[i] is the coefficient of x^i. For 1 + 2x + 3x^2 the + // Horner evaluation at x = 2 is 1 + 2*(2 + 2*3) = 17; under the + // opposite (descending) convention the same slice would give 11 + given coeffs = [_]f64{ 1.0, 2.0, 3.0 } + and p = Polynomial{ .coeffs = @constCast(&coeffs), .allocator = std.testing.allocator } + when value = p.coeffs[0] + 2.0 * (p.coeffs[1] + 2.0 * p.coeffs[2]) + and error_ = @abs(value - 17.0) + then error_ < 1.0e-12 + + test degree_is_one_below_the_coefficient_count + // Verify: a cubic needs four slots, so derivative() must return three + given coeffs = [_]f64{ 4.0, 3.0, 2.0, 1.0 } + and p = Polynomial{ .coeffs = @constCast(&coeffs), .allocator = std.testing.allocator } + when degree = p.coeffs.len - 1 + then degree == 3 + diff --git a/apps/website/public/t27/specs/tri/math/probability.t27 b/apps/website/public/t27/specs/tri/math/probability.t27 new file mode 100644 index 0000000000..ae80a541cc --- /dev/null +++ b/apps/website/public/t27/specs/tri/math/probability.t27 @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Exponential distribution | φ² + 1/φ² = 3 | TRINITY + +module TriProbability; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // bernoulli(p: f64, rng: *std.Random.Default) → bool + fn bernoulli(p: f64, rng: *std.Random.Default) -> bool { + // TODO: Implement from .tri spec + } + + // binomial(n: usize, p: f64, rng: *std.Random.Default) → usize + fn binomial(n: usize, p: f64, rng: *std.Random.Default) -> usize { + // TODO: Implement from .tri spec + } + + // poisson(lambda: f64, rng: *std.Random.Default) → usize + fn poisson(lambda: f64, rng: *std.Random.Default) -> usize { + // TODO: Implement from .tri spec + } + + // normal(mean: f64, std_dev: f64, rng: *std.Random.Default) → f64 + fn normal(mean: f64, std_dev: f64, rng: *std.Random.Default) -> f64 { + // TODO: Implement from .tri spec + } + + // exponential(lambda: f64, rng: *std.Random.Default) → f64 + fn exponential(lambda: f64, rng: *std.Random.Default) -> f64 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test bernoulli_basic_case + given input = default_input() + when result = bernoulli(input) + then result != undefined + + test binomial_basic_case + given input = default_input() + when result = binomial(input) + then result != undefined + + test poisson_basic_case + given input = default_input() + when result = poisson(input) + then result != undefined + + test normal_basic_case + given input = default_input() + when result = normal(input) + then result != undefined + + test exponential_basic_case + given input = default_input() + when result = exponential(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/math/statistics.t27 b/apps/website/public/t27/specs/tri/math/statistics.t27 new file mode 100644 index 0000000000..98f3ec2501 --- /dev/null +++ b/apps/website/public/t27/specs/tri/math/statistics.t27 @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Pearson correlation coefficient | φ² + 1/φ² = 3 | TRINITY + +module TriStatistics; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // mean(values: []f64) → f64 + fn mean(values: []f64) -> f64 { + // TODO: Implement from .tri spec + } + + // variance(values: []f64) → f64 + fn variance(values: []f64) -> f64 { + // TODO: Implement from .tri spec + } + + // std_dev(values: []f64) → f64 + fn std_dev(values: []f64) -> f64 { + // TODO: Implement from .tri spec + } + + // median(allocator: std.mem.Allocator, values: []f64) → f64 + fn median(allocator: std.mem.Allocator, values: []f64) -> f64 { + // TODO: Implement from .tri spec + } + + // percentile(allocator: std.mem.Allocator, values: []f64, p: f64) → f64 + fn percentile(allocator: std.mem.Allocator, values: []f64, p: f64) -> f64 { + // TODO: Implement from .tri spec + } + + // correlation(x: []f64, y: []f64) → f64 + fn correlation(x: []f64, y: []f64) -> f64 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test mean_basic_case + given input = default_input() + when result = mean(input) + then result != undefined + + test variance_basic_case + given input = default_input() + when result = variance(input) + then result != undefined + + test std_dev_basic_case + given input = default_input() + when result = std_dev(input) + then result != undefined + + test median_basic_case + given input = default_input() + when result = median(input) + then result != undefined + + test percentile_basic_case + given input = default_input() + when result = percentile(input) + then result != undefined + + test correlation_basic_case + given input = default_input() + when result = correlation(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/net/async.t27 b/apps/website/public/t27/specs/tri/net/async.t27 new file mode 100644 index 0000000000..3733311296 --- /dev/null +++ b/apps/website/public/t27/specs/tri/net/async.t27 @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Promises can only be fulfilled once | φ² + 1/φ² = 3 | TRINITY + +module TriAsync; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Future(T) = struct { + completed : bool, + value : T, + }; + + pub const Promise(T) = struct { + fulfilled : bool, + future : "Future(T)", + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // new_promise() → Promise(T) + fn new_promise() -> Promise(T) { + // TODO: Implement from .tri spec + } + + // fulfill(promise: Promise(T)) → void + fn fulfill(promise: Promise(T)) -> void { + // TODO: Implement from .tri spec + } + + // await(future: Future(T)) → void + fn await(future: Future(T)) -> void { + // TODO: Implement from .tri spec + } + + // map(future: Future(T)) → void + fn map(future: Future(T)) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // new_promise, fulfill, await and map are still `TODO: Implement from .tri + // spec` and compile to `@panic`, so none of them is called here -- and the + // "fulfilled only once" rule cannot be tested until fulfill has a body. + // The pair of state bits that rule is built on is real and is what the + // tests below pin down. + + // A fresh promise: neither side has fired. The payload slot exists from + // the start, so fulfilling never has to allocate. + test fresh_promise_is_unfulfilled + given p = Promise(i64){ .fulfilled = false, .future = Future(i64){ .completed = false, .value = 0 } } + then p.fulfilled == false + and p.future.completed == false + + // A fulfilled promise: both bits set, and the value is visible through the + // future rather than stored twice. + test fulfilled_promise_completes_its_future + given p = Promise(i64){ .fulfilled = true, .future = Future(i64){ .completed = true, .value = 99 } } + then p.fulfilled == true + and p.future.completed == true + and p.future.value == 99 + + // The producer's bit and the consumer's bit are separate fields. That + // separation is what a once-only fulfill checks against: the state the + // rule guards is the promise's, not the future's. + test promise_and_future_track_state_separately + given p = Promise(i64){ .fulfilled = true, .future = Future(i64){ .completed = false, .value = 0 } } + then p.fulfilled != p.future.completed + + // Future stands alone and is generic over its payload; it does not need a + // promise wrapped around it to hold a completed value. + test future_is_generic_and_standalone + given f = Future(bool){ .completed = true, .value = true } + then f.completed == true + and f.value == true + diff --git a/apps/website/public/t27/specs/tri/net/async_stream.t27 b/apps/website/public/t27/specs/tri/net/async_stream.t27 new file mode 100644 index 0000000000..80f627f008 --- /dev/null +++ b/apps/website/public/t27/specs/tri/net/async_stream.t27 @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Lazy evaluation | φ² + 1/φ² = 3 | TRINITY + +module TriAsyncStream; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Stream(T) = struct { + generator : fn() ?T, + state : StreamState, + }; + + pub const StreamState = struct { + enum : [Ready, Pending, Done], + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // from(items: []T, allocator: std.mem.Allocator) → !Stream(T) + fn from(items: []T, allocator: std.mem.Allocator) -> !Stream(T) { + // TODO: Implement from .tri spec + } + + // map(stream: Stream(T), fn: fn(T) U) → Stream(U) + fn map(stream: Stream(T), fn: fn(T) U) -> Stream(U) { + // TODO: Implement from .tri spec + } + + // filter(stream: Stream(T), predicate: fn(T) bool) → Stream(T) + fn filter(stream: Stream(T), predicate: fn(T) bool) -> Stream(T) { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // from()/map()/filter() have no body yet, so they panic when called and the + // lazy pipeline -- from(xs) mapped then filtered -- cannot be stated. What + // the module states is its two types. + // + // Note on StreamState: the `enum : [...]` line now lowers to a real enum, + // so Ready, Pending and Done are mutually exclusive tags. A value of this + // type is exactly one of the three. Both the count and the tag-ness hold. + + test stream_state_declares_the_three_stream_kinds + // Ready, Pending, Done + given kinds = std.meta.fields(StreamState).len + then kinds == 3 + + test stream_state_kinds_are_mutually_exclusive_tags + // A real tag needs at least one bit to tell Pending from Done; three + // states need two, and the enum carries exactly that -- a u2 + // discriminant, distinct per state and ordered Ready, Pending, Done. + given ready = @intFromEnum(StreamState.Ready) + and pending = @intFromEnum(StreamState.Pending) + and done = @intFromEnum(StreamState.Done) + and tag = @typeInfo(StreamState).@"enum".tag_type + then ready == 0 and pending == 1 and done == 2 + and tag == u2 + + test stream_pairs_a_generator_with_a_state + // Verify: the two declared field names, which is what makes the stream + // resumable rather than a plain slice + then (@hasField(Stream(u8), "generator")) and (@hasField(Stream(u8), "state")) + + test stream_is_generic_over_its_element_type + // Verify: T is a real parameter, so instantiations do not collapse + then Stream(u8) != Stream(i64) + + test generator_signals_exhaustion_by_returning_null + // Verify: the generator yields ?T, not T -- null is the only end-of- + // stream marker the type provides, which is what Done would otherwise + // have to encode + given Gen = @FieldType(Stream(u8), "generator") + and ret = @typeInfo(Gen).@"fn".return_type.? + then ret == ?u8 + diff --git a/apps/website/public/t27/specs/tri/net/channel.t27 b/apps/website/public/t27/specs/tri/net/channel.t27 new file mode 100644 index 0000000000..761ac12f5e --- /dev/null +++ b/apps/website/public/t27/specs/tri/net/channel.t27 @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Go-like channel semantics | φ² + 1/φ² = 3 | TRINITY + +module TriChannel; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Channel(T) = struct { + capacity : usize, + sender_count : usize, + receiver_count : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // new_channel(capacity: usize) → Channel(T) + fn new_channel(capacity: usize) -> Channel(T) { + // TODO: Implement from .tri spec + } + + // send(channel: Channel(T), value: T) → bool + fn send(channel: Channel(T), value: T) -> bool { + // TODO: Implement from .tri spec + } + + // recv(channel: Channel(T)) → Maybe(T) + fn recv(channel: Channel(T)) -> Maybe(T) { + // TODO: Implement from .tri spec + } + + // close(channel: Channel(T)) → void + fn close(channel: Channel(T)) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test new_channel_basic_case + given input = default_input() + when result = new_channel(input) + then result != undefined + + test send_basic_case + given input = default_input() + when result = send(input) + then result != undefined + + test recv_basic_case + given input = default_input() + when result = recv(input) + then result != undefined + + test close_basic_case + given input = default_input() + when result = close(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/net/cloud.t27 b/apps/website/public/t27/specs/tri/net/cloud.t27 new file mode 100644 index 0000000000..ee06b1e6fa --- /dev/null +++ b/apps/website/public/t27/specs/tri/net/cloud.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// > | φ² + 1/φ² = 3 | TRINITY + +module TriCloud; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "cloud_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/net/http.t27 b/apps/website/public/t27/specs/tri/net/http.t27 new file mode 100644 index 0000000000..9cd3a75abc --- /dev/null +++ b/apps/website/public/t27/specs/tri/net/http.t27 @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Standard HTTP status codes | φ² + 1/φ² = 3 | TRINITY + +module TriHttp; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const HttpMethod = struct { + enum : [GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS], + }; + + pub const HttpStatus = struct { + code : u16, + reason : []const u8, + }; + + pub const Url = struct { + scheme : ?[]const u8, + host : ?[]const u8, + port : ?u16, + path : []const u8, + query : ?[]const u8, + fragment : ?[]const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // method_to_string(method: HttpMethod) → []const u8 + fn method_to_string(method: HttpMethod) -> []const u8 { + // TODO: Implement from .tri spec + } + + // status_from_code(code: u16) → HttpStatus + fn status_from_code(code: u16) -> HttpStatus { + // TODO: Implement from .tri spec + } + + // is_success(code: u16) → bool + fn is_success(code: u16) -> bool { + // TODO: Implement from .tri spec + } + + // is_redirect(code: u16) → bool + fn is_redirect(code: u16) -> bool { + // TODO: Implement from .tri spec + } + + // is_client_error(code: u16) → bool + fn is_client_error(code: u16) -> bool { + // TODO: Implement from .tri spec + } + + // is_server_error(code: u16) → bool + fn is_server_error(code: u16) -> bool { + // TODO: Implement from .tri spec + } + + // parse_url(allocator: std.mem.Allocator, url: []const u8) → !Url + fn parse_url(allocator: std.mem.Allocator, url: []const u8) -> !Url { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above is an unimplemented stub: each one emits + // `@panic("not yet implemented")`, so calling any of them aborts the test + // binary rather than failing a test. The assertions below are therefore + // about the representation the module actually declares. + + test a_status_is_a_numeric_code_plus_a_reason_phrase + // RFC 9110 status-line: a three-digit code and a textual reason. u16 + // covers the whole 100..599 range with room to spare, and the reason + // is borrowed, not owned -- status_from_code can hand back a literal. + then @typeInfo(HttpStatus).@"struct".fields.len == 2 + and @FieldType(HttpStatus, "code") == u16 + and @FieldType(HttpStatus, "reason") == []const u8 + and std.math.maxInt(u16) >= 599 + + test the_class_predicates_partition_the_code_space + // is_success / is_redirect / is_client_error / is_server_error are the + // 2xx / 3xx / 4xx / 5xx classes: four disjoint hundreds-blocks, so a + // code belongs to at most one of them and 200/301/404/500 land apart. + given ok = 200 / 100 + given moved = 301 / 100 + given not_found = 404 / 100 + given server = 500 / 100 + then ok == 2 + and moved == 3 + and not_found == 4 + and server == 5 + + test every_url_component_but_the_path_is_optional + // A bare "/index.html" is a legal Url: no scheme, host, port, query or + // fragment. The path is the one field that is always present. + then @typeInfo(Url).@"struct".fields.len == 6 + and @FieldType(Url, "path") == []const u8 + and @typeInfo(@FieldType(Url, "scheme")).optional.child == []const u8 + and @typeInfo(@FieldType(Url, "host")).optional.child == []const u8 + and @typeInfo(@FieldType(Url, "port")).optional.child == u16 + and @typeInfo(@FieldType(Url, "query")).optional.child == []const u8 + and @typeInfo(@FieldType(Url, "fragment")).optional.child == []const u8 + + test a_path_only_url_is_constructible + // parse_url is a stub, so this builds the value directly: the point is + // that the declared type admits a relative reference at all. + given rel = Url{ .scheme = null, .host = null, .port = null, .path = "/index.html", .query = null, .fragment = null } + then rel.scheme == null + and rel.port == null + and std.mem.eql(u8, rel.path, "/index.html") + + test http_method_lists_its_verbs + // The spec used to declare `enum : ,` -- an empty variant list -- so + // HttpMethod emitted as `struct { enum: void }` and could not represent + // GET, POST or anything else; method_to_string had nothing to switch + // on. (The old assertion here was `@typeInfo(...).@"struct".fields.len + // >= 1`, which passed on that marker field and so read green for a type + // with no verbs in it.) The seven verbs were recovered from the upstream + // spec this file was converted from: trinity-fpga tri_http.tri:7. + then @typeInfo(HttpMethod).@"enum".fields.len == 7 + and std.mem.eql(u8, @typeInfo(HttpMethod).@"enum".fields[0].name, "GET") + and std.mem.eql(u8, @typeInfo(HttpMethod).@"enum".fields[1].name, "POST") + and std.mem.eql(u8, @typeInfo(HttpMethod).@"enum".fields[6].name, "OPTIONS") + diff --git a/apps/website/public/t27/specs/tri/net/net.t27 b/apps/website/public/t27/specs/tri/net/net.t27 new file mode 100644 index 0000000000..4de81e61a8 --- /dev/null +++ b/apps/website/public/t27/specs/tri/net/net.t27 @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Supports both IPv4 and IPv6 | φ² + 1/φ² = 3 | TRINITY + +module TriNet; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const IpAddress = struct { + is_v6 : bool, + bytes : [16]u8, + }; + + pub const SocketAddr = struct { + ip : IpAddress, + port : u16, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // parse_ip(addr: []const u8) → ?IpAddress + fn parse_ip(addr: []const u8) -> ?IpAddress { + // TODO: Implement from .tri spec + } + + // is_localhost(addr: IpAddress) → bool + fn is_localhost(addr: IpAddress) -> bool { + // TODO: Implement from .tri spec + } + + // is_valid_port(port: u16) → bool + fn is_valid_port(port: u16) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // parse_ip(), is_localhost() and is_valid_port() have no body yet, so + // they panic when called. The two address types are what this module + // states, and the "supports both IPv4 and IPv6" claim lives in them. + + test ip_address_holds_an_ipv4_loopback + given loopback = IpAddress{.is_v6=false,.bytes=[_]u8{127,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0}} + then !loopback.is_v6 + and loopback.bytes[0] == 127 + and loopback.bytes[3] == 1 + + test ip_address_reserves_sixteen_bytes_so_an_ipv6_address_fits + // ::1 -- the widest address the type must carry + given v6_loopback = IpAddress{.is_v6=true,.bytes=[_]u8{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}} + then v6_loopback.is_v6 + and v6_loopback.bytes.len == 16 + and v6_loopback.bytes[15] == 1 + + test socket_addr_port_spans_the_whole_u16_range + given any_ip = IpAddress{.is_v6=false,.bytes=[_]u8{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}} + and lowest = SocketAddr{.ip=any_ip,.port=0} + and highest = SocketAddr{.ip=any_ip,.port=65535} + then lowest.port == 0 + and highest.port == 65535 + and !highest.ip.is_v6 + diff --git a/apps/website/public/t27/specs/tri/net/url.t27 b/apps/website/public/t27/specs/tri/net/url.t27 new file mode 100644 index 0000000000..f54f4acaec --- /dev/null +++ b/apps/website/public/t27/specs/tri/net/url.t27 @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// RFC 3986 compliant | φ² + 1/φ² = 3 | TRINITY + +module TriUrl; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Url = struct { + scheme : []const u8, + host : []const u8, + port : ?u16, + path : []const u8, + query : []const u8, + fragment : []const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // parse(str: []const u8, allocator: std.mem.Allocator) → !Url + fn parse(str: []const u8, allocator: std.mem.Allocator) -> !Url { + // TODO: Implement from .tri spec + } + + // encode(component: []const u8, allocator: std.mem.Allocator) → ![]u8 + fn encode(component: []const u8, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // decode(encoded: []const u8, allocator: std.mem.Allocator) → ![]u8 + fn decode(encoded: []const u8, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // to_string(url: Url, allocator: std.mem.Allocator) → ![]u8 + fn to_string(url: Url, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // parse/encode/decode/to_string are unimplemented stubs returning void, so + // no round trip can be asserted yet. What the module does declare -- the Url + // component fields, and the optional port RFC 3986 allows to be absent -- is + // tested here. + + test url_without_a_port_leaves_it_absent + // Verify: port is optional, so a URL that relies on the scheme default + // carries no port at all rather than a sentinel value + given u = Url{.scheme="https",.host="example.com",.port=null,.path="/",.query="",.fragment=""} + then u.port == null + + test url_with_an_explicit_port_carries_it + // Verify: an explicit authority port is retained + given u = Url{.scheme="http",.host="example.com",.port=8080,.path="/a",.query="",.fragment=""} + then u.port.? == 8080 + + test port_field_spans_the_full_range + // Verify: the declared u16 holds 65535, the highest legal TCP port + given u = Url{.scheme="http",.host="h",.port=65535,.path="/",.query="",.fragment=""} + then u.port.? == 65535 + + test empty_query_and_fragment_are_representable + // Verify: the optional RFC 3986 components are empty strings, not absent + given u = Url{.scheme="https",.host="example.com",.port=null,.path="/index.html",.query="",.fragment=""} + then u.query.len == 0 and u.fragment.len == 0 + + test components_keep_the_values_they_were_built_from + // Verify: Url has the declared field names and stores each component + given u = Url{.scheme="https",.host="example.com",.port=null,.path="/docs",.query="q=1",.fragment="top"} + and scheme_kept = std.mem.eql(u8, u.scheme, "https") + and query_kept = std.mem.eql(u8, u.query, "q=1") + then scheme_kept and query_kept + diff --git a/apps/website/public/t27/specs/tri/pipeline/batch_runner.t27 b/apps/website/public/t27/specs/tri/pipeline/batch_runner.t27 new file mode 100644 index 0000000000..5ad1553ec8 --- /dev/null +++ b/apps/website/public/t27/specs/tri/pipeline/batch_runner.t27 @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// CLI entry | φ² + 1/φ² = 3 | TRINITY + +module BatchRunner; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const CompileStatus = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/batch_runner.tri:23. The converter dropped bare `- name` bullets. + enum : [pass, ast_fail, compile_fail, gen_fail, lint_fail, timeout, skipped], + }; + + pub const FilterMode = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/batch_runner.tri:33. The converter dropped bare `- name` bullets. + enum : [all, lint_pass, lint_fail, changed_only], + }; + + pub const PipelineResult = struct { + spec_path : String, + success : Bool, + status : CompileStatus, + duration_ns : Int, + error_msg : String, + }; + + pub const BatchConfig = struct { + parallel : Int, + filter : FilterMode, + directory : String, + dry_run : Bool, + timeout_seconds : Int, + }; + + pub const BatchReport = struct { + total_specs : Int, + filtered_specs : Int, + passed : Int, + failed : Int, + skipped : Int, + total_duration_ns : Int, + parallel_workers : Int, + failures : List(FailureEntry), + }; + + pub const FailureEntry = struct { + spec : String, + status : CompileStatus, + error_msg : String, + }; + + pub const ThreadSafeResults = struct { + items : List(PipelineResult), + lock_active : Bool, + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // ═══════════════════════════════════════════════════════════ + // TDD: Invariants (from .tri constraints) + // ═══════════════════════════════════════════════════════════ + + invariant batch_runner_constraint_0 + given input = valid_input() + then true // Thread-safe result collection via mutex + + invariant batch_runner_constraint_1 + given input = valid_input() + then true // Per-spec failures do NOT block other specs + + invariant batch_runner_constraint_2 + given input = valid_input() + then true // Verilog specs skip ast-check (no zig output) + + invariant batch_runner_constraint_3 + given input = valid_input() + then true // Output to /tmp/tri-batch/ to avoid polluting generated/ + + invariant batch_runner_constraint_4 + given input = valid_input() + then true // Maximum 20 failure details in report + + invariant batch_runner_constraint_5 + given input = valid_input() + then true // Sacred formula V = phi * (rate/100)^2 in every report + + invariant batch_runner_constraint_6 + given input = valid_input() + then true // name: "extractStem from path" + + invariant batch_runner_constraint_7 + given input = valid_input() + then true // name: "parseFilterMode lint:pass" + + invariant batch_runner_constraint_8 + given input = valid_input() + then true // name: "scanSpecs finds files" + + invariant batch_runner_constraint_9 + given input = valid_input() + then true // name: "ThreadSafeResults append" + + invariant batch_runner_constraint_10 + given input = valid_input() + then true // name: "detectVerilog false for missing file" + diff --git a/apps/website/public/t27/specs/tri/pipeline/builder.t27 b/apps/website/public/t27/specs/tri/pipeline/builder.t27 new file mode 100644 index 0000000000..c1dc26ee4f --- /dev/null +++ b/apps/website/public/t27/specs/tri/pipeline/builder.t27 @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// O(1) amortized append | φ² + 1/φ² = 3 | TRINITY + +module TriBuilder; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Builder(T) = struct { + items : []T, + capacity : usize, + len : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(capacity: usize, allocator: std.mem.Allocator) → !Builder(T) + fn init(capacity: usize, allocator: std.mem.Allocator) -> !Builder(T) { + // TODO: Implement from .tri spec + } + + // empty() → Builder(T) + fn empty() -> Builder(T) { + // TODO: Implement from .tri spec + } + + // append(builder: *Builder(T), item: T, allocator: std.mem.Allocator) → !void + fn append(builder: *Builder(T), item: T, allocator: std.mem.Allocator) -> !void { + // TODO: Implement from .tri spec + } + + // append_slice(builder: *Builder(T), slice: []const T, allocator: std.mem.Allocator) → !void + fn append_slice(builder: *Builder(T), slice: []const T, allocator: std.mem.Allocator) -> !void { + // TODO: Implement from .tri spec + } + + // len(builder: Builder(T)) → usize + fn len(builder: Builder(T)) -> usize { + // TODO: Implement from .tri spec + } + + // capacity(builder: Builder(T)) → usize + fn capacity(builder: Builder(T)) -> usize { + // TODO: Implement from .tri spec + } + + // finish(builder: Builder(T), allocator: std.mem.Allocator) → ![]T + fn finish(builder: Builder(T), allocator: std.mem.Allocator) -> ![]T { + // TODO: Implement from .tri spec + } + + // reset(builder: *Builder(T)) → void + fn reset(builder: *Builder(T)) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test init_basic_case + given input = default_input() + when result = init(input) + then result != undefined + + test empty_basic_case + given input = default_input() + when result = empty(input) + then result != undefined + + test append_basic_case + given input = default_input() + when result = append(input) + then result != undefined + + test append_slice_basic_case + given input = default_input() + when result = append_slice(input) + then result != undefined + + test len_basic_case + given input = default_input() + when result = len(input) + then result != undefined + + test capacity_basic_case + given input = default_input() + when result = capacity(input) + then result != undefined + + test finish_basic_case + given input = default_input() + when result = finish(input) + then result != undefined + + test reset_basic_case + given input = default_input() + when result = reset(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/pipeline/cloud_orchestrator.t27 b/apps/website/public/t27/specs/tri/pipeline/cloud_orchestrator.t27 new file mode 100644 index 0000000000..c594023902 --- /dev/null +++ b/apps/website/public/t27/specs/tri/pipeline/cloud_orchestrator.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module CloudOrchestrator; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "cloud_orchestrator_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/pipeline/codegen.t27 b/apps/website/public/t27/specs/tri/pipeline/codegen.t27 new file mode 100644 index 0000000000..bad3dbf6f9 --- /dev/null +++ b/apps/website/public/t27/specs/tri/pipeline/codegen.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module TestSpec; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const ParseError = struct { + message : String, + line : Int, + column : Int, + source : String, + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "codegen_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/pipeline/pipeline.t27 b/apps/website/public/t27/specs/tri/pipeline/pipeline.t27 new file mode 100644 index 0000000000..354b354068 --- /dev/null +++ b/apps/website/public/t27/specs/tri/pipeline/pipeline.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module TriPipeline; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "pipeline_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/pipeline/pipeline_parallel.t27 b/apps/website/public/t27/specs/tri/pipeline/pipeline_parallel.t27 new file mode 100644 index 0000000000..a72f431a64 --- /dev/null +++ b/apps/website/public/t27/specs/tri/pipeline/pipeline_parallel.t27 @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Empty DAG (0 jobs) returns completed immediately | φ² + 1/φ² = 3 | TRINITY + +module TriPipelineParallel; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const JobStatus = struct { + enum : [pending, running, completed, failed, skipped], + }; + + pub const DagJob = struct { + id : U8, // 0..MAXJOBS-1 + command : String, // tri subcommand to execute + args : String, // arguments string + group_id : U8, // which execution group + status : JobStatus, + exit_code : U8, // process exit code + duration_ms : u64, + }; + + pub const GroupResult = struct { + group_id : u8, + total : u8, + completed : u8, + failed : u8, + duration_ms : u64, + }; + + pub const PipelineDAG = struct { + jobs : List, // max 16 + job_count : u8, + groups : List, // max 8 + group_count : u8, + status : JobStatus, // overall DAG status + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "pipeline_parallel_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/pipeline/spec_parser.t27 b/apps/website/public/t27/specs/tri/pipeline/spec_parser.t27 new file mode 100644 index 0000000000..57e0732ca7 --- /dev/null +++ b/apps/website/public/t27/specs/tri/pipeline/spec_parser.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module TriSpecParser; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "spec_parser_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/pipeline/spec_writer.t27 b/apps/website/public/t27/specs/tri/pipeline/spec_writer.t27 new file mode 100644 index 0000000000..e065a91839 --- /dev/null +++ b/apps/website/public/t27/specs/tri/pipeline/spec_writer.t27 @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// []const u8 | φ² + 1/φ² = 3 | TRINITY + +module SpecWriter; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SpecField = struct { + field_type : []const u8, + }; + + pub const SpecType = struct { + }; + + pub const SpecBehavior = struct { + inputs : []const SpecField, + output : []const u8, + steps : []const []const u8, + }; + + pub const SpecTemplate = struct { + module_name : []const u8, + types : []const SpecType, + behaviors : []const SpecBehavior, + }; + + pub const WriteResult = struct { + spec_path : []const u8, + generated_path : []const u8, + compile_ok : "bool", + error_msg : ?[]const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "spec_writer_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/pipeline/workflow.t27 b/apps/website/public/t27/specs/tri/pipeline/workflow.t27 new file mode 100644 index 0000000000..ed0f4321a1 --- /dev/null +++ b/apps/website/public/t27/specs/tri/pipeline/workflow.t27 @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module Workflow; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const WorkflowStep = struct { + command : String, + depends_on : []const []const u8, + condition : ?[]const u8, + }; + + pub const Workflow = struct { + version : String, + steps : []const WorkflowStep, + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "workflow_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/pipeline/workflow_executor.t27 b/apps/website/public/t27/specs/tri/pipeline/workflow_executor.t27 new file mode 100644 index 0000000000..62737e64c3 --- /dev/null +++ b/apps/website/public/t27/specs/tri/pipeline/workflow_executor.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module WorkflowExecutor; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "workflow_executor_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/pipeline/workflow_parser.t27 b/apps/website/public/t27/specs/tri/pipeline/workflow_parser.t27 new file mode 100644 index 0000000000..21ad38d08c --- /dev/null +++ b/apps/website/public/t27/specs/tri/pipeline/workflow_parser.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module WorkflowParser; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "workflow_parser_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/search/aho_corasick.t27 b/apps/website/public/t27/specs/tri/search/aho_corasick.t27 new file mode 100644 index 0000000000..fd2190b39f --- /dev/null +++ b/apps/website/public/t27/specs/tri/search/aho_corasick.t27 @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// BFS to build failure links | φ² + 1/φ² = 3 | TRINITY + +module TriAhoCorasick; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const ACTrieNode = struct { + children : [256]?*ACTrieNode, + fail : *ACTrieNode, + output : [][]const u8, + char : u8, + }; + + pub const ACAutomaton = struct { + root : *ACTrieNode, + patterns : [][]const u8, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // build(allocator: std.mem.Allocator, patterns: [][]const u8) → ACAutomaton + fn build(allocator: std.mem.Allocator, patterns: [][]const u8) -> ACAutomaton { + // TODO: Implement from .tri spec + } + + // search(ac: *ACAutomaton, text: []const u8) → []Match + fn search(ac: *ACAutomaton, text: []const u8) -> []Match { + // TODO: Implement from .tri spec + } + + // deinit(ac: *ACAutomaton) → void + fn deinit(ac: *ACAutomaton) -> void { + // TODO: Implement from .tri spec + } + + // match() → void + fn match() -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above is still `TODO: Implement`, so each one compiles + // to `@panic("not yet implemented")`: calling one aborts the test binary. + // What this module does state is the layout of the trie and the signature + // of each operation, and that is what these tests hold to. + + test trie_node_branches_on_every_byte_value + given c = @FieldType(ACTrieNode, "children") + then c == [256]?*ACTrieNode + and @FieldType(ACTrieNode, "char") == u8 + + // The failure link is a plain pointer, not an optional: the root's link + // points at the root itself, so there is no node without one. + test failure_link_is_never_absent + given f = @FieldType(ACTrieNode, "fail") + then f == *ACTrieNode + + // output holds pattern indices, so one node can end several patterns -- + // that is what lets a single pass report overlapping matches. + test a_node_can_end_more_than_one_pattern + given o = @FieldType(ACTrieNode, "output") + then o == []usize + + test automaton_owns_a_root_the_patterns_and_an_allocator + given r = @FieldType(ACAutomaton, "root") + then r == *ACTrieNode + and @FieldType(ACAutomaton, "patterns") == [][]const u8 + and @FieldType(ACAutomaton, "allocator") == std.mem.Allocator + + test build_takes_an_allocator_and_returns_nothing + given f = build + then @TypeOf(f) == fn (std.mem.Allocator) void + + // search is given the automaton with no text to run it over, and match + // takes no arguments at all -- the stub test that used to sit here called + // it as `match(input)`, which it has never accepted. These record the + // signatures as they stand. + test search_and_deinit_take_one_automaton_pointer + given f = search + then @TypeOf(f) == fn (*ACAutomaton) void + and @TypeOf(deinit) == fn (*ACAutomaton) void + + test match_is_declared_with_no_arguments + given f = match + then @TypeOf(f) == fn () void + diff --git a/apps/website/public/t27/specs/tri/search/bloom_filter.t27 b/apps/website/public/t27/specs/tri/search/bloom_filter.t27 new file mode 100644 index 0000000000..e90c8f0828 --- /dev/null +++ b/apps/website/public/t27/specs/tri/search/bloom_filter.t27 @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// May return true for items not added (false positive), never false negative | φ² + 1/φ² = 3 | TRINITY + +module TriBloomFilter; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const BloomFilter = struct { + bits : []bool, + hash_count : usize, + size : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(size: usize, hash_count: usize) → BloomFilter + fn init(size: usize, hash_count: usize) -> BloomFilter { + // TODO: Implement from .tri spec + } + + // add(filter: *BloomFilter, item: []const u8) → void + fn add(filter: *BloomFilter, item: []const u8) -> void { + // TODO: Implement from .tri spec + } + + // contains(filter: *const BloomFilter, item: []const u8) → bool + fn contains(filter: *const BloomFilter, item: []const u8) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // init, add and contains have empty bodies, so the no-false-negative claim + // in the header cannot be exercised yet -- that needs a real add/contains + // round trip. What the module has decided is the storage. + + test the_bit_array_is_one_byte_per_bit + // bits is a bool slice, not a packed bitset, so an m-bit filter costs + // m bytes -- eight times what the packed form would need + given bits_type = @FieldType(BloomFilter, "bits") + and bool_bytes = @sizeOf(bool) + then bits_type == []bool and bool_bytes == 1 + + test size_and_hash_count_are_unsigned + given size_type = @FieldType(BloomFilter, "size") + and hash_count_type = @FieldType(BloomFilter, "hash_count") + then size_type == usize and hash_count_type == usize + diff --git a/apps/website/public/t27/specs/tri/search/boyer_moore.t27 b/apps/website/public/t27/specs/tri/search/boyer_moore.t27 new file mode 100644 index 0000000000..4e655bc0b0 --- /dev/null +++ b/apps/website/public/t27/specs/tri/search/boyer_moore.t27 @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Skip sections of text using bad character rule | φ² + 1/φ² = 3 | TRINITY + +module TriBoyerMoore; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const BMBadChar = struct { + table : [256]usize, + pattern_len : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // build_bad_char(pattern: []const u8) → BMBadChar + fn build_bad_char(pattern: []const u8) -> BMBadChar { + // TODO: Implement from .tri spec + } + + // search(text: []const u8, pattern: []const u8, bad_char: *const BMBadChar) → []usize + fn search(text: []const u8, pattern: []const u8, bad_char: *const BMBadChar) -> []usize { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Both functions above are still `TODO: Implement`, so each compiles to + // `@panic("not yet implemented")`: calling one aborts the test binary. + // What this module does state is the shape of the bad-character table and + // the signature of each operation, plus the rule named in the header, + // and that is what these tests hold to. + + test bad_char_table_has_one_entry_per_byte_value + given t = @FieldType(BMBadChar, "table") + then t == [256]usize + and @FieldType(BMBadChar, "pattern_len") == usize + + // The bad character rule: on a mismatch against text byte c, shift the + // pattern so c lines up with its LAST occurrence in the pattern, i.e. by + // pattern_len - 1 - lastIndexOf(c); a byte absent from the pattern shifts + // the whole pattern_len. For "abca": a (97) last occurs at 3, b (98) at 1, + // c (99) at 2. Byte values are spelled as numbers because the t27 lexer + // drops the quotes off a character literal. + test shift_for_a_pattern_byte_is_the_distance_from_its_last_occurrence + given pattern = "abca" + then pattern.len - 1 - std.mem.lastIndexOfScalar(u8, pattern, 97).? == 0 + and pattern.len - 1 - std.mem.lastIndexOfScalar(u8, pattern, 98).? == 2 + and pattern.len - 1 - std.mem.lastIndexOfScalar(u8, pattern, 99).? == 1 + + test a_byte_absent_from_the_pattern_shifts_the_whole_pattern + given pattern = "abca" + then std.mem.lastIndexOfScalar(u8, pattern, 122) == null + and pattern.len == 4 + + // Neither signature can express its operation: build_bad_char has nowhere + // to put the table it builds, and search is given text with no pattern and + // no table to search it with. These record the signatures as they stand. + test build_bad_char_and_search_each_take_one_byte_slice_and_return_nothing + given f = build_bad_char + then @TypeOf(f) == fn ([]const u8) void + and @TypeOf(search) == fn ([]const u8) void + diff --git a/apps/website/public/t27/specs/tri/search/knuth_morris_pratt.t27 b/apps/website/public/t27/specs/tri/search/knuth_morris_pratt.t27 new file mode 100644 index 0000000000..33f9327941 --- /dev/null +++ b/apps/website/public/t27/specs/tri/search/knuth_morris_pratt.t27 @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// O(n + m) time where n=text length, m=pattern length | φ² + 1/φ² = 3 | TRINITY + +module TriKmp; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const KMPPrefix = struct { + table : []usize, + pattern : []const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // build_prefix(pattern: []const u8, allocator: std.mem.Allocator) → !KMPPrefix + fn build_prefix(pattern: []const u8, allocator: std.mem.Allocator) -> !KMPPrefix { + // TODO: Implement from .tri spec + } + + // search(text: []const u8, prefix: *const KMPPrefix) → []usize + fn search(text: []const u8, prefix: *const KMPPrefix) -> []usize { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // build_prefix and search are both unimplemented stubs: each one emits + // `@panic("not yet implemented")`, so calling either aborts the test + // binary rather than failing a test. (search's signature is also truncated + // to `search(text: []const u8)`, with nowhere to receive the pattern.) The + // tables below are written out by hand and checked against the border + // property that build_prefix has to satisfy. + + test the_table_has_one_slot_per_pattern_byte + // table[i] is the longest proper border of pattern[0..i+1], so the two + // slices are the same length and the table is indexed by byte, not by + // match position. + then @typeInfo(KMPPrefix).@"struct".fields.len == 2 + and @FieldType(KMPPrefix, "table") == []usize + and @FieldType(KMPPrefix, "pattern") == []const u8 + + test the_border_table_of_ababa + // "ababa": no border at 'a' or 'ab', then "aba" borders on "a" (1), + // "abab" on "ab" (2), "ababa" on "aba" (3). This is what build_prefix + // must produce for this pattern. + given table = [_]usize{ 0, 0, 1, 2, 3 } + given kmp = KMPPrefix{ .table = @constCast(&table), .pattern = "ababa" } + then kmp.table.len == kmp.pattern.len + and kmp.table[0] == 0 + and kmp.table[2] == 1 + and kmp.table[4] == 3 + + test a_border_is_proper_so_an_entry_never_reaches_its_own_length + // table[i] <= i for every i, with equality impossible at i = 0. The + // all-same pattern "aaaa" is the extreme case that saturates the + // bound, and even there the last entry is 3, not 4. + given table = [_]usize{ 0, 1, 2, 3 } + given kmp = KMPPrefix{ .table = @constCast(&table), .pattern = "aaaa" } + then kmp.table[0] == 0 + and kmp.table[1] <= 1 + and kmp.table[3] == 3 + and kmp.table[3] < kmp.pattern.len + + test a_fallback_chain_strictly_decreases_so_the_scan_terminates + // On a mismatch the search follows j = table[j-1] repeatedly. Each hop + // lands on a strictly smaller index, which is why the whole scan is + // O(n + m) rather than quadratic: "ababa" falls 3 -> 1 -> 0. + given table = [_]usize{ 0, 0, 1, 2, 3 } + given first_hop = table[4] + given second_hop = table[first_hop - 1] + then first_hop == 3 + and second_hop == 1 + and second_hop < first_hop + and table[second_hop - 1] == 0 + diff --git a/apps/website/public/t27/specs/tri/search/match.t27 b/apps/website/public/t27/specs/tri/search/match.t27 new file mode 100644 index 0000000000..754b87c9bd --- /dev/null +++ b/apps/website/public/t27/specs/tri/search/match.t27 @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Check exhaustiveness at compile time when possible | φ² + 1/φ² = 3 | TRINITY + +module []const u8; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Match = struct { + matched : bool, + captures : []MatchCapture, + }; + + pub const MatchCapture = struct { + value : []const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // match_literal(input: []const u8, pattern: []const u8) → bool + fn match_literal(input: []const u8, pattern: []const u8) -> bool { + // TODO: Implement from .tri spec + } + + // match_type(type_name: []const u8, value: Any) → bool + fn match_type(type_name: []const u8, value: Any) -> bool { + // TODO: Implement from .tri spec + } + + // exhaustive(cases: [][]const u8, handled: []bool) → bool + fn exhaustive(cases: [][]const u8, handled: []bool) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // match_literal/match_type/exhaustive are unimplemented stubs -- each emits + // `@panic("not yet implemented")`, so no matching can be exercised. The + // module does state what a match result looks like, and that is testable. + + test a_match_reports_success_separately_from_captures + // matched is its own bool: zero captures does not mean no match, which + // is what a caller checking captures.len alone would conclude. + then @FieldType(Match, "matched") == bool + and @FieldType(Match, "captures") == []MatchCapture + + test a_capture_is_its_text_and_nothing_else + // No start/end offsets are declared, so a caller cannot locate a + // capture within the input -- only read its bytes. + then @typeInfo(MatchCapture).@"struct".fields.len == 1 + and @FieldType(MatchCapture, "value") == []const u8 + + test exhaustiveness_is_not_carried_in_the_result + // The header promises a compile-time exhaustiveness check, but Match + // has no field recording it: whatever exhaustive() decides is not + // reported through this type. + then @hasField(Match, "exhaustive") == false + and @typeInfo(Match).@"struct".fields.len == 2 + diff --git a/apps/website/public/t27/specs/tri/search/pattern.t27 b/apps/website/public/t27/specs/tri/search/pattern.t27 new file mode 100644 index 0000000000..6fc2ada683 --- /dev/null +++ b/apps/website/public/t27/specs/tri/search/pattern.t27 @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Standard glob pattern syntax | φ² + 1/φ² = 3 | TRINITY + +module TriPattern; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const MatchResult = struct { + matches : bool, + captured : []const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // glob_match(pattern: []const u8, text: []const u8) → bool + fn glob_match(pattern: []const u8, text: []const u8) -> bool { + // TODO: Implement from .tri spec + } + + // wildcard_match(pattern: []const u8, text: []const u8) → bool + fn wildcard_match(pattern: []const u8, text: []const u8) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // glob_match and wildcard_match are still `TODO: Implement from .tri spec` + // and compile to `@panic`, so neither is called here. What exists to test + // is MatchResult: a verdict and a capture, kept apart so that a match with + // nothing captured is distinguishable from no match at all. + + // A hit with a capture. + test match_result_hit_with_capture + given r = MatchResult{ .matches = true, .captured = "src/main.zig" } + then r.matches == true + and std.mem.eql(u8, r.captured, "src/main.zig") + + // A miss carries no capture; the empty slice is the absence. + test match_result_miss_is_empty + given r = MatchResult{ .matches = false, .captured = "" } + then r.matches == false + and r.captured.len == 0 + + // The verdict is not derived from the capture: a pattern with no capture + // group can still match, so an empty capture does not mean a miss. + test empty_capture_does_not_imply_a_miss + given hit = MatchResult{ .matches = true, .captured = "" } + and miss = MatchResult{ .matches = false, .captured = "" } + then hit.captured.len == miss.captured.len + and hit.matches != miss.matches + diff --git a/apps/website/public/t27/specs/tri/search/rabin_karp.t27 b/apps/website/public/t27/specs/tri/search/rabin_karp.t27 new file mode 100644 index 0000000000..f83320d67b --- /dev/null +++ b/apps/website/public/t27/specs/tri/search/rabin_karp.t27 @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// O(1) hash update when sliding window | φ² + 1/φ² = 3 | TRINITY + +module TriRabinKarp; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const RKState = struct { + pattern_hash : u64, + pattern_len : usize, + base : u64, + modulus : u64, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(pattern: []const u8) → RKState + fn init(pattern: []const u8) -> RKState { + // TODO: Implement from .tri spec + } + + // search(state: *RKState, text: []const u8) → []usize + fn search(state: *RKState, text: []const u8) -> []usize { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // init()/search() have no body yet, so they panic when called and the + // O(1) rolling update -- the whole point of the algorithm -- cannot be + // exercised. What the module states is the shape of the state that update + // would carry, and that is what is tested here. + + test empty_pattern_state_has_zero_length + // Verify: the boundary case, where every position is trivially a match + given state = RKState{.pattern_hash=0,.pattern_len=0,.base=256,.modulus=1000000007} + then state.pattern_len == 0 + + test pattern_hash_is_reduced_modulo_the_modulus + // Verify: the stored hash is a residue, not a raw accumulation -- + // otherwise the sliding-window comparison would compare unlike things + given state = RKState{.pattern_hash=123456789,.pattern_len=5,.base=256,.modulus=1000000007} + then state.pattern_hash < state.modulus + + test base_is_smaller_than_the_modulus + // Verify: base 256 covers the byte alphabet while staying inside the + // residue class, so a single character is never itself out of range + given state = RKState{.pattern_hash=0,.pattern_len=3,.base=256,.modulus=1000000007} + then (state.base > 255) and (state.base < state.modulus) + + test rolling_update_cannot_overflow_the_declared_u64_fields + // Verify: the update computes (hash * base + c) % modulus, so + // base * modulus must stay inside u64 for the intermediate product to + // be representable in the declared field width + given state = RKState{.pattern_hash=0,.pattern_len=3,.base=256,.modulus=1000000007} + and product = @as(u128, state.base) * @as(u128, state.modulus) + then product <= std.math.maxInt(u64) + + test pattern_length_is_a_usize_not_a_hash_sized_field + // Verify: length indexes the haystack while the hashes are u64 + // residues; the two are deliberately different types + then (@FieldType(RKState, "pattern_len") == usize) and (@FieldType(RKState, "pattern_hash") == u64) + diff --git a/apps/website/public/t27/specs/tri/search/regex.t27 b/apps/website/public/t27/specs/tri/search/regex.t27 new file mode 100644 index 0000000000..87bc5fb70c --- /dev/null +++ b/apps/website/public/t27/specs/tri/search/regex.t27 @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Limited regex syntax | φ² + 1/φ² = 3 | TRINITY + +module TriRegex; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Regex = struct { + pattern : []const u8, + compiled : bool, + }; + + pub const Match = struct { + start : usize, + end : usize, + groups : [][]const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // compile(pattern: []const u8, allocator: std.mem.Allocator) → !Regex + fn compile(pattern: []const u8, allocator: std.mem.Allocator) -> !Regex { + // TODO: Implement from .tri spec + } + + // match(regex: Regex, text: []const u8) → ?Match + fn match(regex: Regex, text: []const u8) -> ?Match { + // TODO: Implement from .tri spec + } + + // find_all(regex: Regex, text: []const u8, allocator: std.mem.Allocator) → ![]Match + fn find_all(regex: Regex, text: []const u8, allocator: std.mem.Allocator) -> ![]Match { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test match_bounds_select_the_matched_substring + // Verify: start is inclusive and end is exclusive, so a match at 6..11 + // of "hello world" names exactly "world" + given text = "hello world" + and found = Match{ .start = 6, .end = 11, .groups = &.{} } + when selected = text[found.start..found.end] + and selected_is_world = std.mem.eql(u8, selected, "world") + then selected_is_world and selected.len == 5 + + test match_length_is_end_minus_start + // Verify: the half-open convention makes the length a plain subtraction, + // with no off-by-one correction + given found = Match{ .start = 6, .end = 11, .groups = &.{} } + when length = found.end - found.start + then length == 5 + + test a_zero_width_match_has_equal_bounds + // Verify: the boundary case — a pattern that matches the empty string, + // such as an anchor, yields start == end and selects nothing + given text = "abc" + and found = Match{ .start = 1, .end = 1, .groups = &.{} } + when selected = text[found.start..found.end] + then selected.len == 0 and found.start == found.end + + test a_match_over_the_whole_string_starts_at_zero_and_ends_at_len + // Verify: a full-string match reproduces the input exactly + given text = "abc" + and found = Match{ .start = 0, .end = 3, .groups = &.{} } + when selected = text[found.start..found.end] + and selected_is_input = std.mem.eql(u8, selected, text) + then selected_is_input + + test a_match_with_no_capture_groups_has_an_empty_group_list + // Verify: groups is a slice, so "no captures" is length 0, not a null + given found = Match{ .start = 0, .end = 3, .groups = &.{} } + then found.groups.len == 0 + + test a_regex_keeps_its_pattern_and_a_compiled_flag + // Verify: the pattern text is retained verbatim and compiled starts false, + // so compile() has an observable before-and-after state to change + given regex = Regex{ .pattern = "a+b", .compiled = false } + when pattern_kept = std.mem.eql(u8, regex.pattern, "a+b") + then pattern_kept and regex.compiled == false + diff --git a/apps/website/public/t27/specs/tri/search/regex_advanced.t27 b/apps/website/public/t27/specs/tri/search/regex_advanced.t27 new file mode 100644 index 0000000000..456f2a5996 --- /dev/null +++ b/apps/website/public/t27/specs/tri/search/regex_advanced.t27 @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Capture groups support | φ² + 1/φ² = 3 | TRINITY + +module TriRegexAdvanced; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const RegexFlags = struct { + enum : [IgnoreCase, Multiline, DotAll], + }; + + pub const RegexMatch = struct { + matched : bool, + groups : [][]const u8, + start : usize, + end : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // compile(pattern: []const u8, flags: RegexFlags, allocator: std.mem.Allocator) → !Regex + fn compile(pattern: []const u8, flags: RegexFlags, allocator: std.mem.Allocator) -> !Regex { + // TODO: Implement from .tri spec + } + + // match(regex: Regex, text: []const u8, allocator: std.mem.Allocator) → !RegexMatch + fn match(regex: Regex, text: []const u8, allocator: std.mem.Allocator) -> !RegexMatch { + // TODO: Implement from .tri spec + } + + // replace(regex: Regex, text: []const u8, replacement: []const u8, allocator: std.mem.Allocator) → ![]u8 + fn replace(regex: Regex, text: []const u8, replacement: []const u8, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test compile_basic_case + given input = default_input() + when result = compile(input) + then result != undefined + + test match_basic_case + given input = default_input() + when result = match(input) + then result != undefined + + test replace_basic_case + given input = default_input() + when result = replace(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/search/search.t27 b/apps/website/public/t27/specs/tri/search/search.t27 new file mode 100644 index 0000000000..9f16b43d07 --- /dev/null +++ b/apps/website/public/t27/specs/tri/search/search.t27 @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// O(log n) binary search | φ² + 1/φ² = 3 | TRINITY + +module TriSearch; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SearchResult = struct { + index : ?usize, + found : bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // binary(sorted: []const T, target: T) → SearchResult + fn binary(sorted: []const T, target: T) -> SearchResult { + // TODO: Implement from .tri spec + } + + // linear(items: []const T, target: T) → SearchResult + fn linear(items: []const T, target: T) -> SearchResult { + // TODO: Implement from .tri spec + } + + // lower_bound(sorted: []const T, value: T) → usize + fn lower_bound(sorted: []const T, value: T) -> usize { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test miss_carries_no_index + // Verify: a failed search reports found = false and leaves index null, + // so there is no position to misread + given miss = SearchResult{ .index = null, .found = false } + then miss.found == false and miss.index == null + + test hit_carries_the_position + // Verify: a successful search reports found = true and an index that + // can be unwrapped + given hit = SearchResult{ .index = 2, .found = true } + then hit.found and hit.index != null and hit.index.? == 2 + + test found_flag_agrees_with_the_optional_index + // Verify: the two fields are redundant -- `found` is exactly + // (index != null) on both outcomes, and any result where they disagree + // is malformed + given miss = SearchResult{ .index = null, .found = false } + and hit = SearchResult{ .index = 7, .found = true } + when miss_agrees = miss.found == (miss.index != null) + and hit_agrees = hit.found == (hit.index != null) + then miss_agrees and hit_agrees + diff --git a/apps/website/public/t27/specs/tri/sort/counting_sort.t27 b/apps/website/public/t27/specs/tri/sort/counting_sort.t27 new file mode 100644 index 0000000000..4a9171c407 --- /dev/null +++ b/apps/website/public/t27/specs/tri/sort/counting_sort.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Count occurrences of each value | φ² + 1/φ² = 3 | TRINITY + +module TriCountingSort; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // sort(allocator: std.mem.Allocator, values: []usize, max_val: usize) → []usize + fn sort(allocator: std.mem.Allocator, values: []usize, max_val: usize) -> []usize { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test sort_basic_case + given input = default_input() + when result = sort(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/sort/heap_sort.t27 b/apps/website/public/t27/specs/tri/sort/heap_sort.t27 new file mode 100644 index 0000000000..880b0c1d0c --- /dev/null +++ b/apps/website/public/t27/specs/tri/sort/heap_sort.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Build max heap, then extract max repeatedly | φ² + 1/φ² = 3 | TRINITY + +module TriHeapSort; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // sort(values: []i64) → void + fn sort(values: []i64) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test sort_basic_case + given input = default_input() + when result = sort(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/sort/insertion_sort.t27 b/apps/website/public/t27/specs/tri/sort/insertion_sort.t27 new file mode 100644 index 0000000000..0cdb681836 --- /dev/null +++ b/apps/website/public/t27/specs/tri/sort/insertion_sort.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Build sorted portion one element at a time | φ² + 1/φ² = 3 | TRINITY + +module TriInsertionSort; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // sort(values: []i64) → void + fn sort(values: []i64) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test sort_basic_case + given input = default_input() + when result = sort(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/sort/merge_sort.t27 b/apps/website/public/t27/specs/tri/sort/merge_sort.t27 new file mode 100644 index 0000000000..2b0990479f --- /dev/null +++ b/apps/website/public/t27/specs/tri/sort/merge_sort.t27 @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Split in half, recursively sort, merge | φ² + 1/φ² = 3 | TRINITY + +module TriMergeSort; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // sort(allocator: std.mem.Allocator, values: []i64) → []i64 + fn sort(allocator: std.mem.Allocator, values: []i64) -> []i64 { + // TODO: Implement from .tri spec + } + + // sort_in_place(allocator: std.mem.Allocator, values: []i64) → void + fn sort_in_place(allocator: std.mem.Allocator, values: []i64) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test sort_basic_case + given input = default_input() + when result = sort(input) + then result != undefined + + test sort_in_place_basic_case + given input = default_input() + when result = sort_in_place(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/sort/quick_sort.t27 b/apps/website/public/t27/specs/tri/sort/quick_sort.t27 new file mode 100644 index 0000000000..0ca7beeeba --- /dev/null +++ b/apps/website/public/t27/specs/tri/sort/quick_sort.t27 @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Use last element as pivot | φ² + 1/φ² = 3 | TRINITY + +module TriQuickSort; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // sort(values: []i64) → void + fn sort(values: []i64) -> void { + // TODO: Implement from .tri spec + } + + // sort_range(values: []i64, low: usize, high: usize) → void + fn sort_range(values: []i64, low: usize, high: usize) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Both functions have empty bodies, so neither the ordering nor the + // last-element pivot rule in the header can be exercised: calling either + // one panics. The single decision the module has made is its signature. + + test sorting_is_in_place + // a mutable slice in, nothing out -- the caller's array is reordered + // rather than copied into a freshly allocated one + given sort_type = @TypeOf(sort) + then sort_type == fn ([]i64) void + + test sort_range_shares_the_in_place_signature + given sort_range_type = @TypeOf(sort_range) + then sort_range_type == fn ([]i64) void + diff --git a/apps/website/public/t27/specs/tri/sort/radix_sort.t27 b/apps/website/public/t27/specs/tri/sort/radix_sort.t27 new file mode 100644 index 0000000000..6d98010581 --- /dev/null +++ b/apps/website/public/t27/specs/tri/sort/radix_sort.t27 @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Least significant digit first | φ² + 1/φ² = 3 | TRINITY + +module TriRadixSort; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const RadixSorter = struct { + base : usize, + max_digits : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // sort(allocator: std.mem.Allocator, values: []usize) → []usize + fn sort(allocator: std.mem.Allocator, values: []usize) -> []usize { + // TODO: Implement from .tri spec + } + + // sort_in_place(allocator: std.mem.Allocator, values: []usize) → void + fn sort_in_place(allocator: std.mem.Allocator, values: []usize) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // sort and sort_in_place are unimplemented stubs -- both emit + // `@panic("not yet implemented")`, so no ordering can be exercised. The + // module does state the sorter's two parameters, and that is testable. + + test sorter_is_base_and_digit_budget_only + // No allocator and no scratch buffer are stored; sort() takes the + // allocator as a parameter instead. + then @typeInfo(RadixSorter).@"struct".fields.len == 2 + and @hasField(RadixSorter, "base") and @hasField(RadixSorter, "max_digits") + + test both_parameters_are_unsigned_counts + then @FieldType(RadixSorter, "base") == usize + and @FieldType(RadixSorter, "max_digits") == usize + + test sorting_is_not_a_method_on_the_sorter + // Both entry points are module-level functions taking an allocator; + // RadixSorter carries no state that survives a sort. + then @hasDecl(@This(), "sort") and @hasDecl(@This(), "sort_in_place") + and @hasDecl(RadixSorter, "sort") == false + diff --git a/apps/website/public/t27/specs/tri/sort/selection_sort.t27 b/apps/website/public/t27/specs/tri/sort/selection_sort.t27 new file mode 100644 index 0000000000..e5e5e03695 --- /dev/null +++ b/apps/website/public/t27/specs/tri/sort/selection_sort.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Find minimum, swap to front | φ² + 1/φ² = 3 | TRINITY + +module TriSelectionSort; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // sort(values: []i64) → void + fn sort(values: []i64) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test sort_basic_case + given input = default_input() + when result = sort(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/sort/shell_sort.t27 b/apps/website/public/t27/specs/tri/sort/shell_sort.t27 new file mode 100644 index 0000000000..95e4f2004d --- /dev/null +++ b/apps/website/public/t27/specs/tri/sort/shell_sort.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Sort elements at gap distance, reduce gap | φ² + 1/φ² = 3 | TRINITY + +module TriShellSort; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // sort(values: []i64) → void + fn sort(values: []i64) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test sort_basic_case + given input = default_input() + when result = sort(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/sort/sort.t27 b/apps/website/public/t27/specs/tri/sort/sort.t27 new file mode 100644 index 0000000000..5e0b8e8892 --- /dev/null +++ b/apps/website/public/t27/specs/tri/sort/sort.t27 @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Stable sort | φ² + 1/φ² = 3 | TRINITY + +module TriSort; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SortOrder = struct { + enum : [Ascending, Descending], + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // sort(items: []const T, order: SortOrder, allocator: std.mem.Allocator) → ![]T + fn sort(items: []const T, order: SortOrder, allocator: std.mem.Allocator) -> ![]T { + // TODO: Implement from .tri spec + } + + // sort_by(items: []const T, key_fn: fn(T) ?Order, allocator: std.mem.Allocator) → ![]T + fn sort_by(items: []const T, key_fn: fn(T) ?Order, allocator: std.mem.Allocator) -> ![]T { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test sort_order_names_exactly_two_directions + // Verify: SortOrder offers Ascending and Descending and nothing else, so + // a comparator only ever has to pick a sign + given has_ascending = @hasField(SortOrder, "Ascending") + and has_descending = @hasField(SortOrder, "Descending") + and variant_count = @typeInfo(SortOrder).@"enum".fields.len + then has_ascending and has_descending and variant_count == 2 + + test sort_order_is_an_enum_whose_two_directions_are_alternatives + // Verify: SortOrder lowers to a real enum, so Ascending and Descending + // are mutually exclusive tags of one value rather than two void fields + // held simultaneously. The tags are ordered, which the void-field struct + // could not express: Ascending is 0 and Descending is 1. + given ascending = @intFromEnum(SortOrder.Ascending) + and descending = @intFromEnum(SortOrder.Descending) + then ascending == 0 and descending == 1 + + // NOTE: sort() and sort_by() are unimplemented — both bodies lower to + // @panic("not yet implemented"). There is no stability, ordering or + // permutation property to test until they have a body. The two tests above + // cover the only thing this module currently declares. + diff --git a/apps/website/public/t27/specs/tri/sort/tim_sort.t27 b/apps/website/public/t27/specs/tri/sort/tim_sort.t27 new file mode 100644 index 0000000000..58ab286c1c --- /dev/null +++ b/apps/website/public/t27/specs/tri/sort/tim_sort.t27 @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Find runs, merge using galloping mode | φ² + 1/φ² = 3 | TRINITY + +module TriTimSort; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // sort(allocator: std.mem.Allocator, values: []i64) → void + fn sort(allocator: std.mem.Allocator, values: []i64) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test sort_basic_case + given input = default_input() + when result = sort(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/trees/avl_tree.t27 b/apps/website/public/t27/specs/tri/trees/avl_tree.t27 new file mode 100644 index 0000000000..728a4b2bc0 --- /dev/null +++ b/apps/website/public/t27/specs/tri/trees/avl_tree.t27 @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Left/right rotations to maintain balance | φ² + 1/φ² = 3 | TRINITY + +module TriAvlTree; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const AVLTree = struct { + generic : K, V, + root : ?*AVLNode, + size : usize, + }; + + pub const AVLNode = struct { + generic : K, V, + key : K, + value : V, + height : i32, + left : ?*AVLNode, + right : ?*AVLNode, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init() → AVLTree + fn init() -> AVLTree { + // TODO: Implement from .tri spec + } + + // insert(tree: *AVLTree, key: K, value: V) → !void + fn insert(tree: *AVLTree, key: K, value: V) -> !void { + // TODO: Implement from .tri spec + } + + // find(tree: *const AVLTree, key: K) → ?V + fn find(tree: *const AVLTree, key: K) -> ?V { + // TODO: Implement from .tri spec + } + + // delete(tree: *AVLTree, key: K) → bool + fn delete(tree: *AVLTree, key: K) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test init_basic_case + given input = default_input() + when result = init(input) + then result != undefined + + test insert_basic_case + given input = default_input() + when result = insert(input) + then result != undefined + + test find_basic_case + given input = default_input() + when result = find(input) + then result != undefined + + test delete_basic_case + given input = default_input() + when result = delete(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/trees/b_tree.t27 b/apps/website/public/t27/specs/tri/trees/b_tree.t27 new file mode 100644 index 0000000000..5103cbaa3b --- /dev/null +++ b/apps/website/public/t27/specs/tri/trees/b_tree.t27 @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Split full child node during insertion | φ² + 1/φ² = 3 | TRINITY + +module TriBTree; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const BTreeNode = struct { + keys : []usize, + children : []?*BTreeNode, + leaf : bool, + count : usize, + }; + + pub const BTree = struct { + root : ?*BTreeNode, + t : usize, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(allocator: std.mem.Allocator, min_degree: usize) → BTree + fn init(allocator: std.mem.Allocator, min_degree: usize) -> BTree { + // TODO: Implement from .tri spec + } + + // search(tree: *BTree, key: usize) → bool + fn search(tree: *BTree, key: usize) -> bool { + // TODO: Implement from .tri spec + } + + // insert(tree: *BTree, key: usize) → !void + fn insert(tree: *BTree, key: usize) -> !void { + // TODO: Implement from .tri spec + } + + // deinit(tree: *BTree) → void + fn deinit(tree: *BTree) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // init, search, insert and deinit are all unimplemented stubs: each one + // emits `@panic("not yet implemented")`, so calling any of them aborts the + // test binary rather than failing a test. (Their signatures are also + // truncated to the first parameter -- `insert(tree: *BTree)` has nowhere + // to receive the key -- so there is nothing to call them with either.) + // The assertions below are about the representation the module declares. + + test an_empty_tree_is_a_null_root_and_still_owns_its_allocator + // root is an optional pointer, so "no nodes yet" needs no allocation; + // the allocator is stored on the tree, so insert can grow it later and + // deinit can free it without being handed one. + given empty = BTree{ .root = null, .t = 3, .allocator = std.testing.allocator } + then empty.root == null + and empty.t == 3 + and @typeInfo(@FieldType(BTree, "root")).optional.child == *BTreeNode + and @FieldType(BTree, "allocator") == std.mem.Allocator + + test a_node_counts_its_live_keys_separately_from_its_slice + // keys is the allocated run, count is how many of them are in use -- + // that is what lets a split move keys around without reallocating. + then @typeInfo(BTreeNode).@"struct".fields.len == 4 + and @FieldType(BTreeNode, "keys") == []usize + and @FieldType(BTreeNode, "count") == usize + and @FieldType(BTreeNode, "leaf") == bool + + test children_are_optional_pointers_so_a_slot_can_be_empty + // A leaf's child slots exist but hold null; only an interior node + // fills them in. The child type is the node type itself. + then @typeInfo(@FieldType(BTreeNode, "children")).pointer.child == ?*BTreeNode + and @typeInfo(@FieldType(BTreeNode, "children")).pointer.size == .slice + + test minimum_degree_t_bounds_the_keys_and_the_children + // "Split full child node during insertion": a node of minimum degree t + // is full at 2t-1 keys, and a node with k keys has k+1 children. So a + // full node splits into two halves of t-1 keys with one key promoted. + given max_keys = 2 * 3 - 1 + given max_children = 2 * 3 + given half = (max_keys - 1) / 2 + then max_children == max_keys + 1 + and half == 2 + and 2 * half + 1 == max_keys + diff --git a/apps/website/public/t27/specs/tri/trees/fenwick_tree.t27 b/apps/website/public/t27/specs/tri/trees/fenwick_tree.t27 new file mode 100644 index 0000000000..4bde938914 --- /dev/null +++ b/apps/website/public/t27/specs/tri/trees/fenwick_tree.t27 @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Use least significant bit for navigation | φ² + 1/φ² = 3 | TRINITY + +module TriFenwick; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const FenwickTree = struct { + data : []i64, + size : usize, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(allocator: std.mem.Allocator, size: usize) → FenwickTree + fn init(allocator: std.mem.Allocator, size: usize) -> FenwickTree { + // TODO: Implement from .tri spec + } + + // build(allocator: std.mem.Allocator, values: []const i64) → FenwickTree + fn build(allocator: std.mem.Allocator, values: []const i64) -> FenwickTree { + // TODO: Implement from .tri spec + } + + // query(tree: *FenwickTree, index: usize) → i64 + fn query(tree: *FenwickTree, index: usize) -> i64 { + // TODO: Implement from .tri spec + } + + // range_query(tree: *FenwickTree, left: usize, right: usize) → i64 + fn range_query(tree: *FenwickTree, left: usize, right: usize) -> i64 { + // TODO: Implement from .tri spec + } + + // update(tree: *FenwickTree, index: usize, delta: i64) → void + fn update(tree: *FenwickTree, index: usize, delta: i64) -> void { + // TODO: Implement from .tri spec + } + + // deinit(tree: *FenwickTree) → void + fn deinit(tree: *FenwickTree) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above is still `TODO: Implement`, so each one compiles + // to `@panic("not yet implemented")`: calling one aborts the test binary. + // What this module does state is the layout of FenwickTree, the + // least-significant-bit navigation named in the header, and the signature + // of each operation, and that is what these tests hold to. + + test fenwick_stores_signed_partial_sums_in_a_flat_buffer + given cells = @FieldType(FenwickTree, "data") + then cells == []i64 + and @FieldType(FenwickTree, "size") == usize + and @FieldType(FenwickTree, "allocator") == std.mem.Allocator + + // The header's rule: an index walks the tree by its lowest set bit, + // `i & -i`. Query subtracts it, update adds it, and from index 12 + // (0b1100) that walk is 12 -> 8 -> 0 downward and 12 -> 16 upward. + test lowest_set_bit_of_twelve_is_four + given i = @as(i64, 12) + then i & -i == 4 + + test query_walk_from_twelve_strips_bits_down_to_zero + given i = @as(i64, 12) + and a = i - (i & -i) + then a == 8 + and a - (a & -a) == 0 + + test update_walk_from_twelve_carries_up_to_sixteen + given i = @as(i64, 12) + then i + (i & -i) == 16 + + test init_and_build_take_an_allocator_and_return_nothing + given f = init + then @TypeOf(f) == fn (std.mem.Allocator) void + and @TypeOf(build) == fn (std.mem.Allocator) void + + // query, range_query and update are declared with the tree alone: no + // index, no range bounds, no delta. These record the signatures as they + // stand, not the ones the operations need. + test query_range_query_update_and_deinit_take_one_tree_pointer + given f = query + then @TypeOf(f) == fn (*FenwickTree) void + and @TypeOf(range_query) == fn (*FenwickTree) void + and @TypeOf(update) == fn (*FenwickTree) void + and @TypeOf(deinit) == fn (*FenwickTree) void + diff --git a/apps/website/public/t27/specs/tri/trees/kd_tree.t27 b/apps/website/public/t27/specs/tri/trees/kd_tree.t27 new file mode 100644 index 0000000000..ec301b4c81 --- /dev/null +++ b/apps/website/public/t27/specs/tri/trees/kd_tree.t27 @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Free tree | φ² + 1/φ² = 3 | TRINITY + +module TriKdTree; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const KDNode = struct { + point : []f64, + axis : usize, + left : ?KDNode, + right : ?KDNode, + }; + + pub const KDTree = struct { + root : ?KDNode, + k : usize, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(allocator: std.mem.Allocator, k: usize) → KDTree + fn init(allocator: std.mem.Allocator, k: usize) -> KDTree { + // TODO: Implement from .tri spec + } + + // build(allocator: std.mem.Allocator, points: [][]f64, k: usize) → KDTree + fn build(allocator: std.mem.Allocator, points: [][]f64, k: usize) -> KDTree { + // TODO: Implement from .tri spec + } + + // nearest(tree: *KDTree, target: []f64) → []f64 + fn nearest(tree: *KDTree, target: []f64) -> []f64 { + // TODO: Implement from .tri spec + } + + // range(tree: *KDTree, center: []f64, radius: f64) → [][]f64 + fn range(tree: *KDTree, center: []f64, radius: f64) -> [][]f64 { + // TODO: Implement from .tri spec + } + + // deinit(tree: *KDTree) → void + fn deinit(tree: *KDTree) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above is still `TODO: Implement`, so each one compiles + // to `@panic("not yet implemented")`: calling one aborts the test binary. + // The signatures are real, and that is what these tests hold to. + // + // KDNode CANNOT BE INSTANTIATED AS DECLARED. `left : "?KDNode"` and + // `right : "?KDNode"` store a KDNode BY VALUE, so the type contains + // itself and has no finite size; KDTree.root has the same problem. Zig + // resolves struct fields lazily, which is the only reason this file + // compiles at all -- the moment any test touches a field, e.g. + // + // then @FieldType(KDNode, "left") == ?KDNode + // + // the whole module dies with "type 'KDNode' depends on itself for field + // declared here" and NOTHING in it runs. That test is left out on + // purpose so the rest of the file still has tests; the fix is + // `?*KDNode`, and it belongs in the type declaration, not here. + + test init_and_build_take_an_allocator_and_return_nothing + given f = init + then @TypeOf(f) == fn (std.mem.Allocator) void + and @TypeOf(build) == fn (std.mem.Allocator) void + + // nearest and range are declared with the tree alone: no query point, no + // radius, no result buffer. These record the signatures as they stand. + test nearest_range_and_deinit_take_one_tree_pointer + given f = nearest + then @TypeOf(f) == fn (*KDTree) void + and @TypeOf(range) == fn (*KDTree) void + and @TypeOf(deinit) == fn (*KDTree) void + diff --git a/apps/website/public/t27/specs/tri/trees/octree.t27 b/apps/website/public/t27/specs/tri/trees/octree.t27 new file mode 100644 index 0000000000..482509ba29 --- /dev/null +++ b/apps/website/public/t27/specs/tri/trees/octree.t27 @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Free tree | φ² + 1/φ² = 3 | TRINITY + +module TriOctree; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const BBox = struct { + min_x : f64, + min_y : f64, + min_z : f64, + max_x : f64, + max_y : f64, + max_z : f64, + }; + + pub const OctNode = struct { + bounds : BBox, + children : [8]?OctNode, + data : ?void, + divided : bool, + allocator : std.mem.Allocator, + }; + + pub const Octree = struct { + root : ?OctNode, + min_size : f64, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(allocator: std.mem.Allocator, bounds: BBox, min_size: f64) → Octree + fn init(allocator: std.mem.Allocator, bounds: BBox, min_size: f64) -> Octree { + // TODO: Implement from .tri spec + } + + // insert(ot: *Octree, x: f64, y: f64, z: f64, data: void) → !void + fn insert(ot: *Octree, x: f64, y: f64, z: f64, data: void) -> !void { + // TODO: Implement from .tri spec + } + + // query(ot: *Octree, bounds: BBox) → []void + fn query(ot: *Octree, bounds: BBox) -> []void { + // TODO: Implement from .tri spec + } + + // deinit(ot: *Octree) → void + fn deinit(ot: *Octree) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // init, insert, query and deinit are still `TODO: Implement from .tri + // spec` and compile to `@panic`, so none of them is called here. + // + // Nor is OctNode or Octree touched. `children : [8]?OctNode` stores the + // eight octants BY VALUE, so the struct contains itself and has no finite + // size; naming the type anywhere in this module turns the whole spec into + // a compile error ("type depends on itself") and takes the tests below + // down with it. The field wants to be `[8]?*OctNode`. Until that is + // decided, BBox is the part of the module that can be exercised. + + // The unit cube: six independent f64 bounds, min below max on each axis. + test bbox_unit_cube + given b = BBox{ .min_x = 0.0, .min_y = 0.0, .min_z = 0.0, .max_x = 1.0, .max_y = 1.0, .max_z = 1.0 } + then b.max_x - b.min_x > 0.0 + and b.max_y - b.min_y > 0.0 + and b.max_z - b.min_z > 0.0 + + // Bounds are signed and the box need not be centred on the origin; an + // extent is a difference, not a magnitude. + test bbox_extent_is_a_difference + given b = BBox{ .min_x = -2.0, .min_y = -2.0, .min_z = -2.0, .max_x = 2.0, .max_y = 2.0, .max_z = 2.0 } + then @abs((b.max_x - b.min_x) - 4.0) < 1e-12 + and @abs((b.max_y - b.min_y) - 4.0) < 1e-12 + and @abs((b.max_z - b.min_z) - 4.0) < 1e-12 + + // A degenerate box -- min equal to max -- is representable, which is what + // the min_size cutoff on Octree exists to stop subdivision reaching. + test bbox_degenerate_is_representable + given b = BBox{ .min_x = 1.5, .min_y = 1.5, .min_z = 1.5, .max_x = 1.5, .max_y = 1.5, .max_z = 1.5 } + then @abs(b.max_x - b.min_x) < 1e-12 + and @abs(b.max_z - b.min_z) < 1e-12 + + // The three axes are independent: a box may be flat in one and thick in + // another, so an octant split cannot assume a cube. + test bbox_axes_are_independent + given b = BBox{ .min_x = 0.0, .min_y = 0.0, .min_z = 0.0, .max_x = 8.0, .max_y = 2.0, .max_z = 0.0 } + then b.max_x - b.min_x > b.max_y - b.min_y + and @abs(b.max_z - b.min_z) < 1e-12 + diff --git a/apps/website/public/t27/specs/tri/trees/quadtree.t27 b/apps/website/public/t27/specs/tri/trees/quadtree.t27 new file mode 100644 index 0000000000..6907c27749 --- /dev/null +++ b/apps/website/public/t27/specs/tri/trees/quadtree.t27 @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Free tree | φ² + 1/φ² = 3 | TRINITY + +module TriQuadtree; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Rect = struct { + x : f64, + y : f64, + width : f64, + height : f64, + }; + + pub const QuadNode = struct { + boundary : Rect, + children : [4]?QuadNode, + points : [][2]f64, + divided : bool, + allocator : std.mem.Allocator, + }; + + pub const QuadTree = struct { + root : ?QuadNode, + capacity : usize, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(allocator: std.mem.Allocator, boundary: Rect, capacity: usize) → QuadTree + fn init(allocator: std.mem.Allocator, boundary: Rect, capacity: usize) -> QuadTree { + // TODO: Implement from .tri spec + } + + // insert(qt: *QuadTree, x: f64, y: f64) → !void + fn insert(qt: *QuadTree, x: f64, y: f64) -> !void { + // TODO: Implement from .tri spec + } + + // query(qt: *QuadTree, range: Rect) → [][2]f64 + fn query(qt: *QuadTree, range: Rect) -> [][2]f64 { + // TODO: Implement from .tri spec + } + + // deinit(qt: *QuadTree) → void + fn deinit(qt: *QuadTree) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // init/insert/query/deinit are unimplemented stubs returning void, so no + // insert-then-query behaviour can be asserted yet. QuadNode and QuadTree + // cannot be instantiated either: QuadNode holds [4]?QuadNode by value, a + // type that depends on its own size, so Zig rejects it the moment anything + // forces the layout. Rect, the boundary geometry every subdivision is + // computed from, is what remains testable and is tested here. + + test rect_is_origin_plus_extent_not_two_corners + // Verify: the far edges are derived by addition, unlike the min/max + // corners used in TriRtree + given r = Rect{.x=1.0,.y=2.0,.width=4.0,.height=6.0} + and right = r.x + r.width + and bottom = r.y + r.height + then (@abs(right - 5.0) < 1e-12) and (@abs(bottom - 8.0) < 1e-12) + + test rect_area_is_the_extent_product + given r = Rect{.x=0.0,.y=0.0,.width=4.0,.height=6.0} + and area = r.width * r.height + then @abs(area - 24.0) < 1e-12 + + test quadrant_subdivision_partitions_the_parent_area + // Verify: the subdivision a quadtree performs -- four children of half + // width and half height -- exactly covers the parent, with no overlap + // and no gap. This is arithmetic on the declared Rect fields; the + // insert path that would perform it is still a stub. + given parent = Rect{.x=0.0,.y=0.0,.width=8.0,.height=8.0} + and half_w = parent.width / 2.0 + and half_h = parent.height / 2.0 + and quadrant_area = half_w * half_h + then @abs((quadrant_area * 4.0) - (parent.width * parent.height)) < 1e-12 + + test north_east_quadrant_starts_at_the_parent_midpoint + // Verify: the offset arithmetic for one child, so the halves are not + // silently both anchored at the parent origin + given parent = Rect{.x=0.0,.y=0.0,.width=8.0,.height=8.0} + and ne = Rect{.x=parent.x + parent.width / 2.0,.y=parent.y,.width=parent.width / 2.0,.height=parent.height / 2.0} + then (@abs(ne.x - 4.0) < 1e-12) and (@abs(ne.x + ne.width - (parent.x + parent.width)) < 1e-12) + + test degenerate_rect_has_zero_area + // Verify: the boundary case -- a point-sized boundary is representable + given r = Rect{.x=3.0,.y=3.0,.width=0.0,.height=0.0} + then (r.width * r.height) == 0.0 + + test rect_coordinates_are_signed + // Verify: f64 origin, so a boundary may sit left of and above zero + given r = Rect{.x=-2.0,.y=-2.0,.width=4.0,.height=4.0} + then (r.x < 0.0) and (r.x + r.width > 0.0) + diff --git a/apps/website/public/t27/specs/tri/trees/red_black_tree.t27 b/apps/website/public/t27/specs/tri/trees/red_black_tree.t27 new file mode 100644 index 0000000000..cdcfb2738d --- /dev/null +++ b/apps/website/public/t27/specs/tri/trees/red_black_tree.t27 @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// 1) Root is black 2) Red children are black 3) Equal black depth to all leaves | φ² + 1/φ² = 3 | TRINITY + +module TriRbTree; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const RBTree = struct { + generic : K, V, + root : ?*RBNode, + size : usize, + }; + + pub const RBNode = struct { + generic : K, V, + key : K, + value : V, + color : Color, + left : ?*RBNode, + right : ?*RBNode, + parent : ?*RBNode, + }; + + pub const Color = struct { + enum : ["RED", "BLACK"], + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init() → RBTree + fn init() -> RBTree { + // TODO: Implement from .tri spec + } + + // insert(tree: *RBTree, key: K, value: V) → !void + fn insert(tree: *RBTree, key: K, value: V) -> !void { + // TODO: Implement from .tri spec + } + + // find(tree: *const RBTree, key: K) → ?V + fn find(tree: *const RBTree, key: K) -> ?V { + // TODO: Implement from .tri spec + } + + // delete(tree: *RBTree, key: K) → bool + fn delete(tree: *RBTree, key: K) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test init_basic_case + given input = default_input() + when result = init(input) + then result != undefined + + test insert_basic_case + given input = default_input() + when result = insert(input) + then result != undefined + + test find_basic_case + given input = default_input() + when result = find(input) + then result != undefined + + test delete_basic_case + given input = default_input() + when result = delete(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/trees/rtree.t27 b/apps/website/public/t27/specs/tri/trees/rtree.t27 new file mode 100644 index 0000000000..c241272e0f --- /dev/null +++ b/apps/website/public/t27/specs/tri/trees/rtree.t27 @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Spatial indexing | φ² + 1/φ² = 3 | TRINITY + +module TriRtree; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Rect = struct { + x_min : f64, + y_min : f64, + x_max : f64, + y_max : f64, + }; + + pub const RTreeNode = struct { + rect : Rect, + children : []RTreeNode, + is_leaf : bool, + }; + + pub const RTree = struct { + root : ?RTreeNode, + max_entries : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(max_entries: usize) → RTree + fn init(max_entries: usize) -> RTree { + // TODO: Implement from .tri spec + } + + // insert(tree: *RTree, rect: Rect, allocator: std.mem.Allocator) → !void + fn insert(tree: *RTree, rect: Rect, allocator: std.mem.Allocator) -> !void { + // TODO: Implement from .tri spec + } + + // query(tree: RTree, search_rect: Rect) → []Rect + fn query(tree: RTree, search_rect: Rect) -> []Rect { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // init/insert/query are unimplemented stubs returning void, so no insert-then- + // query behaviour can be asserted yet. What the module does declare -- the + // bounding-box corners and the optional root -- is tested here. + + test empty_tree_has_no_root + // Verify: root is optional, so a configured but unfilled tree is empty + // rather than holding a sentinel node + given t = RTree{.root=null,.max_entries=4} + then t.root == null and t.max_entries == 4 + + test rect_stores_min_before_max_on_both_axes + // Verify: a well-formed bounding box orders its corners + given r = Rect{.x_min=0.0,.y_min=1.0,.x_max=3.0,.y_max=5.0} + then r.x_min < r.x_max and r.y_min < r.y_max + + test rect_extent_is_the_corner_difference + // Verify: those corners give width 3 and height 4 + given r = Rect{.x_min=0.0,.y_min=1.0,.x_max=3.0,.y_max=5.0} + and width = r.x_max - r.x_min + and height = r.y_max - r.y_min + then @abs(width - 3.0) < 1e-12 and height > width + + test degenerate_rect_is_a_single_point + // Verify: a zero-area box, the boundary case for spatial indexing, + // is representable + given r = Rect{.x_min=2.0,.y_min=2.0,.x_max=2.0,.y_max=2.0} + then r.x_min == r.x_max and r.y_min == r.y_max + + test leaf_node_carries_a_box_and_no_children + // Verify: RTreeNode has the declared field names; a leaf holds its + // bounding box with an empty child list + given n = RTreeNode{.rect=Rect{.x_min=0.0,.y_min=0.0,.x_max=1.0,.y_max=1.0},.children=@constCast(&[_]RTreeNode{}),.is_leaf=true} + then n.is_leaf and n.children.len == 0 + diff --git a/apps/website/public/t27/specs/tri/trees/segment_tree.t27 b/apps/website/public/t27/specs/tri/trees/segment_tree.t27 new file mode 100644 index 0000000000..e2e04230a6 --- /dev/null +++ b/apps/website/public/t27/specs/tri/trees/segment_tree.t27 @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Answer sum queries in O(log n) | φ² + 1/φ² = 3 | TRINITY + +module TriSegmentTree; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SegmentTree = struct { + data : []i64, + size : usize, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(allocator: std.mem.Allocator, values: []const i64) → SegmentTree + fn init(allocator: std.mem.Allocator, values: []const i64) -> SegmentTree { + // TODO: Implement from .tri spec + } + + // query(tree: *SegmentTree, left: usize, right: usize) → i64 + fn query(tree: *SegmentTree, left: usize, right: usize) -> i64 { + // TODO: Implement from .tri spec + } + + // update(tree: *SegmentTree, index: usize, value: i64) → void + fn update(tree: *SegmentTree, index: usize, value: i64) -> void { + // TODO: Implement from .tri spec + } + + // deinit(tree: *SegmentTree) → void + fn deinit(tree: *SegmentTree) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above has no body yet, so it panics when called; and + // query(tree) names no range while update(tree) names no index or value, + // so no sum-query behaviour can be stated until those signatures grow + // their arguments. What can be stated is the shape of the tree value. + + test segment_tree_empty_state_holds_no_nodes + given empty = SegmentTree{.data=&[_]i64{},.size=0,.allocator=std.testing.allocator} + then empty.size == 0 + and empty.data.len == 0 + + test segment_tree_node_array_and_size_are_separate_fields + // size counts the elements the tree covers; data is the node storage, + // and the spec fixes no relation between the two, so this only holds + // them to carrying what they were built with + given tree = SegmentTree{.data=@constCast(&[_]i64{1,3,2,4}),.size=4,.allocator=std.testing.allocator} + then tree.size == 4 + and tree.data.len == 4 + and tree.data[0] == 1 + and tree.data[3] == 4 + diff --git a/apps/website/public/t27/specs/tri/trees/splay_tree.t27 b/apps/website/public/t27/specs/tri/trees/splay_tree.t27 new file mode 100644 index 0000000000..b901d846bf --- /dev/null +++ b/apps/website/public/t27/specs/tri/trees/splay_tree.t27 @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// O(log n) amortized, O(n) worst case per operation | φ² + 1/φ² = 3 | TRINITY + +module TriSplayTree; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SplayTree = struct { + generic : K, V, + root : ?*SplayNode, + size : usize, + }; + + pub const SplayNode = struct { + generic : K, V, + key : K, + value : V, + left : ?*SplayNode, + right : ?*SplayNode, + parent : ?*SplayNode, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init() → SplayTree + fn init() -> SplayTree { + // TODO: Implement from .tri spec + } + + // find(tree: *SplayTree, key: K) → ?V + fn find(tree: *SplayTree, key: K) -> ?V { + // TODO: Implement from .tri spec + } + + // insert(tree: *SplayTree, key: K, value: V) → !void + fn insert(tree: *SplayTree, key: K, value: V) -> !void { + // TODO: Implement from .tri spec + } + + // delete(tree: *SplayTree, key: K) → bool + fn delete(tree: *SplayTree, key: K) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test init_basic_case + given input = default_input() + when result = init(input) + then result != undefined + + test find_basic_case + given input = default_input() + when result = find(input) + then result != undefined + + test insert_basic_case + given input = default_input() + when result = insert(input) + then result != undefined + + test delete_basic_case + given input = default_input() + when result = delete(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/trees/suffix_array.t27 b/apps/website/public/t27/specs/tri/trees/suffix_array.t27 new file mode 100644 index 0000000000..ad5fb7e6bf --- /dev/null +++ b/apps/website/public/t27/specs/tri/trees/suffix_array.t27 @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Sort all suffixes by their starting index | φ² + 1/φ² = 3 | TRINITY + +module TriSuffixArray; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const SuffixArray = struct { + data : []usize, + allocator : std.mem.Allocator, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // build(allocator: std.mem.Allocator, text: []const u8) → SuffixArray + fn build(allocator: std.mem.Allocator, text: []const u8) -> SuffixArray { + // TODO: Implement from .tri spec + } + + // search(sa: *SuffixArray, text: []const u8, pattern: []const u8) → []usize + fn search(sa: *SuffixArray, text: []const u8, pattern: []const u8) -> []usize { + // TODO: Implement from .tri spec + } + + // deinit(sa: *SuffixArray) → void + fn deinit(sa: *SuffixArray) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above is still `TODO: Implement`, so each one compiles + // to `@panic("not yet implemented")`: calling one aborts the test binary. + // What this module does state is that a suffix array is a []usize of + // starting indices, and the header states what orders them. These tests + // hold to that, and pin the reference answer for "banana" that a working + // build would have to produce. + + test suffix_array_is_a_flat_list_of_starting_indices + given d = @FieldType(SuffixArray, "data") + then d == []usize + and @FieldType(SuffixArray, "allocator") == std.mem.Allocator + + // The suffixes of "banana" sorted lexicographically are a(5), ana(3), + // anana(1), banana(0), na(4), nana(2). Each pair below is checked against + // std.mem.lessThan on the real slices, so the order is verified, not + // asserted from memory. + test banana_suffixes_are_listed_in_lexicographic_order + given text = "banana" + and sa = SuffixArray{ .data = @constCast(&[_]usize{ 5, 3, 1, 0, 4, 2 }), .allocator = std.testing.allocator } + then std.mem.lessThan(u8, text[sa.data[0]..], text[sa.data[1]..]) + and std.mem.lessThan(u8, text[sa.data[1]..], text[sa.data[2]..]) + and std.mem.lessThan(u8, text[sa.data[2]..], text[sa.data[3]..]) + and std.mem.lessThan(u8, text[sa.data[3]..], text[sa.data[4]..]) + and std.mem.lessThan(u8, text[sa.data[4]..], text[sa.data[5]..]) + + test banana_suffix_array_holds_one_entry_per_position + given text = "banana" + and sa = SuffixArray{ .data = @constCast(&[_]usize{ 5, 3, 1, 0, 4, 2 }), .allocator = std.testing.allocator } + then sa.data.len == text.len + and sa.data[0] + sa.data[1] + sa.data[2] + sa.data[3] + sa.data[4] + sa.data[5] == 15 + + test build_takes_an_allocator_and_returns_nothing + given f = build + then @TypeOf(f) == fn (std.mem.Allocator) void + + // search is given the array with no needle to look for. This records the + // signature as it stands, not the one the operation needs. + test search_and_deinit_take_one_suffix_array_pointer + given f = search + then @TypeOf(f) == fn (*SuffixArray) void + and @TypeOf(deinit) == fn (*SuffixArray) void + diff --git a/apps/website/public/t27/specs/tri/trees/tree.t27 b/apps/website/public/t27/specs/tri/trees/tree.t27 new file mode 100644 index 0000000000..43472c6d85 --- /dev/null +++ b/apps/website/public/t27/specs/tri/trees/tree.t27 @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Operations return new trees | φ² + 1/φ² = 3 | TRINITY + +module TriTree; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Tree(T) = struct { + is_leaf : bool, + value : T, + left : Tree(T), + right : Tree(T), + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // leaf(value: T) → Tree(T) + fn leaf(value: T) -> Tree(T) { + // TODO: Implement from .tri spec + } + + // branch(left: Tree(T), right: Tree(T)) → Tree(T) + fn branch(left: Tree(T), right: Tree(T)) -> Tree(T) { + // TODO: Implement from .tri spec + } + + // is_leaf(tree: Tree(T)) → bool + fn is_leaf(tree: Tree(T)) -> bool { + // TODO: Implement from .tri spec + } + + // height(tree: Tree(T)) → usize + fn height(tree: Tree(T)) -> usize { + // TODO: Implement from .tri spec + } + + // size(tree: Tree(T)) → usize + fn size(tree: Tree(T)) -> usize { + // TODO: Implement from .tri spec + } + + // inorder(tree: Tree(T)) → []T + fn inorder(tree: Tree(T)) -> []T { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above is an unimplemented stub: each one emits + // `@panic("not yet implemented")`, so calling any of them aborts the test + // binary rather than failing a test. + // + // Tree(T) itself cannot be instantiated either: `left` and `right` hold a + // Tree(T) BY VALUE, so the type is infinitely large and Zig rejects it + // with "depends on itself for field declared here". Naming Tree(u8) + // anywhere below would be a compile error that takes the whole module + // down, not a test failure -- so nothing here mentions an instance. The + // children need to be pointers (?*Tree(T)) before this type is usable. + + test tree_is_a_type_constructor_over_one_element_type + // Tree is a comptime function from a type to a type, not a type. + given info = @typeInfo(@TypeOf(Tree)).@"fn" + then info.params.len == 1 + and info.params[0].type == type + and info.return_type == type + and @TypeOf(Tree) != type + + test branch_takes_a_value_and_both_subtrees + // FAILING ON PURPOSE. `branch(left: Tree(T))` declares one parameter, + // so it has no way to receive the node's value or its right child -- + // it cannot build a branch. The spec kept only the first parameter of + // the signature; every other function in this module is truncated the + // same way, which is why none of them are called above. + then @typeInfo(@TypeOf(branch)).@"fn".params.len >= 3 + diff --git a/apps/website/public/t27/specs/tri/trees/trie.t27 b/apps/website/public/t27/specs/tri/trees/trie.t27 new file mode 100644 index 0000000000..d993ab13f6 --- /dev/null +++ b/apps/website/public/t27/specs/tri/trees/trie.t27 @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// O(k) lookup where k = key length | φ² + 1/φ² = 3 | TRINITY + +module TriTrie; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const TrieNode(T) = struct { + is_end : bool, + value : "T", + children : "std.StringHashMap(*TrieNode(T))", + }; + + pub const Trie(T) = struct { + root : "*TrieNode(T)", + size : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // empty() → Trie(T) + fn empty() -> Trie(T) { + // TODO: Implement from .tri spec + } + + // insert(trie: *Trie(T)) → void + fn insert(trie: *Trie(T)) -> void { + // TODO: Implement from .tri spec + } + + // get(trie: *const Trie(T)) → void + fn get(trie: *const Trie(T)) -> void { + // TODO: Implement from .tri spec + } + + // has_prefix(trie: *const Trie(T)) → void + fn has_prefix(trie: *const Trie(T)) -> void { + // TODO: Implement from .tri spec + } + + // keys_with_prefix(trie: *const Trie(T)) → void + fn keys_with_prefix(trie: *const Trie(T)) -> void { + // TODO: Implement from .tri spec + } + + // remove(trie: *Trie(T)) → void + fn remove(trie: *Trie(T)) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // empty, insert, get, has_prefix, keys_with_prefix and remove are still + // `TODO: Implement from .tri spec` and compile to `@panic`, so none of them + // is called here. Trie(T) itself is not constructed either: its `root` is a + // plain `*TrieNode(T)`, and a test clause binds a const, which yields a + // `*const` that will not coerce. A leaf TrieNode is buildable, and that is + // where the O(k) claim in the header actually rests -- one map lookup per + // key character. + + // A freshly built node has no children: the edge map starts empty, so a + // lookup of any character on it terminates immediately. + test leaf_node_has_no_children + given leaf = TrieNode(i64){ .is_end = true, .value = 7, .children = std.StringHashMap(*TrieNode(i64)).init(std.testing.allocator) } + then leaf.children.count() == 0 + and leaf.is_end == true + and leaf.value == 7 + + // is_end is independent of the child map, which is what lets one stored key + // be a prefix of another: an interior node can end a key and still branch, + // and a node with no children need not end one. + test is_end_is_independent_of_children + given dead_end = TrieNode(i64){ .is_end = false, .value = 0, .children = std.StringHashMap(*TrieNode(i64)).init(std.testing.allocator) } + then dead_end.children.count() == 0 + and dead_end.is_end == false + + // The payload rides on the node, one T per stored key, not one per edge. + test node_carries_the_payload + given a = TrieNode(i64){ .is_end = true, .value = -3, .children = std.StringHashMap(*TrieNode(i64)).init(std.testing.allocator) } + and b = TrieNode(i64){ .is_end = true, .value = 3, .children = std.StringHashMap(*TrieNode(i64)).init(std.testing.allocator) } + then a.value != b.value + and a.is_end == b.is_end + diff --git a/apps/website/public/t27/specs/tri/utils/args.t27 b/apps/website/public/t27/specs/tri/utils/args.t27 new file mode 100644 index 0000000000..8d27f6aac4 --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/args.t27 @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// -- separates options from positional args | φ² + 1/φ² = 3 | TRINITY + +module []const u8; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Arg = struct { + short : ?u8, + long : ?[]const u8, + required : bool, + }; + + pub const ArgValue = struct { + value : ?[]const u8, + present : bool, + }; + + pub const ParseResult = struct { + positional : [][]const u8, + named : []ArgValue, + error : ?[]const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // parse(allocator: std.mem.Allocator, args: [][]const u8, spec: []Arg) → !ParseResult + fn parse(allocator: std.mem.Allocator, args: [][]const u8, spec: []Arg) -> !ParseResult { + // TODO: Implement from .tri spec + } + + // has_flag(result: ParseResult, name: []const u8) → bool + fn has_flag(result: ParseResult, name: []const u8) -> bool { + // TODO: Implement from .tri spec + } + + // get_value(result: ParseResult, name: []const u8) → ?[]const u8 + fn get_value(result: ParseResult, name: []const u8) -> ?[]const u8 { + // TODO: Implement from .tri spec + } + + // get_positional(result: ParseResult, index: usize) → ?[]const u8 + fn get_positional(result: ParseResult, index: usize) -> ?[]const u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // parse, has_flag, get_value and get_positional all have empty bodies, so + // no command line can be parsed here and the "--" rule in the header + // cannot be checked. These pin the shapes the parser will have to fill. + + test both_argument_spellings_are_optional + // nothing in Arg requires at least one spelling: an option with + // neither a short nor a long name is representable + given short_type = @FieldType(Arg, "short") + and long_type = @FieldType(Arg, "long") + and nameless = Arg{ .short = null, .long = null, .required = true } + then short_type == ?u8 and long_type == ?[]const u8 and nameless.required + + test presence_is_tracked_apart_from_the_value + // a flag that was supplied without an argument is present with a null + // value, so the two fields cannot be collapsed into one optional + given value_type = @FieldType(ArgValue, "value") + and present_type = @FieldType(ArgValue, "present") + and bare_flag = ArgValue{ .value = null, .present = true } + then value_type == ?[]const u8 and present_type == bool and bare_flag.present + + test parse_failure_is_reported_beside_the_results + // the error is an optional string held alongside positional and named + // results rather than a Zig error, so a failed parse still carries + // whatever it managed to collect + given error_type = @FieldType(ParseResult, "error") + and positional_type = @FieldType(ParseResult, "positional") + then error_type == ?[]const u8 and positional_type == [][]const u8 + diff --git a/apps/website/public/t27/specs/tri/utils/arrow_time.t27 b/apps/website/public/t27/specs/tri/utils/arrow_time.t27 new file mode 100644 index 0000000000..658f05005b --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/arrow_time.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | φ² + 1/φ² = 3 | TRINITY + +module arrow_time; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "arrow_time_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/utils/bytes.t27 b/apps/website/public/t27/specs/tri/utils/bytes.t27 new file mode 100644 index 0000000000..06ec400363 --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/bytes.t27 @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Views share underlying data | φ² + 1/φ² = 3 | TRINITY + +module TriBytes; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Bytes = struct { + data : []u8, + owned : bool, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // empty() → Bytes + fn empty() -> Bytes { + // TODO: Implement from .tri spec + } + + // from_slice(slice: []const u8) → Bytes + fn from_slice(slice: []const u8) -> Bytes { + // TODO: Implement from .tri spec + } + + // clone(bytes: Bytes, allocator: std.mem.Allocator) → !Bytes + fn clone(bytes: Bytes, allocator: std.mem.Allocator) -> !Bytes { + // TODO: Implement from .tri spec + } + + // equals(a: Bytes, b: Bytes) → bool + fn equals(a: Bytes, b: Bytes) -> bool { + // TODO: Implement from .tri spec + } + + // slice(bytes: Bytes, start: usize, end: usize) → Bytes + fn slice(bytes: Bytes, start: usize, end: usize) -> Bytes { + // TODO: Implement from .tri spec + } + + // concat(a: Bytes, b: Bytes, allocator: std.mem.Allocator) → !Bytes + fn concat(a: Bytes, b: Bytes, allocator: std.mem.Allocator) -> !Bytes { + // TODO: Implement from .tri spec + } + + // index_of(bytes: Bytes, pattern: []const u8) → ?usize + fn index_of(bytes: Bytes, pattern: []const u8) -> ?usize { + // TODO: Implement from .tri spec + } + + // split(bytes: Bytes, delimiter: u8, allocator: std.mem.Allocator) → ![]Bytes + fn split(bytes: Bytes, delimiter: u8, allocator: std.mem.Allocator) -> ![]Bytes { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test empty_basic_case + given input = default_input() + when result = empty(input) + then result != undefined + + test from_slice_basic_case + given input = default_input() + when result = from_slice(input) + then result != undefined + + test clone_basic_case + given input = default_input() + when result = clone(input) + then result != undefined + + test equals_basic_case + given input = default_input() + when result = equals(input) + then result != undefined + + test slice_basic_case + given input = default_input() + when result = slice(input) + then result != undefined + + test concat_basic_case + given input = default_input() + when result = concat(input) + then result != undefined + + test index_of_basic_case + given input = default_input() + when result = index_of(input) + then result != undefined + + test split_basic_case + given input = default_input() + when result = split(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/utils/color.t27 b/apps/website/public/t27/specs/tri/utils/color.t27 new file mode 100644 index 0000000000..c89fb8c661 --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/color.t27 @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Alpha channel support | φ² + 1/φ² = 3 | TRINITY + +module TriColor; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Color = struct { + r : u8, + g : u8, + b : u8, + a : u8, + }; + + pub const ColorSpace = struct { + enum : [RGB, HSV, HSL, LAB], + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // rgb(r: u8, g: u8, b: u8) → Color + fn rgb(r: u8, g: u8, b: u8) -> Color { + // TODO: Implement from .tri spec + } + + // to_hex(color: Color, allocator: std.mem.Allocator) → ![]u8 + fn to_hex(color: Color, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // blend(a: Color, b: Color, factor: f64) → Color + fn blend(a: Color, b: Color, factor: f64) -> Color { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // rgb, to_hex and blend are all unimplemented stubs: each one emits + // `@panic("not yet implemented")`, so calling any of them aborts the test + // binary rather than failing a test. The assertions below are therefore + // about the representation the module actually declares. + + test a_color_is_four_eight_bit_channels + // "Alpha channel support": alpha is a full u8, so opacity has 256 + // levels rather than the two a bool would give. Four u8 fields pack + // into exactly 32 bits with no padding. + given opaque_red = Color{ .r = 255, .g = 0, .b = 0, .a = 255 } + then @typeInfo(Color).@"struct".fields.len == 4 + and @FieldType(Color, "a") == u8 + and @sizeOf(Color) == 4 + and @bitSizeOf(Color) == 32 + and opaque_red.r == 255 + and opaque_red.a == 255 + + test a_hex_string_needs_two_digits_per_channel + // to_hex has to render each u8 as 4 bits + 4 bits, so "#rrggbbaa" is + // one '#' plus eight nibbles: nine bytes, and 255 is the widest value + // any single channel can reach. + given digits_per_channel = @bitSizeOf(u8) / 4 + given hex_len = 1 + 4 * digits_per_channel + then digits_per_channel == 2 + and hex_len == 9 + and std.math.maxInt(u8) == 255 + + test color_space_names_four_models_and_stores_a_discriminant + // The four names are variants of an enum, so a ColorSpace value is + // exactly one model and can tell RGB from LAB at runtime. Four + // variants need two bits, so the value is a u2 discriminant in one + // byte -- not the zero bytes a struct of void fields would occupy. + then @typeInfo(ColorSpace).@"enum".fields.len == 4 + and std.mem.eql(u8, @typeInfo(ColorSpace).@"enum".fields[0].name, "RGB") + and std.mem.eql(u8, @typeInfo(ColorSpace).@"enum".fields[3].name, "LAB") + and @typeInfo(ColorSpace).@"enum".tag_type == u2 + and @intFromEnum(ColorSpace.RGB) == 0 + and @intFromEnum(ColorSpace.LAB) == 3 + and @sizeOf(ColorSpace) == 1 + + test the_constructors_take_all_of_their_operands + // FAILING ON PURPOSE. `rgb(r: u8)` declares one parameter, so it + // cannot receive g, b or a; `blend(a: Color)` declares one, so it has + // nothing to blend with. Both spec signatures keep only their first + // parameter, which is why neither is called above. + then @typeInfo(@TypeOf(rgb)).@"fn".params.len >= 3 + and @typeInfo(@TypeOf(blend)).@"fn".params.len >= 2 + diff --git a/apps/website/public/t27/specs/tri/utils/colors.t27 b/apps/website/public/t27/specs/tri/utils/colors.t27 new file mode 100644 index 0000000000..604d00e83c --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/colors.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module TriColors; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "colors_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/utils/config.t27 b/apps/website/public/t27/specs/tri/utils/config.t27 new file mode 100644 index 0000000000..dbb78de17b --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/config.t27 @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Values are parsed as their natural type | φ² + 1/φ² = 3 | TRINITY + +module TriConfig; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const ConfigValue = struct { + string : ?[]const u8, + number : ?f64, + boolean : ?bool, + is_null : bool, + }; + + pub const ConfigEntry = struct { + key : []const u8, + value : ConfigValue, + }; + + pub const Config = struct { + entries : []ConfigEntry, + error : ?[]const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // parse(allocator: std.mem.Allocator, content: []const u8) → !Config + fn parse(allocator: std.mem.Allocator, content: []const u8) -> !Config { + // TODO: Implement from .tri spec + } + + // get_string(config: Config, key: []const u8, default: []const u8) → []const u8 + fn get_string(config: Config, key: []const u8, default: []const u8) -> []const u8 { + // TODO: Implement from .tri spec + } + + // get_number(config: Config, key: []const u8, default: f64) → f64 + fn get_number(config: Config, key: []const u8, default: f64) -> f64 { + // TODO: Implement from .tri spec + } + + // get_bool(config: Config, key: []const u8, default: bool) → bool + fn get_bool(config: Config, key: []const u8, default: bool) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // parse and the three getters are unimplemented stubs -- each emits + // `@panic("not yet implemented")`, so no parsing can be exercised. The + // declared value model is what remains testable. + + test each_natural_type_is_its_own_optional_slot + // "Values are parsed as their natural type": three payload slots, each + // optional, so an unset slot is distinguishable from a set one. + then @FieldType(ConfigValue, "string") == ?[]const u8 + and @FieldType(ConfigValue, "number") == ?f64 + and @FieldType(ConfigValue, "boolean") == ?bool + + test null_is_a_flag_not_a_missing_payload + // is_null is the only non-optional field: an explicit null in the + // source is recorded here, not by leaving all three slots empty. + then @FieldType(ConfigValue, "is_null") == bool + + test numbers_are_double_precision + // f64, not f32: a config literal keeps full double precision. + then @FieldType(ConfigValue, "number") == ?f64 + + test parse_failure_is_reported_in_band + // Config.error is optional rather than parse returning a Zig error, so + // a caller gets a partially built Config plus a message. + then @FieldType(Config, "error") == ?[]const u8 + and @FieldType(Config, "entries") == []ConfigEntry + + test an_entry_pairs_a_key_with_one_value + then @typeInfo(ConfigEntry).@"struct".fields.len == 2 + and @FieldType(ConfigEntry, "key") == []const u8 + and @FieldType(ConfigEntry, "value") == ConfigValue + diff --git a/apps/website/public/t27/specs/tri/utils/error.t27 b/apps/website/public/t27/specs/tri/utils/error.t27 new file mode 100644 index 0000000000..1368040c70 --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/error.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Convert error to exit code (1-9) | φ² + 1/φ² = 3 | TRINITY + +module TriError; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const TriError = struct { + // Recovered from the upstream spec this file was converted from: + // trinity-fpga specs/tri/tri_error.tri:8. The converter dropped bare `- name` bullets. + enum : [command_not_found, invalid_arguments, missing_argument, file_not_found, io_error, permission_denied, parse_error, validation_error, out_of_memory], + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "error_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/utils/exit_codes.t27 b/apps/website/public/t27/specs/tri/utils/exit_codes.t27 new file mode 100644 index 0000000000..8693b4aba9 --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/exit_codes.t27 @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | | φ² + 1/φ² = 3 | TRINITY + +module TriExitCodes; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + // The CONTROL CASE for the converter bug (#2717): these bullets are + // written `- success: 0`, WITH a value, so they parsed as fields and + // survived where bare bullets were dropped. What was left behind is the + // `variants : ,` marker line and a set of fields wearing dashes. + pub const ExitCode = enum(u8) { + success = 0, + command_error = 1, + validation_error = 2, + runtime_error = 3, + timeout = 4, + job_failed = 5, + artifact_failed = 6, + internal_error = 7, + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "exit_codes_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/utils/help.t27 b/apps/website/public/t27/specs/tri/utils/help.t27 new file mode 100644 index 0000000000..57f2a883f5 --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/help.t27 @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | φ² + 1/φ² = 3 | TRINITY + +module TriHelp; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const HelpOptions = struct { + category : ?CommandCategory, // default: null + search : ?[]const U8, // default: null + verbose : Bool, // default: false + }; + + pub const HelpSystem = struct { + registry : *const CommandRegistry, + allocator : std.mem.Allocator, + terminal_width : Usize, // default: 80 + }; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "help_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/utils/logger.t27 b/apps/website/public/t27/specs/tri/utils/logger.t27 new file mode 100644 index 0000000000..fd39051fbc --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/logger.t27 @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Structured logging | φ² + 1/φ² = 3 | TRINITY + +module "[]const u8"; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Level = struct { + enum : [Trace, Debug, Info, Warn, Error, Fatal], + }; + + pub const LogEntry = struct { + timestamp : Instant, + level : Level, + message : []const u8, + }; + + pub const Logger = struct { + min_level : Level, + writers : []LogWriter, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // new(name: []const u8, min_level: Level) → Logger + fn new(name: []const u8, min_level: Level) -> Logger { + // TODO: Implement from .tri spec + } + + // log(logger: *Logger, level: Level, message: []const u8) → void + fn log(logger: *Logger, level: Level, message: []const u8) -> void { + // TODO: Implement from .tri spec + } + + // with_field(entry: *LogEntry, key: []const u8, value: []const u8) → void + fn with_field(entry: *LogEntry, key: []const u8, value: []const u8) -> void { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test new_basic_case + given input = default_input() + when result = new(input) + then result != undefined + + test log_basic_case + given input = default_input() + when result = log(input) + then result != undefined + + test with_field_basic_case + given input = default_input() + when result = with_field(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/utils/logging.t27 b/apps/website/public/t27/specs/tri/utils/logging.t27 new file mode 100644 index 0000000000..e9e68d8edb --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/logging.t27 @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Uses ANSI colors for terminal output | φ² + 1/φ² = 3 | TRINITY + +module TriLogging; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const LogLevel = struct { + enum : [debug, info, warn, error], + }; + + pub const LogEntry = struct { + level : LogLevel, + message : []const u8, + timestamp : u64, + tag : ?[]const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // level_to_string(level: LogLevel) → []const u8 + fn level_to_string(level: LogLevel) -> []const u8 { + // TODO: Implement from .tri spec + } + + // level_from_string(s: []const u8) → ?LogLevel + fn level_from_string(s: []const u8) -> ?LogLevel { + // TODO: Implement from .tri spec + } + + // level_color(level: LogLevel) → []const u8 + fn level_color(level: LogLevel) -> []const u8 { + // TODO: Implement from .tri spec + } + + // format_entry(allocator: std.mem.Allocator, entry: LogEntry) → ![]u8 + fn format_entry(allocator: std.mem.Allocator, entry: LogEntry) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // should_log(msg_level: LogLevel, min_level: LogLevel) → bool + fn should_log(msg_level: LogLevel, min_level: LogLevel) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // Every function above is an unimplemented stub: each one emits + // `@panic("not yet implemented")`, so calling any of them aborts the test + // binary rather than failing a test. The assertions below are therefore + // about the representation the module actually declares. + + test an_entry_is_a_level_a_message_a_timestamp_and_an_optional_tag + // Four fields and no more: no allocator, no formatted buffer. The tag + // is the only optional one -- an untagged entry is still an entry. + then @typeInfo(LogEntry).@"struct".fields.len == 4 + and @FieldType(LogEntry, "level") == LogLevel + and @FieldType(LogEntry, "message") == []const u8 + and @FieldType(LogEntry, "timestamp") == u64 + and @typeInfo(@FieldType(LogEntry, "tag")).optional.child == []const u8 + and @hasField(LogEntry, "allocator") == false + + test the_timestamp_is_wide_enough_for_nanoseconds + // u64 nanoseconds since the epoch runs past year 2500; u32 seconds + // would have wrapped in 2106. The declared width is the deciding fact. + given ns_per_year = 31536000000000000 + then @FieldType(LogEntry, "timestamp") == u64 + and std.math.maxInt(u64) / ns_per_year > 500 + and std.math.maxInt(u32) / ns_per_year == 0 + + test an_untagged_entry_is_constructible + // format_entry is a stub, so this builds the value directly: the point + // is that the declared type admits a null tag at all. + given e = LogEntry{ .level = LogLevel.debug, .message = "boot", .timestamp = 0, .tag = null } + then e.tag == null + and e.timestamp == 0 + and std.mem.eql(u8, e.message, "boot") + + test log_level_lists_its_severities + // The spec used to declare `enum : ,` -- an empty variant list -- so + // LogLevel emitted with no severities at all: nothing for + // level_to_string to name or for should_log to compare, which made + // every LogEntry.level identical. The four names were recovered from + // the upstream spec this file was converted from: trinity-fpga + // specs/tri/tri_logging.tri:7. `error` is a Zig keyword, so the emitter + // escapes it -- the variant is spelled @"error" in the emitted enum but + // its reflected name is still "error". + then @typeInfo(LogLevel).@"enum".fields.len == 4 + and std.mem.eql(u8, @typeInfo(LogLevel).@"enum".fields[0].name, "debug") + and std.mem.eql(u8, @typeInfo(LogLevel).@"enum".fields[3].name, "error") + and @intFromEnum(LogLevel.debug) < @intFromEnum(LogLevel.warn) + diff --git a/apps/website/public/t27/specs/tri/utils/random.t27 b/apps/website/public/t27/specs/tri/utils/random.t27 new file mode 100644 index 0000000000..0616d590dc --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/random.t27 @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Xorshift64* PRNG algorithm | φ² + 1/φ² = 3 | TRINITY + +module TriRandom; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Rng = struct { + state : u64, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // init(seed: u64) → Rng + fn init(seed: u64) -> Rng { + // TODO: Implement from .tri spec + } + + // next(rng: *Rng) → u64 + fn next(rng: *Rng) -> u64 { + // TODO: Implement from .tri spec + } + + // range(rng: *Rng, max: u64) → u64 + fn range(rng: *Rng, max: u64) -> u64 { + // TODO: Implement from .tri spec + } + + // range_inclusive(rng: *Rng, min: i64, max: i64) → i64 + fn range_inclusive(rng: *Rng, min: i64, max: i64) -> i64 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test rng_state_is_exactly_one_64_bit_word + // Verify: xorshift64* carries all of its state in a single u64 — no + // counter, no stream selector, nothing else + given width = @sizeOf(Rng) + then width == 8 + + test a_zero_state_is_a_fixed_point_of_the_xorshift + // Verify: the three xorshift rounds are xor and shift only, so 0 maps to + // 0 forever. init() must therefore refuse or replace seed 0, or next() + // returns zero for the rest of the run. + given seed = Rng{ .state = 0 } + when x1 = seed.state ^ (seed.state >> 12) + and x2 = x1 ^ (x1 << 25) + and x3 = x2 ^ (x2 >> 27) + then x3 == 0 + + test a_non_zero_state_leaves_zero_after_one_round + // Verify: the same three rounds on the reference seed 88172645463325252 + // do not collapse, which is what makes the zero case a special case + given seed = Rng{ .state = 88172645463325252 } + when x1 = seed.state ^ (seed.state >> 12) + and x2 = x1 ^ (x1 << 25) + and x3 = x2 ^ (x2 >> 27) + then x3 != 0 + + test a_right_xorshift_composed_with_itself_doubles_its_distance + // Verify: for f(x) = x ^ (x >> 12), f(f(x)) = x ^ (x >> 24), because the + // two copies of x >> 12 cancel. f is therefore not an involution, and the + // inverse of one round needs the shifts 12, 24, 48 applied in turn. + given seed = Rng{ .state = 88172645463325252 } + when once = seed.state ^ (seed.state >> 12) + and twice = once ^ (once >> 12) + and expected = seed.state ^ (seed.state >> 24) + then twice == expected and twice != seed.state + + test the_multiplier_is_the_star_in_xorshift64_star + // Verify: the output stage multiplies by 2685821657736338717, which is + // odd and therefore invertible modulo 2^64 — it scrambles the bits + // without ever mapping two states onto one output + given multiplier = 2685821657736338717 + when parity = multiplier % 2 + then parity == 1 + + test range_endpoints_bound_a_half_open_interval + // Verify: range(lo, hi) spans hi - lo values and range_inclusive spans one + // more, which is the only difference between the two entry points + given lo = 3 + and hi = 7 + when half_open = hi - lo + and inclusive = hi - lo + 1 + then half_open == 4 and inclusive == 5 + diff --git a/apps/website/public/t27/specs/tri/utils/string.t27 b/apps/website/public/t27/specs/tri/utils/string.t27 new file mode 100644 index 0000000000..8bb7b21511 --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/string.t27 @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// | φ² + 1/φ² = 3 | TRINITY + +module string; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test "string_smoke_test" { + expect(true) + } diff --git a/apps/website/public/t27/specs/tri/utils/template.t27 b/apps/website/public/t27/specs/tri/utils/template.t27 new file mode 100644 index 0000000000..489fc8cabb --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/template.t27 @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// {{variable}} syntax | φ² + 1/φ² = 3 | TRINITY + +module TriTemplate; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Template = struct { + parts : []TemplatePart, + }; + + pub const TemplatePart = struct { + is_literal : bool, + text : []const u8, + variable : []const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // compile(source: []const u8, allocator: std.mem.Allocator) → !Template + fn compile(source: []const u8, allocator: std.mem.Allocator) -> !Template { + // TODO: Implement from .tri spec + } + + // render(template: Template, context: [td.StringHashMap([]Const u8), allocator: std.mem.Allocator) → ![]u8 + fn render(template: Template, context: [td.StringHashMap([]Const u8), allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test literal_part_carries_text_and_no_variable + // Verify: is_literal = true selects `text`; the variable slot is empty + given part = TemplatePart{ .is_literal = true, .text = "Hello ", .variable = "" } + then part.is_literal and part.text.len == 6 and part.variable.len == 0 + + test variable_part_stores_the_bare_name_without_braces + // Verify: the {{variable}} syntax -- a variable part holds "name", four + // bytes, not the eight bytes of "{{name}}" + given part = TemplatePart{ .is_literal = false, .text = "", .variable = "name" } + then part.is_literal == false and part.variable.len == 4 and part.text.len == 0 + + test compiled_template_alternates_literal_and_variable_parts + // Verify: "Hello {{name}}!" compiles to three parts -- literal, then + // variable, then literal -- so rendering is a walk over parts in order + given head = TemplatePart{ .is_literal = true, .text = "Hello ", .variable = "" } + and slot = TemplatePart{ .is_literal = false, .text = "", .variable = "name" } + and tail = TemplatePart{ .is_literal = true, .text = "!", .variable = "" } + and parts = [_]TemplatePart{ head, slot, tail } + and t = Template{ .parts = @constCast(&parts) } + then t.parts.len == 3 and t.parts[0].is_literal and t.parts[1].is_literal == false + + test a_template_with_no_placeholders_is_a_single_literal_part + // Verify: the boundary -- source without {{ }} compiles to one part + // whose text is the whole source + given only = TemplatePart{ .is_literal = true, .text = "no slots here", .variable = "" } + and parts = [_]TemplatePart{only} + and t = Template{ .parts = @constCast(&parts) } + then t.parts.len == 1 and t.parts[0].is_literal and t.parts[0].text.len == 13 + diff --git a/apps/website/public/t27/specs/tri/utils/terminal.t27 b/apps/website/public/t27/specs/tri/utils/terminal.t27 new file mode 100644 index 0000000000..bb77b565a1 --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/terminal.t27 @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Uses ANSI escape codes | φ² + 1/φ² = 3 | TRINITY + +module TriTerminal; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Color = struct { + enum : [black, red, green, yellow, blue, magenta, cyan, white, default], + }; + + pub const Style = struct { + enum : [bold, dim, italic, underline, reverse], + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // get_size() → TerminalSize + fn get_size() -> TerminalSize { + // TODO: Implement from .tri spec + } + + // colorize(allocator: std.mem.Allocator, text: []const u8, fg: Color) → ![]u8 + fn colorize(allocator: std.mem.Allocator, text: []const u8, fg: Color) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // reset() → []const u8 + fn reset() -> []const u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test get_size_basic_case + given input = default_input() + when result = get_size(input) + then result != undefined + + test colorize_basic_case + given input = default_input() + when result = colorize(input) + then result != undefined + + test reset_basic_case + given input = default_input() + when result = reset(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/utils/text.t27 b/apps/website/public/t27/specs/tri/utils/text.t27 new file mode 100644 index 0000000000..ba6a6532bd --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/text.t27 @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Basic ASCII text processing | φ² + 1/φ² = 3 | TRINITY + +module TriText; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const TextMetrics = struct { + width : usize, + height : usize, + lines : usize, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // word_wrap(allocator: std.mem.Allocator, text: []const u8, width: usize) → ![]u8 + fn word_wrap(allocator: std.mem.Allocator, text: []const u8, width: usize) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // count_words(text: []const u8) → usize + fn count_words(text: []const u8) -> usize { + // TODO: Implement from .tri spec + } + + // count_lines(text: []const u8) → usize + fn count_lines(text: []const u8) -> usize { + // TODO: Implement from .tri spec + } + + // indent(allocator: std.mem.Allocator, text: []const u8, spaces: usize) → ![]u8 + fn indent(allocator: std.mem.Allocator, text: []const u8, spaces: usize) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + test word_wrap_basic_case + given input = default_input() + when result = word_wrap(input) + then result != undefined + + test count_words_basic_case + given input = default_input() + when result = count_words(input) + then result != undefined + + test count_lines_basic_case + given input = default_input() + when result = count_lines(input) + then result != undefined + + test indent_basic_case + given input = default_input() + when result = indent(input) + then result != undefined + diff --git a/apps/website/public/t27/specs/tri/utils/time.t27 b/apps/website/public/t27/specs/tri/utils/time.t27 new file mode 100644 index 0000000000..d542b3aec7 --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/time.t27 @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Monotonic clock | φ² + 1/φ² = 3 | TRINITY + +module TriTime; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Instant = struct { + epoch_seconds : i64, + nanos : u32, + }; + + pub const Duration = struct { + seconds : i64, + nanos : u32, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // now() → Instant + fn now() -> Instant { + // TODO: Implement from .tri spec + } + + // since_epoch(instant: Instant) → Duration + fn since_epoch(instant: Instant) -> Duration { + // TODO: Implement from .tri spec + } + + // add(instant: Instant, duration: Duration) → Instant + fn add(instant: Instant, duration: Duration) -> Instant { + // TODO: Implement from .tri spec + } + + // sub(a: Instant, b: Instant) → Duration + fn sub(a: Instant, b: Instant) -> Duration { + // TODO: Implement from .tri spec + } + + // format(instant: Instant, fmt: []const u8, allocator: std.mem.Allocator) → ![]u8 + fn format(instant: Instant, fmt: []const u8, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // now, since_epoch, add, sub and format are still `TODO: Implement from + // .tri spec` and compile to `@panic`, so none of them is called here. The + // split-second representation they all operate on is real and is what the + // tests below pin down: whole seconds in a signed field, the sub-second + // remainder in an unsigned one that never reaches a full second. + + // The epoch itself. Both fields zero, and the seconds field is signed, so + // instants before 1970 are representable rather than clamped. + test instant_epoch_and_before + given epoch = Instant{ .epoch_seconds = 0, .nanos = 0 } + and before = Instant{ .epoch_seconds = -1, .nanos = 0 } + then epoch.epoch_seconds == 0 + and epoch.nanos == 0 + and before.epoch_seconds < epoch.epoch_seconds + + // The nanos field is the sub-second remainder: it holds up to 999_999_999, + // one less than a whole second, and u32 has room for it. + test instant_nanos_hold_a_sub_second_remainder + given tick = Instant{ .epoch_seconds = 1, .nanos = 999999999 } + then tick.nanos == 999999999 + and tick.nanos < 1000000000 + + // Duration has the same two-field shape, and its seconds field is signed + // too, so the difference of two instants can run either way. + test duration_is_signed + given forward = Duration{ .seconds = 90, .nanos = 500000000 } + and backward = Duration{ .seconds = -90, .nanos = 500000000 } + then forward.seconds == 90 + and backward.seconds == -90 + and forward.nanos == backward.nanos + + // A zero duration is representable and is not a sentinel for anything. + test zero_duration_is_representable + given d = Duration{ .seconds = 0, .nanos = 0 } + then d.seconds == 0 + and d.nanos == 0 + diff --git a/apps/website/public/t27/specs/tri/utils/utf8.t27 b/apps/website/public/t27/specs/tri/utils/utf8.t27 new file mode 100644 index 0000000000..3d199df6c2 --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/utf8.t27 @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Full Unicode support | φ² + 1/φ² = 3 | TRINITY + +module TriUtf8; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Codepoint = struct { + underlying : U21, + }; + + pub const Rune = struct { + bytes : [4]u8, + len : u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // decode(str: []const u8, index: usize) → Rune + fn decode(str: []const u8, index: usize) -> Rune { + // TODO: Implement from .tri spec + } + + // encode(codepoint: Codepoint, allocator: std.mem.Allocator) → ![]u8 + fn encode(codepoint: Codepoint, allocator: std.mem.Allocator) -> ![]u8 { + // TODO: Implement from .tri spec + } + + // count_codepoints(str: []const u8) → usize + fn count_codepoints(str: []const u8) -> usize { + // TODO: Implement from .tri spec + } + + // validate(str: []const u8) → bool + fn validate(str: []const u8) -> bool { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // decode/encode/count_codepoints/validate are unimplemented stubs returning + // void, so no behavioural assertion is possible yet. What the module does + // declare -- the width of a codepoint and the size of a rune buffer -- is + // tested here at both boundaries. + + test codepoint_holds_the_largest_unicode_scalar + // Verify: the declared u21 field is wide enough for U+10FFFF, the + // highest codepoint Unicode defines + given cp = Codepoint{.underlying=0x10FFFF} + then cp.underlying == 1114111 + + test codepoint_holds_the_null_scalar + // Verify: U+0000, the boundary at the other end, is representable + given cp = Codepoint{.underlying=0} + then cp.underlying == 0 + + test rune_buffer_is_four_bytes_the_utf8_maximum + // Verify: the declared fixed buffer matches the longest UTF-8 sequence + given r = Rune{.bytes=[_]u8{0,0,0,0},.len=0} + then r.bytes.len == 4 + + test ascii_rune_occupies_one_byte + // Verify: an ASCII character uses a single byte of the 4-byte buffer + given r = Rune{.bytes=[_]u8{65,0,0,0},.len=1} + then r.len == 1 and r.bytes[0] == 65 + + test four_byte_rune_fills_the_whole_buffer + // Verify: U+10FFFF encodes as F4 8F BF BF -- four bytes, exactly the + // declared buffer size + given r = Rune{.bytes=[_]u8{0xF4,0x8F,0xBF,0xBF},.len=4} + then r.len == 4 and r.bytes[3] == 0xBF + diff --git a/apps/website/public/t27/specs/tri/utils/version.t27 b/apps/website/public/t27/specs/tri/utils/version.t27 new file mode 100644 index 0000000000..6456e37fbb --- /dev/null +++ b/apps/website/public/t27/specs/tri/utils/version.t27 @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/ +// Semantic versioning | φ² + 1/φ² = 3 | TRINITY + +module TriVersion; + use base::types; + use math::constants; + + // ═══════════════════════════════════════════════════════════ + // 2. Types + // ═══════════════════════════════════════════════════════════ + + pub const Version = struct { + major : usize, + minor : usize, + patch : usize, + prerelease : []const u8, + build : []const u8, + }; + + // ═══════════════════════════════════════════════════════════ + // 3. Core Functions + // ═══════════════════════════════════════════════════════════ + + // parse(version_str: []const u8, allocator: std.mem.Allocator) → !Version + fn parse(version_str: []const u8, allocator: std.mem.Allocator) -> !Version { + // TODO: Implement from .tri spec + } + + // compare(a: Version, b: Version) → i8 + fn compare(a: Version, b: Version) -> i8 { + // TODO: Implement from .tri spec + } + + // next(version: Version, part: []const u8, allocator: std.mem.Allocator) → !Version + fn next(version: Version, part: []const u8, allocator: std.mem.Allocator) -> !Version { + // TODO: Implement from .tri spec + } + + // ═══════════════════════════════════════════════════════════ + // TDD: Tests (from .tri behaviors) + // ═══════════════════════════════════════════════════════════ + + // parse, compare and next are still `TODO: Implement from .tri spec` and + // compile to `@panic`, so nothing below calls them. What can be asserted + // today is the shape of Version: five fields, the three numeric ones and + // the two textual ones that SemVer 2.0.0 keeps separate. + + // A full 1.2.3-rc.1+build.5 reads back field by field. + test version_carries_all_five_fields + given v = Version{ .major = 1, .minor = 2, .patch = 3, .prerelease = "rc.1", .build = "build.5" } + then v.major == 1 + and v.minor == 2 + and v.patch == 3 + and std.mem.eql(u8, v.prerelease, "rc.1") + and std.mem.eql(u8, v.build, "build.5") + + // A release version is the same shape with both textual fields empty -- + // absence is an empty string, not a separate case. + test release_version_has_empty_text_fields + given v = Version{ .major = 2, .minor = 0, .patch = 0, .prerelease = "", .build = "" } + then v.prerelease.len == 0 + and v.build.len == 0 + and v.major == 2 + + // 0.0.0 is representable: the numeric fields are unsigned and have no + // reserved value standing in for "unset". + test zero_version_is_representable + given v = Version{ .major = 0, .minor = 0, .patch = 0, .prerelease = "", .build = "" } + then v.major == 0 + and v.minor == 0 + and v.patch == 0 + + // prerelease and build are two independent fields, not one packed string: + // SemVer orders by the first and ignores the second, so a version may + // carry either without the other. + test prerelease_and_build_are_independent + given pre_only = Version{ .major = 1, .minor = 0, .patch = 0, .prerelease = "rc.1", .build = "" } + and build_only = Version{ .major = 1, .minor = 0, .patch = 0, .prerelease = "", .build = "sha.abc" } + then pre_only.prerelease.len > 0 + and pre_only.build.len == 0 + and build_only.prerelease.len == 0 + and build_only.build.len > 0 + diff --git a/apps/website/public/t27/specs/vm/jit_semantics.t27 b/apps/website/public/t27/specs/vm/jit_semantics.t27 new file mode 100644 index 0000000000..56de177fa6 --- /dev/null +++ b/apps/website/public/t27/specs/vm/jit_semantics.t27 @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: Apache-2.0 +// Module: JIT Compilation Semantics +// phi^2 + 1/phi^2 = 3 | TRINITY + +module JitSemantics { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; // Trit enum + use ternary::hybrid_arithmetic; // HybridBigInt for operands + use numeric::gf16; // GF16 for numeric values + + // ======================================================================== + // 1. Function Pointer Types + // ======================================================================== + + // JitVsaFn: JIT-compiled function pointer for VSA operations + // Takes two HybridBigInt pointers and writes result to first pointer + // Signature: fn(*HybridBigInt, *HybridBigInt) -> void + pub const JitVsaFn = *const fn (*anyopaque, *anyopaque) void; + + // JitSimilarityFn: JIT-compiled function pointer for similarity + // Takes two HybridBigInt pointers and returns f64 similarity score + // Signature: fn(*HybridBigInt, *HybridBigInt) -> f64 + pub const JitSimilarityFn = *const fn (*anyopaque, *anyopaque) f64; + + // ======================================================================== + // 2. JIT Compiler Type + // ======================================================================== + + // JitCompiler: Code generation engine for ternary operations + // Generates platform-specific machine code for VSA operations + // Manages code buffer, executable memory allocation, and function finalization + pub struct JitCompiler { + code_buffer : []u8, // Generated machine code bytes + allocator_id : u16, // Allocator identifier (implementation detail) + exec_memory : ?[]u8, // Executable memory region (mmap'd) + } + + // ======================================================================== + // 3. JIT Cache Type + // ======================================================================== + + // JitCache: Compilation cache for dimension-specific operations + // Caches compiled functions by dimension to avoid redundant compilation + // Maintains compiler instance for on-demand compilation + pub struct JitCache { + bind_cache : map, // Cached bind functions by dimension + compiler : JitCompiler, // Compiler instance for compilation + allocator_id : u16, // Allocator identifier + } + + // ======================================================================== + // 4. VSA Operation Types (for JIT compilation) + // ======================================================================== + + // VsaOperation: Enumeration of VSA operations to compile + // Different operations require different code generation strategies + pub const VsaOperation = enum(u8) { + bind, // Element-wise multiplication (bind) + bundle, // Element-wise sum with threshold (bundle) + dot_product, // Dot product for similarity + }; + + // ======================================================================== + // 5. JIT Compiler Functions + // ======================================================================== + + // initCompiler(allocator_id: u16) -> JitCompiler + // Create a new JIT compiler instance + // Returns initialized JitCompiler with empty code buffer + // Complexity: O(1) + pub fn initCompiler(allocator_id: u16) -> JitCompiler; + + // deinitCompiler(compiler: &JitCompiler) + // Destroy JIT compiler and free resources + // Releases code buffer and executable memory + // Complexity: O(1) + memory deallocation + pub fn deinitCompiler(compiler: &JitCompiler); + + // resetCompiler(compiler: &JitCompiler) + // Reset code buffer for new compilation + // Retains allocated capacity but clears content + // Complexity: O(n) where n = current code buffer size + pub fn resetCompiler(compiler: &JitCompiler); + + // compileOperation(compiler: &JitCompiler, op: VsaOperation, dimension: usize) -> usize + // Compile a VSA operation for given dimension + // Returns code size in bytes of generated code + // Generates platform-specific machine code for the operation + // Complexity: O(dimension) for code generation + pub fn compileOperation(compiler: &JitCompiler, op: VsaOperation, dimension: usize) -> usize; + + // finalizeCompiler(compiler: &JitCompiler) -> ?JitVsaFn + // Make generated code executable and return function pointer + // Allocates executable memory, copies code, sets permissions + // Returns null pointer if code buffer is empty + // Complexity: O(1) for mmap + copy + mprotect + pub fn finalizeCompiler(compiler: &JitCompiler) -> ?JitVsaFn; + + // codeSize(compiler: &JitCompiler) -> usize + // Get current size of generated code + // Returns byte count of code buffer + // Complexity: O(1) + pub fn codeSize(compiler: &JitCompiler) -> usize; + + // ======================================================================== + // 6. JIT Cache Functions + // ======================================================================== + + // initCache(allocator_id: u16) -> JitCache + // Create a new JIT cache instance + // Returns initialized JitCache with empty cache and compiler + // Complexity: O(1) + pub fn initCache(allocator_id: u16) -> JitCache; + + // deinitCache(cache: &JitCache) + // Destroy JIT cache and free all cached functions + // Releases compiler instance and clears cache map + // Complexity: O(n) where n = cached functions + pub fn deinitCache(cache: &JitCache); + + // getOrCompile(cache: &JitCache, op: VsaOperation, dimension: usize) -> ?JitVsaFn + // Get cached function or compile if not cached + // Returns function pointer or compiles new function if needed + // Complexity: O(1) for cache hit, O(dimension) for compile + pub fn getOrCompile(cache: &JitCache, op: VsaOperation, dimension: usize) -> ?JitVsaFn; + + // ======================================================================== + // 7. High-Level JIT API + // ======================================================================== + + // jitBind(cache: &JitCache, a: &HybridBigInt, b: &HybridBigInt) + // Execute JIT-accelerated bind operation + // Ensures operands are in unpacked mode, gets or compiles function, executes + // Complexity: O(dimension) for compile (cached: O(1)) + pub fn jitBind(cache: &JitCache, a: &HybridBigInt, b: &HybridBigInt); + + // jitBundle(cache: &JitCache, a: &HybridBigInt, b: &HybridBigInt) + // Execute JIT-accelerated bundle operation + // Ensures operands are in unpacked mode, gets or compiles function, executes + // Complexity: O(dimension) for compile (cached: O(1)) + pub fn jitBundle(cache: &JitCache, a: &HybridBigInt, b: &HybridBigInt); + + // jitDotProduct(cache: &JitCache, a: &HybridBigInt, b: &HybridBigInt) -> f64 + // Execute JIT-accelerated dot product operation + // Ensures operands are in unpacked mode, gets or compiles function, executes + // Returns dot product as f64 similarity score + // Complexity: O(dimension) for compile (cached: O(1)) + pub fn jitDotProduct(cache: &JitCache, a: &HybridBigInt, b: &HybridBigInt) -> f64; + + // ======================================================================== + // TDD - Tests + // ======================================================================== + + test jit_compiler_init_creates_valid_compiler + // Verify: initCompiler returns valid compiler + given compiler = initCompiler(0) + then compiler.code_buffer.len() == 0 + + test jit_compiler_reset_clears_buffer + // Verify: resetCompiler clears code buffer + given compiler = initCompiler(0) + and compileOperation(&compiler, .bind, 16) + when resetCompiler(&compiler) + then compiler.code_buffer.len() == 0 + + test jit_compile_operation_generates_code + // Verify: compileOperation generates code for bind operation + given compiler = initCompiler(0) + when size = compileOperation(&compiler, .bind, 32) + then size > 0 + + test jit_finalize_returns_function_ptr + // Verify: finalizeCompiler creates executable function + given compiler = initCompiler(0) + and compileOperation(&compiler, .bind, 16) + when func = finalizeCompiler(&compiler) + then func != null + + test jit_cache_init_creates_valid_cache + // Verify: initCache returns valid cache + given cache = initCache(0) + then cache.compiler.code_buffer.len() == 0 + + test jit_cache_deinit_clears_resources + // Verify: deinitCache clears cache + given cache = initCache(0) + and _ = getOrCompile(&cache, .bind, 16) + when deinitCache(&cache) + then cache.compiler.code_buffer.len() == 0 + + test jit_cache_hit_returns_cached_function + // Verify: getOrCompile returns cached function on second call + given cache = initCache(0) + and func1 = getOrCompile(&cache, .bind, 16) + and func2 = getOrCompile(&cache, .bind, 16) + then func1 == func2 + + test jit_cache_miss_compiles_new_function + // Verify: getOrCompile compiles on cache miss + given cache = initCache(0) + and func1 = getOrCompile(&cache, .bind, 16) + and func2 = getOrCompile(&cache, .dot_product, 16) + then func1 != func2 + + test jit_dot_product_returns_f64 + // Verify: jitDotProduct returns f64 similarity score + given cache = initCache(0) + and _ = getOrCompile(&cache, .dot_product, 16) + when result = jitDotProduct(&cache, a_dummy, b_dummy) + then result > 0.0 or result < 0.0 + + // ======================================================================== + // TDD - Invariants + // ======================================================================== + + invariant jit_compiler_code_buffer_allocated + // Verify: JitCompiler code buffer is initialized + // Compiler must have valid code buffer + assert true; + + invariant jit_compiler_executable_memory_protected + // Verify: Executable memory is set to read+execute only + // Finalized code must be in non-writable executable region + assert true; + + invariant jit_cache_keyed_by_dimension + // Verify: JIT cache is keyed by dimension + // Same dimension returns same function, different dimension may compile new + assert true; + + invariant jit_cache_preserves_function_pointers + // Verify: Cached functions remain valid across cache operations + // Function pointers from cache must be valid until cache deinit + assert true; + + invariant vsa_operation_enum_valid + // Verify: VsaOperation enum has valid values + // bind, bundle, dot_product must be distinct + assert true; + + invariant jit_function_signatures_compatible + // Verify: JIT function signatures match expected calling convention + // JitVsaFn takes two HybridBigInt pointers + assert true; + + // ======================================================================== + // TDD - Benchmarks + // ======================================================================== + + bench jit_compile_bind_latency + // Measure: cycles for compiling bind operation (dimension=32) + // Target: < 1000 cycles for code generation + @setEvalBranchQuota(10000); + var compiler = initCompiler(0); + _ = compileOperation(&compiler, .bind, 32); + _ = compiler; + + bench jit_compile_bundle_latency + // Measure: cycles for compiling bundle operation (dimension=32) + // Target: < 1500 cycles for code generation (more complex) + @setEvalBranchQuota(10000); + var compiler = initCompiler(0); + _ = compileOperation(&compiler, .bundle, 32); + _ = compiler; + + bench jit_compile_dot_product_latency + // Measure: cycles for compiling dot product (dimension=32) + // Target: < 2000 cycles for code generation (most complex) + @setEvalBranchQuota(10000); + var compiler = initCompiler(0); + _ = compileOperation(&compiler, .dot_product, 32); + _ = compiler; + + bench jit_cache_get_hit_latency + // Measure: cycles for cache hit (function already compiled) + // Target: < 100 cycles (hash map lookup) + @setEvalBranchQuota(10000); + var cache = initCache(0); + _ = getOrCompile(&cache, .bind, 32); + _ = getOrCompile(&cache, .bind, 32); + _ = cache; + + bench jit_cache_get_miss_latency + // Measure: cycles for cache miss (compilation required) + // Target: < 1500 cycles (compile + cache insert) + @setEvalBranchQuota(10000); + var cache = initCache(0); + _ = getOrCompile(&cache, .bind, 32); + _ = cache; + + bench jit_finalize_latency + // Measure: cycles for finalizing compiled code + // Target: < 500 cycles (mmap + copy + mprotect) + @setEvalBranchQuota(10000); + var compiler = initCompiler(0); + _ = compileOperation(&compiler, .bind, 32); + _ = finalizeCompiler(&compiler); + + bench jit_bind_execution_latency + // Measure: cycles for executing JIT-compiled bind (dimension=32) + // Target: < 100 cycles (native call overhead) + @setEvalBranchQuota(10000); + var cache = initCache(0); + var func = getOrCompile(&cache, .bind, 32); + _ = func(dummy_ptr, dummy_ptr); + _ = func; + + bench jit_dot_product_execution_latency + // Measure: cycles for executing JIT-compiled dot product (dimension=32) + // Target: < 500 cycles (native loop) + @setEvalBranchQuota(10000); + var cache = initCache(0); + var func = getOrCompile(&cache, .dot_product, 32); + _ = func(dummy_ptr, dummy_ptr); + _ = func; +} diff --git a/apps/website/public/t27/specs/vsa/jones_polynomial.t27 b/apps/website/public/t27/specs/vsa/jones_polynomial.t27 new file mode 100644 index 0000000000..6af457f11e --- /dev/null +++ b/apps/website/public/t27/specs/vsa/jones_polynomial.t27 @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/vsa/jones_polynomial.t27 +// Jones Polynomial -- Link invariant computed from input structure +// V(L, t) = (-t^(-3/4))^w(L) * where is Kauffman bracket + +module JonesPolynomial { + use math::constants; + + // =========================================================================== + // 1. Knot/Link Representation + // ========================================================================================= + + // Crossing sign: +1 for positive (overcrossing), -1 for negative (undercrossing) + const CROSSING_POSITIVE : i32 = 1; + const CROSSING_NEGATIVE : i32 = -1; + + // =========================================================================== + // 2. Writhe Calculation + // ========================================================================================= + + // Compute writhe w(L) from crossing signs + // w(L) = sum of crossing signs (positive = +1, negative = -1) + fn writhe(crossings: [i32]) -> i32 { + let mut w: i32 = 0; + for c in crossings { + w = w + c; + } + return w; + } + + // =========================================================================== + // 3. Kauffman Bracket + // ========================================================================================= + + // Skein relation for Kauffman bracket: + // = A + A^{-1} + // = A^{-1} + A + // = (A + A^{-1}) + // Where A = t^(-1/4), L_+ = positive crossing, L_- = negative crossing + + // A = t^(-1/4) parameter for Kauffman bracket + fn bracket_parameter(t: f64) -> f64 { + return pow(t, -0.25); + } + + // Kauffman bracket of unknot (trivial knot) + // = 1 + const BRACKET_UNKNOT : f64 = 1.0; + + // Compute Kauffman bracket from crossing signs (input structure) + // For small links, use known formulas; for larger, approximate + fn bracket_from_crossings(crossings: [i32], t: f64) -> f64 { + let A = bracket_parameter(t); + let n = crossings.len(); + let mut pos_count: i32 = 0; + let mut neg_count: i32 = 0; + + let mut i: u32 = 0; + while i < n { + if crossings[i] > 0 { + pos_count = pos_count + 1; + } else if crossings[i] < 0 { + neg_count = neg_count + 1; + } + i = i + 1; + } + + // Known bracket formulas for common knots + if n == 0 { + // Unknot + return BRACKET_UNKNOT; + } else if n == 3 and pos_count == 3 { + // Trefoil (all positive): = -A^7 - A^3 + return -pow(A, 7.0) - pow(A, 3.0); + } else if n == 3 and neg_count == 3 { + // Negative trefoil: = -A^{-7} - A^{-3} + return -pow(A, -7.0) - pow(A, -3.0); + } else if n == 4 and pos_count == 2 and neg_count == 2 { + // Figure-eight: = A^8 - A^4 + 1 - A^{-4} + A^{-8} + return pow(A, 8.0) - pow(A, 4.0) + 1.0 - pow(A, -4.0) + pow(A, -8.0); + } else { + // Generic approximation + let power = (pos_count as i32) - (neg_count as i32); + return pow(A, power as f64); + } + } + + // =========================================================================== + // 4. Jones Polynomial + // ========================================================================================= + + // Compute Jones polynomial V(L, t) from link structure (crossing signs) + // V(L, t) = (-t^(-3/4))^w(L) * + // Input: crossings = array of crossing signs (+1 or -1) representing link L + // Output: Jones polynomial value at variable t + fn jones_polynomial_from_structure(crossings: [i32], t: f64) -> f64 { + let w = writhe(crossings); + let bracket = bracket_from_crossings(crossings, t); + let factor = pow(-pow(t, -0.75), w as f64); + return factor * bracket; + } + + // Legacy: Compute Jones polynomial V(L, t) from writhe and Kauffman bracket + // V(L, t) = (-t^(-3/4))^w(L) * + fn jones_polynomial(t: f64, bracket: f64, w: i32) -> f64 { + let factor = pow(-pow(t, -0.75), w as f64); + return factor * bracket; + } + + // Jones polynomial for trefoil knot with variable t + fn jones_trefoil(t: f64) -> f64 { + // Trefoil crossings: 3 positive + let crossings: [i32] = [1, 1, 1]; + return jones_polynomial_from_structure(crossings, t); + } + + // Jones polynomial for trefoil knot with t = phi + // Expected: V(trefoil, phi) = phi^2 + 1 + fn jones_trefoil_at_phi() -> f64 { + return jones_trefoil(PHI); + } + + // =========================================================================== + // 5. Helper Functions + // ========================================================================================= + + // Power function (reused from constants.t27) + fn pow(x: f64, n: f64) -> f64 { + if x < 0.0 and n != floor(n) { + return 0.0 / 0.0; // NaN + } + if x == 0.0 { + if n > 0.0 { return 0.0; } + if n == 0.0 { return 1.0; } + return 1.0 / 0.0; + } + if n == 0.0 { return 1.0; } + + let negative = n < 0.0; + let exp = if negative { -n } else { n }; + let is_integer = exp == floor(exp); + + if is_integer { + let exp_int = exp as i64; + let mut result = 1.0; + let mut base = x; + let mut e = exp_int; + + while e > 0 { + if e % 2 == 1 { + result = result * base; + } + base = base * base; + e = e / 2; + } + + if negative { result = 1.0 / result; } + return result; + } + + // Fractional: use log/exp + let ln_x = ln(x); + let mut result = 1.0; + let mut term = 1.0; + for i in 1..=12 { + term = term * exp * ln_x / (i as f64); + result = result + term; + } + + if negative { result = 1.0 / result; } + return result; + } + + // Natural logarithm + fn ln(x: f64) -> f64 { + if x <= 0.0 { + return 0.0 / 0.0; + } + if x == 1.0 { + return 0.0; + } + let t = (x - 1.0) / (x + 1.0); + let t2 = t * t; + let t3 = t2 * t; + let t5 = t3 * t2; + let t7 = t5 * t2; + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Floor function + fn floor(x: f64) -> f64 { + let xi = x as i64; + if x >= 0.0 || x == xi as f64 { + return xi as f64; + } + return (xi - 1) as f64; + } + + // NaN check: true if x is Not-a-Number + fn isnan(x: f64) -> bool { + return x != x; + } + + // Infinity check: true if x is positive or negative infinity + fn isinf(x: f64) -> bool { + return x == 1.0 / 0.0 or x == -1.0 / 0.0; + } + + // =========================================================================== + // 6. TDD-Inside-Spec: Tests + // ========================================================================================= + + test writhe_empty_crossings + given crossings: [i32] = [] + when w = writhe(crossings) + then w == 0 + + test writhe_single_positive + given crossings: [i32] = [1] + when w = writhe(crossings) + then w == 1 + + test writhe_single_negative + given crossings: [i32] = [-1] + when w = writhe(crossings) + then w == -1 + + test writhe_mixed_crossings + given crossings: [i32] = [1, 1, -1, 1, -1, -1] + when w = writhe(crossings) + then w == 0 + + test writhe_trefoil + // Trefoil has 3 positive crossings + given crossings: [i32] = [1, 1, 1] + when w = writhe(crossings) + then w == 3 + + test bracket_parameter_phi + given t = PHI + when A = bracket_parameter(t) + then abs(A - pow(PHI, -0.25)) < 1e-6 + + test bracket_parameter_two + given t = 2.0 + when A = bracket_parameter(t) + then abs(A - pow(2.0, -0.25)) < 1e-6 + + test jones_trefoil_at_phi_approx + given result = jones_trefoil_at_phi() + and expected = PHI * PHI + 1.0 + // V(trefoil, phi) ~= phi^2 + 1 + when error = abs(result - expected) + then error < 0.1 // Allow 10% tolerance for numeric approximation + + test jones_trefoil_at_phi_identity + // phi^2 + 1 = phi + phi^2 (trivial from phi^2 = phi + 1) + given result = jones_trefoil_at_phi() + and phi_sq_plus_one = PHI * PHI + 1.0 + when error = abs(result - phi_sq_plus_one) + then error < 0.1 + + test bracket_unknot + given bracket = BRACKET_UNKNOT + then bracket == 1.0 + + test pow_negative_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-6 + + test jones_polynomial_from_structure_trefoil_phi + // V(trefoil, phi) from crossing signs + given crossings: [i32] = [1, 1, 1] + and t = PHI + when result = jones_polynomial_from_structure(crossings, t) + and expected = PHI * PHI + 1.0 + then abs(result - expected) < 0.1 + + test jones_polynomial_from_structure_unknot + // V(unknot, t) = 1 for any t + given crossings: [i32] = [] + and t = 2.0 + when result = jones_polynomial_from_structure(crossings, t) + then abs(result - 1.0) < 0.01 + + test jones_polynomial_variable_t + // Jones polynomial with variable t (not fixed phi) + given crossings: [i32] = [1, 1, 1] // trefoil + and t1 = 1.5 + and t2 = 2.0 + when v1 = jones_polynomial_from_structure(crossings, t1) + and v2 = jones_polynomial_from_structure(crossings, t2) + then v1 != v2 // Different t gives different V + + test bracket_from_crossings_trefoil + given crossings: [i32] = [1, 1, 1] + and t = PHI + when bracket = bracket_from_crossings(crossings, t) + then !isnan(bracket) and bracket != 0.0 + + test bracket_from_crossings_unknot + given crossings: [i32] = [] + when bracket = bracket_from_crossings(crossings, PHI) + then bracket == BRACKET_UNKNOT + + test jones_trefoil_variable_t + given t = 2.0 + when result = jones_trefoil(t) + then !isnan(result) and !isinf(result) + + // =========================================================================== + // 7. Formal Invariants + // ========================================================================================= + + invariant jones_trefoil_phi_relation + // V(trefoil, phi) ~= phi^2 + 1 + assert |jones_trefoil_at_phi() - (PHI * PHI + 1.0)| < 0.1 + + invariant writhe_additivity + assert writhe([a, b]) = writhe([a]) + writhe([b]) for any a, b in {1, -1} + // Rationale: writhe is linear sum of crossing signs + + invariant bracket_unknot_normalization + assert BRACKET_UNKNOT = 1.0 + // Rationale: Kauffman bracket of unknot is 1 (definition) + + invariant jones_polynomial_invariance + assert jones_polynomial(t, bracket1, w1) = jones_polynomial(t, bracket2, w2) + when bracket1 and bracket2 are related by Reidemeister moves + // Rationale: Jones polynomial is a link invariant (unchanged under Reidemeister moves) + + invariant phi_jones_trefoil_identity + // For trefoil at t=phi: V = phi^2 + 1 = phi + phi^2 (from phi^2 = phi + 1) + assert jones_trefoil_at_phi() ~= PHI + PHI * PHI within 0.1 + + // =========================================================================== + // 8. Benchmarks + // ========================================================================================= + + bench writhe_computation + measure: cycles to compute writhe for 100 crossings + target: < 1000 cycles + + bench jones_trefoil_computation + measure: cycles to compute jones_trefoil_at_phi() + target: < 5000 cycles + + bench bracket_parameter_computation + measure: cycles to compute bracket_parameter(t) + target: < 500 cycles +} diff --git a/apps/website/public/t27/specs/vsa/ops.t27 b/apps/website/public/t27/specs/vsa/ops.t27 new file mode 100644 index 0000000000..0ae8af8341 --- /dev/null +++ b/apps/website/public/t27/specs/vsa/ops.t27 @@ -0,0 +1,669 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/vsa/ops.t27 +// Vector Symbolic Architecture Operations +// Bind, Unbind, Bundle, Similarity for ternary hypervectors +// phi^2 + 1/phi^2 = 3 | TRINITY + +module VSAOps { + // Import base types and operations + use base::types; + use base::ops; + + // ================================================================= + // 1. Constants + // ========================================================================= + + const VSA_DIM : usize = 1024; // Default hypervector dimension + const SIMD_WIDTH : usize = 32; // SIMD trits per operation + const MAX_VECTORS : usize = 32; // Maximum vectors for bundleN + + // Similarity metrics + const SIM_COSINE : u8 = 0; // Cosine similarity [-1, 1] + const SIM_HAMMING : u8 = 1; // Hamming similarity [0, 1] + const SIM_DOT : u8 = 2; // Dot product similarity + + // Binding results + const BIND_IDENTITY : u8 = 0; // Self-binding (vector unchanged) + const BIND_INVERT : u8 = 1; // Inverted binding + + // ================================================================= + // 2. Bind Operation + // ========================================================================= + + // bind(a: []Trit, b: []Trit, len: usize) -> []Trit + // XOR-like operation for balanced ternary hypervectors + // Algorithm: if a[i] == 0 return b[i]; else if b[i] == 0 return a[i]; + // else return a[i] * b[i] (which is either -1 or +1) + // Used for: associative memory binding, role-value pairing + fn bind(a: []Trit, b: []Trit, len: usize) -> []Trit { + var result : []Trit = []; + result.reserve(len); + + var i : usize = 0; + while (i < len) { + const ai = a[i]; + const bi = b[i]; + + if (ai == Trit.zero) { + result.push(bi); + } else if (bi == Trit.zero) { + result.push(ai); + } else { + // Both non-zero: multiply (both are +/-1) + const product = if (ai == bi) { Trit.pos } else { Trit.neg }; + result.push(product); + } + + i = i + 1; + } + + return result; + } + + // ================================================================= + // 3. Unbind Operation + // ========================================================================= + + // unbind(bound: []Trit, key: []Trit, len: usize) -> []Trit + // Inverse of bind operation + // For XOR-like bind: unbind(x, y) = bind(x, y) + // Used for: retrieving bound values from hypervectors + fn unbind(bound: []Trit, key: []Trit, len: usize) -> []Trit { + // unbind = bind for this XOR-like implementation + return bind(bound, key, len); + } + + // ================================================================= + // 4. Bundle Operations + // ========================================================================= + + // bundle2(a: []Trit, b: []Trit, len: usize) -> []Trit + // Majority vote of 2 ternary vectors + // Algorithm: if a[i] == 0 return b[i]; else if b[i] == 0 return a[i]; + // else return sign(a[i] + b[i]) in {-1, 0, +1} + // Used for: superposition of items, set union + fn bundle2(a: []Trit, b: []Trit, len: usize) -> []Trit { + var result : []Trit = []; + result.reserve(len); + + var i : usize = 0; + while (i < len) { + const ai = a[i]; + const bi = b[i]; + + if (ai == Trit.zero) { + result.push(bi); + } else if (bi == Trit.zero) { + result.push(ai); + } else { + // Both non-zero: determine majority + const sum = ai as i8 + bi as i8; // -2, 0, or +2 + const trit = if (sum > 0) { Trit.pos } + else if (sum < 0) { Trit.neg } + else { Trit.zero }; + result.push(trit); + } + + i = i + 1; + } + + return result; + } + + // bundle3(a: []Trit, b: []Trit, c: []Trit, len: usize) -> []Trit + // Majority vote of 3 ternary vectors + // Algorithm: sum = a[i] + b[i] + c[i]; result = sign(sum) + // Used for: robust superposition, noise reduction + fn bundle3(a: []Trit, b: []Trit, c: []Trit, len: usize) -> []Trit { + var result : []Trit = []; + result.reserve(len); + + var i : usize = 0; + while (i < len) { + const ai = a[i]; + const bi = b[i]; + const ci = c[i]; + + const sum = ai as i8 + bi as i8 + ci as i8; // -3, -1, +1, or +3 + + const trit = if (sum > 0) { Trit.pos } + else if (sum < 0) { Trit.neg } + else { Trit.zero }; + result.push(trit); + + i = i + 1; + } + + return result; + } + + // ================================================================= + // 5. Similarity Operations + // ========================================================================= + + // similarity(a: []Trit, b: []Trit, len: usize, metric: u8) -> f64 + // Compute similarity between two hypervectors + // Metrics: COSINE, HAMMING, DOT + fn similarity(a: []Trit, b: []Trit, len: usize, metric: u8) -> f64 { + if (metric == SIM_COSINE) { + return cosine_similarity(a, b, len); + } else if (metric == SIM_HAMMING) { + return hamming_similarity(a, b, len); + } + // Default to dot + return dot_product(a, b, len); + } + + // cosine_similarity(a: []Trit, b: []Trit, len: usize) -> f64 + // Cosine similarity: (a*b) / (||a|| * ||b||) + fn cosine_similarity(a: []Trit, b: []Trit, len: usize) -> f64 { + const dot = dot_product(a, b, len); + const norm_a = vector_norm(a, len); + const norm_b = vector_norm(b, len); + + if (norm_a == 0.0 || norm_b == 0.0) { + return 0.0; + } + + return dot / (norm_a * norm_b); + } + + // hamming_similarity(a: []Trit, b: []Trit, len: usize) -> f64 + // Hamming similarity: 1 - (hamming_distance / len) + fn hamming_similarity(a: []Trit, b: []Trit, len: usize) -> f64 { + const dist = hamming_distance(a, b, len); + return 1.0 - (dist as f64 / len as f64); + } + + // dot_product(a: []Trit, b: []Trit, len: usize) -> f64 + // Compute dot product Sigma a[i] * b[i] + fn dot_product(a: []Trit, b: []Trit, len: usize) -> f64 { + var acc : i64 = 0; + + var i : usize = 0; + while (i < len) { + const product = (a[i] as i8) * (b[i] as i8); + acc = acc + product; + i = i + 1; + } + + return acc as f64; + } + + // vector_norm(v: []Trit, len: usize) -> f64 + // Compute L2 norm: sqrt(Sigma v[i]^2) + // For trits: v[i]^2 is always 0 or 1 + // So norm = sqrt(count of non-zero trits) + fn vector_norm(v: []Trit, len: usize) -> f64 { + var nonzero_count : usize = 0; + + var i : usize = 0; + while (i < len) { + if (v[i] != Trit.zero) { + nonzero_count = nonzero_count + 1; + } + i = i + 1; + } + + return sqrt(nonzero_count as f64); + } + + // hamming_distance(a: []Trit, b: []Trit, len: usize) -> usize + // Count positions where a[i] != b[i] + fn hamming_distance(a: []Trit, b: []Trit, len: usize) -> usize { + var distance : usize = 0; + + var i : usize = 0; + while (i < len) { + if (a[i] != b[i]) { + distance = distance + 1; + } + i = i + 1; + } + + return distance; + } + + // ================================================================= + // 6. Permutation Operations + // ========================================================================= + + // permute(v: []Trit, len: usize, shift: usize) -> []Trit + // Circular shift of hypervector by shift positions + // Used for: sequence encoding, position tagging + fn permute(v: []Trit, len: usize, shift: usize) -> []Trit { + var result : []Trit = []; + result.reserve(len); + + const normalized_shift = shift % len; + + var i : usize = 0; + while (i < len) { + const src_idx = (i + normalized_shift) % len; + result.push(v[src_idx]); + i = i + 1; + } + + return result; + } + + // encode_sequence(items: [][]Trit, count: usize, item_len: usize) -> []Trit + // Encode sequence of items using position-aware binding + // result = items[0] + permute(items[1], 1) + permute(items[2], 2) + ... + fn encode_sequence(items: [][]Trit, count: usize, item_len: usize) -> []Trit { + var result : []Trit = items[0].clone(); + + var i : usize = 1; + while (i < count) { + const permuted = permute(items[i], item_len, i); + result = bundle2(result, permuted, item_len); + i = i + 1; + } + + return result; + } + + // probe_sequence(seq: []Trit, candidate: []Trit, position: usize, len: usize) -> f64 + // Probe if candidate is at position in encoded sequence + // Returns similarity between seq and permute(candidate, position) + fn probe_sequence(seq: []Trit, candidate: []Trit, position: usize, len: usize) -> f64 { + const permuted = permute(candidate, len, position); + return similarity(seq, permuted, len, SIM_COSINE); + } + + // ======================================================================================================= + // TDD-Inside-Spec: Tests and Invariants for VSAOps + // ======================================================================================================= + + test vsa_bind_with_zeros + given a = [Trit.zero, Trit.pos, Trit.neg] + and b = [Trit.pos, Trit.zero, Trit.neg] + when result = bind(a, b, 3) + then result[0] == Trit.pos and result[1] == Trit.pos and result[2] == Trit.pos + + test vsa_bind_nonzero_multiply + given a = [Trit.pos, Trit.pos, Trit.neg, Trit.neg] + and b = [Trit.pos, Trit.neg, Trit.pos, Trit.neg] + when result = bind(a, b, 4) + then result[0] == Trit.pos and result[1] == Trit.neg and result[2] == Trit.neg and result[3] == Trit.pos + + test vsa_bundle2_with_zero + given a = [Trit.zero, Trit.pos, Trit.neg] + and b = [Trit.pos, Trit.zero, Trit.neg] + when result = bundle2(a, b, 3) + then result[0] == Trit.pos and result[1] == Trit.pos and result[2] == Trit.neg + + test vsa_bundle2_majority_vote + given a = [Trit.pos, Trit.neg, Trit.pos] + and b = [Trit.neg, Trit.neg, Trit.neg] + when result = bundle2(a, b, 3) + then result[0] == Trit.zero and result[1] == Trit.neg and result[2] == Trit.zero + + test vsa_bundle3_consensus + given a = [Trit.pos, Trit.pos, Trit.neg] + and b = [Trit.pos, Trit.neg, Trit.pos] + and c = [Trit.pos, Trit.pos, Trit.pos] + when result = bundle3(a, b, c, 3) + then result[0] == Trit.pos and result[1] == Trit.pos and result[2] == Trit.pos + + test vsa_dot_product_identical + given a = [Trit.pos, Trit.neg, Trit.pos, Trit.zero] + and b = [Trit.pos, Trit.neg, Trit.pos, Trit.zero] + when result = dot_product(a, b, 4) + then result == 3.0 + + test vsa_dot_product_orthogonal + given a = [Trit.pos, Trit.neg, Trit.zero] + and b = [Trit.neg, Trit.pos, Trit.zero] + when result = dot_product(a, b, 3) + then result == -2.0 + + test vsa_hamming_distance_identical + given a = [Trit.pos, Trit.neg, Trit.zero] + and b = [Trit.pos, Trit.neg, Trit.zero] + when result = hamming_distance(a, b, 3) + then result == 0 + + test vsa_hamming_distance_different + given a = [Trit.pos, Trit.pos, Trit.pos] + and b = [Trit.neg, Trit.neg, Trit.neg] + when result = hamming_distance(a, b, 3) + then result == 3 + + test vsa_vector_norm_zero_vector + given v = [Trit.zero, Trit.zero, Trit.zero] + when result = vector_norm(v, 3) + then result == 0.0 + + test vsa_vector_norm_all_nonzero + given v = [Trit.pos, Trit.neg, Trit.pos] + when result = vector_norm(v, 3) + then abs(result - 1.732) < 0.01 // sqrt(3) + + test vsa_cosine_similarity_identical + given a = [Trit.pos, Trit.neg, Trit.pos] + and b = [Trit.pos, Trit.neg, Trit.pos] + when result = cosine_similarity(a, b, 3) + then abs(result - 1.0) < 0.01 + + test vsa_cosine_similarity_orthogonal + given a = [Trit.pos, Trit.neg, Trit.zero] + and b = [Trit.neg, Trit.pos, Trit.zero] + when result = cosine_similarity(a, b, 3) + then abs(result + 1.0) < 0.01 + + test vsa_permute_shift_by_one + given v = [Trit.pos, Trit.neg, Trit.zero, Trit.pos] + when result = permute(v, 4, 1) + then result[0] == Trit.pos and result[1] == Trit.pos and result[2] == Trit.neg and result[3] == Trit.zero + + test vsa_permute_shift_by_len_returns_original + given v = [Trit.pos, Trit.neg, Trit.zero] + when result = permute(v, 3, 3) + then result[0] == Trit.pos and result[1] == Trit.neg and result[2] == Trit.zero + + test vsa_permute_shift_zero_unchanged + given v = [Trit.pos, Trit.neg, Trit.zero] + when result = permute(v, 3, 0) + then result[0] == Trit.pos and result[1] == Trit.neg and result[2] == Trit.zero + + test vsa_bind_unbind_identity + given x = [Trit.pos, Trit.neg, Trit.zero, Trit.pos] + and key = [Trit.neg, Trit.pos, Trit.pos, Trit.neg] + and bound = bind(x, key, 4) + and unbound = unbind(bound, key, 4) + and sim = similarity(x, unbound, 4, SIM_COSINE) + then sim > 0.95 + + test vsa_bundle2_idempotent + given v = [Trit.pos, Trit.neg, Trit.zero] + when result = bundle2(v, v, 3) + then hamming_distance(result, v) == 0 + + test vsa_similarity_symmetry + given a = [Trit.pos, Trit.neg, Trit.pos, Trit.zero] + and b = [Trit.neg, Trit.pos, Trit.neg, Trit.pos] + and sim_ab = similarity(a, b, 4, SIM_COSINE) + and sim_ba = similarity(b, a, 4, SIM_COSINE) + then abs(sim_ab - sim_ba) < 0.0001 + + test vsa_similarity_bounds_cosine + given a = [Trit.pos, Trit.neg, Trit.pos] + and b = [Trit.neg, Trit.pos, Trit.neg] + and sim = similarity(a, b, 4, SIM_COSINE) + then sim >= -1.0 and sim <= 1.0 + + test vsa_similarity_bounds_hamming + given a = [Trit.pos, Trit.neg, Trit.pos] + and b = [Trit.neg, Trit.pos, Trit.neg] + and sim = similarity(a, b, 4, SIM_HAMMING) + then sim >= 0.0 and sim <= 1.0 + + invariant vsa_bind_commutative + given a = [Trit.pos, Trit.neg, Trit.zero] + and b = [Trit.neg, Trit.pos, Trit.pos] + and ab = bind(a, b, 3) + and ba = bind(b, a, 3) + then ab[0] == ba[0] and ab[1] == ba[1] and ab[2] == ba[2] + + invariant vsa_bind_associative + // For XOR-like bind: bind(a, bind(b, c)) == bind(bind(a, b), c) + given a = [Trit.pos, Trit.neg] + and b = [Trit.neg, Trit.pos] + and c = [Trit.pos, Trit.pos] + and bc = bind(b, c, 2) + and abc = bind(a, bc, 2) + and ab = bind(a, b, 2) + and abc2 = bind(ab, c, 2) + then abc[0] == abc2[0] and abc[1] == abc2[1] + + invariant vsa_bundle2_commutative + given a = [Trit.pos, Trit.neg, Trit.zero] + and b = [Trit.neg, Trit.pos, Trit.pos] + and ab = bundle2(a, b, 3) + and ba = bundle2(b, a, 3) + then hamming_distance(ab, ba) == 0 + + invariant vsa_bundle3_commutative + given a = [Trit.pos, Trit.neg] + and b = [Trit.neg, Trit.pos] + and c = [Trit.pos, Trit.pos] + and abc1 = bundle3(a, b, c, 2) + and abc2 = bundle3(b, a, c, 2) + then hamming_distance(abc1, abc2) == 0 + + invariant vsa_unbind_reverses_bind + given x = [Trit.pos, Trit.neg, Trit.zero, Trit.pos] + and key = [Trit.neg, Trit.pos, Trit.pos, Trit.neg] + and bound = bind(x, key, 4) + and recovered = unbind(bound, key, 4) + and sim = similarity(x, recovered, 4, SIM_COSINE) + then sim >= 0.95 + + invariant vsa_permute_involutivity + given v = [Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg] + and len = 5 + and n = 2 + and p1 = permute(v, len, n) + and p2 = permute(p1, len, len - n) + then hamming_distance(v, p2) == 0 + + invariant vsa_similarity_non_negativity_hamming + given a = [Trit.pos, Trit.neg, Trit.pos] + and b = [Trit.neg, Trit.pos, Trit.neg] + and sim = similarity(a, b, 4, SIM_HAMMING) + then sim >= 0.0 + + invariant vsa_vector_norm_non_negative + for (const v) |item| in [[Trit.pos], [Trit.neg], [Trit.zero], [Trit.pos, Trit.neg]] { + assert vector_norm(item, item.len()) >= 0.0; + } + + invariant vsa_hamming_distance_symmetric + given a = [Trit.pos, Trit.neg, Trit.pos] + and b = [Trit.neg, Trit.pos, Trit.neg] + and d_ab = hamming_distance(a, b, 3) + and d_ba = hamming_distance(b, a, 3) + then d_ab == d_ba + + invariant vsa_dot_product_symmetric + given a = [Trit.pos, Trit.neg, Trit.pos] + and b = [Trit.neg, Trit.pos, Trit.neg] + and d_ab = dot_product(a, b, 3) + and d_ba = dot_product(b, a, 3) + then abs(d_ab - d_ba) < 0.0001 + + invariant vsa_vsa_dim_positive + assert VSA_DIM > 0 + + invariant vsa_simd_width_positive + assert SIMD_WIDTH > 0 + + invariant vsa_max_vectors_positive + assert MAX_VECTORS > 0 + + // ================================================================= + // Ring 048: Additional Algebraic Group Invariants + // ========================================================================= + + // bind_self_inverse: a BIND (a BIND b) == b + // For XOR-like bind, bind is its own inverse + invariant bind_self_inverse + given a = [Trit.pos, Trit.neg, Trit.zero] + and b = [Trit.neg, Trit.pos, Trit.pos] + and ab = bind(a, b, 3) + and aab = bind(a, ab, 3) + then hamming_distance(aab, b) == 0 + + // bundle3_majority: Bundle of 3 identical vectors returns that vector + invariant bundle3_majority + given v = [Trit.pos, Trit.neg, Trit.zero, Trit.pos] + and result = bundle3(v, v, v, 4) + then hamming_distance(result, v) == 0 + + // similarity_range: Cosine similarity is bounded [-1, 1] + invariant similarity_range_cosine + given a = [Trit.pos, Trit.neg, Trit.pos, Trit.zero] + and b = [Trit.neg, Trit.pos, Trit.neg, Trit.pos] + and sim = cosine_similarity(a, b, 4) + then sim >= -1.0 and sim <= 1.0 + + // similarity_range: Hamming similarity is bounded [0, 1] + invariant similarity_range_hamming + given a = [Trit.pos, Trit.neg, Trit.pos, Trit.zero] + and b = [Trit.neg, Trit.pos, Trit.neg, Trit.pos] + and sim = hamming_similarity(a, b, 4) + then sim >= 0.0 and sim <= 1.0 + + // self_similarity: Cosine similarity of vector with itself is 1.0 + invariant self_similarity_cosine + given v = [Trit.pos, Trit.neg, Trit.pos, Trit.zero] + and sim = cosine_similarity(v, v, 4) + then abs(sim - 1.0) < 0.0001 + + // self_similarity: Hamming similarity of vector with itself is 1.0 + invariant self_similarity_hamming + given v = [Trit.pos, Trit.neg, Trit.pos, Trit.zero] + and sim = hamming_similarity(v, v, 4) + then abs(sim - 1.0) < 0.0001 + + // bind_distributes_over_bundle2: a BIND bundle2(b, c) = bundle2(a BIND b, a BIND c) + // This holds for the balanced ternary representation + invariant bind_distributes_over_bundle2 + given a = [Trit.pos, Trit.neg] + and b = [Trit.neg, Trit.pos] + and c = [Trit.pos, Trit.pos] + and bc = bundle2(b, c, 2) + and left = bind(a, bc, 2) + and ab = bind(a, b, 2) + and ac = bind(a, c, 2) + and right = bundle2(ab, ac, 2) + then hamming_distance(left, right) == 0 + + // zero_vector_bind_identity: bind(zero, x) == x and bind(x, zero) == x + invariant zero_vector_bind_identity + given x = [Trit.pos, Trit.neg, Trit.zero] + and zero = [Trit.zero, Trit.zero, Trit.zero] + and zx = bind(zero, x, 3) + and xz = bind(x, zero, 3) + then hamming_distance(zx, x) == 0 and hamming_distance(xz, x) == 0 + + // ================================================================= + // 27-Dimensional Coptic Trit Space (Ring 048) + // ========================================================================= + + const COPTIC_DIM : usize = 27 + + // TritVec27: 27-dimensional trit vector for Coptic alphabet encoding + // Each of the 27 Coptic letters maps to a unique 27-dim hypervector + fn coptic_bind(a: [27]Trit, b: [27]Trit) -> [27]Trit { + var result : [27]Trit = [Trit.zero; 27]; + var i : usize = 0; + while (i < 27) { + const ai = a[i]; + const bi = b[i]; + if (ai == Trit.zero) { + result[i] = bi; + } else if (bi == Trit.zero) { + result[i] = ai; + } else { + result[i] = if (ai == bi) { Trit.pos } else { Trit.neg }; + } + i = i + 1; + } + return result; + } + + fn coptic_unbind(bound: [27]Trit, key: [27]Trit) -> [27]Trit { + return coptic_bind(bound, key); + } + + fn coptic_bundle3(a: [27]Trit, b: [27]Trit, c: [27]Trit) -> [27]Trit { + var result : [27]Trit = [Trit.zero; 27]; + var i : usize = 0; + while (i < 27) { + const sum = a[i] as i8 + b[i] as i8 + c[i] as i8; + result[i] = if (sum > 0) { Trit.pos } + else if (sum < 0) { Trit.neg } + else { Trit.zero }; + i = i + 1; + } + return result; + } + + fn coptic_similarity(a: [27]Trit, b: [27]Trit) -> f64 { + var matches : usize = 0; + var i : usize = 0; + while (i < 27) { + if (a[i] == b[i]) { matches = matches + 1; } + i = i + 1; + } + return matches as f64 / 27.0; + } + + // Coptic space invariants + invariant coptic_bind_commutative + given a = [Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero] + and b = [Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos] + and ab = coptic_bind(a, b) + and ba = coptic_bind(b, a) + then hamming_distance(ab, ba) == 0 + + invariant coptic_bind_self_inverse + given a = [Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero] + and b = [Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos] + and ab = coptic_bind(a, b) + and aab = coptic_bind(a, ab) + then hamming_distance(aab, b) == 0 + + invariant coptic_bundle3_majority + given v = [Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero] + and result = coptic_bundle3(v, v, v) + then hamming_distance(result, v) == 0 + + invariant coptic_similarity_range + given a = [Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero] + and b = [Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos] + and sim = coptic_similarity(a, b) + then sim >= 0.0 and sim <= 1.0 + + invariant coptic_self_similarity + given v = [Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero] + and sim = coptic_similarity(v, v) + then abs(sim - 1.0) < 0.0001 + + test coptic_bind_unbind_identity + given x = [Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero, Trit.pos, Trit.neg, Trit.zero] + and key = [Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos, Trit.neg, Trit.pos, Trit.pos] + and bound = coptic_bind(x, key) + and unbound = coptic_unbind(bound, key) + then hamming_distance(unbound, x) == 0 + + bench vsa_bind_throughput + measure: nanoseconds to bind([Trit.pos; 1024], [Trit.neg; 1024], 1024) + target: < 1000ns + + bench vsa_bundle2_throughput + measure: nanoseconds to bundle2([Trit.pos; 1024], [Trit.neg; 1024], 1024) + target: < 1000ns + + bench vsa_bundle3_throughput + measure: nanoseconds to bundle3([Trit.pos; 1024], [Trit.neg; 1024], [Trit.pos; 1024], 1024) + target: < 1500ns + + bench vsa_similarity_latency + measure: nanoseconds to cosine_similarity([Trit.pos; 1024], [Trit.neg; 1024], 1024) + target: < 2000ns + + bench vsa_permute_latency + measure: nanoseconds to permute([Trit.pos; 1024], 1024, 100) + target: < 500ns + + bench vsa_dot_product_latency + measure: nanoseconds to dot_product([Trit.pos; 1024], [Trit.neg; 1024], 1024) + target: < 1500ns + + bench vsa_hamming_distance_latency + measure: nanoseconds to hamming_distance([Trit.pos; 1024], [Trit.neg; 1024], 1024) + target: < 1000ns +} diff --git a/apps/website/public/t27/specs/vsa/packed_vsa.t27 b/apps/website/public/t27/specs/vsa/packed_vsa.t27 new file mode 100644 index 0000000000..c52e3313d3 --- /dev/null +++ b/apps/website/public/t27/specs/vsa/packed_vsa.t27 @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: Apache-2.0 +// Module: Packed VSA Operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module PackedVsa { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; // Trit enum for trit values + use ternary::packed_trit; // PackedBigInt for storage + use ternary::hybrid_arithmetic; // HybridBigInt for conversion + use numeric::gf16; // GF16 for similarity scores + + // ======================================================================== + // 1. Constants + // ======================================================================== + + // Trits per byte for packed encoding (5-trit-per-byte) + pub const TRITS_PER_BYTE : u8 = 5; + + // Maximum number of packed bytes (supports ~12000 trits) + pub const MAX_PACKED_BYTES : u16 = 2400; + + // Lookup table dimensions (3^5 = 243) + pub const LUT_DIM : u16 = 243; + + // Lookup table size in bytes (243 * 243 = ~59KB) + pub const LUT_SIZE : u32 = 59049; + + // ======================================================================== + // 2. Lookup Table Types + // ======================================================================== + + // BindLookupTable: Precomputed bind operation results + // BIND_LUT[a][b] = packed(bind(unpack(a), unpack(b))) + // Uses element-wise multiplication of 5 trits + // Stored as flattened 1D array for spec-first design + pub const BindLookupTable : [59049]u8; + + // BundleLookupTable: Precomputed bundle operation results + // BUNDLE_LUT[a][b] = packed(bundle(unpack(a), unpack(b))) + // Uses element-wise sum with threshold (+/-1 normalization) + pub const BundleLookupTable : [59049]u8; + + // DotLookupTable: Precomputed dot product results + // DOT_LUT[a][b] = sum of element-wise products (offset by +5) + // Range: [-5, +5] encoded as [0, 10] in u8 + pub const DotLookupTable : [59049]u8; + + // ======================================================================== + // 3. Packed VSA Operations + // ======================================================================== + + // packedBind(a: &PackedBigInt, b: &PackedBigInt) -> PackedBigInt + // Bind two packed vectors using lookup table + // Element-wise multiplication: bind = a * b + // Complexity: O(n/5) where n = max trit length + pub fn packedBind(a: &PackedBigInt, b: &PackedBigInt) -> PackedBigInt; + + // packedBundle(a: &PackedBigInt, b: &PackedBigInt) -> PackedBigInt + // Bundle two packed vectors using lookup table + // Element-wise sum with threshold normalization + // Complexity: O(n/5) where n = max trit length + pub fn packedBundle(a: &PackedBigInt, b: &PackedBigInt) -> PackedBigInt; + + // packedDot(a: &PackedBigInt, b: &PackedBigInt) -> i64 + // Dot product using lookup table + // Returns sum of element-wise products + // Complexity: O(n/5) where n = min trit length + pub fn packedDot(a: &PackedBigInt, b: &PackedBigInt) -> i64; + + // packedUnbind(a: &PackedBigInt, b: &PackedBigInt) -> PackedBigInt + // Unbind operation using bind (since b^2 = 1 for trits) + // Unbind: packedBind(a, b) where b in {-1, 1} + // Complexity: O(n/5) where n = trit length + pub fn packedUnbind(a: &PackedBigInt, b: &PackedBigInt) -> PackedBigInt; + + // packedCosineSimilarity(a: &PackedBigInt, b: &PackedBigInt) -> f64 + // Cosine similarity using dot product and magnitudes + // Returns normalized similarity in [-1, 1] + // Complexity: O(n/5) for dot products + O(1) for sqrt + pub fn packedCosineSimilarity(a: &PackedBigInt, b: &PackedBigInt) -> f64; + + // ======================================================================== + // 4. Conversion Functions + // ======================================================================== + + // fromHybrid(h: &HybridBigInt) -> PackedBigInt + // Convert HybridBigInt to PackedBigInt + // Encodes unpacked trits into packed format (5 trits/byte) + // Complexity: O(n) where n = trit length + pub fn fromHybrid(h: &HybridBigInt) -> PackedBigInt; + + // toHybrid(p: &PackedBigInt) -> HybridBigInt + // Convert PackedBigInt to HybridBigInt + // Decodes packed trits into unpacked format + // Complexity: O(n) where n = trit length + pub fn toHybrid(p: &PackedBigInt) -> HybridBigInt; + + // randomPackedVector(size: usize, seed: u64) -> PackedBigInt + // Generate random packed vector for initialization + // Uses seeded random for deterministic generation + // Complexity: O(n/5) where n = size + pub fn randomPackedVector(size: usize, seed: u64) -> PackedBigInt; + + // ======================================================================== + // TDD - Tests + // ======================================================================== + + test packed_bind_matches_unpacked + // Verify: packedBind produces same result as unpacked bind + given h_a = randomVector(100, 12345) + and h_b = randomVector(100, 67890) + and ref = bind(&h_a, &h_b) + when p_a = fromHybrid(&h_a) + and p_b = fromHybrid(&h_b) + and result = packedBind(&p_a, &p_b) + then compareVectors(&result, &ref) == 0 + + test packed_bundle_matches_unpacked + // Verify: packedBundle produces same result as unpacked bundle + given h_a = randomVector(100, 11111) + and h_b = randomVector(100, 22222) + and ref = bundle(&h_a, &h_b) + when p_a = fromHybrid(&h_a) + and p_b = fromHybrid(&h_b) + and result = packedBundle(&p_a, &p_b) + then compareVectors(&result, &ref) == 0 + + test packed_dot_matches_unpacked + // Verify: packedDot produces same result as unpacked dot + given h_a = randomVector(100, 33333) + and h_b = randomVector(100, 44444) + and ref = dot(&h_a, &h_b) + when p_a = fromHybrid(&h_a) + and p_b = fromHybrid(&h_b) + and result = packedDot(&p_a, &p_b) + then result == ref + + test packed_unbind_recovers_original + // Verify: unbind recovers original vector + given p_a = randomPackedVector(100, 12345) + and p_b = randomPackedVector(100, 67890) + and bound = packedBind(&p_a, &p_b) + and unbound = packedUnbind(&bound, &p_b) + when sim = packedCosineSimilarity(&unbound, &p_a) + then sim > 0.5 + + test packed_cosine_self_similarity_is_one + // Verify: Self-similarity equals 1.0 + given vec = randomPackedVector(100, 999) + and sim = packedCosineSimilarity(&vec, &vec) + then abs(sim - 1.0) < 0.001 + + test packed_cosine_orthogonal_is_zero + // Verify: Orthogonal vectors have similarity ~0 + given vec_a = randomPackedVector(100, 111) + and vec_b = randomPackedVector(100, 999) + when sim = packedCosineSimilarity(&vec_a, &vec_b) + then abs(sim) < 0.3 + + test from_hybrid_preserves_data + // Verify: fromHybrid then toHybrid preserves data + given h = randomVector(100, 12345) + when p = fromHybrid(&h) + and h2 = toHybrid(&p) + then compareVectors(&h, &h2) == 0 + + test to_hybrid_preserves_data + // Verify: toHybrid then fromHybrid preserves data + given p = randomPackedVector(100, 12345) + when h = toHybrid(&p) + and p2 = fromHybrid(&h) + then compareVectors(&p, &p2) == 0 + + // ======================================================================== + // TDD - Invariants + // ======================================================================== + + invariant packed_bind_commutative + // Verify: packedBind(a, b) = packedBind(b, a) + // Element-wise multiplication is commutative + assert true; + + invariant packed_bundle_commutative + // Verify: packedBundle(a, b) = packedBundle(b, a) + // Element-wise sum is commutative + assert true; + + invariant packed_dot_symmetric + // Verify: packedDot(a, b) = packedDot(b, a) + // Dot product is symmetric + assert true; + + invariant packed_cosine_range + // Verify: Cosine similarity is in [-1, 1] + // Similarity must be normalized by magnitudes + assert true; + + invariant packed_unbind_inverts_bind + // Verify: unbind(bind(a, b), b) ~= a (for valid trits) + // Unbind is the inverse operation of bind + assert true; + + invariant lut_dimensions_match + // Verify: LUT dimensions are correct (243 x 243) + // All LUTs must have same dimensions for indexing + assert true; + + invariant trits_per_byte_consistent + // Verify: 5 trits per byte encoding is consistent + // All operations must use same encoding + assert true; + + invariant conversion_roundtrip + // Verify: fromHybrid(toHybrid(x)) ~= x + // Conversion must preserve data within tolerance + assert true; + + // ======================================================================== + // TDD - Benchmarks + // ======================================================================== + + bench packed_bind_latency_100_trits + // Measure: cycles for packedBind (100 trits) + // Target: < 1000 cycles (20 LUT lookups) + @setEvalBranchQuota(10000); + var a = randomPackedVector(100, 12345); + var b = randomPackedVector(100, 67890); + _ = packedBind(&a, &b); + _ = a; + + bench packed_bundle_latency_100_trits + // Measure: cycles for packedBundle (100 trits) + // Target: < 1000 cycles (20 LUT lookups) + @setEvalBranchQuota(10000); + var a = randomPackedVector(100, 12345); + var b = randomPackedVector(100, 67890); + _ = packedBundle(&a, &b); + _ = a; + + bench packed_dot_latency_100_trits + // Measure: cycles for packedDot (100 trits) + // Target: < 500 cycles (20 LUT lookups + sum) + @setEvalBranchQuota(10000); + var a = randomPackedVector(100, 12345); + var b = randomPackedVector(100, 67890); + _ = packedDot(&a, &b); + _ = a; + + bench packed_cosine_similarity_latency_100_trits + // Measure: cycles for packedCosineSimilarity (100 trits) + // Target: < 2000 cycles (2 dot + sqrt operations) + @setEvalBranchQuota(10000); + var a = randomPackedVector(100, 12345); + var b = randomPackedVector(100, 67890); + _ = packedCosineSimilarity(&a, &b); + _ = a; + + bench from_hybrid_latency_100_trits + // Measure: cycles for fromHybrid conversion (100 trits) + // Target: < 500 cycles (encode 100 trits) + @setEvalBranchQuota(10000); + var h = randomVector(100, 12345); + _ = fromHybrid(&h); + _ = h; + + bench to_hybrid_latency_100_trits + // Measure: cycles for toHybrid conversion (100 trits) + // Target: < 500 cycles (decode 100 trits) + @setEvalBranchQuota(10000); + var p = randomPackedVector(100, 12345); + _ = toHybrid(&p); + _ = p; + + bench random_packed_vector_latency_1000_trits + // Measure: cycles to generate random packed vector (1000 trits) + // Target: < 5000 cycles (PRNG + encoding) + @setEvalBranchQuota(10000); + _ = randomPackedVector(1000, 12345); + + bench packed_unbind_latency_100_trits + // Measure: cycles for packedUnbind (100 trits) + // Target: < 1000 cycles (1 bind operation) + @setEvalBranchQuota(10000); + var a = randomPackedVector(100, 12345); + var b = randomPackedVector(100, 67890); + _ = packedUnbind(&a, &b); + _ = a; +} diff --git a/apps/website/public/t27/specs/vsa/sdk.t27 b/apps/website/public/t27/specs/vsa/sdk.t27 new file mode 100644 index 0000000000..badf550a92 --- /dev/null +++ b/apps/website/public/t27/specs/vsa/sdk.t27 @@ -0,0 +1,625 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/vsa/sdk.t27 +// Trinity SDK - High-level API for VSA operations +// phi^2 + 1/phi^2 = 3 | TRINITY + +module SDK { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; + use numeric::gf16; + use vsa::vsa_core; + use ternary::hybrid_arithmetic; + use tritype-base::Trit; + + // ======================================================================== + // 1. Hypervector - Main abstraction for developers + // ======================================================================== + // + // Hypervector provides high-level wrapper around HybridBigInt + // for intuitive VSA (Vector Symbolic Architecture) operations. + // + // WHY: Simplifies VSA API by abstracting away packed/unpacked + // modes and providing intuitive function names for common ops. + // + // Operations: bind, unbind, bundle, similarity, permutation + // ======================================================================== + + // Hypervector: VSA vector with optional label + pub struct Hypervector { + data: hybrid_arithmetic::HybridBigInt, + label: ?[]const u8, + } + + // ======================================================================== + // 2. Hypervector Creation + // ======================================================================== + + // hypervector_zero(dim: usize) -> Hypervector + // Create zero hypervector with given dimension + // + // Complexity: O(1) + // ======================================================================== + pub fn hypervector_zero(dim: usize) Hypervector { + const empty = hybrid_arithmetic::HybridBigInt::zero(); + var result : Hypervector = undefined; + result.data = empty; + result.label = null; + return result; + } + + // hypervector_random(dim: usize, seed: u64) -> Hypervector + // Create random hypervector for atomic symbols + // + // Complexity: O(n) where n = dim (delegates to VSA) + // ======================================================================== + pub fn hypervector_random(dim: usize, seed: u64) Hypervector { + return Hypervector{ + .data = vsa_core::random_vector(dim, seed), + .label = null, + }; + } + + // hypervector_random_labeled(dim: usize, seed: u64, label: []const u8) -> Hypervector + // Create random hypervector with label + // + // Complexity: O(n) where n = dim + // ======================================================================== + pub fn hypervector_random_labeled(dim: usize, seed: u64, label: []const u8) Hypervector { + return Hypervector{ + .data = vsa_core::random_vector(dim, seed), + .label = label, + }; + } + + // hypervector_from_raw(raw: hybrid_arithmetic::HybridBigInt) -> Hypervector + // Create hypervector from existing HybridBigInt + // + // Complexity: O(1) + // ======================================================================== + pub fn hypervector_from_raw(raw: hybrid_arithmetic::HybridBigInt) Hypervector { + return Hypervector{ + .data = raw, + .label = null, + }; + } + + // hypervector_get_dimension(self: Hypervector) -> usize + // Get dimension (number of trits) + // + // Complexity: O(1) + // ======================================================================== + pub fn hypervector_get_dimension(self: Hypervector) usize { + return self.data.get_dimension(); + } + + // hypervector_get(self: index: usize) -> Trit + // Get trit at position + // + // Complexity: O(1) - delegates to HybridBigInt + // ======================================================================== + pub fn hypervector_get(self: Hypervector, index: usize) Trit { + return self.data.get(index); + } + + // hypervector_set(self: Hypervector, index: usize, value: Trit) -> void + // Set trit at position + // + // Complexity: O(1) - delegates to HybridBigInt + // ======================================================================== + pub fn hypervector_set(self: Hypervector, index: usize, value: Trit) void { + self.data.set(index, value); + } + + // ======================================================================== + // 3. VSA Operations + // ======================================================================== + + // hypervector_bind(self: Hypervector, other: Hypervector) -> Hypervector + // Bind two hypervectors (creates association) + // + // bind(A, B) represents "A associated with B" + // Properties: self-inverse, preserves similarity + // + // Complexity: O(n) where n = dimension + // ======================================================================== + pub fn hypervector_bind(self: Hypervector, other: Hypervector) Hypervector { + return Hypervector{ + .data = vsa_core::bind(&self.data, &other.data), + .label = null, + }; + } + + // hypervector_unbind(self: Hypervector, key: Hypervector) -> Hypervector + // Unbind (inverse of bind) + // + // unbind(bind(A, B), B) = A + // + // Complexity: O(n) where n = dimension + // ======================================================================== + pub fn hypervector_unbind(self: Hypervector, key: Hypervector) Hypervector { + return Hypervector{ + .data = vsa_core::unbind(&self.data, &key.data), + .label = null, + }; + } + + // hypervector_bundle(self: Hypervector, other: Hypervector) -> Hypervector + // Bundle two hypervectors (creates superposition) + // + // bundle(A, B) is similar to both A and B + // + // Complexity: O(n) where n = dimension + // ======================================================================== + pub fn hypervector_bundle(self: Hypervector, other: Hypervector) Hypervector { + return Hypervector{ + .data = vsa_core::bundle2(&self.data, &other.data), + .label = null, + }; + } + + // hypervector_bundle3(self: Hypervector, b: Hypervector, c: Hypervector) -> Hypervector + // Bundle three hypervectors + // + // bundle(A, B, C) = bundle2(bundle2(A, B), C) + // + // Complexity: O(n) where n = dimension + // ======================================================================== + pub fn hypervector_bundle3(self: Hypervector, b: Hypervector, c: Hypervector) Hypervector { + return Hypervector{ + .data = vsa_core::bundle3(&self.data, &b.data, &c.data), + .label = null, + }; + } + + // hypervector_permute(self: Hypervector, k: usize) -> Hypervector + // Permute (cyclic shift) - for sequence encoding + // + // Complexity: O(n) where n = dimension + // ======================================================================== + pub fn hypervector_permute(self: Hypervector, k: usize) Hypervector { + return Hypervector{ + .data = vsa_core::permute(&self.data, k), + .label = null, + }; + } + + // hypervector_inverse_permute(self: Hypervector, k: usize) -> Hypervector + // Inverse permute (within permutation) + // + // Complexity: O(n) where n = dimension + // ======================================================================== + pub fn hypervector_inverse_permute(self: Hypervector, k: usize) Hypervector { + return Hypervector{ + .data = vsa_core::inverse_permute(&self.data, k), + .label = null, + }; + } + + // ======================================================================== + // 4. Similarity Operations + // ======================================================================== + + // hypervector_similarity(self: Hypervector, other: Hypervector) -> gf16 + // Cosine similarity + // + // Returns: cosine of angle between vectors + // Range: [-1, 1] for VSA vectors + // + // Complexity: O(n) where n = dimension + // ======================================================================== + pub fn hypervector_similarity(self: Hypervector, other: Hypervector) gf16 { + return vsa_core::cosine_similarity(&self.data, &other.data); + } + + // hypervector_hamming_distance(self: Hypervector, other: Hypervector) -> usize + // Hamming distance (number of differing trits) + // + // Returns: count of positions where trits differ + // + // Complexity: O(n) where n = dimension + // ======================================================================== + pub fn hypervector_hamming_distance(self: Hypervector, other: Hypervector) usize { + return vsa_core::hamming_distance(&self.data, &other.data); + } + + // hypervector_hamming_similarity(self: Hypervector, other: Hypervector) -> gf16 + // Hamming similarity + // + // Returns: 1 - (hamming_distance / dimension) + // Range: [0, 1] where 1 = identical + // + // Complexity: O(n) where n = dimension + // ======================================================================== + pub fn hypervector_hamming_similarity(self: Hypervector, other: Hypervector) gf16 { + const dist = vsa_core::hamming_distance(&self.data, &other.data); + const dim = gf16::from_f64(@as(f64, @floatFromInt(self.data.get_dimension()))); + const similarity = gf16::sub(gf16::from_f64(1.0), gf16::div(dist, dim)); + return similarity; + } + + // hypervector_dot_product(self: Hypervector, other: Hypervector) -> i32 + // Dot product of two hypervectors + // + // Returns: sum of element-wise products + // Used for similarity metrics + // + // Complexity: O(n) where n = dimension + // ======================================================================== + pub fn hypervector_dot_product(self: Hypervector, other: Hypervector) i32 { + return vsa_core::dot_product(&self.data, &other.data); + } + + // ======================================================================== + // 5. Utility Functions + // ======================================================================== + + // hypervector_negate(self: Hypervector) -> Hypervector + // Negate all trits + // + // Complexity: O(n) where n = dimension + // ======================================================================== + pub fn hypervector_negate(self: Hypervector) Hypervector { + return Hypervector{ + .data = self.data.negate(), + .label = self.label, + }; + } + + // hypervector_count_non_zero(self: Hypervector) -> usize + // Count non-zero trits + // + // Complexity: O(n) where n = dimension + // ======================================================================== + pub fn hypervector_count_non_zero(self: Hypervector) usize { + return vsa_core::count_non_zero(&self.data); + } + + // hypervector_density(self: Hypervector) -> gf16 + // Density (ratio of non-zero trits) + // + // Returns: count_non_zero / dimension + // Range: [0, 1] + // + // Complexity: O(n) where n = dimension + // ======================================================================== + pub fn hypervector_density(self: Hypervector) gf16 { + const non_zero = gf16::from_f64(@as(f64, @floatFromInt(self.data.count_non_zero()))); + const dim = gf16::from_f64(@as(f64, @floatFromInt(self.data.get_dimension()))); + return gf16::div(non_zero, dim); + } + + // hypervector_clone(self: Hypervector) -> Hypervector + // Clone hypervector + // + // Complexity: O(n) where n = dimension + // ======================================================================== + pub fn hypervector_clone(self: Hypervector) Hypervector { + return Hypervector{ + .data = self.data.clone(), + .label = self.label, + }; + } + + // ======================================================================== + // TDD - Tests + // ======================================================================== + + test "hypervector_zero_has_zero_dimension" + given result = hypervector_zero(10) + when dim = result.data.get_dimension() + then dim == 0 + + test "hypervector_zero_is_packed_mode" + given result = hypervector_zero(5) + then result.data.mode == hybrid_arithmetic::packed_mode + + test "hypervector_random_non_zero" + given result = hypervector_random(100, 12345) + then result.data.count_non_zero() > 0 + + test "hypervector_bind_preserves_dimension" + given a = hypervector_random(50, 123) + and b = hypervector_random(50, 54321) + when result = hypervector_bind(a, b) + then result.data.get_dimension() == 50 + + test "hypervector_bind_is_self_inverse" + given a = hypervector_random(50, 123) + when bound = hypervector_bind(a, a) + and unbound = hypervector_unbind(bound, a) + then unbound.data == a.data + + test "hypervector_unbind_reverses_bind" + given a = hypervector_random(50, 123) + and b = hypervector_random(50, 98765) + const bound = hypervector_bind(a, b) + when unbound = hypervector_unbind(bound, a) + then unbound.data == a.data + + test "hypervector_bundle_dimension_preserved" + given a = hypervector_random(50, 123) + and b = hypervector_random(50, 54321) + and c = hypervector_random(50, 87654) + when result = hypervector_bundle(a, b) + then result.data.get_dimension() == 50 + + test "hypervector_bundle3_dimension_preserved" + given a = hypervector_random(50, 123) + and b = hypervector_random(50, 54321) + and c = hypervector_random(50, 87654) + when result = hypervector_bundle3(a, b, c) + then result.data.get_dimension() == 50 + + test "hypervector_permute_preserves_dimension" + given a = hypervector_random(50, 123) + when result = hypervector_permute(a, 5) + then result.data.get_dimension() == 50 + + test "hypervector_inverse_permute_is_inverse" + given a = hypervector_random(50, 123) + when result = hypervector_inverse_permute(result, 5) + then result.data == a.data + + test "hypervector_negate_all_non_zero" + given a = hypervector_random(50, 123) + when result = hypervector_negate(a) + then result.data.count_non_zero() == 0 + + test "hypervector_negate_flips_sign" + given a = hypervector_random(50, 123) + when result = hypervector_negate(a) + then result.data.sign == -a.data.sign + + test "hypervector_similarity_identical" + given a = hypervector_random(50, 123) + when result = hypervector_similarity(a, a) + then result == gf16::from_f64(1.0) + + test "hypervector_similarity_opposite" + given a = hypervector_random(50, 123) + and b = hypervector_negate(a) + when result = hypervector_similarity(a, b) + then result == gf16::from_f64(-1.0) + + test "hypervector_hamming_distance_self" + given a = hypervector_random(50, 123) + when result = hypervector_hamming_distance(a, a) + then result == 0 + + test "hypervector_hamming_distance_different" + given a = hypervector_random(50, 123) + and b = hypervector_random(50, 98765) + when result = hypervector_hamming_distance(a, b) + then result > 0 + + test "hypervector_hamming_similarity_max" + given a = hypervector_random(50, 123) + and b = hypervector_random(50, 98765) + when result = hypervector_hamming_similarity(a, b) + then result == gf16::from_f64(0.0) + + test "hypervector_hamming_similarity_min" + given a = hypervector_random(50, 123) + and b = hypervector_negate(a) + when result = hypervector_hamming_similarity(a, b) + then result == gf16::from_f64(0.0) + + test "hypervector_dot_product_positive" + given a = hypervector_random(50, 123) + when result = hypervector_dot_product(a, a) + then result > 0 + + test "hypervector_dot_product_orthogonal" + given a = hypervector_random(50, 123) + and b = hypervector_random(50, 98765) + when result = hypervector_dot_product(a, b) + then result == 0 + + test "hypervector_dot_product_negative" + given a = hypervector_random(50, 123) + and b = hypervector_negate(a) + when result = hypervector_dot_product(a, b) + then result < 0 + + test "hypervector_density_empty" + given result = hypervector_zero(5) + then result.data.count_non_zero() == 0 + + test "hypervector_density_full" + given result = hypervector_random(50, 123) + then hypervector_density(result) == gf16::from_f64(1.0) + + test "hypervector_clone_preserves_data" + given a = hypervector_random(50, 123) + when clone = hypervector_clone(a) + then clone.data == a.data + + test "hypervector_clone_preserves_label" + given a = hypervector_random_labeled(50, 123, "test") + when clone = hypervector_clone(a) + then clone.data == a.data and clone.label == a.label + + test "hypervector_set_preserves_dimension" + given a = hypervector_random(50, 123) + when a_set = hypervector_set(a, 1) + then a_set.data.get_dimension() == 50 + + test "hypervector_set_extends_dimension" + given a = hypervector_random(50, 123) + and zero = hypervector_zero(5) + when a_ext = hypervector_bundle(a, zero) + then a_ext.data.get_dimension() == 50 + + test "hypervector_get_within_bounds" + given a = hypervector_random(50, 123) + when result = hypervector_get(a, 5) + then result.data.get(5) == a.data.get(5) + + test "hypervector_get_out_of_bounds_low" + given a = hypervector_random(50, 123) + when result = hypervector_get(a, 100) + then result.data.get(100) == 0 + + test "hypervector_get_out_of_bounds_high" + given a = hypervector_random(50, 123) + when result = hypervector_get(a, 200) + then result.data.get(200) == 0 + + // ======================================================================== + // TDD - Invariants + // ======================================================================== + + invariant hypervector_bind_self_inverse + // bind(a, a) then unbind(bind(a, a), a) = a + const a = hypervector_random(50, 123); + const bound = hypervector_bind(a, a); + const unbound = hypervector_unbind(bound, a); + assert unbound.data == a.data; + + invariant hypervector_bundle_commutative + // bundle(a, b) = bundle(b, a) + const a = hypervector_random(50, 123); + const b = hypervector_random(50, 54321); + const ab = hypervector_bundle(a, b); + const ba = hypervector_bundle(b, a); + assert ab.data == ba.data; + + invariant hypervector_bundle3_commutative + // bundle3(a, b, c) = bundle3(c, b, a) + const a = hypervector_random(50, 123); + const b = hypervector_random(50, 54321); + const c = hypervector_random(50, 87654); + const abc = hypervector_bundle3(a, b, c); + const bca = hypervector_bundle3(b, c, a); + assert abc.data == bca.data; + + invariant hypervector_permute_identity + // permute(permute(v, k), k) = v + const a = hypervector_random(100, 12345); + when result = hypervector_permute(a, 0) + then result.data == a.data + + invariant hypervector_permute_inverse_identity + // inverse_permute(inverse_permute(v, k), k) = v + const a = hypervector_random(100, 12345); + when result = hypervector_inverse_permute(result, 0) + then result.data == a.data + + invariant hypervector_similarity_range + // Similarity always in [0, 1] + const a = hypervector_random(100, 123); + const b = hypervector_random(100, 98765); + when result = hypervector_similarity(a, b) + then result >= gf16::from_f64(-1.0) and result <= gf16::from_f64(1.0); + + invariant hypervector_hamming_distance_non_negative + // Hamming distance is always >= 0 + const a = hypervector_random(100, 123); + const b = hypervector_random(100, 98765); + when result = hypervector_hamming_distance(a, b) + then result >= 0; + + invariant hypervector_dot_product_bilinearity + // dot(a, b) is bilinear: dot(a, b + c) = dot(a, b) + dot(a, c) + const a = hypervector_random(10, 123); + const b = hypervector_random(10, 98765); + const c = hypervector_random(10, 54321); + const ab = hypervector_dot_product(a, b); + const bc = hypervector_dot_product(b, c); + const abc = hypervector_dot_product(a, c); + const lhs = gf16::add(ab, bc); + const rhs = gf16::add(gf16::mul(gf16::from_f64(2.0), abc), gf16::sub(gf16::from_f64(2.0), ab)); + assert lhs == rhs; + + invariant hypervector_negate_flips_all_trits + // Negate flips all trits + const a = hypervector_random(100, 123); + when result = hypervector_negate(a) + then for (i = 0; i < 100; i += 1) { + const t1 = a.data.get(i); + const t2 = result.data.get(i); + assert t1 == -t2; + } + + invariant hypervector_density_range + // Density always in [0, 1] + const a = hypervector_random(100, 0); + when result = hypervector_density(a) + then result >= gf16::from_f64(0.0) and result <= gf16::from_f64(1.0); + + invariant hypervector_zero_has_zero_trits + // Zero hypervector has zero dimension + const result = hypervector_zero(10); + assert result.data.get_dimension() == 0; + + invariant hypervector_clone_preserves_label_null + // Clone preserves null label + const a = hypervector_random(50, 123); + when clone = hypervector_clone(a) + then clone.label == a.label; + + // ======================================================================== + // TDD - Benchmarks + // ======================================================================== + + bench "hypervector_bind_latency" + // Measure: cycles for bind operation (50 trits) + // Target: < 200 cycles + @setEvalBranchQuota(10000); + var a = hypervector_random(50, 12345); + var b = hypervector_random(50, 54321); + var result : Hypervector = undefined; + for (0..1000) |_| { + result = hypervector_bind(a, b); + } + _ = result; + + bench "hypervector_bundle_latency" + // Measure: cycles for bundle operation (50 trits) + // Target: < 200 cycles + @setEvalBranchQuota(10000); + var a = hypervector_random(50, 12345); + var b = hypervector_random(50, 54321); + var result : Hypervector = undefined; + for (0..1000) |_| { + result = hypervector_bundle(a, b); + } + _ = result; + + bench "hypervector_permute_latency" + // Measure: cycles for permute operation (50 trits) + // Target: < 200 cycles + @setEvalBranchQuota(10000); + var a = hypervector_random(50, 12345); + var result : Hypervector = undefined; + for (0..1000) |_| { + result = hypervector_permute(a, 5); + } + _ = result; + + bench "hypervector_similarity_latency" + // Measure: cycles for similarity operation (50 trits) + // Target: < 200 cycles + @setEvalBranchQuota(10000); + var a = hypervector_random(50, 12345); + var b = hypervector_random(50, 98765); + var result : gf16 = undefined; + for (0..1000) |_| { + result = hypervector_similarity(a, b); + } + _ = result; + + bench "hypervector_dot_product_latency" + // Measure: cycles for dot product (50 trits) + // Target: < 300 cycles + @setEvalBranchQuota(10000); + var a = hypervector_random(50, 12345); + var b = hypervector_random(50, 98765); + var result : i32 = undefined; + for (0..1000) |_| { + result = hypervector_dot_product(a, b); + } + _ = result; +} diff --git a/apps/website/public/t27/specs/vsa/sequence_hdc.t27 b/apps/website/public/t27/specs/vsa/sequence_hdc.t27 new file mode 100644 index 0000000000..ed796865f8 --- /dev/null +++ b/apps/website/public/t27/specs/vsa/sequence_hdc.t27 @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: Apache-2.0 +// Module: Sequence Hyperdimensional Computing (HDC) +// phi^2 + 1/phi^2 = 3 | TRINITY + +module SequenceHdc { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; // Trit enum + use numeric::gf16; // GF16 for similarity scores + use ternary::packed_trit; // PackedBigInt for HV storage + + // ======================================================================== + // 1. Constants + // ======================================================================== + + // N-gram order for sequence encoding + pub const NGRAM_ORDER : u16 = 3; + + // Default dimension for hypervectors + pub const DEFAULT_DIM : u16 = 1000; + + // Number of printable ASCII characters in alphabet + pub const HEBDIAN_CHARS : usize = 95; + + // ASCII offset for printable characters + pub const HEBDIAN_OFFSET : usize = 32; + + // ======================================================================== + // 2. Item Memory Type + // ======================================================================== + + // ItemMemory: Maps symbols to random hypervectors + // Uses JIT engine for VSA operations, caches computed vectors + // Thread-safe access via allocator-based hash map + pub struct ItemMemory { + allocator_id : u16, // Allocator identifier + dimension : usize, // Hypervector dimension + seed : u64, // Seed for random generation + cache : map, // Symbol -> HV cache + } + + // ======================================================================== + // 3. N-gram Encoder Type + // ======================================================================== + + // NGramEncoder: Encodes sequences using n-gram permutations + // Uses permutation + bind operations with JIT acceleration + // Complexity: O(n) per encode where n = string length + pub struct NGramEncoder { + item_memory : *ItemMemory, // Shared item memory reference + jit_enabled : bool, // JIT acceleration enabled flag + } + + // ======================================================================== + // 4. Sequence Memory Type + // ======================================================================== + + // SequenceMemory: Stores encoded sequences with labels + // Supports similarity queries and top-k retrieval + // Thread-safe access via allocator-based hash map + pub struct SequenceMemory { + allocator_id : u16, // Allocator identifier + n : u16, // N-gram order for encoding + item_memory : *ItemMemory, // Reference to item memory + sequences : map, // Label -> encoded sequence + jit_enabled : bool, // JIT acceleration enabled flag + } + + // ======================================================================== + // 5. Sequence Types + // ======================================================================== + + // EncodedSequence: Label with encoded n-gram vector + // Stores result of n-gram encoding + pub struct EncodedSequence { + label : []const u8, // Label for the sequence + vector : PackedBigInt, // N-gram encoded vector + } + + // QueryResult: Result of sequence similarity query + // Stores matching sequence and similarity score + pub struct QueryResult { + label : []const u8, // Matching sequence label + similarity : f64, // Cosine similarity score + } + + // ======================================================================== + // 6. Item Memory Functions + // ======================================================================== + + // initItemMemory(allocator: std.mem.Allocator, dimension: usize, seed: u64) -> ItemMemory + // Create new item memory with given dimension and seed + // Initializes allocator and hash map + // Complexity: O(1) + pub fn initItemMemory(allocator_id: u16, dimension: usize, seed: u64) -> ItemMemory; + + // deinitItemMemory(item_memory: &ItemMemory) + // Destroy item memory and free resources + // Deallocates hash map and allocator + // Complexity: O(n) where n = cached items + pub fn deinitItemMemory(item_memory: &ItemMemory); + + // getVector(item_memory: &ItemMemory, symbol: u32) -> ?PackedBigInt + // Get hypervector for symbol (create if not exists) + // Returns vector pointer or null if not found + // Complexity: O(1) for hash lookup + pub fn getVector(item_memory: &ItemMemory, symbol: u32) -> ?PackedBigInt; + + // ======================================================================== + // 7. N-gram Encoder Functions + // ======================================================================== + + // initEncoder(item_memory: &ItemMemory, n: u16) -> NGramEncoder + // Create n-gram encoder with configured order + // Returns encoder ready for encoding operations + // Complexity: O(1) + pub fn initEncoder(item_memory: &ItemMemory, n: u16) -> NGramEncoder; + + // encodeNGram(encoder: &NGramEncoder, chars: []const u8) -> !EncodedSequence + // Encode a character sequence as n-gram vector + // Uses permutation + bind with JIT acceleration + // Complexity: O(len) where len = chars.length * n + pub fn encodeNGram(encoder: &NGramEncoder, chars: []const u8) -> !EncodedSequence; + + // ======================================================================== + // 8. Sequence Memory Functions + // ======================================================================== + + // initSequenceMemory(allocator: std.mem.Allocator, n: u16, seed: u64) -> SequenceMemory + // Create new sequence memory with given n-gram order + // Initializes hash map for sequence storage + // Complexity: O(1) + pub fn initSequenceMemory(allocator_id: u16, n: u16, seed: u64) -> SequenceMemory; + + // deinitSequenceMemory(seq_mem: &SequenceMemory) + // Destroy sequence memory and free resources + // Deallocates hash map and allocator + // Complexity: O(n) where n = stored sequences + pub fn deinitSequenceMemory(seq_mem: &SequenceMemory); + + // store(seq_mem: &SequenceMemory, label: []const u8, str: []const u8) + // Encode and store a labeled sequence + // Returns encoded n-gram vector for the sequence + // Complexity: O(len) where len = str.length * n + pub fn store(seq_mem: &SequenceMemory, label: []const u8, str: []const u8); + + // query(seq_mem: &SequenceMemory, str: []const u8, k: usize) -> ?QueryResult + // Find top-k most similar sequences to query + // Returns best matching sequence with similarity score + // Complexity: O(n * m) where n = stored sequences, m = NGRAM_ORDER * len + pub fn query(seq_mem: &SequenceMemory, str: []const u8, k: usize) -> ?QueryResult; + + // queryTopK(seq_mem: &SequenceMemory, k: usize) -> []QueryResult + // Find top-k most similar sequences + // Returns array of top-k results sorted by similarity + // Complexity: O(n * m * k) for full scan + pub fn queryTopK(seq_mem: &SequenceMemory, k: usize) -> []QueryResult; + + // ======================================================================== + // 9. Language Detection Functions + // ======================================================================== + + // initDetector(item_memory: &ItemMemory, dimension: usize) -> LanguageDetector + // Create language detector with given dimension + // Returns detector ready for training and detection + // Complexity: O(1) + pub fn initDetector(item_memory: &ItemMemory, dimension: usize) -> LanguageDetector; + + // trainDetector(detector: &LanguageDetector, language: []const u8, samples: []const u8) + // Train detector on labeled text samples per language + // Stores character n-grams for each language + // Complexity: O(p * n) where p = samples.len, n = dimension + pub fn trainDetector(detector: &LanguageDetector, language: []const u8, samples: []const u8); + + // detect(detector: &LanguageDetector, text: []const u8) -> !SequenceMemory.QueryResult + // Detect language of input text + // Returns best matching language with similarity score + // Complexity: O(n * m) where n = dimension, m = languages + pub fn detect(detector: &LanguageDetector, text: []const u8) -> !SequenceMemory.QueryResult; + + // ======================================================================== + // TDD - Tests + // ======================================================================== + + test item_memory_get_vector_creates_on_miss + // Verify: getVector creates new vector for missing symbol + given item = initItemMemory(0, 100, 123) + when result = getVector(&item, 65) + then result != null + + test item_memory_get_vector_returns_existing + // Verify: getVector returns cached vector for existing symbol + given item = initItemMemory(0, 100, 123) + when vec = getVector(&item, 65) + and stored = getVector(&item, 65) + then vec == stored + + test ngram_encoder_creates_valid_encoding + // Verify: encodeNGram creates valid n-gram vector + given item = initItemMemory(0, 100, 123) + and encoder = initEncoder(&item, 3) + when result = encodeNGram(&encoder, "abc") + then result.vector.trit_count() > 0 + + test ngram_encoder_same_input_same_output + // Verify: Encoding same input produces identical output + given item = initItemMemory(0, 100, 123) + and encoder = initEncoder(&item, 3) + when result1 = encodeNGram(&encoder, "abc") + and result2 = encodeNGram(&encoder, "abc") + then compareAbs(&result1.vector, &result2.vector) == 0 + + test ngram_encoder_different_input_different_output + // Verify: Encoding different input produces different output + given item = initItemMemory(0, 100, 123) + and encoder = initEncoder(&item, 3) + when result1 = encodeNGram(&encoder, "abc") + and result2 = encodeNGram(&encoder, "xyz") + then compareAbs(&result1.vector, &result2.vector) != 0 + + test sequence_store_creates_entry + // Verify: store adds labeled sequence to memory + given seq = initSequenceMemory(0, 3, 123) + when store(&seq, "hello", "hello") + then query(&seq, "hello", 1) != null + + test sequence_query_finds_best_match + // Verify: query returns most similar sequence + given seq = initSequenceMemory(0, 3, 123) + when store(&seq, "hello world", "hello world") + and store(&seq, "hello there", "hello there") + and result = query(&seq, "hello ther", 1) + then result != null and result.label == "hello world" + + test detector_trains_on_samples + // Verify: trainDetector learns from labeled samples + given item = initItemMemory(0, 1000, 123) + and detector = initDetector(&item, 100) + when train(&detector, "english", "the quick brown fox") + and train(&detector, "german", "der schnelle braune fuchs") + and english = detect(&detector, "the quick brown fox") + then english.label == "english" + + test detector_detects_correct_language + // Verify: detect returns correct language for sample + given item = initItemMemory(0, 1000, 123) + and detector = initDetector(&item, 100) + and _ = train(&detector, "english", "the quick brown fox") + and result = detect(&detector, "der schnelle braune fuchs") + then result.label == "german" + + // ======================================================================== + // TDD - Invariants + // ======================================================================== + + invariant item_memory_thread_safe + // Verify: Item memory operations are thread-safe + // Concurrent access must be safe via allocator locking + assert true; + + invariant ngram_encoding_preserves_order + // Verify: N-gram encoding preserves sequential information + // Permutation ensures characters are processed in order + assert true; + + invariant sequence_query_returns_subset + // Verify: Query results reference stored sequences + // Returned labels must be from stored sequences + assert true; + + invariant language_detector_separates_languages + // Verify: Detector can distinguish between trained languages + // Languages should have distinct signatures + assert true; + + invariant query_similarity_normalized + // Verify: Query similarity is normalized [-1.0, 1.0] + // Cosine similarity must be in valid range + assert true; + + // ======================================================================== + // TDD - Benchmarks + // ======================================================================== + + bench ngram_encode_latency_10_chars + // Measure: cycles to encode 10-character sequence + // Target: < 50000 cycles (JIT-accelerated n-gram encoding) + @setEvalBranchQuota(10000); + var item = initItemMemory(0, 1000, 123); + var encoder = initEncoder(&item, 3); + _ = encodeNGram(&encoder, "hello world"); + _ = encoder; + + bench ngram_decode_latency_10_chars + // Measure: cycles to decode 10-character sequence + // Target: < 50000 cycles (VSA operations + similarity search) + @setEvalBranchQuota(10000); + var seq = initSequenceMemory(0, 3, 123); + var _ = store(&seq, "test", "test"); + _ = query(&seq, "test", 1); + _ = seq; + + bench sequence_query_latency_100_entries + // Measure: cycles to query top-k from 100 entries + // Target: < 200000 cycles (similarity comparisons) + @setEvalBranchQuota(10000); + var seq = initSequenceMemory(0, 3, 123); + for (0..100) |_| { + _ = store(&seq, "entry", "entry"); + } + _ = query(&seq, "query", 10); + _ = seq; + + bench detector_train_latency + // Measure: cycles to train detector on 2 languages + // Target: < 100000 cycles (n-gram encoding per sample) + @setEvalBranchQuota(10000); + var item = initItemMemory(0, 1000, 123); + var detector = initDetector(&item, 1000); + _ = train(&detector, "english", "the quick brown fox"); + _ = train(&detector, "german", "der schnelle braune fuchs"); + _ = detector; + + bench item_memory_cache_hit_latency + // Measure: cycles for cache hit (vector already exists) + // Target: < 200 cycles (hash map lookup) + @setEvalBranchQuota(10000); + var item = initItemMemory(0, 100, 123); + var vec = getVector(&item, 65); + _ = getVector(&item, 65); + _ = item; + + bench item_memory_cache_miss_latency + // Measure: cycles for cache miss (needs vector creation) + // Target: < 1000 cycles (hash insert + vector generation) + @setEvalBranchQuota(10000); + var item = initItemMemory(0, 100, 123); + _ = getVector(&item, 66); + _ = item; +} diff --git a/apps/website/public/t27/specs/vsa/similarity_search.t27 b/apps/website/public/t27/specs/vsa/similarity_search.t27 new file mode 100644 index 0000000000..14c2053310 --- /dev/null +++ b/apps/website/public/t27/specs/vsa/similarity_search.t27 @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/vsa/similarity_search.t27 +// VSA Similarity Search Specification +// Ring 062 - Efficient similarity search in hyperdimensional space +// Defines semantic similarity operations for VSA trit vectors +// phi^2 + 1/phi^2 = 3 | TRINITY + +module VSASimilaritySearch { + use vsa::vsa_core; + use math::constants; + + // ===================================================== + // 1. Search Configuration + // ========================================================================= + + const MAX_VECTORS : usize = 10000; + const TOP_K_DEFAULT : usize = 10; + const SIMILARITY_THRESHOLD_DEFAULT : f64 = 0.5; + + // Similarity metrics + const SIMILARITY_COSINE : u8 = 0; // Cosine similarity + const SIMILARITY_DOT : u8 = 1; // Dot product + const SIMILARITY_HAMMING : u8 = 2; // Hamming similarity + const SIMILARITY_JACCARD : u8 = 3; // Jaccard index + + // Search result entry + struct SearchResult { + index : usize, + similarity : f64, + distance : f64, + } + + // Search results + struct SearchResults { + results : [TOP_K_DEFAULT]SearchResult, + count : usize, + query_hash : u64, + } + + // ===================================================== + // 2. Cosine Similarity + // ========================================================================= + + // cosine_similarity(a: []i32, b: []i32, len: usize) -> f64 + // Compute cosine similarity between two trit vectors + // Range: -1.0 (opposite) to 1.0 (identical) + fn cosine_similarity(a: []i32, b: []i32, len: usize) -> f64 { + var dot_product : i64 = 0; + var norm_a : i64 = 0; + var norm_b : i64 = 0; + var i : usize = 0; + + while (i < len) { + const ai = a[i]; + const bi = b[i]; + + dot_product = dot_product + @as(i64, @intCast(ai * bi)); + norm_a = norm_a + @as(i64, @intCast(ai * ai)); + norm_b = norm_b + @as(i64, @intCast(bi * bi)); + + i = i + 1; + } + + // Avoid division by zero + if (norm_a == 0 or norm_b == 0) { + return 0.0; + } + + const norm_a_f = @sqrt(@as(f64, @floatFromInt(norm_a))); + const norm_b_f = @sqrt(@as(f64, @floatFromInt(norm_b))); + + return @as(f64, @floatFromInt(dot_product)) / (norm_a_f * norm_b_f); + } + + // ===================================================== + // 3. Hamming Similarity + // ========================================================================= + + // hamming_similarity(a: []i32, b: []i32, len: usize) -> f64 + // Compute Hamming similarity between two trit vectors + // Range: 0.0 (no match) to 1.0 (identical) + fn hamming_similarity(a: []i32, b: []i32, len: usize) -> f64 { + var matches : usize = 0; + var i : usize = 0; + + while (i < len) { + if (a[i] == b[i]) { + matches = matches + 1; + } + i = i + 1; + } + + return @as(f64, @floatFromInt(matches)) / @as(f64, @floatFromInt(len)); + } + + // ===================================================== + // 4. Jaccard Index + // ========================================================================= + + // jaccard_index(a: []i32, b: []i32, len: usize) -> f64 + // Compute Jaccard index between two trit vectors + // Uses binary representation (non-zero = 1) + fn jaccard_index(a: []i32, b: []i32, len: usize) -> f64 { + var intersection : usize = 0; + var union_count : usize = 0; + var i : usize = 0; + + while (i < len) { + const a_nonzero = a[i] != 0; + const b_nonzero = b[i] != 0; + + if (a_nonzero and b_nonzero) { + intersection = intersection + 1; + union_count = union_count + 1; + } else if (a_nonzero or b_nonzero) { + union_count = union_count + 1; + } + + i = i + 1; + } + + if (union_count == 0) { + return 1.0; + } + + return @as(f64, @floatFromInt(intersection)) / @as(f64, @floatFromInt(union_count)); + } + + // ===================================================== + // 5. Top-K Search + // ========================================================================= + + // find_top_k(query: []i32, vectors: [][]i32, k: usize) -> SearchResults + // Find top K most similar vectors to query + fn find_top_k(query: []i32, vectors: [][]i32, k: usize, metric: u8) -> SearchResults { + var results : SearchResults = undefined; + results.count = 0; + + var i : usize = 0; + while (i < vectors.len and i < MAX_VECTORS) { + var similarity : f64 = 0.0; + + if (metric == SIMILARITY_COSINE) { + similarity = cosine_similarity(query, vectors[i], query.len); + } else if (metric == SIMILARITY_HAMMING) { + similarity = hamming_similarity(query, vectors[i], query.len); + } else if (metric == SIMILARITY_JACCARD) { + similarity = jaccard_index(query, vectors[i], query.len); + } else { + similarity = 0.0; + } + + // Insert into results if similarity is high enough + if (similarity >= SIMILARITY_THRESHOLD_DEFAULT) { + insert_result(&results, SearchResult{ + .index = i, + .similarity = similarity, + .distance = 1.0 - similarity, + }, k); + } + + i = i + 1; + } + + // Sort by similarity (descending) + sort_results(&results); + + return results; + } + + // insert_result() -> void + // Insert a result into top-K list, maintaining size + fn insert_result(results: *SearchResults, result: SearchResult, k: usize) void { + if (results.count < k) { + results.results[results.count] = result; + results.count = results.count + 1; + } else { + // Replace lowest similarity if higher + var min_idx : usize = 0; + var j : usize = 1; + + while (j < k) { + if (results.results[j].similarity < results.results[min_idx].similarity) { + min_idx = j; + } + j = j + 1; + } + + if (result.similarity > results.results[min_idx].similarity) { + results.results[min_idx] = result; + } + } + } + + // sort_results() -> void + // Sort results by similarity (descending) - bubble sort + fn sort_results(results: *SearchResults) void { + var i : usize = 0; + while (i < results.count) { + var j : usize = 0; + while (j < results.count - i - 1) { + if (results.results[j].similarity < results.results[j + 1].similarity) { + const temp = results.results[j]; + results.results[j] = results.results[j + 1]; + results.results[j + 1] = temp; + } + j = j + 1; + } + i = i + 1; + } + } + + // ===================================================== + // 6. HNSW-like Approximate Search + // ========================================================================= + + // Hierarchical Navigable Small World graph structure + struct HNSWNode { + vector_idx : usize, + neighbors : [4]usize, + neighbor_count : usize, + level : u8, + } + + const HNSW_MAX_NODES : usize = 1000; + var hnsw_nodes : [HNSW_MAX_NODES]HNSWNode = undefined; + var hnsw_count : usize = 0; + + // hnsw_search(query: []i32, k: usize) -> SearchResults + // Approximate search using HNSW graph + fn hnsw_search(query: []i32, k: usize) -> SearchResults { + var results : SearchResults = undefined; + results.count = 0; + + if (hnsw_count == 0) { + return results; + } + + // Start from a random node (simplified) + var current : usize = 0; + var best_similarity : f64 = -1.0; + + // Greedy search on top level + var i : usize = 0; + while (i < hnsw_count) { + const similarity = cosine_similarity( + query, + get_vector(hnsw_nodes[i].vector_idx), + query.len + ); + + if (similarity > best_similarity) { + best_similarity = similarity; + current = i; + } + + i = i + 1; + } + + // Collect results from neighbors + collect_neighbors(&results, current, k, query, query.len); + + return results; + } + + // collect_neighbors() -> void + // Collect results from node neighbors + fn collect_neighbors(results: *SearchResults, node_idx: usize, k: usize, query: []i32, len: usize) void { + const node = hnsw_nodes[node_idx]; + var i : usize = 0; + + while (i < node.neighbor_count and i < 4) { + const neighbor_idx = node.neighbors[i]; + const similarity = cosine_similarity(query, get_vector(neighbor_idx), len); + + insert_result(results, SearchResult{ + .index = neighbor_idx, + .similarity = similarity, + .distance = 1.0 - similarity, + }, k); + + i = i + 1; + } + } + + // get_vector() -> []i32 + // Get vector by index (placeholder) + fn get_vector(idx: usize) -> []i32 { + // Placeholder: would return actual vector + var dummy : [10]i32 = [0; 10]; + return dummy[0..10]; + } + + // ===================================================== + // 7. TDD - Tests + // ========================================================================= + + test cosine_similarity_identical + var a : [5]i32 = [_]i32{1, 0, -1, 1, 0}; + var b : [5]i32 = [_]i32{1, 0, -1, 1, 0}; + + when result = cosine_similarity(&a, &b, 5) + then abs(result - 1.0) < 1e-10 + + test cosine_similarity_opposite + var a : [5]i32 = [_]i32{1, 1, 1, 1, 1}; + var b : [5]i32 = [_]i32{-1, -1, -1, -1, -1}; + + when result = cosine_similarity(&a, &b, 5) + then abs(result + 1.0) < 1e-10 + + test cosine_similarity_orthogonal + var a : [3]i32 = [_]i32{1, 0, 0}; + var b : [3]i32 = [_]i32{0, 1, 0}; + + when result = cosine_similarity(&a, &b, 3) + then abs(result) < 1e-10 + + test hamming_similarity_identical + var a : [5]i32 = [_]i32{1, 0, -1, 1, 0}; + var b : [5]i32 = [_]i32{1, 0, -1, 1, 0}; + + when result = hamming_similarity(&a, &b, 5) + then abs(result - 1.0) < 1e-10 + + test hamming_similarity_partial + var a : [5]i32 = [_]i32{1, 0, -1, 1, 0}; + var b : [5]i32 = [_]i32{1, 1, -1, 0, 0}; + + when result = hamming_similarity(&a, &b, 5) + then abs(result - 0.6) < 1e-10 + + test jaccard_index_identical + var a : [5]i32 = [_]i32{1, 0, 1, 0, 1}; + var b : [5]i32 = [_]i32{1, 0, 1, 0, 1}; + + when result = jaccard_index(&a, &b, 5) + then abs(result - 1.0) < 1e-10 + + test jaccard_index_disjoint + var a : [5]i32 = [_]i32{1, 0, 0, 0, 0}; + var b : [5]i32 = [_]i32{0, 1, 0, 0, 0}; + + when result = jaccard_index(&a, &b, 5) + then abs(result) < 1e-10 + + test find_top_k_basic + var query : [3]i32 = [_]i32{1, 0, 1}; + var vectors : [3][3]i32 = [_][3]i32{ + [_]i32{1, 0, 1}, + [_]i32{-1, 0, -1}, + [_]i32{1, 1, 0}, + }; + + var vectors_slice : [3][]i32 = undefined; + var i : usize = 0; + while (i < 3) { + vectors_slice[i] = vectors[i][0..3]; + i = i + 1; + } + + when results = find_top_k(&query, &vectors_slice, 2, SIMILARITY_COSINE) + then results.count >= 1 + and results.results[0].index == 0 + + // ===================================================== + // 8. TDD - Invariants + // ========================================================================= + + invariant cosine_similarity_bounds + // Cosine similarity must be in [-1, 1] + var a : [3]i32 = [_]i32{1, 0, -1}; + var b : [3]i32 = [_]i32{0, 1, 1}; + + const result = cosine_similarity(&a, &b, 3); + assert result >= -1.0 + assert result <= 1.0 + + invariant hamming_similarity_bounds + // Hamming similarity must be in [0, 1] + var a : [3]i32 = [_]i32{1, 0, -1}; + var b : [3]i32 = [_]i32{0, 1, 1}; + + const result = hamming_similarity(&a, &b, 3); + assert result >= 0.0 + assert result <= 1.0 + + invariant jaccard_index_bounds + // Jaccard index must be in [0, 1] + var a : [3]i32 = [_]i32{1, 0, -1}; + var b : [3]i32 = [_]i32{0, 1, 1}; + + const result = jaccard_index(&a, &b, 3); + assert result >= 0.0 + assert result <= 1.0 + + invariant similarity_symmetry + // Similarity is symmetric: sim(a, b) == sim(b, a) + var a : [3]i32 = [_]i32{1, 0, -1}; + var b : [3]i32 = [_]i32{0, 1, 1}; + + const cosine_ab = cosine_similarity(&a, &b, 3); + const cosine_ba = cosine_similarity(&b, &a, 3); + const hamming_ab = hamming_similarity(&a, &b, 3); + const hamming_ba = hamming_similarity(&b, &a, 3); + + assert abs(cosine_ab - cosine_ba) < 1e-10 + assert abs(hamming_ab - hamming_ba) < 1e-10 + + invariant top_k_ordered + // Top-K results must be ordered by similarity (descending) + var query : [3]i32 = [_]i32{1, 0, 1}; + var vectors : [3][3]i32 = [_][3]i32{ + [_]i32{1, 0, 1}, + [_]i32{-1, 0, -1}, + [_]i32{1, 1, 0}, + }; + + var vectors_slice : [3][]i32 = undefined; + var i : usize = 0; + while (i < 3) { + vectors_slice[i] = vectors[i][0..3]; + i = i + 1; + } + + const results = find_top_k(&query, &vectors_slice, 2, SIMILARITY_COSINE); + if (results.count >= 2) { + assert results.results[0].similarity >= results.results[1].similarity + } + + // ===================================================== + // 9. TDD - Benchmarks + // ========================================================================= + + bench cosine_similarity_1000 + // Measure: cycles to compute cosine similarity for 1000-dim vectors + // Target: < 5000 cycles + var a : [1000]i32 = undefined; + var b : [1000]i32 = undefined; + @setEvalBranchQuota(10000); + var result : f64 = 0.0; + for (0..10) |_| { + result = cosine_similarity(&a, &b, 1000); + } + _ = result; + + bench find_top_k_100_vectors + // Measure: cycles to find top-K from 100 vectors + // Target: < 10000 cycles + var query : [100]i32 = undefined; + var vectors : [100][100]i32 = undefined; + + var vectors_slice : [100][]i32 = undefined; + var i : usize = 0; + while (i < 100) { + vectors_slice[i] = vectors[i][0..100]; + i = i + 1; + } + + @setEvalBranchQuota(10000); + var results : SearchResults = undefined; + for (0..5) |_| { + results = find_top_k(&query, &vectors_slice, 5, SIMILARITY_COSINE); + } + _ = results; +} diff --git a/apps/website/public/t27/specs/vsa/vsa_core.t27 b/apps/website/public/t27/specs/vsa/vsa_core.t27 new file mode 100644 index 0000000000..c8f3e8f8c0 --- /dev/null +++ b/apps/website/public/t27/specs/vsa/vsa_core.t27 @@ -0,0 +1,1011 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/vsa/vsa_core.t27 +// VSA (Vector Symbolic Architecture) Core Operations +// 01234 567891011: V = n 12 3^k 13 14^m 15 16^p 17 e^q +// phi^2 + 1/phi^2 = 3 | TRINITY +// +// VSA provides high-dimensional vector operations for: +// - Symbolic reasoning (bind/unbind for role-filler pairs) +// - Set operations (bundle for superposition) +// - Similarity search (cosine, hamming, dot product) +// - Sequence encoding (permute for position) +// +// Key properties: +// - bind(a, bind(a, b)) = b (self-inverse for XOR-like binding) +// - bundle is used for set-like superposition +// - permute provides position-aware encoding + +module vsa_core; + +// ============================================================================ +// Imports +// ============================================================================ + +use tritype-base::Trit; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Default VSA dimension (hypervector size) +pub const DEFAULT_DIM : usize = 1024; + +/// SIMD width for parallel trit operations +pub const SIMD_WIDTH : usize = 32; + +/// Maximum trits per hypervector +pub const MAX_TRITS : usize = 256; + +/// Cosine similarity threshold for "match" +pub const COSINE_THRESHOLD : gf16 = 0.7; + +/// Hamming similarity threshold for "match" +pub const HAMMING_THRESHOLD : gf16 = 0.8; + +// ============================================================================ +// Types +// ============================================================================ + +/// Hypervector representation (array of trits) +pub const Hypervector = []Trit; + +/// Search result with similarity score +pub const SearchResult = struct { + index : usize, + similarity : gf16, +}; + +// ============================================================================ +// Core VSA Operations +// ============================================================================ + +/// Generate random hypervector with given seed +/// Uses deterministic random number generation +/// Complexity: O(dim) +pub fn random_vector(seed: u64, dim: usize) Hypervector { + var result = [_]Trit{TRIT_ZERO} ** dim; + var rng = seed; + + var i : usize = 0; + while (i < dim) { + // Simple LCG for deterministic random + rng = rng * 1103515245 + 12345; + const trit_val : i8 = @intCast(@mod(rng, 3)); + result[i] = @intCast(trit_val - 1); // Map 0,1,2 to -1,0,1 + i = i + 1; + } + + return result; +} + +/// Bind operation (XOR-like for associative memory) +/// Property: bind(a, bind(a, b)) = b (self-inverse) +/// Used for: role-value binding, symbolic reasoning +/// Complexity: O(dim) +pub fn bind(a: Hypervector, b: Hypervector) Hypervector { + const dim = @min(a.len, b.len); + var result = [_]Trit{TRIT_ZERO} ** dim; + + var i : usize = 0; + while (i < dim) { + const ai = a[i]; + const bi = b[i]; + + if (ai == TRIT_ZERO) { + result[i] = bi; + } else if (bi == TRIT_ZERO) { + result[i] = ai; + } else { + // Both non-zero: multiply (both are +/-1) + result[i] = if (ai == bi) { TRIT_POS } else { TRIT_NEG }; + } + i = i + 1; + } + + return result; +} + +/// Unbind operation (inverse of bind) +/// For XOR-like binding: unbind(x, y) = bind(x, y) +/// Complexity: O(dim) +pub fn unbind(bound: Hypervector, key: Hypervector) Hypervector { + return bind(bound, key); +} + +/// Bundle2 operation (majority vote of 2 vectors) +/// Used for: superposition, set union +/// Complexity: O(dim) +pub fn bundle2(a: Hypervector, b: Hypervector) Hypervector { + const dim = @min(a.len, b.len); + var result = [_]Trit{TRIT_ZERO} ** dim; + + var i : usize = 0; + while (i < dim) { + const ai = a[i]; + const bi = b[i]; + + if (ai == TRIT_ZERO) { + result[i] = bi; + } else if (bi == TRIT_ZERO) { + result[i] = ai; + } else { + // Both non-zero: determine majority + const sum = ai + bi; + result[i] = if (sum > 0) { TRIT_POS } + else if (sum < 0) { TRIT_NEG } + else { TRIT_ZERO }; + } + i = i + 1; + } + + return result; +} + +/// Bundle3 operation (majority vote of 3 vectors) +/// Used for: robust superposition, noise reduction +/// Complexity: O(dim) +pub fn bundle3(a: Hypervector, b: Hypervector, c: Hypervector) Hypervector { + const dim = @min(@min(a.len, b.len), c.len); + var result = [_]Trit{TRIT_ZERO} ** dim; + + var i : usize = 0; + while (i < dim) { + const sum = a[i] + b[i] + c[i]; + result[i] = if (sum > 0) { TRIT_POS } + else if (sum < 0) { TRIT_NEG } + else { TRIT_ZERO }; + i = i + 1; + } + + return result; +} + +/// Permute operation (circular shift of hypervector) +/// Used for: sequence encoding, position tagging +/// Complexity: O(dim) +pub fn permute(v: Hypervector, shift: usize) Hypervector { + if (v.len == 0) { + return v; + } + + const effective_shift = @mod(shift, v.len); + if (effective_shift == 0) { + return v; + } + + var result = [_]Trit{TRIT_ZERO} ** v.len; + + var i : usize = 0; + while (i < v.len) { + result[(i + effective_shift) % v.len] = v[i]; + i = i + 1; + } + + return result; +} + +/// Inverse permute (circular shift in opposite direction) +/// Property: inverse_permute(permute(v, n), n) = v +/// Complexity: O(dim) +pub fn inverse_permute(v: Hypervector, shift: usize) Hypervector { + if (v.len == 0) { + return v; + } + + const effective_shift = @mod(shift, v.len); + if (effective_shift == 0) { + return v; + } + + // Inverse shift is (len - shift) mod len + const inverse_shift = (v.len - effective_shift) % v.len; + return permute(v, inverse_shift); +} + +/// Cosine similarity between two hypervectors +/// Formula: (a*b) / (||a|| * ||b||) +/// Range: [-1, 1] where 1 = identical, -1 = opposite +/// Complexity: O(dim) +pub fn cosine_similarity(a: Hypervector, b: Hypervector) gf16 { + const dim = @min(a.len, b.len); + + // Compute dot product + var dot : i64 = 0; + var i : usize = 0; + while (i < dim) { + dot += @as(i64, a[i]) * @as(i64, b[i]); + i = i + 1; + } + + // Compute norms + var norm_a : f64 = 0; + var norm_b : f64 = 0; + i = 0; + while (i < dim) { + if (a[i] != TRIT_ZERO) { + norm_a += 1.0; + } + if (b[i] != TRIT_ZERO) { + norm_b += 1.0; + } + i = i + 1; + } + + norm_a = @sqrt(norm_a); + norm_b = @sqrt(norm_b); + + if (norm_a == 0.0 or norm_b == 0.0) { + return 0.0; + } + + return @as(gf16, @floatCast(dot) / (norm_a * norm_b)); +} + +/// Hamming distance between two hypervectors +/// Counts positions where trits differ +/// For unequal lengths, adds the difference in length +/// Complexity: O(min(len(a), len(b))) +pub fn hamming_distance(a: Hypervector, b: Hypervector) usize { + const len = @min(a.len, b.len); + var distance : usize = 0; + + var i : usize = 0; + while (i < len) { + if (a[i] != b[i]) { + distance += 1; + } + i = i + 1; + } + + // Add difference in length + if (a.len > b.len) { + distance += a.len - b.len; + } else { + distance += b.len - a.len; + } + + return distance; +} + +/// Hamming similarity (normalized to [0, 1]) +/// Formula: 1 - (hamming_distance / max_length) +/// Complexity: O(min(len(a), len(b))) +pub fn hamming_similarity(a: Hypervector, b: Hypervector) gf16 { + const max_len = @max(a.len, b.len); + if (max_len == 0) { + return 1.0; + } + + const distance = hamming_distance(a, b); + return @as(gf16, 1.0) - @as(gf16, @floatCast(distance) / @as(gf16, @floatCast(max_len))); +} + +/// Dot product similarity (raw dot product, normalized by dimension) +/// Formula: (a*b) / dim +/// Range: [-1, 1] for unit trits +/// Complexity: O(dim) +pub fn dot_similarity(a: Hypervector, b: Hypervector) gf16 { + const dim = @min(a.len, b.len); + if (dim == 0) { + return 0.0; + } + + var dot : i64 = 0; + var i : usize = 0; + while (i < dim) { + dot += @as(i64, a[i]) * @as(i64, b[i]); + i = i + 1; + } + + return @as(gf16, @floatCast(dot) / @as(gf16, @floatCast(dim))); +} + +/// Vector norm (L2 norm) +/// Formula: sqrt(Sigma trit[i]^2)) +/// For trits, this is sqrt(count of non-zero trits) +/// Complexity: O(dim) +pub fn vector_norm(v: Hypervector) gf16 { + var count : usize = 0; + var i : usize = 0; + while (i < v.len) { + if (v[i] != TRIT_ZERO) { + count += 1; + } + i = i + 1; + } + + return @as(gf16, @sqrt(@floatCast(count))); +} + +/// Bundle N operation (generalized majority vote) +/// Iteratively applies bundle3 to all vectors +/// Complexity: O(n * dim) +pub fn bundle_n(vectors: []const Hypervector, dim: usize) Hypervector { + const count = vectors.len; + if (count == 0) { + return [_]Trit{TRIT_ZERO} ** dim; + } + if (count == 1) { + return vectors[0]; + } + + var result = bundle3(vectors[0], vectors[1], vectors[2]); + + var i : usize = 3; + while (i < count) { + // Use bundle3 with result and next vector + const temp = result; + result = bundle3(temp, temp, vectors[i]); + i = i + 1; + } + + return result; +} + +/// Count non-zero trits in hypervector +/// Used for: sparsity analysis, norm calculation +/// Complexity: O(dim) +pub fn count_non_zero(v: Hypervector) usize { + var count : usize = 0; + var i : usize = 0; + while (i < v.len) { + if (v[i] != TRIT_ZERO) { + count += 1; + } + i = i + 1; + } + return count; +} + +/// Encode sequence using position-aware binding +/// Each item is bound with its position (via permute) +/// Complexity: O(sequence_len * dim) +pub fn encode_sequence(items: []const usize, dim: usize) Hypervector { + var result = [_]Trit{TRIT_ZERO} ** dim; + + var i : usize = 0; + while (i < items.len) { + // Generate item vector (simplified - uses item value as seed) + const item_vec = random_vector(@intCast(items[i]), dim); + // Shift by position + const shifted = permute(item_vec, i); + // Add to result via bundle + result = bundle2(result, shifted); + i = i + 1; + } + + return result; +} + +/// Probe sequence for item at position +/// Checks if item is present at given position in encoded sequence +/// Complexity: O(dim) +pub fn probe_sequence(sequence: Hypervector, item: usize, position: usize, dim: usize) gf16 { + // Generate item vector + const item_vec = random_vector(@intCast(item), dim); + // Shift to position + const shifted = permute(item_vec, position); + + // Check similarity (should use cosine, but using dot for simplicity) + const sim = dot_similarity(sequence, shifted); + + return sim; +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "vsa_random_vector_dimension" { + // Verify random vector has correct dimension + const v1 = random_vector(42, 100); + try std.testing.expect(v1.len == 100); + + const v2 = random_vector(123, 256); + try std.testing.expect(v2.len == 256); +} + +test "vsa_random_vector_deterministic" { + // Verify random vector is deterministic (same seed = same result) + const v1 = random_vector(42, 50); + const v2 = random_vector(42, 50); + try std.testing.expectEqual(@as(usize, v1.len), @as(usize, v2.len)); + + var i : usize = 0; + while (i < v1.len) { + try std.testing.expectEqual(v1[i], v2[i]); + i = i + 1; + } +} + +test "vsa_bind_self_inverse" { + // Verify bind(a, bind(a, b)) ~= b + var a = [_]Trit{TRIT_POS} ** 10; + var b = [_]Trit{TRIT_NEG} ** 10; + + // Set some values + a[0] = TRIT_POS; + a[1] = TRIT_NEG; + a[2] = TRIT_ZERO; + + b[0] = TRIT_NEG; + b[1] = TRIT_POS; + b[2] = TRIT_POS; + + const bound = bind(a, b); + const unbound = unbind(bound, a); + + // Check that unbound approximates b + var match_count : usize = 0; + var i : usize = 0; + while (i < b.len) { + if (unbound[i] == b[i]) { + match_count += 1; + } + i = i + 1; + } + try std.testing.expect(match_count == b.len); +} + +test "vsa_bind_zero_identity" { + // Verify bind with zero acts as identity + var a = [_]Trit{TRIT_POS} ** 10; + var zero = [_]Trit{TRIT_ZERO} ** 10; + + const result1 = bind(a, zero); + const result2 = bind(zero, a); + + try std.testing.expectEqual(@as(usize, result1.len), @as(usize, a.len)); + try std.testing.expectEqual(@as(usize, result2.len), @as(usize, a.len)); + + var i : usize = 0; + while (i < a.len) { + try std.testing.expectEqual(result1[i], a[i]); + try std.testing.expectEqual(result2[i], a[i]); + i = i + 1; + } +} + +test "vsa_bundle2_consensus" { + // Verify bundle2 gives correct majority + var a = [_]Trit{TRIT_POS} ** 5; + var b = [_]Trit{TRIT_NEG} ** 5; + + const result = bundle2(&a, &b); + + // POS + NEG = ZERO (tie, so zero) + try std.testing.expect(result[0] == TRIT_ZERO); + try std.testing.expect(result[1] == TRIT_ZERO); +} + +test "vsa_bundle3_voting" { + // Verify bundle3 does majority voting + var a = [_]Trit{TRIT_POS} ** 3; + var b = [_]Trit{TRIT_POS} ** 3; + var c = [_]Trit{TRIT_NEG} ** 3; + + const result = bundle3(&a, &b, &c); + + // POS + POS + NEG = POS (2 vs 1) + try std.testing.expect(result[0] == TRIT_POS); +} + +test "vsa_permute_shifts_correctly" { + // Verify permute shifts elements correctly + var v = [_]Trit{TRIT_POS} ** 5; + v[0] = TRIT_POS; + v[1] = TRIT_NEG; + v[2] = TRIT_ZERO; + v[3] = TRIT_NEG; + v[4] = TRIT_POS; + + const shifted = permute(&v, 1); + + try std.testing.expect(shifted[1] == TRIT_POS); // v[0] moved to position 1 + try std.testing.expect(shifted[2] == TRIT_NEG); // v[1] moved to position 2 + try std.testing.expect(shifted[3] == TRIT_ZERO); // v[2] moved to position 3 + try std.testing.expect(shifted[4] == TRIT_NEG); // v[3] moved to position 4 + try std.testing.expect(shifted[0] == TRIT_POS); // v[4] wrapped to position 0 +} + +test "vsa_inverse_permute_reverses" { + // Verify inverse_permute reverses permute + var v = [_]Trit{TRIT_POS} ** 10; + + var i : usize = 0; + while (i < v.len) { + v[i] = @intCast(@mod(i, 3) - 1); + i = i + 1; + } + + const shifted = permute(&v, 3); + const restored = inverse_permute(&shifted, 3); + + try std.testing.expectEqual(@as(usize, restored.len), @as(usize, v.len)); + + i = 0; + while (i < v.len) { + try std.testing.expectEqual(restored[i], v[i]); + i = i + 1; + } +} + +test "vsa_cosine_identical" { + // Verify cosine similarity is 1.0 for identical vectors + var v = [_]Trit{TRIT_POS} ** 10; + v[0] = TRIT_POS; + v[1] = TRIT_NEG; + v[2] = TRIT_ZERO; + + const sim = cosine_similarity(&v, &v); + try std.testing.expect(sim == 1.0); +} + +test "vsa_cosine_opposite" { + // Verify cosine similarity is -1.0 for opposite vectors + var a = [_]Trit{TRIT_POS} ** 10; + var b = [_]Trit{TRIT_NEG} ** 10; + + var i : usize = 0; + while (i < a.len) { + b[i] = trit_negate(a[i]); + i = i + 1; + } + + const sim = cosine_similarity(&a, &b); + try std.testing.expect(sim == -1.0); +} + +test "vsa_cosine_orthogonal" { + // Verify cosine similarity is 0.0 for orthogonal vectors + var a = [_]Trit{TRIT_POS} ** 10; + var b = [_]Trit{TRIT_NEG} ** 10; + b[5] = TRIT_ZERO; // Only non-zero in b is at position 5 + + // b is mostly zero except one position, a has values at other positions + // Result should be close to 0 + const sim = cosine_similarity(&a, &b); + try std.testing.expect(@abs(sim) < 0.5); +} + +test "vsa_hamming_distance_identical" { + // Verify hamming distance is 0 for identical vectors + var v = [_]Trit{TRIT_POS} ** 10; + + const dist = hamming_distance(&v, &v); + try std.testing.expect(dist == 0); +} + +test "vsa_hamming_distance_opposite" { + // Verify hamming distance for all-different vectors + var a = [_]Trit{TRIT_POS} ** 10; + var b = [_]Trit{TRIT_NEG} ** 10; + + const dist = hamming_distance(&a, &b); + try std.testing.expect(dist == 10); +} + +test "vsa_hamming_similarity" { + // Verify hamming similarity is inverse of distance (normalized) + var a = [_]Trit{TRIT_POS} ** 10; + var b = [_]Trit{TRIT_NEG} ** 10; + + const dist = hamming_distance(&a, &b); + const sim = hamming_similarity(&a, &b); + + try std.testing.expectEqual(@as(gf16, 1.0) - @as(gf16, @floatCast(dist) / 10.0), sim); +} + +test "vsa_dot_similarity" { + // Verify dot similarity for known vectors + var a = [_]Trit{TRIT_POS} ** 3; + var b = [_]Trit{TRIT_POS} ** 3; + + const sim = dot_similarity(&a, &b); + try std.testing.expect(sim == 1.0); // All same = 1.0 +} + +test "vsa_vector_norm_all_zero" { + // Verify norm of zero vector is 0 + const v = [_]Trit{TRIT_ZERO} ** 10; + + const norm = vector_norm(&v); + try std.testing.expect(norm == 0.0); +} + +test "vsa_vector_norm_all_positive" { + // Verify norm of all-positive vector is sqrt(len) + var v = [_]Trit{TRIT_POS} ** 10; + + const norm = vector_norm(&v); + try std.testing.expect(norm == @as(gf16, @sqrt(10.0))); +} + +test "vsa_count_non_zero" { + // Verify count_non_zero returns correct count + var v = [_]Trit{TRIT_POS, TRIT_NEG, TRIT_ZERO, TRIT_POS, TRIT_POS}; + const expected : usize = 4; + + const count = count_non_zero(&v); + try std.testing.expect(count == expected); +} + +test "vsa_encode_sequence" { + // Verify sequence encoding produces non-zero result + const items = [_]usize{ 1, 2, 3 }; + + const encoded = encode_sequence(&items, 100); + + // Result should have some non-zero values + var non_zero : usize = 0; + var i : usize = 0; + while (i < encoded.len) { + if (encoded[i] != TRIT_ZERO) { + non_zero += 1; + } + i = i + 1; + } + try std.testing.expect(non_zero > 0); +} + +test "vsa_probe_sequence" { + // Verify probe_sequence detects item at position + const items = [_]usize{ 42, 123 }; + const encoded = encode_sequence(&items, 100); + + // Probe for item 42 at position 0 + const sim = probe_sequence(encoded, 42, 0, 100); + // Should have some similarity (exact match would be 1.0) + try std.testing.expect(sim > 0.0); +} + +test "vsa_bundle_n_single_vector" { + // Verify bundle_n with single vector returns that vector + const vectors = [_]Hypervector{ + [_]Trit{TRIT_POS} ** 10, + }; + + const result = bundle_n(&vectors, 10); + try std.testing.expectEqual(@as(usize, result.len), @as(usize, 10)); + try std.testing.expectEqual(result[0], TRIT_POS); +} + +test "vsa_bundle_n_consistency" { + // Verify bundle_n is consistent with iterated bundle3 + var v1 = [_]Trit{TRIT_POS} ** 5; + var v2 = [_]Trit{TRIT_NEG} ** 5; + var v3 = [_]Trit{TRIT_ZERO} ** 5; + var v4 = [_]Trit{TRIT_POS} ** 5; + + const vectors = [_]Hypervector{ v1, v2, v3, v4 }; + + // Using bundle_n + const result1 = bundle_n(&vectors, 5); + + // Using iterative bundle3 + var result2 = bundle3(v1, v2, v3); + result2 = bundle3(result2, result2, v4); + + try std.testing.expectEqual(@as(usize, result1.len), @as(usize, result2.len)); + + var i : usize = 0; + while (i < result1.len) { + try std.testing.expectEqual(result1[i], result2[i]); + i = i + 1; + } +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant vsa_bind_self_inverse_property { + // bind(a, bind(a, b)) ~= b for all a, b + const dims = [_]usize{ 10, 50 }; + inline for (dims) |dim| { + var a = random_vector(1, dim); + var b = random_vector(2, dim); + const bound = bind(a, b); + const unbound = unbind(bound, a); + // Check that unbound approximates b (should match exactly in this case) + @compileAssert(true); // In full test, would iterate and check + } +} + +invariant vsa_bind_commutative { + // bind(a, b) = bind(b, a) for all a, b + const v1 = random_vector(1, 100); + const v2 = random_vector(2, 100); + const result1 = bind(v1, v2); + const result2 = bind(v2, v1); + @compileAssert(true); // In full test, would verify element-wise equality +} + +invariant vsa_bundle2_commutative { + // bundle2(a, b) = bundle2(b, a) for all a, b + const v1 = random_vector(1, 100); + const v2 = random_vector(2, 100); + const result1 = bundle2(v1, v2); + const result2 = bundle2(v2, v1); + @compileAssert(true); // Would verify element-wise equality +} + +invariant vsa_bundle3_associative { + // bundle3 is associative: bundle3(a, b, c) = bundle3(bundle3(a, b), c) + // (approximately, due to thresholding) + const v1 = random_vector(1, 100); + const v2 = random_vector(2, 100); + const v3 = random_vector(3, 100); + const result1 = bundle3(v1, v2, v3); + const result2 = bundle3(bundle3(v1, v2, v2), v3); + @compileAssert(true); // Would verify approximate equality +} + +invariant vsa_permute_twice_returns_original { + // permute(permute(v, n), n) != v in general (it's a shift) + // But permute(v, len) returns to v + const v = random_vector(42, 100); + const shifted = permute(v, 100); + const shifted_twice = permute(shifted, 100); + @compileAssert(shifted_twice[0] == v[0]); // At least some elements should match +} + +invariant vsa_inverse_permute_reverses_permute { + // inverse_permute(permute(v, n), n) = v for all v, n + const v = random_vector(42, 100); + const shifted = permute(v, 5); + const restored = inverse_permute(shifted, 5); + @compileAssert(true); // Would verify element-wise equality +} + +invariant vsa_cosine_symmetric { + // cosine_similarity(a, b) = cosine_similarity(b, a) + const v1 = random_vector(1, 100); + const v2 = random_vector(2, 100); + const sim1 = cosine_similarity(v1, v2); + const sim2 = cosine_similarity(v2, v1); + @compileAssert(sim1 == sim2); +} + +invariant vsa_cosine_range { + // cosine_similarity result is in [-1, 1] + const v1 = random_vector(1, 100); + const v2 = random_vector(2, 100); + const sim = cosine_similarity(v1, v2); + @compileAssert(sim >= -1.0 and sim <= 1.0); +} + +invariant vsa_hamming_distance_symmetric { + // hamming_distance(a, b) = hamming_distance(b, a) + const v1 = random_vector(1, 100); + const v2 = random_vector(2, 100); + const dist1 = hamming_distance(v1, v2); + const dist2 = hamming_distance(v2, v1); + @compileAssert(dist1 == dist2); +} + +invariant vsa_hamming_distance_range { + // hamming_distance result is in [0, max(len(a), len(b))] + const v1 = random_vector(1, 100); + const v2 = random_vector(2, 100); + const dist = hamming_distance(v1, v2); + @compileAssert(dist <= @max(v1.len, v2.len)); +} + +invariant vsa_hamming_similarity_increases_with_distance { + // hamming_similarity decreases as hamming_distance increases + // (indirectly tested by: sim = 1 - dist/max) + const a = random_vector(1, 100); + var b = random_vector(2, 100); + const dist = hamming_distance(a, b); + const sim = hamming_similarity(a, b); + @compileAssert(@abs(sim - (1.0 - @as(gf16, @floatCast(dist)) / 100.0)) < 0.01); +} + +invariant vsa_vector_norm_non_negative { + // vector_norm is always >= 0 + const v = random_vector(1, 100); + const norm = vector_norm(v); + @compileAssert(norm >= 0.0); +} + +invariant vsa_vector_norm_max_value { + // vector_norm <= sqrt(dim) for trits in {-1, 0, 1} + const v = random_vector(1, 100); + const norm = vector_norm(v); + @compileAssert(norm <= @sqrt(@as(gf16, @floatCast(v.len)))); +} + +invariant vsa_count_non_zero_accurate { + // count_non_zero returns actual count of non-zero trits + const v = random_vector(1, 100); + const count = count_non_zero(v); + var expected : usize = 0; + for (v) |trit| { + if (trit != TRIT_ZERO) { + expected += 1; + } + } + @compileAssert(count == expected); +} + +invariant vsa_bind_preserves_sparsity { + // bind(a, b) preserves sparsity pattern (roughly) + // This is a weak invariant, just checking result exists + const v1 = random_vector(1, 100); + const v2 = random_vector(2, 100); + const result = bind(v1, v2); + @compileAssert(result.len == @min(v1.len, v2.len)); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "vsa_random_vector_latency" { + // Measure: cycles for random vector generation (dim=1024) + // Target: < 5000 cycles + @setEvalBranchQuota(10000); + var result : Hypervector = undefined; + for (0..1000) |_| { + result = random_vector(42, DEFAULT_DIM); + } + _ = result; +} + +bench "vsa_bind_latency" { + // Measure: cycles for bind operation (dim=1024) + // Target: < 5000 cycles + @setEvalBranchQuota(10000); + var a = random_vector(1, DEFAULT_DIM); + var b = random_vector(2, DEFAULT_DIM); + var result : Hypervector = undefined; + for (0..1000) |_| { + result = bind(a, b); + } + _ = result; +} + +bench "vsa_unbind_latency" { + // Measure: cycles for unbind operation (dim=1024) + // Target: < 5000 cycles + @setEvalBranchQuota(10000); + var a = random_vector(1, DEFAULT_DIM); + var b = random_vector(2, DEFAULT_DIM); + var result : Hypervector = undefined; + for (0..1000) |_| { + result = unbind(a, b); + } + _ = result; +} + +bench "vsa_bundle2_latency" { + // Measure: cycles for bundle2 operation (dim=1024) + // Target: < 5000 cycles + @setEvalBranchQuota(10000); + var a = random_vector(1, DEFAULT_DIM); + var b = random_vector(2, DEFAULT_DIM); + var result : Hypervector = undefined; + for (0..1000) |_| { + result = bundle2(a, b); + } + _ = result; +} + +bench "vsa_bundle3_latency" { + // Measure: cycles for bundle3 operation (dim=1024) + // Target: < 6000 cycles + @setEvalBranchQuota(10000); + var a = random_vector(1, DEFAULT_DIM); + var b = random_vector(2, DEFAULT_DIM); + var c = random_vector(3, DEFAULT_DIM); + var result : Hypervector = undefined; + for (0..1000) |_| { + result = bundle3(a, b, c); + } + _ = result; +} + +bench "vsa_permute_latency" { + // Measure: cycles for permute operation (dim=1024) + // Target: < 10000 cycles (memory move) + @setEvalBranchQuota(10000); + var v = random_vector(1, DEFAULT_DIM); + var result : Hypervector = undefined; + for (0..1000) |_| { + result = permute(v, 1); + } + _ = result; +} + +bench "vsa_cosine_similarity_latency" { + // Measure: cycles for cosine similarity (dim=1024) + // Target: < 10000 cycles (dot + norms + sqrt) + @setEvalBranchQuota(10000); + var a = random_vector(1, DEFAULT_DIM); + var b = random_vector(2, DEFAULT_DIM); + var result : gf16 = 0; + for (0..1000) |_| { + result = cosine_similarity(a, b); + } + _ = result; +} + +bench "vsa_hamming_distance_latency" { + // Measure: cycles for hamming distance (dim=1024) + // Target: < 5000 cycles (comparison only) + @setEvalBranchQuota(10000); + var a = random_vector(1, DEFAULT_DIM); + var b = random_vector(2, DEFAULT_DIM); + var result : usize = 0; + for (0..1000) |_| { + result = hamming_distance(a, b); + } + _ = result; +} + +bench "vsa_vector_norm_latency" { + // Measure: cycles for vector norm (dim=1024) + // Target: < 5000 cycles + @setEvalBranchQuota(10000); + var v = random_vector(1, DEFAULT_DIM); + var result : gf16 = 0; + for (0..1000) |_| { + result = vector_norm(v); + } + _ = result; +} + +bench "vsa_count_non_zero_latency" { + // Measure: cycles for count_non_zero (dim=1024) + // Target: < 5000 cycles + @setEvalBranchQuota(10000); + var v = random_vector(1, DEFAULT_DIM); + var result : usize = 0; + for (0..1000) |_| { + result = count_non_zero(v); + } + _ = result; +} + +bench "vsa_bundle_n_latency" { + // Measure: cycles for bundle_n (4 vectors, dim=1024) + // Target: < 20000 cycles + @setEvalBranchQuota(10000); + const vectors = [_]Hypervector{ + random_vector(1, DEFAULT_DIM), + random_vector(2, DEFAULT_DIM), + random_vector(3, DEFAULT_DIM), + random_vector(4, DEFAULT_DIM), + }; + var result : Hypervector = undefined; + for (0..100) |_| { + result = bundle_n(&vectors, DEFAULT_DIM); + } + _ = result; +} + +bench "vsa_encode_sequence_latency" { + // Measure: cycles for encode_sequence (3 items, dim=1024) + // Target: < 50000 cycles + @setEvalBranchQuota(10000); + const items = [_]usize{ 42, 123, 456 }; + var result : Hypervector = undefined; + for (0..100) |_| { + result = encode_sequence(&items, DEFAULT_DIM); + } + _ = result; +} + +bench "vsa_dot_similarity_latency" { + // Measure: cycles for dot similarity (dim=1024) + // Target: < 5000 cycles + @setEvalBranchQuota(10000); + var a = random_vector(1, DEFAULT_DIM); + var b = random_vector(2, DEFAULT_DIM); + var result : gf16 = 0; + for (0..1000) |_| { + result = dot_similarity(a, b); + } + _ = result; +} diff --git a/apps/website/public/t27/t27_compiler.wasm b/apps/website/public/t27/t27_compiler.wasm new file mode 100755 index 0000000000000000000000000000000000000000..dfba7d374db56cc1fdac43ae614b3b7c27065996 GIT binary patch literal 488709 zcmdSC3zS_~dFOdv_fd6km5yxLD3Q{fx+pWH6}zKMyCnylL0iku7y_L*X=aAim}xWP zRWfNmG;XKISY=zb1qe{U1QDD>Ap#4K6DKG{1P96>gGfveAV35Ygb^Tv0|E>PFnE6d zZ|`%?J-4bPIZ3ZxV?o_>_St9eZ-4La+m92y^oEzmQ5400ko^3W>E_Lwh0O zMGp zsxapDv2HhRk9V{2WGvxripEU73BU7(BOY^)RWAcKZ!eW z?~Jal&g8Py2G9K0i91>1QOC%WZZUk|Yw^`R{E0{|vk0&+_u3 z`CHbt3Thx8i}gSK$9Na`k5VC_twD5`ECF#p^RqyZbdsps>IiwXIUaYTW=H;R4Ezg3 z`!7qnjG{Hsiqa@e#}dZP_}~-3=q6x4N?I$HkHu}(9%o#T39SFj%8BIXKwoz$7z2U- z=9xKIAIpkj3dRs`0S&dY?u00oWfOqJ%Skqfp7qRCpz+#x2me}W9G%hrnRIjPW@qQ3 z-sZ?1*!7V89@KB=`R(@0U-t6Y|_Ui*^&^QAYuEKSD#lp(pe z_ojG!u=E8xT5o7Q|JVK@{dD^B54UzFhm*fbzmYwW{UCcVeK4$ z^i%1f^h?=y(?_zer$^E+rvH?FIsIz-jr4Qrhtq@U_tKB2N7K)zhtnt0$J0mBucco~ zzmR?>{Yd)5^pojh>HE{Kr2m*cnjTC4F8yZq{p?5Sm$SQD+gfjK?P`6l_3_q6TXU_y z&fc3HX#G`oNA^(bYuN+oSF>-Yug(50dtG)Y+n#+a``zqw+3T~z*&DLYWOrwOn|(HW zWA^vi?_~$GH)S8qc4QyV-kg0R`~B?G*~8gevVX{qr$@7|WOL~sXP?S`klvGhKKq00 zliByvW7%Wb*48UquWG%zbyw^6TW@LI)B3~KM_PBZwzNLfda(7u*8bL^)~8#aX??bJ zxHZ1*{mYNE{&Spee%37iMK0esNLJZpvOZa#t}hbTohjn{Ry{3}>7+YIFPeyT)gNRR zO+>vSD$*j$Z&QVS%q^6joxN#o!lT6HGrqOnTjRQuqR~kapBw!=ExBkaRs->Ll1-(& zyG$LwZ42!tz%iXHbMYk;k)E%d$_j&F<=SMWx}TY{+vP>JaVk|K0ql3`Z39*<0{xUA zEYh_}#!n28)GSIEuc|==52=T|NQHsK#V*bd7#32sqxZS!4_dyhjJ6WMWFSRLz&$x0 z8FG@fNtZS%M4Se)0eaS;{^uh5OX!b?-j`llHFe<>sF_aIPhnS#&=(GT!JvKd1SPGa zO_Qp!rfAPhrJ@yxHC%gIRcL%Lsfl$wsu-$B7>41`dS@WfAQnv$w}uhg$Ofs~{+fqi z_9Va4W7@3}h1blK2mo&0T;<{B>KuSd-15-CbiTEIEr$7ol#Tf-L!pVv+T;S>2$$yp zRYD(Ylk@FrIag~a>Cd>gzh-XM1DC(rzfZsFd^+D&-Y?hvN`LQpyDvymFLT0CvO4Ln zgO?-Es*|Vcb zer`>k6ZieYZV_#W)}_&so{Ohia6y~@cew5*H0a`sk?DA9jQ@}GpsJL@7F1{#y_rG3 z$X+lJahDcjGv`K;dn&$eFveY9cZ2?A_HeA|`?l0cuNa?d7vnCTnHuk>FkeB-GrTlb zv@Ra>S5RS$!v2b4Ts0Ev)03)U?j0YRjq*1_CdsPm(N0FE0(Kbiu14ko z2iQ&x`eJ}zJ`q!~0$lG4@Xo@3cNYeHVG#XLIvb~~(I(hEp|<>2WFWbt|6e}ntr$!& zE+nj5a6Mj3%nW$-GE{l5$P8MeLJHQ3iTsf}KnY^AA)1;jQb5n9#?$p1sQgP&bZ0TS zg<_#ygL0cetqeg{Ol+jl30j=Ee1)F!Y-+5SnCkP}6Vfv(8;gilgycTIunUH!59-1H z5^)4VRrEGO$IUkc9_b3{MR#T+3WEwAKgN<#jBWbdwTx|A8fhPPpdqL@W4)-ib}=^9 z6-z7;68lAWqw%ajEBUC;i|vaCE%6b+6|EI;=2X828pp9dJ!@tN_$9-@=L-YhGT@h# zz_(8h{N*PBeu;q3J@8BZ8Nh?rC2#=$zkJ2UslLc7U<&BRm?WlSh6|V<`WVGis8SOh z8By;&Lj_#X3o3dswNdoeCeuY!jIBkjnMx_7OJugAVtj3~oFDAUGJYo3COJQo>rxbp zlWj(^Pz=)?GdX>)6e4^E43r&ilu1MK zhBxH}@vJ}93Z7UlOEXq<@;!Q8Rw0)UX4ZHaohCAtSPKb0CT1lj#$1=>Gx@IajjoHY z!+}fI7cH(#Lvt~eQ9yhBLEFv7h>uGc64$w|Q)kz+*<~xHGVC_(r!xaNQ7{YlvJHK7 zF;7~EEbVgd+s7Qs7ns!3VHCsx1*0!9Iw8vKoqY(+{QjVs%_PAU=C}cESB-Vy5IK64 z`}W>Aj!WVV`)@L2Ky2=w$mkwF`rvHj3cyYBKNeHT%D5vxn4@&fa4Eg0j`wYmywus{ z+C;jB@rzO4{V1>Tq&wp+xYXR~)Kjg>o0)q~GPXInC2-zsHtcNGq;4y33Hi^>?+0c|rjxCyxCO^yI@y*ETDr`j(sT3g z84h^h4)7Lb!Iw!(2WF1Z`loA?2T}%o;5PcBTTP!miXM=G&^mJ%2w`0racvtyb3cFHOW!@TuGFd)o<; z9D{LB8Z39*PP+qX2C0W`i8xUKIY?aOwyCk)*F&7~T8HZa@+_+NkZ2jMANG``g|*2+ ze@)+OMa#&&{f;@KGDHmCDFcH3{GAI_*7a^H*)vRc8@DM*&r{UenKF*Z?KvF?|5^MN zoAYBheZX*<2ArZQ!1D(dDEpw5bxMLsay`YQ-bbkrxrc{fUz`fDdt|8I2o*}{-D5br zKXs2A2;&u~6=hDFO1FJ!gi2lmEkIx+>fik1NOc?ZDXHqQiwFvr-#4JM5^p7=fjgxOcN%H}_Fy!X9sMT2m*_I)JCe+}_kCZb1djx@v(lYj z;RyO8iKSn3c@!n)X?~b@-t!3|zVVJK9j3lHd6{XL>EsdK5{L&)i?;GNOQEUep`a5F z;sVI2$DLR)fa(S2Vg-tJI{$*#Z-}$Br@T2+9T;Sx8}iHhcsNbh^YDNau;I3zQENVpR-gO-Ae)XgzCGhPRHQ=Pb7M!M}K;QIsM za|v!#>h@`1Qs%0<*Q%Dv{C)v7o$NDZ{ytIA{QF~}cMGxlEDPVKk zeP8=Ac`kGPGXEru9~Gw4)#>(RUGfN5?h&{;%fIE3Tqrf?jpI@L1F&hnpc;jBaD`m| z8ggcyLS{W~R>DB$A_`_DmhWhk0yn;Wm6`kSRuMg+*{xCL9ugwTm$0`8U@$2?F(w| zw6*2?E#cxh(q~)%?o`^(qcyl$)#$y_VXU3B8Y*5DoGXm4& z|J}JzP?<;Td&AWl;ibSkvc6scxKY(tkOtE(<{TIym{N^naMP1amy3w#!JwRZBnEc@k`o^kPX6+S1}#k zlD;H4L63*kFLD+W9!T@;H_aD3UU$$xJ#32fk=Uyv-jcZS)G*#>IQt>A7gAXk9f}{bA&a*Ql^tl0(UyOc1mMBrgvpX z^!f>V{n6mk9r3SUt;*Mwuj}Tj0r|{*M(F9}O3k$HWv1<-31E?zDdM8`G7B~mZ*`@u z_NZM(>LUuA)BHY_-~(swi^Lct^)H$@!^|aakOn3d%6|-glel zy_4z6Gt5}aW@EZF)ybr_g>62(nNDWoDGuCLk%@Cu385gMPn*z zDiftj-QGr99TmN&@gj~z>zDmm>o-GkJTvlRixU59kaM|wLWg1KM99bVFeTVCn1RgFcDLhfzG$WU} z@f**LzVdFO(Lu{Cd%;9z+|qK{CZ4r#bW5(g{@m#BWBcv-FYDcwy9DRYopt#HfxnA4 z4aU!nj(jClcmbBN<z``>jMhK^=IjN9=ghEvU{x7^uo_ejYvyM2ukz<|Qyn<{h`jV~=@<~g34 zd3@e*$h>|qX8L71xsaJ;CNDil;Vh>cQ1_Njkh@yhOLT`>3{5jv*0O8X^8ALeROd|P z)mlLX7r=33t6D5qY-J}im#LeUm82K9RnBT&nmHL68k*LhbMhZzSIn26OEm!wg()V^ zA9IO#XAIUDj6+3M8UZ2(O96cWBBn{c@Fxp~vgK}4b|)iA!;BqmtkNX4E*N|_KZ+@f z`e^)A%U8zgz#WVcpb0ro8nIWW%hx4O2wzVC=U9l{@zj_+VHQGtaCrik(Naf=;f)DN zrZ36-7*H-#o~Sh7W#o;0W$9MnFq@DxwyTp$3~VH+G>&K4of&BU0KjGH1!LJDFr0oU z|HNzOgl5IJi+P29$Ym(sz+1Ozvx&lHu+NBF*QX>|QZ$Yw7p->~twS!djOo^B?B@kq zTUK*i)*WJt+i?b~h*I~&k~S0PLr8>AwT1&i=FE)5m_9uj3J78BY&akc4$M3`KAjx7 z<>$%$38bOf!>)2h#<5Hid&@qY@Ew>betPB|rVdGq(!SG(*>_qt`;MJA9bWb{WZS*O zWR)S1ihkjY8lgy)QSY;fb!>)i7HI_SFACZ_>p*Wu^$ z+r7?pHu=lh>PVZ%Lwpnwq%3^CKPzxX-iRJ`St@e-uao)^;g$H8GH8UW?uNje^ z<~7Z}gBm?FrOCv=Yt_{fdDvX|^|Swx^$enKLd%dp4oh1Dg0v1-s>IJB&}DPUDWmZ# zCk2F-=z**-;IGH^^3XN)(n-xJ`}Hgl^s!-?5oBN)nJ8OM1==tqipyvp6mA#PHWsEp zw8lsC0@U%NYWvapvGE88O%u@PkP}Fp)&@=HZVG~K_?fL6o{DbYaO-Vbwr<}!HyhsqyHh2al4MFE;gOluNF4g+jzP9z z^BuhO)K7l=gKvHD15e#@^X(gE%U3twE~{>Y&0Yu~m_Xo%8~)q9F1?XfaMU)+&f*T- zWD_%E&L(K`zw|yXoCvFQi|&T_&PKa0)N-I0;lh4))C{vNNIbyYTx9ereT%5|mN0ri zXI}ADqDk-3A(p5YJ_#W#?ltsiiI7+CLWyVc7MjKu0?FC^X=H4!rD5E8HkYB$8XeZi z;wjrm+7&lI;uZ^A^{2?+ATU4zUW%&oOV{GEzzP(NvI0d-R>%q#a>vDDf65cE8Cy3)3*NJZNc6KC*hx ztmrRXEWg*-9C8i3U!9wqg91!T5$^>%GXH7J?O-^!<4L9@z>3 z>r-eLF_~;os8z5w#(pt=FLKIAJC{wQ63)7HDl{4b!!#9vCxao;%87XmYC+dHQ@iDC z5FY9nVL&NedHxN-B*R2vR-mS+%36%f7hi)Ix%QMg5Tei@E;mc>x>_=TbYocbn>;&W zc(8D(Z2z~$0=%%1#P<`nkeJM9QIcPkj{>q~hn3%hxJx|eZ6En`T$`+anSRO^(oY~_ zzJ6l=Ned2>nk2B*Z%|!9C3WYkB#N3Use5vj^qIKfAeI<=I%?Ons?0Mz1LC0L4!wi0 z(~bG~Kz2@^{1>dST^AblTW)0-Ut!}j{b!5Dj4bZU_Ex>nM^vXZkB9lHw)C@lXQH=7 zS~e+IPRYeL^maCP0Hvkw)w)c1c(xmd_%bZkEE4$enBolP5UnLO~Y) zDxOwC!;~K&^ByqExXq~%vLE0QL%R)>`DtwkumSNs||smb_~=mDJJtj z^c|YBBsluO&rf$sd2%gj$2XL_3wzYs4~MMd9jt9}Y@2FeF?m_$NOtodOY<`FP>Sa% zkVZB12!Q!O>f41mTgI3=BbEhIxERF$8XZ2wrMg?ESpShrP7N zvQXHR=ftaM2iYrV0Et`K``IX2UjVOl)ez?1%NbtqqU!yE9?|pFyivk7_R04E>HV4v zw$D$z4xNz1AebJnl`c6bY=-WJqLoC}YkqgqcSILj%1(R4Bho8HvOXDJ;GLGsz5yD& zg0R;HC@I%?`zWOdQOdHJu+lrJq7*ZMtCM9_(5c3}7G??tbn%ikvU)`&Sw2+SB~|7j(kofWJp4&aggT!_PsgwX ze_r+yA5Pq_xB+q^LWw4h!8lO3L$}S%0;`YZ=HsEF>~GhEozF*aQ%9nk z399#cl&B9uu!PbA6B3XN8zO?AjVEQK&&h|d7FE|?8zf#Y`;DgzhGlGGLwZfmA(Bfi zuL6T4?iP!BdBU>{|89`Yk*HsPDg2hXXDZHuiB<6x3sh{E6_*TEyx9^2;7#6Q=Q`Kk zglc>4b=PYUvF=!#KXjMcoh|(t6_?)L)9@oa4O<$3aUI?zi*W%NJwg;0)R}fO=JyKs z!h420!S(D-(&=pw-Z64>tFX@VgjHk@Pq(N~(f@6nI8X;< zM`~;8VJ!a^WWa85H_l9sMX@%gXIX0QCsfQesAa(&G2fK?%tjgt6@N%|8n0}^T}ZTB zMS4N*cxMyht?ii*ogJAx47)UKNS8p$$COI8?Uh6S!c-~;*-w3MMuHY z$nv~Ou`HwKTWKa}9rG4NsWp%eBkDf##kpAxw%mZDuA!w8@7wemk5&x<1~+VS5_@pP z_45YC880Nnzwc`_1j5Q$P%+4pU!7m}@OS2yeQdbQQqKa&SdW%LojbAb!PzK3EQq{( z(LX?vZ}LbI#cDZGaA!#C4?L#CC*Vzus=e54eeWXeY2gD%Z?*@wckT@{1Xa`PjP;?j zHX{cVYtvY`1o7x0m{)kNN~QTd^0m{<3k;&~_I?ShaKAVinme1NABM&&mF58tTH;6* zRo=6Ra-*w|cZH7HkkBiXjKVLQMHPPk>ngnIa~oxelXq|Qi~dk2K{ z)<)LUs5wues1*LKO#R(iQ%m- zAx%%Reu{eV$aTG6!{QaV1>LDwKfItH+^$q0AdH3)Wu2+t4|&}f(`|WOaG5E=A?##s z(@gKDLs59`ifK4_G3Ja1hfJKEhm#`2vZfg;|C^xGSYLno-t*FB5ppeDlKb(F9L!0M z7^T*N3(v7$ND}W6%}j8#XMkaZ6+T*M?^pN`0N@|d(#qk9G2NH)W+u#X88Te-lktO) zoy>8&wBfMCs{mtGWMhO;*kWm{NSRns;!_$s&?D~!QpVS<==~p-*%w`Hvn4#4h$G$+ zKi_x`y-1EMlUQU>QaacOXs)=Vu0h`gAb zA;j#@Y_wGfYuMhrRNj7GEZyk0$Qy7X6Ry+q)ya7txHVJ3-1=o8J@(2N?B~-E4fNSC zuie-!#wnDU5d(*$`-_fOtU`UHZL?x{+&wY8d1~`?FDq_V8E-45MMs()RWv-;3@cJ! zOXBVK#L75392H1miBKsU(?9w5#JG^KEDquCXz{dapHtQ5#^T?Wip%&HeS}nmokGfq z@U)Q9GB&Z8NjVY5Xyd2LN(i&wT|+8tDCj^q>WL0d_Sj3bIKpT>6oS~gJ+T8_$7Uy# zuaKIfoebKlIhf7%DZ_rkbnvfBPkoSQuzn>yx7IkP|ai?L%DVf>&sr676x7@AZNY567-@ z2#Qwke@2mlc{G1@hzpAKIq{_w!-^N85>XNBd+TG)H$k&PDh)w8v!3&VW9~I|YD1<) zjRX=>{!W!ExV}siy*vP~9xsVug*vt6jM3s1Mu?j6L{h3}=SF^J6bEnfOz+aC+tRYC zA-XsE`i5R!C)tbPDJ)B5g!Q7v^Zcpu5fMD08S-W8BBbA?%G^`5I%=(p7@65<=(;+J z)~RZp6a9Bl0uvP8{OAH-NbD@zX3>+78mn>iT+FXJNd z5E0N`Fm)J z-s{&*DP?sJV&z2|`tn^1HY9yUdrzaGd$yjsp_2?~q1WHYnQf&(A4)j~U12u-eL^CH zLIW%>mAwc|n8&g%u0&v)YA=^XAj`g@-dUMH5E?*n6{bx@YK*$MrSL-^ZB|$dvgwv| z@49oVG%R!emWKTsHKP_e9IS?e`(;p&*(O0|)<1DHT0sUJn!@!y zo8L}q2EizD-Vi-#y&R2?dQ3?~RXRcjP?~rZ*qe=dXGK6K%`H?%`Ig&N*qe>Xy<(jb z{-07ohB`1!-#CY57r)KHWBySd9<9`)W%fvs6)BW*_Fv)>)@FHuk+GCNC7mHxh+vyx z2Nr`6z4r~dP<0GtfJiuCULOYGr=n8jXd)Au8^Vtek|8Y;2&>8y=T99~X)Z)|lRy3Z zLC-_-b#oyA5~R~opmtTP)5;f}-i0;T5Oau^5)Q49>8T#*bT46bO^H;$5Q>uV{8j>h zsCOY=Ok#N6G>9>=7M7T+CL}Y4DF2`!VX{pQhUkN1rU`IWSKIMSZ!brPmI_X1xn-V& zrmQ8SA=Y59k^p|>tnibS^ZG!*Exn5mNs5ch?dLpxSWj^GSLS9TJOv-KkllJt{34zp zApAW~f6$^FDFm1AH)!oy{!qBq3MFfyRkfgv2AIJ7uY8qrqZc`jutTy;>Y#+RhFw~n z3W=c{+WB%Oi9>mjtxjLG4n)}l{?_0CUImUoE5RWQQ?*JOFi_d*1W{Qjxj2pC>kPN2 zIsV-?{;aCXvRXT}(((P3YpX6@C_dFsZwZ#U$s^ zx#(95{#?3It8FHeDArydbNZ(FVMbvUKZL6VKPW#kIo=f78CU9{69Usr>e^N(ceS#H z@PSF|WD}-uM`{aKXjW>#tYT?Hd>3nYg1R`zccFeUk?PmnfhAIyOM>|i_D(2rJ+0Bt z9NYT9LZ&T+Y|qPV@O|fxpVJLs_1*u=85cQv2MNxwSoQ`GNve#Hno^ox1js$baT zhD836y7wz~Oi8QUl()+Ldc&&ROoYm- zrHKgZ>f`~e=gt^LMuIifA$~wxYfxoBgKBj0d_pmVSy~R-Jy{jAxb_T#ALJBE)+I0I zhm-M8XTr=*c5`1v19Re>&`~}!b!NCk{wDD`CQ@-8F*N5+NOtAq40^UQR7SI%ztdQ1 zLJ6`Rtl||#c}x#N3dQ-mJf5U2DHEp5ykJ`eA*Qi3J9<0hzMt_AZ;h-F2sGDsY& z#dl2NE0L4bkJn;b7n!(v+_IKL;_7=li?b#J|HF$6Zg94tN~2cp(*#n&pr`P~GZ4Wc zfo<7y__;6n<9V8%`^F=oH8$; zjMw0UrBso z9c*F|eIb<2LMr+`*0F#cI}rGcV>3+;RD!rP6jTy;Mkp8(J@Kxjy%s^P@rk{jP{he< z9*`n0+V+K>i3#5mrrKWx6SI6Q0!M*ZrVLQ+$KXXB|I&OX4pXoYhCa{Hd%W9q*nM6ogWtK zKvpom5-3b{fMskH^9@2bw2Dxj{78`udj6r-OI4y~`H)Zrfg7vdujS4bd8!I&18;+2 zs95kx3$mGz=1meH#|!ZMwFxYqF)`AmnNINn`4==_J7~V;691szRn05D5^Pg^LtvWH zQ~KHui%`FB^*FF!2d+hG4OO>e0!^zfCWF!VdYVJ#8 ziCor@7}VrcdzHamx*)^9g7+48!^~ulPhTWsS<0W7^?I3Gbq&Uk(7ubpQ__|t-Yl0) z<#;l`qyt1b`dKFvuR`&%(yv_H*z>rDR73M4)F$WgVtKW+wF!T&t&3|lp|p(a9^z_= z&gEt%+8FlyFEv;ldQ87dFd4b}m)XEsTE;vN%Ng}AoY!Tp6VoU_ijf-^PBFPk!C)mc zH^qut&B3F5)^S`co;fxgP0E1UTDstS!@rFo;<(ybqd2#%wIXr;QNg0Z^R$?e^HcH5 ztP94EC7`?WQRdo|WoZ?wh)1aNX^BlZ2pUy6=y19a^%?#+L+Oxd>S~215R6}h0W|>S zM&i`4j#CUs01jRM)_1N(@&Z}I8!G@(3pzp5-{~ok_Vb5zrDTegS9fgH=0)RUwSJds ztxcZCrJb;@M1H$mjc@@_@DQZAExw)*r}i`#s4Fei17%$0T!BxJ=eFNDC-i`;u(e43 zDM&r2He{N>3`he8c&dwrV&vm&wt;O7)=#LOPe>{A#ZujIV-KBm#bo z`kupYgVs^Pm5=W(rT|?Iw}Ls`&k4W5Pr5jjY@>01-kmf%JI( zNyC_gLHIwRkQFw??g-x%IYs0^M-e$(LgbR(qAv7?jFf3rmM5~(wVudbuEY(?X56PN z={`hc*n5?DygWr;M28^*(U%l?{;3kmAoLP>&7VrxO_wmF_%t!pcGZMw@2Xm(GQAk$ zadZ~5gNE$q#XmcQR*)9^+2>tlBhmeYDUqy?P-IDb@Cp;V|d>vriHAgmt zb(Fh}H`Z}G1JlA?mX*zZ4f*oR%DhfSkCa;3Dycb^i)~-5p2^!yQyuG{JimGuNnvSS z#%|Y7_5*8qh8JYx;ZC(SQubCWtC!EI-k&T`wrhcR-@ZWEZY!$~&r*BuYSsg0l{Oac zp^4mU#4oU&5a#aJJNYAG5;(Knybnlihv!HnZQ5&(OL2u_vD(l*9NrKJJI5W@(2$7M zS;pkoPoH|~sm_e!a|UF%R!}f5dS>TF_q<9Aw>qLXRl)VCs@f*sj&fuSmA^{S08o$* zTl6Y`!@aGH)v69^nC-IB_{+YIx}g!cOg-EAJjPDkA*&qsW2mw_r*pkb1!-2r zGp-a85ka82?u<6FCw>!I8^0i^&0~ose~8lPg45-O39M3poL?3Js^=X6#`)EM_+ZHv z;xzx-zPb?BuzLIJqDAV3cZV9}U6?54|C!nljiQ=6B4Z?`Uc^XD>v%@8+@~dY;<_rl z2+xgHW$8F`GB6}egovm1n_4E5g@B9=oXy^JgImg_f4_g3dw^A*?x|;9ccZwks!(>& zyWD+5h%}`V*5&@}*~-iNO+1+_%34wH!JED~oYms&^y%1Am_xDurYI7Or$gsPsK+2_9Lf zrKO?L53G`(#hce1DJg^LM<=VK#S{YkCnu{k87j>l*XS03iF4zurQ591A~0dftN?T7>Nrp~OJ54px*hkSLMV;`Ky#+LLpPz@)LSQy515Z zDAVE79OS4mbd;9XIjp=o*hUYb^hq;Isv=7S9ui^J`}=88!!<3Ax%8W-W_!txp{(&oM4O5WrP1 z-my?#pY3Y4q$siZHfx)Q-VS&_?YGIkoc7y_wzczu5Ug+PU|#~mflrlr>DW9aR8<}$ za*up<4n1eo5EIzx9RA7U^%G?_iH!#H5@iUM$SNA}8+OK^j5=@z2_-Gi>YW=c_Zx{> zc5GelsT*c4)i?~My$K#|D}74Lz5z1L=IfF*o+S`t<$YeHh+l_{u1&6>D%q`X+z#-b zPki=9>zf-Xqg(x}S5gVlEnCscYneJLQwM5}y-+{iGSVEanKW2lz;a^x6ZQ>>K}lHq zF+EU^sicz+vp_U?HVL4mKUAAzaZgg)8dcpYJ*JZ33>lA_8M3}<(xG%q$GT%gjqHj? zCMu-kb1)jfT^=b{*Ox+7z?u-Q&et-hSfa`!?Sh&H_ue&%G*CpIhLveyMTC$@dMzYZ zY-kpFcm#&fBk`&RMksvX!4&e0xOXMsntCy*G^PTlJlNE%cdbPz_bi6lhH^m-*%?$# zed_fMEoW-J8#GPZj;2*y1BYAOB-O*`6hV|U_A8`fI$qvQK@VvfwS)RFl{-aXqYVWF z7s#PyLB>%g@hR2GkUm!ELW?%iZSzN3Y9%2Sg}VycqU~Ol)Qh@VP?q}QA}qDb+{*H^ z5lRzbiP3J!WhCY7HoxPM7UaeV zh2GSEr$zhEGPFB9h#J)ZgXIVtWi(--p!H798+XE{v;Fud1Mz9WZ& z8vXKfT;ij}|3ROuI{5s}VgodS%8q;!X)O~=3|VEPCkUzRA*)v=S6FjPS@Q%T&Hs#^ zAcXRzta;oK)9aKq4?E(#Q_7l09cfk0=qX36IHIh18j+T=jGji+Gb5t%=4nKJ%#G8C zLT+XoII&Ctv69|vo@R?GwNgCaYPYAg<ASIGu5V;l#eW9m%oa6ct9J`(rog z?iQDg;U^X~F?0szGaJcCa|Sh;(QGx9?M8uskZw06jqRo+vE7suwi``~R`19ZS2vp9 zs%}c^+B+q2?WUxy-IP3SHzl39QTjdqf{-n(=cISlbODZ`>--9@J5_~}59$_ZN@kNk z!E71~rG(gxj+j!ny%&mMELyd+z0Ef-7Wbhu%~enfw&ZSTy?4o}y?2yrwrN+~H5knS z4GgmR<*=cPYekA07>OV~>b@w{*%&lQBV?YWr1Tz!5Muh*QeSKrTJQ^$a(mffh*)85 z{!HC5TdC=W5 zd?F$z7sfmvzlA^Dtvub3bKWAV>-aNo-@wo8?4x6PRu&yq(Sd`VTW;p?0EHAE{Om8@ zax+kdqmVnCrb4i-!@Ct-3pkcW*)%==8Hsq7#Ib*N&{rDNacnG+vwH8}rvtWWNR?@f z;p?zc@IfQO!j+mDko-W?_(9gdf-ul8@-{Z}WmBOrK1FpbBx*l2+@M+`mVA3N_(~dpZ49jHrn=J7M zF2J3Zy@ETo-`UxAoK4PX3plp7agV;^$~rw;d>9L}w61-6o=V(^O0KxxeMMhw0GYPN z!rGe01>1Tx*hyP-IGA9tKsy106!|NtG49U2 zcm+Og_hwcXOk6Zo=3U&fU$b%PQ%<1k7>mkPS2IkEb4p_zme*03LW3;qxP&YT`LX~dGyG~MVPIrO$d7#JgXBlbC2Z=3 zz1iBi#B$U2{Mbc6(B1IA-@M^MatW5cK>Ossb$yXtKam1Tcf;AgsVeuPEZToP zbp{2|Zy4jHn|>kP{geH(QE|pwr_PX=u;LcR=yKHSCU^V|ADXo#tsA2Ji}((N39D1y z{dFBB$*Qk`n9csne~Y5AzrOJGpMHM(A7ai=ra_r6}3JvM{~Yz8!4xPV(y$ zQL{FX|NU+6r92DeCul{s`;6Z&&S3r>HveUgt6>65D{#+ne&C!oy=mhwq|u)&Ru_NU zf+jMZ`CEkr6J?8)mZPON|0+jbit{f;9KlM?o?(0Cev;O_vmw6Ozh)2h=l=Gf4e)3e zVybJnyIsApz_#lNT=B_h1mM4_}aamR!(ORViwB&-YOUovmZ3Us9J*zu(W zqHy?R(SezyZM~tYdhuV~KGXK@)4sr+=%dw4E-*~i+`!K5;?cMsjcetDK3dJp@cm@r zSS(pts<@bB?Hg)<&;}5CX1(;HswwzU`UxNnDAT}d4G5)ERm`Gg^z1l|bWj8|;TOi~ zeI}KGjXtD!T+we-a6 zNX-0fTl1K*`6wF#sGe+SyWxAZhkcKB!}l9Hme7K}RvjbZAD-uBSAqy*D zOb;)kUl?NvO3HxKTGFYrKn)7oFae4$bC* z(avzSpZ`!!dic{D7-jv8GF19Lfr@g z%d?0T@OA7p?6LtNBNwL;8qzpUOMDrweO6Q*Gwd1PpP-NnF)n7txZWz_gMI`r<;O}> zou9FfHhK(mVrr5>VQ%Evo*1SPP)|y?OSHYGLD1l#Qr|rrkp#{Uk?MkN(4jhEW#u#@ z1&9E9D6~i)vFHeaLHG+*4h{!SlX;pf2e}4`U^kRDUEoe!I+$1iz9tbAF392=z9G8@ z0$#BeHHQ91DkW02vP{B@CODx2m^cH|-E(J(GTN0Icz9flqR~s)p#!%C9k@zaH7UYv zZCjU*7bM+4qb#4Z*L4IoNt;)2%AI>^2~RTjm&vaN48cR*#n40m?{(7k?gH^5wa%1R zo1GgSka^W=?bOj*Ki}!KyLwQ|v)B65Owdl+&JthB7a=_N_4wtbbI=-|xz#Lf%0c7B z?DC1!B~Csd$xQQ})wc%8&=>9gkDA>-wS?|%V6w~NsS*tDM46+Kxm#~y$mx0;gS+h} zbxF;KR;c0CXKC*cS;& z{mBo899=moU{;p^AUA)i%hOIVAq00}n9j^EMG=mQbgZ~$zywfYOHNr?1YUa#P3`$N zU@*829Lg9PpNpT13AmoOv<00QTy{Vlt#4gYlOyP@_ip8= zT%lsoo7mRqZ8!NJ3-|cHP#ta*W`YL~7ZcX`|2ntBf(BT=y7#fT-daq?HulA!R5*?_BQ~y~Yw}UKg>Wj(4m>We z1(fj^@SMyn^1eNMK-MC5UW<^!hrc#A%OVUP#T!OqmSpJ3K|Kkd|4~BAk|LHjaJdia z6@2Fx`BS{C2Cs#MQQ?WQJ7p@4tiZF#F93W7@OVNpdFm={oKXC-dc-WVX@!h}9Yq*Z zJvbIe-&3!{3rbyTiBz7#1!iuq!xkWJ{>Ou zikE@{WCP8kPb5i&tYP|#^8}xJmz?b2Qj^-&L^B=eln}wMwWpUF4`{cJwrQ*(;qctM z&0JQ-;c8#awTABBiWxflu4Crc!_a|B)=l#qBd8~; zlJY2r9!6LWy?@`$e&~i+6pdO%Nd(kd(%X81YtULcA2XWL@vEw@A{{zP$E;xWEXfLd zULMcCBC+P%ElHRoLI;2bmYKvTQ(jJK!x!W#VD3nbrjGQYY&3QTD(YARHM4R_5W~nFF@5nX`PT#t#HA@sTk9}I(zQD5vY2u2+CFCwS=Ol( zFyL!7%BlgaYf*c6m$_7cQCe6P07kmp7953@BQTdHDC3lIChH5G62(yJ4|RwFmSz<( ztbaL3B3M}YA>~8HH9yt__8e*{1rFS+cu%kRbNUjpr~aTAChLCs89#`k90agy2F%97 zF>@Nr$}kqKxbOp^Ff6^XdnizjZL^Ina!=g$&@AK%Z$|mi0Nl{5q-bi2!WLlIY~KIf zP>X4x2gEhS&+f>8My#=>N%`sLU$-(Y;o}O=U@APQc&bqJ8hCAYx9&(goa&yW19&$W z7jO2_WjZr2DX0BPw+*3{ROkh_>zm-Q{Mag7pj0uWhu0HPRaKf(auAua=9HW-Yfj1e zvgU_JeAx%or7J)B5rtlw6N$cNb0X1~H762%S@TmQzU=QudkOtCCj@=n#^*`Q%xW51 z0}XR_@^ZraqmzWf#{5Y_y@nU z2Kr#6l@5OjMYZ?}$`91LHa3oo38&7WEN68+lnTQ%{T*IDQ8FpZaIRC@sEHTi0&AoE z=(cZz5ZOx04|Mxd#S_Rf{)^h4{T576eZG)&C2*aeqz?KZEtS{v@C;HGTWFJxHM{#k zUgZ+I8x$DtUZ4iTq7lo3$tR*{C0ffmJT82VbQsRK@e1f)(&2pUqFI`_@*Jcn1K3w{ z@^ZqJ)S->3oR5-5ssKT$+Ebl46S;fBuvuW|)i$FG-vHbuD(S`AD!n&z_=jMOcO`=>a-s|JtAv@BL3i0`%Rj? zI+NDLmEvc)UmT(S{B{$xF#BtQXg z6fT7sI`p%#jE9S6g4O-ih3DV=epZ|_O^c>B?`}uV;Y_htwd=n88qcnvG%5G0KKOIZ zWt~Ih5iRO}-niEH@^J!#$T$g- zMFNln9;cHl7sK)?Nnaj^e-hG*CLZr>T#4L|49;%wqw+wCP5u~2QPg0Dd08R!7(^MC zF9sC?7-=+5{;O#7;A=hkD_y2R4U=EE`A4^Ww^{&(H`p`d~s%t60k*+73T}zwU zfcMtDV)A6UMqZp~zSu<9{`&3Q)X`$-`tj#a1+y(eYog1hYfRT#VAt&W(CK!4YD%Hq zM@MZ_%yrilx{lVpf-Z~Hn=t!rLhzOxj#PqTF=>hJCmQdQ37*%p-@+&Z69!!>E+g07 zUtg3z5ggesZD$J{D~lF%rmWhxMy#4Rc38)g(<$bg$WBFne`395dNiCC;<9UTaD3;} zXiM3c^Jd&*U-m3e%B>?GQMwid>qRfdLK>chb_Tn*ZhMf)<4D5(4r}cvk|kp}&{wjtvhBgWX{pcKCRKwv-G@ABP%`j*#aI z>d6D)<0XshNz*yENiN((P|<{Gb$4P1aJp~Q^I5n@@1lI+QIDiWdxeT>S7NHQ7jnzcfnEX%FRV-r6p|O= z?hgJA9J#2s5kS0p5DUbT0l2)XC{OG~zDt)a$c-Q_1;JeVfY%(?%2fQS=<2gaaS2$=X3 zEn}hn6@dYHT`dY=oP2Ut4j_SgmmfO~6WytdQmf{#7d+h!NY$<*Wd_QVqG&gN)fQ;i zJEFDk&%#u~6XXc1$pkb;ywP=TBv7u?EJSFs2t@EYFHX5yIe%G|W#Jupkt8K^Hfre1 z9$k}OviwQ{;{9%}Dpuib+jdo2K&iIy!Asr|*~*EQYNZanD{mr9QB|Km#!6Z{_Jup# zN}q$PQhooSo-Sd!Iu7ba%kZV9>UkM)G*I(X&dW%7fhh03Y5Ls1+xz+%bzXJo14!cc zll}8~J8jNRBmHM*O2o;S(lce+?o#2sOxvO9K|+D)U+%SS2OLi*YH7G{T z%zPM(C8jzK+sii0LApYcm$zkoxI~a)7oFD$yyFHa-&S zZrAAe3>_h$3|5=Ks5I3@eM$HzLx}M%EgIn)H|I0%Rd#g_SBf?LRH%eYXnWVEJTln- z@oEWkVaT}D`=ZaQDS2TKkXj;nIs|98KXDPOV>gg^r_oB$|}?lEAcRP`xkgv16#sf zbBOo(gqxBt{`Tp@J4zqjpQAr(0==~BF#oRSF?UD+l0+?oEhK;mBQNo6zpe!0@VtQ- zfAR@GHNC73=U7AR`=J;2Aq(X~>u|L6wL(T4pBCm#*hwYV4zxHWcE{>Y^z{lm+X6hI zbcQ$SBGma420pfhB{ssevgg4#S|{Yqg+jWxm<2iYa8KB9#{#$>pa70QNZb~wL9f{6 zfsmR?R8+?>=z$)!OY`L5RG7Xy=-dXTgV>aq7C+rnLlDetz%*)X1k=GfEQ)DxYG}ay zx{P^WBc`}=Pu<0-2boz?^E3zffDJNhvJ+h@M_|E(%t!3zh5#VEVDSXD+rOm292{w7 z0W2M=Tlm+*5_yKW#1b)Y-Nor(iO~M!tRT_SL5}`>A?pWJumM=kz_2Z?6`Hr1I?+-l zQ)c3cpbjdV2~VIZ@FTp`0RuNDGaKdPY9B;57RuNPDn)4XpZKxyMAT`%BrDWZFjC0% z9QLo(!6M#U<(~Qtk0=lL0h9OR+r7y%X$Ot)M7~{RTFO#6Zme%S<=Vw}$tzBnot8If z`9-cyy>`*Top@Ve?c%R20yJ)FB0kRFppP|7xyl&-zF~1g2S3A{6YwE zNcUJMWDDO^n!n9jV-`^UaH`qBDjfamm2tat@3+U*8UVluPR4Rm(qyQV&H0h3Md-1T zNyJ26HP?K9Xeb0og!xT!KP)V~hH8)F*@YND^_js3e`W)i?&s(S6)Ih=Io1|2fTb_i z@ZuJhWr#ZX?EKpK7^4fpo<}w4sD)s7ZvGwnG$)rjf`!&}pi4LmI&qEs+!m08j_C3 zJq8VqZ|gc)4~!u9#pQIu8bkPtP>9F$+&jK36lx@+hMbZs=qH%`dB_k3UTA1p#g$?Q zEywU!0lDt)!YaQJtS~+k^Q|$4lL2gtgFz#-3Ti;Qo-p2YP6yvbW)e(jG|76#j!KOg zSc`c9*khSReSRq%$dEOaIhP@o ziF7Qy^po9MpPOk~LIG70=|4{C>vp23O+Zze>?*L@8iO4ctSf#HUx-3(cUV9OuIjAr z`Vu`CdhA>poaYKTc0xmG#1~R`A^NGNDT4hGyI0c`DVlqat$(tklZK}$M7hPd;pSQh zZVH1a!yv@qX$(Si7anT)@UF0;A<*ihp{wa)G#a`}c(kv)dBImyg!wJ1m}qp^z||b~ z1Ea$hv8@g7HwJEhF`b7?$w1Hok^$6Ttq^El3j)OYD56px{8BL$9#x@)RcI}vq*Duo zUjbXwd^lP5$T+dYU=}&)-bZuxP~Ykx@`xrqIR4`LNAzTBw72B=|D-xayM}wG$#3zPtT6 z1z|m3Aszh9(Hc*wV7SKd(HcLlYGC(~09yIZaGgPm$q4rYD|$kI3kqZ;m9s}>C_jgm z7#7|ju@c@O(%zbz@Oo)6IrS#Yn6wnxcYItu%(LvJk{+^QBO)t7X-Er`sHItm<^`^v zkL+mdpsr!bVd8LNj&R#>w1Err?_=0YgAJtw_BqWM*7|Gt@TxKmtb%kq>b?dzpccwP z3Wc(`Q0k!(N19XK3Slkbtb)tZ5QnW={!C^C5IAJ!dX*ho6tUymql2qRwSdhjnf_mF za~d=miT(oLAM?y^RNPFuXFtzTh9fFj0YH9n_&G@`Ik z4*23>qh|?ljwbM`WgbhMsGA)QBmV^~0cowrmY=n*1_6_}tu>A2F-7H-6^P?W7@^8N zAPhFgX0xS>W*JTc2mAgz;()wER1;EyaiH#P9u5`|44Y9oDLyezoJ-&uiI^D@I_4Vvxi>Y^f*s(b^gk4x(ANq$O;yo=K=h$zoMbTm-6U%V?>^Q%2>MXz2fYqLeGD`@P zFlM2_AKdp~8n?osEmSuVQ zt`@tT#&#@L91zN8DQ3b{s41^%SC+o`>sLqwRj@z=Q*%y*LzXlez=la5t2ey$l{^OR z%Na!Q)R$4h>}77la^DM~*6>0DKCcV07$}(*{q(~?S-3pJ*kyadn4KeM7`yAUBN*d- z9eWrMXYG}UtJ>2vdSl-fy2Tbwi;Lcy)DwksQI#q&6cPtB?c)c`ydk$Mn*lrrZIqjm z%-5Me{{@dLsD>^148nH+e%O=n9x05lSlo93SPY)idG+Nvo+i`63~Xd+K0~)YYs2}y zoI&(SkEZDcV-?IjfUi|GV==SkQC^c?+(xBpHMLEK%3y8_ngVMWG{v99q^ttB+2({5 zw&xkY1)&!o+Rz7j#JYuC*@DEK#V4U`S^p+2P>&#u_r)9MG}}sA`m4{ft8ZvFaXmE_ zxw>?*mU`NsCs=rm^(wcxGXx{bl`RqwMyD;{?Q^WTG%lWl7PAOUJ~QxLQR`MIjeJ&r zrJjM8KNzGZ>!(xp9j7UY2(scgPowi{eM56P&+r=?;niYR)B6k4b*Jb%T4d zKbg|1etqVUy8{V3nv0oyi+}7MraJ02za!j}C3_-CaSq*1o}_x7->GMTHZ-v_@mWnS zcj>q_=JKa`Nu!5|Vb{FoLvd^&jPNsBh zfyT-QIraa`b*P1A*AiBH#2PQp`i0i4G8TPAi!FNhJO~M2rEWy<2{#Z6q>c{VAS`DU z9!MIG^Fav?8qr22Ugu5+1o>7TZ?xjRWQS4eH4fX*3LDQD-wnlVu~Z?A#2o3OZac0J z&vqQ24z;X8!jTH$O)mm$ZJ`PYH#f$Q7KREX69GcW$yCg61SpC_-wCt#Ii-zZcHEOq z01I|pQA%yU-5Y%GN;%_&-#w}FD{-`1c)ybu-GXM6)^ZiFqDtUhp$MVDM} zRN#uyv-UwvbJ&>(E=l~RA>7V08808|gLC!}90(3Dy}+{}9LFBVe)Axyal;M9iQhfx zU}^qp@eU*5emOdnAC^7AJ0>M`;d$w?ur^u<@lD+?mge7hh0Ez=Z>sa?d}I4k*g_xa z;uSmIro(YBVq<8_$Kx3P6E)U!ew$pN@xnI7je`z-oY?S)*9WumOk9J>(aYm*%iD$W zZ9%@~00zi&24F*2CM@Nzv8U`t=O7GvHQI@zM6$c>{Zeq8agOqPEsH(m{ z_23@xoWB%M$H3cCzRDXsUbN8*O#8sB#hAPM{sxp4-_vfTea1vP4*e7RFZIiTMf@K} z7fb)Jw^Mu+^nkA`&p}s%G0!gIC=i4F5zDjCR22jO-`Q#aU@9iKDG*)4sujnn79UuB z=)Tw9c&+CfE!$w+7r6aa`Z|?j-oSg(UBID0J=cJWl#Akf!2y9kW! z!}SJnm)CEELpaK_v-=~dS{hkn+KZKq`F*MZE@C^WjGr%3M*$T_k@*eK(^|LKF3v_p zA2)cndmK5jIBiYq&pCcIJ=EZ4Hq|y(@T;3AlL4R5d|2SpUx3`K^&WLy!37BU?|M9! z)aN@9e=a_Hedv%M<-+%BS6!;^Ih;yY2$p+?ZB*g}rnL0_=o|KDxW-A`9Rw0Vbf`RX z0C@@y9;Ju`f1fOuz?sO8X+FP6a#dxsXXdEHw8u!4Bv^FG+U0=E=nJ zR+17BgpdNayV19M+h#kcGbRZT6fgp4$AZc7O@X;YfS6wpTG-cY;R^_qF#zdJ8y;^Q zuqr{$OCEk9D!~YGMQtBUrugQ&fJUXD-a;pb8=d6u5LcSW$e`dDX){)+daO~^{3riN z_Sv0iK9w7#U0k#vdoO?|-PS$B^a$e0SM2TKj>bb1-7HKiHe1=ugM53UuO z+}EfHO|=NaljlJ5k&xS>1ssi@XzDewB7qp1*f$vcdxsnERT{@XJl1%~5Xjk9gbjL# zW6Lb{0as&tmx{ZGyg3w}+1^@E&;blT$=?w4MsNc5^4WTv1Zv}gj2gSj2ZvKxm({R& z^WAS0uCWI$L#gxy^w#@^iG^wlPPc5vh|?XsK06(NYni;D|Ip%I3-jgW3G%m1OBh&Q zm;p%ih5?JguzU0d{63;L_AwV5BSMeA0q9&OzguYWqG!lJMR9--CAxbcw(rk!GmenK z8{W-F>72_MQ;G2ykxY5!@Azdn+>~;@)CV}kQs+$lERv$6i>%-i>avOn(awM}?l=Zg z@NBtT4(PS9PU!oIA{m**;u!=l2HLLSfleoP$5YP`AbjHfY`6`ZR9X15i)S=!J5E;WwrVV9 zG4J=I(>%66BD<#IHb&)MG&9*s;#Mou=0Tt`4Vtv#aC?Uos@?KTqaZ7Rg>#!thdRU- z9nHA?LEK%XDwtoP_X({3Gu++=hBejAAZ882TN)U?SqR=A5R4(fils_35Wvuwn%X~% z5&LEgO_47mi&B!x9q}Dpt`>_7N}QoUz6rT#2olb%Wv1j0GJ8Tp z$P^rRXm|u#*w29iGUnwh`>~wv*B3&ntNW5$9jvb2~CSA5ztTrDfk3RhxYz*MLWnnTq_^K?Q0}0g3iGEmqLLe2O5v5|DSXo^uO_JJ+aB%x^s3mLrO(G zg{R_ESe7i9rO6cDOmJsXf(MRf(FZ6}l6TetCe0nTE&|b+;LsRWxnoxPIeTQSv3Uk)#Lq*$E4a`LcYX3(<5_ilT*6=CTTkIr1G}GDu*@;U1H6tkZ=)M zC2|}@E?$w81mc!4ou2kW+72T6?2l4tBwrdSw8}*IhkF|j4P(k}>b?EVNB`C|Qf)jK zA`2LW#j^{f>%OD;m=G-tH z1(-Jd5CM|0dd#ar-b-x#2ygI#5k6bQtSz)uWp z`-5S=A0n}2^q$7Uio|-fr}@Z}IPqaT9F?r62Qv7c$j7jpmgeWh2kga?%J+NSbu2FP zx{nXJ3gGz&Z!*H&>1aNiXJ|`3J5s%Slu%ZoMUM<&omR%dE)w8#mGdOb9TZVal@KiqN!i(I>Y&9DheDQeg}cXkKDC@7 z@0Gs|SNL8{&W;~hSkCBZQ8^>Ua|$`@l<7Eg+5x<@gz`vy!{y`^4-w48Uog)^Rye%i zeb33t*&*NO%5!#UdIvgKA;Fsi>f|nS&p$f4C)t?)zJI`4=aBlF4HH((G8m3gGE99r zmM+{aSK;e&N7p9Xbxqke{eIP#ZPhhO(wvPWzHAoaN*Q={fRjRMtgw8tdG3UbXlFUB z^0d4Ug9onl8HmTASDxt|2~FF9U9skbJFdp7B*0e68O?TdC2=xqJvAg)le?|K)!)Ya zshiHfAZ=@|mC~ZZi)qT53W_1>iB{7EHcP|Y)>*O%Ho-bkoH>;+tfgj^$HU;reB3ZK znR>}*Iz!q>p{qsuOh{sRG^AMR9fZcFPYd#zzVjwLtbNrK)-;nEpa%3v8>|lVdzera zF^@7cSjuyKPXhPlEae)_XS69KoAwKHR`|N0=uBp7CfxQv6Y91Fb_i;1wwh?(k~B=; z39l0Olll@m(Qe&g^q!D9r2I*Q+x?11uN)90!tK%*cUXD*rGRsl?NsMOWmWh_mdzU4 zm*#IN8S+Hpc(wF}5m|8$8pPzZ=?$O28@&T#tp+jao3 zZwb^|jpNq;6LEe|K#eG(p^?nhZ)H`v9`@O-ONbP@G`sDOcGFvkauqO}><@+Sghpq2 zz2GMT8Z5Bj%QRRi4pxRR2}bJPc!(JR7s0z{xmlxc_?l9{NX_Wd9?FMSJ!3vU+KzkW zVvAXJK&3va=UrUkn18q&uM|>Xc`au^`p+xU4WbPnkpBOedmkvfj{45?-TPkm>+aVr zJxOgOTO!=|qGY5QIZ%=t`0W#qMSH8%q)|`o^Z}GyR4+0m5kQooptPIt8EJ+ zCkmLv2@WVAutMZuhyW2pXg~x794x>gPH>38c2I~hN)U<9V50qef4{2x=XJN#mN_%A zj{DtPRk!L_zxw^FU;V0TgDPC>kqzrumlui=WbbkvuATperq{^}=}kX7MOa;Cx4{6UwTxaguRqLYK>&bY zTsN7&41N@+k}@fmSDJ89B5fg?NHCvQ(jti$^LRD52qi8Rs~Fx4{lK_z+l1Yyk_uGH z`+~u^IYfmZmc_lhP}@}gto1qCAgmpNFD;E5y{)Nn8_Uu9&>UW!eDC|jsqsVhChE(1 z*Q6B(sLZRKi8dGX{kVT*Q?c4+bw4uDa4PeXSaKU8xmsFoz}kx=DUD0-m4O1dfq=VxJmP;w!4` zvZ-)(b-2w6m!sp~q5u*Wfjdm#aF>KxK9xJx4;kL#>?%wyAq*N=pKhoehq0kLsn+yv zB&uooFP#hUZ8fzY-K)NDo~m^t3w+oT7}uo<7exN3Sfi1%8#DDvAVvm=ISB`&oUpEH zZ=eG7#Y{TOq$#jcmJ|Gg&W|<7L+FE!!?+6c)_7U6mXbJ0GyiT)=i`;*iz@+n;E6e6 zAok?)#*c}On2o@_b_QGG`yo@~jVmmwLiyU#NP{9H_9y6Io91@0W!6+sC(MxA$$H)Q z%?KdMaeWLkLi5_|SQ>G#l`;X5B0cDV8Vs9H6-2AgGW}T#Atl`hVpXfcmUuw{3@&4Y zk6eE8N)_OduRZP#&?;46H7IM0Q!1!}Jz5-1;gReXMGB4;^P0*#6OjDiSh6GvzWO=w zLxRsqK!aEWVi@%KU$+6JQlXlLs-ij zS#0f_CBT?SbcIFHHm0^2h#-!3HtNq6^$?rj^js62mE-74E!aaZRQgqM%y{%ZQ2BWN zZn^VY3C@0A*30oQp1+hI&S(;l*$8oB6MW2jk8$vTr8H1ey18jjD9!X*U3BOf;F@ic z`H6b}l8RKNX}TzrD9JalfPj?ddN$bi+FjY?jZ9-uX9(^$+Fdy)WMv~yF`nFuOrBPo z@`M5=h?(0;5S#Db;&>bc7ABYGj~N_p;sEF?Q4(Aw$~nK+_LE)M1DIAfdN`K?dqgZy zRFBDwMo>=FL{3!fQkf5&4JA#2Dkz+&gnR)r_Ka@Vn82O7goA2EM?j%x*brE397sVl zxF2!?Jt4z)BA55n7P}_G$Rwa=!m!0cOd`d^>%a8EKg|!;AeiI+pc?@#d87NUTre@j zLBeS^rhkm5R&*Lvgdf^!7KFUJTN(~Bas;;1hN!4vTs7&0AG7u9J`iN(j~5LYcZpc$ zuYwgnuY&U)q8lYZd+=>;0J?($_p&Rft6d#!E&?N6Q)rhxW3x5dR?-iJ0fkfslci%~ zQ=vMRayg+KO&1pUtwP|V$y_Q#fuDw&MLAyIBV)@cKUhgY z9+_AHqPb#MXLlKoOh>VsDguqRIlIP!wxzoHA6Y7IXo_G#eOl}a%$HE?Ky7demyBmV zRLcY?bG)RbEV&S6Z_0?8!Pu~^7eaK#`2#U*wMyzZ5Dhj6hj9c;0-%%6)&a=WHOXw) z8JU>BoIVPp8krDx!o_(-?zK}!Rf@M^?qp#p8m&?3#Y#1J0$4C>wv#sUu9Nrl8Ayk% zelioz-gjs>NaI$apKLE6;JOSNNrGvnDjHklunasU_B>XQ7_5FQjL z?AHvNfU&8Dhvtb~n2#Y`$tWEwU4*Rc=TcgBSR8 zl4%=li3Mp(CK;TP@q-hG?HdAs>7911tYe!k1SKwkUYZz!0iNLEHjksR#2RPmHSn*R zu<7BtA;=;;Z)GD4jYFQ=9m`2YjJAQpa6?*vwibn_VD3_$uc;XDY~P4}0Qo_&dCZ!& zsp%}!8R$x3jgHq&uL1et-kE>f0i@m8QJyPO+s*|Zm_i91KOwfM<|kDZOLgbYH9TmEfF;aPJ1@VHzfVFg*N2VnLo*O{=V!xyAK+w-k4Xtj zX@VM!rmSSvlZf_@3ap*PGD})6@L4_j5ViSUR zkHISDD&U@7N^Je&XO$~QXn?`CnM!lBMiAJL(50`Yoj++|FNOJ#`fP={?i+h8%!d?3 zYI7Bwl(K{I3&6+XS)4;6U*>oqbCXdU6S&83uZl0oy)dU~Is`?zg+K0dO_p=h{N-#o z+MVDfWyId8b4GH;-^ z>bwD}3jU`hScjT7s6i5+g5(+ot*48ngKJ0Lf|0eNN$hdwKmb92-xZP&uS)OUZSNFX zpSG@Qs&HX_b$+4hEE?7~=#|X0Su>we9S?5F?&`y3@U<>Xl6j!FNqg(&XYQH>6th^1 z>yv)IgtvA|mdu7Mp@Z&OYUz+B9q-ZoJ+r~F^ibQm{j9C(uF9cV_2at=FPC)OBm-!G zn+P(lXR0h$PQX{vxwSk{x71?BV-ndWFn}@XTHvDDJML$h47Lw6BGVG@um&p@)QHQ) z#Sil-33hCW7C+cDjv5D`v%6HHRjkW+K#i*p=Tr8k<=@Mgn6gr9?C90c^lB`ysIqk+ zrW!4@{eLu3Vs~w%$h*vM5YwF!<_UmaYdQ&El|VO(LmEoTb5aSE13nl|I8_U0zA2Fq zwAA<8$wZ`49cI=HIG4d@LmqBE1Oy;n=WGHK#icRVPHDd0_iKSo+~}s*m80(DYB$`9 zc;DOAv~BFL(W6QffNEZAt8IR#p1^1E%5T`SK(PknXc1Y8d!<%*Sw%KK=`bC#bo3qD_cbsaVdl0k?s4W`PoTAE z7&my2*mQg)>|$_QR8*c0u_hPBlNI@Tkjgp+Sa3hGRfdP#zm}}k!HK%~w#o7e8*BWG z6WSf|<#$fRXz4mGJUb$P-QK08R*=D+xy213b_fnr7^NN3;Kijlbw!C`pPok!s!7!_ zS=d*gI^HMN6uVqa2YlfGov&d`y5I33^tA{M$Jq@WdGjVaI8OVcSlQ)}JNgn(495$> z8Ue(P37u95j?;3mXYoLu5JuVc=^NZxezPG=JNdB%DH2f!AiP0{#`xoGm4DKP!Vba4 zLu?g+d%uB2YMS+Soy(&;?fH<6M?1oB`qt#Y}vZi&+!X3bkPI?TeR8Jy{Dc_Un)h&spE@6#aOKWWG-0+k@=79tXz z;1|hEr-Nv`8J-b0P25(IWpBY8tIWNE#8}fGH?fwD`z#*9l(_(j>%a-s(0!*E;xbj2 zyez@LC1&108K7I|(5>Z%fzA9aI0L9gCDpUFh#?Uw2)1=!L15{*kHW;!$LY z$`)B;z-iB-Z{ zARbT!^m3~NLIb4q8b*z2Vdix{kd)`WlvWDrbFBuivVrQuD~DCd1JjBA$RaCY|A!LN`@0e(p? z>cPAeQ@~>*AK`(15@E9Y?lAIxfLaxj%Gf{CELTcuA`;MT>l{X=*VNumWXCX8XCDJduEgZR`>24Xl-3adv7!=A_b$T*2Hk|f4>lNcMz+6W0* zCx3-WZbNFu+01lTwN%rDNF$d>5NRn{iCB1VeNfRn&MfW2HFu65wDo}B39 zUJW&lXJfW_v;%C19fqFuxro@b}f}dgWdP4 zHd(nHSyKv;Viw|&-kAUB6tGgkiLEWreUWX81tKIC_Cqg#@@_G}s4e#gw-{Rd_Tx9#~a4={b8U<&H8CA*l^EKuQr)3qZM;gaYE}75v8>T)a+IciC#o-ZV3az|Q z?AZwJ#G~JvWeWVe>RO}oo}_Mo2g2i{P20AxAqQg9&qgfP#hadmQlS*RfGQMHVv&NRMKl!%re&y*uKlZe8#w_W3pZk+N ze|Gq*ANj75Xu!tn12e#bF8ZK;d!n)u>ZJ3x$%vwq^gn02C0Y8wE)-+B<8Q*%#n8*-c0Fjd@u-9??M*Q?6L zSK}06x~r+5whGfHDab>k&1pK?ie8cW#3&KEO1B-F020)!hbqtsJY5ZIYj&W!yA&~l zeAK0DB(?feY%$lcr{9J)$#7~1$UpNC?=VJ6$IBE!*M(7ZkN7XWtW`w zF3pzqF5~~zS=%Om$dFe~wL=ds1uxUveyY*f`v8>#>5Hg*X?`0vHq0}vS829xw%Y%H z)oRP_uUXxHG3?3zIOH$MUaT(ZUz7g{S!Q7q1;RkD&ekZu>j(FdW1gwY$RT|xk{7_) z(~b29yZcgBHvY3L@we!U@ThpvWlS9|Z(y4ND!;h+HS?a%Rs!pqYoSCct3vWG(tn=zlGH8thK@ zR;w-_`k$S0mvn8oAZwAjMhv-VAyVO$Zppad$T6!ncm)|O-CYc=PZbWzDa0Q1RtkoH zE@A<{0%E}gZ`7R^D;B6~A+fNck>B?lpB^;wf5l`ZEV2C~5x-GP)Saic#Kszx#Jng| z+GW~3op>d|VP#{1Eqrq>PT|bN;m?2&6>~rqNBsPTsA#YZ%$13ota^)t^cUpCj~40i zRox&5hh5M>h&c|%=;6o{1bj1RuD+GA$K`dMXp74r(!#xIQNUV6ardUlAC?tZg|Sdx z2TV=PpqhjFqmxui#Dt7Di_>DBS~&NvlP*MO7$vXgyTbco^%lSZik6i62A$tmXR{V6 z<}N?gzg5t$ORqE804J$0Soid0i$KvT{wr{YA@HQ!INeqX7f{ZS31G5q;_{6_=?)un zlL>41VH%muznXFuN2JMsEgff@7!`V(=0~h@R(>?l^c%B<+RZT3(yo;;0Jh!g&G&y%av19pdxp1LroeuF?>(OcF&$s#wzV{wt|m~qlc@Xq!Z*`-&Q!%nVj9pr zqLhBZs_w%H$q(+)_erLN6G}LRPH1<3Po=C~dT`oS-3kI3P|OQnnPQ@AM^*mR6O0ef zaCGUZPeuRFQrdU|*>@(9$5CDAC-kMLaX)3EjM%H^J6v9bmT185qalY=K&(}*Wb9K> zOTS~EpUOMCF_rvLEkNio&5Dr5Akj8~!KkLKhaOtj{ z0t&+)r6g|+e?Y`2QL8ROnu#cTbao+>nY;yML3SI;qReIJuIRThnr%tQHC&!xa<}q- zM;vFT!M_aKIsaGJ_O%+vXeYR%Fp$PsLPodSwy;ct-^R%`C30v#xMkwj{`i(`?*q(X zRGW-lFZt)v2PxRwp8&s4ohwx%DmFVWW$- zA{3wt+cP7NqGZ>zC6zfw%q-9M-ECUOLCcr*n)#oppah21C4vMie#}8WMgHeJpU?9p zydLvcTlr-4T$5I;(W<1CRP>&g~pun*CBiR-hA+WKGCLeiKJs;>;%! zOL>pdK-GjbT%LCJFjMUq6t2cB1_2ncB2elTAi`)Yq)#EYXfzsjzw?dxu{%*( zkBE{Kv#fqFXRr>~70+~l<~D{76j0YEH<2V7`LF&H=*^c!GkJmX)VQIqlRYYvp$u2L z$V*4z5Y-l|ELhAG@*c$vxzu+hPv3V38D2_!-U&$gTR&29iY5w<*=?i zT~of|^~m>gs15*SVc=3Cm1V@S{~&n^lJ}%yJb)!7~g% zY%KycP|E?3AZK1U1SOFNyExEDq!}Z`3h|28CN**OrNKCyjeH@>d6!YnzrFzFl=VDn z_wbE3?->2I_2C3DKZdx7#Fw)F8iG%nwfqRCop}`RE=zdqish^9MOJ#m7xh@ZB8-n?Rr#^4C>vOF)`1GBs9kyc zrYuncr$fdB+}DIpE0J~lYN7?xvy4b^~4cNTvbmTwZu#7iET@~te!Y#iL2|0 z)t%$2pw zl_9g~nVni@CuHKO)R&XB%*l`m?9S15S z0Kt%TF^0tV7JWaXlxj|fYm8t51Ob8Q9)A`E$9%sIWfR#=r*I1-`A_dcbTspq>{1vg zy=~^dK)*rU=sD!8m^<1Jjl=#K6geZNh z6o=`?RO1q`C-DquY}O-`!!SLtozuOXMqO1^?Y%Gvb6sl2ym>$`LiP0~mjY?ovn zWdZrE0Dvf&uX(jLTlw$M<`NX_AE-^RD+r0KEVkmBGkLXbQ1rn_d@%UYU>N#+t1WnZ zkp@GPo-i19XfTp&nGFWbnwnuGgw8O%GmnPfqB}%~qt;8K!qGYvn#w#66^>S^uvXZq zaI~bta%2DqY77MM-*0e+hK3+Q9~u@=v>;I#o9m}KN(l&bCK6J)&rVnm)J#4@?GckJ z5jB_E$)-4;zDn>z{>;m-U(ISxl7BnZ4pi-e;<(4R6TiVeI?wrtYMeLctGd2$-khIU zC}ZV98J&ePMiF!7BCh0mHV=Eg(et2-q|8(E8syDfGIua&aceEk zgD^e{jYbF~PMN;m!a|R31U9P{H(IOvF>$K023UeN(}l~WlZY$hb$g}J!vcR*+kmTwrS;)Hz9pj#_#-tbLVU?rG25TZ7co5OY>93hn7b? zxbjrw(oln!Z05^v%6D2Lx>&`bfEX3`Yd8_N2?6K9$qPWmWEB;PH3tfreCm#VFOLzpl~g;K9rQthj`9ba5YRN?SRsl@7`ZYlOtK>q<&&GHg zX?c&gA@>wbluk%JpRkmND~?Kh=A_TYlobr`w%!Wt6@&{@!DEsMjZXLD{+q#GF(^mg z%L(E_DVCpn>!$|AmS&}50I$wxYpGLGgH!H-D*P_LcDwv2g+HdO-D!s7#9-;{!v2r< zSv~*59RJFYzl3;vLS*UiD+rq2ve99E9hmsv_N>xo2CTwrXlOtOljUU5vyX5Do75V zG!uwmV_{Dks25K~R-&k)6q{%K@I}0Xy$Z-W46czM)v$BljibAUh6GVnA4GG%JST93=HQ$jGg4yHosh9k@oEJHt{@^S`?QK~w?V?dkYop(= zDm-M_+;8+#gvOSZfS3skzizRF`ys37@(U2IC{sp0N1FI<8(OiTaYgnZhpGyco3~N6 zB3u{~iJ#38)#!$TZueaT-D)Bb<`W`W+8A3D$<98pXp*&XDM+@600Q86p_K%C2+CA1 znFKxeD{%8rP-bb#GG8iK$@umK_us@@tpT=*u#k_C<)GrIXc(;>izS0^7#X|(`Sk`i z1By!I#u;H+R#AvPR78t5`p-|qyyMUX$IAnbZC)m!R9_t%a0>)_X@02^6k$P#r~i1E zmBHUFQTYux$y?-Kn91i;e&M)i+$4p@D+GUyahvEbIEf{Gt&cjMbW@*vJI{lzf5>=h zABwJ&! zBV9L@0}%9(%__q4z%gF6>g-vwBq9I(0k~q8Bn*S=3tM&bn6DU7HL(*R8ycLUEVxTlIcJ`EC}&E6R5^ zD-ktNzOU2!hVp%l-n00wYnaw5>Y&m2c3K`cS3Bn;XMdO&^zz~isN;|?6ppt;YD?$w zjNl<)yDBmqjf!(Rp5Z35U8i?&sKD4&X+{YWbkZ10o#t@l01)kSOclU z7dWaR8{Hm^1SJ;TbGv~J;&y4U9qSPep)bGXFr@fx*pWyDg$ARQaEcWWaSm4)-s(w0 zKgo7OBT`Mli|J?nsdLAfTeJdLz9&M|*7;h(2cl~<^{J{0#2nk4Uce!GA^V7Bhx2SS z!Qzuo*VFPYJ2sX6VM}j@UAbQNGxfAQ3uQlAPq*(+)YAe4$Ev9UaF*kA`IBe%(dVn# zt9!DsI{z!(%afuHL-5*XLvUHzQ2TSM_u5adO}cRxrHDIqq8H}V=kZTV(6{C{ZClbE zP11Iwv1DqJqhcpoBjW^Lrui?q4;hvNv;Tx4O+@=z(l>6)pRn`K(tK^uqWME_)+CY* zuiA*yGNzR=94v=tRFe`pUn0teYV~EsPyVS(;aD*kuqq*c$$`QK-dH%ASTSYfi>9o^ zbcv^^#H{tx57jz=p>e319>Hvu}P;WpCP6L~?- zff+Ub4Ie#?KufDC$Xq=m`gFXgENwf!E&3s^=JOdR9i%ret8n;ivZraD*qsX5uGQdmXqmE6-;HGG zEFmhYP4m6t{_2!pfYtF%Z|#G=EDIvC2w9_*uJP@W}Yg3HwZ%00QD+PmQ@&c#Tz7DSzxgN!GY@Mid`e^y|pwf zN74?~(l9wmd$*;+Pi9LIzENftXi5&sJaqz3H2v6%(+DQLM=_^XUkDjU|15i zm0MqnG?x8I`(o`|wo#GxB}L#xCIl?>!(W2;1t_b(D{aFu8WAB2)Nx=OUIxgpVpSBdP=EIzmKRibUO2_4H?uCCP8UA)`$1mIc*KgJ{t<$a&sl}qY~cTPf8h?T%eiBep_0R zr!l|m3eXT8m@ixCz(A*LY{W^{w%C2{+axrp<+n31qqAuvs&R>{zDG^6PX`q?f$U*o z)Z#d(CB3fNvv&@g&PI@U+*Hk)fTCD6@suW1bQxCngg4 z%ATrgsxTCadpGSsaw(ahkHJ*_+;CHh&`daOYn)3G!PK}yP+5eit1^JCSP8ZLx+2jb zZAvhg4Q;xaNDaS8Y-!fkB}x+kk?iM_rjKC|QiBJFNy>+FPFr;;0@F}+ey@G}HqjK5 zC=C~bq3foY&rfl7R0s@_LsqC?mcR2KV1O6^VD_vX*tk)9-cy;!pE6^~h86k4$(%&o z>253JDK}e5x=P57D{hffl{e8C2vTD-J;m;YB^-*T9d^*4Az@zAf;>YE*oyQ-_#Ue# z0O@D)h)kfc0Cy8S9iM|Iq{%#1i_mhUh88-*Gyp=)IR?^i0+Voz4f?G+MrE=DIL|)+ z!C7Ic+5NgG(}R8)IDjpv@^np9*v=f2N#WP;~Y6O1fn!wlb80n@u~jAOQIRpZwb}G z;?wsCs35}_Bj%`DU8&gB$4g9Ly|+bXaUj8rMcmE(=&WQ)VT_~hDpe-=Osz`vURmKa z96neni^QtcggUgcX6SNe9`yJpCcZ)!c%N5`Y5b+g7}-b53Jb*5S$(LH)*>oBI!~o1 zs(?vnJLIA?M;syr%xQ{ULHE;|@)Ve(h!~T6H{-#TBxJm)v>>ZLg?Jo#=WDiT$#{3YhyK_ju}&I3Mw95Baj`ak-O?KhzYs4>~SJ^vrI z-#7=B?Q&Y8oLKxgae!J(=#8iuJ7!d2rg{X=VIYRujk3rsYOdxHhV>)EW_y=y$m&l57(b!zCZX}e%-?$os5h9g$Jr~|?&D#HY7b~9{E5?n! zHgB|p{W8WIcvM;9DOd_iJJwrd$!>gqiAaMo+i%>(V!B26M@pg93Z+mBCz}feL~Uyr zh@lsy5NGKyj8$L|*EH-d384JYLpn0)I(p1{L%@-s zpdFwz767Nh98v)gjS0ZI$VHvDcf%^fjVo6Buh>o>FklB%3YR_vJh=cYyUsiWH(TsF zSaG~Kud|00p`fm}yz6XhD7xORbX})esc-6fJ9b_3mx9|B1`FS+>oA0~5B!q1?)o<e+qbFS;aB7ns;^m8@h*hH`e)VnQM zVjk+$U?o(TZs(G8c(^K_c#(J-m3SI;0b*jzrN~Gro+yav8ga@(pbZ6|K<7i^X=GSD zjg;bPBvxFCCwi!Pi)eyJPA0F5C`eGJ2dk7sl$~Qy5m6|eiipB3U@(F!VVX`eqmYQA zQgG-Z3g8iSK}3-;?N>}e(GpRfVN_7%1VL5jgvbX{r7j70k~E5ZU_wBK0PQFFpg6?m z;X4O7lv86TH4!yl6aa1K0u*S$1-M(;nH}|;U%g^!r3iqoD||rTts@cvfe+}N_yB-> z=*X$?0V29CNww)kcF-1>Z_zm}k# z^;`xzKOqx9_Kf&ON8a<((vju5j7BeG($>Vh6$3GIkT1l$G?k6$f>jy?ZI&*{c$x-UG7w^6OC@)pWFnJ8ZYXT2;yxK3D7V57Lg;)FKps4!m(eP@&SEk47ue0IRei^Fz>xuAc zzx1g7dNRD)FQN1G*T=%E{W8?y^^|@Y>UyrFf{oLjd$wNKQpr8zxzB~%P}ow*eb#el zMA~IxOXYQ-WQIEAhQgLg?q1KmH{^!GmP+n^&%Hn7hQgLg?tPwnFyw~9mP+mc&pjM+ zLt#rL_mJlv3Av%LrIP!Q=N=2Wp|GWrd(?A}hul!uQpuh5+=oMMC~T?Zp77j9LvAQ+ zspOvY+{Z(1C~T?D#wI?>f{%GY4C&XHATlv0!5S^4^`(7iO)rg*p>ma=w&0n`orrFJ=H6+xxF-L3X^agCK zPaURlj5Ce{=l!IFh7Jk0iz6D@IL`}7(F}UF@u`GyIoe(2kn9gry{Lyv5zAOb;OTG#EIvqT)z zx~54wdpl7-W>4GraS4ULoe}icPPOPHP7&_U(gpR2OgoJiM)PR*cG(`l!@f+aAkxZ0 zVs#6mia6p>6lZN1H*mrz z)x>E@B9`+yN9~voCqKigKb}@Q0xML)laaryd84vo@?a%_kUlXv43h~)FHR8krA-i3 z!fT_yHUk3!=6)gqmlmN%P6hH2l|3^QB-_;m7fskpwiQcFdJ3dl^qs7Egq5U{Fl_C3 zlm01?gKB>Uq17Hzyg*BEp{S!2cN7)Hg-$0SpFX(_SOBaD8FPUN4KHLUwd&gWYRXl3 z*qE?JzMMh&?#J0lkIOF2$8WTsl;aFeeEs+Uhth8HJG=WS?miZ>^A@}F`Kc9%d30>S zJe*I@Kd;tIAD7{^%wf{&xh8_z$b=;fN-dk=zH*2!xv7`tzutDo5}@{_SC|~Na&lOA zxB1RUxCDy>x*D8L0)wh%9A(?ws7=^q{f4}V}02z+OPANfbk6P=eAH3ABPSWe4SCqeKp?%oDOUEydxOxFyQ z!FLL>d4O)y9ItFXrl;;*lI!Z0i6V)7EraOKWLck-f1(k0ZG+&{&X(fFi#4hs%zl#Y z$JhbcDEE-BY~)X9V|f0f`B~c_{^1$RUxDn%fJfmH-M0veM(4p7!-t`#r>YU1>E^}% z%Mz_w?bxCv1;jCc`!HFS8eg-jM(L-zjiCxJUG`O-yn}SM@%!z|;`~32!dmeKPKC96#f+X1N3S8t50*8^dmheP zEu?cKY-i>49g7_s@s)B}XP71*)an8-{K|eo9}en{(1(RyYg=mKJ{4Ll^yr4QRKya2 zd4>c(>MK7~)-eZuU+qW4WXPQJfvxV9_qtM;#bTPPcBsDnj>E_`9)T-)e1p4b5P@h& z-d|501KS7oeJ$d!eCmtMa>l_M)%8if$oH!#YpWKbQ8X*EQzympXC{1-gp>-hktapN(Yz$GR9dE;FVWg=6J*H6%CdI|fU!nQ(Z|3rnOC zlno^)ESE;8h%XT+`QayK4Xkyw@Y2^HRDtTXB~-62j*-wPd0b-A#G+gK{NwYtW;m#| z7GmecDNk8fN8@(Kf>R=`N&Q4TnF0c(>@#@P>ip>vY5(oWPKdisf3|uAw?`hI-Y41# z_fSk)r0w@CX{q;10bh|Z=?wnrYo?YOX~EW6q+3Qz-RGv`Ds5^UXDzOLGSX%-G+S-R zDPb4ch!Ec!MjVQ+Miifi5TVqF-~8b+IJ8(ZopZQ4B+m|((0!Rt1$h#S(S`CJS1mHU zsdo)ZmLK{UPp63%+i&JY6^ z8ipS8n7^;3DJm2P_%DgOVttAVP4YWxX^ILZUZR$!sL&+8tCpsy&?Mh$X>mJ)qC%7W zm#S&9b@DovIY?pxPH_b6SK+GCr1$}=AlM2Dd<`=;Qn6`S@_{W3ACH0QTN_KEAK^ zF^g0SeSCHvtk2XwW=UtEk5A3>@fjPkh%i=w7W(+%c|Lx;_OXS0)hQ-o`{+C$pQwFo zexUh3J~+?EN9^N3X#j0R$%5`MZmmCi#5&=$D8ykQUQCtBz9I#C)Le~G*SIYf92pO% zimR`f7ewxil=GjJzGKy9V#{#qJkNF!kpa_;;iP`Q*KMI zs+n5ZCh);i_WWRZj6?vLILbAPO=0PG`mM-glN5f5_gk5uv#iQu`u6O>2mlFwYri} zm;9gBuPgITq;Srd1Mt41VF>EX~SM1b=DuGJi%)QA$wgp;Kwn&po5 zri@E0J9jWQcUZc@oEc>~Mb)VK;j6+44e~bDeY2b+d|0;k=E|kEC-HfVIvm!b#T~RR zZ*Xug4q$?|Qo5CN|4^MGfo3-1qaNjk_p2~QN0|VyKl&Xu|3`vs6ia5Q#*w!T@Ts5i zIwm&dSY*7#3@ySFh{vv7t#cQtU8h)ubENVA;h{569#yPQ*J&BXc1ANhvz={P=|@@% zs1r7H2Gm*yip}9`94IU-06UCTEerw`5RB%><;e!-?t8pH9Mh*o2b}*hQb*)*1Qcx< zCt)iS<5A5CuLxiCFvumX{5H7TRA@QcY>ljYv_)G6;2im{jQ3q4Tf&| zP=FN#OJY#lfnx4o>z-4O!LYf`MB+wS@{5TKRj6zb9IsDXx-A|ur$qOi8l}r%M=4hR z>;^%fv5~{zwL{7p=GCz^H$Ex2X#bD4x^^*qa%=FE&9=7pt8~g}u#BQg*^Hr|mM=De zDos{eTLi22aBQ+nR(sHz!m@pWpQKGJ**13PxANnV!$daPBlJiF$=4l3(6~4VFfGE~ zq{C&e)KZe}z?3nK7VqoE7>%ah6_=~KCZH3d(|_ZWgJ8N!QE{tX(=MX0gBZy_Smt+6 z0VsY&d7h8p(hQqb`K@?x#8<~!#P{=iRQpQgs%-ZN_~|W!%owmHU@uc!t0lv_TK-!O zG=00Qy~|U!f_9IW?W(NByj!NqlK*I#-(L}2Nh=%>HVrHZ8>V6ZWrmn#KPI5BQV4^hAs_PrHA+XdRLaGPWuZp&B^C~ldYXLjdbOEQbN97Qwnhr7$W zpEZhErjy*%ppKw3KB%+_Gef=Hs-AD!)_qP0g#{DZ;scia7Ao$7i^2xtZQ$XIM#@E8APis5E>KuN6$6H z5Utd%s8s_kYSp`_b==+=^}WAZqgJSbO(yE9eS!b9%x4fqpho#Tv7#oLhqm197-*&Y zd38(i?AB}4_kN8@@m{U`P+56Vs+xba%x8Jen+d#;pHN;t-u-oFArAcl7`Vf3a$1tL zVUp3_3TF`Y+G%wKTWOYd9~Ow*(z3#^wIW+i;G%rXP{i@Hzg)mC7x5w?)}^y)LA~RV zY@t`=vZw?w{C*pP!n+TGJ10wH^gh8>k z*MC}`866*QThzEAlpR6Breggk>>+To4M?`E`-sru-FAjn5Th2u=ost8zzMl7>z*|1 z5hCcC$`ryfnK-~hkJ}vXh>j3i38w)*1cWHn?kX_J6k0OS?Xy^C5b_v-j0*Lekp&D? zNyUTYxe1oD3?_Qf?JWyZIgA*^+aM!gTw3wTQkn*mdbeO#kViL;GhdC?zdF0XSGZv8 zE87hN`9Wjc;A4`_R2nJy%x@Nwt=ggqHmMiLIls3oV>(gyFs@^5+WPTAMXC?nL^DuV1EgD#LQXEEzCZWRlS|h%j!ygs8T&gVs;hYHr@S_a7BliCR$0= zf+RRvDNH)Du~HbExET-n*BN82Z)SavxxTrvzv>sa-12IGvTnI$!jv&TQC7&bVp)DV z=sF2(C;w&nlTq6|W`%u;c3CiDmN~hGWiXr;G~VrB!o?CTM0S@-AXE+7O65%UUSvX? z+vB9yE@7XGiOrpw)O7RRrf4T%O`D;O!Yw)_lreOU>2by6 zNA#>!@6ccx>h=M!*=gtlQl|5&DQy_B>1f!b_+=4`B@hpoO>WD$%L)~*!irB-KgX?? zBnGdOuyme8bS^31enI~UBk zP46}&ST|EXXnACBQNaZBu}1|zGbx)4hG~VMIW2l)HR%rULjO!QNEd7oo+KQQ|K{0N|6kL7<_7Mp%}V@X_R%g^|-4s>tJSsOQ~Dtn_&=XQI}F|a+=gIfWML&3uH1f zR-4ya&Pk1|fCg>yX97_63wY*YC1&-)uT4T_;Bz0NMrGLEv1x#DAM+byM98!_BF|=a zvY(BTojJvl@|blWix}zlIIZkO&dhM(q#QD+jP-nV6>FW#C~VzUdW&M%bt@&mo7p9L zzC=z^FgT2}uX{<)fEp!NJ0;Ta(>#J96cw&$&zs;(VBoR4mQ)u3I* z#5R~EFQRqacD_%are<5)+&$EJlLFj=FNXN6iUW`FYarZ_)HWTGE5>{*$c(t032n>! z0|`K5Jnif?U~7dxufDbiwPj;Q2h=o<`;k3-*@TokGDV&w@G}ye0OR5xTeuTdvCYIt zfNfsAaiji5KJn7Vt+>13OTLSDLnA)bY}XZQX8S|A_ewIeaFn&AF&jGfpt}dQVR=Iy ze|Fz#UMCM);XJ)PO`05NSpKvA%hVrFNAzDKX=#a%9`|uf8a8&~)nuamWHIR>x>kN_ zE8{9ocroASeYH{ncdCg{UoV61tClfLb>FwgY26Cp?^NAjoSn}gxJg`GOAL@2KN1_m zkU_L~*KJtrX=g8`s7*PTEeCH&!%$4VeQ2AKJ9g$FqeT<-P2c+jgTP50Zz`c^$yGZP zJei<-3h%1s@Wne`U%6NOz4ISa4vdVBKZq@H8U&<&u#rjDHyaAj=4ehyS}G!jzIkXc zOALLpK>}|Fink0_T_up@n5_7~%h)`3YvsusJ6}v{RNnN4HPSxDFBYTICr+$lG6bdS zEeiD2{7PXS1tbpP78OU?l{G4dmWtmUo|xv7Pjcz=4!$UTh3h+2R}nre2$ z{P80q7PK7Vid?l4{1*_HjSE5qfI5OmD=?8(vysjtb;cbjyA(QwYxMu&4!lO1I6<-t z%m&t4iRWX{_%KTmh+Xg#u3!}?g(U1FSFk(KMFgSJzU{j0J|)y>I%(As(5=RdV;yOH zvAF7;g(O%EXcsK_#UxlzUCgFhsRL~? zcg%IR!~LZoOd>vw)WxMnAX;#s6O-MKiX$L9kTaQ&8DDhYWNAz#D6n0Bp&O_quA5Y7 z^h-(7sx+2*W1x!iJ9S7WHG($Y6vYCwvr=Q7O&N;rr)Ox}$5?#G`vKA0OLPnzPcRy`%@a!A(hwT5 z=H(4;iq6Pe-&}Q7b8L%U4u>#BkH|WgBvp;wuPyQ;eNwDq{d{eeH3`fs#Yc-!Bk~Aq zYmi*T0`4*a9lrjwglWgD67%_^8p$i9$}bxlIA9YTYvRkF>EI!5XjaYp8vV(N#I=^V zPKg_e#Eq7Cg%URviC0_VW+lF&NF+*830w8&hLD>4By$4k>PJ2EHOhQ#@d@0hgxBlO z8@m71Am;IA^rW$;(s%05?(SPmcrs!yOKzc`sq8%l?8@}!&S#S{Q~|T06-BerlWf#Q zS*Rl#8dJY^3X5az!sGQ~>%j&6J~m z28{=|WOpIr6ixczj7%|UtQ02DTQ~p0U(F6=FES0@YhIGDb>fHF#^6-Z#B4L`;40G?MjkqroQ5cwg`rWy%Ms;;I@4hF}@>tK9a#DtCsSdHK z2gl)MR@P5v$KyWFsNy_DMJr3(t=uEuqkG6i%O!_+ z03?{`F4)?w{0hDEMKrg0BfulWcy)?g>z^t7BTsY?i|kOSk&SqA2kNpHdXLoz0E3%l z2>=A-E62zh`?mWvX8|t(waa?N;su0$`zoqpK~@gm_5(dRWaKZqdRzCe^iiK<4&i2n zoTx5|5?pRA{Ta)DvinRWmfP=`*}MP1 z>>#<-4c}DK;lJgT`cv zWf#0Q^Lu{nlZv_@V14G0+b#U;^`x7#<`xlTc2&Alin(5NYN#^RKmp8`xlb$xA#)yw$>>*2jTD>W1#ud|B!tYYP<`_%;|0+)8Aq7MV zF-QpPSCX~pxD){=$9hwKd2A5%Qv=ui2jk%;Mw9xvPJcGI9jM2(&^|vIzM`gV71kM` z9zH&3AAci!jEJMuD*ahgemo6}kkYjXv2l>r@BXDe0302^7T$2sfKb84Oc4MjnvR2- zj1x9z^~-+xhxH4+oe;|IP#sQt;c+*)&D?I)gbaC{mD5O}#3%9Kzy0eB2hWh8?^Xdh ztf^S^l4RmZ9D?Ez7{Nb?o<=$No2qBi_4Fj*t>Xer7fCq$3mRrd zT&LlYDh-cRX?Ubc!#cg^h@pBx8V(c;^aSZKJjg5!&~f{!pkm)_2^CK{6(i;4tRSWc zOxV$SOR^D0nFt|O2wgXFIzG5LXb(gGJ`Dq+V6TROyH#mXO+8v8qyvid(d{$*d2RQU z*hpy5>j5oY77=XwJ#Ghnu?NUob{WIBo`P33_nUj35h>c4`SmKnN;OgE4f^xyNOU=s zD&ac)*&5yN5CnytyPvEPZUEMxd&T*H6(Jdc%n$6z04#Av-pturGuZCHvq^uhEl>v^w;QqqKH`hO+pWAu&;oI(LOY|^5gI)$p` z{d~tI@{wlc?esxnFhu^RlBlPD#*AV{QumR)JGvf4y=*R!bv-r?KQv1yRIN|s)7CN0PG z42R&lLYYd|D*=JqerZFz!UtA`Qc*2BJm9FF5x=+T>mrqSq?q32Fym^!cH~oF54~gDJ#k)bXRs{oZ&C+biZ^b?USzwO!v$Fl0SB@5c1>s;BCUk-wJ#cfkP0E z4{kY^?3rQiBX8h7j~?@p_*qCT5B15A((C92vpb zX_}$n@V6R*I$foCiu(%=CZ%^m+t6EUX>`yKoo7u*FR|!vz1X>?ddP|RlI@*sQ(fv$ z*B~WnT9b-wTla3&p@pkyA7cf+i+HT{?(c}2l#9FdT8}O_%M(H~N3#OUTXM}gELpSj zj!NM8Mh*iE8EwpLAOMDK-8Vrakb}$vBv950DS4yEs;HJEH)L(20K!1lYE?XA5sIHR zT7Z&y04o{1O}cK~M}TPcms29u`TWs?lKcC-8OT+|nUOHrKhOgPmP)kpv$f3pCosL>eu-{A^~Sy6%zM=Ia4oVQ@zG$ch9L1VjBq*c zv-bLQZDCEQ5a_3QNkxEOm5qn%(|BQ;9$z?m-0_N4ZZ&&9?yWcel!g|vz+6K7hGpz` zr6m<1LQ9KX^HCb_&KTN2i-Ze5j2Kh9baO_6(M1% zXKSfup~zI2EYNMG3UoIMA<;S5oX?3b2BX_BQhsQUK#ul(!_xl2nyaUsdS;e#;R1PM z@}z#Ld`SDgeLEiJ3tr#M{c2;ZmbTB*>hhz`z$bV4?Rh`ziH#G zO*=it8cy#ue2DeJeKh)EUYmc=zK^${AQV_)`Tcqt91OFNex#a?=7iVC@M{q?AD1QB zY;IP%q#jeNM9XzHi`L{tGV9uHCcwX^Lk@Iou0)Mwb|GngtG=r1Yubq&Do>q6)l;XI zS=6AOc52FwMm{FsJjkh78u6Tm%bY$_Ser!4)eRnslh=#&HOSf^Rm7I9Jtf{D6o$WM zjqcwFxAN|WvRZ7?35|5$KxzrDv3lL%T(c0SD7bqKuUVqX!xZPQ+3Q8~=~SZ5UpJe0 zFm5fDz7WR2UefSpX!?IE0Z8}q6}YwIR+-cVFzjphD6ZAymmTH{lwfsuE!l{z%blw` z`3jHx$r76Q>AU+a+#0`#MnB#Dd75ka8qn}b)u)?`>&0YDEjsMg~7w!JZ*zt`LnyGVu{#)^5B^4sY8K$l>}89#^_*)skbLk!?Fsk%>W)yM-fIZ6md7iw$t zK|3ylYLXkMWJDen?^#n?#;OAP%NxHT=-Jz(Tr`7qo|u&tS+ll3$WU8|D26Mji?q-H z=W%A{kXQHE3?wLc%+$b4P+~X?2YM>_Dec^mCZjrTs>%N?8y+ktG=RJWBa;3Zi;I07 zt*b&Ha#R;=o3$#rC}&H*4HGjyFSog?(V5N~^nHeu8S{B-($Z96%;&8Q3(nxneg}pd_(z*5g_VPo@8UY zH9Mbe?xK*%AgN%RJ2W)cXmqYYIf^HlhByXHb_`vWsiDLca*YDv2AxhQOW=WLo27%f zoTjKN{x3@|Z*1fopd@XznkYPtjU;3meFyBkjE|f-Ayq7kKS3|L#oyYNf9}hu7K&lS zR_>p`A1Qe~+5L8u>R2biy8v#zA*p7vHB(wdL;JthhCAvU1pZtVc_`5D&mCm!_b zu=*-+PdnVJO1QfbZbnE0VpBysykkRt#SZ9?zO3sdl~KYbea3p-%(u#WoZvX`S?C_5 zqAL0vDn?~+wGQ#o^&7>WazmHsi&|^-BcyZ`8DA|}JDrWwi_S+sn>3f;J1$Pf8gL9; z5W6K(o`q;MsAu`jpT*Jn>^?`z*<$L=&~mM~V`Y$(Xu(|-{MU{Avj|G;-Bk7;`UzcnXjWLZ?E+?b~5Ts0b8_4!+O(%jyJ7v z@dm5u4TYu#Gk@FYZyR`HUM8NI(Wu?w&$p1961Qj`ZarSjEF3< z%VSu12mq*MZ_+n@-IwI+cHoe1mhx{>S8CCD$2yz8*1)g)tb%e1j2M^r;Wh=MyyN{2 zLCGi)r9$!$O6RNK@ejSg3Z6Q$hzihcno~L{RtFem?5=2`Sz7`Ij&T;*%K`r0BG3$p zstNX?i@q1r#N*W_{^sM}i}RAEMq~}EOC~Od8DOEFY^ZeP;YGDVA1~^N z2=X~!1rL7s1y(S75gbtK!gu$-KnOnhu|-tCiVK0RF2f|W7bJ1P7PzP;CJ)YfD;FT~ z3lLwW_9|uHwV28y%z^l7NPKE>IJh|Ip7_LY*T?c#XqQG4{e+~AGEzR)k&wfkWrlVLTLL68a~1bV28L2N`xg<~nZ$hu^XoBGF_)a#pH) zK?)IIfkGs5qk4YJ`-ZVJ2QSi4fd*v`ET#pGa%^E{&N$55!UYDo0)CYQ?_P}j)8gBC zh3f*9A6X1;B0lCD|IaQ)O`flUJ#)m=SbmUsmxvfuR-xiT^BWZxqcoq$KX_rPbbkj< z5xK!-uDSn54q5nMJw~yw1B3~bwNf?ZAOHFsx>Z3EMs6v(;QjFsf5|dTqbWfs2^p#Q}_Z&lm$7`8VPXYP`~~tG{#4 zXPETRJ+h&64nH@4Utyu&k9v-i(R-z;F*jJkEYi#<4%YmP(!;Y`_F}mE^p%ljmb@zxM;`#7`fmo8{CIimA=e zGtIPVLNm%WSvjGUlqP9iB`JddHtcXRUxTw|nkXBmbTh1svH@xA^2R!&CYG&){!G)% z)wqPoLmiE25r!0KnfYn!1` zhk_bV>f{(?i^x$kqyfK?gq0oKUx`E9wC zRzwXY!bjZtz;M`ilMn4<6ptqHaGswoBlb|-A0CtZpw)3ee~outIEz%kX~`y0TEic5 z8qJT;3O7XLR=8l`7_D-_GYfD5I~mncxB!N$aKTtMIX@TRF&&1?c#@%3vvp{y5d7W6 zLSTw`Xy}|R+_+&a9G3mEyN(bl>c=X7mng(m*buy&`5_yEHN`M;GLB7M*9B7|Xpt&* z!?UoagXRl--(U+e}w;AkPF0fHp$hFoe_t1oAtW+q%$(HD3js%Jj&^;KYm&Y z4_(h!T0|dr8(D-Am#A{04QLaCSe|4>WInB5A~HqP837#|Y-G*tO~DNTIMsb~gd_np z(^IE)a1Nyq8>~-$N4-$Nr;{G~iEs|Q&4T7;0i%1ED2m!CfT2NI0Gb~H&6q@bq2{S0 z@S1OT3#9MVWJF&O)EDNAy+JW-%Bu(STk{~)I*rwBF!2>TI7XnkISv0vv*Y?E)%*l&iDOv@4THPQ z5Rf2)Edhnn;xDT8^(0;v^I<8@oD92vCwi4wndnVQdYhEn+FvRucY{eeI|`0t z`s|>CG+T;%L)+?l^ppE+lC&F)@^;3jOt`_arQ-Q9{jBOw?$9zx=Bd*nbF3t~HrSv1 zCpe>>56;}WLo=%FucWhLz&vgdE@Ru05xJ3-cPXj}{vrD#Nj z0C{Zc-4)D{nZ#HHpfxA90lK#YGd{vkab;y?#S9Iw_{yF;w8E0|*;}B~8C!xww zs*F{|VywpLbLlzQbQM1zS81DzOTcKUYl-6!Zhl&rfTlz@S|gWY;j=6oldAyvtOv*S zYpE)X`5aV3p#y4&lqVVCD<1*VwPlGvcw`vWHq4a?v~CIKkvAjuR-T7od5 zNaU?=L?1DKK@`e0bVwhhHsK3%Kgqy>J{MxY@sZgHb+ z{LA_)cvkgwbq|I?&TK@!iIJR*dzUvZH4n4F`M37PQ}%`Y$fpJTa=O!6Enuh3-ORj# z58JCb;VsAb)a9^aH)beeq7_#&kl0~)uqO`c`|xbM7oLYDNhHb{l@A0Ogz#RH=z2GD zm`}um2oc>JP=Si`jb}-gXteBmY<5VnL?vZ2t3pY+85M8-aSx^2`IGtU-37)ywVEgJ z;}dt#PiV)uh9QJ@AmuUG$90-&rRVA`b3_4a!X_YLpC`+%R3{^zgab;;t~BWw>`I}7 zaf{tLvtYN*Zyi#FtYXEv$;i#U%Eedzc?h(0ewLvBl} z{;bfU3G9bH?RBCdsx~l`E<>Ym+w)w8gB9yuLS(F6*h{sK(UBUTBkYg7Fxa0*OZSEz zJwL0SOP!1}gX<(~)TW+Q%Bgo)Bp1f2Hn%wT$3OhBdV8qz`HU6Bmtp?njDc-T1#L*> z^3d)$(de`$4Yq~8%bs3Mc8$So2Q>4Wu%aMOfD!FNQYR3zNu4V5LH5I(SI3`epder> zI^J*@M39o_L-9SqSyB$P8#JZwwQeM{u_svYLxWodK0HfuR0kcQr0wWIV1z;yk`JI7 z*y`{w`S1OsVRCR-OYg}7%XK9flI}7g%w7yQHheDx^1RRrnHK`~^CvGJ*yjbzg6R70 z3qaQo2ZE1$=HhWsWq`O;6QG(|yu^IVXXl6Dg5(WCRg(i6jH}SqPPjA9+HFA05->lZ(m$79cPj?N; zyE&Fsb%;mUATd|Wu{A07H_m+|FXXf|@nC{4%xF~5>=w<_zW5iz;~vc^TN55Kr;3=4 zp^)q}qo1$#2Dg?%SCzg4a$G!eo^v6-t`ULf1u*CLfzdqc)Q{4p@u(P`QfC#lJ=~f@| zS&;`7@`%rKA(JSs>WcY*{MuLM2eJhafV;3Gh>-^*`6vPDw4~0WV6qVkKt=N9+Zy6N z`_B-jBXgK$k%Jk9okbAIyoF*H2BhXDiW{&=D#Buu1&&CJu&Jbu=D=u%&w|lD z9Hta=3^?QLy0IM@o65V)fSUO<%NiZceF&^z_JfI`6$y(O#G#roN?{&jvmRW48pddB z8TBp5moj1_TYUA8=BK!@s0qc@#ZVbmR;;~UJRmgzabS*s@G-qGBswh(0k56sC@iql z^#!kh02>>|s~DHB9P&Pw<3dX}GZhVT(9};cJjEKm$F1mk4jI?J0FkiT@t69ki(YlkfN> zj*fp(1Y`~G|4HKB9{7pxAFeL-Rx!X%sev|mo3^V+B+{Q#0F}{fz>;W|^v9I0zDKf> zUg3a8B}bzD+F#C3v=>H0QEO=J%>1oAS#9lCe(7RcYvliEh+I-2@=lv|mhW%1_t7H1 zK&-q|qg*v-J__9DX`sY`226BC?nr`aGD@ci zB36P7q7i8WN>T=qD8bWlr?qcr4a5HP?0-sjwpS5>Mi*#^wAwOseU zd(J)goW1wiUuU0vL`?5~WN~7ubibxK&L*-Dwq@&UO9M*lniZV3iJG2cFxkwXz}5)3 z7-jMj=1>AJprkRQzxag(C@BytxHx!EUcne+!)Pq%C417+D5VM*Itw;-5pOWtznsU6 zPv6s0N|@P40=5owjh^O1x)0@MONILdLUDYd2#D{A?Kd!czkR6W5_~}tb52i#v%0jY ze*1G5h;J#`43fVwLR3o`^DSp6x;jUx8Np_Wo(NuR-{pcS8r;?xqUs-xx9icPIMwW? zfx><1x6B4^b1hy-nVbLNV(<6xi_bl}hOB#RMd%Uxt9Sk<_N*39vBBSd{m;JSpf+zT z=J)*2&omWL1AUOR=QCrYl-^_gbB%wN`#SjDJ077MAT6L9D$>&bPK{{X*wW*>gMnFj z>P&RNv(M+a9^Pea-uYbPyms0adkG{hM2H?yrh?08kjwVL=xx~XGRmZhO<0%;`c4xv zMtn6LWC#M=scrxWRYZSd#@fVOY81x3U{Erb`6a_jn!1>2voRi&CY5Uf^Fv-n^}4oH zQ19fDhPLU2>ID6%d8O60s3!g10+-TC@Blz}Sfq%%hGBkA#E5MAS%+G*HUugMiU1h; z6Fc#bODynLytlb*R0fXI5}?s+jNddNup~etQo{E`R~qW`yZ+O1d||1+WTpBng1ge; zgAVabq4THTM$kFCgmJL~OhpWdo2-Z`-h08{sxX1H1s6o8Nu_NS_t>tA7+j7qsOTsv z^amY`L*f99Co4jVK9aRmigVvqYPEEGz}_x5`*;LWa?JpePN3|SQDdA ztu|l~<7!&6YFLcZfc;s>_2Efp3PtGPQ-XvPhNBBC=wKWyy^I)GxNYDOx_;FFx-M8g zX#cWWKo@JL+MN`)B5B_iv72BSxo4O-7BHa)3oubTNzHcBv-4k_695az26-sk(KcFa&iW&s`V17 zfAnc@veMj_MJn(LJzuQBPd(cWo(iiptLb4)Rg1wUJ3YkUDlBDuY(Z5E5e8w(4o_cH zA)B;gW@JR!_E&#)AxMDw<;atMVhoBY;nXh`09{^_U^-)}PL#I0h%`nNOz@tmIr;c? zip;f15%($%Pl|n-v324K_b#$au0tg;qeaB`d-e4p{^dy-x`#LII!n8Mvi8hmD$yT?!k7%D$r)Cc_ASN>T4`M zeC_H3+m&Z+T`~MZva>f z&VTvo3nHqeS<8}N`ojRD)ftB++*dKCuU*iWhWHIrTT$uK0~O|liFq@Zk-U9d!yG!F zsSUrFusl{$Hlls4aY)q?GNRkWaU@23ze+=RRb4}jg6Gtd!D>xMmfM`cnWcWq;AALx zA57=S`I&5JvQFB}ig9@4XbfMa?F zMI5cwI}++0u01;xp3T*siDim7#p~iXq%-N6*e&-17q0kR4SSmvS|;TdwWMUOxGe`B_+IZ404!MtC9x?NP~2$OfZKj4L!Q?8e50 z()hgkP)(c)*Hme+w3Q@G(xiD7zj=5i8KB}oY)}n#ehKKk_roiJ-m^QB4=zZQm6l~dD<)X3Za<4Sm5@Yz07p3wO4v6bX9UC}&kH2e6omLpi1{34Woefbo@@GXla zUm5xveg2!}*}zK4s$p8~|Io8F4I0fJ4yiVuIVX_Bj39o`&oA3qK<-zHC#46@v z;{*TcSyLez&3@!r(;*to9$%h&R03m9bswq zne(JZvw#1rDH4rlXOAvBiB_&jG+JH(cukdf=3JqXVf}Lp?2QX0mPWJxuo5G1MLI&G z*>^u{0<3PaKE6CZUY(B6==0l`pEu^=FdxFRCm$PqII;r9i-Vnw?RFya|CN-dc5oJb zIJCnIyrlnbcu;GQXn|3_107lACz=F1bHW?~Z63;5^67b>FD!1pQ{Dzmj+VkSDcr>a zqBK2+T|9lKx3ZJp+ZS;1`Xt<~-rZ zoL^KYbT|@*{;QL#xBwnhQtid#BoTRMw)-!RJ9S@;RhiMlT!6lkcXr@8%6RKio$05> zcd(wwdt$wRlBT_)Y#x8@x>HA6ZK_G_~lD4)31&bso>;nmVfpjWH)ZQi*V zJLQX(@!vJ6kiaZh& z`L#{Itfvut2A^kP9k6D^{>E^;6`@r`6NE#kbkJIa{og_2 zI#ab{$GAb=;K9z1vcn1=ElPJV)kbmC+SlT7>j`E;8(!i8iiaVCfjcG$)y z^!UlXssLTY&G8BYSA{AHoENsHZDS;HG}bD|#W~4qO0aPV&ZI{FUSr6+c7UAH8{6S^ z=1uFAhk*4yk}SppX~B3G7mkr69tJ2S=}t5=7~By}j{)t?p3wB*aVty|uHX1Z>-OSz z?%rLzi1XRmC%4#3z@$i~?a-L&&DZQM{`HRuSfLG$Y%hMA2bR1na?L!^nPu(B-Og6r zjoHTGq8hqEkw5Kl@TqPUNFuJG5HG9Q|B3;Jx%v7*$2bWf(Nw*8U;r$h_`t)n<;mwN z=nu$dHd?}K(SJP9igFX=51L6>>XsX1wVfz~Xg0VMU*TC1myzchfbz*PQ`(*7rw3-Z} zTiW6dAYe--7nSaGi$6jLL{TZ;MMhCLmQV_)c8ZeXKdN6D$uQAja~(TYO-IKer?1*Q z#bKUETl)nWg`X6OVz1Ew@&Vb~2Z=DVW6*Yq#T)$GAOsjP6K2uzkmK7fi;fpLZyBCRx~cvU1X z3D2Lr|BoWy2t1zLq{laxkNI(nBI-JN{D!a@!)9McF==pFfKTQ>E`64ux(d-`w&@#6 z8e*-38er6?*?`bh$7o_QTh)yMMp|@uCuFcVwH@g-TU3=nz88EG?8<0 z=#Y3zjWzTetf9_ZB5N=pbnWe9_V!nth>Ro~oXHrAp{C?b9IzR9HDHlSqjR{O-|EhJ zcHpIsZ!zVqAf6N<+sw%m{&)W24tMGWjkrTBY`n^M)zmm_}`t6B(=#~vakFOKD>2)6vfa7IuDhql_O%d;DDO?ciJ-(@+S+q>=+Ce(OQrO#?8Bh(V^A*-3N$ZLlADZft5KbfO-(xSdFKz87%^jx?m!KHc zd#8b@5Q#>wv_7_1V04qd&hTAKTUXXAi#l zEtb1H8y$@QbB*{XHVs;vF3(VZ^efl>C_0BVwV$1WYAY+-`WIe6*Q>@TrLmuEG{#o9 z*ckk*Gtm{8o&pAwj0qY{UppDbIAFpuN@nKX!}TG;-r~@xsXBVKNQ?WcYSS%=X7CTo z9mcm9UU^7L?CaL)@qk+Qv2E&Gh~W92st-T7M=Inoe@c^Au&b z&7EXK(aIAxskC~3+)dNF+mR>FF2<7qTvq%qyNjD&D@!N-%hRc}MQ&y=&5_Q@1oJ() zv<0gxeiC&vO;S*z2jOVw_!^CM?37~FO41e$r1+H_ADmokAM48sN{i|1CfmYym;dGQ zfNctnzz8mAyoJ=1$SYB)$gTe;Vi4TpV76~VRKKbDBBEk} zHj|9!zxlzz*o_aqWiax#`*c`186TyJCd&Bv7PE%cSu-K6-*StoyjSmdKGbneK!<0k z->5;NGJ}yYKNY$H>dT#(612(gE2rwJ1Gw??3NTJRW9SJz% z%Lb|zz3G&2s&xXEppV1%v;H?U;PZhmn z?}y3~JW_rffLMiC=ZU|bpJh~0D)}oTq_AbaPD26lOq)OrVhb3CiFmg$iCc@2H@yz% zix25F`RV%inzYZ%kCM?2X4aN=ob z7=4?$WNaQv!tZnPlMJvJtviR(U_45b@k(WifL5MF=TLRVd+&!}wfgR;*r4Vh{%H9_ zTBar0xoPrjtF-^9af@0vR^+2*mR*34`kgzIHIX{xA4r1`(1GXjTrB*T*)usSNbjr5 z=%9lIf;I7W;lJdHKv<2s8nZacBa1a&0;F3!-YgeDio4zOP{E~L zoLN|{F@|%^a*csLw`JD_Ak}W;BXM=&PsJdGHUYWBF}g3`&!`sW<{HvoL?Q}0`_Vci=+QOC(BLn5p6){J(t6iM zKoW*t-D$|ycCFzO!8VC8aufSzIeTi9V7>7^%>MgEAI4vUa||rl2dYlfcI@>2<_jRt zzTMjUH{jBuIo(s>p{mKU{%kB`_=Kzhn{Qh`%>cvO&5c zxE*&Vp&$x|^0vfOoKEt`sF#7^sD7Oq{Y_0RI)Ei--wi?`A`NnoAlZC*Ix&I5sc$r? zpf>hLn?oBg^b)xk8V)1_gU;$GPB*GU$T#eEglh|DyZ9+zH-N^A7wZWx2Gm$5Xy9z4 z0rzB;yu@&%&-?7-U`d}X1D`D4BMOZZG1Z{!lxpLwQRHN7#yY48mrP8Qp8^p>?+3C| zM{BJBff_3SJT|CGD%9vY^z1?iLwjedahDw~Ki=TDHcsUdyEhtZv}cn2^^E>%gD`|3V!T5SvFoN5zzqHb(7;0;`*!q=jiJc zDdf)323=#Mq90qq`&gY(s79Tx$hWk=QK1;U^N!(4*o-jvLblM~lzHXS7Zk_>BarulCEdyJqqt(er{?ZWz@crq3q2TTP&f zm#L*DFVhn-BT&cpTXaNU*u2b)Srm{u8%7^owBfTu4S#yI4XKA4?`^v1;?eHF^*5|t zW7{7`WxHE?#j9}d zUfe4#=WI13kP9t z=Yp~EPvwq{#K^YvD_WPJs}U}=gN41cxP)+_?Od^aQ_h<0|17#v3tf%)CyX=MrQB_{ z#TIV1VBg4JaTn_vwnBh6ssE5Xp^6LZ(q&c~r3hX{9uty_u#%pmV^VS8It?Bm5(`#`X z1jw9Z$;d$CfMMB~fhy-xm%ebb8AF!KmGml(GRI7>`gD~dy{JLoc*6jzuE~KHhe!R; z0mV;xyJ~Rd!_3Rk<->!rHI#rl&;&LxQ;9)9J0}bl6+0M#_zU%Hvrbn9ctlItB+vT) zMucJY`>=tD+vFf-lQT{moj)csjVRg{gO>S5ZkCeh^yQK$cBu<^yRPI3+u9W=NidSJ zBmKcpXh#ketZm;s+%CRPwlz-NVv3VnnHu(vSlEcYIG8jOUDoPkK2ki{U}3y$xv&`0 zm`oX*k$e+wv#o3HU^R(1KWn7U3j;+*Ic8}_ zw6Ao^KxW1-d7ke23kLltORpPwL{^P0D#=HH(l%RINaeqp2$42)Vgh$ej3!c>__5Od zI|Y9}W$|2qYU5y1qc?1g4yHgK&>5khl(jiTd35)sEV1@$(n6~_qx`Q*eOr2MwOe>1 zCYcSiLlX&SE!%t5ThlNbUK2y%qCX?0(?O`deK2XX+V%#M7G%!4omQP7_Q90a%YoAF z{`)lFL-f>VTd%p}4gje|BqYgL^*OqrHQCqX*&Vq~54^(+P&(W$-Y@ZxCM*O?_Iz5l zDc8r$M$L&nVI~$&3++nAEnoqWuhxIFj;jSYxJvP-v%Rk z1)}BQ@%WH%ln^7L)wU6gc3xuvTeFm^hTDrz=hk*Kv%u0*)G|1=e4_JBI-=?^0l1e4} z{)fKWt=jF_oA%bPSXud2=*bx^Sde-z0=^ij_vFh~TI(l^gw-hi(MoGy8>^Sj507HJ zUivHbF=&jvdg*UjX$^W4_0m7Kk<@xMSug!iuy@N5U0g3cY^Al9FR7P)*jlcYZmgI7 z;xKfl>ZNZVE?rYE{lnpw`&L?96mUALm9R)J(7UjCpFIokb%w*!FaC{i9PlW}t*d|M ztM`7kS-QSn`V}j!L2g66^zXl{mTRRi@;<(}*kt|rdBe)6)5<2vc4RW?Vzc5nLMb`^NHVsQ+cL6>1xBuJq-z%}W9Y&9GckBSuK7V*g#@kqqXp=Ioan5wX`;Ep_R45L(*SEr6HSKbxHB_~mFvLNOqZ%HX0V(B z#M~fkP)hM#i*#~9*93|({Jpm$$rM+cfKP%GHik%wfr$o1D@gPf`~E`%xzK0Q`>qzR zpyzrhE>Rw(Pyju-Bjc+#HZnH#-{m$)o9)4-<3!><~x1EgK zO;e(7g{UxgaN3q2i_DJzkx6mg9>5<~Cw--Ni4LpZUo6m=^C$#DFcD4Mpr7)sG>a0%)g8!OTl}zcsHNm6-C*guq_<)?@-Qe4T>>j zmMT{4j{$h;cIbl^$}{T06VR6s^^8QzpwjW%Tx?@Z0x@+gP*4#=+T90DXzuWALX|)* z1_It2G*1N(u51(M#03P%{2*I=p!$@!A=jvtZHQhKZ&u8ZQ6H>9Lzmhh)t}KokJt3B z^C)#*Q;=0cCDtBfR`M%VYq}8&vjV{+g@aO$$}yhyUcYVm6U#e>bsHrZVxMuLp0n2gO?o zvNplE+ShGTFPO+2hr_4T!MIzaw~f*8vC?O_3sp@<82eBNHzr85WoDT+7ftlOrePGQ zXzE1uFH@HUimCBVr_*k?TWQvXl#Sygw@oKDFb~L5O<6H1{bbr(j4z{Bu#!E11GId{ zFr7tDZfT1#U1!-_?@-^`ofx16unZ`rU5*X=7I&B;Y-Dh7AfpA1wE8Uxhn85Duhnnz zAIJLB`b{o0I%ZV7QnN4pWN2_xug+pn>rQ+hn#@R9mPwuBn32Qufa1zR6{}&>+uWoR=6>qUoERoQnT-JN3E4Hy1zA+Y>b3; zHmjn)$gC=xrT_C1k-V@Mt{~peQ$7SSX+oMWGr#6o@7#aWk@WH9oKJl#SPFT~`gDzl56(`@}XYgR?9f*;N96PMcW8MMt3#cFA zGhXGi9Y^E$>`F)dNsE&(Y$B?y2~r;m3Tlo0b^yn2Iksmq+_K#X+tQvwSZlad7thdC zKiumRYs0-hu`b-x^!jj1(>lXOZ_@Nd;g+T^4!1Ndf7w$KC0V3`K~`K09RSX(c$t7$ zl0y-FiKcmByfS|kz9?YLnj=fxJ?XzE;EWHK7*sHA)PKMmJ3!bAAhZRfg>>J`AkF9? z!rr44`6P`v#)z71pp2vAgsm4@ih)QlLR5A(AwyNPAn5Kag0@Chl=L88jFHy zJ*0cuKB_BK57)}(LfQSHY=5n6Zz!9sJv*y8Ieta!Z)(p@=$>{T(UtAPr^4?iVjZ!` z$Fa%tjRDGQ@=Js&I=zMgy(fAh%?-nVjGv%Ix z?MQZ9_XFHQCLCJi!!@mZki{p89Uaw^$npwvh+6UqCZ`x{CYt#qDxY?v3}U< zqNY!yLzFs}tK6oY>QBtVp9zLmwGwfd!&}qS37g)qIil93rl(ULm@!veu2|Qs_`cGw z!S#QTnp^Q-1)jh?B{4Za`7)qk(* zraSdd19~zUnA5~w)`KS!*6Gu}eb}v#SIA36$!V6`0+pC`velwygLY|8Ua2SEjUNsp zIed?n;7XDTKJ`n^z>a35tf4`4;h8Ipj>u1Y>V2jKCC!%oKQ~F_7{ua2o21SN4A~0?B(I%b5Sngh`~kYpFv@K19i^#GIfh zV!OxFotZ%v*hZ9^>@(~=!s?N9&F=?F>ZE_{`~73XAJS59XyOM=YS!RvE&yC5P7pgl zkNbkCe}gSuih#E1BLV%gXKkuH3aoZ%D9oahiTBm04ac6|VFBfBb!W5siw6In?OC}> z)K<@KFozKfrHK&bpZ2Ut#Yu>g*8&Y?`d#ErY#96-bEM;#L#vYodxnmN^TD%d)jpwT z*4nE?i(r6u|7v!AHcVPi?-j=!;1}TG(9=fb#AkrR--%J}l*DMW_!P64PXv%t(yK`|Df)}B=&iWjDxlvD4$5zG@F)pHS>EU`Mc1nQ`l`Hv{dWJH=2+f! z6UsPIJnXCYBWdzhwk4t}Y;<2{E|(edp5z=s_u!V_l`bL+E!Q~NR@D2`i;}UZ4JZXv zaA|>cCN&WWy8`p+7X%|o2Bm>I+Qkya!TZ!%`BzgMK*h_OY^1=K=G4`8>0L_B;^0F< zYPqBs-oO77dfJZStS^IvzvE?M!*Re$4Y}NL&2S-alv` z8}X01V~bY5cac`{@i5myA5VW+ee5>-Xe^dcFQJPr;#B*mnkEd}FIoBe1NPOd`MRRE z)awW=By}sUkoah<)Z5OA*AdsBD!RhPv301&+A^eYt&YnT&F8L$=lh%drC}W=56nJ} z!v%npqYCcDLP@7Z0TVPop>!~t@s$^_jynsPj37$BaA7J+mlWqNUosuXO61MT2LP5y z6d=MaSq}rV4tdb(Fa=1gd5H5_(~DxmNg)$YwpU~2MJcALR!o=8E=>ii`7UH2;{? z=`|(@hTVKd_dqaA`XwPK;K3ru4;H4dASoRV^8LV~TxkhNV(bhNa~6o16_W6d8w9D+ zB`j_b2;>Onw0J@Z72<@MNpJJDEPGSmxn+P3a&Vhj*z%m-6lq%HqIocaqW#7H1=unK0| z$*=}F60s;`>7P^hNb%Y@pGS-uZ9XdylK3d7(Q{lt3g{6SHC#48cc}z0fsiPRS}0XK zE^?b-T+c^pzqaM+uEQ|aqdmC^(p_Jbu*QI9IZ29gw||J=E$a!bavUj2jpdotM@BJ* zxa`o{3d#vfEBfeW@-dasgy_@SLY|M^nvd^BP>Owy6#q<|y<2kj&$N^mfLANFz6@{b zu!`5|5fI{ph>2~Z-gq_F2u@>N;4-kTXn(H^u`w>Jf!K%+;~SEt88WucJ6t>;b@) z3scrg3G^-g8?Z3m0>92&br8+CB~Y7NK_gMH5rfK#AA_trZDWs*uxhT9D?CLKcbyu} zCyBM}yBIPxJBvRNa_+X-VV$Fwuu3Am;M%Gng&mKVd6vSCdx)>J2wAa)?=l= z^C#->+-}kD;Nj@)ODNVPXMKw0SLxDh*|s!{^?G@W2zV6f{_f~qbC=y{`CD(|va**J z@zEu`U%o0gT|xu#f!^Ih>6^pE;@IStHepb@G zo|rq%*J^cGG!PU0^*-|x>B!wxramGcf`Q=#cACc^U5}|&3lC~J7%-h>g*X~DPwF14@3gL9?y*|gqoM3%D4Va9Jrv50hO)!8 zvN`UH4Og`O9p8)Jc`DR9rz`wC|86`pP=zD^qB2}#p1zSL>Bm@vhp zIJ!pT`qkRFB#3xAJgzkw7cXmMYe**#S=XWz79SaQwMI7a68LJdP0?mHM*hz8fo&l+ zfv|@)lDRYKnKg=qEOvNJft-`@sJSE5l$1lX(#TYu;{?!V{Rwt!TIU(k+yxE^uzd46 zdmgHYumkx*?t|uaDUk~)z$1KT{$&KD!m}AUz%FNlsCsS^lOKp8U!qJicM@nsI*OXe zmZ=bS`Z2?++9`zTFq0md0@hL->$9JBV3P8xnlSbvDFpw3ZQPl+*iy*GtXE517tG-4*R#QeoK$>OOutj}nT?iZ^ zbz}`8s&908bxROS;_UXYwlO06L-h1vL&n*Oy6i7J%n{+?3S52#BO2w((%lOD4A^{UbdBj#jK;K z2U{K^aip3RCZ^!bW0CJT;bW_W@4RMmvOYNx1BYE93HIj%#G4$%LTL#`RO&@RtpP_6OO6wAby(1r_1j2DR<{#`3$4~k+liV3 zzkt*-xUuOr2Nkzrum)@SA9R8MuS50@NO6lOzE_ULVH|F#O}e?>k~r)&J`L`KLncO? zZ-=A}J%4f7$Xd4&9QNusd`T{4uQtCdo0!QTf4-vmHH?{cb zW-O`HGsO%tzQWANl9(A;Y|dbPmzc3UEsIzpX;jg^t#yS)3c8hGW~7c8Hz8|s#^Cm-#%_%^Ha&SAv;N9W$2lJ1=LSqTcv=_q+ z3zH>gN$Z$ltLzeUhHc#ouG zUSmlxwp~3KvFa?3g&wl~tc*9&yR1fHjx7~O!m?NyuO>%S=sBb*hZ$b7(b!u~s`8ck z3B~8->Lq6l+gEWThaR%%T3eeRA(eki`{DF`*6%C~hAgxJI?H=+c#Z?Of zR|rN@dZ`wAx&>SLwii)y4BIlE!^A6NytS|y!ax)s#jA@@qEetaF)~jq#46BVNm}|S z^mi2(VneP}bQO(pZbpMj6;0y-3T+y?kVS32F|?>{5ppapo9SPP)Y`&_`2%cr)fO%d z&Si$Ae@*@o9+3FtMz3=- z7mF0KH^>ZP<&t%`MgL@41E?d%7xgY}-3GOhH}s0uZ8Se?d!~J9kIh8>aF4>S#Bpbn zYI!Z8_CB<$5Ex?IT=DX=NLOv~O5nH9% z*kG~-jKEgtdMZ(^ZRLVdm^|SFJY(~9|KHt!$KUeZ9wJ(Zqkv+}?A7t57f9~ec8EhR-BsF;kBqs&HQRv#(RZg?`1NbP$)YX$_~`Z_HnQ2`m~-gfu0C; z9EA zP6wP!JX5*d?xth(~Or(zu&7e9(w?A-H=OX&zR<0eL2 z(7mEeq4Q$9&BZu7!lvUilF=&W2j-)TU5hZJKfujqugyEB;Mi_8>qIbAaCqC^qOdL6 zA6MI=$)@g=Xv5qk9M39IZ6;|NXAJ}6WK4~V?MaA{a)ldW+{e0$koLH^GeA65)gnJEs@-vpf;=7sD@R zoA%5(WoNw*q=m@u1xyX)WQeKJfq*pIZo*GwtEm~!4$=Zy^({!A0gHVlj`8!euZr%{ ziZShvp6LaQ>7aS0>x>BozCU=T#h4Cg%Z>0Pu!|Oi6VU?e3PuaM2K+sud(lEDJ6gnm8z@+8nuZju*65w4GR!VikHQNB-+vtn5;5o+BEV~ zQK$vK8WX7RwYHdCZ$E|y2m3e^R@mqh5xhal4)*0@A(fR$?GMRK-X*B@bY zfcE1|UqST#kVR7LxM_0l$u7_Krv@M?Wm#0pN~3%K@>wL+>VhniF0Jc{BzaTK;WP?5OPWCX~-q1j-nx1 zE=k?%6Urs2dwoK=By}HhN$NJ_lGH6tE0?5hLoP|(hFp?+q9}7IaFgfxC4)DZE0aY6 zfhspfTA&MCFtvr>E^wOAc!|M9)36C`rb%am;VhDiBhBa_>PSRnCwhBt*rLzK&@Wt6 z)>2FEr;l|cN{___GL%}9?`0_K;}p>jQfhIF!xR-m?Uh zl+_bZ&Qh*qlV7jZTd8*RElM^yYcu}2N;WAMpijR>zR7!`H3_&`uF5!hmw260ilxdp z`No>I+srtL&KtP?JrmV&>H9l%R!6hXxv5M__X{$WgIXJQi70hMsr*S>G(o{Ej(e<9 z$(nZ2H{~~IiK1*VEtv^oSR9xn^L8ZH)Oh7JZsCf_!Ga^dY&*N$Z%mji;$VbZo=UHE zvw7NP4D*MlL_p|Y6qerU$>PU%52oTS=BYc2`{FL<$>L?|r!~rOV(@NY4U-IjS-2)L znlk6DnSh*~;$o8_=Cl#>2Uh|v57C5VKK3R|y6WsBlFBK0gk0_nXUJ%7N6DouYHBz= zfU+Z_8An202SJfcO?eyi83d`SN)BRKS|>xli30V$(N;dC6u4>7@Z&CHF$81+cZLxH&@X=@58{YQRAqyJRDDMIeqgz zuv^5CN!o+DbE1Y$AIp;HG{1+_Lr9KQf}@PzUP%*D!!N9#9v1r%X&*oM@T_tvq04b? zmAQG-;v7oHWM*^M=d2W3XCbBiY!jtvKewBjZEdP7Vv3daGxnF^y0IRc`AavC2V4qm^Q8f4zB>cYl+jeB(9MSozy+FJg+OX`jDs`t#7q!%T(f`2oZ zn_(w2MNAXPNX=il(h!9*UUiFjlT>u*nh~K}o=Pf8Wa&MSA@S#vDDHE#o&5T+AS9t9 zJ(6o2%}L!0Lb?h~JWXtCig!%3z)jp<3B2_XQ~z)R7fEv53rbq{g{pI{-PoLc2!U?GhnjSaaEX1=Mmqvr`f(h9sNVLAr#)z*nyB0jG>iyiOapXANiI;lVG=&jy0^tNzf|#QHG2yjU-Gbt#Z`a$wX9>q9|XKogm61gDCYHkUNr( zm0Atd?+gRE&KKp{0Q^8Yp8)!n$|w>+YNRe<1X-J)gWOEe0X&&UChRCPK}VShx=?1q zjxrN;lpRnzp=_Txs2uRMXQy;8a@JMk9DbWH)O$wGJha)pgb>Ue+|s2%;;{4B0ADnO z-^zu7f4EeQU_X}PKujg{scy?Vj$@KdW3!Gx6UTaCaj~bzmDsZh6oUjIBl9TNHKw3a zMSTl_73N&%XC=TE413u98fD;+%%{mPRGWhQQCfH1&31Qh5kgzN zzwITRD8UyPakB|vf;6=dsp`<%_1r>IS8;(-uB-~Sd%-p@*isd2_5yrqdIKdsHYGOm zk}Fy=MB}5IywdBrFo;|H7nZ4&i~8OexkPGY=6@Y2i>`Q@Lnr9zSZ8jU?Tss*dy>jT zN$3q)Gkl`P129w+WE2)1_f{*Od(67y6?s-l9pQW{o-6H%8Jcz5>-;O8%XT@fl$ymm zEplDVc&j|?PFB%)8`97OgHj9_v6C~(Ry_6C!v(?zs7lhkA){i2pJ5V2lsmc`o6c=k zLIC#thUF2=u<|R(c?aW58jrY5Uo8o}DiymdiEvL=a8y!dou@%K4`n>VE|z?ya#$Hu zN|S-|k_7G^R=rFX?Z21VC|FvsDMRqYC}maR7y_Q)WP9n&41F3-Tr#8SIg{QHGA(fK zk{Bb6RWywuyA``s8OQBop7bL$os`LpX$9joK+Q-pwN(+#+2@%#w%p2?87K{j#K?zP zb0q#D)-1S{<`}qMXpZ6Mpb{7eJlobhjY5~Q$OK~H-=>c2pP2vIA6KG@hAWrK(5w`( zh=x$@SXJT8(qvmN?FDAXDyf}5QY8OJ@1#x=3}>qC=jf}InRO93|Mgs8 z#8+_%B7q#tD%kD?+p2;sUZ4dP-XLpQe1lwRitT)B^@Kdls&g|JX!8yJ>&{Rv#$P`I z$JTDril&(jrmIPX4sN%0xW%eqJy-`J$pTSoZQb%zdF*9pux%nvWY@4gOSg~~v(?Mt zUWS9DXp+p0q)8zxMI8VH@@)-K2pq+Px6E>;ljq?^dG^DZqN`3Nrh{^Db$whTu7f7! zSI0^frsV55r%KI|v7%Yh6J=-84z1fPSkwcUHu8E>k?@Qdv|J`cR&3Z^TsoBe zSufcE1lVtunK|W*JF8l=dK#HBImf&74M8GG6bkY530vdW225XmZC1vwvQ}3!4diJu zsBIJw%yyd_nC(7+G#zI313cg5Wp4~cC}_8M!Rx(%G_!Hi-QWdeNLS-KtAZW59XP=J zRZt=GSBcwaxNv4lu09Zj?I!}1;nI$Ax3bQESOA@*Umh2c%w^D`4s;+saA-G za~C=`u!&g#)*5h2DkoDZc08PnCq7-;foAB+JrIPn;U*J3(FYPb7P+*1^FgT>H{2Q}h58ZifYaL^dy=5z%Q zIA=->8Kyz1VLBo?Fj*m*A|aX*AvqMho&nFcU)}nXT0;cVTQvgfjh~(fzwzvV+$&mm zb}CXLBEN(pNiC-}MIS+B>A1e-*s*z8=DNfaL@FXuKqepy5YC5#7tUgfypw(b`I06? zAz4954UC385kH-uN#79El}w8%;#yX{&IXw+oO74GK6aFRvniMs(m7Gy)^PmgEiiTh zOvUG+p)+xq!2OA&Vm2BW+(pUc(<(`AWH(vyM5D8gbJ&eesONLZ^_HEEO2v$G2=q7_ z>`J@qDuI->P=ws^Qt|?P0fa3y@ON)q;}yFPFO+r%vdIroV5Q{J7Q>Mcd87r~&`;xv zrWW`n8)r*Klv(jKB2A#_+9YV`$N>o)() zu92~Bw>#M#ArdJ9)rZtR z7-Hpnji3S5K}yKM6fu_-2cd`M;1>8(0Dg3YtD&gYy850|p zm_jwdVN=BA%hQPjOmPMi)r5&jk<&a9=Xgw4eN|Ef->GpG{H9ho9}Af<_)e`1zp0hs zH}w}On+s+5O|1;gqAc6Xy#xq$vq_M1f+v&lX|9oH!8hzZ{H289aWJG7tqerbjT|IL zKMownw56o;YqqyIDt;j3hvMe&drieaBdn6;@mWP!MRzM;oP(`oSRmLbF(bNTDP)2 zp^UYHvJBt6(cIrrW};zxf-&hp=%j5xLo{FjmE^=tevhiS$|&B0n@7?8sQb+h5o6 zCs5il9b}Lvcnj}`O9VXMj99Caxb)I*4M(BlvkX!_Uy8HTIG;G)QcT8x+y@ACAWxhJ z{DgUgjcf)cNBDVx?^9$_=I)~lfqLV2Dp1!919i>PG+hC;;xJ7uzfO131Ha>h9Gd`5 z&g3kRMj=nL(EFOYs%@1@dqFKlMMTY%2Fe|}A!;Za1|w5)-=crRWEL8cv59aT z3HM0kG-!fBe40X87o~ikCAm>rMPzEdkPsO&9Oon_r7=Bq-~6nm6Ebsny-?cCcOdHQ zoCZK~7!X9|W{v}@bnYc+vmI@wi6`XH77Xf|L7lMcJS%EvtSXxnVwKq>+#|ZG5Dn~v z?$H*Hhq7a}vLm7FP$)ZCD?1R%_Jy)NwKDNj$p>j_vi%!cT5qA27U&Y~zP9S>Qhp*I zVyhF+kQ&_pNrROW2++*X5i>)vfmy^o8we0$TtI-4l%;4&jJkW3b?dBPSqQB^RA5xH zsNAX?dKMzbjq^8|l;+c_>?v1pOQ^(%5tkU1VPHzY(uP4b$)o37&WOuBVd0L@{#b zV2p4&bLjNs*2s3|V|kZlzSOy^Coc~x0mZ3be|WZ4jQO$#x|NeoG02gJy9Xnj$b{>9 zFph^k*IoaJut+7ul~?UQ=ua^6+tX%G9%@fdsaqIwYZ^5_Y&FO8se;gsO#@OHSgm}G z{nn4U_NIL5*5U(}inr~Z^7Tnv{pjgkx%^F9GvIuZH1-mwJ^Z~Ar`{(m1HD(mR^xSu zB3&c`Y$GbOX}?*T+5ld(`8Ux&sp|UvwQdVsKd=)!=gbRg;iA@f<>X;`k{~L5n ze-QyPeI)|!r^)HGz>Wa)PHS0lf9#b$bGBS5Gq;l-owNyT<}!Zmzl|MwgzO4E@W;j% zb8xw1Dfqu2!CIF9j$CcluT=XvBI8HY?xkzH1OjZaY_{$0#aXO$^Iu`2dpiT<0&oz= zIjN|?c0S@H;mn?H?s4OM9>shF(9I?hcCrd3(0Mo8iL--S!^cSfBZhU+n;Z%n>*4mm zHo$O;I+Q}Pbhd<5*H72TyCS!t*evy|rV(i`4eL>HWc1w@%e5M&+s7%wmeDQSVyqTg zfPS)AEk!`L9EB7sUD>F!y{xjWtg)q0V{=(!Ls?^@Q3LQoAbdu2p|3qveXTrBBISr0 zDDjFGrls(vuxTgv5qU>P{0KltHcIGti6iJUL*M9lF7f8#l`U!+wTSab=F%uglL zXciMPZc${!K8ggj>f{4(g8pNoJmg@LOT-Cjr>p8MeBN3oBTQu`&E4qb;bk_XSsLL? zT#nA_e*>Mv=GA37HC(UF@S2LZbIMl#s8I&L@HTp}X0VlS<21it_DKNa;YCXNAJv0u zR_DQS%z;`sut&{nw9%*ZXe3J=YUe9PgDD*l-Oi_E1+#W!{_Z%735>T{46;tPaV5-@ zoX|gA%-8`uMx#Wz+NAdGXfXW&)5N;X@)R5g&&KeC{__Y4CNwr1d9-(nsS1_jz}l$< z1HC?lnJ_km3toYpud!LHhQ(GnL7F)EX_7V4DWI1?GD z7DFE+R|r#D9H6^kCO;l#GE{p?Gx-!4j_+n7)Pp*I);9xnFu5SInx-f&cSA^S}rwvfjm5; zm#<-u#So@85{mRBBuiDlwcAAj@6>hxmqy-o#>NB z@jjhW)3>(=fK39;r+7S+)zk?(1&LEE@`f#ez)d`NF(#ohih=L7nMuYpi)&2Tv)-t9d zpeknuU>D&p;zH1tv8?9)7Rvw!=W|~(fZ-+O8FJ+C@QuN@#cjh%=l32Xta3e;C;RQm zfpVR1;mRKbYITm(>KwBt50y`fd7Kb_-hAMTg74{a_vH4L7AFA}Orl`&kb~&hyFWe4 zgPC;pUAm!|;{+$xg3Z~M{plS~$W8=5NclhG{}9{eli5hl*2Fu;NrHZn?qo(ffH{KM z&P9W(KtCNVrgpm{N!sq9nFnMG&Z#DBs#8eQNjxOgVm^-VaQHKEY&r~`V?>Sesc%U< z{M0sycd;l%%fx000{MqD>BcZK(gdU)D59ZIn<++Ie97e*e!)6%n^GN3T?-YehUtd@ zm&3GzkuX#-wvL9Yp#p*t8*G}jJ0qh=qLJby*Gx+1Ws+WNN@v(HYIzDtRts+!vaakyG!*N+a&M3+RPR8DO zI%p@W!QIUc=eLP886tL&ZbdABVJ_WWMgyhN@ThJJQIWH#@oBk!s(;`{Ea9#3sAp7u z{v*6-)Jbhl(HUyzHT?%%I!N*3Tgw%kuwQ2_zt!CU})EifXD^!+d^?umE!~z3R`fD-nsG^D~Bi0K#lHmG3J;y-S*DvaIYA+rkCqs3L(TzdN4ik zBYFtr5W`pa9fUd*;iisL1{oq`Q)Nd34huztP~$J3qY1EGr*=;NK^ ze5AOEkc>1N)qkzg_DH+i9-$j(;CC=)SjLEFY|BO&uT;b7G12@O_pKkjl^;xxaXlUv z=JXl|QwwH(t7l>o%qO_+{U|#wC<&`@L8)OmXhu0egs~C72&A?l>kjeDFmAF=QJla& zp2;nwhSRzGU%Lm2K#9snt6K{}j8f0W8b~mZ9<0GcvtqNv{EHvz{|~VV#{uDs)+y_X z3reRr3W`C98MMyTs>Lt^y-b^jG+3^OlWGZu0mda5?_g-l<*oW5cnDCC4%Y*$gbf84 z?oFl*miv?G@w$~_G@-R?bNi!G$aTQTYFvf2jQ5ib4#AnV~>x_9g;t^GjoUfWOn$W&~Ah4iUNSR5E z!E4Th7!kPJ?Tz5VwzC}M6JMX8DS{Wp%D>8Xc_ON0RCaO@EUs_dcfx;ZL7Z{4xey1W zEbZ9T(q$xtr9qMgAh5IEu_PIdqO6K%6vBy`*QGxo$hf0BY42PnqJf)tOwI^cgHcEs zsmGvT4~O>9zZ62|og(3cRVJ#PnJ&?xxE2#1`ZIBB-o~4vcFZ3Nb3)7WC4C@|h=!p3 zkpQ;xUQZ17x?k_LdK!C;r1V~YP%~q>Ub|@$d)=?~n(hkjbbiTRe{Yi9%?A=`My4K( z4$@m+3n?%*v;=eAy+^q1^=_tWh&@KZXb{3?Lm&Nb;|PwlAjtuC`}(s%0Pa5^B+znu zYc_g$#ce|P7mH)EjEc1Xc85HSyj`%b#=6>nhaPG; zZ;+sc@@EJZ?c6FF#7Ka1@ZdK49_qsgo)8aMZlm5XW2k8tPdbtxD)F z*a-83U}jlGZQ{`51o`SRd616XtA4(a2MH&sj&O_HrO`v}{xor%Ys3Asl?MsB6(bGV zxa6udPPL6(MUVcSNGd5;DRA1#4JZU*ABJ#BDLdNl#vIz{K847Gy`%hctU_cJdBDqU z6T`yXNN{tyBt@P5DKz^+QX^a1e<7)nZHiu7kTHH5f6!=y&q zu1kAcNx8OIa)^~C=+e}fEp7YL%P_xBzQFtpci%+j=dADL@SOAWHZ--6YQQ%sL_^is zG91oP_X3Ag5YkEXepw9jx-vgsNZTfTsF$yIZiN|)dbzIRV`KTcQ1ZOza8FJp8>1?_ zSena^y};qVnH($;ial${etK54r?8h^NS}{7xS>)9C-M!!k79Pq^E7?_>}VAYHA~X1 z5Dq2t9HKA-{q@+=#YSIa%;5MnD8z{RY%+H&WmEN6%Bm3bF!uGkI|OFXDa#I(__ z3p0Vuy-)s_S#|U@D^2FMC>Jn|h}eJd7+YC_lY=1<4Q3vWm$d_>Bvsa(S3zVGt5LIo#v|DSaezfzTs!fpFaw+#M%%FJEiz8L2t!nOp_29w|BO z87Vo+XS3Yfkr=gSr*tnDh^}&h#4?46oMC%X94oI1p6>gg^L5_`&+&b*q8yzhJaFF! z73;nao-goy7PjUZ5xIfL8&dST>YfZAv1cA8F)GF31+L#r7<-}_VDR<_O z3jPira;c}Pa|D-ekHFMDK&7{TQ*v>1qRuzY3OZ_zkcW%J>^;nk6%e?{xVYCF{5qkAUbK3$oa><6^p+E-?E zG&4DhqKsKR1m*IJ0T3OhJHh%_?`0*QdmuZ!1-cdyHU*gdRc^xc1^8PzA3=c}LR^ej zG;L34vp4jK5Dvh?X&s*6gA{HfW1}hmbrtUtfSqch7!boipALXI8vyg9uA%I7D0?iF zE$IaY77GA6bwL1S?wGK>QNp%Y3fueow@+HKPSNOxEpb%Vzn5;dEUD|&1V$TZ#&Urh zstsVNBB^-{xPU`ExZo(U6ik~%^Nb&D(+{LxHHXo(d8A1=QJaL5!b5Z-GjFzLN1+wE zpg9<+fT*Zfl?SJJqalixM%CY~9Z*3QF;0m;h9F{NgmBcN`&&CGbCPg zK}c+nto_h1EPBweG-5b%j6_UXh+F{RLLQkG57~wg)Bp>FH(vE|<0xIHgL?3#c`PG8 z1Z-q_fDtY-lQe&aXA)$#aI!oDj`K*EJSS=s>p1rp&T%yD9|_8%&4C&(S->pxrQsnG z>-#(y;?sWt^CS#v*&&MC4iC{*G@!cWrO7vKEJ7gbpFku8JtUeF<3%D>N=`S>C-Ph5%jx6Qu?kMa{R< z>=8HEi}`VsaA=6jH<&G$HOV3*rFB{DN16uq+`#b=9+fOGkGQcEzKqAl) z=(d}u>V+C;(qxCpNSh7nAlV0n9_t?05IAV$<3sR7=cO*X(if=54s==^v=w&i&;Uxu z+5-Y7XVQn-gLOsnCZugv%s+`a+dsjyww@o$rq`>=(QKMi2@H&DiPM<4jR@R4yHz*2 zUVF6p8Y|A3Rbr1n$4>I8Z2IDSJ;>3XoWUj2~sKjm*f{KNAQOHA`tgk z`@Zj*!kxi_g`QhuYJd$x3P#KFi;J`N;a`vtmU4>*ZXhHixH$3&4aK&$^8z8y3_zO- zGyvN&35A_?6xj@oyNZMgkr;LRhpZzyQmiDDc-)DM2?q_z5Y5cqRT}Jzn-Xb(brPqa z@|x!Taz`XhNqvDVhN3%tb$!szqr`+4nCUmR7hltwQE()Fw*o^F7&`i9^)Uk2_i7q$GGPC_r7-ulgtTHYc;lyG21%eo_Kx#7-& z37EWD(lNbs)UsoBg)V7lid~~?+yXV#+--dn{1=X5g>hgb%t}bTa1<+w(DNc{`9oKv zmsER1br?{#MIwM{S7<+ibOtPd??bjFTjf$?%D5y+p~m{*p4w}(pr=S@@qc;u(v~v4 zcv3_hLK0%dMKX46d#VIq@|wXqZPAQoh_p%H;~bu_4QCx+!Opd`#u_;^yq`Cx*HcDd z6K@PQ$jPJ}a`b(Hu4?_Yy?NQ-B35)zd^c~ z52aGZeVb6ds{jw-S|P(b!~k9!ckX&d^)c=E+@Nis*6;?YIvbYzzy|iv8dmv=lpLpo zn5uu#&RM;dD=qTfQz;&L!dB|gbK8?G63j2k*SRiQQ_6+0wy1#Jm2GrHt|Zt3^G*2r zVoBYXs1)=d=3)W1!n9!-CGX&d*Q|H;|rsH#+17oi)tolggSIL3K(%!=gf23Cb2kOuOxdg$jETa{+sFpKl2<26$CGb(m8a@qDk?FVEgfhy{o|&7mC2cyV;AQa zZ5mvn7O#|+y=ib!{}-h;TX%VSnToa-Uv2gOM4U|BSK_8i7`3H0R`E6)l~ytuk8_o6 zDcv4+SdQgQU`bA9O|3)99%b`=<6z?^M!k`ixVc0KW3{T*tkx^kF8qughsCAEiNBsV zm%|D5S`V)Wa$K&Z87@~%Z>+nSc(Om8eo>udm5O;5h9uYV(?DyN7?)hdXY~U+_(@&) zeL5Dx)dXgh3A`#hSu2|lW!Q>VhK*S6-#yH|(4#CZ`NcRKb$t;V&vm^7&Z+A~A(!1o z%Vnn=LUJOq$5USt0us*k?c~NpL8@=@UB~oD^YyhAKcR53e}VClA7D*FK8MkfZp;7c zn630K;W_kxEIs?_kx7w1e;0rbXbVZ)#lJ5L34OTT?y030 z5`sj$Kfpb|X5~IH$YHW3a@PBEBuIdm_0ak{Reb8c1^tXX?FLZfoE z(HO6t^EwS50M+ z%)?DY9-K5$g0gjncgs2T9bvSmMU(mEL_Xo zd+&wC*IzVM9?hks!TQl&jssq^WjLxBE&9wgzEKkc66(~n6E3VC={;jj01}zM(r56? znE9-pOV#@5!_K&2_Y3^urh84lSWTi}lEKAAVI>hhH`e(j2!A~A%~O$I zg78OlPuYo3cDz=0ER;!T>B|yWaYh{EUVPO=_lpC=CJ6@;+6=HLp|N7!onKNXR)xD8FdzR#Q#qGT!6^xRDPTJAPG)psYG% zcmzq(OkKUGkq%Eq@iXa>1!YyO^WWj{^fX^9{?5`Ivpp8W~c5! z7R%d)kdw8t`B294ww1~49FcW6l+A^*`)g%V42m%jS3@pw3DyR23Qknv_nuhCCTv#D zE(06CeyusfY-UMr7IuDif}ZwUsv*_B6vmmeG$u@xog{`*{FjVK(%o>`2Cw}MTS5(I zFd->YnC?gHSxl1b&mK_zzM}c;@E4h4F)XbT`Q=HP;BkK~KeEwfq+eN~@gz-#$zwlB zQ?_nM2AnwA;4M!2zpe4nIV*!rxD6`q(2zzYe=wo9JVicwkHFS-SEV$fAh{3{L2{8m z$*5&}8 z*+eBsFzL%o**eFEA=-EG^XztukYgF>;%(~V#SCcO^o9&sCf2<#M1U~>dg*-(N*Dy2 zVuJFRtA*}`1vt7{pDH$jps+0MHt>NKpSJ`wBOwZchY+=FE{T%jKEFS}y;m=7oPDx` z+#Z0D|Jp)(OvbGc7WuSo0izKlYN{B-|z=tIx}hr_rN{f3Dm(1$Z()GvxXW5 zBFOv41q=8a9xwUp)LZvvSt`3eX1xI5rt&z(HP)u(IuuhAH z>*BcKA&`%EdEU8}022LXPf{ojZ2UJMzvS6AF^lhWVu9*uhWcSe))?k{oTx>$dqIxP zRRq|q0m9^h>CkEM6T7F{S&}GGo=X7J;DGdA^5MxK5Eg5hM)_ImS3{SMFk1CQmgixy zi7bTgm0ODIo^<+>^H1!HG_h%FXkt(J^k@(xPV7jGh5)Yu2rGKkL{Fb|%3FXc<;4pK zpTJFYah#m_1tUD5RvPh4(+*j5bJw@T?7eU8L7GN`Wf zLE+f+tcAF8^n&ttVai|Il|MfK@$Ck)jJV-P}Re!Q83p(L)69C{bnv>W4p|o>kxaq>XYbN8mGxCTGt{SQA!eD z%b*Z7g8exXk=&7gaWa+b3X_Z$ShQ4Z6O83Do=Ab?#do4%p#l4==sVu-b*goh{_Gqi zx006G-@9tMohND;XZ%$RkGsuQE-2yPg5VgnC%q)5Mp|ObKir!lRJfpdheGd<#?z^4 zdH5(tySKMU9ai2yw1d&S9hC!h$rp-S%HiB~acf;B+w~5P3^m=|9@xmSsa`{7Sh`G# z$L#|;Y+=}*{ZhF*^?XKuwgm}EBv60ACs{6p@;4?C9y+juikk=4i+nH<3l?+< z>T6TG(op!!*7P!xT|#%0j?8TpGPm#)D;}-aaOXLa&3 zG;cU2zyOwy{n%t8O-BI8$6gDC$12m4A_5xo^xUl^P5B>+m-96-8kX{`Srq@Nl^ISD z;<9Zp)dU)S8Of)L51E@F?E47M{=1#S)QyKjfkU801Cx9TI^%$ClC5$(W!<4{X0Qea zm@37z{cGS8mbRa&Yxuvf{t1-%n*Q%=!11}>x@@r4*@^(f?;V6NaM@0T!`f-qqo;5i z{efQMxsJu~sL(!H7u69Pqz%z40YBJ+693Z13`5NOU)IzUcW5)mO2yiGZv^`i`!Hp- zwoJ?RUh_`ORcu2LRQ-dVs#vXBhdFFU-H&-f8Cls|`*LUn4Ed~RbSF}IsgBarwt*!N z;%*}luOeb0!YztFK#Ox=w2Nu>0w{Seeh@MTw?pROww8+K_$^l^IhvjC$bmrS{t_KjX}y%)eacZtFR~K`T{0KJgfzmPy9M_4bfSyaELyv?#Hisgv+|U1 z)SrR*Q#d-^2FN@DvoigcFhV_+6hDu(qHAnISM_z>oHN|7&6idDSKSdg``hXRE(Yl1Tz3W%^1YP$V;Z<88b7U=sH1 zS_;_&_EY|5i9Eqc;lF$TUpwj5Bx@FgOiKq}A1tN)$_Izyxv ze;zy`+$YVwo5$F7$?@@uVU9~L+Zbn!@Tg46quO~jDuG}} z0l=19a53%@mGX>JF+>0L0w|*(EA<+fZ!Bd7_FmmGbfY*`o-=ewO)hAbJFO?` z9q*lVq{Lmvz219XnRaU`(O9{On-`%{Q{r}L)-Lx?+TIO*i4?th-tTNEp3jB@?c_JkEz)%nDJv=#;=*w zN}73hyg_w=Q%}VZxVueyqx99=n7?yc$_=~N;HdtPZHLrBpPb7$HA38xb?}7RNANi| z^8b?We2$u)2bi@dZ7!XZKWq-Iow;ux;3h|qswb`0^@Qo&z!>b#xQp0_G^%oL zOsto?YS}KQwj&nMEY)eXa&$y|?E8J=ogi~`B<^~D&7{u6#?y0b#JAnK%3YEa$Bv8I z`tO%UFtN4*0E9sJ%vbOI?5x&8D4nGllYeX=G5M#fpl|X|KP3M`;cP5Ka6QciU6q~H z72)!vu6mKO)1mCKQ1)o8>?HTn?;ol?J30zLp0-T90?ARj_;kyTmCIWS_^ozF>&?3= znXYk0|Gy9ID>k&wcfio#5m~W`qYp(35Lq>IEt4z~DvUfcc3|@j`WOoG^x)6btF#rC z>GKiI+W1(zn9+KN0i6baK?D;U76asmeZK#@Dq$|EDZU-Z)7Q>LoDX5cz|ghXr&YYd zJ`*ewqg+)v77k)FuD#%?zqwV5ArYm{LiD8U#v$vVb+`XM>yl=i=0^PsP=ybvf4>h1 zUNqg7PTBS-9kC@yZEHFqwWzf#Qv7Y)qYb`+3p9;@vl3I_Y-j%UUT{@aa3!})gY90h ztt!~!1)IHKLsc*VQEMFmq?UYxN9TxGwzxL?G${b=6tWtvGmV>I5cOLtG4QeB^=?8@ zAx#Si$@O=nRz+OJ+rKnFt2K=G0TO0peSR@7X*6aLcxc2kb!fIWGj$x}q=Tb&I1hLk zjXX;vA&^tFSPlUROoZXYBv>+zPF>q0YaCr4DLJ~vaRhfiTWUwjy+U!na{PjJvzwwH zc-V#TDIs<~@USMoqHVG^e7MFB_XHlfPgh9jK=^$q))7NJ$dBS_#uB%hK%GFlvQbEM zFlG|0vD+Am;XT11IHfqGG|mwpWMHbZHKzJBGr}0Ou42%-iq6HLL)l}Y?9p1;|Igmr z0NZt4b)NT}d*Ana(zUHPiY3T-7eUEWHgU}u$CHF99km0=#1x`2Or|PT?V2)OE-5u* z2Q_7SocJY{6A_#sz|ev=AcAO%MkWH3L=Zt;t2&C`JsD_92ccSx7R90_f;GK5%B>>&8UkyCn zAI@bwaBjrAm6aoR;sLJf2iYw+rNPbY#zKq($bu7SVYUMq$ymy<->fAXv;Z7)Oj55* zjOQCWj7Mk^D4RFTADY(3@pv7aJv2=<;w7{AfCQ<|X0_?{aF)$#(+vV@2x+7HhA|wb z#}~k17Rbh`kR^Bss0fD-(c)o_%b-MaaE~qV$|&iZ3hR4u8DJl1Ogz| zl6Nw@GxC_hcA2%8%JSPu@G;nt?637Hz5~5}*58%-wBiq1G3a2*QOG8~Qz z?L3uP9h#h|hM&WGWGD)fQqDY!;fHLeXE8kHVy9Hw0unyRlK}nL$D!mU2Gt(dmOB-> zlel}KERkLZfKnzRTyO#1F;*8_lAVc2on=GVSSBLaX&(m#e1(@^Op!0aS60?bKg8By zKy%^rL!1-bW(?C*lYXd4H>7*g5B0@R3_b4YP?LU0?r6@t9nc%@Sc}hCzhxDBELpWce zpt#U6%RHlOc@i8v8R%P{=D9ek%AC=e!Rh)e&&7q^NTeT9ce?3^)XaGLAw7xoLn=Fw ze&{J`e=60!Z;oI}z@%0W%n_v^1-nf@qTy`@x4GT`$7L z8TXN?i>VKXrpg_O6}JIcjNzJBkBTsxrt`!=E7ydr01rGrtA&m2d~}| zGh)G-&Ywojx-pfv#R&Rp9u7@)1WRz9sh9U_x+Nt$q zQP!VuRwVwd#VLV`{GwuU5a5&ad{0^OE{Xf9dlvoz;a}f}iWpr>=UJLT z!v8uhqM(T}CQKA{G}4hrlc!<;r8OE;SDmtYl^B6lF1XpVViGW6UiE&I)*EV5sF&t;%L z{6s()3`r3FZ_Y?J^@2IA+1Dy_+BFBoLBbP8ZARS$JU%`=Jpwa{s+}IjLPDFa3e~6q zoYW)%?xPuSQw`uIZA0(^x;&KB^MhUW{D5vin*YlD9U4EVk=8LfV8k2gVP6=G|E&$R zGdP7Z$$)!ML;<0kXO1&a2dD{Kq=)9!>8~r+2HJcPG-jW9i+I&bvcg*H>~{ zB<z-`*=5=o*@xCOvXjap$4*(_KaITwx0Cx+ZAl!3f=**& z8mQhN#G*T`@+n6wW4Fpob8s?s6)$YsgXcTo;T?HE!>*x)j)g{P!z~Rr!SUh)U1OQ8 zJ5YBu>t~Yf$ugVjT4s`E>kk-K5~Iyk$exhina6sZSON7uXSK^QIEEvdL^92c!%gb~ zx}f7=k(h&hAp|6e(2lNWQN{kz^jLz4$Jg;#ZipNgdomtxC0g`Czisd)F^ z8&X8Qj4g^;PB(57`it&50YanU-0p4kjrF&kpt33^@l1Vw-$z%-S6K~)@287$ODcd>_+6m2|570j*)+=I4Y;dl9|nIJLc}Gfk&ZsC&H|dT3D9 zw0_dU<3RiP2js=zXDSwcsj7cx1i6Ujw%$1+Z-6?%4;7c7nvaEMBi>>j3P0#rY!a?g7OXp~wuqj2gzJVXu(QR?J zH@z1#>Lv?K8JrrW49!@p7JY%1H5kX#unb!tB9hh_JL3{KPwT1u7K8}IB~L*De}jso ze|`Tgla@KBbtV$j)i1~8?39M8VU1+Mx;k7amjfcri-FQnch%SFTaIy#B|3ku(~OrJ zWmeNTuGhLfuZ;qBg4V&e47Ys>!tlA&`iyN>>vW`Y(C2>9B%Crp_A`e2dWNi8h;Ez6o> zWis35Oi|QBu(zCAi-}eBt0ma7riyHBKdb4L4CPly>3ut3iTCj&v?Rx<-;rO`xJ9 z{w?eYcI~Ne@;W`Uv6#y-1&2s%Qf?D4d*U}>0ssda`MsgMwQ6*U*KG6ng-&&)^8H2o=(Z@t@~Rkv4ouoi6fXr-a38-7`0vhi#5>j; zy35I7FHY4r2w3E?;;m$|CYf<%>N?QxC(190iO_lq+6TFF%R*aTryLDYMq&&_1eydD zyA}LA2Sx>-MzejOPv9LQi>-{z@V~(<>W$*Wb;t#X&Z>e^f|D@192e#ChX$j$AR+e` zhA{|B{IE{sp3|AtQ+yL)QG|0;Q^8*7_N-K%*7$Id(1NtTw%Jw?%@)H0Z=+XqLIKNk z^su5@_&qX6{#33HA)=!C2EM}65Lx+A3`NR?$L&fE_EYMJXpA==+~SZn)#!G*%b%N4 z6x`74r-}Z?IKOU^D?+X(XkT%+1JLv9E~{0hv6afTH-Z zaV;%j=1_xR(u8|9LvPaKf=+gt0+0!nlEL@2b6dRuHo~^9vtRqI$ADt)oN~2$i-mwJ zk;#IA)aTKho0l*^pPX)ZG|L-y@e8NqW@@xaPAMn0%-Wy;WkRSoxx2VF|+tvX(!-v~l!tgn{wx5I(;u+X!%=4YW z(mw=}x2pR%%67E)j@bL+5oV6=b%xZ<4#$r9Xu>~|y_9hkd!suo4!AOjwaD>G5W#Gm zI5NNhf+bB{!C84ddJH-qmwXHc?zoLLfhTRhV%h`em|GhWffAj$KPqO?LXGK`!2~1C zfI{4rmd;?iB*0lfB-ucie8X%X)iv)tM<^8z_f>m8&O;m}r+Ctc@uaA!I&(|zZ`Sv_+V{xe?d2QV z``dJXd%kD>cBf4UgK%I6$>(!VjK3RbN}*mabus!YZ%(Z)FjuFNQbSVsm2Yy@QZ+%{oqZ{alrgnolB>tO?8 zN-$V7?MmoIaN}1L9!eEVpZ_%ROwePh=wGg1QAI_tD>aV|y@-<0D?uB$?wUAJ)q^y=mEz6uFVAxA5iXIcusp+$0^mumaB*=QR5`c;P^4EMhn- zYY$#n|2XlC0M?LHtIW8-g|zTtIfVeoW`mC^F)~Q3j0q*jkOgyMJFLWwikw9W%-4#T z)neXb>>DX4ym!aRukmd^|hK% zET5iJHFFw9A=6r{u zM>@X_iyGrl7d_`T8UhPqHfK6ff1r}%0X>DnM<#Idq*Mi_Ybp3r&x1FKJG9u$V{AfK zfGiXF-?M~5ok+PAG1&i}B@{03pF2ya0>vP_#tQzvqP)i}t9Na0Q@wk)O~InRN}j@E zxvcL!dH%&!@dMh#gK^Ch1)2}gwfLDG_15gQ;l=n~8$uwez!8Jp*1`P`y|znOL7xP z^nuB{xJ0hLh97!~fXXlAD&WO+@nU=XqKX%C74QZ8HGd%v4h-I2w2$5pg;0N_3(9f8 zCwHg!0?XcVNBpy9056@BmDN{vw4$}0>yyeLo)fDm%pHL2a+IYDqApqm8jCDtt{f`v zn(>(wbdO>wTC<>)PD^dF@}eL|Od}5e8FTneghk3^=!g4~ z(I`t0E=zqEHw4f+&_2fCJ6G7Gmz=1sGZJ8=E?iOvk_ALh>u-)=tudgeD&qE4ikzsa zYn31$oyn<7GgLHwzWF{AVO=Uq{H1^)deOKXk`j$L;s2?n#;+;WpgfJ9I-B54drkz< zucb8>x&Lx<k%$CA#9c{(PTX&-W7XH~WdFh_Ks_K)bY(w}YZ`)hQ6U3(9Kl0me+ze)G6 zY44$I_TaSl(Bk&;)_kwXo@9fRI+UQbL(b;yp9|YyaMGzpuotxKz{y_yu6KPDVl3i* z;pj`-1Hle;j|3FC+-)Xly=*m+uxr7V(EMT}rESFKh}^S*eE365nsy-rQ21p1xzDef z_L*4Jc71f)vU%X`IN;!ku*wlhGgs(hr6nBg+`{7omrNZ{7e-DFKMUWI-Sp{Msgo+?pq0yc zzkYGY?Bh3i^P@i?Ok*q2#9ip;MZI~M4MAxO6Mc6Kvb;9#yZUtq`N;pYbp;@zBaZp_;4E+kj<0-De?<0EMoRKRyY_ZPBAVf=b(?MtI5@#;i2#*~CuB z06;NDKx-2t*azkAimB^}85n(km!?yZX3XCmtYs|fVlRspGkbRr|2Vp=fa~QAeNcmO zF{;}}cA)}0ZO;OLHluIlw%jYgjTSicqrOV?tr3hi@=U^En$3#`G>f{Sp4WpDvLzsg zW%vrLZeDNZ>)T~w$dQD_F)RPqkROe-8p__A)1L*N>sOV>PTZw@hK1&4>~#bhHEXDc zHSa7v94grT`l3ax~UIo!sH01Wow@4c1o0=PW%*N-j>SMhbb*&RoCS`m1O87V*l zTsD!~j=IiUNJC>^8ooDA{y`E-0mUWPf+M)VD!NQ?0GS1cp=Fpxu*euR3I9P|sOEsk zunVO?K@Pp}$rpIhl?a$Wn}+Z42N!<-CBJW=oH3}}A!a+8we$$mq#KFn0)R?yHj*%i zP6fR3JD%a}znuPpmt6laHOMzEFE*%nM-`%6sCav|w9I}j zLvaNt?i4ji8M#Y1`FdNTq;))^qO zs%FD!U?j$g0Qm5mx`jifMnm=}xYovMVfUVDo?#p+*~$&l1Hx){Pg?xAw)b1wEDGa# z1y^e0aO$oqVOq*-dnZfkhGI|}(g30w9#M_w&THZ{8EDThbH$>YDi-G3^-+K5p566J zaOqc507UC4P%HxFxW+6y`h`*`n;ZRUD?8ixdqC!g$ z^$_aPtVoum0X?cG07Kw|sTdzcRbbLUe`bgK(>G9qV)3*iLEk#DSOQD^#m}s|Uh(;e zqF@kAlV>4|Y)LHo86w$-0Ua(zMyMl67=xzsZRwSQm-Vzox zHi4J?3^U_8C1OD?fflR2yrC4SNl=>9=#c2F{-&U_o&O%VohN!kM7HzKxp{Pf&^r2H z4p!}EYrlzwPSM?M?Yf?LSz2AHH0c5LdTRK?Vv>n>5Dw4w-`$AG`91a7Uy+$HJ^ZD_ z*(B?=7oI;`V*#y9qKUHsEOGUUq#mXz$tfdy>7Fse$sLD{8hZVz+eZRQj-tI-s9Sxx3w^P0!RYM2=grpl~lURCjks>EtgPhtuL z{etEb^toJz)fn)EK4&(=p7=zyu0|`yjz<>lzKD~lz*v+?(eD{y3w0{M16Up|N7mer zl$o+ksVV!4@k6Ut?t|x5e>R0u%hSXKG=uqCvlr#ljJmBrDXWYB&YSJcGdkRXaVpNG zrFYI;nA^@@_X&g>n_^h16PNl$G9b5x!yc^;~R*LsQ*a#1d!m0<}?6OIr6t%OAAXpAG} z-~ek^lt2KKUxB$2wgcy7fmBrBgqu2U_446=&=|d7xt$lto}22tqabaxEXX48Cjp82 zWA6Xg2V^uM`IYg^G@t={xD5?}6zkJK#$}s2#{G`D^H+ZuC?)HpMJZ!S7k@hw$21z7 zp`$Tuj%jRAj&3tj&uN$nBJg`Wxs0cN+WWMNH2Glx=CkEw{{DHed8|-1bL;Sr1$+ns z11Fot08#H6vsz1#+bFi8^a@P5O`-)YIxvGdkLcmRV1#)GgCD*CUCjl_f&;kH#Htfr z&E-Ssm~|#!N(;J6*kB8oPQqQ-A=XsJu%f|}u(alfAMgMZ*4WMSO?VRnWTpi+A&Q6g zDuUT;{Qmg83~gcImlRC-4IXEWKeQi1t|Hj4D)!VfKfv-K|B|)bm!eE;q2k4GaS~ow zv-@c<{5?YyJlaZ>1k^-8ndB+48;oUOfds6i+M+B;gHM5&Smq#lVd&xxOxZ%br5RIS z4#LJi`r)E=c(RZ;mAa}7E;Of{ElLFyP2obFwt*#CWqsG~QHiyz_v0C9B_(Qs31eJfB<{fxGVZB9rg^^7`l%?)wL^c>7^kQ(`nvF2 z|5LFgLtAI6XK?pwty;-%w94-kzm>YJ9?$F@&4?rP>vJe-4nwSnFzho>{X($vXVmlPE%QVxvrK;;9Q*o8Fvn-;9RI zG#)?Hd{Iv#ikZ!)ZoH<6$*}HN-hqu+NQRpNq?z7#K#9 z2F7th)n&u~B>Y~2?`oKvBvUlbD`;2za$~iLd{;6N`}JyGP}m5^5PSqujrT=d#)i1j zl@mGHdN9E&CvvjIv+SFmaey;C#?1qZ;Ix)zc(KLky}njeZ4QT$8^RrImluh`AO zQ5%yr&!{fPzDEF&pDonv)K3N>75j_)lG8i&lS<}6SVnSQ);#lR6`K+a9u<2*kECM5 zL*`M0x8(G55E8R>m8ePR16B?n20t`GXZoJ{$E3%&qT$=(jr!bZF-gG?kW5(&Mf_FF3hYkcn6$${NkKtm9D-4>?c@@1jfJpX0C z;-+c=20|CEpx8pa?;$a#cdCtGeHq#?vzsRlQQV~PmQr>5jHJK@IOt?GD_IKcS= z4&a8LE+mwP@ACVFnq3Y8xH*bhfgK%c%;~m0fXnNr|DB-c0km~KS>)+^s$1up%J|}J zUlLMSqm~+B36dl8_Y*;RmMifMPH(Vw)_@;Q+Z%D{fwEbWO#O|cVUXdu{%AhrV71Y& z_P)`O0hyOK>gz^CZpes0vMpqHD8}julEGqVgdh<=-xOS09ob=Am`Y>40xIO;O~aoK zpqj{xi4d8{pEU(fr`XDNm`r`_LxR^6iL2t<bs~cc_)8`>+sxpFy70f1dmTVm9Dm{mEXWLk$(@m|IXr@dE zkOhMf4q6^We^1s;Qq~((`30uHD~r|`siI{X7|&w3>rPH)8g##%9GHfZ?9mlrseT)# zAvil!!m6=2b{yy->7Pg_Rx=XQ(5h_U?fEghRfY%Z-K9UbG&7g@=xg-!=4O7G2lf9% zAQxp4)1%MzPba}35z}FMs^GDFkgG?6i0~9QB7zd2X!2#rKNW0Tf}v&JheTd2_qI;O zhldJg4HmT}pmSIw>q}_-H4y0L!qcCihF?e9OXk{Rp2Yr9DmiYWbT{Ap!m9<)syABVz*i;*}SGwJz z`9x_e?%2LoJ|w0+{5df{;lL%bzjhwhXMdO6(MuzW@#a3UMmxNe=1c-#OLJ?P`z18z z85-AH{78F7F}bti#dRwhZP#FSU8D$g$XrQGnA|IIFiwMkp>GmYhCn@P2C4&-+kyxv$!gKDQCr*Ql5!* z>cI4FYWTL@^}o`J`&eB(u>O_iCdn~1lN`mE(*HZNEJ(5pN2XK%wh6Sdwx zn%DC;B4}C>byi=rB8uzBqK;eP3QVVsjN5E(H1z>BRYX{u_5(#IK&vk#xF>l_*^3$( zmjEC&BM`UB&p6szcffOC6<)8BtkBtx(z}Oe>5@>7yW@@iM#ITGk1rKNN@s(~bPuZ2Xod^iIuIdwf6CTb(;~vhFcJ*`m zf@(QYjxJAufg4AgWD;?>pB>%vsOiJuVND*+pLzQ$@j9VfK;M@w&*KM6&W{}Wrtj#I zzx(<09d2`QgOeTfU5pB`?6@%CUhaU+5S3OCN2aeY!^x&rAMw{T;jb^Z^1Sxz@#?rE zQv46`CJ841ZTQMTJRJ^0t&O(rwFI8EL?e~oy2}S*p=T2ef44U{^ zVAqqDA$`)}a1FZ+9Kz~aRuZoRCQS6Fr^#&alSohO`C;i$%h+d7$8U-F3 zohXQXp`bN9Dp)r4uf8n^lBybYUR&B#xgan*@?zcMXeOV^(rp)QCKl|bI2Oh5VW|z| z$xC(w;A*QLi*?vX+LPMTVEiWuU{NynDcUq;E6%HB0(P^)_kWBn@Z}8+26c}e9`}5I z{ZjUS)KpfJ?F&??U;3{`{qL{(d;DbhllMUuBg~`us<&tOZrv@ba9)3_dJFHFYI7~D z_L>#Aa)%B}5Q=I>Snr(eu6N?N(|RYHD6XT#b(9!>RfSgM!h@XuWnpS>!+Ce?ufqCeL+k!O!bDNO;NRWld= ztm5p2VSJ*MT_6|pWjmU1#62-}+mzbDNUnJ%IPH3VKx@r4vn)Hg5*%nXh;7^M`qRk) zv$aE0l$RqnXvAD1z+n8iCNTwyY-2w|6e1jQcut5UD!>_?R*5g`f!jqzclX3s3;(Pq z1RcwoR6nA5ytK?o^m}&Ue^4Ftg;ft-2-~5J$|p!H5aYnB;rsR(8$P$WC6e=FTZ^Oz zwE>UWAcNrFKz*wAB0B*o;b-p_@-=ZewWq#%_>(5f^cm3{DsXdsOb(Lfiw2c93R7JWP*iGfz>xwd!yXUo zS9|u0a!0*g;P<$qJN0(&ErV^8^*Q%ZLv|n4+PS7@B|tWi4yBC%xKp6nFK9F>dAL}{ zVwolcL)j{>=1Xl{NJ%lMx#^(L2@W(uPS8Ug<&|PwN)i6;RTC`Tzf}ypRD=Q_bD{hQuP&+*ay(nG@Y8!vKu{RQ-@qcq(OiW!Yq>$ zE@hXfe_!DT^-QxSso=V5G-%<*{Z{&&=2i{RHxlUvwwQ3$42)U4rxedHzT^cM-d(SV zK*F_D?z$0sNXT?Um5S!M%hhSS%u#_B?ygW~M|M&oj@pX-R8&ndb#JTtn0rK3%)U~T zcd+hPR9Pud&}7Cp08$T@$z$Z;+iAd^&~N)1?As2c_>xfZ7WcjeXa&P;}>0-q-}g7XRE^wb$Yw$P4S`(S`AkysYH2W2;V!e~CaQ@NY`^2M$f( zpOM&8b;!tU)Jta^n}R@2ZTPt@MszDUWMW#Vr-gMeAqB+!>ff(|Kj|d*(O^poO+RC_ zx?e9VJv^lOfwPFqpVGAvhd^b)i}}Ob?|44p#H!vi*Yy(z^m!m!s8q)2yU^Hx%-uN* z4GO5T69Q(dC;;Qu3TAwn{z=p*%%yKKkWwbdNr6%b)`;yFy-8Ala7_vXa%+`3Ma7vi z6js%Viu+BGP+FUcEU9o6ecd4(QzPabDbhj)ji1W=4XN%n$Q8d?HWG8BRy72fsf zBr?*Ru9;t#jCqZ^V!lPM^}T;OImZkb{(#&`(wob3U<837S)Ox!)Cmdo^cdR`^?N*r zC88C@mmn(`bZ_|biRq4$dMs{u1_?nng!<7WY07#er%t#qh zKV>uo|1nqv(J}r*MvwpB`H%TThySERY;a&~O7WkJRt=|M6D^~{nQFdn!Vb5vS!LoA z0QyA3=1wLyx4I6Sn{8A|l*f?Z0vfSO+dzHHo^v;qOZQB_C3!{;5D}Y<{xXTj^?qe! zyrq82?hUiOVyWy8W@k88$od@wJP6ej@zsi~>tDT;Hla@W3L9lY^IK0z%{MPz?-eg zpl%&6${ko@p#z>TQ%`te6C^2tEBM~15PzL}sNStKZel73(mS4vkmHDqju8TOKpiOl z$p~3-sB|(ysJRs{p>B&(>17C*EErGPf(lfVN}RG`#&~uLPTI+NoM?8=;)=o3sRU`X zo-AkRTUH+PgbXxQYxe6sE7VVt=xMH_&Kt^S(zPTJfu^N$mel)`3MdGL3aI%j1gEF7 zI)E@F5>yz;@BX|9%JoX43RL3^oLhQ!M>QQ=JOFbrRU?%oa7VeDWU8!jsqrN5gtB0v ztN?ZK;}R|{2v5^dsTiv`axl`s6g4a76GDv6$&xQD9CN zBgjhDi#u>C@J^OKTM5;r)nYWWWi&Ub-aJEb={LJL^^Z#-FA=n1$Z~4xx8vL5<7fJ~ zi##r(+h>%kP{aaYd;%&Cs|Xm9*j3kM>PZD=L@;Sm@tV)kH(OU!(3j201`!k!$HS-# zBBH+Lu6`u+m8T0<&)(xrOD!J3wJv~1_QUe^V|>ksg=ND(HK>d$>i*-2OP*5+*1M5^q#NA*WEi_mMHqpYq*3vMz!WMakgtO5TFM_sZy4HxNdJ^R> zMUFoP&9=($BlJ=$q4|kX=0{LL4HJ;?4}^s9-Fn~tkFNBFDWa^J(|pZk)O%U+&f>Lk z_8%g3?7yv_en+R8Qb-{~(0)}8o+W%1QLSBFM7t_x~ykFb`Y1Lg0S*xvhiOb}&R+SLS!E#y6Q zph7!A6Kr7-VS>tm zd0r&3IZG?UcZps@2<9lhXg)8QP_=j#9=G$#=1n)KiQL%yeWqVOX!~G#_!$i?odia& z<=^NEwMLK~c^iGw0J9(y)dVuH(J9;7E;jHy6hWzrtxXs` zv1_`nv?4b=Wy(lr1ZWZ(Yy~mFR`L~!IoN|B^B9NfqS$RE-c&OGW*E{o1cCo*{?PlsoGChb3!w}97~IyF0>am)-5-2Zr>v6w=~ z%Lugut&dYnMj@M}B1pFge7j8w+y^0A$$<$&DWg?B_Yr+hGBB2egD3E4oQ0{nZttxn zc1G|><}J+#Ld6o#5WmSv(g7+y_d!jIiGpA%+}ng{!1ISIp}ttT;@JTyQV8TNDnNz} zCUr~ZrxSgF{5yU5%_k@nB)?-%c+%tiN?d*9}0Y51eocD$Q2 zU5j)@P*w`F7LHFznPLQTgMGdl;Qs{SLC_s6k??cVXlBhOMIG4iUY{H{22IzIgF!@S{S{PTr`>}BjT9(I(F&AoQJXQ49&CFK)ve*#e8P5Kwf!txc$Z@0H5NJ^ z+6$Z!Zz`$^5i2~`;TO_crK56&Ov-$v860ikna0X=jAgz;8n6x8Un32ikT6?A?0{B% z1RZF_oxR!f{1AyiWFnPW6K+=DRDHs&ocl^J%}C_e+1%+;K5hm0)>4!Es^wXLGFy>r za$m7sB57~Mj1Wc6J+5^)_Wi6uLjy`ExH;*{NXEX_6ZN_mtcKek;)E?^8vnHsB;`>Vqiwm+WE?=-W&R(zayRW^0eox1veM z!JAdTXZws9=ps%tR0iiVz{ZfXA^FQSUG{SW0@2POo*|pOknWL?aP6VfezG#Qh*C zOfIgCxljH|bV!;<CI()yB z0#P=)@gfe$S$=&qjjeYAD?z{Hxy^|)7;Fc#Pq zSd^ih+QlbQ032(9=Q$e%{L^e;pGFoA!(98i=dtGVs$5^Zgl1_^+&rc0iK5oF(1kb% zo|HQDfvTlsNe0m~?#%o63rktm*aH?)pi%_3bo{;seszDP&o)PX_xGKL-RU-m^{@@0 zQEV3tzY6u>GV;G_H@l7zqbixpG`)dF^&3*f;8+>dLmW7`56rQ1hlFnKkSa`_;Y&B( zbOuUtSJ5htgCxANi zOk1+2`A;VThF1VBj+rr>0KFz@!_|GVZm`*!i`mg-%U1~NHc;Vc%95G*lIQqe@6ORY zLd>N}YmrM6tn2K8$Z&#G9BTk;AHAl@0Mo}ndHS%#bnmd1=*0kmq#1FM}NL+%c#GFsog{i zTx>$)Vz65ngEE~kw@PC}ZVYNx=P0S(E)!zL$JNrqcN18>@s@}0jzHz=ik-dhhLFmd zaqV0w=`Ihdjk`uuTksn%_v_=Ihra9g`|nBpRk@YHEj8NjmM!Rs;Ih*LUA}YWo?EEz zo`0@C-$gHh`f%rSJ>ayn2Z(m@poRmk+3--h+!^>onj}85Z7Gd|0|197#HXPlKzerkVV9+x5i6|UDO;A%VHk9 zSdGCha&OFy3TJ&QVOYU-cscY0QJ~yNDA-__J? zXh#wN6>_m~N|MZ)<`j2kX)*-uhXq=oA@E7<)S$LzJeH-qgr;67&*|Bqvr zylEEnzCoH*nxM7P*dEP{d!6~PT(P;`nVYjIxf;^qf^!qn*?}dUwW~&ig6m0y!s!r| z)yR8j1N7W)C5--uL<)2{3GH=({!r@5#enWk0BlRT2H0l2xf7oBK(;0qE`L_#Bc zOayW&N@I459r>Zzr%kYBz1?;v7I4&$C9J*LbVnhlJPrnYARhYhFuYfxO!J?m3l zOBgWd-8N$X(q>gv00anMm_vH_i!=V>iGYoYZyphBYJ66LA;&+T{jrIUkhi28uL&N%Tm++8)F~@#1ft z0U)Wmz1ct68x}TzQ=&q7UbCU=M4X7`I?@Nzw(>iT~rR1zm zgZf*o;7Mu{HYJ@0EmKD&Fe$sQ?e*$^!IS#ypig1iUU9?Kz78*;O|2PC7XMaO-LZAu zr1}CNnshTa$+I8}Ld!~6F1D?^2Vu;UPbpZG5qX z!ii*{toJXXmj18Z8!qcjPtQ#2f3tH1;Y(BV1ng&vpK%P0-~gi09~L)v3e*QmEuQRu zx6nOlOu&ua?hR)eRmigdR@YA|^JmDys8#C8crsR(zD=LpKKwIL77brWx0DUEA|C~Y zq1JkvmWukBwk3rb-eG4?_B6rN%CL+=_p}gDRiXsyf-$hGVkM?tPW*cO!sb*qYGC=co*Rvl*&f1=0wp88Za%UbFJva@G$)%e*np#k z;R?~~Iu$+-%vZec+Jn9{n0xk6INR@*<=^b0`>JPZ_5$9zK#e?gU4$E@9ZoWHjcd*9 zPo#?-r>;wBPQ7jxh$BGjk{f$*cWff%>H0?wh?Em92DYyyU|8{bmoozU+Hh0u4pYY5Br&Cu;}4_##aXeLYhKU2*v+ zwn75~<3A}+Bta;OeJd}6M;!y^iI+WU(&8^}5)wWAw*vZf5OzHJM?acNk4Q|F88$;| z#NwEux5)7xO`#+jKlDh4o&<}%uU_(=cu(BO(ZgC&q5CoqDUi*>(1d13{YIOWqS`dR zIFw!q<;dTa5?^}Spco*kC6?CyfMW96hYp@=BaH`canzjCjhgZ5B^;(wcq$p!p-0iJ zn?PB9c0@{lHQ+h8slnF<3A7#K=e#n!F!Uw^Ynh>j;6-X*HeXQh?V~9VFU8aFWUnGQ z={L>@4hv(n8MHX(vXerare05PyFLm%yG31v`gp}AyfvFyp0sP?>i&%{FVD)ZJBvLz zl|oONUCII>UVBrC@~%R@-q!+21S$}g$)&;#+2AM0p)84qq==TzW*BX<(_oD!)W;~MCt^v%!(yky z#d$6BtyvK%@p}hU+Mlqj8r)>A#!Vpg0Q=KXAd8R^70BGu+}fjov)lcs|MFKvE=iMI zIU_y%l?=IUc(}wEVAFord9r%a2FQ!Z+t*dYuKV`%Dy9O8qbu&pK>LhP<`o@ zsKp%Op(nS6o9Xt^?C^)9EFc^)1cxxyUu}{r_8B8qqiwR}i^4&nXaCXe)Tx*{7@RyQ z8LKa(8y!vV$qu5a$quIv*DlXGoSp?VHTYZZM89ben$q(IDvuhM%D5M1DvoJ3ZkcvY zC}7S*baR6zD`(ySPR!w>84kB(BU~=7o`U|6IiJ?sut`#UoVdEWl>>o3XMf#bnDQGk zhbD|M(hkCq1ND$6drb43U^NkUJT8}U5k;+M&0eOP!I>{g%*Cp5AXS#4=7|$hwDC5J zJGB2qZIy#77~M>V_O8_S)NB!;UdyZj;fZ$mf=I*1g8ApxxOjeO7ubIsoSj%Fc}gl-QMd{hlSgzkY8Y zE$+PMd(?TNUkF@ZVYRW(SM=?1mZ@COSmt$X~(_%IL%tktqtP{y$<=b41fpYGLVteP9;}zuRh(1 zLBjSr`Z3l=Iwonq5Fd2XxPsr`4rIHl3#1Ba))vfqys#j_xS7Q0PQmwTqUzB_-r^H; zac`G;I;c;`%q40$WT2Py?CqS!AVlk)t{-#%I?D!|b>ecP_$A-Gl|A^T`DMzMYebxw zJ410|?y}a7kPwT37^LMiE@|DgF6dh`2Nt^<=YSG26OVv>k~@HJI`L!WMvzwU5etn* zE1+40456#32WHTZcAZvg z3<5z|CI3(|$R5O7k@wWNU%~I$F5^?=rs~JtT9RD~9^@QN1s-xWSO#M{E6pfHh@FtP zQ}v_&X_cu>s(|>^RHGJqws|0c5A-L=LS&Y9K+|c5)`g{N6LqTol>3nsQ32zEUQ3+( zc1ko*QMVFIkh_=F=TuejdEy^55VV{$G7cFD5CNp3x@(97 zV*lO1nIf%6l{Fi9_2yJ^OP*#6r6drwdn1>22!|LVC;N~4jnznUlv^GiYpf4#=4vD& zH6CXkTQz&N!DO{Y$OXq;7-_g;DL9D*tuEyiiV#vvd^1jcc}rTgN9N>!+inpwC|rnY|ie1^a^|12u^Y= z$@ec0g}mI5=lnapk}#+^b*4W2UUApv+?AsCxht-@9;FR`8>LaAY3eW*oqAI4AhxW) z3-@i3ez_Na;pvF7Ql)f7KwaTh!9VmmbpZ+yG9Djmx2`PT{N~j7n>ig(#0O?1T6~D_ z&y1J)YEK-2p`Yd;Bf5k=ajwvBc$JW-yaPushckFb8s`-2K!eDA#Q{}URMQ-=S~}`P zyIF>ZGj?5r5YMa1RI+_}6JS+>kcU-*R;TK3R`0cQl3#lGB9R|hURTPHsr;NsG50rV zMtLW5rCx2z_H^DvqaY(~uOYX3oCM`S6=U)6FGS)7BSkjjB1C38Qcj<2mH%2x2yDfG z&rCDcH_{6huOeW@iHrZ`Ygz2$DJ}*d-c>*Cp5;v=a;r6-g~p^0!@toQlgpFVeU6)| z0~6(z4J4oj1x1Np1eFRIBgK(0_KJGnF=1V}C>qV4zETmJygdLxqyD8&t*!{eG=wH@ z`7uTuEvgBal^F@l$`r4Gx2$2j8-Yd}!OqDpyMyS2ZpwZTkEro2(zC0}Rd&7sTC=7> z6Ua{lZ9zEhOnu1fWKRU%D4`i8Z`7YAr&*r&>-6;chL=FTvTV))vTSRl4VnhVS)ND7 z(k@ED`hCgb5vn$jnHVjNxVvv`;c-{B)ZXkXs>Sy1K#_C5t9K7#r z{KD-6=zOtSWG)=mV;3U~Uhk5qSIR7YAG^Ak&Eof1Cy$}kX$4ky|Mkc2x^LR+Fj@Zf z?hRKIyQ4`Q8#gSWlFrY~dM)LjST^b{dUa|wukFUEbIlQ*5ker%FABd1nIf5Q zkL#vn`siVO`pY0^R+*U~D5}+vkD4bNSepV?n_Ey(S3pw>EAe4oaH+7IyVY0|IROyX%eqO&67hq ztP;e1@{6Kjd?l3+5W&qWy}jR^ZTGx+JiHiQA_#oK$;$7lpJ|oV%SKdiK*LAPPCbC? zQ?H!Qg}uM@K+7lZ_Q!ZDRbHBnRJ5AoEH9I&Qi(&zAgb|7q2ocAPG4@Ux%tv$Q2PJ^Pi3XNDt* z!S?HSc=UM^hX5b&7laOB?ZBi9mY#Gqg(jNfFb#}XcFqh@rqm__XL?D^%KsQ&MOsgID3xzbIUik8`C=Q6yU9!zwN9Ct#cyd#xtdE;95(>?j3>>fn^5QN*J~OvVMaRITF%~J*IwS{CHmPv3fS=hrEbp$j z>ZB|(6+0HJSILXJE}niY<$wuIpK+RG=sqk%UW$pGQS3+7ybhUGfyndv%o8!MOm-_q z)4EDyUmQkFLr)1h!^8VI90*ANTyEwGb^ga^k?dHD$?_lZWXXbwOVs zR}ONLi}J}&P`tZrxQ3$unxbAmw5mU`B*g*6uU%U52lD+htALX~xc{G-YzIKX@<}aI z@An|mqJ~f*eFROn(|}uoMLf@$V>jf%{thycrQxKu7|`i84fX4LyL7Ej)P^HKSeIC* z{5!hk7Z)|rMg5315RBtO5s}nD7amD&xDc^85UkFIj@957BuQXI7wCuEHW`<er|AFw-W9fZWN5x`SsIiUZ{5+ezn^``+}tKH}W^jR-c&Newbr z;Qz;4tZ;+0m%j<}ReN2qNM0nc4C=#QJ>{-tut0Y8q0d3ObT>(T2kyoaQG^cB>hVsi z4`?f{3>HmQJMHxD)HmFi8c3uox# zzRpyHjGf?hpjUjQ@@Qo4|g~>kEU34-yzk`7SD^hFx7!Nccf6DS?tS z6+U6Gs4v)MPLt&N8QNepTc=$qc+qz`QxcT{1jMMa{tfDDho7`!JRAXdm?Ey*nkz#3=BUK&Kxi?b!CuO z%k72@$O>Jw9{xa!bfW=t$KWROy}obEV4*>Zgiw~!0!1cf{iqltFUOFJvgl{}lj0-G z9Z?`$!fI{Ot3H7gYT~|b6I!yB3b3wPP4zH}fusZ~4cPisXQ@ij3pRloPH(L0kdNST zYAyM!Yq`p6a)|~TxeF(mhjx4)PBe7pvw<2fh<7yPng>JI&rEh5L>T9E#8tGMTx!>h zz?B3m7a>3D_c?SYt=)kT{dz2zXM$Js>O*bANK*$qw`IhWjXZ5J1_w zOUB-~k@q|QMcQX*ZTlB7GR>p@D=>-`K-0kM!utipFg*=9Dm690)!l!es3I|L zK-Se{9$BaS_(&|2?A=TUbs!QmoB3>VfHrKV{(#kfyQ(+ElGA4MPIJ!T=Oyzbi-uM9 zn3VMe>VEepFTlcJaTDH`WIc%`9%~XO|7mA^vuf5ialk0ZoiUI`TZ@sK^iwGiXR{Yc z+U3qhq+7UYp%eF;&bcoORCVrWmsw*3y9)XAH}v}jvEshHues%&6am&nXjn!8ZISc< zi(;u?7d#no%(3~K?!3OA%uwcuW2y%Hq!rWV2&m5#?Or_wR&E2R%r9XD(VJ^~--zx4 z%T!;k?QH|AS!8)dZyVr|QkK^*1V#e6t>%!^byRS&kp#G&EE}*)l~gEO?C~CSu8_1)2!c9>m~wqILp-&qzi>~{ zZb>_YXL=Chw0}9fNRY+q{**&xd_clL^2XgJSJrg>t!edUpfx4G8@|;zh|cxO;^x4F z*Tm0tbdp)!PG+^4YL;}o+HB`X*T6t{K-!O2@qp(y^26Qrx~pcvotvlvXSNQc3oN>} z$Jvys?#5-?R9cnZUhZI%1V1+|v*lU~Q}b!xB+s@rI-@_UNW3yT{3Z335gOe%`HJ=y ztGPYZ5`&>O|GkM7_vpZ>Hy{Z7X0fUoszY zKHF8QN3+AkK2tCb2=eBcAf6(RW?vr3mkFs3X$F#(h{n+W*uRDmh)oxVo*IjNd`KIIB zF&}sO_FX>~>^a-j`t~>cEZNnm+}U-W{ga;+V{M#Vr+;60+ea+|x^4WOpQYnpQX9{$ zQ|`3=^5f&ku{*iGL9NwuLCRiX^9b#(NpZE}BA6FiIR~%#z1yd7ILi(mIwdq_3pa<3 zFTxF>p}yGaB7NGhJTK#fP@P##sp?s%7HiH-T2%f@C2E`Xc@vbMh6t9RNB(>5mf_>~ zF=Hb?*`v%ZoD}!Jf*QTo>=CorQ1%M2TV1(}PKOnQK0)3f?aju&9x0DYWtbyJwmpN; z0mL;C z2KByFBL_$m{5Eag!8q5xnu03~pH!7F)|fGP1wjpDSa>D#2W%I=6X0o1Nshp*kQsX6 zY9@>`%fSTZO-~6&80r+QVS{T55?Jq1o_)1cXudV|hh!`csEW#~<3BZxLM2iXG030|!JBDa;P@3U7GyJ?d ztU$$70VNw0n-z+wq>y#0MXmOcQ7S_=8Xi)EP$~wyD3qFE#Guq=yjhDe%){_o^7w{)1a5(e-^q-CmwN;U6094JqeX-t6+vwh?>RYowV2aa zvktR_>`rqog@CmTYYIat3BpeFBO0)#Jr1oF_c#Vu-I%ZD1mZ}U6%-~E61q3o)8LG{ z8~8AW4JZazmz)v+Ccq7WIfr7H1kfBvAplm=65)7kf{^sC7wTt?aK9lb-CL-S`|g(| zbKNq^xL2+n7yVpJzK2rce&icDb-bPub&#LGo$|*BYbK} z+MDg^*e;av-a~FhB_^T#bbo1T!^sAt7C7X&nRC?Zpgz=C0Ixw$R%LAU5K@pEAD#lX zpqEPX#rHV;pfp<1tHFsT*~BdNXiVatSy=er+epdbo>4C;qB^FPe4O_e!;_&G%Lg^9 zd|~nQgK~B=0Y%+&?fi-p;E&#&RoR9AxmWI}Y!dhaG)|jG)wSRC$4Q8TJg3v$}1SjU@`jK7#qL<3|T#S&%CDciOB8 zPnuvsf$s$Qr*-jKxP_+>wNa?g*eJ5_B2PqJ?5&G8F=6i)sCx?^D>y9}P%^DOQ#5+6 zvV|1QoNA^?1=4db=@mr5R&oU+^E3M$5~myN8 zg}0Wk+ae2x(iK|}#3&1o9ZN^MCyaNLN}SkJ?|5js=&ot33pW?VJ@l=RgZpwhff;o; zfLIBPjX$iBj)fF7C8%f2QeXJ9f_eD{Nw!{1L~k+-g_S0!zOL>VvAGUIWgwwP%aP_% z%};An(4amV?r{9>>L_YInIQZKi#?~c-KM8^i^GV~^_u6fp7c>!Z)0PnsT5Oj(I)@! zqekrEj%*Np3;-jdpb0Oc_cROoD9GA376x}>0?KM2RnQ)`8tldZ8Wq)o^2sAY ze5&+T#;veI(hTacfDnxhcc(`{oB!QKvznq#WqYYi0}ypZW%`~@(ia6`^0e(CG@|-_ zgf&_&v@BR^UP8$ZJvI2>!ta0YU^6pX+%w3tdT6S}+oI|XzhuPVN^Cu$iR?9bDTK_- z8raRs=nT;kDD7|{WMR90r1|k2LEj>;IWT=RUr)boNqeQ{_tu~9F>lN!)-TTu=+OcT zP1;+kx@Xbt0Gr2(uG!In4NJ}rP_W8~-Au#M*%+ULo*jpwAw zO!Z{rGoEX)44w2ifRU?58*c@c;g{VC@xbcEK|Ouy$1#G@-gW(8Tpw_0bTC%9_~xet z=5j~5L)dhz7(vaLK9@7&S7_GFR}>X$$|GJJA&hq3rc^!Ry@Ni-WRq03LA5DV^HNDw zk7@QcrCEgbni&+)cxzEZgmmlb@<93W{;`rnXwl6R+iU~GP{y{ARu|>ML#0trVC3gD|SU&frH(?2Dtr%>=xFqM4 zcVur#7`Mb8HJq8VOIUD#K=8*+v_vhWnb$hTczKx!6OZ)ZtFChtbz&c~<1NGkAOuxo zGwwnXFY-bPv_GdVUlld}+^SqBi6r#tWFx?Pqxf>K{*a6QZXy{?#4ujG{llu`f61Mg z<>Yt(a(O1FP&>VWRqls0{y80%F57@<$UdgaqP|nE@7h$bSAWs>FKO=I`4P3322IC~ z`u>LI{S&^wvAO^0-%_g6LFf7STg<#Zx_@M4b)^@Hk-0k*cj7Vk^ z!u2ZGX3Ap7U=kvb6eEAHYQY8v8Vnq7jGPV$BJN4wy*OXLGZdqg$`it6_TL_w(R`x; z32~#l&!}EBjLdmu{>#50-djHkdqTB}Zfv#$?yFaNj$l@r;TV-3z=0O>1CI;V^QfQ# z07;!u*5~a0tF-WMouF-(b=+_QJ@~5U}kLcHqMxd>0IP@v~+98Q_&^2wG z)UO?v2t2NL!bMijqDCAmEpg(!lo$(#DrH`F5EdOuq+uog^k5TBt&viz#A2dn0j5`< zMM}MhUqljFlY{YcAdWHyX~v*76o_T8Y5B!!yPWt}!ENKP=y?k~M7&g6;-$=Wg;U-Y zA>QJ1B5>|X>W?;}rHtl=KW_#}+4>aylQ8}VO=G1)9V(Scj$lMB-wmbSxrR-?O1Ru{ z4L9F44A&0R^;jx|@?zyEc(F~VNXYpJSzD#xRi>X)0a*b}!+6v|9cKs^R0w=?Zsr5z zGY`O`t*(ICbd}N%Ox2pUbZ(|bcp!<$#a2WfD74n2uvKk=x|)xq%Pk56%bJS0V8qZW zot1tIvK_#Rt`>1S=@(?#KUKCTXExM;^WOGU!k6QW%M&_uAJyb>l>g{oL5HcFjaXA5 zBlpvA=j6=$r<8;p-=kzCojmU2J*9$DCp9;nq(gR`$vq<pzaRE4h?h84HNzdacc_ggMWm2gpccr?p*p_>bn= zW-(~fe0+!g>>Ymct;V|g8HV_kREBHd1;f)SMZ*M+m7}X(-hW0!eAPqI5pg5D)8Zn8 zBk{rtB%tlWV8mm-i2pk-&&;b*b-bc?rgn(^>Rc20btx8fLWU>5GGDaVblShcfY7b<7M90fN#5VoRFp|T>~94G zuN!0?en^F+#mUy;jn;OS>ancD&r>ij%b*mL~5+g?IXOpSb3LOuLp*$Oc6mn~ZlG3eF(@GLE zHpQ6YkcI0|KY-4L8Dn5|rZLO}#o<4t$=i>mXR@T#zy_@kiQiNCv#6*0nH-3baElN* z0yZ;wfX6$TgB6J!mGRWMR`?jy-;l{cwCQoOU=<*t)|2a##p~7N8yd1W2vTIS;F@l( zPZkH6n+uSIDm;%=hj*zP+@(- z;7RKrG^Z$q;3v;a(&R3U@Rp0xOuukblAaT6!D9`pE!#T}!ddU8SyLNno`}T_=>^Npe z{f6Uv%ed4q-JcQX=l4;co?|-rxEMOf80rAYyNAz--bN{(zIdHh4*?Fp22zX!Ri^n0 zCF|0^q9s*IOZD%|KR5g}wHJe>FC9l9Q`~VQ9XL+m=w{yIB#c-Wcuywau^T{o?r=qZ zXIQWm7Je^7agksG6t78sXPkJ!mv7b=_2FNX6inO66>ns??O68qEOJls<^5A5pCfo+ zinMWVA!@v_sqs3OdlmW>xHDei^&yI{TXwq7?b5-TN}`(WGGltxmgU8OP36k2fz!Ia zQ?Bm`0Zg6Q78*#^9Itdgj82;D{(*5Ng48UPE(7u}n^5+|XWuFyMO$#q2azI~Yr?vX zbNP@3O3TC5sihSSu0=gM*RZeWm7YvV<4cq=z~C#tJ&A;L>?~y zkX~ZS$7wHSV->ElC~PN21!YWVIZWtz+2oTH(}OEFxIAqigs7Xg4}>m{@+ZyNRNL(4 zd7A+n$_lUGB2i%Ys9x9z>Q|dzN4Umj++9Glzjs93*Brh-Ikc>~JM1bk19jBiuc&y; z`*nf(8^5>(x;Z#m$t1skf1SwzVc3wvL@y}`foLw|6jqMLpg3$nbFgSPxTxMAM=Knj z3k6S#0GsTayZuwumgH9t`S~_u3(`|ufpKe11yq$20>A*3Ge*#~dR>}h1!C_n<$0!Y zc`)Wv5E(cjcGO{Y6Wbo`LrMI>_Lv8kEH;0eyyF0&{)JDhiY%-?G-(Bn06IB{^Dx+A z+AJeHkd_e+u3bim32gijnU5#`3saH&FEodYRTGV0p~K%A04O^LQe}9r;3s!hr6-ck z%@bKyq8NT+3~M-W(q|=0a07yoD|acsh-ilY=1s)5Q0G0b3=>fz)5A`B#Gqh-ayM_Y z8ZGyDUd^1Xmcm(w5(X-RwnQ7{24pm-A}Y{YjpfHajYF+Svy%1|;8n~_SlL(ssDj*J z34y&=J*4n7J`Vqt(D#5y3P9N2hS8?Q7;Ilr5QTM2T7Q^*?+h3d0-@`t;eQv)#>xd? zTD!4_keP9TyyP0~jm?+TU)7vZOCfa;c+18FfNJJNnY~I>^aa;Q(Z))Y~8` zZ6-vO2LC62xDKL9eG|>Lhze!$Sx1QG8~w(oI5gqkoBTDl&g2&7TL zBi#zLoD0geTb{)cqTJ)_mV04SLT$~Iz!;k;@l?snFX=`67g-=4{w1-9fc$%|@C5Y7|x z7NJ0kRxGAj>*N!8cck^T)HP0)ybJsawdlZ9=&kZpsy7xF$3U7zB-HqqJZ%}U@I^&3 z>0X#TyYp|n@caYz7a%+4g4~cDo`pts%w-xgLpF#(fY33-Vo;szzML1n^QXmk+8^t` z#?zMi+OeH_8*Dc-@@VCOTbBV{8n&-xNnoQH(7`GB`&g~W3`k5i@({?DNXxEE)i7rt zlEs23iphr?t<|5_p}eFFK?o(%zq@l!Rf<9|N$Qomjj>&ZosYdD(!v?!A0B+A6|^yIkS|-u$`? zczqPMSiDPX2tvhXxo$SC>*JU4DvFPB+e@IOLHJF;JV~JR88Wu&!Fx1_#PBP zQ?s)mYzh0-j+evZR(!ry!c%zkaCh|9~T{M)Sm4 zX*Bco8}*w=a30c*r(WtwjDoFRTED!Cw9(a7^d->;V6QjV3gLz@f5?(56sB@dfL?!r z_B-_|;o* zXf*w#tVu0IXV%VuXWU>;M+a44&k-!W->Or%GJK+_kuRk`PPMJUG9%dY<5u8wUXR0) zPHPnet1eIVO+2)~W<3#43|Bh?nlxe32{f8EiYrTZkJ?TaclQA43A^NUJUKM%!IrZ@ z)vTrj-@1kc=0GsFD=sm<1ZzGauQcouqg8^*vyZfxZ3V%>J_ZoUhpiDk&>wADo(;o< z`tvp^h-IFkQ-Putv}wxzJc;gJeNLyAAs_OxjWg|TsE=q?^5KPw#SvVZ!nSo{)eAs0 zk3lsfQ_^b=CXYX+`gHxwy{mo)M8Y8e;EinubVjri=9w@6%>#X6r1Vw|XmBx$n-ow% zL|4S^vgngn!!`-GLpdha5#K1(x+&nP&dug$n0OE%8Y~K8n(bgZ34I?m#j!#zvaRvQ z1Ar^*gGSH4(a1!b+hrtP7VtNDhhc}5R~$lz&&r!(Yat^>%z8d47LkJ0Ms?uULh#uJ zp1fGd`|CQKmBMK5cATDM*1KF4ZdquFc>YeapO@?(eH}+W-@7w2#^No@UytR@d^m=z z4~h_ew)^T{Z}igY>+c(Fg^gBQcRu&kul)TNKK-HR-g5W7V@!2xnMj52w(f$P_Um4? z)l8CFs^ZRIm79V^0{lD799g_gKCY6=;fY)CUEXS-y|jAi&f@K3jqGwhU;1~9lyl68 z1p4&wT|x?iYy_x2%!g@6x$CbMV<9z%2Q&}J3{8oR>Z;+p^%;lgj5b}{>p?b~s;_%U z2LX(Riou;niS0LGxqgI9R?7s=F&Ntl^9-?{MF88GwhFBz3?giA9!?@}p`PvCy;J>S z{f1lDbX?Kua$6)<2L^J^0lCSC((?^e`~hK{7TuKy9AOE!DVRZlNRBJXa;cB@9O&V% z7H#}D`xGt^|E(=c+CR%=4VD>1m`pvX1Oo-eVuw|Oy&TG=HU7%mqJqD|A?Qa6`%bzO zEQiO*gwe7}C>T9rx+8Qq$8@v0NO!%Bj!*Iqh=t>Y_Gln+9HBnY)g8+SiT)1bBMmDi zT^_WB959~vd04UGa9dE1lvLv>VumkZI^X973zbA8bMvo%#)MMt8z!@ zM7*QAOZaqZJ0%A(c1|sPz9^=1pgn*tLb9s!J#9IFw55^&U>&;Km+9o1Ff<9p(xq1- z6rD~N^-=6kAlD`MSP#YUl(Aqu%LsVjkivEB5!fzIRwkZ&&{S887df1FPD8B}zhYo+# z>-y5<59)fk)a3zRdO-t^%qw~%Bp#kn*R}0JeS@TizbSgG0nEcAbX|XqQBu4jDy2Ic zcRw8a#d$3^5$kv=-``o>jaIEDI(=rxj4DkVS+;^r6%&9;t4$viWQ)j`_tD^03kx4C zy{%xn-B!?UsITb#P=C4!3}RaW;yQK<&`A)H{REC8za?;zbnElZdK`+Aj&ZPiRhZo$se6pzOUXsv3h?1GOnK z{D`JkGPLjJtyU^NpzpT2dwZ_#0RZ5_#Ar4F++dho?)tp53uuc0hy ziY@RSDQ$`x{h_Q>hw$k5XGX(IE<_N_bkPaAN|Dij< zx&F?V?4Q&Nj9tBD>R`NZYSJl6EdYn^8}+3Y^e?CuzykHJzoY8!VyU2D@DPY5I#dJ1 z+2GF6d5I10d5_NwT0vkud=a7_xl=a7tt|sQ)bX1eE)zAH)iDq$K}?>>r`42X=puuIG8djG z`h6pInmLHJh*tp7NL)%=5r?ohfQ!YE!_4Fb0Dgs7Gn`xyM9*I~JE*X{gF}ipC_s`e zr`2G>X~3NKX++Q|hAaC-ILv^!<7P!_y51=?zu{Z;&ME>Yo?y^3IjuoY@!37rQ-ZH1 zYrbe$#KN(1M#P&5xV$g{i)ewg8E?|_;W>JWY=@Nb5UoSXGXWkzjS(nPo;E4ZRWtXE zG-f`!kcK}HvYM0!yV&3gZop|paDyP_X*9zNlJff`-Ia<&q&&DcouJBY|4Sj|!lYb? zl(j_@QU-OW7^2{O3;S|4sKpq3v8P`AKb4b*=>i=*Ma*;PSlK3)4@OuDD=j=%d~XrO z0SubIh2>t!=T-4qp+586AO3`xr6ro&yt@~^sS$j}IJW$T1OWBD!13c6@WR3`7A>b) z?X?MP2BcutX=*@EKT118!tm|-+4-f9|KTSdy8q99?djfqFGumvqrd&skACSVA9?%( z=k!SX6#1so&(6=B`-w08$y0xJ@+rMx+Z&I*{PEwv_fL=h`3JtFM>L>`8vd%rg=qaX zdipMiN3J&sgud`co8tq2u%dj7 z@4lkc?n8B{Dju%-4`Tu;YT7eDc!)zjMBos+a6t@Wg!Zi7?zt0V45fz`1>g2-0rM`N zD?ckTGE{-2z)0u+f7yE*D7&ue&hvh}SFfsGeO!ImmMtZ@^-vOdVl~c;vE9bA&{b%_ ziPN~Jy?QY{tQppf*R-eP&RE9nfS?sn6NBgX-}~JA?tS&@!(TwwYJ}_FckaFC?6c24`|Iqp56#ofp*P*r zBdT!rC4zHpxZ8aw?PuoiYo(4r|7+aD9cfY=ksh#wi^c4eb_lSQ^oRYKO8R37o;Mda zxiO+-j~EkBLX^EvRKaF04QC#2ji!j*voGzFb1^abHLA+DMw7vCl$3%4z&f(`>T`*W zOacS}hM#ctyps{TKI3*lzFs3pmP{d-822h{rKohgM)*Lm3AES!5jLW6pf&S~okXF7+mY zi-J1ZnGyOSpXZYbY6g(>SWz0-l#E$j=rh#uG5bn zGSg@F?dfjzEj^FA|DQx>Y|I|UHq$bu3@Hn+x^oYXZVM#r$~W&5SB;Rd&tw}wL0N`A zy=05;-M>iVPD!x+Il+jUcrZ-r)KUs8tAuq@631*2VAUK) z0iB6NzA697zhg5S2E;GprRj_^-`LX;s|!#O8E}h`DH0(@Vkgs2RS$${Tlnq1tRWu} z9a}`UAqeuug439p5G=n3QmNyz4LjQ4E_ao~25YgjBRXJJiU{b6vft4VXj-?FeUE9% zXgh-*1!yz}K|_x^%r-Ry*l;A+lrmFTrU|@SmP5OjczO$fmnMqy1`tNHru*3K55cO+ zj4pd_t^Kl^v>{1i+{7BB>*^7m5ZVnXG+_`kMe7ikid6uWcPeMz{{XAwYr3AtIcLg7 zOmF(DA3Nx|+jQdBt?sLE`>B^k=cVQ@oi_W3NZcRcwbrA!Pz=8A5*8Qp^og!?F-cRH z83I;5V1&Z%r)}BKrPOQcp5m!m=z1fJTcUE52xMtdI*FDQ9fjFaf>^oWWWBzpu$iTNhJcA@C} zhkI|89&(?PSaFk57=oPbPsX?00nx$~__d2W=>)}^V(%BJs8ZkK;>=?IK*>49{R1Tx z03Ik56))O(->!Ri4n#foc%}W7tku_VuBiIgL@^u=sT+tO^Hh(WLDZoFGya@ zYaFYTw|!4bf|ey>H9>tTsx2gr$bK2!f)~uhO!oxI3aP!tgp6q@DsdQ4Yx0=T(q!b(ojO<5&;f4>$qmwGIeJ5DZupf?6B1w>n1w0l<%vVkZ$w(NfGUkx>}hh}ChLKop?{E!_aek}cW5g;fIpRyJ^<8VJ}quYW5&fQ;cf z9RM||d_g#KWSwhwQlxU`-e13NCA2jUv(i2^qbyh)IGak+sD%c-6dk6LPkPZ=G6SuT z1KCu-8OTe3Dtb;vn5FoGu&5KI_v-ONBEK$!_|enGMZ1wKfr18JqYC)@O;IDHU%8|Z@x z>%b+fmjqN=CK^ucdX46jTF2iAOt$WsmutjM@oeObc>#Jp#?|OeE0Ra`qjf=|gaUXY zEbT(|a3MTYnj1YlpsT>wm;3dDFZb!n@4dQ~4|nSa4|nOx!#Q2chZxFwcC|mcvi9Uv z{^S+@1czqz4U9zJXsS@%g|#QpzIyd|Tq}Dl7QV{#Kw9RKfl@5s?~@BX+!E@5cBg5D z{Ppzwv*WG=LxH6WY(ycq#~U ze?w2N?%7M%7F*!!lcvD%&;vMu?uj~dCk36ty(;LwFaX`j0qFew4fcMRNqc2ovRP*j zSHtlq5BR*;&+ zf5?AB!pTfhM(*yo*mM;Da=Q=JAy~L1u!*Ucp}PwNSA?}krjD3IHFSUUcfeWvR7F^z z-{A>_+zbh-tu+r{ea_bnYttcIS9r=4LF+@ z4na39F-f2;?Nbnd3wAZmY`IE=+|NDW^=s;sWahA{#UrXrqAK1RZIGw}SISwwbl<UY{%Br_|FdSk*a z8ufQRg;C$M!l-|4Wh_3`QQw4OQ;qt~A6?z3qw6g@+P8;Uy$p3~w3*eb9qm{6X!FG~ zqy0c!TpUK5L%A2v@x|=9F~{$-(f-+Lw1M7fu}1G(5*Idg_RJKj*z@14f}i;eKevuS zy6$UPEQCy=iit6wlO z@2x=Q%VYJPRbUkyoV$7yTqYDu>x*T2eyrbfj>Ue;^mNeZsn5=z4n2MAhu?F^lX-Lk zON;*n^P0({t%a4waxkdEr(&aBY2%->-u6#lKe(8kQSE$EeemDxgP*GiWB3QV`ybrB zSM&3ySNLFG|ARlW4^{%{?*0c~w-0`1$rh?f0<4+od#2()9+QYSNP^=g3D8#2^`wcd z%yy1_50+6puWEESnY?%rlc@9W4+lxT9N%^Jb?LJ8d}IIW*w>civSsV}n^iC^hv)KT z>v?i@@G&w)kv#G3;{hmmt6FH>Kq}H?(>3 zV))Mf+LLKM7;yT*XWR)m?J&9b+*~fT>hG^rBDjLcU!>rj`ZXUSx>$+WzB+ouRApeo zu(hCwL>498VA~IKHQ83om+RBAjpq~havSpx-Z%3|Fr)R=y>jFp|5MGokZoiDGE<(- z=unJqvG2}Bz-=*wAHiI1BaWQ0$PHO*mV3gf-X`qYoWfOy_oE8+IFJTk41Sj>b#96g z;bI&8%`mnRg9uPj^yfmJA2yWU)gQ6&ghOR(PoCr{wOJ}c}KR|+l5OGs;q+Cr9iRjH{za@ZmPEsr-F=lGMO6*|5R7Nx2>3~JB$Lh+}Jx-g1Q9vWX0io$F zchN>UDU?UDNNjmb=%6->sb|sIJ?|q_jEl*C`8>R=XB~T)K4*82z%hC!+e=t}2H6XV zT=TZ#?AB?u-DT3pM*_DRA3Qcp1)>J={hCKh%QFhJSNG)MnzoLy%Z8B@1!eD0v!1g| zY{Y(&Cs(oE;umz41imBy7ILP5V^nqNI1Y`3P&H{?mmR*?ds||kZWXjsxdFnxxdF%$ zt)0NK1bK@cj_wvH?#2Lr|7#ZJ#2Q3j<_mD|I*$e!tGiGYPUapNfC=>L{6EmIqtNJD z^_RhlYWnQtZa44~MYInRwxL8(^vplef{}!X18`3Wbk_ANg0?PYZ&HU#o_jKJRwsX7 zivy968~mpuFxB$o3F)pkfl+y>S^Q}goBH`XGs!b%hp5H8P4o={31rL-m=_0~;9DwC ze=8aZSw8DHfJ2CT0IwWR^6;7E!f%<9`ML|hgN?i4iZ?-&_eD@Fgnr<7goyb-Ten2O z&W}=yd)bs(u$&(5O+ByYw4$s4-^rQPLXNcwa9h@TAD9a6uue5V+thCHKma~1zTm`c zAZRJi0VYR@aRS9-&_dYKV(zBLG*EQ-SQsz{IVn09MMrgI(2v{I^Xi&@n_VEcm7K?- zzHR(3rNdxo_3PdFjhk=&BqbSO*t)1OHphLL+g&*(tFCr zKv?whL(6xKp+&gZ(2hREp*0#}u4syH(w{s?q#$gEQWh%)ziQrmcFFpV99!P$mWRSK z&x@l{oBU+a8F&b1nt=~7%x-;|xWWoIu;xB@Vm+gr_=k21Qlac`EM6!&il_nfAdy*1nu z;F0YO_Z)$h{YJRwXoBoF!~Mb9{lnb9&C9ii8&*zl4#hKxX1=jmB($yKK=)XVgtw{7 zVGCK%d39h&c9+!xu2Hr87W!lX3|TST)1Eh-TY|W@GhW17bDhJ+HB_SId|vN>Hb0dr zr=<-RaD=?skUfZ%AqkF>5W@SJ{o))g;PR&{L{Yb|o$da6;!~1sMk)mUX!s-?qU;j_ zSqr?0=_jK$sQloY9%L5yfbCWi@2`Gq(YMb#H0Jy!w|c^QVbndE61S0TEC12^2sKX; zM_gVyshBd5?Ems)YOFKT9ve%OI2uz(RVpAR^fnhI6Xyzn8}PQW2r4)+WhPFk!UDv4 z3Ob9~il&bV#Rc}j69l<%F4FI5_oRL#UYLDGsGTI_R=AI$T%Y`rPkvtW---a#c!k%E z12!P!bQ71|7VHkN)UW!&T}1yA1#Mu~f#9&IQ)58OzQa?KJ=9MiFkrH5sxHv$umloG zu&N&;t{`(Bo&YJ)#e2*~oi1m{^2TQ(^qBC<)+iSC37!X$TO%8a#C3kJK29<9L8Ij@ZFj$vj?wl8Qdl`)Mtd^ zT|y;7n{6PV^VJ3dh0#e9vzcLw{H@vS35n!x_ZOC+^97^xWLt5Lj1KE_Qozq>`kazI zhAGhf{LjYYWi;EdVNplkdr{wN{|WULnKaGOO6@3GyC$2Snw;o*__C?c=Mx>osOU@t zG5Z%oGTI6Bx ze&b&(%xN-IRf`R%(%35XBiMC&X&%mep4cRj4_LJZ-)S;r#V}4{PR7mFc@9G_Xs~_;NP5$ z-SO6b_<5#uQna88`P?Yn`e+=9psDhxSMN`pNNHFZ zgwybPU`^I3kU^t7oKamnj8JMYFAOQ5DAKm}SJG;xkrHI9q)J?7}*--!=kF&79VF!ZE}( zWmN~3QQT>8reRc0S5+l3dF`jEsH8bBT5klqNfER*YqoJ=m&L`P80ET;?~75XN_>Cj zOAB)l#o`%d2HEAI|2Ct_j8ab@8K3$vfuQ*ODscPA2uLV#Qb$JRwKQ(i2mN!;6m$rh zo$nUT3;I13fXGDrcIgpqc4D2V8KGQam#I^t_S7QyghoP&0_U-5SYntn#GLm5z(}e7 zbJ{SRKW|I!vcsgfQ+(8wf2Sb@tBY#b(C0)nfIlKH_1M!YM9qT&X(%omvMx)gc_sj) z6{i6H#iGVIyyrGei?)v-HKgvSs(o74iTVEcuo_->d!q)g!?s0nBB&n4fjl&Q`Vdvs zl51#KwT+VwC-5BF2MS@g-SfXZIbGJ)gMOtJN5un!6)~eqE^H*KPxP{Cp+64~dH?pn zwVkAmb$t9`b)yreP6@a|^*A1_j^TUz^)c^tq8v+yY7yy$P^*qmhiuM;4RbjUIrF~n zGGl9J$Z$;7S2Tw)cj?u^U1~hU()UI-7IBUOwsB=(?i>WB4mfV`I^cIT-X*NMe7A^y zd6JWceSU;Nnz1y^-od2!p{1G9FE8Pxl~+UQfEV0-oEy;dH+>a{nbM9=F( z9`b{REuL6>j-5jn=K~b3;S<Ae)C1uhTGU$9JeqT2Qr!Fzy;gKXeMY)Ew{?Ast`=(tF>Z2shs_vbZ)C6P{` z)@`c>v6ouJn*g#2x!wWdC2+7s`loPeDYVk!ZmatfGE1le9Gv6kyiQ!4G5l=ljpzfJ zIpR@mD#2`?8Ta;P*n%TwEBQPkjZ<-Y+^5A}%nuo#zR4pk_V8O7d-?7i-x0aALJ|O2 zWRtVyY}wL_xLqQ~`oI?k#~Mz0tGZvJOU=NSRr?H@q?T7-2~Dy66iG?2sT88ixv)!( zm{a|_l(z_mA1{x@kyfqVZze;7PQ}sE#AtEZY%wO*#)DiM9PdzPv9*7i4_kQH>>$LN zipqY;RVt^>9eLjvkEmyWHPaS0hiR!H#@I;$ge(jqM80EiQfI#^Xz*0(b3aXSM4wl= zy%cL*{J9mCT8?#esm714s_{~+1(IPSf$2!8iL>BTD2`jJSs+i_76rG#@g&06E|_M5 zoJi;|C7#j(<)Wn^@vzQ|`N4Ujt{wgd(OM8nd;wdk8su^F>|Hw$m&Q3wl_Af5r*CM4 zmB%1fnyBqVg^bX=2rx+s@9D#Yj4)(>)>O)Ahoau6VN(2)mfOORemkrfZj$!noII zWo@?;XwM+oP;--K0-<t>$d|#k6d&OCyQ85%0 z>hX;XwfQL8pI^CB(Qa+}xZq+Q$U|0}MTdof;5FAunLugI+|cw8RpDs=+hDjw#c{)E zFl0mSm)v~L&IXL zxmDWN`70hejNj~9xl+v2sQA2*Y2e+BF&tpbB8205d60>nvKqz$q05(7e8`GMR@eOp zhE>`eDA?^Yo5_M|RWljx5=4x20hSUrV^ZQB{QHf`8Em|Kp8Zl@wbUPgF&vGAT}koq ze|cgXKy3H3Ee3}?YDjH&JSBxvB;;-LM5CH%OR*irKvgH6eDcW-SubE&toJB&RnA#e z7?v6uojgL~1nu4KjcW({9GfCK*X5%sur2LgKu*FUv9~7rZ>i9>eG`CDeX7Eiy*bq^ zY+E5aFUm&r=7<$$&BBfqau#C!%~2~{+bkTj!gbBUaVuQkEbLn0Y0bh3D?GhfIBA7v zGz+J!kfc9#kfyEh%vz!CDaoWEj^Ry&#zx3rSL8*MpIN*kY&kjWl8yP9ueKxt#gsqX z(0I5ZJe>50XEYw35gtzX!_ylNPY(~f{_wQM!_&gUaeuhJ@o;^3IOY%6H6E@D4_R?m zSJpNjt_=@6{%}p>;hOMp#2;pjhgoDoLa6CNJlWyZl*Pe{wq-73uhz{y?ifP+Agdc|_BQ~FGHYpWI zCugmMEo456#*`ERW7ZlRYg9W{L4z8iX~*JG47>#J^nOQWe3bCfHCj*P<*UVhI=nt{t-qWOyqxM~p0B+YBkDxNhw3qS z0$;KSF6oUs$3rIEL5hxI|BJ{$(xI_=q6KtKuVtz6I}gr9`N%K%lTWgWHvGvKk-)>B z{QOc6HZ1jEVyOq@>>J+3k^m=5eKD*fbXZ1m=(3y6{5~WUV0X_QNa00u4~VP0;enep zSd8hD+SJCnJ1-vj{GmA|Gw7V1JfhnkS%5oAa2n8!FfL*ic~j|Glv$D;pr=GQ4f+{2YHqeL0j5BLgiH=fjRAOhV)+=Fed&`c`8IwXqG{6p}mXRTeJ7_HX zgPW0vdNPU=fy;za;kNT}rjfNaVZD3NPHe?%cw>Yd19rYf0Gsf__C-<5g-r_aHSkvF zy%X0Fcs2p$*>@8L;*(FN!j*lYxWoznaI!d7eQ3{8f2LO^Lk<4sNU`?1;%aMz$uORe z(3~dX7_+`dA{&~ap^nqE<0oV?qAVa;1%_mb%1lNa9>jN;R>IFnA3r#N`}pA?PTG=nLNCWjGCxAr0`)dD2x#MieXN0=v$jw0+5mn!kdWcWK2cPY;b#mS^zmcQ z)M1|Kl{$XJ9_#oS%g?&l@k6I-_;GTAp6t9nXei()l+Pvq7RpMj)DTscFGQDH-Y2rN ztcejMu-aDs87d0+QxfItO4fsp8*|^8+W4gIRimFO{nW2Dx9+9bA>@G z?|RN~&gW)kwh7yTQJioV9VS(2-_de-*;ao0Ug zZRIyegHFB?q?QGmz%=aB`oGV}ANh>TpZa_(hmx^6)rC zVycR?)sa_A-o%&8j7wmMlZ#W4-e?gg@ANrUHTPgx1H5XanlFnv`A}9x_S>a7`P1iA zO-?=*IJs$WeU*z75sq?n<&iszg-cNlH;ZWyUQ)yavxTH>*+(VW zqV7E@NwVEOGev+SM z!fldm^?x2vbJgtB)JUZNbw-ImYPH7{%a*+>c#6x|0NnWodYrvmPi%dte5u&=di5%J zBbj9KKUNMjoRAP&R6PEdLwqJr{E@y)y;3?wRM7@ZiJs+Rw6$CdAy5r*@kGCd9!t<6 z13!0H`{5^+RZTTOBU-;AlhEDPjnZ4+h96Xm&lU7<*ew`p-bg{u3Xpjr1-+JLcMq-- zP=B!$^q4yvKtrprO%rm%%0Mdi_3Ky!zr{ED8vpWOt9xzft-knSYgjZ_8?)l%YaXIQ z*6ybff$v8s`#E2jfEoRwyb*BSO71(GdRNgKVS8xjAa-kdZb9G)GOJfRI7@} zYE|Jy!O*bEEmI3(q?O%ca|XrRiac-&s(+1GuL@zeiq{}8#zUQ|5_VzvH|Hlx8^bzf zaV$i$5e)1(By2hF6+K(qe5Jb@wZL~d?cS++^@B(iR=@qXl)yxbg$l4G49X(6=n!w@ z#BM?NRrCF&My1m{cpFG}DC$iU1E6m&>^Dt@;CfT!{ehopEfP-vZQgH~-fh;C0=v@< z7g&}k1(PxP?VZE+&X)wgc$PjVU#l);70>nyp9ZLooF<=_qf&Ohg`KIy*SdTR_{@S^ zCi*Bh+%|#+IkKPnBAX zmQ*+VM8&E8lb*!7Ip_z|m4wk^ zzutp4e9eg8dma}DR{lB>8>%(k1Rfk}Tx?X1_Hn-yuQ1=j08gWOWSR{5a@;{sgIU{G z)(kHech(uK7K$x`-ZLt+wVdS9BSildclBQtooo`F^c&g#`$9<-0VGmshlOXpy|@4E zOl*QES}fA7dBx&ag1x?;b*eAg`UqJ{JW9AbDzrzUS{GC0NXcD~RwTMes-$8C%AFdd zyq2Gr6PJ41si+bw;PsbngIBX%k~h}dT^`x{`>(DJ+1mI3qVMm2$J)8A`c*P&!G>tG zl+2|j1giQ&Wf3f{?>rTKp;Trn9P|lekosygvkXAiou>{AqoHwIEjZ%k{e`FJ6P516 z?j&kyvZCUXPacA@%Z2b*0X-J6+Wkq0BuDI&Y@m4E>mK6Ga#8E6GHsp*hoDSpF~c$z z^Al$G_ol}y-X_WB5EPMdNestx4R-rwF0b=?NhOO?^1UF6lD;TP6`Z6J7e)30m28m- zdQK^c(o*>jUIEU{`WoU*sVo zTjrjAZSv722IuXm?hj9gZR{O=Y8&ez0Hx zmqjHOasBXC=#B-IQ@>d;d2vuXk;tFydYhiyEagy#t%fLf?yhEKmu+lv5mO&a{V{E@ zL-9tSC2hq`v%t>HU3OD^=q^dT5Rs=2aiT1n6I1;?y* zpV)_~zQ^e0>%I2QQ8C8snEo0y&P41Yc@y6gYbl45b+R~L-! zyDH2|B4tl}rCt_W*~8zimx){G%|C9GMxpdinx*#UXB%alY0B3}8f7dUQue2fGWqmX z`e#;JMI&3;BlR-H;f-%!i2f~Ce22F~j99&1!iggJ_-&M6^p0OPIVud~W7|be_P?9{ z7Z5nU{ic7Vz)oJh*O~sV(ffS3t3x4W4*gPKGh!i0MsspRo&7 zqRD47Feq2lL%jcLhwI|TaGkgOa3#e;C9gL`#dmF2&KKERt{IShc;4vr41U{#7Zu0r zdpdoBA(Daun~l0V!*)v>B&7#JBxzTtjT{Qx`_$zb$$WMz^O;Ek zK1P?+()9GsnK<31`ut6t7>LJT+Q=96S5>iv@x`;<>uK?MqHDIpR_A`Hxybi#a@r>F z@_V$0jN)hvC%>Fgnc#soZ}wGMsf9Uo{#Q=ttX`;Pww1ecM?3#ETU&Y5{fCKovTS)@ z|9Ce5rRq(7Sy@nq-$UPL;uSydgGk>m*4BRd7d6#Sbz8f zduV&Ex9Q=F>kn`Hs-OkQ2t@t4tp0G9J%pH*`-nfU^oI%6Y>jRZ>$0)?zHK+CPTwwW zPF@(Vek{ZmsqR;+>fU6nhNRrI@RX}dwyDZ3$(8OK;O>eU>>k(-{Ff8D>ukq7nwIbX z9NsvGH?j}-aA95lYDl!=#lSnTV3btGM9QG>>Q?fQpo%z?}Gu zSz-g-EjI3MvFtxPDQ<;u3l7MTOAqR00`+G~zl4XPT39IJ7*OKO-1Ijtziy zl$;-FxgUZ0G9CUi;z%PZMX-RyaVszk!CI zU_b(GC1x+cwMAd^agR7sg!h(Yj_4AjHK#1m4I5|-C`HXtiPJ>D{g|-eWvIux7YJ~JFY!H)TUwLW^ zG5lahM+m{$i{1Z4Te(e`v*{3u|G@4K0o9=9PAWQu9=2MyP4$&xL3JC|3IclAb?KW(2oEXLPVgtKXm%43tLE=CiJJQC%R zVgrT~PBeUB9N%aeIFtZEaawYfj%NGj1!{FiyC4O9GAALKaMx0B)D;aOo_e3e&xCd9 zQXf+-GF&XNhHfTwVl@?LZ8l1_`MFNWo;>6qqyY`mnDHRi}Oj>0KS)K)uEdAxo|TbTn#z7&*q=3J_kEI}q?2nW`Gx`(5- zRPT(oLFMCihO179D`Jb{g>56)$LtLS783|FV;WL!A@xP{FVVUt@eIMgV+29Hb*o!R z;BYVb#3zAe)O}~n>GOV0XE;uUCZzf6^P6t1*HE4K;AHRA9u@oWNx>|Jy$|zIWnsS+ z{KtUcAX(*C;uLA=5iL&rlO9o82y%)yvDm#D_jj@le1%*(h|}wLQUUHNcwD-(mu|#- zVtqy^m8OVlUHt6qFDVo^%AOQG5;Jt_%@4sy|5>sBEnKs&%R+%n@7FI%kJn47BIe6m zZnbx&BTHw5f9j(W0l4uD6kC0ctHctAy%}0A^GaAr_fO-dz)3b6sTk=CZ^gqW5M|04 z!kN*3Z?~wHbT!AUk=hvww^*T)^>6MM=2p0YLK#dCcunk<&#Rr!J%h5 zs?tnqbKzqY6nA}#mW#3CaaB=BoyDvh$v&a1lTNR(^QzJQ1PI)F?q77}?5P>@-+G z6tXEu85o2}1`BvYq05o~gbPS1kVjR=vp9>>u8DHUfx$=Aa?@s?6bjE`)UVWBQ!+1n z3~~sF4QCG;g88`Ln^~}2|D>Y83b#@BP|{DL$KpZIcVLDhJN#+3>Z(P7wzCbv zI8u$b-h*y*ASHr8n0}|HR?&|EO!Er7In?Y2>G~&Og>J|va2;y_61XT*TI>C)U6V1* zM?yk1yW?=r$Fj_U93=sgRXeJtqS^@*8qts(2!4=xV7&r_D)c= zbEyBKNCZ;4?wQ8h8B!pHUMidkQIK0ygv=bnXpe@XoJwJ`-5&x)pSL_kztyOuqUcMV zqR(5IqQ9C9os1Pli+|EZA>;3UAf`wfk45BRs{qEp7!@%vn~2OuxKio0g63JK-P>ng z#fJlv4U@xJTIKz*DPprGJovUXueq~f)~8c=5Y~-s+7`BE5V#}kL0phIEH0Q{Gb65t z5+N=q&SYFr3RaKFQgT7fe(ncM-VF zO8-iw2)`nfB6q3bFI9@Wrz~|5d{m|9^jq6v?~-g{h!D1TD@ox1gG*s@tt~BvCAY7l zO7axzwJR8+eZ~Q&iiMlSN1-Qi!ikz3CXl8S8{Hkt^JE>ghl{rO?5(ORqnC&0b=Tpp z;=+Gx52w7L-!vlq^e~ot32ljp^_$ig(+m8RR!9C4=wTY`>Lt`^9TCb zUCXOH-Wur**QoBSkqFXdqU@p7BAhQKG-cSfi3g4}@g_`jppc1II$243(w}uXJRknp z@hQTh6{|scH%u6OR+=zWstFVOgh_nDFmJr8%82q@Oc*7ME_X}p6o0{abu0-y#j)-dA08y=0f%~Abh{X!rC#`&>&N}+sq*CBM zYj2^JiorNH8LGgo1Wn1h-uaJ`yu>(sMOWx2sFPtqh~Kyr-=;}kJZeI9oT7@ieP4WE znKMLqks~AuPvR8t9#p#LO;ELna+lpKmi#Mf#F|awzBhkIKww!d*#%sTmOQa| z*Vpx`#1Vr2?bfT;+Sk&|>B4J`n+-+i3%m=(mrPgq8~wHJ^rDT0wqitB$cvb0_hdRu zf=Fjx{LiYJ8joz>#YksVTQ3A^EA(@>AzOw?h3r!H4 zQ>S?A5#h`fwMDv!vclO*cUe8Qu(tRmMVQkM9e$wk`t9&;#F9brTf(?^=^cjrFNtGe zEfGUd2lU8O92;hfN?4A^#I4`5#@1{6CmBOt7}|*n&im$+pUzkO|h&Z!tOc zvjV^%z91|e?`0m9yw{V40M-M8)tqY2iX-I;Z#%_nZ%aVP7}9PHU|${#>m z?GoCu1YF6bq0p=vlyVY+GMRV^RI2Pi1uqTQftJW7SbTU?Wari_zLnUnq`c<9F)0|u z)FPhs4J${z(;Roh{=ZpKhaz~E8}@V3*U>VKu9p!4eL8%kt{6IL{;{aM`<0s8p%z^h zv;|stR06_Pp`g#Z=Kj<4M5<6Ibj4jJ-6B;er*M%fBt%FR3c9>G&*-4AQ+d=zHAQ73 zYNjsss6Fz8a7c`wU4;#HGj?j4eBGmV*9yj{st?BKeJ1Bf(X@y(mo-M0w>b?s7ccH# zqgT>h;C;b@j5UpiS&(rxt(_5-ezYO**)c1|tx7-Iu$LO#O3L@j3h|uy;}C^dYQ!Ri zShy4wJp4WTD7W!DlR{C!177IHn+AL-g;;8JQz70Pbd}?(VkL!mrI{#F)lB@5RZ1}F zfcCK9Mk!(@{*I?#LIOdbApFOgqPqNQH{?_=%yK8EHZDVPlS)9;Rw49X1F~4>9fhgiq zAZmCI&ZF?f6gHd(FZ59_3*C2c5rvCf2mhQxkzRuVT}YuwFEr$&cP@pC7|=NsE=77G z%U43Ra6h>XE{9AGn%Xx}JobIjUeI{QYs}_Y&26v}?Kz)|i{A}W;C{QdAgs-^#k-8K z9i5{7rd}5xr7enuR%u?zUk324VM-odR8LiQKwWk6<_lo|MrNoD3rur0sYAO+- z(C4r~RG&hj_emXHY7UFLTx@+R{V8_`!~5hd(wsPirTF+q;|r6E#7EzyT+!q% zo5F3wcbL_$vm(i3-de`^{2{CYY#NXuu<;4tDrLw$O&M~9uL9@t-cg$wtRxLi z3>SK={0~vLE{Ygjt-C?2G6qiL;$TTF?2~}#!lIuL%Z|ivNL*3Mj7$@N8cIoNCIv$t4ii^zQ=k(I+i zBN2}Wwx`yOZBZOnq-CL+o%nv~gRrCi9O(vvMjX{Q;YP=E~`XO-8K`nd$_}^^;zxv34 z6;1R96tL;WSrCus3U?NuvPim0kV<1{NY_$hIHAR{lC}QNLAoCELem&*aY929K5v-l zlIBg3dpPjS-F98UM87bQ<3F05{+9k4TiF^#aUX4rT88%7mGGnN&Iz*X1d1rw1xZNzCbw7+n{1u4r-lt1^x75UUKP;usvgCN| z?*a-lmgBR(Cbh8zp`h5@XN|k(weO*LEWEN9Uw*`CwPY7}Kb`aw8lgk9o32nG=yebi zG%j(2M{%MzR*Qj>Boc?M+$eBLg++x7+j-+%q6}b=b>AQx1Jp_1wJ3+FZ(-{crOau3 zFE-3>$KbonN99!_R!Rb(;mM57PKNt0hj=Gpg)4X^#nw9&AFl&6gTJ!op&VJQVRyT^ z7^j#bZ??NN+%c5ye|)zU=@4Iv+yg&!vh@82Zxji1Uv5j>j740`#bjFrSAP7m3PM^* zlYUxIp~%uo;?rkrv)`Mi28IUT28Oair=#wa3fS_LJ)~H}?%YrbRg76!Wnv4Bv&D_G z^kjPH*62-A8ML<{Rhj@XgXv$Iq3WV_9iq%4-5AVbchD@#@7V@M{?8rR>*ZnQ#fIbm zsX=f^$v&QC2!64#-V_HjG?pY4N*;Htt)q_sw6Bju!KGEX)aw3aI%i)3)4V zHy5Xh`}L*cY=%1IreZb_hzd#-tU%DNjfsckYM+cW`7#KVeLD(Gplsnrl9Nb}7rqFv zC5%z3(U6eV9!FDyeGh5HJF2OtKV|kJZYT6R><8_hih)ExK98QwMFs33bQqMA-uZU#d_^*_ALQy))U8Nnu*QkpgYWRF-2~4hpA_#mSwM1 zIWwVBBBAyl_3-?k{iGR)hS+J%(N5wBpB2+!pIH+e6)=7qe=lxEWyr1A{ z7Qw{6y;>9j=wT{hjTFM-6P>;jTMGq<_X@O%1G8@xc3ii=B?g{QhlGu^RZiU!d<1u{ zxmm-&f1sBaq{)`gq|UOryh8m18zg_<$AM+>>c=lc4xm6pSD9)fGLw|S($Z~qyXd0p zAv$mub<+Z|Rifv+?D@kw1UQgzAD0C{+b2vab(}D4C^jvkmqMl#v=BAvj5VJMLyF2& z2&HWXo=mK4gtBxdp{i7Cs$|gCW;(jYf#r5mjp5e60wKzykdla1XkGj+wT$qe)hH`v z&#e!^FWw6ZBl@clVFiflpX$hy=f`hKFQF!*1G+>6~w zEf`)8;ZoUs0;^#13r%_1$&Pa&8VvBK1Mu5iCz?1+;)-g|isoHdMaNsJkpn zd#%xe?*bzBej*%3yj|7ZQC7EE)om#miPFIEJEVaD8A{1@r@vd^lXfoC{nuu03d^xU z(@Vq+)iVvW@Cj;cd*r}20ai*w|3rYyU@}?+ur_+{dQtYLnmx4LpCJGHQ*AFrm8j0S zg^U0SRyWvm9-4mHvekRuA2w0_mf;~~U+G{)=Y)#>pJgi=f)~N**s@KUgV-X_I~@O1 zVk?|C-M#kNX-Vrg**mRYWjEO$H`wTw(7l_U#YUg7K~AwM{b*n&MHr`Z7qx$GX#c)A zoNZpx-AtkxW|W7FD~|);vczfNN(Q2x2kgU@*GQ2LkSrN#NMdE_{?K;!dnfIb=Q9RecT$EGD zOKX7e&gM|MrSa~$q4d03X$A8F8IuM^dZ9e>{;nS#)D?V7bCS!=bkxwYf1*D;F3kd> zcsQXqs$4P31jg@8{pWSz+3jv#xzg@45S{B6A$xtGkgYB{1zcs=h_U-Y?*sklz5p=w zYDv^H&}u2V1L`7IVUWS)R->?)*!Dj^CBt*@SVS`@^VokfK*;9>+-~zhMWsYv zHrp?cpHD)JU~-yWr_7)-_#$a`#RJ4z$9b|bX!@*XwtY9G)nQ6%i;W$UT){P;^SV`p z{Md?nvb9j$^oyviDpr`I72AO{5?>rhGXi&M6@SZVn$m4Kd!~Jjr7pKGQv0H{)i*oKy@()r{(O<(W{bOmYd)U~m(97=YlDJzlFOtpTZ`L82 zp_fXgD;>Po7Jxc5zK2r2HQLbQNF?~3g_M&rUoUa434*tMiT1@B9k%lEOW2p2$iYGg zqOu^2E9J)cj_f_AsE}uX4Y|Ht!Il7i;x;0!_qhDBRHnS2Tz1UpmqE~#ZNS+av)Z=E zjslBrU@SFkd?nw3w%8W(A7LM0np2g=#&m+>fIQaON7P6A#74UBsOiG$Mi!%kevd*` zKDRX63?~z4Al9|CJWSO(pwk5A4n;3bz`F*Yb5v4|>r}c8QlHl7=4=?% z=n#T0!e}$PGnoo^^w!L*&DYGV(_8+Jxco7_yGA!Viq6j7`W<>FUoQ)AY=)!AIjLrL zW<5n~8Ebt77vr#xm`x=R*+jlx^`Dk=?3rFWEzdx&;{Vsq&YZ^R=2>y`YvxC&V{9*VoU^tS#nvM)WCX9i8Sop7Zsu?w!6PKm8hibXswP$wo>~+mWAk4JYbQ z-Sy$ZesT6^PULIY)a+oja()XCLDxGiz~?}9fey4zhI!9)hoS zJ|#nZBqtjZ)1yr4@Gp1}<)=9nuQ#_`&PBebX<6t(*_vY4Z#>v1VMZLlic@=QWjQ)9 zUpdjIaIpioH;H~462Zv;dh{Bd`Au&}c?kzfilH(8)rt<* zV6!V-9FOj2x!T5mZu$IMai9MM5eY`CtsFw8Ly07+x)#Y9})r zmq*Ev$WY^Gm;J$I2h&!y&Ll!X0NnXN33jSincN2UOD0|P&=HW)RS#_#{3HhL&1QhL zQ@i{Bq6ktao+r>6Z;gH$9;Qy>SiUIw>9TZl*ak&&KZ>1_SbQ8|C1&Xrz<&g=Rq(IFmt_^S zu8GfO$tu(z*pE^g@Y;@wr&VXB048>a5vK!L&v=pbc3cIcGvGx&S>s0Q zXC}plU>PGDXQr5mY$elwzeJS4#vWneIscJ%EmKd8YCEK8fUA{VCNhW)j@!{-AcZwc zq|(nv(XQ?BE%I6?ygsugpZ3$atcENfha&T?imYHtl1&Jl6wM)cU_7}TYIwN`N2*Dq zNpM)iGzVdE2BiRLzkIUI>m0o`+gqEb66DB4n@R~BjVbj~qj=4D?W22$`Tk}pYRrik zKE`L%x}Nq(#6->P$C%{%-a6G4!z4IaY^_xZi&B0jwYh_B^gi^`DOU*$DH$X*NfUoi57oe>RwFX|i{Q(FcSft|!MoJ6QA% z=uGl6>hv)xsj~qkgv*)vX{EMyhMf9mfcO!{piMR0!zBa3$N@X4FIxHfnRO7?S^31w zMr(eZ5t2SHX-G4dE-_#(3wS0PYE6LSvm_<3Jzvh?b2R8Im7S%_S%AsJKQrIRAOqe; zjqgU#&ZU)a$j>atK8C7rBvbVUKuzdzJ=3>pqs62Zq;g(bMVAXo21w@FG&8jh%wD7ae%+JozOKLhCapv!bL*ncM z_?LoW^j#|)Z)T#Cy5MPgq&Hfj0)H{8&WN*DwUF2Sf$Uk0xOw% zx+EL`c}SwY=w8e7?zO!H;#Z0S4wloU@k03w#bU{4$uiH7vBSS>G}A-3d2d{iz27BW>d11Dl;NgCKE$7!v|&& zDLLb_0}h-SQb#VEnb9NuNrWEiKsR)XXBmn6bFzotEBpG_6u**I_SCc=o7NJkz2Rg@~28IVPKs7jlrZfPAL_-aNL+KEGp@uL%^$$ zu(5n}`;+mlH}ppHk?oUr$m+@4{2aNp*S>-9hW7R~cW|pmW81Sk^3hv+DDvC$J9s7M zjrOg*QC{KUX?%Pug&liHy?m~Rs#IT&>0!His7m>6MBmw?@;g<^Tep^9@xrZu)cuc% z(i!>rU&%Hw-D2Ktk4)<`3zY$#eL+Nl4So@m+)Xo6#W0uuDMAi%F6e!?Ih3xgR@4!A*BELgEPb=*HUj}0!mRi&)vsk>AarVG6 zM#z`L8E6GFXhB?YrEbmYS5|!GZx(1q)?@Lo0mq<8ko7#-CMiY%hRq~%u{TxR9L{_G zZTN1SPZd3>!BYg>>52uZDcUhDUN*l4#;BHj@*Mlz4(pwiY(f~AIWQ${k;=_CGEO1B z5sm$nAJyBH4`lor6uwFPC^KEXJ+3l7alZ(v882Tgfkt+p#8fk*d-g6TBgfnDvQm=?EWQ95v+_4b6lR7uVx* zb~frTZM5r4;e@=%xypK<*d$3UE?`Y+IQO*|MU8yP^U)m|$cf%)ZR}yH{z*)Oo#NPs zWaNF!)kH^iakEvAUSKv+oe0dJ;sIlWB2%x{0h~ghg2@8Y2Ahj^fR-|k#3{=PX1Gcf zjN@g{5u|5YkADzic{I578fH{b|9oajN&#)*+CewvxMV4j?DmJ_I|U9Pm!lem=a*t| zK?!oN)RAoPjyZvQ)38*bnu`duQ}&6HfN(kKycnIDDusJFX7Fej?rWH*f`qA6pH+04 zZ*rc^&=q5eojd;G?7kW>8Bh`_&p!lC^5ZG>Fr& z8gSJtR~Qr5_=$0MX4y|fRT}ofx^oIC#qi7K+$@@7ah(r|h&Uu6R2;pV!rtZmB zJX#Z`fuo3^_&7z{>hrBk;*#M29qcOG+sM(-EM+XSA*$3W6E`U+9PZ?2x(Js;^kPhBoqi>vqMu<}3>6q*d z)sAyZlb_g zLa=O!;H;gPuk0ymJiiUOS56KunQsfuLQ(U?)jPYQ(W3@39RKC&( zz&yYZ%D!WW8Rb+H2?@Ax8q?y6zR1Kj2}H*$1PR4ZYnuI`sZ203f+}d78!Q{y8i8N=c|iQd$ybY*__^Df-QRKHNM?Iy#*z+VjX$XeM5`|)7P>~^a*bs zi^P6aQxg(2Ve^VH7EEW9XPdi977qm!lVZR!nMkL^w+e=Xk>?n-7FUUYGKvid!%V&YjU-HgaZPHKWI@*kPHY;)6i@ zl30gLAOq;R+W-s3)d`k_kL=yjR#eW}#A3=YJu9D6xpQLbufJ&15H{}Z;!i?Q0Y$1t zQ7JTPREK{+m-Xd>ij5}YC7*;%4Gz$d=mT^K5u0=)mHD#Dsj>NK5b&qV^r?NKY)UHz zB#d98@rxN3>=qlBc7I%y5?ZobP9J$$#Ee033T~Md5GT}uGGLE2RywH1Sdt-}H^r2C zgvIH7T)bL>lF-s;df!lDF|DC~6@@8LDhA#6jiSff5Hp`fon{uaFlOVf2MHh86yU#MLwG5NSAaN5n`l)^vKfwQwFFD0iCQ9Dkc-X=L#50_sPOOhPEb;axrb zMfr+?$Iw06nt3!W%@%s%NF2=f&Q3gvd_KmfX!q5p+)EgalVle^C;Pmhi%yYThmkub z=aIbqBqdP@p{RQS!s8Ea5LCgPzU+gb!pAUKoM4e?++eg*VK_e?hl85TI|lK%$3w7s zb zV)VM=$(x_(?Bv$2v$7$sDzfXW>srl%jZV-#rdWr5K7kQI=wgM*abt#|KP>rH{lO+t z+aOSwy}>cNVuMm@G{We6Se|^E*mw;c>M@NMT#0Yw#6`^zuAw9vhA(ND)o(kP3u4fS z!v@WUvk})AQ9f3@%B(NxDOHghWDsOH-$(sG#h^z!)DQ|!MJ%+*masBvl&BP_qoj)v1MSh9Xx#^Po#|5zK-`viuNa|?C* z+L_n}wv41|Lj8M9s9!Y@N%~_7^?@HxsLx%VLf+qm`ZC9L6~pzT!u8-*xBbUCVR64>zIS=1?zG5uZzg{oE4v`sI;rr!e)!%vBC{ z6*~E23ibU>WdFE8ea`Z@wurbst}l07pHqfX{-|)hf|&XJCa!;6#LNon(;qaU{wJb7 zJu$3@JnKp`J(Lj6yKLO#}ndi4}?W5dH4q>T9vWcRouO2K&eGu*@0*p2^jYqrdT z>Tap1cx>t2`0@+(7dN8rGi!d~nKs|wRPISF|3iNlYGXa0?abOj_rN@63>F$eNZUD zn_=Lei^C^n$)LD!=Y6*k&Tv6`F7@!L+)Vbyh7#To(Lm+O^I^}t3V@E}1e3GXe60E& zuM~#}1#dAPNd>Ki`tS?e=}-X=WxYTmjHx#BztIIbmxL#QKBAc7)S(B;;B&jUTV`gy z*K#o)&|lk#tE3^Vf;xw0W`1j{ud6{foy{P3E-I0Ti^y69pR&vw`6O`h6r}?(*`}uY zS$E~=SY=)bU)iOC(V>|n%2pCN6A~@2D6*x~&xXrdXjlQXJIf}a({MZ+E_WV>pd)jd z`7H2Y;Pa?*-11`}eUU8=H+rTYVlCw{E&(kyzeowLEtwJim*XX^xmCQg&!i`EOVX2i zr4ZE0J#oOx6i15zR}5^YKC{1&_Sx&c#;Ljc|0|?*ezF&GEcW`n9ay`|?{}&E)VJ-O zIo%)k`|G(q>bF;Od)RNU>@{toSb7vNMG@cI);k zyPenV6?S_{IWk{qw|2_L<#u~arI*^R9Uk&xyR|bslp`n##~kP`WC7$(xz;w@(~$3v zvXJDDy9Kp*mjFx(gKAFRN0!PFRB|!nUxo3qii@#A zy!xh2z44Rk)RyG9-3;l~5cm$487}W%D#PV3mPf=<;RX{hp2{aAzOV%ep5W$Hs8hn( za|HS&b^uo3)bzoF4}O6OX48_9Akxr$^}K5Se3df_$QVKe75LDL!e)+eG+pKg6MiOb zDGaUMU-~<@zoJaAodpjA2esMGoa}aqe1sl9m2(n5F#lz<+1KYOrxZ84$^M?367L**+(DA5kUB$F8-E&@(ybVc0XaW_M3< z=5;-yOZBEk2o`kx>`tj2ieHuZqc!3W30n7M)y>3a_LUM~r&(LIWd&6~6p#|{)V3nz zBrz>^s3X`%t>|8W8iN2fX~D_X)(CP|#X^BM=5db{1qG63CDLn^5r=^)Bpjl*1kqzb zk~D82j&%rGM^gQYp44(#q(QJhVZWnR*v_4Gq1)XQCEKW!q1WDw9I#lh{;wLCv$_`c zsYm+%^3*~m2dL#L+vL+c#`5NtZ9!-o*(q4MM+hQIHfR(i)6mL})jl#q@Cu-`eL;@r z3i;B)DV;dm&N~)PEOm&j$nwO(a&=-?v7XQ z6quZ>qxgQ#PK~;j@wA}^ zc72sJ8j6YdRdOq|OCdpFGFpvJJsVR{-BceK5i-Dd(YcD-Q3A;*w@q-YDSIzJ;jQCx7t$fBfz@|MufgM)NmFf7J1Y zEJE6jleWHoEba$3)Z;Z}0YJqNoTc8eF(e9ay(fX;ZWF_xsi!HV<@gadDfDBsM~Ghx z_PFOP#GF&aSqBa^7K(lV>2R9aU-@@nnyBhBy^qxjTa+dZvSAS{%~=eTNTCe}0_hM2 zK}v=*0K9}Pe_*=ORHnPxx8@-MtQZ05$iaVJJf>?ItonB`JCUAe#8nV_ zlWw+Kbr$H8Vp?RLu!i;IlkE2a8nt|F(fJkN`ghlfkS*>B-dT)7ZCW8B8c?betB#=V zgdj1D%)lE&x{1}yEt_LSR^qSa^0@cgEe3H<(O*(1H~#-s|+V~NV{MU5ja!`i&;G-R%|Z7KQ(~$ zabW*>e4Y4Is$=I!9mR8AlLRC%B>Q%Wi}OS{9AmF;5u|{r20_v$CYmG=JmCCf3m5vh z8ADL#*UiO#Vxhv&C6CyN#~u-wFvJY@giQsK59FbOU5GAh*%Z9PQs7Xr-HiDevJ9IR zVVzqKtH9>h2%Ju|jz}P4(oT}apzC$Blj_nB@5E%^9>ApLpx?1uSW%W0n5z2|iZU|P z*>>11z&V~hVd7ZRE}IviV$m8LBoSmi34o2XKHJKIdQ+5S^zOI-X%+kV6jQe`cR2oBK> z^eF5PPPbJbpkYZn>ZJ=rF-0$fvl2M8gP@N|3tD@*ZOGP3y)+mLk}KDQ?c3U7&1kZz zMJ_8sQM+Zue$CUZ(Z07l2p_m{b}EhHgs4=DdO~!)90dkeQ3Z#$wBgwvVh+BXnh74( z!x@WZhNm1OZdttL>mFcU{FGFroyCL_AasfwcNV|$Qlg$li)}BPw7N!%SMB8dZtWPB zdQ`lFowt^-$O#{0DwjD=Nq_9jmb*!C4yUMNhM39auy3!t;%l-^+;SFj* zoq%&&4^@A^hlDya5Q9$u*Px>6f2lCJ{?{T7LlaPP7dRP3255ewVCixY%Ve(Vlx z&*En?(s9m7p`a}LJ+Wu)YlgZk^PTn~rqU@!6u>A%ct{VH5K%x#FAX(F7bzfp?TsF) zRk`U}A^wVBLb;9s0&Q6_P-+fsY9aRP{kk5CweMjhdp+=k=SNWiwu|Byuz!v~Pc!;> z!i1&4>z`$}`X^hX`4J99Aec3=J;Yo1Mrv|W97)NuW|9ZX=8;qzOAtGz2%MdqFyqIJ zE31@wR5O_PTF64SBxZ!^q)qrZ@ytNi&)_1H=>(DJTauG)$DHnur0knbiUV|wqd1Xm zhXbzZ{BcZ7jLh^vd_j7Q`{EcIKPIvdlwckopP5Rw`9BL)gAcOON+Q=S$z$4$g9XL5 z+oV_+L*r4DU{d@cfoW@mU;axO9K-Sf^krdtzph7=IP|dv2EJ7d{ObhN`2Y?;?8ify zQBR8FXi{H(9j|gP+W55qJNJU&@wmsZ(d#&~Wnl%ApTN(;*sC2F2%&k(A}#cB+eS!B z+4pDg0D6|7e zc;m14+3N=Ks?Vb1&}XardscT^_Hszw+0&@o!)XPC1pSsVHG1#4e zocj&sP(7b7g&fU^Pg}JW3FK2YWs`hegS>>&5Bi*YYV|jml@A-OW`7*m7m#TOYM2Yz zXT5k!ax69Ow_lxLBBw}EEpi3Mz#%^|yX1LtZ#;8avJGic?lC$o2f@CtsG2vLP4}Bi z&b&6oJYttgS=`$%)3_aIUBIj|c9Y!Qx*&N%^rw#BA#F{qodnJ;$*HuQL*(BySKRL4 z0ZO*9ZGdG1z7tiZ{3<~?o|q?0insxx!POK#F3;~Ne<-I@`H@3qPJi26eG&={sT z-yR;5d_+HCiQ=jrIu*GV9);#*&r8dY#{-#zfguUYo^O+`B6rQ>AYH%9wlUlvDJmZ( zySxyA)fvMyf#OyQ>a#h=0&`?GM5tzBj_vS8>Mr-dBP9Z6VzZo`Jduu3uAD$X2BeRa zTx32F=MzY?`h=tJ7nuAmT0>@pOOqv7<(A}$MD$babu7=NDDDGTE>)GiV7XFN(ms|^ zRa!QmJ%t+`RgI>mI3hKQ52Blh??^|HCpD2yvPd^MFt;#Q*G+&L%y}*-^?0$oE_Dqm z)L2z10ZKh1lvjxXj9#9)j$xY9O0rx@qKC_J*I$N@fl?g-WkcSAsZ!o9Eow(AQJV?l zN_oJ93He1*xhYfAzFC_@9itIt=zyfC`q=md=`N9#IPUicCZ>>X-#iV;$g-pSxlLX7 z@VK>_?$$KrzF0ul?eq!g@VG(`62zU!7}9Q#8_1@hC`TaH6x>k~yhJ4di8Mt>MwY_h z_nYdLhRvo*_D9ZLoYjlB^o++G9er*{O4@v>$Vt==Ra=kXgD*wy(Gx?w~C4BA-jlM=;SqVcf2Ucwv4O@k&|5{$#}k;~^05K6GN+!)BDb0Ojs2 zDfhNrQXQu%x$BfmuJNZqxx!%G0F@{)%++-|Cr4N3f_Y6gBA94fwiqf@+by({q}54a zasC!(NO$N>{kOkN3s9BMc7NLNum zt3Ho#MaxJu&$g>A8O%1(c zxlw*PjFDdT2m!jW(1tO6+?90kHEof<1Wd?OvL(5i#8GfT_!)wS{iSt1T`1g(W+9Mv z!PIuknB5HlrP((@KXsF{AMJ&9O(x<|kz%p7!1Hep_u#XVr&CzuQ_Zu6m~$x1pt7f7 za>2giFuAyOWNYq7e}H2Kj!!NtG+jVC8^E-(JId#_oKI6OX1JmAUTcre0F+)o(d}U# znWuo>=&SIIQtP8L1fOQ=?`Zb8+YstP_KzA`trlbPwaVm+4*v#CCa0p(sMaS&^o;^4 zT~ihaz04mPhrh48#~l4K8>L#%VL(QG9LJHQp#akR?Fy~|;QZvE{h|}DXXNV!mSWUV z!#*VIh{?XD&Lofv14rF+(6y11DxVht(}V2O=5~cB@JDJf--pkiy<5*h0zX5^YXW+F zBAq^SrW8nTfi}+Ut?_xY#^%kL`5I9txfWabnk~tda1QoTYtjSE5pC?VInSf(Q9u(> zp#nMhr~~5^-#zLKj@^jGgsLbAGh(_l4BP7!VzPzUNs|svmqBjaCDdDQoFr`6ZBLe+<$^HKU>^^I-RgTWuJpmaiovGRl?FAi8l$Qq zgSs1szIBfGGH+c5*pOk&ZgT?(hekDsrRvZaD$KkvR3ZaOIQvA7Z=r-$4CF>kql|z# zG;?wAfF9oM&&8ym$vLojml(l=eh+sS-gbBS#KnX#2gdb|`0m`77UqOH^~n9ITGBDf zUogdym$D=uTRpJ*iy|0bY!Qgij%b69FbVV* zVEPPXuIB17jz|qJa8-;%@M|gedQ(gp>;b`UuFE>D5ls8cX-#7rh-f8(vPBEXq&~}g32uo^s zNo39?okLPeHNgicuK1RF>9IFjHRRk$O;_AWLUc>ACmhY@7Il~y20sqdXASlTYOtF* z0hc{cE3^Kf;fUjx{gDx3rp>3rM5%NC75l>?r4f56_|uSrsg(WBl{`@sIrgLC?mitr z^~U6~PaC^5Ti{LHUVnb_aFB*#rlm$89pf+TP1u@q>>%)AVs(*d8vDZ@n#=><1&aEx zr-jcScGac&Jf=k!yU3@F)ZgroVg2p(yRj z3nRX*OvG{~(A0Qg$rixiuD~p_oK`(I4;uZ;A7JLKDBd9@(7J=bB zMhDDqZi&`EJ6SN2;5bZ?1lX%cg1#VWHNK%Q48*6Hqu!UUWt%NF%d%GNFzTkGs@Pom zzNG*4utN`Y-J&hwbhT85kTJ>5&yJ))8#DUpaDAm4Ry^etQhQlaHA6_WTH;4HCKK`W+O zMv$n&12!uC*o$oW*o)Ox`}HH9iCDik9KN4{}&MqtFMddpa+h#%(H zs)w3=uLOfoGaUZ7!Q@15%f^5$I^Gf`tl6a>!kzBGnALnHpNj^`Zeh4yq(rpISWr( zkXcD1_?R1ED4LRFDwjYhzaS*-0!-o}wz-MQhqF~F)~Xd8@wnQ6_7`%D`%#&^s1D&8vq1hEJ>(I#%=rh2Di<4Ez zk8{q$+?L*-9^f8D-lqF zCo0hBnlKWnKF+eSuEZZunJo&mG^94Hp%*}~NE8ilQRRKFQKl0XND1&+Kh$)Mb!xB= z882=MG_12hfd+&>B!PBt8G&}MCFM>eOjbn4&B@ck#|dN0{jgF~q!!~N{xQLas0o&| zUm2~-(>r~n<}EOKFgVN^7DT{z`yqvrEa+w>QNixR8gH(LbY<>_)glw~)xDVMQ{`ov zj)4!rXRDcq!m>Os+ieQYB3_oVXcOasT_q2D*ro&gv}$-5_X=#F5Cs_z5DhRf4~*;Y zLH^n-dQX_yno;E{$%)$iZn71Y(Bnw7(ra@} z(k*&n{CHU=^FfF$v_2=yUJ}IJ8Wd5Sg3jWvDIY0Xz^mst|$*^ z0q0xnCWc_O60~vAq9&}YBS&g0U)#vODA|(D&BK(%E!EC0J6w=KGfN3VLA|(gHanAzbp=8YX0@na0Nm_`tQDg>`EU*I^9@!?mC*$az3lsOElM8NP$M zqHiAH8q5!=6yJUgWRz?x_WAh+8Y6#BNwD(+?a-x~T4b40U{Aa(Y?a=s-6Q?`1GW1j zwfhsb``w+{lIGBmZ5E#DWDiIwq2>eqr^ON78_m~rAZOMg?N+fut37O^MHg zia$cn<}xqU>Dwh$$~Bs^>0gM$J_KL4XN4q&2pJXU!2srw0YTwf38e!{ukam0%*`NQ zT2Sm~G1X_!Fnv)hpy~m0PtM$9QhEmjg@n;?W!H5>1YCVcgIv&Tmix#=17&8`N><2? z>huAtv<1vV{8djflNB0_3OT6i;8DN1?14E3z7DhHo?x6iRfaQ6%Vp&=TivKAFgsw& z3W?d?^tlnAuGmm1p~S$CNZA*kbUjm*5EL>}u7}q4oJf?J4lSC|XaprS(1r|(I*A9` zfXA7s_1Y$F5S-~T`l1bcRzSalwDH_15$fR}Ww;5_qzo(;RDGk2)(^uBattnG1|3_% z3>pSJ2nP5tQZ{U%BLT~V)1Cq68nA@UsF^^T0A|w#xHeR?rbg=;$463#cN6uBJ+R9; zkghWe?F@jBbrHWKj4pwu?^ z|DU|KfwSwX>i*AtnfICGHcj71v1e{yG82+al7=)SP05)yrGdWDhN_f0oy^=xCYj76 zb7zuA37q|f)a_CDwC zduJwn{eS+S|L31Jxo7Rywbx#I?X}ik`5S@R{Q516!DLrwwJ-TtuO-yo`soM>O!sXLb=d0xr1yYXB@JV{1klV(} zi_%h({7$a28WuBar=b4oQ4{)2t{iw4aznPof>HMS+LYa?@JiE4aP#2_Tg;&Xr>@Crj3k$5Tg8mmPE zvrgDZ@0P}6u%bQ`4MhQdZP)b6%FUQ=>MBnyWEq54dD4NAJ&W?+(%^-Y!rJf?bvXS` z2a-0eny>DR03i#{8M4y|i0S2ZQ>jW{#)8v1pOGC+!ApcroVBH|O3;*-xFJgM;Gomc zUQ#zCmTE+O8&RrOP#|7Jx|AF!oJJt>(Qz*PIv&#@!uzE(X_ToN%JgO0a{a8N7&oOe z;_0ea;>glds3Vt)EzY4fDXHXX$*4?k*q)C3iiyZF9LY6IH=ho%jQl#8MG4Qy?{BI& zcC`_`s9Cs&kINZ{6`27$X|DK3s3oKk-6somS;3YcQW18v_Kgnt3lZ_`1IsB=kEks`TUar4!jk z^}099!Yw)Hs%9e0F6;2R_kXX~y=Oz++wu|y=r^?g58Gv#!un8PhXlIn3$V*rY_K!e zL$rBhwi(Z!q#*|`WPh%mOfJH9tNLr9f7@? z*GB)&q-L0{Rzs*Z&9-P=q=K8f$G*aWL-K{F^bzlSWLd}@w}>IgAn|V9{q9%}Wip>cWH?K=A(f2+=Vvc8B;aTx30{p+>=ev%x7NS1f8typL&^XH_j6bb|18|g`q21_lR&LJ`^Sr1Z9bz z3KKu(6CVo`XMN(A!o)xJiBE=!PxwULIfeBp{#R~Se_EORz!+mw9nU@dRqWZduk>0rNd`r z3-HobUPwYdvQ5`#X^Iu6K{;CS_N{5-5 zN2L44Ev5IV1=%ptm$2*A319Q0&-yl9Le^nEjhJ+om@IND3wpDtLqv_%h567BR>|M} z`Rt5@iqJ@d-b^8LesYoa>GqWR@`_{Q+$D7NiXWos38Da_(uahvRFU4ok6e_Wm00vE zrVf*LsHTs4H|63L!GzC)p|v2b@z>P;Ptr{Wa98*eTO_R3v#d&SvgCE$y~5gW$!_J^A%+ zBXj$kRFe-suC?M%8#Vs<$nDeo#8WLG_xry7LWcN_l$5oJr!O%`vGj{>&P~br&K*K0 zS7Q{hfR4F5vv3iE(JggR)B&m~+J<5}8`8u*>uNH-;NK?n;#o8eP_6BPon4KzQkaeJ zEF_o5#kk?6BOvuW;f2_V$*3K8z_gHUQ76A@Y;iZ8`h|0azYsbLC+jX`Q1(++_G@l3 zl8vSpY!!hoOSwfWZ=+v%ewEFMLS{_H;q|K!pQ674*|;Kq^ABGLi+I&2lnqk(xtFBi z`6H+Z-ob&fjTK;5nF$2u5vH*FI6T@+5ev$r#UB$b$)l0)0X@BD~2F>Tn3i%U)wk{8b%6|!Q?6;-9jtTq@d## zN*RaPjv>kO)gPb36*l(__$j~KHF(ahQMgm(m-BA=)3L7mKzhuU=0!u^Pu_`Ue=G~; zdc*h$#QGx9XHgANchewH>%RR(hYX``+`dp3;e0X9GDI zp?>2XuPC8JCJyKI6-~#t@4VwtI4Y3(WZdo8l!}V7lICp%}vDE0Tdp zY5oO^v5TQu%4EO3m*ey%~!_$9Zng zQ?Y;gSHws9zQqo+HNvT$M-*jpI!*e>q12@eJ3KL+09Em z>~M%@u4=L=WmcBol+@Svkv{hJ0z_0PlTYaYtm$;Ve(k?Bs(ecQIkIW7F4O6Dwf@ER z>uilok@H`tNoAg`G%aI)G1RCIbs_$|`aW+ak@aH>wsDm{Iakd*39mYOWvf)!)~me8 zW6!g}S=qd@)k%SQE#hA`UR{<_>-l+=^Am1!Np?D}l)9;_);}Wtd|T-i=gL+Ipp$D7 zHLg^!kIFtjSK7wiz?JF7%|J4)#`ymS>j+`Ph@X63f&YjtqUGu2KK~+8vtOyHB)siKJV19StqK}o?)5ICOntUp z7nM5Ob-&=<2Qd1J)JpL<8K31~DYF(x;Poo-89rC9+c_>Wgfe$qx6X~6lS^lVPJI!N zeBn#tvc;) z;uQ9B+zdLFgns;`(mL2u>lRbZocz-Bvev&CYrRNw^6`!` z+m$mX87poLG=XmSwvv*wEOkg-Hgj;1zvk_t9tg5wz6dr*-qV|zCL!21 zoXuZwyM=6ifBbK2^Lh6RgbgoYbSt55*LeI1t>;%g@2*kO#;o^zi?^S5^N{3fG~bny z`S08e)vXh@EGL1#$ubeHij?R+-6^mtIiLUK+qPi3UYsm`x`S8D&0R3OOzsrZ!|i2Q z_Erzx!XY2ZB1L)dZUnz*6hF7nqT%S<#P-WtH1KDzXgcc`jejfc{|k#o%DpLDoJ{Bx zL(v4xlZ*|wbkZ4oq#g3!^C@vwbLsE3ob?J&DxYj&JZ#; zM!j0dH~9bT$}^_VR!!I?@h{SoOyuo6klY#S7m0WnG&k(`yP)bfa<`?t*Bg||J>C(Y<^2uSD-F9$ zEwH;Gf`BupG<+LDgI^kpa)A);a&1+xIG$}va%L>xLa{*Zd+LKpSe&&$Zp%xwk~K|i zN}tvG1biH+6W#`U5kdY^*5FznV9;b)>7PjV>nwTSzboxL zpQdAKy{Xju-%3xZr0paHdg04{(!R}^Dgo*K(}y~bEozCH__$1=s6z}nK4kKz&IZ3) zW~EJJmcV;nP+W+){)h{4DC_*!+#a;eoICX7xCJIbQ;Hv3ab^tY<*8Awm@n7? z3r(PI|3G7OPHva*UzT8pEiU07Eg{g*|Bn(*qM^TxR%;rc)9t`ufmQdy*I2gi*b0Mo zdI_YCAU=4K)n$2!XM}Kdv6_-!RK+xgh=$xn^+#a!fScJ&AK@Cd^)%mIb+;@TV0QIW zQsYt4mL}EB1J+GP_I8HmlfO*C-6(zjW`DE3S+o2kFW$+9$O(%Xm8eDLwg3}hc&D-? z8`0Z#~dQM{LFF3j|MQmR=HKqCq|42c@Rc0XmvdvVmp~bK|CtX3U!2dyy{Z zSp|wb{7Uoh(oetd;|pP+TuZie-V$oRGsvTMoZ(PwoAM_BkW}d5v~H02)exJJqxHn3>K!=Rpg)EAVB_ zsPuhhB+uAk&_(3mjS=&>2$wu?{Lz{0l?^|-z$d|=255pS8nRx7@u-^1<8MHbE=Q)Y z@%B@SD5bOXDX|E6)V>?m@waXITK$ni`ZdPK`5I&1p_t!V|DS#Ccu_Ob3E^ZX!LhqW zrT)KXF0TL3zbcn27u?6)3(HC$d#A>m$NVk*wjMbf=5$b-sv^?#pN7zC-~JMjtf;;; zUn+gVSLGDmwlQOG{Zq_ZB`a&zqFRhYz|0!>R_0|a+BSspHY zeN;=7vXd!2_ z#EJL;(|&8MBMQ36*Ekn;QmJ`rWzYDS#exMGpj{!>6DtfD1i* z`*kY`_}cXCbgqAHq;rJddO=C_1{rUJ0;#}CSD?ogI1h-t*a)51)eNKCL#}gu&epjD zb-$_ECZWk#(%yDy(J2`x%s&%XXeP2XqB+iZt!N{0yk;5p{+BD}l&|mR&IUTxx6KPZ zvuW1G%=})F!Z&7nN=YMM?^WS&LDr|(!X8ZE(Osf_Ko8o^nU zF~kodyxa`h+l+1ck{n3CTTXxRFT%?c=Hr?K5|X7H8@LixSw60ado<=kHT7y%y>JRo$@ zjQv;-OeXos4S7KPcyxnrfn{$gysgOGwkbO-o)ZvivIeL!5t!D%l-g_rg)fVjY?fRlnpFRu^&O4fVya=0t4XUz#t zS20D$mzp6yu;{>#U6U{8)m_A)v#pqXDz@^Fg1H7~8YVl+Ghs%ogk}89haR1Y^Y9QiutX|di)p3vg0!qFNnu=j&H(4zC+&E4EH6^on!yJ2~lVbEVg8{ zc#YO|@=YV72F0tz?U`2G_%sv2xU7PwyXq3MtMVEG7|q-#jEYxv)yOIQfV%U_x3mVN$e<%*>B#ILNvW zbo0|mzw9ykfikV?yEaiAHPK&pzOWT$V4|3ADT6sbQG7(4yTkmGO;n6SpGC=FZ{x)y z_kmPL$FH=os!|RPe?bdLWd6t|aceUQFgJo(F6EYGEPiQ9xn#MWh`2BNG{>!cvE0Z* zzkJ$2LZwjpfi%@}Gl2(?oOfb+>BukNEf?*aIE-7YLGdj%g(A?|VXksNdy|=&R^6j& zLlE#uQe6;Zk|`jW+ai+qrIsVvV87VNm2_&-qSP+LK=`7qK2giJOHnq6iN0Oy!?m`T z;O>&})~dIF@>FtZD9A@_elN6y2CX!QLW^rGCNA-$@*()S4cPbl#EEVtFdAX*I4#Ag zJsp{FSuFPDMkit|ZmtRHhIZfqh73jKUm&spzRac9VMaVR?&emgsyv2x}i80>cXXI$HVLCD3hRL^cX&Rct>-wv0QhQf( z%<}nud&DX*#w%#P^a;gQ*apTcIL3pkwsq;c9sE|Hq2$^3!OO|&yF` zY0{+aqzyA^O_JXdVFX(k9~)X{bB3Bcb}y5`KtaM^=m#;m@DP_9@_ZDNnUr)E#A4gV zN1MUXGO@SPs3pcFtnvM6T~Ji0qmdvPod`Fvn;e5Gg@cFKz71v)V3`g1GvPSum;5`O zG-mKuGiMq(7?5OY$&YejCSm%L4vF(o3KMA*v&n2BAZx!M8&(?arr zXp~Kr_UXTRq2=Z43oDawz6*^=1d3pWw3gG7Qsf*}y%ti#Evu;YoiQh>r0F4zyC)l$ z&DB(yZ;aEECMw%Kc|Ch=_sqeytSC!E+w{99a~15K8^jh99xt>_g6eW3$d;t?^9Fvj3G*-%aHdgr4o45S1oBAZSz zub2J9bjc`l{BPLUFkoYEWp!NZ3fQ`iZ20BE-qdi&mwv}QouUc`9}U^drmT$u+!^~f zO^J2$SR;H6tmDJ^T(ikY@ODH z7#u24AVkIaYA~oco5=cb`q1kS5Mnkdk(o3flpUXTr)_*wr0>$wWgJy-A>|E37>7B{S=uQ(@ zBNIFoB`1Cw$GON@`n2)XV>$Mhb8Y65Qsx{KRImo&fF;WJy^6I3;o+E^uyK=*@LrWFz=5it_PnofZ`Pa?xhi zl{u_=A$1FDk!8)OYczR8H0faNJ2l8jK;u*=18fd4Hny{C^KqBwo{O;4lVAH_2NO-g zlS)XQ{1dJQ%IWy?&)7pQx%+{sJ;{F5TFT7a&GX-md0+ z?|&^ zpV0D-I)uS0s0}yg4qT2Mkay;$rmxKIfq$>QN6EJNhLYdfFqqGLt9~%E28V;;orKw7 zzG8#P&l${@a=~Ek`S<$4d@1)b2BRJ|JVyRp;Q*fNTsInj&E$4=Xntk%%(pcVHlQmY zFQj){O-u4@LDSNT7&wr$PC>YDso)-{Icf3O%&W>K$N!7u(nq!3Nso?O_hPfs7_vRd zRZZD`7B7_}EJ(z7z2`;b@&BA3k^Axuhtx>feYZCJsj$oHq(-< z;xipg{A-X=k08=y*0k_EgKr4yN*@qC?V#S}q=TxwoEqd6O-FVd6Lq(d>~2kUqu_P7 zv$ope+{!3Qe($z7?&;>)x$oMwOxul*ajIl3f=OSlv)q9$rC>{EH?`v+NYe{xy6)HbpF4aYd5B***z|(qHdE8e?baDLvpDZVQ~Ph) z?94})vw^s^+YEU;4$Y0-Cu`)nXzZSI3+tmDi#%*OAGf2d;6|#~Pgtv+amQZQTO`^Q zG;zL9)Bw^j7@mLFg{)uo0%D;S#J&{8T^C8$^^HMj?0QFn_BuqPSo&sD++N>AH`+*B z$My*1^2U9V46v^>y9OF0IT>tm!QoCJshkuGG3xjme<}j6$lH*7^n>Wqa_A?mllGIJ zB8cp3kIY0##}xS;xJo%N!4IAkC+(-MK$@37Ngnoo7Zj+YxjuYM97`I`N2s28i1{zU8_b$q@#U#Qj8VSkga%Da(uu7NgzG3TTcO1t=TCNGo4 z&}-h~1qo=`i=>+oARw;;V`pNs0I;nSwjnvhw&N8>U%B`FpJkeHlP+`T&?qx9z+Dwp zhyPKJt?p8OaF~@01%@KZqKjTdj|Cn17=k)8zEdN9us^<&%Y9AwpdLJaQDGz5f?5q) z!_)D!6wk4x_9P#BU z3hsFG*Yde6G^b{K`+227UQ4Adu%zwTp5g<{BU?QG!LA8!sdhi5pAi;V(FyzF-qKGA zvUyJ0bSu;qCEjNB~Q0Vj_J-eh7^M>>JTfRw6kpC zT^)my0L$?4oIj1mAt^s&I^U3fpFBkp@h4M#x%c1m=!}d>8h}pCs9#S}&XuamC$QmH zzgB#cf_3P$(2Zo1rUg$Fk~WiQ-A!6!S~#Y2B18(c-kKI@R||qS&?GY=UFm(dGiE2b zTug!jbzGas8QgNHmQ$;u4+v><_2fH#G9PXnvxxs@PdWsK#6Rkve0fMR324xl+q601 zXDL7T$sRG;92X@Uo0HGja^_SEZ#f`b8-Lqv18vg*Ku)Wd-2`t!1TW_J zvY(Xp+JDO1Tkc3(#ttHVaEdS_Ex!V){eIdR-(fzEN8Q^?g2YVV#^5o|6mL?;Us zoxu!MDWHmv=+e1ZUfXp;@gfRwP+WcCEm58BE=qS|>L)YrINnMZowUg?%ZrkCzxRR1 zqw3yx(WJ}s!IEq-X-+hWtL6509KXf?XdIogs5;{=JgBxDuoqBkY*i97c~ zf^I|A2*(>-(y`^{B1_FkH4#bN^QV1TIC6w^#(8Z|8D;27^AYQkQ7h6<97LQU7%y6q0#PI`^h65=?+845DZ?H~x7c1q+e z>TXBe$f9cSjcWzV8epzlI&QW@BrzA_Ow3hK>k|3|DH&3Q3@Lo5AyQy0EuAiMFoa22 zP@qt#%Ige)9ZAAOnVz+;U<0dZmU%B95R{S#AR>v7XBw0DN*eHp@6aAm^dB?9^gmdl z2&N^p4%Ymi?r9zWP|Ua8R>b(VoU#-w5n2C%=<7LmLS&V5eYq14gA`)1@nI85-Yr?FNaFL_`qJ)+*8vsvtS0oq@J3-9RDX8=bmz#JfR3W zm&k~cr*74mR|`o@NM;z{(=KMifNV=X@>%dOsLi>#;ISf>orU;AwI@W~n$hw`XkxzuXyj z9w~RlUH$Dd<<9LkETlu}Q8InD<(GN;@%E2YZcg(U4b*Z?#2b9H1i+?JbTSwB6zm zVj6@q7NPbVodRj}s-E-x&ET}83OYBWGJcoK(yAp7)D4OH)(FeQ!dyD;E4Io$O8*vl zcKo6_fBk_6rOs$Q6`{HuX4YjvOo;j=dDL{RqG#cn!ioOq%_2^>bF=_3Ck(@d~L3X89P2mw=Bggclobg!0Xuu6T_DX=G?G|abIk|m5KE(dp z;=-ML{Kp09l1?!T-O)Z{G-X4lFr-_C97DC)3wtr>J>7QT!@99iA^|l@Z9SAto~Y{| zAu2?>wb=2WYk+VIvJ|`Q4u*V$Jr0k8uxs7p#W?heAWRdY-O5k|r`L)5{bULwc01^S zs~8a%a*u*f>?jJnMKqSvwQvIx*~Vef2FOj*O5~W9*=e?iF5;1aq>mL9-5PSq{A}Vs z)+)b0@dzDX&E<2)inqZ7%zsTF=}AZBa@zLa){WVjEP)e^D>2?woJH;UNrroqQp6R| z##+Kcbw&|l$EIBVz=E_fVmP5YL&KU>5jF{Q&s7o5Y&8!S1q#Ko^eGOlk|wr!2^c*W zG10WB5&kDoC(YPI$}oqwX?>)A*Ia+@hUMFq|JVHQm>xDP)_22epQG|~BUw@+9*57ud+$c3iJSI5 zcw%moi*KNu8qwU*9Mhs-g|WzUkok|d)@{$xd8Vu~pN~mtl{r#4BPL%hc%m}Mn3r3v9IUqjZM z9SpK4Fp&B&%Vx^W`Z;z~{OH@4aaL{(T7^%7CX1YtL3xh@m(?iURF_ZKKvQBkX(0)X zJO&|T;3Gi(w%gL=J0e-SjS{fckfQYiWg@K{_`z{0lyHQ}xsev`PuA!Fp7AMArm>Cl zB3h7AB$-`$xMa0MideWGuRz{9)z$el1#(vy5sziC+A596kg zr6is0E=_44otVH=YAd@EVH+9`TNcJJ{`g-vaRFrLn1n|hWuYXua%gKbVo#II!Xf~q*fh}hW#;tr~1$G|Zai<|NfX%tPk?5KbXh?}`*zwqT z0!FqpKz%vq40(S`$-_Y~k>wgXgzj4+TuwD*KD)j`uE{95wtt#EN!^TrQR zudEcQ2>7RU!3Sa0D(v{p+i#PLmrOw)paa@Wy?oZF3mWha<-)!L%KT?N>TnV zC_l)&*UBeII0g&`C-N==6z=PT;I?5|?_v#@g%q-2@W%?!iP%JUjiY0)Z^6oz72Pa4 z%k!SGG@Qos9vb1+4Zmq)?)O^0^k!bqL30bZQvL z4$ZAgG~YPQWsA0z<;oV#{LwHwH4*X}SeO;#k?67XqD_h7V@Sc}f<}i(VWY?!U)UV5 zX`s)b1UZI-ai9t~purHJg<=9>#Bqc?&@pX6RpS{>YON`e77UsU>yG3*@6x*VhtA3I z69Hrck=|8MRk*`nD+7l4jEQXd&~qyt>s3p5&cwk@tb3<2It7SjC3DiGkWk0t&!A{W zpO|t_onw2nT;vBeu^<5`W1-_j3%gbRSd;Bmb5)yMvdSd*r_053KW@1L1fU%%!~Ni` zVYs36uV>F*dW7Y{|0x;O&v4g9uRf}O&%VPH9MgjF>nXUlr9fLW zJu~yOANj_we?2Gl300>*>i^l_{Kr530DsR4m7~^j6J}dnJYs#KyB6>7Zj)WpqKPG# zT3Te$B#1{u8$5qnk4todr^8Bq_*YY%pR-Z*9V$-yVn$BveZQpH{4o&66zIeYsZci#2>H9lTYbSnBr z)xY=_tse!jR`iHs1e@`FU{kj7lFdy~Dej7k7e%f7bOhx%PESt3Xn;wGe^z_9XWvPO}+XOLES1xw*%&}R8MF% z1aff~+)L`$Bg<}09zHe(n1h2&4Z)LOPTuz9AO12*{^84zEV=EC%jEa%Li)n<(oDgu zOJ3j>9q^dxg5pX!)+kWiLZm+KGfK6Kgk3lz+D#^tVqf5C*75T9JJ^!_c)H{vYJnBf zIhCEJ2BF#&rk^EpA)DSz2Edl@ojv zG}xRx^c5RD;rleAwP(h)_$IektF%zlMIR_)VC2ym{VJ40xpQLkES)YFkjkYm8)I5dvA3`W>C4Z?nB6dW-{1Uu%b5MkJ1P|V zK#L%c5r1dtZ+tZz#8$1Pf76{-v%mSoaWw3xzdc)eMR98mxydTutLi7pd6eyP>(RPm z$X)E~8}Q8J;?^As*`(zj{S();e~gDCac;+hNA70>C+kf8Q(|c*H(xgpoX_b%ZAe%!*e`M3MrUfr4H{jmaBLQii4h%n z#J+|-Cx^WKx$TJTu_O0WHBFk0-pu&K8pN4)^5b`A*(^Y4HdV#G6qDKXCT7PcbIQr} z|IW8QzW!K$&x!tLe&f04788*#qw0G-$NEo25YzrMi|^>a=edH<_^0StfBT94|2Fff znP+aF#k24qIC8AY#ye?j<_DN;J$D}0(QtyL&PxSi zN_oM7bc&>{^fB2a(%<$ZyvRQ_TtHdMkkgJ>kG=V&_OUC9QI_iIn;)p+F3>96f&ukx zVpl07_o;OgRHiO~Z$V~pf8n^A$z{?DQ%1ddTOoYjiG>9g?()BkAt8hY<}fRknMf{f zNCq?FQ#gg2iTI1At4iiPa(S}AQ0VPyu`I97>>hE);k=CP8bf*dpg7TsnpqXWNajlH zrE4kbMkT)UW89KbC>Q6Y{mECydrYJknvF>DbaG^AGPtGbK;`o%`(2uBR2;jv-%&n8eiP29HJacvo^CXs@l$VVb#^XFN-Ealv_ z%j#0zyv$BD7~yjYT!!Lqguh#uFZLUDfvYs1$#<=|Cy31fs+FzCNJ_YrY5g)c2ukrp z-gHG4d#n5vrg1SFXhgqfGlWKTw)AXv5Vl}+TB%57)+W-G2k!`u9nssQ9}Xt%OGlg+lU`Z+zzV!UmKoPBFq``_n9;Sc~6YS{th2%Ddu>Fxr|3 z4c-(Hq-_z_3e5I2aRu|9g2;6=GHD152d*&cU@9ULH2ky!FKeHo&g)JvHfNM68~nmi*eN={9_WVO=cVrV2$02{+^{7@Jk>{NpB zb!&P`WeeBe= z`nN27nNL5X^K_qn*N=lgFSm5%PwzauK&+_S~buRVkzh~*4K7DEDsXqPlmVT;F zKecm-Pyf5R^d+6A`1EgC`XXQcl+Kb*f37aQ)Tv!cW3(vIQ#hr_M`UfmMZD*1D z*bL?GuS@Uh7F0haiyZDf~_3=)9S0R-t$`T)>^t*is{UAn^sxyRB85J%*UE~X;clRIG`)&j1eb*DV}QuWaY3g5_Q+L_H5>R$TJs$D)7 z;w4_-V;?B}yyB&N*TO|_3KQqVWO57v1robf$yti{gOX`TL`|}PAXn#o_jW+Sr1s=z z@1vIab$Pd-p7Z!p>=;pOltA%MDOCH``J8h=Hwiwsw(iFgCqS#=ST1bUciJ8NODkd zK^=8iVRc&RX;Tuu%qR|WLkCV4LqjWvqsb1#nESp=3cdhxGzbe^s~bc|2Vm(>9a--V z%oyS?bE#Mm|3qbBPzEEr3K!E0wU)M2Ba`reT5Xa?B zeB}N8^unp7q)ah6dknY2RBz$gpHXT{=AV)rjVb+vm2RbUmd6Vfg{fXoFf?EaPemZ6_>IC`DGoxML!{Dd)AS5~n&mG{aUO#74 zkYIT5=tTwXLfK*S?i!~sOfT*5Ll?jg|C&GZH{Y6)@>voKOXvTL7FlRZwaXb$g6diw2 zDCl?~b4orLWl^sExHtHVoKnOk=;V*vacx}GuVx4@zkK)Cm)p3iph05fYFO8o+qr&4 zf{h(V-%*|@L+LpGPG7~L6RpxuL7g=Fk6dDG!EhEVkto=(nU8LE<1_9p zT#GbBAafI(yUuvznaGsFIA>i_jlxurqm-X3%(1saj~nTI(lxr(WNWQ=oTM61);aya zahkOt2{)!|v!~YPnYA`!C$HfxVV4a9YO3>AHw=Uz(QI-4DAX`gdde9cjW)zWE+f{X z_|E?5N7)Ubf_Su5EYTFVIl0^-j%iMw%shxcob#9F3ed;)A$fhYA>hm{7xDZh%%5}Z z9yd8$ywiDJC6VPX?uK}^;0k)o(?`d6uK5_x1v@L^D2Rii^s)IESQc==l|khgzFy@4 zwH+gh3zrh1C+WIsJ4R;n4%5%bI&u66F;eoI!XNl7*|3fsf#+L0I)3Ngrd))~pN%>m zy0>Y3BCd>2O&*R%CZ@-S`FAqjTR9v>KF;L1Gow-z{yDUJaB6mTHY!e5cEvp824T$Nz z8z0(T8IP-DqeG+PyW*Q?b2o$O&cjoccyxTYa!WjWdOTWIc(%E>)>IQIQv4;n{M)wemi?8Xsmd_Tu8enq#%v+(Sw7pb zyf+@4ipNIB={iTj_$}j=wCL2tUIq_YNSUlutD_U+n-;2fc3r*wtHvq^D`T;>uwWg{ zQj}Z8-xc7?w8imk9^yC0Lx+dPDzmv>vR2RLdhBWWp~1;G7c{5R8}4=9bS!uL=rJR~h!f!O2mpuzYs$@_8zppH&zd9Jgs=yt~F3K|H!| z-}IEPtF~uz-Lp~ajW-QVj8~y!)UqOdTAopvsT`jg9T}}m#)BhM{JY%BEcdjJ&OJ%V z)mf?H;N-4&wn=QTe71A>CQU@cEJnrAaUszT`KnWs(?bGI7sIA3TAdo4oT_S;XS?E^ za4H<`zz8~siR;*RQ#`f15|3%(;lU+VLn*GP(xTI+4)3qT`$nt#2B(I0*Z8NjDXu3T z%Lw{GERd^=Pw(S#$>iYpt_rAvly7skoMPbewCvpg7&nOQn%YgplFQWc^h@srRcj9z zqkXw2AcN?Lp>5SqM_X+S(bkd4iG2fu)uGYRfyu$)(OWjf_7m@$X12#hjoU;JhtJJ7 zu3H&jxZ$Ro<5ga2vDmUckx8v4kXBv75VTqgF>)nZ3b*dA4DE$6_fJmjuS`xIW|c&3 z&`Aqj&x?2-9XGZew}jGQwMs$p_|0RL5vKEucye^t?x~yOk-^ci%5ZxOwMJC7t=;9Q zow?OY_0?SY`KpSiE*+Z~+Dno4_(*(RrCKpmEMwweWl|kQU`d3X1PmfvsGp^Wv9V>Zkba>F{;Kr?% z&!*fPD&v(&+GgWm3gfAD7sXYUi)ABPPLh4b+}iY{il@3Rm?f5(wgB;SUO5HoAbVHe zI@@$KUa^Abb}t7;#(QS-y(F}rJ6suYk`+js5dTNBWiT6n$p^>AHpRC}GQ|^nVGCAo zWwUL1J+p=0_{ePg5wb@|;-2oF*$aDn0j%7};8>M{I}w6=H^)c$&mQAVwRR~;PW0;O z@#^46g^Jaw;Z2+NRrYP#GzH&Q5oi_b)u+X|mth)*k&riKt`$^v+D+}SP!*psF9t0G?6~vyq{#+_ zd8>5}phiqh4!9yq-f;Qa4e{X6P-RRD1F^b%_EpQ{-GkNm?Ul)ict~rT#ZoyijXrT$ z#6ep+d70TxN0S@ZZd!8_+6pAuIXzjOisoiax{T83_~_IqYuc$;tevI-3As7sZ5}u@ zHaKtkq@|xaIrAfgXHAU` zvr#RdT^3LbNjg$^4JvmGx^jzHa?RJ4c4stywcTvVPt0&I{MA8y*~4Gcq_l zQn{$IZo}|}JBQcyjg9V{M49YErc4a=RVRn~h9<_w&=uG&s(m}BCM%V`eS`b^Oub(H zigYltZ|dbVghVnttB0$7)x+aMeUeGH^o>spOzjr?R$orzH8QXoUQR<=5e`jhRDDR$ zq1~>*x3C#k`}U6w9-i3Ow|WR|VxkW-WRwmMTMg4vRu5@Ms;(ZLShet8oCvT7#++%D|rL#Q3VUtJkbv``Wdxr;2r;Ib6Nk|G#r$Vv2^DKsUAf_Os!? zG6O>`>l>~Nts5De((mZL!LfmA<-p4!S~|6(6Ma06PQ0A9Y7;Ou*|&di3SD~q6=@Il z?PKgOr>%6H#=fjDOxMBjsR2pVm(y$=^P+RSGHuP32jhbWM|Z)UFQ@%%dO|kZKR8)^ zWm=scAC-aEXX=+su$O~tjjr?1f?i&kw#{rWq5x{2gg@YGTmsMH_9SL92L0*fU+m{&xWL zZBPwoI^%_=v^Ar&9hjIec~Z$O1K#b`GGP9pYP1X+Y{$eD+SvZl{S|X!jUmv`WzjNQ zoENET2I{pw-_p?-cU7vHx{E(3Q;Cx+-%^e06wMnibee9nQ?2LMyITkLSI4l2Cs7E! zemq;u6w$%q;dwLgE0EDur-}M%sG=@2G*52ynH^rsU${iu0Gi6c!ugmNrpcm;TcK#?zTxH9FuVEFya-FHs>wVC%y?;e(Cpkg-k1g-8HeY-gd zY6EeM)@Fv>E@7FCJj-F|mQ$4FJ=^XkBEAH-O>P*6)&&>fU-5|-TUtx6Gi`9isYV7i zndx4GPZ=CLGUH$OLx3dkvr2Fvr#L9nBV|m`D`gz z)oj93yR-Fs^X&Inp%zmPrd8m%Gi zSAbdP5|X}vM{9xH)NEfMyB_5Q3N224Wc&J6mov>cd_{s+FLw@4vtq|FQ4FW;mwAll z8uiUiI!CibTpqikQ!{^h>KmldW=8>>uc(Ol{!r$tvuC-*i=B$od@`P_j0}o4D896{ z`t76qq&|nthCfsteS2k|^hrx!fEgtc4^`%8V_4Np^@SK&btmbuZBslrepqg+sBLIM zl4~E!xEblBq^b(_Gdx!@UvFCy{%TorEVSJDf3waU^!EjzWv)|y{>Gu!Z1YW4`-Rq2l_rO?&H`Ur zpDpxhvfPlFr^#g*cTZN3OVv?aJ?PCkeOW%cVR>zbvVAFK?dhsK$b)X52y7 za#^bdBw^IV2HUM8aXWkDhy}$zA7=?yzGX)(!476z*14l2oQRH4cHoNO_(o96HMUwW zp3Pkn6>q2v*~1TAJUwpS4joFGmwObiUhOoIvdwmckP;F(3IR4p&5Bq6pVUY&QAu)L z;l%^HMh5T-d+Ic1bDG$iWp%g)ou1b!r})%#b#v6|lD!R692lM49JQc9>1dnUI_5wJ z^iKn1Hd%!rAuEk0Y{v>JpFXRG>mpsqVNO=H)wV#_kv7A+j?#pzbSdQqlNYMh<^)4f zEl8;+rTP~aschC_nwX-V7N{pT8#Q}e)MRtOCdl>}KSAoXy{~IXb6j1*hZt80ujn zG`7U$ol@e<@rquwyImRAw=vJVs_~6$Hd(Fno~M>mj5>`{=qFjd7uZ{?oHsmL)oDXd`NkdFuHSJ}Ji92~9$k(u$%3qc{V?%!BgjHP_}bJ`PI_MIT#th5i>E_-J^qc(-L-7 z#s_qu21oA0fSlZC9s-B;8HzWP`KvQYD7o6j*ysp9o0rnS;Ml$i>Pt;#;BpJN*^yfy zDA+c)1oIb`I3;bWHq&Waia1kS^UY(4NjQJauy<*dWt_Xoz!5Vrdp3bL&L!i6N$TjW zSOk^U{1Awd&JU0C&re#u*=YjAu;ez^LhRsbR=TBmu2puuB()T`^Y|T3DV^>x{l@j? zqG0up3>=bmy+EFM$CTIQ>ZLnOvsJ9j?Zbqc40>hNL>2lhoiJ;e+h!G#YFLF@JVqW# zhmr)}LYXG?lo>=E0xh^G2wtn@x=091Y8n8a3zV5Z3Nm0aS(%!iR9pm1H#NwcMD>)K zLG-5zO6o8dikZ*AKAwp56b07_6%w0HJ4pOuPZWD3YUXLOdhTfcr|gTGD&C;@i#Z% zx*VI>6|d-v+FxS5Ce=gxy2%_5Y>oS`ORicOA0rdhTA5Ap zRad;dH+mIKSQJVvxB>Y)Ie3_c&NeR)Q3_-6`c*rLf_=l~_?#>T;&88vuM&Hty=oQH zaEqC7_81M%iy%qo%F80&n9!EL(6si>*4%oYlZ}tA3*#=9&CO3bRBaM+ix%AR)S8ls5*>TNv zDLqAM5NwVymH4gAX0y%a{FG}ks#3hoBKR2LF+cljH#*W>XE6}0+!1zSc(YYHUp2l( zPg6w0RE>o+H`b{bM^zQ=h1L*{Y*_DmzIap2zU?FWs#DWDS9-0&!l=#0U$sg;0BziH zW#1^%LFo3m>o%;rus4!7%4CqKXT8y?g>(b0zk#U<(+_H@LB8A@^=$QZwn?3+*Cte} zzO;{DZi{=I2X2dTLV5^A1hZqDiAhfJBxFZMiDZV1e$uzhwywl&gr9oLZ2QVsenkw$ zEvmOj+DW2Cb0yIw3Wkmrr~Z0*Wh+%nTcWh!eyY7oAlXxF;cYX+d|>irXk zMl9ump^5#62Q19hPD0TlC#ENf7j{9nKH|1#_BH5fG;Si;Mfwh#isWK18w%4?5go-k zP;ejcQex43z^aSb?NRjb8NG3J_r&zrFp^GwLb{7BgBnXF@!n(gAvG13Y{{qBm9Hy~ zAGv3u8k@FSDm$lltyFON zcx5G<8G&${HeF91O4QqHDVGs&?$dg2+B#U>irsuE%XKTR_Hl)gZ)G2`gpsj{!Kup$ z``$i2wbg(f6M95WuiL7HanpmQX99v>VZxp3X4O;;yZ+wbw`9L2M%$3moFdgxoKZNN*=5e-Fmb}!(otzby%ZQ{Lea?s^uLbZ)?~W@TCF# z4xf+I%jV?Wq=oqWH{g}jIqGBFB`;EZ-tY4c?M9D@Yv~_qXyXB2PU0}F`w7ytMkTP* zv`;tG`+Z-}h4G^h;CvwdKkI)F`QooS-$j7mUy-ldaMK&7*KU};aNYEV_0wzDZ5-V= zx^~0ph3iH)tRDr0pV|AR0%~g_jC73i2gT6ecvH~UztGmX0sIfZTCYx+cH_ZZeZHW!J8+tY*_Q6`!oju8FU{W=Fj3vh6!!Y5z67c;{i=a~TT8 z^f7Q0@2%)N|AHewcJalRu&z>icD>HcYYKNZ!2b+!7YA+q3-GA{{CQx+e%9Wf0*5l{ z3&7#}`g35d-?YEK0M>dN7_Fs!lnGcKl=)Q;2Qm1%CNy^wopZTi{$kVPN*@kGmgXaPTeWL%2mT(Ido2 zO>*IITzU?F_f}o-|M-OYCB`SJ)7XpS!{Y>PsmR3sL2i8z-At9zHXx&^< z$O(?KeJ!_pNXK;1iDsALI7Ko{j0e{-?Sxz8te6-o+H}NaD%fm}N{3;Kh z!*7xNbN%mfpZ~}@SHBOqnf%xK-_88yfw%B0Jhpnc-{)V!ZwKku^DBJc%CE*V#INw! z%dgt|DC1K*2RwYp{~qO6?ap}kPXBwC|9ua?YWD*k{sn%8|A+WhzrV(>>i-76qR+>D z`e*oE1pEb`|7ZLPpD**Pa$*#2iJ;1J&u9KsFg?C^oIquS&X!m3PlF6wHCKWuWfjhB zdG;v1fA{E*t&2HW>2pt8;tS&=@s8_EFS}XRag{|=iPw%-JNagRxS zTk!Y*hQ`RTi#xp(+s1--wikB`sw7;u>iX@g)^18&Wx7tO@X6hkG2B#f4-S!=SKJ)y zQmH&)y;=B`4`dy%6Xo%ViT#^37)J}AH#j$Gr%!U{Rig&Uvw!0TJCD;DqMC7cyY$2# z64})^tY6LwZ>7BNgBmgyl|a2zt-Z**_*VC;v{rj4@=hh3? z(JG#xqw&QT$D_Dl{U1bI`dAww?NiSU&Pq~>|5K8PnBH5J&ViG9^^<@DSjXk*;kTXr zQT5V^eLF|Tjre#hU|VctH<4-k_03RahdfozpUjk{eVXZRN2FCvI9mhPi>vrb?5d^$ zng=FEMi$D^Qr({36Xw+d+$yGFZ6b)>RTiIC>rw6Z>)iqS1}FE%uFtrqI%;lHanZ#1 z*x_DBCFif0v#xYxf|!3h7%F5Y9>iH011ps6&Rg8g%ootld5X}SN7fq5?FG7C<)$fY>v7;oEwX>QBTNe=q_EN!9YWjOqv zhmn7A+TUk<8qBx*qRTH`qd)F>qdlkPKJLrGaX0nicUu9R<~`)|QutRI;Ey!G_cg$s z5T-A{a}jWe_hR5T1o>?Z`I~_GKf~uo0A&C5#;?aaRZG8#^liXvdgacmr4N%H`WZ%m zuMX;62^{K2SEudhT*V!B&apwi-7v=QdbqZNdop~VXej$-593$K$`t4m6)c0#ZGg{j zfNyPpZ)@NjTE0tTPe-|Tk^$g@gX~^L; z?Jv@_AB(fPUrf`E)YASWO}n6$_D5;j`L(o1)3m-?+Goj|b zjcwAp?Y?dbUji&TYx7IfTH$j(K*l;XDVz1Xi1g(F9;X4zwo2=r4y<*ojnU#Hg1lEX z=DDV`x_ik#lkx)tqxEjy$D*h?zE}ahJjo5rk3V2Z`<0OKhjJD|J9OMT~hfhK9&H&9l2Dg3sEx_u4#uWiV`7&xTS7GTjRZEtG>{2a#X ztAl(w3n-G6F9EL!;3dE+&uf6}#SgmPP_CEUU|Q8w^|N* z&C%+C$tgFo*8) zWVHMSPUurePv_#_fbo%M@V9|a4d7>iPYdAI_4RNY@X{cEF>pr!|7=71zXBYV{|xZy zLHW-C{}Ax%TWdIdmNeO0Db8PJgO=Ych5sEm4&eU;4#!x6dCm*+&jdaTm^%h&vp5uk zyla}=`9j+EwGHrA;8TP0+kmAbrRCQHv$Qh!wGHqFV9jG#{&L_hZbO`ah_xTFhv8pS?2@IF7h}1n;sc*<1xXvk+(PM*O+bMl6 z0uJe!0Ee<^3vfB0=OqpBE%?IZuTR?>Yk(&j;3{x;P<{$H?C-v}Jaw=F1u;<2p^EG_dmWkR0+dB%x)lI8zm1AL!{-}bh* zmESi0w#m0mjmnz9H|AVYJlGMMjQAn%3gf=xRQ7DAJL7mT6fiYCI-)B8L4Qxd$Ke?M zp#lCbV+iHSe*uSUvXwO*j;9ki4%!!t%P$+B;E?W1ftLmOtAWGz1mlv;>I=RufV+W1 z{5Lk#e;u&SEsb74i2QJJ-DTUae#1@je`2+Za(-By(E5j>l+XbQSA0MvzWztAzI5R7?U!GZWKS>TMixQlb|VY5YLSZtPA_GV z(q?xD6f*(f?r8DQI-sC$E-dlfZqIf*3#iLM=3hCVP?Op%taR=EHhW?l9AsqMl6ue# z(otu|qYqQA1T6pX*wmIR{S!XD$Ex&Z>7T4C-@Ro%eW*%%p>8z=EP0gTb|&j~aR9#> zI9x|>YbbvcaM<4Cz@dJyAGxD-leT{VIF!$V*`BlfN#Ibv9s)ii$iIYxBt-I;5aD%1aLV1uQcQf4&{>IP%b?M9Mbn6fJ1uBK;KXf z?g3sB^gj)J8nAF0lNV5~bAx*fjQl}3c1rFO6 zES)6n@3Tx$h~wvg!#z&$^+Ea11HTbixKHp`JeRihMe;)21vBMY{$BxyYvKvuaBN=z z4sj98lw*HQ?;Fr&MqJZ$rJCR_%i4Sg^f@Ky@4-t(OX_>WKq!uJ0T_{^Zae+2Fh;C}`V+y58fP$&IY z;86b+%=VVy_if;?J;AU*mj55XVSE1v`1~MWFhk7B{{T4bPjI*w7n!q=USsR($9ot! z4%!nOuDK(?;-U0hDLwB=&tuZ_sq`EwJ-H%I7w7&s3Tnn!QR{yJmzbUS-Zz$Uj9OAMAIK<_xz+vB4 zz?5O%yMTKFTm<(A@Lu51r%?qC`R*Was3X5M@8nX7Zv{By^HJcizThP2ZyY#W2NMnO zPXLFs{YhZWXWHLSHI#n>I9x}9FAmBJ{^0|E6`%q^xA|Nh5SuG~WXmaZ;E=-e6Zo9REK)fV9Mg8UEUoXpHF z23WwqGkFR7bXTf^^1n^_kk21(fPcRM{%iyMIpC0%pKpM_*Z}`o1AI5IWPETg$?+5~ zV#a>Pf#Yn6@U79?uDKkyxV_ApcM=Za?=u4}ou4;itc1M(F5plHIMD$AxQ7Y;Quwig z5~k6?G2qAbZtlVA^!OoOj2ES{vy=INKJ$IxZRGoSwXB>NY)dWuR)sa=Zr$fXJ>1zV$LB`| zj?~(IkUH|?NvBHj`AdW%U1V@O6BFtKO~AMfvV2XPbR-;!nTnsb^>4Hl>Ke}gO9tt* zGu$)35ZCv)9lS!C+yE?^xOYEcc{Lm|VmSKqOz%PUj`&+GNNRM*LR5OBh1KknOX;(l zHd9kz;6Iv5tJ9xlmqx}zzi>I??-^cHMg{3Oe_7E(*D;1hx5D+So56bzZQ5) z0FTj`<_YhuTYm$U1BCZy<##i<5WgxgTX>fL3~(q@hJnL5c{A{;p!{{fp?4hCJ z?7MZVqKE92@hz0}wYB_)bgLkv#}4;`n3eIt0Fxbf{rTg@eS-+H12ch_FCC;E{-pfu zB6oYX%81yG*zMV2?}Anl{t(H`H6FYADj%j?WeS}4XRSf48^T&v(>!TmT}x2yt+b+u z>B|*X4L&QaEC0{xtz|8za#&DNDs##y+%zoI58PPDj;4H%mx^XVXGgPcS1*j}3mNGA zByw@$J}^A&SL4j^vYs0;t0RGkF;6R53xn039n8EVqQq^sHo9L`uSYHiW+`R)J;0LD zsT}C_@KNqZq-np8fZiUIc@#L5pN|2D`|am|wH~=AWACeh2aooy9@)BM_h@x%Sv*#@ z=bqlm0XvKe+CKe!cYfLfl4<{^__P#mZ-6^JymiQhq$IXN?V=2Vn~M|!#3&nxh7p35 zTZn%d;?6dnHCNmxAR>tE8lS35+=u^ev|1f(XsO2)$~x-N(Q0?7yRQPj*D^*{M~dP$ zci+p&5=F(hm%x{M(=q-ob;5o6lOA6E`WjvC_wA;2KIF@!{F2Q2wZgO_Bxon$rHJ$roKx_)r7JSIhUEzdrUn&D|)6!MU;W56}>$dWPLsE%Od-T?747lFRN%B zou~MJ&gVB&&+;Gf`DyvzZGb<~0DsKGy+I!z_i0Yr*2eK*L*4rt;E#Bi@SS<+#3hv# zeGNDGYvX#4ugk^5A-5}e#gSXT=~#N%Bt^#*KEDCp1gw2D&2MhVZ}qUnWbLmEawjz_ z`#s;kz8jL(|F0~qFJ|tuvdbFUUD5zg0$&*5v9GH{@;LW zSpV;VL)-CbV4BF<`)A;70sKv1$$v(KN5;iljS<~VSTFAguuDXCyITt)?Xw?V3;Vd9 zHZ})szLosYKI{Pwaa`R{|9ucldvV(SF9MriU{f4#iq~Z6KjqUc7?MDBj3n-t*YL_U z^wZMN&m!QE7K?#HS}Xzfw0H$CFK{XCdC!ws9jk^YchY+c`;LB*8&1?J~nE26kz7hR!vDO;qWI3gs0dJybEfnKXo8g=aC94`V^fqFa>cptRI-t zHmYC0o2I)eAUm<;>KAaMgZTMO9L?Mu@#+A*mw5gVsgnjg1?%eHud6>}5ghvCz75R( z*?RjIfKbO09NGxq1im^b|6yWzP|>scA8CN^p9j7?DF4e1u|OYffPbr@{>T3>aqk_^ z_4oh(KOfKieA+u(NXjTGDV42|5|M`Nt&|a(icm=lNsE%*7BX5&o0j&Dii~!m@x7hr zlh>=)>;3tBzQ6C~^T+Q|SLfUx^E}Qu_xl;o$2sR;awX(H+p?Sgb$r{uWA=wPaeNbHO0cgYz4K+}b?~VR`Cf+5e-yP^jW}-it zi~R%r{3?*eArJ50--q7^c#usah5gGD;j$rH_q+CRIgp+E-C1zS{k-~*|98JD1mHLW z=LN$h;rhS*PBE=N-2b=VJYqV?^F84@Be&m;M^Y9=%d6=pI~ zo8ZgmC_fB^#$?zF|C%FV4#GvsiK##okZ?KylBcGFfsKO489bDd6pZIhL+qqcz?&>c7ZZT9|GpN@KAM!Mq~N!% zqwvcEvUCmRizX9Tg<=dQej|TO3NipV6cVR%R7j8dn8v0ILBNhZW3+JGp zCY)~G8iSc|b8G|ePr_`4AvL5OzMM1g4{r{Mu z@9&+scX*Ugf4q%_ZcmI*Pk+KC@yS^tDfqTY0j>mO9k}G(C%`3Poe~mh7ZMl&#~G%? zKLkrPItiv}!g3on7^^2J_=tx@#enNhRvSgU3Cb|C({3U8`&YqaMIw`wKLU@S0226Y z(Tg!vI7WbY?f+Uult|MKQ zXNA#f!n555JT3x7CkO4xj5`8Ppx`P&$PpAG^v^vc7`i}Y(i`fES$3w9+5CwIeR}Am zBR=~;@^==QGl6*U^&xSrFm^7Cco11A9AgB>$er9V6M?;{JsR?8#-kB$>W1b!+U{t$ zqtR|M8WAXXxHDO5A|TQlb2vsUa1#zuMT!WYi={+^0-}*rf8{ARB48WHbMEkyS-phW zOE7KN|21FCixcLLK)5VikHws|B&aw@A-#3AAYYtWN-L= z{*Z|p`u!pM|4Sy8Ch(8rLGTCvA&0;p`iC3_fA}9V)K&2P(?IwmATRpk_~L)bk&vSR zsb`S*6hw5tpU5$w|MN)HB#{lGgkN=f2}c)_^$5{>5fCM)0EJ-KF3I}%l5v3#4haRn z$Ov4yFeoYziWY^rbG!se`vOSlSOl%S&nLpjl>jz$hXC-0w?Z$J`^?jH~k42)tQ zknr;grrHvG#~B%qSv&C!<6mh%khWFv!ZP}FzuN#;4DWRtE-Bj|!zE>QOaJ~4aEYy; zEfwYs7F=?kI9w69iv6xOTvAsshD+{e-Y+}#%X9i&lD7ml1s+#Kp-(tM84yioJ&p;m zj0+h}Yyxq~0u4^kzJN&Aa4au~j-drMz=h0vgS8Q-3)Zrz_(^(T(D!TbBwQJ ztiVb+#@az9X2da8Xqp^ji4k&){mU>p#tMv)W2`VdI1-2}Fh>BcjfixN6=*iD78n)B zSfTxJjD@{(jCCVv2tRZh_r+qSxP~lMN{-;qbev+jP;e+7k0B;O5H5j15LmitF$;X5 z>PlJ$`(djE?7jq6Snq2O=Tu3B>_{TCr3|>4wxJr zV7qu)z+&qNRR2(>E$|Tz0G*1#)&UD$1b?R@*8Boo1b+qouz+ZR1lJ}a+6vujusIP3 zd$hoHCNods0REHk5L|;p0;8v5tzCh~g)G8Lb_<^+2(Zvgg5B>UffsuFUqL9~Mf&&R zb%Z{_O9{PG@lHbDWV~Phi6jidSa8KJ{HQ@hEVPXv7U6IEw?rWQEyqHRME_}szt95y zyG)$7LRdBq*t~(V(Hr|ozDKsS!c$TH39TUUxnc$jOEQfGcG>qUxv(AMMXHuWgT2rp zEvPvX4++wMJ&uDPxv+oMY%-8Cij?vFYbXi4q^*Y`>{xA+Tnqy=5axe}u>)Zwy4VMo z=pK8eP!}cO5*fj>T;yvdSj{sY@_^l23P0Hf!n6#25~lvy+sG=Mm>3ZioxkfO3&^75 zQDg#Y;`m2IV@hbKt)D=|ufe!fXwb-#s$RiMalRKmKZuSZlY0y8(BJQofbfxUZ-Pt0 zv=c6AC+zEYi~EmP!6o&Sb8v|bX@W~^&HeuUc)!W$1||i5xcvgmbP|GrhEVtZ<#Mda z*gKSyu!ytp^}0(y5Qz}MLPVrMMyQ0#HDRydR6$8Acnf@On4I~3t6y8_~s<(g|>_M5X`^gR|$Ud5Musqgw|isFD1w={Snr`E)q^7ujJ(8=Z4sW z*&mqJv%ma?s*ov6$W-cN9WNSfInV$kEmwe*U?81W!B4hL0*CiE51VVB9mO=WURVE!ZDCX2lQtkuLdUw^=J`X zl7=hblC)2QOX|zHa7j5akj94~6TQ>vKd%BV$s356nZhYNtWl2r-5$D*$~5V3BdM~; z7uEr4Bkb;m0-e;ihoUmFLuE+iqsIZ$7Jjc1TJ z`s-%M<0#nwjuOl8nCXjzb8q%90*BA(qOgR5k;%;12>b%Hh}7eSCJW>1koaK%mRKNn zNx&2saG`H%L@aJ7$fMQ+m6Axm{@RK^<_PJpx8Rp)Oo$PWb4fx*+l?$1I;nd`TCCq6WY65Y2#qIK!RLO#9~!`#@3* zqv{=ixSWa(Ucq7VMC8LLGP9~sJ5a9r3+qpU1&xH>Khn}TQJ99~;S#%I)9+4$OVaMy zn!nzQ6gax43#lpw}UleL8q*Fn#2Ywqey=ipB zqN&JcC=a6rzq4a3wjUw{i+IJNNrSQ$t;#qQxC6UMf1hAh3*3KTZpL!>rGQ`{lHl(B zLz9r(FjZ@mU}(}G=hwk+@P}Lvzcj#t!_foxJP%n01cyXo&8hxT_5F?)cKVSCuo{DN5mNj>qX=h-nxx&&}l4b_ZgJaVr zX%(Ov>;QW}0Vo4RpNI`W*M?1ab948E%OI&h2Iad;p^9PQR{lq`;Ok#7%@j|qn4}x+~ z2QGjc;30Spy1+L;#iHy3N?<4$0Zf1;a0YXLKUe@_K>|ny+rWNM3d+GLa2{L-cfb?S z21xu6(7i(&xlEEa8khnSw;MTn**6fkyS`zL7nVb(b6F~M8h)mppdk@_2PnU#?@aMe- z!b;8~{(9<_bK37w`iiU@?dVYrs0N1!RJqAQu#Z z!=Mt>f_iWnG=aO|5qJh#KpS`udckk7c`VX!Ir?pYEKmfhKoblD`d}m&1IB|1U=pwd zHoy+pgK5AO%m!W{0EB_XARep+Ngx$$1eqWQLt>JYa!x(;m124=|AC=^G~fwIu8%6rW;JECouP zqA^9qSsaR@+$mLQicV)z6q}+L*da@~QX9Ahln$L@4;w0uJ#_q|xXMhXstC)J;)n-Q zTqc*v;4|oyVGW`<9Ev-fFUsJsr&5v>gDD{qMfUT_ z-GoP2xQ7@+l$<||F4#v=EGCB~&&D2U#vp`^qI0;2Jc=t#7aPywu&GhxnJj7${>o4c zI*Y`uI8%c%=Th2C8KxwqZ9NuOu_P_{Ou7y^m99t85FI2znDk*_@L!SyH4c93A(E2r zbT)%Vm!gzdjtmiwtcaYbtRP%;(b04naipyEY>pnwj;~IMGIED{0On3M>TkHw^NSWf@W<E*^Z&9GK@xOh^evkSZZ`_CYNFj-y^Nf)Mn~(M~jLerb&M7cNwEaX*_v8 zcMzKzPVsEn$RPS8zUc~l1)jPk?#h?v<7qmkY`O)altpry>Tp$tsuq`0Q8vL*88PXF z_G3qhjS|zhJQfs_ zM-qwBlBP7F7R*V4e1;J6IpY+N=lgZ@FC8Kp!s4jl5` z()K}IkHP9Z%7El9BQ@9uaeESH`gjh}NL+~cqEkZvEP;YPO@Svx4P!7OsOgyFMCv@v zM}j8BRwTA<1gx~W)O0B|>5-C5bvc#{&EU3$h9X^2-;p_vBV~cGigI`~OnDbYgJHoP zX%dtwjDt86rF0lejKSmZnH0^9nvBPiATudZgqA~DLi@OIRS*W`5(bB&(G@90*wBB% zrKz*o%^XS!T8x-vO&UxsGT?J0DVmzJK24=XjZQVGQ}SH;a}J41#Qp^{8yFv$VoDh@ z`A^IR5qZlPsf+tDNC89k zstV45;?ZU4D)e!5;hj~fDX_W}>>`~b!tlMtMj#HW#Bb186uV z!Vn|>=%OqxOb--_WyB(egcKU12Gi(^87mMvZw;7#l!*)$i}GZ9GSFkfrcu63a;6H? z8FqFWrN9V8AwujSlTt)kx}B~hEk@~Z6-5jgBN4%Ll!$V4D>ljLqA0($U~@+y1avwJ zQLaph3tWE|j}eb>K*gzTQZz{hB`T>2xfrj^#uZw4?-7&%-I|g^C|**TESkVn)1ZPZ zrY_ot2k11W4$58%`ag&o&9)OUVlJahB!(fT`3xgcW5M0|lrck_MR6xlqV&-`6z`N5 zsevFZ=+ti1G~|%73~C66!LnnEk;q2)+_)q<6{wlWC%Ay##YFi)S0?*$Whl-ehBJV; zFhMjP1ue&!)Mh-BNonwP*>vPA9-GcEM6x&F-6`rac`eFCUSN#qOG!bA)TgK^3KioYDq+c@(VhMaW#CW~|4;-}j)khg{8JH-5Q3Bt zhdNjbGC7HU)aw6G3dA)sGD!%E{tjvWAF9FqQ!&0!eCGc~G5Xbl0i~e+Rtl&Ca|TR0 zVt^CFpAEiO_(N+ z@6ix!MV>e^+Iqekmy*!n50#+tYxy*G4qpe)lgD-FW#H2cM8`?dY0BD6Raneq))hXD ztH>G3b40TymyRm4p#^U=U5(+$3g!;t4&%_+Bgxa$_wa`^7jcr+68IDNoB15sKEWH% z_{L;jj3;Dw)mI3!34DQam6cK+cFvq`afGUG(%~%q(GDkPO>QXXrxu)Oha3O)Wm4c-e?6x|EmQ+BV{J) z{xsC!J>~iGGz*%be)_+vaq=lyHXGAy*!0%`MbQ(14h%7IzMwzxzppN_|EtOy6kUS! zS&{Cj|JX?X!GFEU&=}G*4nrIvVYBHhQMLs8f7*EeqnoxX;y zE`Jyf{nbB{3jaE=9*_a0%lH~A2A;!0XC3J)`s3R(|Bxji_+D7>wHf(okaWTPaTp); z_>m>_@a1@L07jsauQae`&p*#c_K*~O<3hfx843nu5*E^jJ|G`~U*iw?BK$-CkUzsu zzOfoO|2O=D05&egWpLS&JV`N0=|KvER0nBm=xL17n4n>&>#RFhH)v$k$b^yUrn^i_ zO>0cASv;}mwxHX~*$=ayG{bd9$c%&;*|U$#zC8Pt&rhF0zN15(Ll=Z@2rY@c9N8Ah zU#7RrY1yL0ZHbkMj}z%>x@ptXVl(z;T*~;kONAx;ho(O2RLCi$9HEGZ)JoB@LxY#% z33l^1Y;u4JJ4=qCGK>lcIgGkIM^GnYqhc$(8ub>6BfJMH&nzy7E4UMfP3JLq=<*@= zMqd-!|8&7kp@Znh;Ic`j6193*a}J6eWFqoF4x5V>9xAPZhtL_Q>~mQh)L;YwrO<|; z@#tJ03qKT)t64lYjmc&*Ih_9R(|KGPTIINq!xp{@8sDf8!({PrGvRA-m@FC#6(Sy+ zhZ6mCIq! zI2;DAKSmJ_E`x?H7&an+jryT5Vh|WqBT*~mqE7@70ShaLB=XW6C^4#COhgHdgBAoy z1hfxm4BQAmk8l_ago4!c$;+dGLn?PHIv&GjBPd)L06{cz=o~~WdcqKUXkf5-GzKiF zASKug4v)j-A{BY0X31kg<@+NS`I5&a6;KwF#bvS4$%dy2dLSdw=^O?JDa=A=A>spZjz~k`gb9Iigv})>&Ox^b z#YXfa_Hgh1Y{z1wn-j0VWy36T(LqOIk;~)tXGS`y)8IXrh#S)Pi)@6(A(O}H&!VI< zPvatVh$2)SXvkO`Qf23|`g1P26429$pmPuf`1zQN%u8yVbc9!^6D$@g@CZI4j94um zvNQ{6f;wt{E=Pp0(fP(jPDcx#MKU9sgKLozh)O}aa=EDaA|E4MD1=yu1qK5-0ezv! zx56Z5GMO|k$=X~z1euOR2Ck;F*&I5fUr|u>(wIzScVsUlC4)o>xgH&>Y*ZqI36D~l zhRjbR^)!+-S%?^N2P85V=CWU{Sb~NJsRkiD98{%9dj|by$ScTYq`oQ0cTg%8QW6=R zWL>nB5na$-p{;oBswW^oIk4O# zlXF2kfA*D&4ogl?+PCV(p=$fD24e4P<2_v?8rMJDF{kZuRJ^3V{{7Dxtg{t*x%an( zymef^Q<)S0-EMKd$)#x(NiUyg zMVfzPGCPxYHErYN;&X%4XB_@19z3P`%+K!kW_Lb54IH9eCrw`vo&76w$$_L?y8U}B+s8C|I;^v5IvJC^rfe$P<=BhdrHz^ER&V2F+9w%j&A0Z+2$Ri47gQ5VMt@s3_}wzS<{ImA>2EDs%~VWB+W`Z! z0JoAOVL1y;h77(u>e%F-b*I|z>|&qgCoK%AIkRWS68(ik_%>5Mhz+@7-M)x@=E@`g zS;@n%XX(zGAG(#zG_94M)HyUJq4s0P7v@+lt08Rr#N=Dsrw9@Svuko;}`N)Mxy zxi;HW2S4u%?K-}6>YhVCgCpgt{D+s$(qoM0= z&C?vuRWUksr6K2{!mmZ8?9U}r_ZA7xqG71UGCB5hu3QKdDGs#-?ql?{n`WR z7r5SEYK9Ef=`_{y2rr!W@hiXT zJNmxk8O3)$8Oz@Lwv03V)q%wI&riBuJ7WIe+LwoyHRo;8KIri_`1h_&5*EjAp32x-^b;4ynY!z!`yh2=w*4cAFDPW zRKKzD)z+X@4+&RTYvYF&4!cd^YwwhHz^#5EE;#pY5PLoSJ?&@zoR@GH%VVI7(9{Rm%d6RbyaY0!VZz$ zwW9-HFOS@-6}p!`E-zu)`FCf3tE)^)DYKbuopM>j_e9{a-$VCn7T=Rolx^!Ps`jPr z{r$o5#N~p6o5~jNE-s(wU2h&6f#0`x99L*Oe|xZ#X}N^kRlS`vSLN=ezD^1Cw##!J zzOwIm#`Glm*&Pe4eq6PQerPyOajD-BYojw<*S=qFQd@dc_j=TJZJYT1%FybSrEX*5 zPxw`iiCM0b_0(hZhz)LM9rsNblDJj*NR501)z?{abkEfdU0Si4yV@ULIU8RrH{Z@n zA!f9hRoqIi{7C5tx+Ltj*`x#e+a zEbp4pCdt&zN2)rG-Q;xdSSw=w>zG_g`Qz7x>dBWwX+e#Rb~_I2ZtJDHFN~Y6dwDne^HVqHIH>jMTwj&<+-%<3 zRcyDM+^&!FBF;R!eBa@Cf!dVzGgC@NeBN+m){k^m?;AP~wLd(}F#9m8vBEQ6dcqnH zZslF>#0yJ0S8Jb8P`J8p;T3;T$FwCggZC}i_vG=4tGu5pnmk8j6w^g`B62f5e@$KP zX+6g5`|TG6tvVZL_U<{nK*@o+uv)-Q z%a5w>vH5Xt%My#7s!g6&eovpro4EHmnPf0X*I#>#`{|y1 zlbiDo$=FF`>^NoeA~RMwC28%~9oY&pC(Rco&0gmAQnytn`pn8Fv)(kVYaQ{hU1`+x z>?x)Z?OB@#FX{HYbnCsj->Erlkps2m#jas7OF5kqeG5KZbZUI}B15-9dba)B+jT8p zcfI!>wQ=5qn$mj)i~T!xk3O$IF5$@WrjjhD_>FZz7c9TTPR@Oto9Ol<_ul&sZ)N-M zCHgJvY$8wWKA$ZAd7SUg%I2}2WzCJ67h-f(j4qB}Gvw;p=zAO1gqdtJ_FGZwzxdmp z+eOLSjTrP@zdaAVt8+S$%yljL@iUR0xa!M;K!ZHrqANkSmi^SO_j?w3!@D+rV)4_2 z#06^KX79N=*(&ax*hW`*pM37?==W<+J#cXkoU!Qb-1zGo)VGEYHQXHZ^_j8V;s;>~ z6|Rr=-&d)oecFC{`}L;e+;&5uW?%ubfcHJ5+*Y-2D* z&aH22*M(0T3|LYMI~-J;4CJ0D$5g!UinD8;ciPi0H*U@y-&-wZ!xggmqq|2hza+nS zU0~Sl*&GEANBGdev zdhBajGh-8fZ)tim)NVi9ZyEQr{xCQHRTWmvM-`@CGE=(J6&n>gQN3lG!yZ+y>MI(P zz6?+GP2QHit7}czWoA`A8BGbtm*2-+vv@n7Xx}|(c3C06 z=}u8k%H7rk{=EZJTWeJ}GWfT@7Ee_le0^%u-uZd~i;}LTMBh;l3ZZG#PhMhX-f-*L zol8kk&n;=6A9M`kHN{?3;P)Na?BPONEE)Xy+BvtAyRXGJm3IYNY?NKI`1FqEZ7O4n0_h6OTnV;-iA9h_)S=;hniu<6zVPsvZouSQ7 zzXH_-@(!Ce8%|mx*RjiC?=Ct(LIn~#1D-E@Ia^dGN^R+KNbX(}2O?@#kbLO|@ zpVME@?$Wz?GVV*-q$e3UjUI|4d!lRByt!*UGF^83S?aL*1QVCgS9xjXwMTcPemI*m z^lJTUo5!t{g>n+!U$Rb~>6t%XvUJ$f15%HqtB%#ad0+8$${o3AcZs`O6at^U8+_1z z^jv#osm#kmY8v;3Z5!QjiuOS4=#@UR?vQgSc2!n|T^7@4cuwr)hcS;2^LQ;iW4P4* z1LdNRU!Qv^q4aH2X=HW#+BfefZ_AB(wC(xlMq)@wvWh{iL%Y z!6T#GA`_#gZ%^x!8Gmt*m8Du!`!s)Nt-8a#+1&4Yw%%+_Jm9-RJG|`CxYG`mcHeBD zMHYHCX4N~cORP*cPOIJ!HeKq=>gDBoO8oU*gML`Ipwl zq}WnFWs*(2yxR1m*>!JR+OjNvWEV2WR%)3_P0`OS-2Y>S*{Yitmq`lKjrmA znYG<(`ZV7>igL=%`SQ?9%pJ!x^aQt-QU;zoFE)G~U);3w{zS2s?B@8Tidu z-X9#VN}429kDK8!Jn~uNuc}V9Um4a1bW;Pm%BO8VH)&{b!NjE*jmGW2hWTnvj7q5* zmKu0XWwn=!`J5GF*xVwW(Zdsd`{jMpX*%-w9OsJ5$g`upf7f0<@t*!<+wpZjf|h@< zG&R@%@v_YNPMk(r&6!SA-4}~TB%hn#lDepV&C-1< z-cOw~t!8M{5>Gds>Mdz&r)5R-NH+IyLdUls)~)E)d0Df;#Et)L9dljlqhtF^k34KQ zOlrRtU1qBnae2C3*r7{XcW;%ZUEUyKcD3e}0{h|8N}I6?7BveSd37a^cc*Ng-73dY zl?>%uU8`s>J~y*>)+PfjX@@m-#e4k&W`1hxF*yFcH0!Zu_O{7&D~B!8j{W}Zn|tjx zy_IpZO}Z5umbCY6m{Z|4;nB2rvlwByv9hhbhdyY9iEPz;-Fbd(YUb=cCm(ip*ruD5 zdbRCzjrsYFUY;sj{%#Qc>LaPuMYn#OV7w?%%+uL>uFmD|J{QjN{Wd$Az3kuHjau-= z@xZnugV$G9t$k$RE^3j#C?dV8wQ6tOvjUfsVG_>Qr(Qj#qh824xcx?9p`A=VO=IPV zxTs6o*XFOeq$`>hNV}76=DxYtVAa=sy~YwF5@_opuY`?`|E^Yk_2lnc<8v+arq?E~ zzE`P}_p9i1iv1s^z0U?Kt~?O4HuB6hf8E(oBNiF2GSPWU#)|0F zCa>b2;aeKF`%TS{S>8D1#EVBEJFAsmOgeQq`LZ&7r{+T!n{&ZFI~otpecQM1oMq|e zpyrSDC(W&PM851jpvViW_P(%P=}yq)y(!lpOL$*pwN+KrXSS(Isb0G6n}6=0{fKesA1av}xNSo0B1v=57>k;r5(6w>7FNE%Oai z{MbloRgsF5C(WjOimlsxZc4;_y^jq?b&DL7-ZyuYe^L@LsW@b+X6*7rBV0uH<&hT~ z>-`=!4DoVZpP|K>y;1z2>712QcFh|5wk*D5%Hbn3?l-*OJtxt?Msra1P~}ID1JZlK zZH-!b!sH}Q_q^w>|Lvjde|1l~@`Z=Dx=ai^~Ls*X8~KC z7BfeEDGD6vxc9-t=F(NmZZy0!-!aVMS30A}t~JXmCn@gHrqiQt)I~B~vY#&hcIJTl zt*CAbn+2QJ22RR4n3FwpiCkwbpG4DHR6*=JWCbzLr9N|L`&epe!t}tb?j$gTX$*m?t>_~;C*v|{wmw8WQnduEbFFCIz z)OmU3ikzynSIoINOV^)Fw>oxkYr6NRSe1qk7K)ug$8M|59(&C6z1#k=hFiSeQqNKY zM_hPn7arE|vbNgH$Y+CG?nE=kGA*rG(^}>CM;M*B?CEQJpc)_MqC)rjHJ*4|EQC z8ua;xwWI#=#?|*47o}u0wI>+;ke&a{%WB_7x>Ip{;*h*aW;tWNe7kjJq;;;0N6SK< z*cDph#}g?(f4HU&TDbelhAAtbZ>hW)y=UcUwD^72b0Vh z7rJgAO3C`h()^NG_at$=i&>1#b=gxn)??x$vqHC?AG&^PCM{>d*^D&`5))XJF-P^k zRVUCce;DWYM0!Jmwrkh2htbZts@s|Kue_80@%47QlGIjzZfot@FSP|KHJ?)t_+(bB za8T=bZ5)2%Rhy9>$5VA{de_!%H}+cl8XEr|Z(p8oVq6kiRiARF_vEirytyx&&Y#N2 zd|h6=E4l_}=mf0bxo75V+{u>HMpatA^-eLL0MS=adM^&=Ih z)H&idQ75(^C^xb6TC~n+WM)q)KiKF;qfe4l?6uNK%d1WF#LoS!wOZQRoN@ZDlX3`i z$!6n;Cqlvxv+BksOCCLY&OXPyKr3mo^5n7$g)>znM?FqxlF$oKeCeT3d9yfwnuUx{ z{cW>n(>HEkD4p1@W5g{}nd8sDw|~(@{K*&mT{Q7p|8~9K751{y68hZ%CwIWp?_T@vzqf>}?7s#0G-^-%L)H!J`iK0Tp82P2QE6TMr!0Bk&f7ob z;ct?*{2?cN)_8s54>{MU*z46FGV}Ch<@7(~3x>7(j{YrMphhS1^l$m?I;Pe|@>6R6 z=392*T^#AM7H$bk6)JV^-5(HXo68m&$0d_b;e}iIEWWvXaPfdh%j>+>sC@4BfSj8o zYvD6vcG7@+>44(qlLsOW49MdpG(HdAbhU9nPU2o-7=>C(n1pnCHl32ghoNG}}I^2rdWG= za6(&YzUi&zg!^~1Z8zsfTE1WWF3@lPTIb2htMncgOr8-wt-xY}XrcClAg_wen|B0o zUN$*9Hr-gzWclq?`RCtzZ8}#=Z(6-3fAG4c;vafWU(5{Fy!UbNez8zJMHho_hsV{> z-=#&gU)^l8WN$&w=bhZp`XP%%T7Q){yH37g@{w+4}LJ4d`r-TT??nqPB4({FqA zxO06&!Y(cjJ3V3j=K57CL*+j$?Y12^&8~Zc`}l`Vi|==CFFSGd)%y)G+dD4Zuuh-W zR;RH4luBop%fz95=kFNp^Xq)#*yUGLQaH6orBB<-c78@^cl4yIutJ zb=iKUrD3wA*PU`a{9oQQ=v+A=E32VneeNRf%9R`UO_>~POQ<*4XKsV(mZpotVpymSghTc zdtUEu>F^_B_it^c>Nn?`^S?X_C{;@5UQFzL9{;c*NGIOK+&%B;m8yKx*@L!JQHy5j zG?nt^|9pKtIb!|^9s1PHI3JOQIrEPsl(XgPA?RDFXmG1EjzIeP*F3=Bg+UmZ`Ymtu5gxQ8K_BF-UKd%T+ zj1@gHVnf8z9UJQ1K4-T~_Ro~q)we*T!+dPciR@?TW%KLSS=MP!ej{;#_G;z!W3wvs zJGOLmdc1J`dS>VRiO)J;jSZ>@8a5*Q^A3-}bCWkjcden_dLCbwviQuQwhuvvq)PTD zuxH=zXxq3*$-j$wn3-`+V@ibh+hZQdn}3bAOnvfrmFz|q&**7a;pjy0Ae^|ZpjJNrwl~Eei}2T>~@I#K7P_U`G;dGh$@n;>1) z8F|#0yLJs#l-$gpFIFE5IKZqi*mojsJO;(zq63oR*ZMftKg-p8DLtvb)NpfkpPjm}Q*Y_~tRGs&v1wCMTWM;*`z zRH)0oUvF02>Rj=Asa0caF{qrL`qoo?N|cEOpc*d{jh(!h5Y3H4loa zk}N%KCeuW!R6D{S441t9x^+vm-0U-A1zTlWE-JNKZyxa|_~6*=ODVB~p2X+1%q;Jd zs?WAPRXr?Y{>=NARYPAaz5D6c-37xwtJaJjy2s^ZnrNVhS3%2;K`m|6QQJ34M>ef8 z{MO`oX1=Gtm8wGC;Kxzwy`QaP7A>h>X&-rrnYpgWRVCiRo*f$U^pW^O+uqWx!F~$6 zAEhuEceieJzSncN!fJwN(ye0Ka~F2b-I_c%`B<@S^;WI*BF2?#KAHr7EL4-d7aUs_ zp8nfk>#z;I`=-tVQR~cm9AkC$C7q!@V-l-4Z|&<|+pKz1aJ%TjNqT+(&v#e2hP+qa zX0FWGV8(Tcw59baMUPDU=d^}t4%SRqw_X3?OndutKaV_RhZPUc4X>+|EZOuIR=wX9)T_s7#a_J^cOogWIAmAfLRVSllHtF_?!6q+ zB$HHM?GUWER_4pEn`v{q&kYLr9O8M-E70TEO1kNe=J*k(vwfC6=rfNU(alKy?cmYZ zb*lG%Q?GIIT=r$rA{0d?Gsbo z^1UlZYVr3Bg?DL_Z=RHH^Q&a+9h3eO|!go%lPB`vu@9Q%SsepuhPqtui$w* z8HHspo0YNqnb#8gU9Uu{a(}K}Ij3{Qxp#eS2QAY%QAwv{HhIXZ9DlvkU-8z_OPhuq zx_@lp>{nj7JL>k@J+1k4=s~*Im544gX2M;qj`cG=#HO5F_H;;_154wYe&NB;Ly|d# zO74gAce8bWf0KN`e3+3iBTr8`S(6$6JEtVYitG9QmQCMgT|L`br!6y!T^*v!IP&Ai ze2kGd7_qhY_S<<|wy)cnYE{WIT<*DZd6Lw_>R)4gGDC)>je9iu+|C1;n?qJ6p6s6iF!D%^q|iX>U8(+LjT@SnN;q0dBvqF z=MIHkxbd^fc2LQB?%ciE`y+E2zS>S4I&06&gX?$rnHQvJOvrIej{Ev@W|_&V2~C@d zb+4XzQYc@p96Bp$uSor;+aG_vZ=Ru&x6fulfc2Z%apx=dWDPlfG{4osf3K|1qO21E zcC}sVPsR3nPE45{FZx6C_pEpAg>E<5Cq#?yUrl)u^ILw~$JAyk?GJ^###3!GT~9x* z9Pw_w>w-=x(bb09!*#oFxAq=bP;@C+ev{;Gccu84{7tnfHX$phh2Pz5I=t^~-FUpL zIJ5ro>dRw8-G+|a_-)a11=(evU0>F$_06oZuFA1XOYgWA7rh{JTTOMr%s@+z!^eiw zw%@$8)HF2YM%(EbhL83bo%z99wuAlBQuCw2ryr>d^`5#{9qKl7Zk2RB88M->UT4Wc zmA6BiKP0<_Hk*mujXrYwzShgd>)oScd;?e)*V<1@3UW6{`5Jyxy5>}aY2hQE0IIA< zxBTMTdm#&Z*f(9OPf_52dy{V`wKe$CByKtd z>!0U&Tny&E-XFK@jEk+$nz3U<4Zp8h|LmH3&Z5Gr%a2;+U98=?r9#AFiFZNjuScHS z)_pHMHzr^F# zd{90$Z>r(!GlQl_i^}*fQt8^)8TBka;{8)!sW%%YHQca!P8RHWXHWj?u9v9b43uI!FScaHM6?9=31Y|Y-KdTI75X@~hv zCmc&fZ&aLO3>iOCef#4Rxi3}kN+kIovN6S&*L&{NV~HeKxK zWH0@cNkv1V2gPKB?fBsQL^1Z8?lkEUH%DBa@alJ#%le3toCk}->eejl6M1}k!kaP? zH%ii}A>40HulVh5k-_(;l~6OEf15lZZeH7~m_pf<#r)NyJ9^6kvW~x+=X&!&k(g7S zW#PC%tCr`gj4ra3yzQLJla`wDRL!Yni^e*=_C#Z*T`9lQVVk#SMQ~U}OTPJ91$U<+ z)t-}|Us9=^*VvYL=vIQFw^i73y?bXzDp<8>&*9{$_edwt{W|s7 zk6kXRheq089$K+I=;Ns8Pbb(E=08}zv+wBs6;!uHw)}zhZ`oW!%iYtGySy`X)zhaP z+{axXzvcYPp~FvBe=PX0&*SR)j(aD*JUSX36mnSdP=LbeyPpcS>=@Qv^gM6Dl```u z3$GoK{CfJ+@Y^1I-cN<}jP<`CXME~D@`ckq)$zxZ2fHfN`4f9G)4%)6Ngl3qkg&-w zoH}($&4;n4l@ut@^*Y?f}3kI+Axs;^u*7I^_SoGHNoh{{GCj2yx4Xt>z zCRwU7@0_aUsI*#hIadBwt+&lB4xWrE+#zd3~lXe9YdrX<7M} z>QDBGGO>%TmW(jJ8kIBHqe94_Ui7JZaAD3T= zT=B(Ez46Z3_xt_SeMYGKYN!X9`{McU9-ruTv9#EPx;01lk0Z#udcepY%3o2+06HWhHjb7 z^}4;iwth25eYjFBG0ga0rp3X@@e0d}&)hwAr+D_NWX~zru9c>D+Am7$vX>pHUKBm& z_i6W!!Sm8agw9j^p>s>h=yybiVPaIJ=eY-)JUtRiSN^`>`qSvrkT1qJK1CbmyUkx! zS+smob;L3A^CP9lUw=2otM!(0qwdJp4;SiGa){(eukpSe79V6U*q~( z){A=2Gqbgk`8_O7_?RVvM< z3HndU7#1yqPuIKGz4qfzejX>A&2;u`?}{!9fBxf|$DpgC&*&~oT{17dvldlL7%TC! zDJ${ypu+0v*E2T_@n+rrnYdhAi6M#Xy(Xi!bBtd58irp#>Q~Fd%3AOV0e49EI4|{o{NhlX-yVVBQL6xVeBt`+!V3>eK*6 zMF{2r7OuYX?|FbVyvdxp1M>i%LxS2%Cr}II&y12+PcW^}#uWAX&z)E!HKx%(K@QMW6(ffeKIsY5>!n&@{kc zpb4~qHW&hQz)+wIhJoP#lX1}WfdMcC7^F-a31Bp6#&Ac0(O?WP0b{{9FdmqJ31A|a z1k8a2z>r&-6|e?2z!unn$zTd_0FGcPa01i7bl?nT02km2W&$_h4v5k80JFgyFc-`N zo&W>CX+Dh=ERln+nzecgXAEk}+Bv;HH6e@C0lEv$20O z+_`YKz}*VA0dL3|aQ)zB!Nu5D+IG0XaI@j=0J{KLyLUI-NVt39#=y;mn+NuR{U9G4 z00kfc#|z;WfntydxeV?>a0sMAJ`DE=I10#IJ;&gZHGz-AEe9t+1*imDvA+uLNl**w zz;a&!p`rRvVuYs%m?sd40pb6XnH^D7%8{7fS;4Zia?t=&5A$SBHgQwsb zcn)5Gm*5p>0j=OQcmv*oHqZ{B7dnkc}qbecGj0FnShS!vQh z2FL0}ZeT;n0Mu1+>8spaX^iT`&v`2YNss7ywL6LmL4` z0wZ7yMuG7lKp%Dh?gTIqd*A>Z!BpS` zrh(~T5$Z*YK_rL*OF%4G1<0KI$)F6Rf`eckI0V*%!ypY@1L@#8*aRBE7H|}71;@ZP za2#OBKdl^Of)gMMRDkWE5@draumhX~IiMQs1T|n6s0F)09oPd-fxX}~$OUIW9ykm3 zgL;q;&VvKs0w@3tpb%UHMc@)B2A4qzxB^PSRnP?Pf&1VAcnBVW$KVNg3Z8-I;01UI zUV#?S3SNUZfYd|VKs$H`IzT7r0^Q&^I`v2+eKERs3`*)8g5Htxz4U;e$4)fwriLcBqB+h=c|G5rt@2 z5rY93h*%85V8kIF2}r~c3`G)>k%CkV!*GniNQ}a0e1I|d5Mz;sahQhbn1Pv?h1r;c zxtNFfSb&eQ5R0%FpI`|-#b;QGW%wNF$iQ-}z)GybYOKLptV1T&V*@r~6E%4-`US z6hToGLvfTqNt8lqltEdPLwT6t2`^NDH!7kMDx(Ujq8h5h2Q^R=wcrN}{Lvo)h(aKu zQ5#kSAqK%1fDjBsC}I(YL8yblsEat%LpJDN@l4 z!_XYV(E=mT5+l(HqtF_o(FPx&Eykc7K16$rMF*szBgUZ<#-lSPpbI7<0+Y}clacPu z@4N>0InqcjCvC_Tq%FCUG?A-FJ90H?Pp%;y$hD*+xsG%qGf8K1J?TPjAYI9gWC3y$ zS&-aJx{+H*cXBJ~L2e@pk=x0_>AkROqI$*JT%avHgxoK7AfXOIWU zndBjI7I~PQO&%fVkVnb6B8x`5AeUTuNRdmywsr&&exfI(e1MAg__j$?N0_@&>t*yh*MiZ;`9X+vFPZ4!M@R zORgjDk(uOuay|Kg+(14gHzvLS~U$$*1Ht@)^0Ed`|8lzaV##FUVcw zOL8~)CAo+EirhLBR?hY zlb?|f$fe{%avAxE{G5DDrjt*|3^I#cPCg}9kk81K&b7(4dl1vM)Esy6Zt*4nS4cVAzzbQ$sfpV@8mx65Aq=S7kP;Mn>+B~TKjP#R@W7UfVLW_ZF272u7EsD#R>f~u&7>hM7g)I=@#!Vmrk zKp<)(2*C(JD8f(&bx{xX(E#CSh(>6PCTNOgXpRXpau)h)(E?E{H%^ zbVGOaKu`2SZ}dT5^g|>p=#MBw!-^OTz(B-e5C$U-@kl@-hF~a?kcpS|Ou$4;!emUrN0^Ffn2s5kiCLJ9Ihc!in2!bc7|ZaOn^!uSf#q0% zl~{$cP#W|eE1zf}>T*eh##Wh^V4cx>n+{PW;#Xa1|13bhdJjN4b;VGWsIljOPyu_FI z3SZ+Je2ee!Jzn88e!!1-gP-s-e!;K!4Zq_Lyv3jR3x6XUj^+6!zzIe;!v-#}g)2-b z06P?fJ>1}c5^zUJc%T#tp)?Ak42qyEilPe4s0vS1gBPl!0({_&8mNexsDxUmj1c%D z6n+SUKk6U=brFbqsEzsvLIVV&1;Wu14bcjX&>D@=22IcwP0<4#(F>i?2NCFpZWxG2 z#KM9>=#RmOLL8zI4=WN7gG3C#M@Yj|jKegH$8=1<3{1pKOu{To#%xT%V$8uOn2RNt zhfgsdpJ4%(;$tksLVS)zSc`P5Lk2Rj9P6#f71%AT_{Em_M z10Ucm#^6u9K_yQv6YmdIBL{O=s7`)C`jhPti6O9HDEcD_QAkEKQeZ_YVlWH?FdPFh z07>zi5fOw2S0zO0{#$o~{VwX493wGlw_TU=!;yU)>2KM774&W9J;x-Q9 z4i4ikj^G}S;y#Yy0gmG#PT&zv;xSI)2~HymXYdqf@eJqi9Ov-`F5m?&;w3KOOI*fR z&_9Ch3nTns1Ao{e044;&4z*#AAUGfxjtGGhLg9=sxS$SPQ5OYJ4+T*lZfF2^gu??3 z!P?P=MktKND1s&^il!)rW?*$}Lvxfs3zS4lltL?%Mr)Ko8@~< zh)I}?DfkFeF%8o(12ZuTvoQyAF%R>x03Tx^7GW_yf&NW<{X6!bVJVj3bEG2!%drA0 zu?nlP25YennOKhv*oaNoj4jxTZP<<-*oj@(jXl_leb|o!IEX_yj3YRTV>pfzIEhm@ zjWallb2yI+xQI)*j4QZ`Yq*XZxQSc1jXSuDd$^AWc!)=Mj3>y#Q#`{r_!i&cd;Ewu z2w%St$60Oi0ZO|6&&>kJo5uMN(T@Zn;=!Wj-fu87v-spqA=!ZyH z&>vBVh7~awfPsj`APhzv;*o}N7>@~x03Tx^ z7GW_y!4iB5uQl9f;f;!@gvzLbs;GwQ@IejKL@oFu0D-8DAOs@>p$J1A)I~kiM+1bT zAsV4EnxH9~p*dQhC0e01+Mq4kp*=dFBRZiox*!5w(GA_v13l3Tz0n7K(GQWZpg*Dz z4J%?W00R+=K^TlU#3KQT7>Q9BjSnyeBYE8UkQ|FNjKg?Lz(h>KWK6+y%)m@oc|4gz z&c!^;$3iT^Vtj%n_!OUEDVE`Lq$2~%u>vcx3ahaOYq1WQh~fLJCwr3{NDH}-Dea1e)Z7)Njv$8a1cumC49p7T9L z&fiW$mfAZKF>4qd7zQc z^H}*8!Ei?iJP?XP2t#4iK@rqNQPe{*)Cd2L2%qN}`8?3b=Xpjx4>a<5o{`T3jeMSG zmC}iLRtiTwo#D`dg zu~>~Xtid>}#dxg41Y}|&)?*SjU@|sh3O3;*Y{pbd#lF>YcZZebB_V=?aF6WqlT+{351kI(P`OYsoP z@CcvdG1Bn_8OXwNJjDt;!%95IDtv*}c!4!|iM99=>+lsa@io@t8*IS0*og123EyKg zUSSJfV=I2ZHvEVV1-Q+kBZi_AlF%8+=zDGM`J{x38K*yRy0ElnqvT3U?5r|7OgM{ ztuYvF5QnyiM>`~-JrdCYL(maJ(FsZDjAV2{3L=n-t{8@H7>@23fgTu%o*0E*7>(Ze z0DUk9eeogsVJyd3=it_zoAL*=@~mYj#^R+^=y3n%&k6 zw`R9B!~F)=q1kQCaBFs3Gu)cp)(p31w>879*=@~mYj#^R+?w6i4ELXS0L^Yc#NT)X zy)+%c&B6!+Y+(bQIt{$6FgU;-PH=<^oWVn)frs&&8Sa9l2i#E@g-{elP#ncj5G zsE>LGM*}oMLo`8SG(%IgKy$PNx1^j|?pAEKMH{q7J9I<`bVescpbNU8D|(4sPQf?&1ON;~{3@F&-fcPw)&+@dcjaC0^hwe2H)HHNL~Qc!lrr8aCXXek6au zn|Jv$`4fJ}Z+MG8@E86>HvWc@>y|SxXtvr0nyuE1wFB&-*=o&LYqnZ5)|#z$g=VWY zW3Ab0%~)%;S~J#~t=5dSW~()0t=VeLSZlUgGuCCH8Eef}YsOkL)n3p{wPvd;qY^Yz zt=VeLRBN_cGu1xOOtogKHB+tGYRyz@wpz2)nyJ=%wtDY2oYZ@_jmU;*j%Luzv}UI@ zGp*Ta%}i@{S~Js{oz{E8dT&_o6~~i$Z&>dUcP7VieW{Z$nN$~HDyc5QG*aDz*<>$r z4ypHC^xjJ(IiLL&Qt!b;kqg+b_d4{Rhu#a)dqOd!-m@7I4)?}?|8tJy!E)O#=!$aUEt$Y2H6zzu$%pQZ%FSE={+O8chr>UJk4}#)?72^nl;zV`EY3FTrfWv*=r3;&_@l*X+4w&gVfh=bAm& z%(-UIHFK`nbIqJ<_FOaPi=dh7#n8;TE~jSBmta3W#R2Gf`yiG=Gv~{one)$a1nJPs zc?OOlZ}wcN*>laHYxaB?&7NmMv*(&Y*NnYp?KNYsS$oac zYu5e>^mAT?F8@vFd~ZRQ=QcFEeiwIeADUf%hzEF#N65kxXl7lr>zY~D?7C*wU*Icf zc3m^;nqAk-x@Olkv##0o*UVhBcYgrEdMQ4(P&g*qsWx+sHsD2w_ihXyE*aG22$o@fLwG)4t9fj63> zBATHRnxisWpbA=|Dq5i$TBADJzz1zn1MN@~?NJLIzzsKNX5UD5f(<&u7F}RM1nkfi z_UHx&bcZ8)zzIF!j9zd-Z@8il3ZO3vq95E~K_sHkA67(T0Adh}ff$TIh({a}k$|BX zf@CBi6)6~wVHk-K7>!XFgAXtkA7UKRFdfq{6EiRyvoIHPFdy^qF&1DE7UB~u#-~_< zrT7e=V;M4#julvrRal8NSdDd9i}lFFMr^=lY{FJ-!FFuJPVB&L?808`!G7$+K^(wg z9Ktai#R(k8DV)R^oW?nv#RZ(lC0xX19Klsw!F622P29k3+`?Vl!F}ArLp;D^JVF+p z;2ECc3p~e5yuerZ65rr!e1~uG3g6=gyv7^+h@bHje#I~N9lzl%{=i@O6WRD1MrN4} zu!RlmV1fhep_z5fu4`spv+J5!*X+7x)-}7XnRU&sYi3=u>zY~D?7C*wHM_2vbaB;f>0u1kJ2#c3m^;nqAk-x@Olkv#!~73pBglADUf{ zf@asFq1kmSG`k)H&8`oCX4f^duGw|XtZQ~%GwYgN*UY+R*EO@Q*>%mVYj#~T>zZBH z%(`aRHM1TL&8#uJ#J`Z#EIeLOU~J^`9tp9syaPl9IGCquLAQ((vQ zux3~_%c>bx&9Z8SRkN&`Vbv_FW>_`Lsu@bx&9Z8SRkN&`Vbv_FW>_`Lsu@bx&9Z8SRkN&`Vbv_FW>_`Lsu@t#gnl;wUv1W}mbF5io%^YjiSTo0(HP*~=8mB45{c{{ygd9(n zBPWoauB2H3&| zb}+#K_HcqDT;PlXaD^KR!UOIoj6x`iA}EexD2Wm%jZ!F!GANI7@Pru^;Dw6tMrBk& zRa8NBR6`B;pcZPv555S1KWZZo!3aVqLQn@`sE4{}fcj{Na5P3EG({6MM>DiU3$#Wn zv_%`VM>}*x2Xsa!M4$`0p(}czJ9?of`k*)Zp)V|mL=^hNif9Z#3}P`5gE0v4h(jV0 zFcd?Oj3lHY1;a56BQXM_F$!bw0mkA(j6)hGU_2&aBBo$6rs5+^$282u49vzX%*7nc z$2@$D1y~4s9%mMj4&-9ek^F>oBA1ZPiSV;$CFJuHvWd8Jii|p;RG8v!xk79HTi?X@G>1D)W?{?24CbU^|3N05Ec6$RPfjqHa= z41@)-=#N2&!eB%r4pzh?1_>B|L=3?`48?vV;S7>-7AZJ~RGh~!+{19(hh|ZKgJw~G z$0&S_(f9_MP5lF7@D^!s(F3Bde23Ngq;k$u-E&$(m$3S&PgdeaYpdAGw0`Cs&dI$WGnItc?VZ<7uV35 z!>^NV$XjGv@;2Fyd_sPSEPREhXwTu#$PVOlvLpEg`7K_c6Z>D1oyi}`F60|Bg8Yf> zO8!E2BY!2klYf#u$abDw{}_Tu421+`vBE#D3hu0o=wx+`%E-#bMmT5!}a7 zJiswL#Bn^r2|UJ0Ji#es;WVD&44&aEp5q+8z%*z#9>$ zh_0xFZm5jzsDd7-ik_&3UZ{@V@IfEcKws2EKh%N+k+4E*jKq-j$${hm3_>j8Fc`^5 z!Z4&_9MUiW<1q;nF$I$`6(3S6u>?!;89v7{ zWFQ?YupFzf5^Jy;>#!EgbAk`gTPO3Yu4!OGH>X55Dt`51nW=Ru z)g9kOsyn`$RCjz2sqXk*Qr+==q`KqkkgGec4!OGH2T66u50UDQA12iuKSHWIew0*q z{1~b3xH{zOj;lki?zlSS>W-f#)g4!dT(jYt5!YCxMssOBd*zS&4_C@Tr=XD4cCnLBus>6!!;wW*>KH>Yc^an z;+hTDjJRgQH6yOsaLtHoHe55}nhn>CxMssOBd*zS&4_C@Tr=Vo_#58~72%D_sD!Gh zg6gP-8t{Q;!)qb{{-}*W1S1Hc2tgf$p&sg@0qUb6!qFIw&=gJ39L>-YEzlaR&=zf= z+3R);t>bUh9_VY zM&bjE##nraaY(}ijK?HQ#1u@%3{1yd%)xxjgJz~rU=bGL6D-E3Sc0Ya44-2eGLViH zSdLX#i8WY_by$m?*n!>Hg}vB={n&?tIDo@Agrhiu<2Z&hIE`~Si;K7b&1zqQX0hv*z1Lt?i&S9<;WD)_Bm`4$H}8QfoYDZ3nIKptT*e z#)H;&&>9a~+d*qQXl)0r@u0OGw8n$hcF-CRTH8TuJZNnPt?{6>9kj-S)^^Yu4_ezn zYdmOe2d(j-wH>s^gVuJ?8V_3AL2Eo{Z3nIKptT*e#)H;&&>9a~+d*qQXl)0r@u0OG zw8n$hcF-CRTH8TuJZNnPt?{6>9kj-S)^^Yu4_eznYdmOe2d(j-wH>s^gVuI9M$RX- z#)H;&&>9a~+d*qQXl)0r@u0OGw8n$hcF-CRTH8TuJZNnPt?{6>9kj-S)^^Yu4_ezn zYdmOe2d(j-wH>s^gVuJ?8V_3AL2Eo{Z3nIKptT*e#)H;&&>9a~+d*qQXl)0r@u0OG zw8n$hcF-CRTH8TuJZNnPt?{6>9kj-S)^^Yu4_eznYdmOe2d(j-wH>s^gVuJ?8V_3A zL2Eo{ZHK4ic2a9RXl)0r@u0OGw8n$hcF-CRTH8TuJZNnPt?{6>9kj-S)^^Yu4_ezn zYdmOe2d(j-wH>s^gVuJ?8V_3AL2Eo{(5WYXFBr*Qqz&1dv?cqHCbBPSNA@G_$w;y= zX(5Y{{mG(a6j_XnCX16+vSBvQU1$U^G)4t9fj63>BATHRnxisWpbA=|Dq5i$TBADJ zzz1zn1MN@~?NJLI;ERs%Lnrv7GXl^BfrvnDbVU%lAsFf+gg{+{Q1nC?s+f$t=*aCN z7|Cj|L3P-|2PV{j9cscJwcr3>IKmI=B>2M_0dRr32(C~Up#aoHCLfISItk68PC^Tq zp)P_aTEPpgQ2}k>jkc(WcBq8*sEj+1=LA+0d*2yLY;&!aRllj9L3i-hHr2j-{J(m!%2LPQ+S2b zc#Sjo0qP|Dh;w*@^H3Mz0@Ovg2z3!IL0yE)_zhR^JFemnT*F&jhq?&rB&ds^PJ+4! z>LjR(piY9i2BLK@UX7zcF`)JaemL7fD35!6Xg7eSo_brIA_P!~a+1a%SANl+I-odk6e)Jaem zL7fD35!6Xg7eSo_brIA_P!~a+1a%SANl+I-odk6e)JaemL7fD35z?S8!Z@glFdoM- z0qP=5gt`cmpf18>sEaTK>LPpubrGgQU4&^+7hyWoMVJ9~5oSVNgjrA*VK&r7m;-eY z=0aVBc~BQYodk6e)JaemL7fD35!6Xg7eSo_brIA_P!~a+1a%SANl+I-odk6e)Jaem zL7fD35!6Xg7eSo_brI5`EMOcn!SOIkrRzh8bRZtgUHPl5|19cJBLS2M)P!}N+ z>LRR%x(FMfF2Y8ri?9jmB5a1b2wR{o!d9q@unp=WsFR>Bf;tK6BB+y~E`mA<>LRF< zpe}+s3F;!Klb|kwItl6`sFR>Bf;tK6BB+y~E`mA<>LRFBf;tK6BB+y~ELN6Rx(JP+E<$6di_iq>A~c1%2+g1_LUX8#&;sfrw1m0{ zt)MPKYp9FR2I?ZTg}Mmspe{mtsEg16>LPT6x(J=1E<$Ihi=a+|x(MndsEeRZg1QLm zB&ds^PJ+4!>LjR(piY9i2Bf;tJOpiaVRsFR>Bf;tK6BB+y~E`mA<>LRF< zpe}+s3F;!Klb|kwItl6`sFR>Bf;tK6BB+y~E`mA<>LRFM`-s9o@ zeeLu|)-w=3X=tqFoF4@ipzbK*!Op8=T*5_sF;1knKZ!Kkam$h2I_L{Wkma zw)4KPj?)N^<#R8SPV1C!+d1F1OTKN_eA@-`Z5Pb9?Urx*fBO8ZWiTSqwr_X<1zVD@3 zVcov9o1AZ3hwHYcUEa2im;dur^NyqadG|p>zHQw{GLZj%ly};^<7>Y@gVlLyr#a)iZOszrZR_y7)8=jKYx$4g zjMIyDN_Anh({`Jjt=#RruPO7srpFsSMrfCRTk~w1Vbf0AdVI;-*36uCno-Mt9L>&Y zx2Ui?pX2d2G2HU)uf}$TeEX}jT>@Kp?CDcD(paeY6Z66HX3r0N%(d)%BLj-2KMDM8 zWn_@+>hz#;Z=VDi{(jQvFQcV#nuBL^qjOes?*bVu))(|_RjY(&>pCT~dcG-V>E#=k z5mnCOX?;B?!x|p%8Ph&7E9UiP%YdZ^G6othTVe-X&5AYLvkWpk$r!xpsb~B*&pi{b ze3_B>_KjsIuNa5EHa1Ao?{b34%xn2bE?2stP;qm4S94_fS3Z%|tNA{up13;Q$7Zd$ zM!C1dsEJ{b$DP#=W>>;F0Th!BDgFO zOAlDOayhzLuV!>J-1F?t<>zVG|mIJRG;J?~*UJu!s4ryFAn zKe7A1SfqWklIiwC%;^r1-sw)mDnD`l%O}#s!8hGyp5GG}^Xhb0-?ip~uh*v+bk8=s zI~XnQSvDE&-5o6+f4F89iY%B>tV0n?@eakZN!+Tcub*2yPk))=IrLjkFT)QR zUa#L~RruqrzqdZyNG}{|pH?Q_p;&pd<1X)TCxcIVq43pD3i+;&EMjfIUgtjpkyj=`<^h%)=v zY}@SYBaL%UssHO7_+MIf_SkG4h3!9eC_8F;xu@a0f8{3jy35Vn%l9w;=j|N*_c**k zPG&dIsrnba*C6+73=PaV8`J6zNl$!yBbzO1hpvkkfD{D9VBHqAhN|56s``1it5&1q3;oXcrr+r6|LrdCT6lK^_kyAS<)~Hv%f6hC-u^knv2gu=_OR8| z$57iZz)OcY<@{vtziMl6uk(Hn8%6T%VZzc7o^QW4^B!{F9bCbqL{PB5uY-4WLs?g| zrb|LYLk-1?H!ohXRLM#PyB3CWZf)Nkm)B(V4BXpw4QuCVa4%iFa+TaYIsbPLv?z}B5SJzm3l%UnYFD&%8&gfgE?zROHY_)MZdhnoV3=i?Y?#1(sgbdn zv9rf zIP~3db;ICz{ZvlV{et^~BZF%`vKji4#!|md7(=cex%FI>~5!WIj zsx*y=7}GEJVE(_p+PhyF_$}$jrSw)o)$8zxC#|O&&bSxN-IAsYW}ucNgE5 zi+3gZQG=Y*+Ep>-T=JYt*T9g;4c`!c`|s?xIj72g_al!Tt24;{HCSO7mGd2WpV^$V zH|Kn~7?~48D%bB&&+Ui~UHWy!BMk6z@SGP08-D)m?3cPu_};H`PnG>E-z(=1=i_5n z!RP(Xj@baa_xo};s^o-+Mq_)=<3PRV>r^{r3NE zBQ*aP{Tk%o=i}2b+^0xEcaskE|1V#u?c-BE{{cZY^Y07rajNB0&GY?1frV`gIs3lf z>l0YXqx3)fe0;);rGi}@N`_ReV{cQxUfm`Jd!y0TXtJ?$aIkT7bh2@_bFnFCayNR| z6tXK^riihqO>vu&135xK3Mxnp(#^mEi)A;=uxPuUtn0%W-VK_YaeZ$He=@OT}O|fICcJ3?_Ik~ zmvL}(b}3RkC^T&Cx;uBBg6GU#>)>3ceoXA_c^--V4uAiuXaBwXe)==JbC)Hb)~r>z zO2pFij1?=_tlhZpzzKU7*P>;^8Z_;=dd-E4=?*1Jc~z|6;PK0EUuB;>WinT&SgA@t zNLY(jZ8~(0=-Rz!@4k^y)|kP`BgTxKwlZ_`mLpd-ZB9r${qWKJzFwp4Y)v(6V{DDJ zYNm}ZYwPD$#^mi(&aS#$6H~!zX_@xkCT~*}$3T~Mwt=qWf}M&vJC>-^G}JcA(aE=% zou_SSyV8|HOs(x|nVcP*9Ky|&O|DKswqbUq989hb9a;tl6bNvr>F8`<$+oPcP1==C z?)HvOo;4k-6)RP#Op)SF?f7O*3Y2tkwr}BB*(uefQT=N6b?lt&JK7uV+->b#dN|sY z?O(2iqjTEozFtjTob3w~4zqW*FH*+GR6K2O-RRD)Eu5U2H7(u3v2%fz4$fU2yL%OM zwr}QK*0y=eVB3Ox<514q$K;&$OG$@1wq+xXZvF)(d>WJLl6GQRo2UX4ecg*W&;HbI zeDfuH!zP~^AL>xe)Z4z2b2H~Ec7?}n2}?WG%P!ETwJFrWBizo#!RfPGRn3;jwKtj~>`DjPj4SA4igxYloVF>XYylsWlY>n``?Mt!?%I0T z7O+jbXU_*EZmy;fe)uYmJ|>%jPL;=XcPwk`Vq3>lj$c#STiP8)TRS^@dm9IPM+YYl z=Q1uOT}u{lFX-xKamPnjN}ipPTB&BOHJf)I zK7IN2<0XdhMT>oBm%o(Vu6@s54vuc_m1}+fDj_krUV}zWI}b=bbN0&BJ9l65JZ0|Z zHU6<_d=tmgCVP)@8w#eaw=+A8D`Q*I(P*k=3N$&`8XfE%Je)hY6>^Ahur-x&cCvM} zb+EPJpJ8-0+1a|-8w(b-Yv)kfp__w^T?y9?rpC54c!cw?cXJIhm95y%9B&#_G3|`q z_${`j?8pCQ+ry!lQwb;CIsLfIHrQ!g?;>e?9Mc{ascY+OAL7`|(bYc1rJQX~QxB)K2_?!n7jtT3N}FcC zex+-1lV65u+`UQ;u6B0gx0|ck^M#qFv_rO~ZQTl7adT&Olh-WJ-*0*QOR*gU?M&}X zC+pSIA=nsC1VPj)qHcCyAa zw#LPoEpbU!OY}%{|HP!E#NpOxGrJa+=(wsQVniLb4I9&5j z?#wXf{w=N7g!TB_TJPPpG3NX=^8VlQe9midy-3abw>`DrGvEF@|FVA|za&OL4XN*T z{ZkTBW0Lar{gZs+&9VOPcD?`aPWJ!xRK9-x0fDuHf|AO5t` z^_{oB2K#;4KgKJ@5|?aENlLXQq{hYd8toI-C&m)1fA2IU(L6jUHpOZlJ}{AAX8+Wf z7;Dl;4Lz87XO<2&Sjql59_~4RF!bPWEd37j9*rybl`wD3yb0PPZ^y5i`D$hr%uQmG zhs0S%nq%XK#98C52`QG8*u(^Lk~JkYDS?Y%NibWvWhI$Y6RabKSff&`(QzXUmkPQw z*UkG0F6t>T+h9$ON{St#lUAu}j!ia4TZbfBqbwY|jyW~in)hw4Ck?HzW89h652JS5 zcS_s4JU%wsT;CjLP2fE8Ziffh*5zxeZ%dZq@u44i3YFpPl6!robEKN=w2fC|M!)}AxbXST$CmTe&!2aFcIWcz_LsN682fAG+n>RmZap^gaKxYIW9Iiws}C5hAir>_jN#HCvEZlUJLu*iS5eIm`=ek1$*`$1#=IVd)o+y490 z{L8s{MaCv@5I0tC=awPXynP9&@%^nyx&JpOrlyz^W6VjGgaPl5keU!1#V^F1^S>d9 zu?Z}n|dnpHK&B&H@r|J#+7`;!m0jx@)oCa0MD zTe$*r56inSIhQ%i{QhUqad`mAIp({I7@J@onwpqmP3D=vl48z(ZZWY5dZ4K?ATh<< z&eBfTSoK;pViRJj#_?|}a~HKl>$jGi%Dr*`_iHofHPAZZ-4&9O!~>ST9+em$Z~13m z-Vd4l&!dOg%1_7j^v{WTph)F5!J|%GYj4O zkb7?b{JN`HYYwPsP97Q04QQ}g$E%v0V&Rb@@0!xNmkIi|?&FW9~EOC~Hz;LgM?&mCVZ_YqHfk*v!o>H9=cBuaopWbl0%FzxCyO z*ObIWbDSk zp_8l$x}cm{yd_~|&cQqe=02k69Q0MbORAoKa~>ACt!_fQmXjo0sz+XDAl-GmakQzYd+Ag3ni*?B->QtT+MuAHa%`stz%34JbyvxVg zr9r#&oc}Yz`!4^;&G651v%~S+^!_O)(-tnyN%Mh2Iay`!_c{5yjUL7f;kz5}Q@XFZ z@ujlb%ZYl5PW#FHVkepIx31qa(scN2X+52vta)JhR!%i6Y_Q8C{v4WWs&IN5)2tg# zeludDA#GIg-IZ?NG0fSJ;6GyW6vK{g&JBtUTxZDm?!u+g$F8SOED~<%*kj_ERVC-_ zU6r^h%+35$<=N*iC6=C5#l6d{&G*_q@4M;sw+^k|e*V+q%1yQ|9p3QqWsi%WIK28~ zl1qcXw-wFY@!-Xp@{^+u^|W_cbznzk=fSC?9KLgNJnU07B6{qu#y7VXZ*!(jske1r z#U7i|Y)_*#--oyF*r(n%51)5vR;tjx!q0g)Z2M;3*PE;CdR6J^{ux0L&c9AIO?8Vr zZ}-)nT?akS9^F6XD?^us{gzA|aewyVz$*unOq~`#syuDp$A%^GM;DAQ?pD`scPiHd zyS(d}*Jsb#1QjY7d@}d&Dsi_m)0Vz^?REN>5BKiA7S^^=n=?ai#?LQ&_Un*x7fXg6 zuq_j@-k9d16`fHg&(=G2N+j`vr7^^fna<4;k8dN7)g2 zdNlC*#Lw5?H^4X0x3+JPZ?JEOZ>XQIpP!$?Z5Z@5L5dV;Xkid}IAweO*At525pfXj}9c zrTzM`3!*6WT7W-oa<;YK4f>jntH+cOl!X2U({c3}r}r;(emYFY6`iLZU-(mxzH;ti zI!-a@eDyuFt>bB1^gZ00dl<~{Lt}JC6o%m=e2f*?gA=%n=lC6tEj$dxP!;Vl1?kw0 m&7DjjcG18+eSb@xK5q9=!Pg literal 0 HcmV?d00001 diff --git a/apps/website/qa/browser-audit.mjs b/apps/website/qa/browser-audit.mjs index 6389202df3..34e4c0251b 100644 --- a/apps/website/qa/browser-audit.mjs +++ b/apps/website/qa/browser-audit.mjs @@ -14,7 +14,7 @@ export const ROUTES = [ '', 'gft', 'start', 'select', 'verification', 'ip', 'proof', 'blog', 'cases', 'course', 'resources', 'formats', 'ladder', 'theorems', 'bounds', 'landscape', 'reproduce', 'about', 'queen', 'dashboard', 'tree', 'play', - 'chat', 'quantum', 'lab', 'canvas', 'wasm', + 'chat', 'quantum', 'lab', 'canvas', 'wasm', 'specs', ] const CHROME_CANDIDATES = [ diff --git a/apps/website/scripts/sync-t27-specs.mjs b/apps/website/scripts/sync-t27-specs.mjs new file mode 100644 index 0000000000..2af1e61afa --- /dev/null +++ b/apps/website/scripts/sync-t27-specs.mjs @@ -0,0 +1,100 @@ +#!/usr/bin/env node +// Vendor the t27 spec corpus and the compiler wasm bridge into public/. +// +// The explorer page runs the REAL compiler in the browser, so the two things it +// needs are the spec sources and the wasm build of `bootstrap/src/compiler.rs`. +// Both are copied here rather than fetched at runtime: the page then works +// offline, deterministically, with no dependency on GitHub availability or +// rate limits. +// +// The cost of vendoring is drift, so the manifest records the exact t27 commit +// the snapshot came from and the page displays it. Re-run this script to +// refresh: +// +// node scripts/sync-t27-specs.mjs +// +// Requires the wasm bridge to be built first: +// cd /Users/playom/t27/bindings/wasm-explorer +// cargo build --target wasm32-unknown-unknown --release + +import { readFileSync, writeFileSync, mkdirSync, rmSync, cpSync, existsSync } from 'node:fs' +import { execFileSync } from 'node:child_process' +import { join, relative, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' + +const HERE = dirname(fileURLToPath(import.meta.url)) +const WEBSITE = join(HERE, '..') +const T27 = process.env.T27_ROOT || '/Users/playom/t27' +const SPECS_SRC = join(T27, 'specs') +const WASM_SRC = join(T27, 'bindings/wasm-explorer/target/wasm32-unknown-unknown/release/t27_wasm_explorer.wasm') + +const OUT_DIR = join(WEBSITE, 'public/t27') +const SPECS_OUT = join(OUT_DIR, 'specs') + +function fail(msg) { + console.error(`sync-t27-specs: ${msg}`) + process.exit(1) +} + +if (!existsSync(SPECS_SRC)) fail(`spec corpus not found at ${SPECS_SRC} (set T27_ROOT)`) +if (!existsSync(WASM_SRC)) fail(`wasm bridge not built.\n cd ${T27}/bindings/wasm-explorer\n cargo build --target wasm32-unknown-unknown --release`) + +const sha = execFileSync('git', ['-C', T27, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim() +const shortSha = sha.slice(0, 9) +const dirty = execFileSync('git', ['-C', T27, 'status', '--porcelain', '--', 'specs', 'bootstrap/src/compiler.rs'], { encoding: 'utf8' }).trim() + +// Walk without a dependency -- `find` is already required by nothing else here. +const files = execFileSync('find', [SPECS_SRC, '-name', '*.t27', '-type', 'f'], { encoding: 'utf8' }) + .split('\n').filter(Boolean).sort() + +if (!files.length) fail('no .t27 files found') + +rmSync(OUT_DIR, { recursive: true, force: true }) +mkdirSync(SPECS_OUT, { recursive: true }) + +const entries = [] +for (const abs of files) { + const rel = relative(SPECS_SRC, abs) + const text = readFileSync(abs, 'utf8') + const dest = join(SPECS_OUT, rel) + mkdirSync(dirname(dest), { recursive: true }) + writeFileSync(dest, text) + + const parts = rel.split('/') + // A spec's `module X {` line is a better label than its filename when the two + // disagree, which they often do. + const moduleMatch = text.match(/^\s*module\s+([A-Za-z0-9_]+)/m) + entries.push({ + path: rel, + category: parts.length > 1 ? parts[0] : 'root', + name: parts[parts.length - 1].replace(/\.t27$/, ''), + module: moduleMatch ? moduleMatch[1] : null, + lines: text.split('\n').length, + bytes: Buffer.byteLength(text, 'utf8'), + }) +} + +cpSync(WASM_SRC, join(OUT_DIR, 't27_compiler.wasm')) +const wasmBytes = readFileSync(WASM_SRC).length + +const byCategory = {} +for (const e of entries) byCategory[e.category] = (byCategory[e.category] || 0) + 1 + +writeFileSync(join(OUT_DIR, 'manifest.json'), JSON.stringify({ + generatedFrom: { + repo: 'gHashTag/t27', + commit: sha, + shortCommit: shortSha, + specsOrCompilerDirty: dirty.length > 0, + }, + wasmBytes, + specCount: entries.length, + totalLines: entries.reduce((a, e) => a + e.lines, 0), + categories: Object.fromEntries(Object.entries(byCategory).sort((a, b) => b[1] - a[1])), + specs: entries, +}, null, 0)) + +console.log(`sync-t27-specs: ${entries.length} specs, ${Object.keys(byCategory).length} categories`) +console.log(` t27 @ ${shortSha}${dirty ? ' (DIRTY -- snapshot includes uncommitted spec/compiler changes)' : ''}`) +console.log(` wasm ${(wasmBytes / 1024).toFixed(0)} KB -> public/t27/t27_compiler.wasm`) +if (dirty) console.log(` warning: commit t27 before shipping, or the recorded SHA understates the snapshot`) diff --git a/apps/website/src/lib/t27Compiler.ts b/apps/website/src/lib/t27Compiler.ts new file mode 100644 index 0000000000..5ef39ca88f --- /dev/null +++ b/apps/website/src/lib/t27Compiler.ts @@ -0,0 +1,127 @@ +// Browser-side driver for the real t27 compiler, compiled to WebAssembly. +// +// The wasm module is `bootstrap/src/compiler.rs` built for +// wasm32-unknown-unknown -- the same code the CLI runs, not a reimplementation. +// See apps/website/scripts/sync-t27-specs.mjs for how the artifact gets here. + +export interface T27Node { + kind: string + line: number + name?: string + value?: string + type?: string + field?: string + size?: string + nodeKind?: string + op?: string + returnType?: string + pub?: boolean + mutable?: boolean + params?: { name: string; type: string }[] + children: T27Node[] +} + +export interface T27Token { + kind: string + lexeme: string + line: number + col: number +} + +export interface T27Target { + ok: boolean + code?: string + bytes?: number + error?: string +} + +export interface T27Analysis { + sourceBytes: number + sourceLines: number + tokenCount: number + tokens: T27Token[] + ast?: T27Node + astError?: string + nodeCount?: number + astDepth?: number + topLevel?: number + /** Declarations the parser's error recovery dropped while still reporting a parse. */ + discarded: string[] + swallowed: { what: string; line: number }[] + lexerDiscarded: { char: string; line: number; col: number }[] + typecheck?: { ok?: boolean; errorCount?: number; warnings?: number; errors?: string[]; fatal?: string } + hir: { ok: boolean; text?: string; error?: string } + targets: Record + error?: string +} + +export interface SpecEntry { + path: string + category: string + name: string + module: string | null + lines: number + bytes: number +} + +export interface SpecManifest { + generatedFrom: { repo: string; commit: string; shortCommit: string; specsOrCompilerDirty: boolean } + wasmBytes: number + specCount: number + totalLines: number + categories: Record + specs: SpecEntry[] +} + +interface Exports { + memory: WebAssembly.Memory + t27_alloc: (len: number) => number + t27_free: (ptr: number, len: number) => void + t27_analyze: (ptr: number, len: number) => number +} + +let modulePromise: Promise | null = null + +/** Instantiate once and share; the module is stateless between calls. */ +export function loadCompiler(): Promise { + if (!modulePromise) { + modulePromise = WebAssembly.instantiateStreaming(fetch('t27/t27_compiler.wasm'), {}) + .catch(async () => { + // instantiateStreaming needs an exact application/wasm content type, + // which not every static host sends. Fall back to the buffer form. + const res = await fetch('t27/t27_compiler.wasm') + if (!res.ok) throw new Error(`could not fetch compiler wasm (${res.status})`) + return WebAssembly.instantiate(await res.arrayBuffer(), {}) + }) + .then((r) => r.instance.exports as unknown as Exports) + } + return modulePromise +} + +/** Run one .t27 source through every pipeline layer. */ +export async function analyze(source: string): Promise { + const wasm = await loadCompiler() + const bytes = new TextEncoder().encode(source) + const inPtr = wasm.t27_alloc(bytes.length) + new Uint8Array(wasm.memory.buffer, inPtr, bytes.length).set(bytes) + + // t27_analyze takes ownership of the input allocation and returns a + // length-prefixed blob: [u32 LE byte length][utf8 json]. + const outPtr = wasm.t27_analyze(inPtr, bytes.length) + const len = new DataView(wasm.memory.buffer).getUint32(outPtr, true) + const json = new TextDecoder().decode(new Uint8Array(wasm.memory.buffer, outPtr + 4, len)) + wasm.t27_free(outPtr, 4 + len) + return JSON.parse(json) as T27Analysis +} + +export async function loadManifest(): Promise { + const res = await fetch('t27/manifest.json') + if (!res.ok) throw new Error(`could not fetch spec manifest (${res.status})`) + return res.json() +} + +export async function loadSpecSource(path: string): Promise { + const res = await fetch(`t27/specs/${path}`) + if (!res.ok) throw new Error(`could not fetch spec ${path} (${res.status})`) + return res.text() +} diff --git a/apps/website/src/main.tsx b/apps/website/src/main.tsx index 84038c2d67..7a24360691 100644 --- a/apps/website/src/main.tsx +++ b/apps/website/src/main.tsx @@ -32,6 +32,8 @@ const Queen = lazy(() => import('./pages/Queen.tsx')) // unwrapped into the shape lazy() expects. const BlogIndex = lazy(() => import('./pages/Blog.tsx').then(m => ({ default: m.BlogIndex }))) const BlogPost = lazy(() => import('./pages/Blog.tsx').then(m => ({ default: m.BlogPost }))) +// Lazy matters more than usual here: this page pulls a 477 KB compiler wasm. +const SpecExplorer = lazy(() => import('./pages/SpecExplorer.tsx')) const RouteFallback = () => ( // spinSlow, not spin — index.css only defines the former. @@ -75,6 +77,7 @@ createRoot(document.getElementById('root')!).render( } /> } /> } /> + } /> } /> } /> diff --git a/apps/website/src/pages/SpecExplorer.tsx b/apps/website/src/pages/SpecExplorer.tsx new file mode 100644 index 0000000000..1767d277e9 --- /dev/null +++ b/apps/website/src/pages/SpecExplorer.tsx @@ -0,0 +1,878 @@ +// Spec Explorer -- the .t27 corpus, layer by layer. +// +// Pick any spec from the library and watch it through every stage the real +// compiler puts it through: tokens, AST, type check, HIR, and five codegen +// backends. The compiler is not reimplemented here -- `bootstrap/src/compiler.rs` +// is built to WebAssembly and run in the browser, so what this page draws is +// what `t27c` produces, and it cannot drift. +// +// Deliberately not using Monaco (already a dependency, used by /play): a +// hand-rolled line-numbered view lets an AST node highlight the source line it +// came from, and avoids Monaco's CDN fetch on a page that already loads a +// 477 KB wasm module. + +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { Link } from 'react-router-dom' +import { useI18n } from '../i18n/context' +import { usePageMeta } from '../hooks/usePageMeta' +import { + analyze, + loadManifest, + loadSpecSource, + type SpecEntry, + type SpecManifest, + type T27Analysis, + type T27Node, +} from '../lib/t27Compiler' + +const UI = { + en: { + title: 'Spec Explorer', + subtitle: 'Every .t27 spec, layer by layer', + metaTitle: 'Spec Explorer', + metaDescription: + 'Browse the whole t27 spec corpus and watch each spec through the real compiler: tokens, AST, HIR and five codegen backends.', + search: 'Search specs', + allCategories: 'All categories', + specs: 'specs', + loading: 'Loading compiler…', + compiling: 'Analysing…', + back: '← Home', + source: 'Source', + tokens: 'Tokens', + ast: 'AST', + hir: 'HIR', + lines: 'lines', + nodes: 'nodes', + depth: 'depth', + topLevel: 'top-level', + bytes: 'bytes', + expandAll: 'Expand all', + collapseAll: 'Collapse all', + noResults: 'No spec matches that search.', + pickSpec: 'Pick a spec from the library to see it through every layer.', + typecheckOk: 'Type check passed', + typecheckFail: 'Type check errors', + typecheckSkipped: 'Note: no codegen path runs the type checker — t27c compile gates on it, t27c gen does not.', + lossTitle: 'The parser dropped part of this file', + lossBody: + 'Error recovery discarded declarations while still reporting a successful parse. The tree below is missing them.', + discarded: 'discarded declarations', + swallowed: 'swallowed constructs', + lexBad: 'characters the lexer dropped', + noHir: 'This spec produces no hardware IR.', + targetFailed: 'This backend rejected the spec:', + emptyOut: 'This backend produced no output.', + showingFirst: 'Showing the first', + ofTokens: 'of', + showAll: 'Show all', + provenance: 'Corpus and compiler vendored from', + snapshotDirty: 'snapshot includes uncommitted changes', + wasmNote: 'Real compiler, run in your browser', + took: 'took', + astEmpty: 'The parser returned no tree for this spec.', + filterKind: 'Filter by node kind', + clear: 'Clear', + matchedNodes: 'matching nodes', + }, + ru: { + title: 'Обозреватель спек', + subtitle: 'Каждая .t27-спека, слой за слоем', + metaTitle: 'Обозреватель спек', + metaDescription: + 'Просмотр всего корпуса спек t27 и каждой спеки через настоящий компилятор: токены, AST, HIR и пять бэкендов кодогенерации.', + search: 'Поиск по спекам', + allCategories: 'Все категории', + specs: 'спек', + loading: 'Загрузка компилятора…', + compiling: 'Анализ…', + back: '← На главную', + source: 'Исходник', + tokens: 'Токены', + ast: 'AST', + hir: 'HIR', + lines: 'строк', + nodes: 'узлов', + depth: 'глубина', + topLevel: 'верхний уровень', + bytes: 'байт', + expandAll: 'Развернуть всё', + collapseAll: 'Свернуть всё', + noResults: 'Ни одна спека не подходит под запрос.', + pickSpec: 'Выберите спеку из библиотеки, чтобы увидеть её на всех слоях.', + typecheckOk: 'Проверка типов пройдена', + typecheckFail: 'Ошибки проверки типов', + typecheckSkipped: 'Заметьте: ни один путь кодогенерации не запускает проверку типов — t27c compile её требует, t27c gen нет.', + lossTitle: 'Парсер отбросил часть этого файла', + lossBody: + 'Восстановление после ошибок отбросило объявления, но парсинг всё равно отмечен успешным. В дереве ниже их нет.', + discarded: 'отброшенных объявлений', + swallowed: 'проглоченных конструкций', + lexBad: 'символов отброшено лексером', + noHir: 'Эта спека не даёт аппаратного IR.', + targetFailed: 'Этот бэкенд отклонил спеку:', + emptyOut: 'Этот бэкенд не выдал ничего.', + showingFirst: 'Показаны первые', + ofTokens: 'из', + showAll: 'Показать все', + provenance: 'Корпус и компилятор взяты из', + snapshotDirty: 'снимок включает незакоммиченные изменения', + wasmNote: 'Настоящий компилятор, запущенный в вашем браузере', + took: 'за', + astEmpty: 'Парсер не вернул дерево для этой спеки.', + filterKind: 'Фильтр по типу узла', + clear: 'Сбросить', + matchedNodes: 'подходящих узлов', + }, +} as const + +type Ui = Record + +const C = { + bg: '#000000', + panel: '#0a0a0a', + raised: '#111111', + border: 'rgba(255,255,255,0.10)', + borderBright: 'rgba(255,255,255,0.20)', + text: '#FFFFFF', + muted: '#888888', + accent: '#00FF88', + golden: '#FFD700', + bad: '#f85149', + warn: '#f0a020', + mono: "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace", +} + +/** Colour a node by its family so structure reads at a glance. */ +function kindColor(kind: string): string { + if (kind.startsWith('Expr')) return C.accent + if (kind.startsWith('Stmt')) return '#58a6ff' + if (kind.endsWith('Decl') || kind === 'Module') return C.golden + if (kind.endsWith('Block') || kind === 'EnumVariant') return '#c792ea' + return C.muted +} + +const LAYERS = [ + { id: 'source', kind: 'source' }, + { id: 'tokens', kind: 'tokens' }, + { id: 'ast', kind: 'ast' }, + { id: 'typecheck', kind: 'typecheck' }, + { id: 'hir', kind: 'hir' }, + { id: 'zig', kind: 'target' }, + { id: 'verilog', kind: 'target' }, + { id: 'verilog_hir', kind: 'target' }, + { id: 'c', kind: 'target' }, + { id: 'rust', kind: 'target' }, +] as const + +type LayerId = (typeof LAYERS)[number]['id'] + +const LAYER_LABEL: Record = { + source: 'Source', + tokens: 'Tokens', + ast: 'AST', + typecheck: 'Types', + hir: 'HIR', + zig: 'Zig', + verilog: 'Verilog', + verilog_hir: 'Verilog (HIR)', + c: 'C', + rust: 'Rust', +} + +// ---------------------------------------------------------------- AST tree + +interface AstRowProps { + node: T27Node + depth: number + path: string + open: Set + toggle: (path: string) => void + onHover: (line: number | null) => void + filter: string +} + +function AstRow({ node, depth, path, open, toggle, onHover, filter }: AstRowProps) { + const hasKids = node.children.length > 0 + const isOpen = open.has(path) + const dim = filter.length > 0 && !node.kind.toLowerCase().includes(filter.toLowerCase()) + + const scalars: [string, string][] = [] + if (node.name) scalars.push(['name', node.name]) + if (node.value) scalars.push(['value', node.value]) + if (node.type) scalars.push(['type', node.type]) + if (node.op) scalars.push(['op', node.op]) + if (node.returnType) scalars.push(['→', node.returnType]) + if (node.field) scalars.push(['field', node.field]) + + return ( +
+
onHover(node.line)} + onMouseLeave={() => onHover(null)} + onClick={() => hasKids && toggle(path)} + style={{ + display: 'flex', + alignItems: 'baseline', + gap: 8, + padding: '1px 6px', + paddingLeft: 6 + depth * 14, + cursor: hasKids ? 'pointer' : 'default', + opacity: dim ? 0.25 : 1, + borderRadius: 3, + fontSize: 12.5, + lineHeight: 1.65, + fontFamily: C.mono, + }} + > + + {hasKids ? (isOpen ? '▾' : '▸') : ''} + + {node.kind} + {scalars.map(([k, v]) => ( + + {k}={v.length > 40 ? v.slice(0, 40) + '…' : v} + + ))} + {node.params && node.params.length > 0 && ( + ({node.params.map((p) => `${p.name}: ${p.type}`).join(', ')}) + )} + {hasKids && !isOpen && ( + {node.children.length} + )} + + {node.line || ''} + +
+ {hasKids && isOpen && ( +
+ {node.children.map((c, i) => ( + + ))} +
+ )} +
+ ) +} + +// ------------------------------------------------------------------- page + +export default function SpecExplorer() { + const { lang } = useI18n() + const ui: Ui = lang === 'ru' ? UI.ru : UI.en + usePageMeta(ui.metaTitle, ui.metaDescription) + + const [manifest, setManifest] = useState(null) + const [query, setQuery] = useState('') + const [category, setCategory] = useState('') + const [selected, setSelected] = useState(null) + const [source, setSource] = useState('') + const [result, setResult] = useState(null) + const [busy, setBusy] = useState(false) + const [err, setErr] = useState(null) + const [layer, setLayer] = useState('ast') + const [open, setOpen] = useState>(new Set()) + const [hoverLine, setHoverLine] = useState(null) + const [kindFilter, setKindFilter] = useState('') + const [tokenLimit, setTokenLimit] = useState(400) + const [ms, setMs] = useState(null) + const sourceRef = useRef(null) + + useEffect(() => { + loadManifest().then(setManifest).catch((e) => setErr(String(e))) + }, []) + + const filtered = useMemo(() => { + if (!manifest) return [] + const q = query.trim().toLowerCase() + return manifest.specs.filter((s) => { + if (category && s.category !== category) return false + if (!q) return true + return ( + s.path.toLowerCase().includes(q) || + s.name.toLowerCase().includes(q) || + (s.module ? s.module.toLowerCase().includes(q) : false) + ) + }) + }, [manifest, query, category]) + + const pick = useCallback(async (spec: SpecEntry) => { + setSelected(spec) + setBusy(true) + setErr(null) + setResult(null) + setTokenLimit(400) + try { + const text = await loadSpecSource(spec.path) + setSource(text) + const t0 = performance.now() + const r = await analyze(text) + setMs(performance.now() - t0) + setResult(r) + // Open the root and its immediate children: enough to show the shape of + // the module without rendering thousands of rows on a large spec. + const seed = new Set(['0']) + r.ast?.children.forEach((_, i) => seed.add(`0.${i}`)) + setOpen(seed) + } catch (e) { + setErr(String(e)) + } finally { + setBusy(false) + } + }, []) + + const toggle = useCallback((path: string) => { + setOpen((prev) => { + const next = new Set(prev) + if (next.has(path)) next.delete(path) + else next.add(path) + return next + }) + }, []) + + const expandAll = useCallback(() => { + if (!result?.ast) return + const all = new Set() + const walk = (n: T27Node, p: string) => { + if (n.children.length) all.add(p) + n.children.forEach((c, i) => walk(c, `${p}.${i}`)) + } + walk(result.ast, '0') + setOpen(all) + }, [result]) + + const kindCounts = useMemo(() => { + if (!result?.ast) return [] + const counts = new Map() + const walk = (n: T27Node) => { + counts.set(n.kind, (counts.get(n.kind) || 0) + 1) + n.children.forEach(walk) + } + walk(result.ast) + return [...counts.entries()].sort((a, b) => b[1] - a[1]) + }, [result]) + + const sourceLines = useMemo(() => source.split('\n'), [source]) + + useEffect(() => { + if (hoverLine && sourceRef.current && layer === 'source') { + const el = sourceRef.current.querySelector(`[data-line="${hoverLine}"]`) + el?.scrollIntoView({ block: 'nearest' }) + } + }, [hoverLine, layer]) + + const lossCount = + (result?.discarded.length || 0) + (result?.swallowed.length || 0) + (result?.lexerDiscarded.length || 0) + + const box: React.CSSProperties = { + background: C.panel, + border: `1px solid ${C.border}`, + borderRadius: 6, + } + + return ( +
+ {/* header */} +
+ + {ui.back} + +
+ {ui.title} + {ui.subtitle} +
+
+ ◆ {ui.wasmNote} + {manifest && ( + + {ui.provenance} {manifest.generatedFrom.repo}@{manifest.generatedFrom.shortCommit} + {manifest.generatedFrom.specsOrCompilerDirty ? ` (${ui.snapshotDirty})` : ''} + + )} +
+
+ +
+ {/* library */} + + + {/* main */} +
+ {err && ( +
{err}
+ )} + + {!selected && !err && ( +
+ {manifest ? ui.pickSpec : ui.loading} +
+ )} + + {selected && ( + <> + {/* stats */} +
+ {selected.path} + {busy && {ui.compiling}} + {result && ( + <> + {result.sourceLines} {ui.lines} + {result.tokenCount} {ui.tokens.toLowerCase()} + {result.nodeCount !== undefined && ( + {result.nodeCount} {ui.nodes} / {ui.depth} {result.astDepth} + )} + {ms !== null && {ui.took} {ms.toFixed(0)}ms} + + )} +
+ + {/* loss banner -- the honest bit */} + {result && lossCount > 0 && ( +
+
⚠ {ui.lossTitle}
+
{ui.lossBody}
+
+ {result.discarded.length} {ui.discarded} · {result.swallowed.length} {ui.swallowed} ·{' '} + {result.lexerDiscarded.length} {ui.lexBad} +
+
+ )} + + {/* layer tabs */} +
+ {LAYERS.map((l) => { + const t = l.kind === 'target' ? result?.targets?.[l.id] : null + const failed = l.kind === 'target' && result && t && !t.ok + const active = layer === l.id + return ( + + ) + })} +
+ + {/* layer body */} +
+
+ {/* source */} + {layer === 'source' && ( +
+ {sourceLines.map((l, i) => ( +
+ + {i + 1} + + {l || ' '} +
+ ))} +
+ )} + + {/* tokens */} + {layer === 'tokens' && result && ( +
+
+ {result.tokens.slice(0, tokenLimit).map((t, i) => ( + setHoverLine(t.line)} + onMouseLeave={() => setHoverLine(null)} + style={{ + display: 'inline-flex', + flexDirection: 'column', + gap: 1, + padding: '3px 6px', + background: C.raised, + border: `1px solid ${C.border}`, + borderRadius: 3, + fontFamily: C.mono, + fontSize: 11, + }} + > + + {t.lexeme.length > 18 ? t.lexeme.slice(0, 18) + '…' : t.lexeme || '␠'} + + {t.kind} + + ))} +
+ {result.tokens.length > tokenLimit && ( +
+ {ui.showingFirst} {tokenLimit} {ui.ofTokens} {result.tokens.length}.{' '} + +
+ )} +
+ )} + + {/* ast */} + {layer === 'ast' && result && ( +
+
+ + + setKindFilter(e.target.value)} + placeholder={ui.filterKind} + aria-label={ui.filterKind} + style={{ + background: C.raised, + border: `1px solid ${C.border}`, + borderRadius: 4, + color: C.text, + padding: '4px 8px', + fontSize: 12, + fontFamily: C.mono, + outline: 'none', + width: 160, + }} + /> +
+ {kindCounts.slice(0, 8).map(([k, n]) => ( + + ))} +
+
+ {result.ast ? ( +
+ +
+ ) : ( +
+ {result.astError || ui.astEmpty} +
+ )} +
+ )} + + {/* typecheck */} + {layer === 'typecheck' && result && ( +
+ {result.typecheck?.fatal ? ( +
{result.typecheck.fatal}
+ ) : ( + <> +
+ {result.typecheck?.ok ? `✓ ${ui.typecheckOk}` : `✕ ${ui.typecheckFail}`} + {' · '} + + {result.typecheck?.errorCount ?? 0} / {result.typecheck?.warnings ?? 0} warn + +
+
{ui.typecheckSkipped}
+ {result.typecheck?.errors?.map((e, i) => ( +
+ {e} +
+ ))} + + )} +
+ )} + + {/* hir */} + {layer === 'hir' && result && ( +
+                      {result.hir.ok ? result.hir.text || ui.noHir : `${ui.targetFailed}\n\n${result.hir.error}`}
+                    
+ )} + + {/* codegen targets */} + {LAYERS.find((l) => l.id === layer)?.kind === 'target' && result && ( + <> + {(() => { + const t = result.targets[layer] + if (!t) return
{ui.emptyOut}
+ if (!t.ok) + return ( +
+                              {ui.targetFailed}
+                              {'\n\n'}
+                              {t.error}
+                            
+ ) + return
{t.code || ui.emptyOut}
+ })()} + + )} +
+
+ + )} +
+
+
+ ) +} + +const ctrlBtn: React.CSSProperties = { + background: 'transparent', + border: `1px solid ${C.border}`, + borderRadius: 4, + color: C.muted, + padding: '3px 9px', + cursor: 'pointer', + fontSize: 11.5, + fontFamily: 'inherit', +} + +const preStyle: React.CSSProperties = { + margin: 0, + padding: 14, + fontFamily: C.mono, + fontSize: 12.5, + lineHeight: 1.6, + color: '#d8d8d8', + whiteSpace: 'pre-wrap', + wordBreak: 'break-word', +} From 06fc91a84b4097e1985c802a86f4ff75fc73bd82 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasilev Date: Sat, 5 Sep 2026 17:49:31 +0700 Subject: [PATCH 02/15] feat(website): whole-repo corpus, real syntax highlighting, nav link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things, all driven by the same point: a corpus that stops at specs/ cannot show a problem living outside it. CORPUS 497 -> 667. Now every .t27 in the t27 repo, not just specs/. Excluded: .git/, and .claude/ (git worktrees, i.e. second checkouts of files already counted -- the same compiler/ast.t27 appears in every worktree). chips/ alone contributed 147 real specs. Widening it immediately surfaced problems that were invisible before: typecheck failures 60 -> 149 specs losing content 49 -> 78 (9.9% -> 11.7%) backend rejections 0 -> 6 verilog_hir, "duplicate signal name: " Those six are gf16_to_fp16 / gf32_to_fp32 across chips/{euler,gamma,phi} -- every one outside specs/, so none of them appeared in the earlier scan. Note the empty name in that error. The compiler's own specs lose content too: compiler/parser/parser.t27 drops 2 declarations, and 10 more files under compiler/ drop one each. SYNTAX HIGHLIGHTING. The .t27 source is coloured from the compiler's OWN token stream -- the lexer already said what every span is, so there is no reason to re-guess it with regexes and no way for the colours to disagree with the compiler. Generated output (Zig/Verilog/C/Rust) has no token stream coming back, so that falls back to a small regex pass; it is presentation only. New shared SpecCodeView adds line numbers and a copy button to every code-bearing layer. Also: byte/count badges on each tab so backends are comparable without opening them, and the failing one reads red with an ✕. NAV. Listed in Navigation.tsx PAGES (EN + RU), so it is reachable rather than a URL you have to know. Fixes found by testing rather than by reading: - header overlapped its own text below ~1100px; inline styles cannot carry media queries, so width is tracked and the optional labels drop out - output dir renamed specs/ -> files/, since paths became repo-root-relative and specs/demos/ was landing at specs/specs/demos/ - .gitignore: enumerating per-directory negations was whack-a-mole (the widened corpus hit three more bare rules like `runtime/` and `legacy/`). Replaced with one `!apps/website/public/t27/**`, which also re-includes the excluded directories -- git cannot re-include a file whose parent is excluded. 667 staged == 667 in manifest, verified. All ten layer tabs exercised in a real browser against the production build. Gates: ratchet 179/26 unchanged, aria clean, build clean. --- .gitignore | 13 +- .../specs/physics/formula_registry.t27 | 209 + .../euler/specs/fpga/avs_controller_48.t27 | 607 +++ .../euler/specs/fpga/avs_controller_96.t27 | 735 ++++ .../chips/euler/specs/fpga/avs_reconf.t27 | 702 ++++ .../files/chips/euler/specs/fpga/dfs_gate.t27 | 600 +++ .../chips/euler/specs/fpga/drowsy_ret.t27 | 716 ++++ .../euler/specs/fpga/fbb_active_path.t27 | 756 ++++ .../files/chips/euler/specs/fpga/fp8_e4m3.t27 | 1046 +++++ .../files/chips/euler/specs/fpga/fp8_e5m2.t27 | 1212 ++++++ .../files/chips/euler/specs/fpga/gf128.t27 | 358 ++ .../chips/euler/specs/fpga/gf16_to_fp16.t27 | 574 +++ .../euler/specs/fpga/gf16_to_posit16.t27 | 664 ++++ .../files/chips/euler/specs/fpga/gf256.t27 | 336 ++ .../chips/euler/specs/fpga/gf32_to_fp32.t27 | 757 ++++ .../t27/files/chips/euler/specs/fpga/gf64.t27 | 320 ++ .../chips/euler/specs/fpga/holo_mux_x4.t27 | 581 +++ .../t27/files/chips/euler/specs/fpga/int4.t27 | 334 ++ .../t27/files/chips/euler/specs/fpga/int8.t27 | 493 +++ .../euler/specs/fpga/lane_l_precheck.t27 | 493 +++ .../euler/specs/fpga/lut_npu_81_entry.t27 | 419 ++ .../t27/files/chips/euler/specs/fpga/nf4.t27 | 313 ++ .../files/chips/euler/specs/fpga/null_pe.t27 | 568 +++ .../files/chips/euler/specs/fpga/posit16.t27 | 979 +++++ .../specs/fpga/purkinje_thermal_gate.t27 | 704 ++++ .../chips/euler/specs/fpga/sparse_mask.t27 | 556 +++ .../chips/euler/specs/fpga/sparse_skip.t27 | 476 +++ .../chips/euler/specs/fpga/spec_exit.t27 | 541 +++ .../chips/euler/specs/fpga/stoch_round.t27 | 575 +++ .../chips/euler/specs/fpga/subth_clk.t27 | 478 +++ .../chips/euler/specs/numeric/binary16.t27 | 420 ++ .../chips/euler/specs/numeric/formats.t27 | 537 +++ .../chips/euler/specs/numeric/fp8_e4m3.t27 | 1046 +++++ .../chips/euler/specs/numeric/fp8_e5m2.t27 | 1212 ++++++ .../files/chips/euler/specs/numeric/gf12.t27 | 481 +++ .../files/chips/euler/specs/numeric/gf128.t27 | 506 +++ .../files/chips/euler/specs/numeric/gf16.t27 | 3436 +++++++++++++++++ .../files/chips/euler/specs/numeric/gf20.t27 | 468 +++ .../files/chips/euler/specs/numeric/gf24.t27 | 468 +++ .../files/chips/euler/specs/numeric/gf256.t27 | 595 +++ .../files/chips/euler/specs/numeric/gf32.t27 | 479 +++ .../files/chips/euler/specs/numeric/gf4.t27 | 305 ++ .../files/chips/euler/specs/numeric/gf64.t27 | 764 ++++ .../files/chips/euler/specs/numeric/gf8.t27 | 521 +++ .../specs/numeric/goldenfloat_family.t27 | 449 +++ .../files/chips/euler/specs/numeric/int4.t27 | 766 ++++ .../files/chips/euler/specs/numeric/int8.t27 | 1151 ++++++ .../files/chips/euler/specs/numeric/nf4.t27 | 839 ++++ .../chips/euler/specs/numeric/phi_ratio.t27 | 694 ++++ .../chips/euler/specs/numeric/posit16.t27 | 979 +++++ .../euler/specs/numeric/tri_net_formats.t27 | 993 +++++ .../gamma/specs/fpga/avs_controller_48.t27 | 607 +++ .../gamma/specs/fpga/avs_controller_96.t27 | 735 ++++ .../chips/gamma/specs/fpga/avs_reconf.t27 | 702 ++++ .../files/chips/gamma/specs/fpga/dfs_gate.t27 | 600 +++ .../chips/gamma/specs/fpga/drowsy_ret.t27 | 716 ++++ .../gamma/specs/fpga/fbb_active_path.t27 | 756 ++++ .../files/chips/gamma/specs/fpga/fp8_e4m3.t27 | 1046 +++++ .../files/chips/gamma/specs/fpga/fp8_e5m2.t27 | 1212 ++++++ .../files/chips/gamma/specs/fpga/gf128.t27 | 358 ++ .../chips/gamma/specs/fpga/gf16_to_fp16.t27 | 574 +++ .../gamma/specs/fpga/gf16_to_posit16.t27 | 664 ++++ .../files/chips/gamma/specs/fpga/gf256.t27 | 336 ++ .../chips/gamma/specs/fpga/gf32_to_fp32.t27 | 757 ++++ .../t27/files/chips/gamma/specs/fpga/gf64.t27 | 320 ++ .../chips/gamma/specs/fpga/holo_mux_x4.t27 | 581 +++ .../t27/files/chips/gamma/specs/fpga/int4.t27 | 334 ++ .../t27/files/chips/gamma/specs/fpga/int8.t27 | 493 +++ .../gamma/specs/fpga/lane_l_precheck.t27 | 493 +++ .../gamma/specs/fpga/lut_npu_81_entry.t27 | 419 ++ .../t27/files/chips/gamma/specs/fpga/nf4.t27 | 313 ++ .../files/chips/gamma/specs/fpga/null_pe.t27 | 568 +++ .../files/chips/gamma/specs/fpga/posit16.t27 | 979 +++++ .../specs/fpga/purkinje_thermal_gate.t27 | 704 ++++ .../chips/gamma/specs/fpga/sparse_mask.t27 | 556 +++ .../chips/gamma/specs/fpga/sparse_skip.t27 | 476 +++ .../chips/gamma/specs/fpga/spec_exit.t27 | 541 +++ .../chips/gamma/specs/fpga/stoch_round.t27 | 575 +++ .../chips/gamma/specs/fpga/subth_clk.t27 | 478 +++ .../chips/gamma/specs/numeric/binary16.t27 | 420 ++ .../chips/gamma/specs/numeric/formats.t27 | 537 +++ .../chips/gamma/specs/numeric/fp8_e4m3.t27 | 1046 +++++ .../chips/gamma/specs/numeric/fp8_e5m2.t27 | 1212 ++++++ .../files/chips/gamma/specs/numeric/gf12.t27 | 481 +++ .../files/chips/gamma/specs/numeric/gf128.t27 | 506 +++ .../files/chips/gamma/specs/numeric/gf16.t27 | 3436 +++++++++++++++++ .../files/chips/gamma/specs/numeric/gf20.t27 | 468 +++ .../files/chips/gamma/specs/numeric/gf24.t27 | 468 +++ .../files/chips/gamma/specs/numeric/gf256.t27 | 595 +++ .../files/chips/gamma/specs/numeric/gf32.t27 | 479 +++ .../files/chips/gamma/specs/numeric/gf4.t27 | 305 ++ .../files/chips/gamma/specs/numeric/gf64.t27 | 764 ++++ .../files/chips/gamma/specs/numeric/gf8.t27 | 521 +++ .../specs/numeric/goldenfloat_family.t27 | 449 +++ .../files/chips/gamma/specs/numeric/int4.t27 | 766 ++++ .../files/chips/gamma/specs/numeric/int8.t27 | 1151 ++++++ .../files/chips/gamma/specs/numeric/nf4.t27 | 839 ++++ .../chips/gamma/specs/numeric/phi_ratio.t27 | 694 ++++ .../chips/gamma/specs/numeric/posit16.t27 | 979 +++++ .../gamma/specs/numeric/tri_net_formats.t27 | 993 +++++ .../phi/specs/fpga/avs_controller_48.t27 | 607 +++ .../phi/specs/fpga/avs_controller_96.t27 | 735 ++++ .../files/chips/phi/specs/fpga/avs_reconf.t27 | 702 ++++ .../files/chips/phi/specs/fpga/dfs_gate.t27 | 600 +++ .../files/chips/phi/specs/fpga/drowsy_ret.t27 | 716 ++++ .../chips/phi/specs/fpga/fbb_active_path.t27 | 756 ++++ .../files/chips/phi/specs/fpga/fp8_e4m3.t27 | 1046 +++++ .../files/chips/phi/specs/fpga/fp8_e5m2.t27 | 1212 ++++++ .../t27/files/chips/phi/specs/fpga/gf128.t27 | 358 ++ .../chips/phi/specs/fpga/gf16_to_fp16.t27 | 574 +++ .../chips/phi/specs/fpga/gf16_to_posit16.t27 | 664 ++++ .../t27/files/chips/phi/specs/fpga/gf256.t27 | 336 ++ .../chips/phi/specs/fpga/gf32_to_fp32.t27 | 757 ++++ .../t27/files/chips/phi/specs/fpga/gf64.t27 | 320 ++ .../chips/phi/specs/fpga/holo_mux_x4.t27 | 581 +++ .../t27/files/chips/phi/specs/fpga/int4.t27 | 334 ++ .../t27/files/chips/phi/specs/fpga/int8.t27 | 493 +++ .../chips/phi/specs/fpga/lane_l_precheck.t27 | 493 +++ .../chips/phi/specs/fpga/lut_npu_81_entry.t27 | 419 ++ .../t27/files/chips/phi/specs/fpga/nf4.t27 | 313 ++ .../files/chips/phi/specs/fpga/null_pe.t27 | 568 +++ .../files/chips/phi/specs/fpga/posit16.t27 | 979 +++++ .../phi/specs/fpga/purkinje_thermal_gate.t27 | 704 ++++ .../chips/phi/specs/fpga/sparse_mask.t27 | 556 +++ .../chips/phi/specs/fpga/sparse_skip.t27 | 476 +++ .../files/chips/phi/specs/fpga/spec_exit.t27 | 541 +++ .../chips/phi/specs/fpga/stoch_round.t27 | 575 +++ .../files/chips/phi/specs/fpga/subth_clk.t27 | 478 +++ .../chips/phi/specs/numeric/binary16.t27 | 420 ++ .../files/chips/phi/specs/numeric/formats.t27 | 537 +++ .../chips/phi/specs/numeric/fp8_e4m3.t27 | 1046 +++++ .../chips/phi/specs/numeric/fp8_e5m2.t27 | 1212 ++++++ .../files/chips/phi/specs/numeric/gf12.t27 | 481 +++ .../files/chips/phi/specs/numeric/gf128.t27 | 506 +++ .../files/chips/phi/specs/numeric/gf16.t27 | 3436 +++++++++++++++++ .../files/chips/phi/specs/numeric/gf20.t27 | 468 +++ .../files/chips/phi/specs/numeric/gf24.t27 | 468 +++ .../files/chips/phi/specs/numeric/gf256.t27 | 595 +++ .../files/chips/phi/specs/numeric/gf32.t27 | 479 +++ .../t27/files/chips/phi/specs/numeric/gf4.t27 | 305 ++ .../files/chips/phi/specs/numeric/gf64.t27 | 764 ++++ .../t27/files/chips/phi/specs/numeric/gf8.t27 | 521 +++ .../phi/specs/numeric/goldenfloat_family.t27 | 449 +++ .../files/chips/phi/specs/numeric/int4.t27 | 766 ++++ .../files/chips/phi/specs/numeric/int8.t27 | 1151 ++++++ .../t27/files/chips/phi/specs/numeric/nf4.t27 | 839 ++++ .../chips/phi/specs/numeric/phi_ratio.t27 | 694 ++++ .../files/chips/phi/specs/numeric/posit16.t27 | 979 +++++ .../phi/specs/numeric/tri_net_formats.t27 | 993 +++++ .../website/public/t27/files/compiler/ast.t27 | 611 +++ .../public/t27/files/compiler/cli/gen.t27 | 511 +++ .../public/t27/files/compiler/cli/git.t27 | 616 +++ .../public/t27/files/compiler/cli/spec.t27 | 487 +++ .../t27/files/compiler/codegen/c/codegen.t27 | 273 ++ .../t27/files/compiler/codegen/testgen.t27 | 909 +++++ .../compiler/codegen/verilog/codegen.t27 | 1067 +++++ .../codegen/verilog/fpga_emission.t27 | 2358 +++++++++++ .../files/compiler/codegen/zig/codegen.t27 | 1516 ++++++++ .../files/compiler/codegen/zig/runtime.t27 | 409 ++ .../t27/files/compiler/parser/lexer.t27 | 597 +++ .../t27/files/compiler/parser/parser.t27 | 1294 +++++++ .../t27/files/compiler/runtime/commands.t27 | 1039 +++++ .../t27/files/compiler/runtime/runtime.t27 | 339 ++ .../t27/files/compiler/runtime/validation.t27 | 514 +++ .../t27/files/compiler/skill/registry.t27 | 312 ++ .../zig/legacy/main_zig_handwritten.t27 | 1125 ++++++ .../examples/fpga/qmtech_minimal/design.t27 | 150 + .../t27/{ => files}/specs/account/auth.t27 | 0 .../t27/{ => files}/specs/account/repo.t27 | 0 .../t27/{ => files}/specs/account/schema.t27 | 0 .../{ => files}/specs/api/c_api_contract.t27 | 0 .../{ => files}/specs/api/sdk_contract.t27 | 0 .../t27/{ => files}/specs/api/tri_net_api.t27 | 0 .../t27/{ => files}/specs/ar/asp_solver.t27 | 0 .../t27/{ => files}/specs/ar/coa_planning.t27 | 0 .../t27/{ => files}/specs/ar/composition.t27 | 0 .../{ => files}/specs/ar/datalog_engine.t27 | 0 .../{ => files}/specs/ar/explainability.t27 | 0 .../t27/{ => files}/specs/ar/proof_trace.t27 | 0 .../t27/{ => files}/specs/ar/restraint.t27 | 0 .../{ => files}/specs/ar/ternary_logic.t27 | 0 .../t27/{ => files}/specs/auth/config.t27 | 0 .../specs/automation/wrapup-auto.t27 | 0 .../t27/{ => files}/specs/base/debounce.t27 | 0 .../public/t27/{ => files}/specs/base/ops.t27 | 0 .../t27/{ => files}/specs/base/ring_32.t27 | 0 .../t27/{ => files}/specs/base/seed.t27 | 0 .../{ => files}/specs/base/ternary_add.t27 | 0 .../specs/base/ternary_encoding.t27 | 0 .../{ => files}/specs/base/ternary_memory.t27 | 0 .../t27/{ => files}/specs/base/types.t27 | 0 .../specs/benchmarks/bench_main.t27 | 0 .../{ => files}/specs/benchmarks/bench_nn.t27 | 0 .../specs/benchmarks/gf16_bfloat16_nmse.t27 | 0 .../specs/benchmarks/ternary_vs_binary.t27 | 0 .../t27/{ => files}/specs/boards/arty_a7.t27 | 0 .../specs/boards/xc7a100t_full.t27 | 0 .../specs/boards/xc7a100t_minimal.t27 | 0 .../t27/{ => files}/specs/brain/brain.t27 | 0 .../t27/{ => files}/specs/brain/bus.t27 | 0 .../specs/brain/cognitive_loop.t27 | 0 .../{ => files}/specs/brain/neural_gamma.t27 | 0 .../{ => files}/specs/brain/phi_timing.t27 | 0 .../{ => files}/specs/brain/unified_state.t27 | 0 .../t27/{ => files}/specs/bus/pubsub.t27 | 0 .../t27/{ => files}/specs/bus/schema.t27 | 0 .../specs/cloud/railway_deploy.t27 | 0 .../specs/compiler/diagnostics.t27 | 0 .../t27/{ => files}/specs/compiler/lexer.t27 | 0 .../t27/{ => files}/specs/compiler/linker.t27 | 0 .../specs/compiler/meta_compile.t27 | 0 .../specs/compiler/mod_structure.t27 | 0 .../{ => files}/specs/compiler/optimizer.t27 | 0 .../t27/{ => files}/specs/compiler/parser.t27 | 0 .../{ => files}/specs/compiler/pipeline.t27 | 0 .../t27/{ => files}/specs/compiler/stdlib.t27 | 0 .../specs/compiler/typechecker.t27 | 0 .../t27/{ => files}/specs/config/load.t27 | 0 .../t27/{ => files}/specs/config/migrate.t27 | 0 .../t27/{ => files}/specs/config/paths.t27 | 0 .../t27/{ => files}/specs/config/schema.t27 | 0 .../specs/conformance/e2e_scenarios.t27 | 0 .../demos/jones_topology_decision_gate.t27 | 0 .../specs/demos/jones_topology_filter.t27 | 0 .../{ => files}/specs/demos/simple_test.t27 | 0 .../t27/{ => files}/specs/depin/prove.t27 | 0 .../specs/enrichment/audio_overview.t27 | 0 .../specs/enrichment/youtube_transcript.t27 | 0 .../t27/{ => files}/specs/file/operations.t27 | 0 .../t27/{ => files}/specs/file/schema.t27 | 0 .../t27/{ => files}/specs/file/watcher.t27 | 0 .../t27/{ => files}/specs/fpga/apb_bridge.t27 | 0 .../t27/{ => files}/specs/fpga/assembler.t27 | 0 .../t27/{ => files}/specs/fpga/axi4.t27 | 0 .../specs/fpga/boards/arty_a7_integration.t27 | 0 .../fpga/boards/qmtech_a100t_integration.t27 | 0 .../t27/{ => files}/specs/fpga/bootrom.t27 | 0 .../t27/{ => files}/specs/fpga/bridge.t27 | 0 .../{ => files}/specs/fpga/clock_domain.t27 | 0 .../t27/{ => files}/specs/fpga/crossopt.t27 | 0 .../public/t27/{ => files}/specs/fpga/cts.t27 | 0 .../public/t27/{ => files}/specs/fpga/dft.t27 | 0 .../t27/{ => files}/specs/fpga/e2e_demo.t27 | 0 .../t27/{ => files}/specs/fpga/fifo.t27 | 0 .../t27/{ => files}/specs/fpga/formal.t27 | 0 .../t27/{ => files}/specs/fpga/gf16_accel.t27 | 0 .../public/t27/{ => files}/specs/fpga/hir.t27 | 0 .../t27/{ => files}/specs/fpga/hw_types.t27 | 0 .../t27/{ => files}/specs/fpga/linker.t27 | 0 .../public/t27/{ => files}/specs/fpga/mac.t27 | 0 .../t27/{ => files}/specs/fpga/memory.t27 | 0 .../t27/{ => files}/specs/fpga/partition.t27 | 0 .../t27/{ => files}/specs/fpga/placement.t27 | 0 .../t27/{ => files}/specs/fpga/power.t27 | 0 .../{ => files}/specs/fpga/power_analysis.t27 | 0 .../t27/{ => files}/specs/fpga/router.t27 | 0 .../t27/{ => files}/specs/fpga/simulator.t27 | 0 .../public/t27/{ => files}/specs/fpga/spi.t27 | 0 .../t27/{ => files}/specs/fpga/stdlib.t27 | 0 .../{ => files}/specs/fpga/ternary_isa.t27 | 0 .../t27/{ => files}/specs/fpga/testbench.t27 | 0 .../specs/fpga/testbench/apb_bridge_tb.t27 | 0 .../specs/fpga/testbench/assembler_tb.t27 | 0 .../specs/fpga/testbench/axi4_tb.t27 | 0 .../specs/fpga/testbench/bootrom_tb.t27 | 0 .../specs/fpga/testbench/bridge_tb.t27 | 0 .../specs/fpga/testbench/clock_domain_tb.t27 | 0 .../specs/fpga/testbench/cts_tb.t27 | 0 .../specs/fpga/testbench/dft_tb.t27 | 0 .../specs/fpga/testbench/fifo_tb.t27 | 0 .../specs/fpga/testbench/formal_tb.t27 | 0 .../specs/fpga/testbench/gf16_accel_tb.t27 | 0 .../specs/fpga/testbench/hir_tb.t27 | 0 .../specs/fpga/testbench/integration_tb.t27 | 0 .../specs/fpga/testbench/linker_tb.t27 | 0 .../specs/fpga/testbench/mac_tb.t27 | 0 .../specs/fpga/testbench/memory_tb.t27 | 0 .../specs/fpga/testbench/partition_tb.t27 | 0 .../specs/fpga/testbench/placement_tb.t27 | 0 .../fpga/testbench/power_analysis_tb.t27 | 0 .../specs/fpga/testbench/power_tb.t27 | 0 .../specs/fpga/testbench/router_tb.t27 | 0 .../specs/fpga/testbench/simulator_tb.t27 | 0 .../specs/fpga/testbench/spi_tb.t27 | 0 .../specs/fpga/testbench/stdlib_tb.t27 | 0 .../specs/fpga/testbench/ternary_isa_tb.t27 | 0 .../specs/fpga/testbench/timing_tb.t27 | 0 .../specs/fpga/testbench/top_tb.t27 | 0 .../specs/fpga/testbench/uart_tb.t27 | 0 .../testbench/vcd_conformance_compare_tb.t27 | 0 .../specs/fpga/testbench/vcd_trace_tb.t27 | 0 .../t27/{ => files}/specs/fpga/timing.t27 | 0 .../t27/{ => files}/specs/fpga/top_level.t27 | 0 .../t27/{ => files}/specs/fpga/uart.t27 | 0 .../specs/fpga/vcd_conformance_compare.t27 | 0 .../t27/{ => files}/specs/fpga/vcd_trace.t27 | 0 .../specs/fpga/verification/build_verify.t27 | 0 .../public/t27/{ => files}/specs/git/diff.t27 | 0 .../t27/{ => files}/specs/git/operations.t27 | 0 .../t27/{ => files}/specs/git/schema.t27 | 0 .../t27/{ => files}/specs/git/status.t27 | 0 .../t27/{ => files}/specs/github/auth.t27 | 0 .../t27/{ => files}/specs/github/comments.t27 | 0 .../t27/{ => files}/specs/github/issues.t27 | 0 .../t27/{ => files}/specs/github/prs.t27 | 0 .../specs/github/tests/e2e_full_flow.t27 | 0 .../specs/graph/knowledge_graph.t27 | 0 .../{ => files}/specs/hslm/forward_pass.t27 | 0 .../specs/interop/gf_cross_language.t27 | 0 .../t27/{ => files}/specs/isa/registers.t27 | 0 .../specs/isa/ternary_arithmetic.t27 | 0 .../{ => files}/specs/isa/ternary_bitwise.t27 | 0 .../specs/isa/ternary_control_flow.t27 | 0 .../{ => files}/specs/isa/ternary_deque.t27 | 0 .../specs/isa/ternary_encoding.t27 | 0 .../{ => files}/specs/isa/ternary_gates.t27 | 0 .../{ => files}/specs/isa/ternary_graph.t27 | 0 .../{ => files}/specs/isa/ternary_hash.t27 | 0 .../{ => files}/specs/isa/ternary_memory.t27 | 0 .../specs/isa/ternary_pattern_matching.t27 | 0 .../{ => files}/specs/isa/ternary_search.t27 | 0 .../t27/{ => files}/specs/isa/ternary_set.t27 | 0 .../{ => files}/specs/isa/ternary_shift.t27 | 0 .../{ => files}/specs/isa/ternary_sorting.t27 | 0 .../{ => files}/specs/isa/ternary_tree.t27 | 0 .../public/t27/{ => files}/specs/jit/jit.t27 | 0 .../t27/{ => files}/specs/lsp/client.t27 | 0 .../t27/{ => files}/specs/lsp/language.t27 | 0 .../t27/{ => files}/specs/lsp/protocol.t27 | 0 .../t27/{ => files}/specs/lsp/schema.t27 | 0 .../t27/{ => files}/specs/lsp/server.t27 | 0 .../t27/{ => files}/specs/math/constants.t27 | 0 .../{ => files}/specs/math/e8_lie_algebra.t27 | 0 .../{ => files}/specs/math/gf_competitive.t27 | 0 .../specs/math/pellis_precision_verify.t27 | 0 .../specs/math/phi_split_optimality.t27 | 0 .../specs/math/phi_universal_attractor.t27 | 0 .../specs/math/property_test_template.t27 | 0 .../{ => files}/specs/math/radix_economy.t27 | 0 .../{ => files}/specs/math/sacred_physics.t27 | 0 .../specs/math/zamolodchikov_e8.t27 | 0 .../specs/memory/formula_embed.t27 | 0 .../specs/memory/memory_primitives.t27 | 0 .../{ => files}/specs/memory/notebooklm.t27 | 0 .../specs/memory/semantic_search.t27 | 0 .../specs/ml/activation/elu_activation.t27 | 0 .../specs/ml/activation/gelu_activation.t27 | 0 .../ml/activation/gelu_approx_activation.t27 | 0 .../ml/activation/leaky_relu_activation.t27 | 0 .../specs/ml/activation/relu_activation.t27 | 0 .../ml/activation/sigmoid_activation.t27 | 0 .../ml/activation/silu_swish_activation.t27 | 0 .../activation/silu_swish_vbt_activation.t27 | 0 .../specs/ml/activation/softmax.t27 | 0 .../specs/ml/activation/tanh_activation.t27 | 0 .../specs/ml/layers/avgpool2d_layer.t27 | 0 .../specs/ml/layers/batchnorm_layer.t27 | 0 .../specs/ml/layers/conv2d_layer.t27 | 0 .../specs/ml/layers/dense_layer.t27 | 0 .../specs/ml/layers/dropout_layer.t27 | 0 .../specs/ml/layers/embedding_layer.t27 | 0 .../specs/ml/layers/flatten_layer.t27 | 0 .../specs/ml/layers/layernorm_layer.t27 | 0 .../specs/ml/layers/maxpool2d_layer.t27 | 0 .../specs/ml/layers/residual_connection.t27 | 0 .../ml/loss/binary_crossentropy_loss.t27 | 0 .../specs/ml/loss/contrastive_loss.t27 | 0 .../specs/ml/loss/cross_entropy_loss.t27 | 0 .../{ => files}/specs/ml/loss/huber_loss.t27 | 0 .../specs/ml/loss/kl_divergence.t27 | 0 .../{ => files}/specs/ml/loss/mse_loss.t27 | 0 .../specs/ml/optimizer/adagrad.t27 | 0 .../{ => files}/specs/ml/optimizer/adam.t27 | 0 .../{ => files}/specs/ml/optimizer/adamw.t27 | 0 .../{ => files}/specs/ml/optimizer/lamb.t27 | 0 .../specs/ml/optimizer/lr_scheduler.t27 | 0 .../specs/ml/optimizer/rmsprop.t27 | 0 .../{ => files}/specs/ml/optimizer/sgd.t27 | 0 .../specs/ml/optimizer/sgd_momentum.t27 | 0 .../t27/{ => files}/specs/ml/pathway/mlp.t27 | 0 .../ml/recurrent/attention_mechanism.t27 | 0 .../{ => files}/specs/ml/recurrent/bilstm.t27 | 0 .../specs/ml/recurrent/gru_cell.t27 | 0 .../specs/ml/recurrent/lstm_cell.t27 | 0 .../specs/ml/recurrent/lstm_single.t27 | 0 .../specs/ml/recurrent/rnn_cell.t27 | 0 .../specs/ml/recurrent/self_attention.t27 | 0 .../specs/ml/recurrent/seq2seq.t27 | 0 .../specs/ml/rl/advantage_estimator.t27 | 0 .../t27/{ => files}/specs/ml/rl/dqn.t27 | 0 .../specs/ml/rl/dqn_target_network.t27 | 0 .../t27/{ => files}/specs/ml/rl/ppo_actor.t27 | 0 .../{ => files}/specs/ml/rl/ppo_clip_loss.t27 | 0 .../{ => files}/specs/ml/rl/ppo_critic.t27 | 0 .../t27/{ => files}/specs/ml/rl/sac_actor.t27 | 0 .../{ => files}/specs/ml/rl/sac_critic.t27 | 0 .../specs/ml/transformer/encoder_block.t27 | 0 .../specs/ml/transformer/feed_forward.t27 | 0 .../ml/transformer/feed_forward_network.t27 | 0 .../specs/ml/transformer/mha_block.t27 | 0 .../ml/transformer/multi_head_attention.t27 | 0 .../specs/ml/transformer/multi_head_attn.t27 | 0 .../{ => files}/specs/ml/transformer/norm.t27 | 0 .../specs/ml/transformer/positional_enc.t27 | 0 .../ml/transformer/positional_encoding.t27 | 0 .../{ => files}/specs/neural/forward_pass.t27 | 0 .../t27/{ => files}/specs/nn/attention.t27 | 0 .../public/t27/{ => files}/specs/nn/hslm.t27 | 0 .../t27/{ => files}/specs/nn/phi_rope.t27 | 0 .../{ => files}/specs/nn/sacred_attention.t27 | 0 .../t27/{ => files}/specs/numeric/bigint.t27 | 0 .../t27/{ => files}/specs/numeric/formats.t27 | 0 .../t27/{ => files}/specs/numeric/gf12.t27 | 0 .../t27/{ => files}/specs/numeric/gf16.t27 | 0 .../t27/{ => files}/specs/numeric/gf20.t27 | 0 .../t27/{ => files}/specs/numeric/gf24.t27 | 0 .../t27/{ => files}/specs/numeric/gf32.t27 | 0 .../t27/{ => files}/specs/numeric/gf4.t27 | 0 .../t27/{ => files}/specs/numeric/gf8.t27 | 0 .../specs/numeric/gf_competitive.t27 | 0 .../specs/numeric/goldenfloat_family.t27 | 0 .../specs/numeric/pellis_verify.t27 | 0 .../{ => files}/specs/numeric/phi_ratio.t27 | 0 .../specs/numeric/requant_boundary.t27 | 0 .../t27/{ => files}/specs/numeric/tf3.t27 | 0 .../specs/numeric/trinity_numeric_surface.t27 | 0 .../specs/physics/chimera_best_gamma.t27 | 0 .../specs/physics/e8_lqg_bridge.t27 | 0 .../specs/physics/formula_discovery.t27 | 0 .../specs/physics/formula_registry.t27 | 0 .../specs/physics/gamma-conflict.t27 | 0 .../specs/physics/gamma_conjecture.t27 | 0 .../specs/physics/gi1_analysis.t27 | 0 .../specs/physics/hslm_benchmark.t27 | 0 .../specs/physics/lqg_cs_bridge.t27 | 0 .../{ => files}/specs/physics/lqg_entropy.t27 | 0 .../specs/physics/p2_brain_physics.t27 | 0 .../specs/physics/pellis-formulas.t27 | 0 .../t27/{ => files}/specs/physics/quantum.t27 | 0 .../specs/physics/sacred_verification.t27 | 0 .../specs/physics/su2_chern_simons.t27 | 0 .../physics/zamolodchikov_4d_conjecture.t27 | 0 .../{ => files}/specs/pins/emitter_xdc.t27 | 0 .../public/t27/{ => files}/specs/pins/ir.t27 | 0 .../t27/{ => files}/specs/pins/parser.t27 | 0 .../{ => files}/specs/pipeline/benchmarks.t27 | 0 .../{ => files}/specs/pipeline/e2e_test.t27 | 0 .../specs/pipeline/experience_save.t27 | 0 .../specs/portable/relay_observer.t27 | 0 .../{ => files}/specs/provider/adapters.t27 | 0 .../t27/{ => files}/specs/provider/schema.t27 | 0 .../t27/{ => files}/specs/provider/stream.t27 | 0 .../{ => files}/specs/provider/transform.t27 | 0 .../specs/queen/brain_summaries.t27 | 0 .../t27/{ => files}/specs/queen/lotus.t27 | 0 .../{ => files}/specs/queen/task_analysis.t27 | 0 .../t27/{ => files}/specs/runtime/execute.t27 | 0 .../{ => files}/specs/runtime/instance.t27 | 0 .../t27/{ => files}/specs/runtime/process.t27 | 0 .../{ => files}/specs/sacred/cosmology.t27 | 0 .../{ => files}/specs/sacred/dark_matter.t27 | 0 .../t27/{ => files}/specs/sacred/gravity.t27 | 0 .../{ => files}/specs/sacred/monopoles.t27 | 0 .../t27/{ => files}/specs/sacred/quantum.t27 | 0 .../specs/sacred/quantum_gravity.t27 | 0 .../specs/sacred/sacred_constants.t27 | 0 .../specs/sacred/sacred_governance.t27 | 0 .../specs/sacred/sacred_identity.t27 | 0 .../specs/sacred/superconductivity.t27 | 0 .../t27/{ => files}/specs/sandbox/health.t27 | 0 .../specs/sandbox/https_enforce.t27 | 0 .../t27/{ => files}/specs/sandbox/modules.t27 | 0 .../specs/sandbox/orphan_detection.t27 | 0 .../specs/sandbox/session_timeout.t27 | 0 .../{ => files}/specs/server/agent-runner.t27 | 0 .../t27/{ => files}/specs/server/api.t27 | 0 .../t27/{ => files}/specs/server/http.t27 | 0 .../t27/{ => files}/specs/server/mdns.t27 | 0 .../t27/{ => files}/specs/server/project.t27 | 0 .../t27/{ => files}/specs/server/provider.t27 | 0 .../t27/{ => files}/specs/server/router.t27 | 0 .../t27/{ => files}/specs/server/routes.t27 | 0 .../t27/{ => files}/specs/server/session.t27 | 0 .../t27/{ => files}/specs/server/sse.t27 | 0 .../t27/{ => files}/specs/server/vm.t27 | 0 .../{ => files}/specs/shell/environment.t27 | 0 .../t27/{ => files}/specs/shell/process.t27 | 0 .../t27/{ => files}/specs/shell/schema.t27 | 0 .../t27/{ => files}/specs/storage/kv.t27 | 0 .../t27/{ => files}/specs/storage/lock.t27 | 0 .../t27/{ => files}/specs/storage/migrate.t27 | 0 .../t27/{ => files}/specs/storage/schema.t27 | 0 .../t27/{ => files}/specs/sync/index.t27 | 0 .../t27/{ => files}/specs/sync/schema.t27 | 0 .../t27/{ => files}/specs/ternary/bigint.t27 | 0 .../specs/ternary/hybrid_arithmetic.t27 | 0 .../specs/ternary/hybrid_bigint.t27 | 0 .../{ => files}/specs/ternary/packed_trit.t27 | 0 .../{ => files}/specs/test_framework/core.t27 | 0 .../test_framework/graph_drift_detection.t27 | 0 .../test_framework/property_test_template.t27 | 0 .../specs/test_framework/runner.t27 | 0 .../test_framework/verilog_bench_harness.t27 | 0 .../t27/{ => files}/specs/tools/registry.t27 | 0 .../t27/{ => files}/specs/tools/schema.t27 | 0 .../specs/tools/tri_to_t27_converter.t27 | 0 .../{ => files}/specs/tri/agent/agent_run.t27 | 0 .../{ => files}/specs/tri/agent/agents.t27 | 0 .../specs/tri/agent/autonomous_lifecycle.t27 | 0 .../specs/tri/agent/autonomous_universe.t27 | 0 .../specs/tri/agent/eternal_monitor.t27 | 0 .../specs/tri/agent/experience_hooks.t27 | 0 .../specs/tri/agent/faculty_board.t27 | 0 .../specs/tri/agent/governance_agent.t27 | 0 .../{ => files}/specs/tri/agent/handoff.t27 | 0 .../{ => files}/specs/tri/agent/memory.t27 | 0 .../specs/tri/agent/swarm_agents.t27 | 0 .../specs/tri/collections/array.t27 | 0 .../specs/tri/collections/bitmap.t27 | 0 .../specs/tri/collections/bitset.t27 | 0 .../specs/tri/collections/bitvector.t27 | 0 .../specs/tri/collections/btree.t27 | 0 .../specs/tri/collections/circular_buffer.t27 | 0 .../specs/tri/collections/context.t27 | 0 .../specs/tri/collections/deque.t27 | 0 .../specs/tri/collections/either.t27 | 0 .../specs/tri/collections/interval.t27 | 0 .../specs/tri/collections/linked_list.t27 | 0 .../specs/tri/collections/list.t27 | 0 .../specs/tri/collections/lockfree_stack.t27 | 0 .../{ => files}/specs/tri/collections/lru.t27 | 0 .../specs/tri/collections/lru_cache.t27 | 0 .../{ => files}/specs/tri/collections/map.t27 | 0 .../specs/tri/collections/maybe.t27 | 0 .../specs/tri/collections/namespace.t27 | 0 .../specs/tri/collections/option.t27 | 0 .../specs/tri/collections/priority_queue.t27 | 0 .../specs/tri/collections/queue.t27 | 0 .../specs/tri/collections/result.t27 | 0 .../specs/tri/collections/ring_buffer.t27 | 0 .../{ => files}/specs/tri/collections/set.t27 | 0 .../specs/tri/collections/skip_list.t27 | 0 .../specs/tri/collections/stack.t27 | 0 .../specs/tri/collections/state.t27 | 0 .../specs/tri/collections/tuple.t27 | 0 .../specs/tri/collections/variant.t27 | 0 .../{ => files}/specs/tri/crypto/base32.t27 | 0 .../{ => files}/specs/tri/crypto/base64.t27 | 0 .../{ => files}/specs/tri/crypto/crypto.t27 | 0 .../t27/{ => files}/specs/tri/crypto/ecc.t27 | 0 .../t27/{ => files}/specs/tri/crypto/hex.t27 | 0 .../t27/{ => files}/specs/tri/crypto/hmac.t27 | 0 .../specs/tri/crypto/reed_solomon.t27 | 0 .../t27/{ => files}/specs/tri/crypto/rsa.t27 | 0 .../{ => files}/specs/tri/crypto/sha256.t27 | 0 .../{ => files}/specs/tri/encoding/bson.t27 | 0 .../{ => files}/specs/tri/encoding/csv.t27 | 0 .../{ => files}/specs/tri/encoding/html.t27 | 0 .../{ => files}/specs/tri/encoding/json.t27 | 0 .../{ => files}/specs/tri/encoding/markup.t27 | 0 .../{ => files}/specs/tri/encoding/mime.t27 | 0 .../specs/tri/encoding/msgpack.t27 | 0 .../{ => files}/specs/tri/encoding/xml.t27 | 0 .../specs/tri/graph/bellman_ford.t27 | 0 .../{ => files}/specs/tri/graph/dijkstra.t27 | 0 .../specs/tri/graph/disjoint_set.t27 | 0 .../t27/{ => files}/specs/tri/graph/graph.t27 | 0 .../{ => files}/specs/tri/graph/graph_bfs.t27 | 0 .../{ => files}/specs/tri/graph/graph_dfs.t27 | 0 .../{ => files}/specs/tri/graph/prims_mst.t27 | 0 .../specs/tri/graph/topological_sort.t27 | 0 .../t27/{ => files}/specs/tri/io/compress.t27 | 0 .../{ => files}/specs/tri/io/filesystem.t27 | 0 .../t27/{ => files}/specs/tri/io/fs.t27 | 0 .../t27/{ => files}/specs/tri/io/io.t27 | 0 .../t27/{ => files}/specs/tri/io/reader.t27 | 0 .../t27/{ => files}/specs/tri/io/writer.t27 | 0 .../t27/{ => files}/specs/tri/io/zip.t27 | 0 .../t27/{ => files}/specs/tri/math/bezier.t27 | 0 .../{ => files}/specs/tri/math/constants.t27 | 0 .../t27/{ => files}/specs/tri/math/math.t27 | 0 .../t27/{ => files}/specs/tri/math/matrix.t27 | 0 .../specs/tri/math/measurement.t27 | 0 .../{ => files}/specs/tri/math/polynomial.t27 | 0 .../specs/tri/math/probability.t27 | 0 .../{ => files}/specs/tri/math/statistics.t27 | 0 .../t27/{ => files}/specs/tri/net/async.t27 | 0 .../specs/tri/net/async_stream.t27 | 0 .../t27/{ => files}/specs/tri/net/channel.t27 | 0 .../t27/{ => files}/specs/tri/net/cloud.t27 | 0 .../t27/{ => files}/specs/tri/net/http.t27 | 0 .../t27/{ => files}/specs/tri/net/net.t27 | 0 .../t27/{ => files}/specs/tri/net/url.t27 | 0 .../specs/tri/pipeline/batch_runner.t27 | 0 .../specs/tri/pipeline/builder.t27 | 0 .../specs/tri/pipeline/cloud_orchestrator.t27 | 0 .../specs/tri/pipeline/codegen.t27 | 0 .../specs/tri/pipeline/pipeline.t27 | 0 .../specs/tri/pipeline/pipeline_parallel.t27 | 0 .../specs/tri/pipeline/spec_parser.t27 | 0 .../specs/tri/pipeline/spec_writer.t27 | 0 .../specs/tri/pipeline/workflow.t27 | 0 .../specs/tri/pipeline/workflow_executor.t27 | 0 .../specs/tri/pipeline/workflow_parser.t27 | 0 .../specs/tri/search/aho_corasick.t27 | 0 .../specs/tri/search/bloom_filter.t27 | 0 .../specs/tri/search/boyer_moore.t27 | 0 .../specs/tri/search/knuth_morris_pratt.t27 | 0 .../{ => files}/specs/tri/search/match.t27 | 0 .../{ => files}/specs/tri/search/pattern.t27 | 0 .../specs/tri/search/rabin_karp.t27 | 0 .../{ => files}/specs/tri/search/regex.t27 | 0 .../specs/tri/search/regex_advanced.t27 | 0 .../{ => files}/specs/tri/search/search.t27 | 0 .../specs/tri/sort/counting_sort.t27 | 0 .../{ => files}/specs/tri/sort/heap_sort.t27 | 0 .../specs/tri/sort/insertion_sort.t27 | 0 .../{ => files}/specs/tri/sort/merge_sort.t27 | 0 .../{ => files}/specs/tri/sort/quick_sort.t27 | 0 .../{ => files}/specs/tri/sort/radix_sort.t27 | 0 .../specs/tri/sort/selection_sort.t27 | 0 .../{ => files}/specs/tri/sort/shell_sort.t27 | 0 .../t27/{ => files}/specs/tri/sort/sort.t27 | 0 .../{ => files}/specs/tri/sort/tim_sort.t27 | 0 .../{ => files}/specs/tri/trees/avl_tree.t27 | 0 .../{ => files}/specs/tri/trees/b_tree.t27 | 0 .../specs/tri/trees/fenwick_tree.t27 | 0 .../{ => files}/specs/tri/trees/kd_tree.t27 | 0 .../{ => files}/specs/tri/trees/octree.t27 | 0 .../{ => files}/specs/tri/trees/quadtree.t27 | 0 .../specs/tri/trees/red_black_tree.t27 | 0 .../t27/{ => files}/specs/tri/trees/rtree.t27 | 0 .../specs/tri/trees/segment_tree.t27 | 0 .../specs/tri/trees/splay_tree.t27 | 0 .../specs/tri/trees/suffix_array.t27 | 0 .../t27/{ => files}/specs/tri/trees/tree.t27 | 0 .../t27/{ => files}/specs/tri/trees/trie.t27 | 0 .../t27/{ => files}/specs/tri/utils/args.t27 | 0 .../specs/tri/utils/arrow_time.t27 | 0 .../t27/{ => files}/specs/tri/utils/bytes.t27 | 0 .../t27/{ => files}/specs/tri/utils/color.t27 | 0 .../{ => files}/specs/tri/utils/colors.t27 | 0 .../{ => files}/specs/tri/utils/config.t27 | 0 .../t27/{ => files}/specs/tri/utils/error.t27 | 0 .../specs/tri/utils/exit_codes.t27 | 0 .../t27/{ => files}/specs/tri/utils/help.t27 | 0 .../{ => files}/specs/tri/utils/logger.t27 | 0 .../{ => files}/specs/tri/utils/logging.t27 | 0 .../{ => files}/specs/tri/utils/random.t27 | 0 .../{ => files}/specs/tri/utils/string.t27 | 0 .../{ => files}/specs/tri/utils/template.t27 | 0 .../{ => files}/specs/tri/utils/terminal.t27 | 0 .../t27/{ => files}/specs/tri/utils/text.t27 | 0 .../t27/{ => files}/specs/tri/utils/time.t27 | 0 .../t27/{ => files}/specs/tri/utils/utf8.t27 | 0 .../{ => files}/specs/tri/utils/version.t27 | 0 .../{ => files}/specs/vm/jit_semantics.t27 | 0 .../specs/vsa/jones_polynomial.t27 | 0 .../public/t27/{ => files}/specs/vsa/ops.t27 | 0 .../t27/{ => files}/specs/vsa/packed_vsa.t27 | 0 .../public/t27/{ => files}/specs/vsa/sdk.t27 | 0 .../{ => files}/specs/vsa/sequence_hdc.t27 | 0 .../specs/vsa/similarity_search.t27 | 0 .../t27/{ => files}/specs/vsa/vsa_core.t27 | 0 .../public/t27/files/test_highlight.t27 | 56 + .../t27/files/tests/comprehensive_suite.t27 | 18 + .../public/t27/files/tests/ring0_trivial.t27 | 10 + .../vscode-trinity-swe/test_highlight.t27 | 0 apps/website/public/t27/manifest.json | 2 +- apps/website/scripts/sync-t27-specs.mjs | 32 +- apps/website/src/components/Navigation.tsx | 1 + apps/website/src/components/SpecCodeView.tsx | 129 + apps/website/src/lib/highlight.ts | 142 + apps/website/src/lib/t27Compiler.ts | 2 +- apps/website/src/pages/SpecExplorer.tsx | 214 +- 675 files changed, 116882 insertions(+), 79 deletions(-) create mode 100644 apps/website/public/t27/files/bootstrap/specs/physics/formula_registry.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/avs_controller_48.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/avs_controller_96.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/avs_reconf.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/dfs_gate.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/drowsy_ret.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/fbb_active_path.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/fp8_e4m3.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/fp8_e5m2.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/gf128.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/gf16_to_fp16.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/gf16_to_posit16.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/gf256.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/gf32_to_fp32.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/gf64.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/holo_mux_x4.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/int4.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/int8.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/lane_l_precheck.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/lut_npu_81_entry.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/nf4.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/null_pe.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/posit16.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/purkinje_thermal_gate.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/sparse_mask.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/sparse_skip.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/spec_exit.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/stoch_round.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/fpga/subth_clk.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/binary16.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/formats.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/fp8_e4m3.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/fp8_e5m2.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/gf12.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/gf128.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/gf16.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/gf20.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/gf24.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/gf256.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/gf32.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/gf4.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/gf64.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/gf8.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/goldenfloat_family.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/int4.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/int8.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/nf4.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/phi_ratio.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/posit16.t27 create mode 100644 apps/website/public/t27/files/chips/euler/specs/numeric/tri_net_formats.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/avs_controller_48.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/avs_controller_96.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/avs_reconf.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/dfs_gate.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/drowsy_ret.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/fbb_active_path.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/fp8_e4m3.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/fp8_e5m2.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/gf128.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/gf16_to_fp16.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/gf16_to_posit16.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/gf256.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/gf32_to_fp32.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/gf64.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/holo_mux_x4.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/int4.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/int8.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/lane_l_precheck.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/lut_npu_81_entry.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/nf4.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/null_pe.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/posit16.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/purkinje_thermal_gate.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/sparse_mask.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/sparse_skip.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/spec_exit.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/stoch_round.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/fpga/subth_clk.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/binary16.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/formats.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/fp8_e4m3.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/fp8_e5m2.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/gf12.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/gf128.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/gf16.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/gf20.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/gf24.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/gf256.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/gf32.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/gf4.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/gf64.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/gf8.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/goldenfloat_family.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/int4.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/int8.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/nf4.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/phi_ratio.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/posit16.t27 create mode 100644 apps/website/public/t27/files/chips/gamma/specs/numeric/tri_net_formats.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/avs_controller_48.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/avs_controller_96.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/avs_reconf.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/dfs_gate.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/drowsy_ret.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/fbb_active_path.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/fp8_e4m3.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/fp8_e5m2.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/gf128.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/gf16_to_fp16.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/gf16_to_posit16.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/gf256.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/gf32_to_fp32.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/gf64.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/holo_mux_x4.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/int4.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/int8.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/lane_l_precheck.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/lut_npu_81_entry.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/nf4.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/null_pe.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/posit16.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/purkinje_thermal_gate.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/sparse_mask.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/sparse_skip.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/spec_exit.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/stoch_round.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/fpga/subth_clk.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/binary16.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/formats.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/fp8_e4m3.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/fp8_e5m2.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/gf12.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/gf128.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/gf16.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/gf20.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/gf24.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/gf256.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/gf32.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/gf4.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/gf64.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/gf8.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/goldenfloat_family.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/int4.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/int8.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/nf4.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/phi_ratio.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/posit16.t27 create mode 100644 apps/website/public/t27/files/chips/phi/specs/numeric/tri_net_formats.t27 create mode 100644 apps/website/public/t27/files/compiler/ast.t27 create mode 100644 apps/website/public/t27/files/compiler/cli/gen.t27 create mode 100644 apps/website/public/t27/files/compiler/cli/git.t27 create mode 100644 apps/website/public/t27/files/compiler/cli/spec.t27 create mode 100644 apps/website/public/t27/files/compiler/codegen/c/codegen.t27 create mode 100644 apps/website/public/t27/files/compiler/codegen/testgen.t27 create mode 100644 apps/website/public/t27/files/compiler/codegen/verilog/codegen.t27 create mode 100644 apps/website/public/t27/files/compiler/codegen/verilog/fpga_emission.t27 create mode 100644 apps/website/public/t27/files/compiler/codegen/zig/codegen.t27 create mode 100644 apps/website/public/t27/files/compiler/codegen/zig/runtime.t27 create mode 100644 apps/website/public/t27/files/compiler/parser/lexer.t27 create mode 100644 apps/website/public/t27/files/compiler/parser/parser.t27 create mode 100644 apps/website/public/t27/files/compiler/runtime/commands.t27 create mode 100644 apps/website/public/t27/files/compiler/runtime/runtime.t27 create mode 100644 apps/website/public/t27/files/compiler/runtime/validation.t27 create mode 100644 apps/website/public/t27/files/compiler/skill/registry.t27 create mode 100644 apps/website/public/t27/files/contrib/backend/zig/legacy/main_zig_handwritten.t27 create mode 100644 apps/website/public/t27/files/examples/fpga/qmtech_minimal/design.t27 rename apps/website/public/t27/{ => files}/specs/account/auth.t27 (100%) rename apps/website/public/t27/{ => files}/specs/account/repo.t27 (100%) rename apps/website/public/t27/{ => files}/specs/account/schema.t27 (100%) rename apps/website/public/t27/{ => files}/specs/api/c_api_contract.t27 (100%) rename apps/website/public/t27/{ => files}/specs/api/sdk_contract.t27 (100%) rename apps/website/public/t27/{ => files}/specs/api/tri_net_api.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ar/asp_solver.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ar/coa_planning.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ar/composition.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ar/datalog_engine.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ar/explainability.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ar/proof_trace.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ar/restraint.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ar/ternary_logic.t27 (100%) rename apps/website/public/t27/{ => files}/specs/auth/config.t27 (100%) rename apps/website/public/t27/{ => files}/specs/automation/wrapup-auto.t27 (100%) rename apps/website/public/t27/{ => files}/specs/base/debounce.t27 (100%) rename apps/website/public/t27/{ => files}/specs/base/ops.t27 (100%) rename apps/website/public/t27/{ => files}/specs/base/ring_32.t27 (100%) rename apps/website/public/t27/{ => files}/specs/base/seed.t27 (100%) rename apps/website/public/t27/{ => files}/specs/base/ternary_add.t27 (100%) rename apps/website/public/t27/{ => files}/specs/base/ternary_encoding.t27 (100%) rename apps/website/public/t27/{ => files}/specs/base/ternary_memory.t27 (100%) rename apps/website/public/t27/{ => files}/specs/base/types.t27 (100%) rename apps/website/public/t27/{ => files}/specs/benchmarks/bench_main.t27 (100%) rename apps/website/public/t27/{ => files}/specs/benchmarks/bench_nn.t27 (100%) rename apps/website/public/t27/{ => files}/specs/benchmarks/gf16_bfloat16_nmse.t27 (100%) rename apps/website/public/t27/{ => files}/specs/benchmarks/ternary_vs_binary.t27 (100%) rename apps/website/public/t27/{ => files}/specs/boards/arty_a7.t27 (100%) rename apps/website/public/t27/{ => files}/specs/boards/xc7a100t_full.t27 (100%) rename apps/website/public/t27/{ => files}/specs/boards/xc7a100t_minimal.t27 (100%) rename apps/website/public/t27/{ => files}/specs/brain/brain.t27 (100%) rename apps/website/public/t27/{ => files}/specs/brain/bus.t27 (100%) rename apps/website/public/t27/{ => files}/specs/brain/cognitive_loop.t27 (100%) rename apps/website/public/t27/{ => files}/specs/brain/neural_gamma.t27 (100%) rename apps/website/public/t27/{ => files}/specs/brain/phi_timing.t27 (100%) rename apps/website/public/t27/{ => files}/specs/brain/unified_state.t27 (100%) rename apps/website/public/t27/{ => files}/specs/bus/pubsub.t27 (100%) rename apps/website/public/t27/{ => files}/specs/bus/schema.t27 (100%) rename apps/website/public/t27/{ => files}/specs/cloud/railway_deploy.t27 (100%) rename apps/website/public/t27/{ => files}/specs/compiler/diagnostics.t27 (100%) rename apps/website/public/t27/{ => files}/specs/compiler/lexer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/compiler/linker.t27 (100%) rename apps/website/public/t27/{ => files}/specs/compiler/meta_compile.t27 (100%) rename apps/website/public/t27/{ => files}/specs/compiler/mod_structure.t27 (100%) rename apps/website/public/t27/{ => files}/specs/compiler/optimizer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/compiler/parser.t27 (100%) rename apps/website/public/t27/{ => files}/specs/compiler/pipeline.t27 (100%) rename apps/website/public/t27/{ => files}/specs/compiler/stdlib.t27 (100%) rename apps/website/public/t27/{ => files}/specs/compiler/typechecker.t27 (100%) rename apps/website/public/t27/{ => files}/specs/config/load.t27 (100%) rename apps/website/public/t27/{ => files}/specs/config/migrate.t27 (100%) rename apps/website/public/t27/{ => files}/specs/config/paths.t27 (100%) rename apps/website/public/t27/{ => files}/specs/config/schema.t27 (100%) rename apps/website/public/t27/{ => files}/specs/conformance/e2e_scenarios.t27 (100%) rename apps/website/public/t27/{ => files}/specs/demos/jones_topology_decision_gate.t27 (100%) rename apps/website/public/t27/{ => files}/specs/demos/jones_topology_filter.t27 (100%) rename apps/website/public/t27/{ => files}/specs/demos/simple_test.t27 (100%) rename apps/website/public/t27/{ => files}/specs/depin/prove.t27 (100%) rename apps/website/public/t27/{ => files}/specs/enrichment/audio_overview.t27 (100%) rename apps/website/public/t27/{ => files}/specs/enrichment/youtube_transcript.t27 (100%) rename apps/website/public/t27/{ => files}/specs/file/operations.t27 (100%) rename apps/website/public/t27/{ => files}/specs/file/schema.t27 (100%) rename apps/website/public/t27/{ => files}/specs/file/watcher.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/apb_bridge.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/assembler.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/axi4.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/boards/arty_a7_integration.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/boards/qmtech_a100t_integration.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/bootrom.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/bridge.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/clock_domain.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/crossopt.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/cts.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/dft.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/e2e_demo.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/fifo.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/formal.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/gf16_accel.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/hir.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/hw_types.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/linker.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/mac.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/memory.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/partition.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/placement.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/power.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/power_analysis.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/router.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/simulator.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/spi.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/stdlib.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/ternary_isa.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/apb_bridge_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/assembler_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/axi4_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/bootrom_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/bridge_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/clock_domain_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/cts_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/dft_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/fifo_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/formal_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/gf16_accel_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/hir_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/integration_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/linker_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/mac_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/memory_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/partition_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/placement_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/power_analysis_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/power_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/router_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/simulator_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/spi_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/stdlib_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/ternary_isa_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/timing_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/top_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/uart_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/vcd_conformance_compare_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/testbench/vcd_trace_tb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/timing.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/top_level.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/uart.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/vcd_conformance_compare.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/vcd_trace.t27 (100%) rename apps/website/public/t27/{ => files}/specs/fpga/verification/build_verify.t27 (100%) rename apps/website/public/t27/{ => files}/specs/git/diff.t27 (100%) rename apps/website/public/t27/{ => files}/specs/git/operations.t27 (100%) rename apps/website/public/t27/{ => files}/specs/git/schema.t27 (100%) rename apps/website/public/t27/{ => files}/specs/git/status.t27 (100%) rename apps/website/public/t27/{ => files}/specs/github/auth.t27 (100%) rename apps/website/public/t27/{ => files}/specs/github/comments.t27 (100%) rename apps/website/public/t27/{ => files}/specs/github/issues.t27 (100%) rename apps/website/public/t27/{ => files}/specs/github/prs.t27 (100%) rename apps/website/public/t27/{ => files}/specs/github/tests/e2e_full_flow.t27 (100%) rename apps/website/public/t27/{ => files}/specs/graph/knowledge_graph.t27 (100%) rename apps/website/public/t27/{ => files}/specs/hslm/forward_pass.t27 (100%) rename apps/website/public/t27/{ => files}/specs/interop/gf_cross_language.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/registers.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_arithmetic.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_bitwise.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_control_flow.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_deque.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_encoding.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_gates.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_graph.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_hash.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_memory.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_pattern_matching.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_search.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_set.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_shift.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_sorting.t27 (100%) rename apps/website/public/t27/{ => files}/specs/isa/ternary_tree.t27 (100%) rename apps/website/public/t27/{ => files}/specs/jit/jit.t27 (100%) rename apps/website/public/t27/{ => files}/specs/lsp/client.t27 (100%) rename apps/website/public/t27/{ => files}/specs/lsp/language.t27 (100%) rename apps/website/public/t27/{ => files}/specs/lsp/protocol.t27 (100%) rename apps/website/public/t27/{ => files}/specs/lsp/schema.t27 (100%) rename apps/website/public/t27/{ => files}/specs/lsp/server.t27 (100%) rename apps/website/public/t27/{ => files}/specs/math/constants.t27 (100%) rename apps/website/public/t27/{ => files}/specs/math/e8_lie_algebra.t27 (100%) rename apps/website/public/t27/{ => files}/specs/math/gf_competitive.t27 (100%) rename apps/website/public/t27/{ => files}/specs/math/pellis_precision_verify.t27 (100%) rename apps/website/public/t27/{ => files}/specs/math/phi_split_optimality.t27 (100%) rename apps/website/public/t27/{ => files}/specs/math/phi_universal_attractor.t27 (100%) rename apps/website/public/t27/{ => files}/specs/math/property_test_template.t27 (100%) rename apps/website/public/t27/{ => files}/specs/math/radix_economy.t27 (100%) rename apps/website/public/t27/{ => files}/specs/math/sacred_physics.t27 (100%) rename apps/website/public/t27/{ => files}/specs/math/zamolodchikov_e8.t27 (100%) rename apps/website/public/t27/{ => files}/specs/memory/formula_embed.t27 (100%) rename apps/website/public/t27/{ => files}/specs/memory/memory_primitives.t27 (100%) rename apps/website/public/t27/{ => files}/specs/memory/notebooklm.t27 (100%) rename apps/website/public/t27/{ => files}/specs/memory/semantic_search.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/activation/elu_activation.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/activation/gelu_activation.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/activation/gelu_approx_activation.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/activation/leaky_relu_activation.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/activation/relu_activation.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/activation/sigmoid_activation.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/activation/silu_swish_activation.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/activation/silu_swish_vbt_activation.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/activation/softmax.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/activation/tanh_activation.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/layers/avgpool2d_layer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/layers/batchnorm_layer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/layers/conv2d_layer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/layers/dense_layer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/layers/dropout_layer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/layers/embedding_layer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/layers/flatten_layer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/layers/layernorm_layer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/layers/maxpool2d_layer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/layers/residual_connection.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/loss/binary_crossentropy_loss.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/loss/contrastive_loss.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/loss/cross_entropy_loss.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/loss/huber_loss.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/loss/kl_divergence.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/loss/mse_loss.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/optimizer/adagrad.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/optimizer/adam.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/optimizer/adamw.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/optimizer/lamb.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/optimizer/lr_scheduler.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/optimizer/rmsprop.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/optimizer/sgd.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/optimizer/sgd_momentum.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/pathway/mlp.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/recurrent/attention_mechanism.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/recurrent/bilstm.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/recurrent/gru_cell.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/recurrent/lstm_cell.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/recurrent/lstm_single.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/recurrent/rnn_cell.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/recurrent/self_attention.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/recurrent/seq2seq.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/rl/advantage_estimator.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/rl/dqn.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/rl/dqn_target_network.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/rl/ppo_actor.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/rl/ppo_clip_loss.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/rl/ppo_critic.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/rl/sac_actor.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/rl/sac_critic.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/transformer/encoder_block.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/transformer/feed_forward.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/transformer/feed_forward_network.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/transformer/mha_block.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/transformer/multi_head_attention.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/transformer/multi_head_attn.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/transformer/norm.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/transformer/positional_enc.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ml/transformer/positional_encoding.t27 (100%) rename apps/website/public/t27/{ => files}/specs/neural/forward_pass.t27 (100%) rename apps/website/public/t27/{ => files}/specs/nn/attention.t27 (100%) rename apps/website/public/t27/{ => files}/specs/nn/hslm.t27 (100%) rename apps/website/public/t27/{ => files}/specs/nn/phi_rope.t27 (100%) rename apps/website/public/t27/{ => files}/specs/nn/sacred_attention.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/bigint.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/formats.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/gf12.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/gf16.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/gf20.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/gf24.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/gf32.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/gf4.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/gf8.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/gf_competitive.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/goldenfloat_family.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/pellis_verify.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/phi_ratio.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/requant_boundary.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/tf3.t27 (100%) rename apps/website/public/t27/{ => files}/specs/numeric/trinity_numeric_surface.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/chimera_best_gamma.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/e8_lqg_bridge.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/formula_discovery.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/formula_registry.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/gamma-conflict.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/gamma_conjecture.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/gi1_analysis.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/hslm_benchmark.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/lqg_cs_bridge.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/lqg_entropy.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/p2_brain_physics.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/pellis-formulas.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/quantum.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/sacred_verification.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/su2_chern_simons.t27 (100%) rename apps/website/public/t27/{ => files}/specs/physics/zamolodchikov_4d_conjecture.t27 (100%) rename apps/website/public/t27/{ => files}/specs/pins/emitter_xdc.t27 (100%) rename apps/website/public/t27/{ => files}/specs/pins/ir.t27 (100%) rename apps/website/public/t27/{ => files}/specs/pins/parser.t27 (100%) rename apps/website/public/t27/{ => files}/specs/pipeline/benchmarks.t27 (100%) rename apps/website/public/t27/{ => files}/specs/pipeline/e2e_test.t27 (100%) rename apps/website/public/t27/{ => files}/specs/pipeline/experience_save.t27 (100%) rename apps/website/public/t27/{ => files}/specs/portable/relay_observer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/provider/adapters.t27 (100%) rename apps/website/public/t27/{ => files}/specs/provider/schema.t27 (100%) rename apps/website/public/t27/{ => files}/specs/provider/stream.t27 (100%) rename apps/website/public/t27/{ => files}/specs/provider/transform.t27 (100%) rename apps/website/public/t27/{ => files}/specs/queen/brain_summaries.t27 (100%) rename apps/website/public/t27/{ => files}/specs/queen/lotus.t27 (100%) rename apps/website/public/t27/{ => files}/specs/queen/task_analysis.t27 (100%) rename apps/website/public/t27/{ => files}/specs/runtime/execute.t27 (100%) rename apps/website/public/t27/{ => files}/specs/runtime/instance.t27 (100%) rename apps/website/public/t27/{ => files}/specs/runtime/process.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sacred/cosmology.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sacred/dark_matter.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sacred/gravity.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sacred/monopoles.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sacred/quantum.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sacred/quantum_gravity.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sacred/sacred_constants.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sacred/sacred_governance.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sacred/sacred_identity.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sacred/superconductivity.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sandbox/health.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sandbox/https_enforce.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sandbox/modules.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sandbox/orphan_detection.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sandbox/session_timeout.t27 (100%) rename apps/website/public/t27/{ => files}/specs/server/agent-runner.t27 (100%) rename apps/website/public/t27/{ => files}/specs/server/api.t27 (100%) rename apps/website/public/t27/{ => files}/specs/server/http.t27 (100%) rename apps/website/public/t27/{ => files}/specs/server/mdns.t27 (100%) rename apps/website/public/t27/{ => files}/specs/server/project.t27 (100%) rename apps/website/public/t27/{ => files}/specs/server/provider.t27 (100%) rename apps/website/public/t27/{ => files}/specs/server/router.t27 (100%) rename apps/website/public/t27/{ => files}/specs/server/routes.t27 (100%) rename apps/website/public/t27/{ => files}/specs/server/session.t27 (100%) rename apps/website/public/t27/{ => files}/specs/server/sse.t27 (100%) rename apps/website/public/t27/{ => files}/specs/server/vm.t27 (100%) rename apps/website/public/t27/{ => files}/specs/shell/environment.t27 (100%) rename apps/website/public/t27/{ => files}/specs/shell/process.t27 (100%) rename apps/website/public/t27/{ => files}/specs/shell/schema.t27 (100%) rename apps/website/public/t27/{ => files}/specs/storage/kv.t27 (100%) rename apps/website/public/t27/{ => files}/specs/storage/lock.t27 (100%) rename apps/website/public/t27/{ => files}/specs/storage/migrate.t27 (100%) rename apps/website/public/t27/{ => files}/specs/storage/schema.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sync/index.t27 (100%) rename apps/website/public/t27/{ => files}/specs/sync/schema.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ternary/bigint.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ternary/hybrid_arithmetic.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ternary/hybrid_bigint.t27 (100%) rename apps/website/public/t27/{ => files}/specs/ternary/packed_trit.t27 (100%) rename apps/website/public/t27/{ => files}/specs/test_framework/core.t27 (100%) rename apps/website/public/t27/{ => files}/specs/test_framework/graph_drift_detection.t27 (100%) rename apps/website/public/t27/{ => files}/specs/test_framework/property_test_template.t27 (100%) rename apps/website/public/t27/{ => files}/specs/test_framework/runner.t27 (100%) rename apps/website/public/t27/{ => files}/specs/test_framework/verilog_bench_harness.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tools/registry.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tools/schema.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tools/tri_to_t27_converter.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/agent/agent_run.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/agent/agents.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/agent/autonomous_lifecycle.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/agent/autonomous_universe.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/agent/eternal_monitor.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/agent/experience_hooks.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/agent/faculty_board.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/agent/governance_agent.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/agent/handoff.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/agent/memory.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/agent/swarm_agents.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/array.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/bitmap.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/bitset.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/bitvector.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/btree.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/circular_buffer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/context.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/deque.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/either.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/interval.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/linked_list.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/list.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/lockfree_stack.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/lru.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/lru_cache.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/map.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/maybe.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/namespace.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/option.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/priority_queue.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/queue.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/result.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/ring_buffer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/set.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/skip_list.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/stack.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/state.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/tuple.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/collections/variant.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/crypto/base32.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/crypto/base64.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/crypto/crypto.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/crypto/ecc.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/crypto/hex.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/crypto/hmac.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/crypto/reed_solomon.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/crypto/rsa.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/crypto/sha256.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/encoding/bson.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/encoding/csv.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/encoding/html.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/encoding/json.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/encoding/markup.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/encoding/mime.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/encoding/msgpack.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/encoding/xml.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/graph/bellman_ford.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/graph/dijkstra.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/graph/disjoint_set.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/graph/graph.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/graph/graph_bfs.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/graph/graph_dfs.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/graph/prims_mst.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/graph/topological_sort.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/io/compress.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/io/filesystem.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/io/fs.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/io/io.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/io/reader.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/io/writer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/io/zip.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/math/bezier.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/math/constants.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/math/math.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/math/matrix.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/math/measurement.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/math/polynomial.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/math/probability.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/math/statistics.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/net/async.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/net/async_stream.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/net/channel.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/net/cloud.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/net/http.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/net/net.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/net/url.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/pipeline/batch_runner.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/pipeline/builder.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/pipeline/cloud_orchestrator.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/pipeline/codegen.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/pipeline/pipeline.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/pipeline/pipeline_parallel.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/pipeline/spec_parser.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/pipeline/spec_writer.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/pipeline/workflow.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/pipeline/workflow_executor.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/pipeline/workflow_parser.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/search/aho_corasick.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/search/bloom_filter.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/search/boyer_moore.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/search/knuth_morris_pratt.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/search/match.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/search/pattern.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/search/rabin_karp.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/search/regex.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/search/regex_advanced.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/search/search.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/sort/counting_sort.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/sort/heap_sort.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/sort/insertion_sort.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/sort/merge_sort.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/sort/quick_sort.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/sort/radix_sort.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/sort/selection_sort.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/sort/shell_sort.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/sort/sort.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/sort/tim_sort.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/trees/avl_tree.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/trees/b_tree.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/trees/fenwick_tree.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/trees/kd_tree.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/trees/octree.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/trees/quadtree.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/trees/red_black_tree.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/trees/rtree.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/trees/segment_tree.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/trees/splay_tree.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/trees/suffix_array.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/trees/tree.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/trees/trie.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/args.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/arrow_time.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/bytes.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/color.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/colors.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/config.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/error.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/exit_codes.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/help.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/logger.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/logging.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/random.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/string.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/template.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/terminal.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/text.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/time.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/utf8.t27 (100%) rename apps/website/public/t27/{ => files}/specs/tri/utils/version.t27 (100%) rename apps/website/public/t27/{ => files}/specs/vm/jit_semantics.t27 (100%) rename apps/website/public/t27/{ => files}/specs/vsa/jones_polynomial.t27 (100%) rename apps/website/public/t27/{ => files}/specs/vsa/ops.t27 (100%) rename apps/website/public/t27/{ => files}/specs/vsa/packed_vsa.t27 (100%) rename apps/website/public/t27/{ => files}/specs/vsa/sdk.t27 (100%) rename apps/website/public/t27/{ => files}/specs/vsa/sequence_hdc.t27 (100%) rename apps/website/public/t27/{ => files}/specs/vsa/similarity_search.t27 (100%) rename apps/website/public/t27/{ => files}/specs/vsa/vsa_core.t27 (100%) create mode 100644 apps/website/public/t27/files/test_highlight.t27 create mode 100644 apps/website/public/t27/files/tests/comprehensive_suite.t27 create mode 100644 apps/website/public/t27/files/tests/ring0_trivial.t27 create mode 100644 apps/website/public/t27/files/vscode-trinity-swe/test_highlight.t27 create mode 100644 apps/website/src/components/SpecCodeView.tsx create mode 100644 apps/website/src/lib/highlight.ts diff --git a/.gitignore b/.gitignore index e040aea90c..ae7de44093 100644 --- a/.gitignore +++ b/.gitignore @@ -424,8 +424,11 @@ fpga-flash !.trinity/souls/ !templates/SOUL.md -# The vendored t27 spec corpus backs the /specs explorer -- it is data, not -# build output. The bare `demos/` and `runtime/` rules above match at any -# depth, which silently dropped six specs from the snapshot. -!apps/website/public/t27/specs/demos/ -!apps/website/public/t27/specs/runtime/ +# The vendored t27 corpus backs the /specs explorer -- it is data, not build +# output. Many bare directory rules above (`demos/`, `runtime/`, `legacy/`, …) +# match at any depth and silently drop files from the snapshot; because git +# cannot re-include a file whose parent directory is excluded, the `**` has to +# re-include those directories too, not just the files inside them. +# Enumerating them one by one was tried first and is whack-a-mole: the widened +# corpus immediately hit three more. +!apps/website/public/t27/** diff --git a/apps/website/public/t27/files/bootstrap/specs/physics/formula_registry.t27 b/apps/website/public/t27/files/bootstrap/specs/physics/formula_registry.t27 new file mode 100644 index 0000000000..3e7657edd1 --- /dev/null +++ b/apps/website/public/t27/files/bootstrap/specs/physics/formula_registry.t27 @@ -0,0 +1,209 @@ +// Trinity Formula Registry — All 69 φ-parametrizations from v06/v07 +// Generated from FORMULA_TABLE_v06.md and FORMULA_TABLE_v07.md +// SSOT for Trinity formula discovery + +// ============================================================================ +// CONSTANTS +// ============================================================================ + +const PHI: f64 = 1.6180339887498948; +const PI: f64 = std::f64::consts::PI; +const E: f64 = std::f64::consts::E; +const GA: f64 = 360.0 / (PHI * PHI); // Golden angle = 222.5° + +// ============================================================================ +// SECTOR 1 — GAUGE COUPLINGS (8 formulas) +// ============================================================================ + +// [VERIFIED] sector=gauge-coupling cx=1 Δ=-0.62% +fn gamma_phi() -> f64 { + return pow(PHI, -3.0); +} + +// [VERIFIED] sector=gauge-coupling cx=1 Δ=0.00% +fn ln2_over_pi() -> f64 { + return ln(2.0) / PI; +} + +// [VERIFIED] sector=gauge-coupling cx=1 Δ=0.00% +fn ln3_over_pi() -> f64 { + return ln(3.0) / PI; +} + +// [VERIFIED] sector=gauge-coupling cx=1 Δ=0.029% +fn alpha_inv_pellis_exact() -> f64 { + return GA - 2.0 / pow(PHI, 3.0) + pow(3.0 * PHI, -5.0); +} + +// [VERIFIED] sector=gauge-coupling cx=1 Δ=0.029% +fn alpha_s() -> f64 { + return 1.0 / (pow(PHI, 4.0) + PHI); +} + +// [VERIFIED] sector=gauge-coupling cx=1 Δ=0.00% +fn tc_qcd() -> f64 { + return 156.5; +} + +// ============================================================================ +// SECTOR 2 — ELECTROWEAK & NUCLEAR (2 formulas) +// ============================================================================ + +// [VERIFIED] sector=electroweak cx=1 Δ=0.034% +fn neutron_proton_ratio() -> f64 { + let alpha_em: f64 = 1.0 / 137.035999084; + return 1.0 + alpha_em * gamma_phi(); +} + +// [VERIFIED] sector=electroweak cx=1 Δ=0.027% +fn muon_electron_ratio() -> f64 { + return 8.0 * pow(PHI, 2.0) * pow(PI, 2.0); +} + +// ============================================================================ +// SECTOR 3 — LEPTON MASSES (5 formulas) +// ============================================================================ + +// [VERIFIED] sector=lepton cx=1 Δ=0.029% +fn electron_mass_mev() -> f64 { + return 1.0 / (E * PHI); +} + +// [VERIFIED] sector=lepton cx=1 Δ=0.029% +fn muon_mass_mev() -> f64 { + return 2.0 * pow(PHI, 2.0) * pow(PI, 2.0); +} + +// [VERIFIED] sector=lepton cx=1 Δ=0.028% +fn tau_mass_mev() -> f64 { + return 4.0 / (PHI * PHI); +} + +// [VERIFIED] sector=lepton cx=1 Δ=0.000% +fn koide_q() -> f64 { + return 2.0 / 3.0; +} + +// ============================================================================ +// SECTOR 4 — QUARK MASSES (8 formulas) +// ============================================================================ + +// [VERIFIED] sector=quark cx=1 Δ=0.034% +fn bottom_mass_gev() -> f64 { + return 5.0 * PI * pow(PHI, -2.0) * pow(E, -1.0); +} + +// [VERIFIED] sector=quark cx=1 Δ=0.043% +fn top_mass_gev() -> f64 { + return 4.0 * 9.0 * PI * pow(PHI, 4.0) * pow(E, 2.0); +} + +// [VERIFIED] sector=quark cx=1 Δ=0.000% +fn strange_down_ratio() -> f64 { + return 2.0 * PI * PHI / 3.0; +} + +// ============================================================================ +// SECTOR 5 — CKM MATRIX (3 formulas) +// ============================================================================ + +// [VERIFIED] sector=ckm cx=1 Δ=0.096% +fn theta_cabibbo() -> f64 { + return GA / 16.0; +} + +// [VERIFIED] sector=ckm cx=1 Δ=0.043% +fn v_cb() -> f64 { + return 1.0 / (7.0 * pow(PHI, 2.0) * pow(PI, 2.0) * pow(E, 2.0)); +} + +// [VERIFIED] sector=ckm cx=1 Δ=1.36% +fn v_us() -> f64 { + return 1.0 / (E * PHI); +} + +// ============================================================================ +// SECTOR 6 — PMNS NEUTRINOS (4 formulas) +// ============================================================================ + +// [VERIFIED] sector=pmns cx=1 Δ=0.062% +fn sin2theta23_pmns() -> f64 { + return 3.0 * pow(PHI, -8.0) * PI * E; +} + +// [VERIFIED] sector=pmns cx=1 Δ=0.018% +fn delta_cp_pmns() -> f64 { + return 9.0 / (PHI * PHI); +} + +// [VERIFIED] sector=pmns cx=1 Δ=0.036% +fn sin2theta12_pmns() -> f64 { + return 4.0 / (pow(PHI, 2.0) * pow(PI, 4.0) * pow(E, 4.0)); +} + +// ============================================================================ +// SECTOR 7 — COSMOLOGY (1 formula) +// ============================================================================ + +// [VERIFIED] sector=cosmology cx=1 Δ=0.00% +fn lambda_exponent() -> f64 { + return 122; +} + +// ============================================================================ +// SECTOR 8 — HIGGS (1 formula) +// ============================================================================ + +// [VERIFIED] sector=higgs cx=1 Δ=0.022% +fn higgs_z_ratio() -> f64 { + return (1.0 / 8.0) * pow(PHI, 2.0) * pow(PI, 3.0) * pow(E, -2.0); +} + +// ============================================================================ +// V07 CHIMERA ADDITIONS (9 new VERIFIED formulas) +// ============================================================================ + +// [VERIFIED] sector=ckm cx=7 Δ=0.017% +fn v_ud_chimera() -> f64 { + return 7.0 * pow(PHI, -5.0) * pow(PI, 3.0) * pow(E, -3.0); +} + +// [VERIFIED] sector=ckm cx=7 Δ=0.080% +fn v_cs_chimera() -> f64 { + return 7.0 * pow(PHI, -5.0) * pow(PI, 3.0) * pow(E, -3.0); +} + +// [VERIFIED] sector=ckm cx=6 Δ=0.037% +fn v_td_chimera() -> f64 { + return 2.0 * pow(PHI, -4.0) * pow(PI, -4.0) * E; +} + +// [VERIFIED] sector=pmns cx=6 Δ=0.098% +fn sin2theta12_chimera() -> f64 { + return 8.0 * pow(PHI, -5.0) * PI * pow(E, -2.0); +} + +// [VERIFIED] sector=pmns cx=2 Δ=0.017% +fn delta_cp_rad() -> f64 { + return 9.0 * pow(PHI, -2.0); +} + +// [VERIFIED] sector=lepton cx=5 Δ=0.078% +fn strange_muon_ratio() -> f64 { + return pow(PHI, -2.0) * pow(PI, -1.0) * pow(E, 2.0); +} + +// [VERIFIED] sector=qcd cx=6 Δ=0.021% +fn bottom_top_ratio() -> f64 { + return 4.0 * pow(PHI, -2.0) * pow(PI, -1.0) * pow(E, -3.0); +} + +// [VERIFIED] sector=cosmology cx=5 Δ=0.041% +fn omega_b_chimera() -> f64 { + return 4.0 * pow(PHI, -2.0) * pow(PI, -3.0); +} + +// [VERIFIED] sector=cosmology cx=6 Δ=0.094% +fn ns_chimera() -> f64 { + return 3.0 * pow(PHI, 3.0) * pow(PI, -4.0) * pow(E, 2.0); +} diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/avs_controller_48.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/avs_controller_48.t27 new file mode 100644 index 0000000000..c528b272e3 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/avs_controller_48.t27 @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: Apache-2.0 +; avs_controller_48.t27 — 48-pin AVS Controller +; Adaptive voltage scaling controller for 48-pin configuration +; φ² + 1/φ² = 3 | TRINITY + +module avs-controller-48; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const PINS_48 : u8 = 48; +pub const VOLTAGE_MIN_MV : u16 = 800; // 0.8V minimum +pub const VOLTAGE_MAX_MV : u16 = 1200; // 1.2V maximum +pub const VOLTAGE_STEP_MV : u16 = 10; // 10mV step size +pub const VOLTAGE_LEVELS : u8 = 41; // (1200-800)/10 + 1 + +pub const TARGET_MV : u16 = 1000; // 1.0V target +pub const HYSTERESIS_MV : u16 = 20; // 20mV hysteresis +pub const SETTLING_US : u16 = 100; // 100us settling time + +pub const CONTROL_PIN_OFFSET : u8 = 16; // AVS control pins start at 16 +pub const CONTROL_PIN_COUNT : u8 = 6; // 6 control pins for 48-pin config + +pub const FREQ_MIN_KHZ : u32 = 10000; // 10 MHz minimum +pub const FREQ_MAX_KHZ : u32 = 100000; // 100 MHz maximum + +// ============================================================================ +// Types +// ============================================================================ + +pub const AvsState = enum(u8) { + disabled = 0, + enabled = 1, + adjusting = 2, + settled = 3, + fault = 4, +} + +pub const AvsMode = enum(u8) { + manual = 0, + automatic = 1, + adaptive = 2, +} + +pub const AvsPinConfig = struct { + pin_index : u8, + is_output : bool, + function : u8, // 0=control, 1=feedback, 2=enable +} + +pub const AvsVoltageLevel = struct { + level : u8, // 0 to 40 + voltage_mv : u16, + control_code : u8, // 6-bit code for control pins +} + +pub const AvsStatus = struct { + state : AvsState, + mode : AvsMode, + current_mv : u16, + target_mv : u16, + level : u8, + fault_code : u8, +} + +pub const AvsConfig = struct { + mode : AvsMode, + min_mv : u16, + max_mv : u16, + target_mv : u16, + hysteresis_mv : u16, + settling_us : u16, +} + +// ============================================================================ +// Voltage Level Functions +// ============================================================================ + +// avs_voltage_from_level(level: u8) -> u16 +// Convert voltage level to millivolts +pub fn avs_voltage_from_level(level: u8) -> u16 { + return VOLTAGE_MIN_MV + @as(u16, level) * VOLTAGE_STEP_MV; +} + +// avs_level_from_voltage(voltage_mv: u16) -> u8 +// Convert voltage to level +pub fn avs_level_from_voltage(voltage_mv: u16) -> u8 { + var mv = voltage_mv; + if (mv < VOLTAGE_MIN_MV) { + mv = VOLTAGE_MIN_MV; + } else if (mv > VOLTAGE_MAX_MV) { + mv = VOLTAGE_MAX_MV; + } + return @as(u8, (mv - VOLTAGE_MIN_MV) / VOLTAGE_STEP_MV); +} + +// avs_control_code_from_level(level: u8) -> u8 +// Generate 6-bit control code for voltage level +pub fn avs_control_code_from_level(level: u8) -> u8 { + // Simple binary encoding for 6 pins + return level & 0x3F; +} + +// avs_level_from_control_code(code: u8) -> u8 +// Decode level from 6-bit control code +pub fn avs_level_from_control_code(code: u8) -> u8 { + return code & 0x3F; +} + +// ============================================================================ +// Voltage Validation +// ============================================================================ + +// avs_voltage_valid(voltage_mv: u16) -> bool +// Check if voltage is within valid range +pub fn avs_voltage_valid(voltage_mv: u16) -> bool { + return voltage_mv >= VOLTAGE_MIN_MV and voltage_mv <= VOLTAGE_MAX_MV; +} + +// avs_voltage_in_hysteresis(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool +// Check if current voltage is within hysteresis band of target +pub fn avs_voltage_in_hysteresis(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool { + if (current_mv > target_mv) { + return (current_mv - target_mv) <= hysteresis_mv; + } else { + return (target_mv - current_mv) <= hysteresis_mv; + } +} + +// avs_should_adjust(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool +// Check if voltage should be adjusted +pub fn avs_should_adjust(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool { + return not avs_voltage_in_hysteresis(current_mv, target_mv, hysteresis_mv); +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// avs_config_init() -> AvsConfig +// Initialize AVS configuration +pub fn avs_config_init() -> AvsConfig { + return AvsConfig { + .mode = AvsMode.automatic, + .min_mv = VOLTAGE_MIN_MV, + .max_mv = VOLTAGE_MAX_MV, + .target_mv = TARGET_MV, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + }; +} + +// avs_config_manual(target_mv: u16) -> AvsConfig +// Create manual AVS configuration +pub fn avs_config_manual(target_mv: u16) -> AvsConfig { + return AvsConfig { + .mode = AvsMode.manual, + .min_mv = VOLTAGE_MIN_MV, + .max_mv = VOLTAGE_MAX_MV, + .target_mv = target_mv, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + }; +} + +// avs_config_adaptive(min_mv: u16, max_mv: u16) -> AvsConfig +// Create adaptive AVS configuration +pub fn avs_config_adaptive(min_mv: u16, max_mv: u16) -> AvsConfig { + return AvsConfig { + .mode = AvsMode.adaptive, + .min_mv = min_mv, + .max_mv = max_mv, + .target_mv = (min_mv + max_mv) / 2, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + }; +} + +// ============================================================================ +// Status Functions +// ============================================================================ + +// avs_status_init() -> AvsStatus +// Initialize AVS status +pub fn avs_status_init() -> AvsStatus { + return AvsStatus { + .state = AvsState.disabled, + .mode = AvsMode.manual, + .current_mv = VOLTAGE_MIN_MV, + .target_mv = TARGET_MV, + .level = 0, + .fault_code = 0, + }; +} + +// avs_status_enable(status: AvsStatus, config: AvsConfig) -> AvsStatus +// Enable AVS controller +pub fn avs_status_enable(status: AvsStatus, config: AvsConfig) -> AvsStatus { + return AvsStatus { + .state = AvsState.enabled, + .mode = config.mode, + .current_mv = status.current_mv, + .target_mv = config.target_mv, + .level = status.level, + .fault_code = 0, + }; +} + +// avs_status_adjust(status: AvsStatus, new_level: u8) -> AvsStatus +// Adjust to new voltage level +pub fn avs_status_adjust(status: AvsStatus, new_level: u8) -> AvsStatus { + const new_mv = avs_voltage_from_level(new_level); + return AvsStatus { + .state = AvsState.adjusting, + .mode = status.mode, + .current_mv = status.current_mv, + .target_mv = new_mv, + .level = new_level, + .fault_code = status.fault_code, + }; +} + +// avs_status_settled(status: AvsStatus, measured_mv: u16) -> AvsStatus +// Mark voltage as settled +pub fn avs_status_settled(status: AvsStatus, measured_mv: u16) -> AvsStatus { + return AvsStatus { + .state = AvsState.settled, + .mode = status.mode, + .current_mv = measured_mv, + .target_mv = status.target_mv, + .level = avs_level_from_voltage(measured_mv), + .fault_code = status.fault_code, + }; +} + +// avs_status_fault(status: AvsStatus, fault_code: u8) -> AvsStatus +// Mark fault condition +pub fn avs_status_fault(status: AvsStatus, fault_code: u8) -> AvsStatus { + return AvsStatus { + .state = AvsState.fault, + .mode = status.mode, + .current_mv = status.current_mv, + .target_mv = status.target_mv, + .level = status.level, + .fault_code = fault_code, + }; +} + +// ============================================================================ +// Pin Functions +// ============================================================================ + +// avs_control_pins_start() -> u8 +// Get starting pin index for control pins +pub fn avs_control_pins_start() -> u8 { + return CONTROL_PIN_OFFSET; +} + +// avs_control_pins_end() -> u8 +// Get ending pin index for control pins +pub fn avs_control_pins_end() -> u8 { + return CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT - 1; +} + +// avs_is_control_pin(pin_index: u8) -> bool +// Check if pin is a control pin +pub fn avs_is_control_pin(pin_index: u8) -> bool { + return pin_index >= avs_control_pins_start() and pin_index <= avs_control_pins_end(); +} + +// avs_control_pin_for_bit(bit_index: u8) -> u8 +// Get control pin for a bit index (0-5) +pub fn avs_control_pin_for_bit(bit_index: u8) -> u8 { + if (bit_index >= CONTROL_PIN_COUNT) { + return CONTROL_PIN_OFFSET; + } + return CONTROL_PIN_OFFSET + bit_index; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_avs_48_cmd(opcode: u8, level: u8, reserved: u8) -> u16 +// Encode AVS 48-pin command +pub fn encode_avs_48_cmd(opcode: u8, level: u8, reserved: u8) u16 { + // Format: [OP:8][LEVEL:6][RES:2] + const op_field : u16 = @as(u16, opcode) << 8; + const level_field : u16 = @as(u16, level & 0x3F) << 2; + const res_field : u16 = @as(u16, reserved & 0x03); + return op_field | level_field | res_field; +} + +// decode_avs_48_cmd(encoded: u16) -> struct { opcode: u8, level: u8, reserved: u8 } +// Decode AVS 48-pin command +pub fn decode_avs_48_cmd(encoded: u16) -> struct { opcode: u8, level: u8, reserved: u8 } { + const opcode : u8 = @as(u8, @truncate((encoded >> 8) & 0xFF)); + const level : u8 = @as(u8, @truncate((encoded >> 2) & 0x3F)); + const reserved : u8 = @as(u8, @truncate(encoded & 0x03)); + return .{ .opcode = opcode, .level = level, .reserved = reserved }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "pins_48_constant" { + try std.testing.expect(PINS_48 == 48); +} + +test "voltage_constants" { + try std.testing.expect(VOLTAGE_MIN_MV == 800); + try std.testing.expect(VOLTAGE_MAX_MV == 1200); + try std.testing.expect(VOLTAGE_STEP_MV == 10); + try std.testing.expect(VOLTAGE_LEVELS == 41); +} + +test "target_voltage_constant" { + try std.testing.expect(TARGET_MV == 1000); +} + +test "control_pin_constants" { + try std.testing.expect(CONTROL_PIN_OFFSET == 16); + try std.testing.expect(CONTROL_PIN_COUNT == 6); +} + +test "avs_voltage_from_level_zero" { + try std.testing.expect(avs_voltage_from_level(0) == VOLTAGE_MIN_MV); +} + +test "avs_voltage_from_level_max" { + given max_level = VOLTAGE_LEVELS - 1 + try std.testing.expect(avs_voltage_from_level(max_level) == VOLTAGE_MAX_MV); +} + +test "avs_voltage_from_level_target" { + given level = avs_level_from_voltage(TARGET_MV) + try std.testing.expect(avs_voltage_from_level(level) == TARGET_MV); +} + +test "avs_level_from_voltage_min" { + try std.testing.expect(avs_level_from_voltage(VOLTAGE_MIN_MV) == 0); +} + +test "avs_level_from_voltage_max" { + try std.testing.expect(avs_level_from_voltage(VOLTAGE_MAX_MV) == VOLTAGE_LEVELS - 1); +} + +test "avs_level_from_voltage_clamp_low" { + try std.testing.expect(avs_level_from_voltage(700) == 0); +} + +test "avs_level_from_voltage_clamp_high" { + try std.testing.expect(avs_level_from_voltage(1500) == VOLTAGE_LEVELS - 1); +} + +test "avs_control_code_from_level_zero" { + try std.testing.expect(avs_control_code_from_level(0) == 0); +} + +test "avs_control_code_from_level_max" { + given code = avs_control_code_from_level(63) + try std.testing.expect(code == 63); +} + +test "avs_control_code_from_level_clamp" { + given code = avs_control_code_from_level(100) + try std.testing.expect(code == 36 // 100 & 0x3F); +} + +test "avs_level_from_control_code_roundtrip" { + given level = 20 + try std.testing.expect(code = avs_control_code_from_level(level)); + try std.testing.expect(decoded = avs_level_from_control_code(code)); + try std.testing.expect(decoded == level); +} + +test "avs_voltage_valid_in_range" { + try std.testing.expect(avs_voltage_valid(900) == true); + try std.testing.expect(avs_voltage_valid(1000) == true); + try std.testing.expect(avs_voltage_valid(1100) == true); +} + +test "avs_voltage_valid_out_of_range" { + try std.testing.expect(avs_voltage_valid(700) == false); + try std.testing.expect(avs_voltage_valid(1300) == false); +} + +test "avs_voltage_in_hysteresis_true" { + try std.testing.expect(avs_voltage_in_hysteresis(1000, 1000, 20) == true); + try std.testing.expect(avs_voltage_in_hysteresis(1015, 1000, 20) == true); + try std.testing.expect(avs_voltage_in_hysteresis(985, 1000, 20) == true); +} + +test "avs_voltage_in_hysteresis_false" { + try std.testing.expect(avs_voltage_in_hysteresis(1050, 1000, 20) == false); + try std.testing.expect(avs_voltage_in_hysteresis(950, 1000, 20) == false); +} + +test "avs_should_adjust_false_in_hysteresis" { + try std.testing.expect(avs_should_adjust(1010, 1000, 20) == false); +} + +test "avs_should_adjust_true_out_of_hysteresis" { + try std.testing.expect(avs_should_adjust(1050, 1000, 20) == true); +} + +test "avs_config_init_structure" { + given config = avs_config_init() + try std.testing.expect(config.mode == AvsMode.automatic); + try std.testing.expect(config.target_mv == TARGET_MV); + try std.testing.expect(config.hysteresis_mv == HYSTERESIS_MV); +} + +test "avs_config_manual_structure" { + given config = avs_config_manual(900) + try std.testing.expect(config.mode == AvsMode.manual); + try std.testing.expect(config.target_mv == 900); +} + +test "avs_config_adaptive_structure" { + given config = avs_config_adaptive(850, 1150) + try std.testing.expect(config.mode == AvsMode.adaptive); + try std.testing.expect(config.min_mv == 850); + try std.testing.expect(config.max_mv == 1150); +} + +test "avs_status_init_structure" { + given status = avs_status_init() + try std.testing.expect(status.state == AvsState.disabled); + try std.testing.expect(status.current_mv == VOLTAGE_MIN_MV); +} + +test "avs_status_enable" { + given status = avs_status_init() + try std.testing.expect(config = avs_config_init()); + try std.testing.expect(result = avs_status_enable(status, config)); + try std.testing.expect(result.state == AvsState.enabled); + try std.testing.expect(result.mode == AvsMode.automatic); +} + +test "avs_status_adjust" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_adjust(status, 10)); + try std.testing.expect(result.state == AvsState.adjusting); + try std.testing.expect(result.level == 10); +} + +test "avs_status_settled" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_settled(status, 1000)); + try std.testing.expect(result.state == AvsState.settled); + try std.testing.expect(result.current_mv == 1000); +} + +test "avs_status_fault" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_fault(status, 1)); + try std.testing.expect(result.state == AvsState.fault); + try std.testing.expect(result.fault_code == 1); +} + +test "avs_control_pins_start" { + try std.testing.expect(avs_control_pins_start() == CONTROL_PIN_OFFSET); +} + +test "avs_control_pins_end" { + try std.testing.expect(avs_control_pins_end() == CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT - 1); +} + +test "avs_is_control_pin_true" { + try std.testing.expect(avs_is_control_pin(16) == true); + try std.testing.expect(avs_is_control_pin(21) == true); +} + +test "avs_is_control_pin_false" { + try std.testing.expect(avs_is_control_pin(15) == false); + try std.testing.expect(avs_is_control_pin(22) == false); +} + +test "avs_control_pin_for_bit" { + try std.testing.expect(avs_control_pin_for_bit(0) == CONTROL_PIN_OFFSET); + try std.testing.expect(avs_control_pin_for_bit(5) == CONTROL_PIN_OFFSET + 5); +} + +test "avs_control_pin_for_bit_clamp" { + try std.testing.expect(avs_control_pin_for_bit(10) == CONTROL_PIN_OFFSET); +} + +test "encode_avs_48_cmd" { + given encoded = encode_avs_48_cmd(0x10, 20, 0) + try std.testing.expect((encoded >> 8) == 0x10); +} + +test "decode_avs_48_cmd" { + given decoded = decode_avs_48_cmd(0x1050) + try std.testing.expect(decoded.opcode == 0x10); + try std.testing.expect(decoded.level == 20); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant voltage_range_positive + assert VOLTAGE_MIN_MV > 0 and VOLTAGE_MAX_MV > VOLTAGE_MIN_MV + +invariant voltage_step_positive + assert VOLTAGE_STEP_MV > 0 + +invariant voltage_levels_calculated + try std.testing.expect(VOLTAGE_LEVELS == ((VOLTAGE_MAX_MV - VOLTAGE_MIN_MV) / VOLTAGE_STEP_MV) + 1); + +invariant target_voltage_in_range + assert avs_voltage_valid(TARGET_MV) + +invariant hysteresis_less_than_step + assert HYSTERESIS_MV < VOLTAGE_STEP_MV * 3 + +invariant control_pins_valid_range + assert CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT <= PINS_48 + +invariant control_pin_count_six + assert CONTROL_PIN_COUNT == 6 + +invariant avs_voltage_from_level_roundtrip + given level = 15 + try std.testing.expect(voltage = avs_voltage_from_level(level)); + try std.testing.expect(result_level = avs_level_from_voltage(voltage)); + try std.testing.expect(result_level == level); + +invariant avs_voltage_in_hysteresis_exact + try std.testing.expect(avs_voltage_in_hysteresis(TARGET_MV, TARGET_MV, 0) == true); + +invariant avs_should_adjust_false_at_exact_target + try std.testing.expect(avs_should_adjust(TARGET_MV, TARGET_MV, HYSTERESIS_MV) == false); + +invariant avs_status_init_disabled + given status = avs_status_init() + try std.testing.expect(status.state == AvsState.disabled); + +invariant avs_status_enable_sets_mode + given status = avs_status_init() + try std.testing.expect(config = avs_config_manual(950)); + try std.testing.expect(result = avs_status_enable(status, config)); + try std.testing.expect(result.mode == AvsMode.manual); + +invariant avs_status_adjust_changes_level + given status = avs_status_init() + try std.testing.expect(result = avs_status_adjust(status, 25)); + try std.testing.expect(result.level == 25); + +invariant avs_is_control_pin_monotonic + try std.testing.expect(avs_control_pin_for_bit(3) > avs_control_pin_for_bit(2)); + +invariant avs_control_pin_for_bit_start_at_offset + try std.testing.expect(avs_control_pin_for_bit(0) == CONTROL_PIN_OFFSET); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench avs_voltage_from_level_latency + measure: nanoseconds to avs_voltage_from_level(20) + target: < 20ns + +bench avs_level_from_voltage_latency + measure: nanoseconds to avs_level_from_voltage(1000) + target: < 20ns + +bench avs_control_code_from_level_latency + measure: nanoseconds to avs_control_code_from_level(20) + target: < 15ns + +bench avs_voltage_valid_latency + measure: nanoseconds to avs_voltage_valid(1000) + target: < 15ns + +bench avs_voltage_in_hysteresis_latency + measure: nanoseconds to avs_voltage_in_hysteresis(1000, 1000, 20) + target: < 25ns + +bench avs_config_init_latency + measure: nanoseconds to avs_config_init() + target: < 30ns + +bench avs_status_init_latency + measure: nanoseconds to avs_status_init() + target: < 30ns + +bench avs_status_adjust_latency + measure: nanoseconds to avs_status_adjust(avs_status_init(), 20) + target: < 30ns + +bench avs_is_control_pin_latency + measure: nanoseconds to avs_is_control_pin(18) + target: < 15ns + +bench encode_avs_48_cmd_latency + measure: nanoseconds to encode_avs_48_cmd(0x10, 20, 0) + target: < 20ns + +bench decode_avs_48_cmd_latency + measure: nanoseconds to decode_avs_48_cmd(0x1050) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/avs_controller_96.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/avs_controller_96.t27 new file mode 100644 index 0000000000..16b47c6bdc --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/avs_controller_96.t27 @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: Apache-2.0 +; avs_controller_96.t27 — 96-pin AVS Controller +; Adaptive voltage scaling controller for 96-pin configuration +; φ² + 1/φ² = 3 | TRINITY + +module avs-controller-96; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const PINS_96 : u8 = 96; +pub const VOLTAGE_MIN_MV : u16 = 700; // 0.7V minimum +pub const VOLTAGE_MAX_MV : u16 = 1300; // 1.3V maximum +pub const VOLTAGE_STEP_MV : u16 = 5; // 5mV step size +pub const VOLTAGE_LEVELS : u8 = 121; // (1300-700)/5 + 1 + +pub const TARGET_MV : u16 = 1000; // 1.0V target +pub const HYSTERESIS_MV : u16 = 15; // 15mV hysteresis +pub const SETTLING_US : u16 = 80; // 80us settling time + +pub const CONTROL_PIN_OFFSET : u8 = 64; // AVS control pins start at 64 +pub const CONTROL_PIN_COUNT : u8 = 7; // 7 control pins for 96-pin config + +pub const FREQ_MIN_KHZ : u32 = 10000; // 10 MHz minimum +pub const FREQ_MAX_KHZ : u32 = 200000; // 200 MHz maximum + +pub const DUAL_CHANNEL : bool = true; // 96-pin supports dual channels +pub const CHANNEL_PIN_GROUPS : u8 = 2; // 2 groups of control pins + +// ============================================================================ +// Types +// ============================================================================ + +pub const AvsChannel = enum(u8) { + channel0 = 0, + channel1 = 1, + both = 2, +} + +pub const AvsState = enum(u8) { + disabled = 0, + enabled = 1, + adjusting = 2, + settled = 3, + fault = 4, + calibrating = 5, // 96-pin supports calibration +} + +pub const AvsMode = enum(u8) { + manual = 0, + automatic = 1, + adaptive = 2, + dual_independent = 3, // 96-pin exclusive mode +} + +pub const AvsChannelConfig = struct { + channel : AvsChannel, + enabled : bool, + target_mv : u16, + current_mv : u16, + level : u8, +} + +pub const AvsPinConfig = struct { + pin_index : u8, + is_output : bool, + function : u8, // 0=control, 1=feedback, 2=enable, 3=calibration +} + +pub const AvsVoltageLevel = struct { + level : u8, // 0 to 120 + voltage_mv : u16, + control_code : u8, // 7-bit code for control pins +} + +pub const AvsStatus = struct { + state : AvsState, + mode : AvsMode, + channel0 : AvsChannelConfig, + channel1 : AvsChannelConfig, + fault_code : u8, + calibration_complete : bool, +} + +pub const AvsConfig = struct { + mode : AvsMode, + min_mv : u16, + max_mv : u16, + target_mv : u16, + hysteresis_mv : u16, + settling_us : u16, + enable_dual_channel : bool, +} + +// ============================================================================ +// Voltage Level Functions +// ============================================================================ + +// avs_voltage_from_level(level: u8) -> u16 +// Convert voltage level to millivolts +pub fn avs_voltage_from_level(level: u8) -> u16 { + return VOLTAGE_MIN_MV + @as(u16, level) * VOLTAGE_STEP_MV; +} + +// avs_level_from_voltage(voltage_mv: u16) -> u8 +// Convert voltage to level +pub fn avs_level_from_voltage(voltage_mv: u16) -> u8 { + var mv = voltage_mv; + if (mv < VOLTAGE_MIN_MV) { + mv = VOLTAGE_MIN_MV; + } else if (mv > VOLTAGE_MAX_MV) { + mv = VOLTAGE_MAX_MV; + } + return @as(u8, (mv - VOLTAGE_MIN_MV) / VOLTAGE_STEP_MV); +} + +// avs_control_code_from_level(level: u8) -> u8 +// Generate 7-bit control code for voltage level +pub fn avs_control_code_from_level(level: u8) -> u8 { + return level & 0x7F; +} + +// avs_level_from_control_code(code: u8) -> u8 +// Decode level from 7-bit control code +pub fn avs_level_from_control_code(code: u8) -> u8 { + return code & 0x7F; +} + +// ============================================================================ +// Voltage Validation +// ============================================================================ + +// avs_voltage_valid(voltage_mv: u16) -> bool +// Check if voltage is within valid range +pub fn avs_voltage_valid(voltage_mv: u16) -> bool { + return voltage_mv >= VOLTAGE_MIN_MV and voltage_mv <= VOLTAGE_MAX_MV; +} + +// avs_voltage_in_hysteresis(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool +// Check if current voltage is within hysteresis band of target +pub fn avs_voltage_in_hysteresis(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool { + if (current_mv > target_mv) { + return (current_mv - target_mv) <= hysteresis_mv; + } else { + return (target_mv - current_mv) <= hysteresis_mv; + } +} + +// avs_should_adjust(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool +// Check if voltage should be adjusted +pub fn avs_should_adjust(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool { + return not avs_voltage_in_hysteresis(current_mv, target_mv, hysteresis_mv); +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// avs_config_init() -> AvsConfig +// Initialize AVS configuration +pub fn avs_config_init() -> AvsConfig { + return AvsConfig { + .mode = AvsMode.automatic, + .min_mv = VOLTAGE_MIN_MV, + .max_mv = VOLTAGE_MAX_MV, + .target_mv = TARGET_MV, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + .enable_dual_channel = false, + }; +} + +// avs_config_dual(target0_mv: u16, target1_mv: u16) -> AvsConfig +// Create dual-channel AVS configuration +pub fn avs_config_dual(target0_mv: u16, target1_mv: u16) -> AvsConfig { + return AvsConfig { + .mode = AvsMode.dual_independent, + .min_mv = VOLTAGE_MIN_MV, + .max_mv = VOLTAGE_MAX_MV, + .target_mv = (target0_mv + target1_mv) / 2, // Average target + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + .enable_dual_channel = true, + }; +} + +// avs_config_adaptive(min_mv: u16, max_mv: u16) -> AvsConfig +// Create adaptive AVS configuration +pub fn avs_config_adaptive(min_mv: u16, max_mv: u16) -> AvsConfig { + return AvsConfig { + .mode = AvsMode.adaptive, + .min_mv = min_mv, + .max_mv = max_mv, + .target_mv = (min_mv + max_mv) / 2, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + .enable_dual_channel = false, + }; +} + +// ============================================================================ +// Channel Functions +// ============================================================================ + +// avs_channel_init(channel: AvsChannel) -> AvsChannelConfig +// Initialize channel configuration +pub fn avs_channel_init(channel: AvsChannel) -> AvsChannelConfig { + return AvsChannelConfig { + .channel = channel, + .enabled = false, + .target_mv = TARGET_MV, + .current_mv = VOLTAGE_MIN_MV, + .level = 0, + }; +} + +// avs_channel_enable(channel: AvsChannelConfig, target_mv: u16) -> AvsChannelConfig +// Enable channel +pub fn avs_channel_enable(channel: AvsChannelConfig, target_mv: u16) -> AvsChannelConfig { + return AvsChannelConfig { + .channel = channel.channel, + .enabled = true, + .target_mv = target_mv, + .current_mv = channel.current_mv, + .level = channel.level, + }; +} + +// avs_channel_adjust(channel: AvsChannelConfig, new_level: u8) -> AvsChannelConfig +// Adjust channel to new level +pub fn avs_channel_adjust(channel: AvsChannelConfig, new_level: u8) -> AvsChannelConfig { + const new_mv = avs_voltage_from_level(new_level); + return AvsChannelConfig { + .channel = channel.channel, + .enabled = channel.enabled, + .target_mv = channel.target_mv, + .current_mv = new_mv, + .level = new_level, + }; +} + +// ============================================================================ +// Status Functions +// ============================================================================ + +// avs_status_init() -> AvsStatus +// Initialize AVS status +pub fn avs_status_init() -> AvsStatus { + return AvsStatus { + .state = AvsState.disabled, + .mode = AvsMode.manual, + .channel0 = avs_channel_init(AvsChannel.channel0), + .channel1 = avs_channel_init(AvsChannel.channel1), + .fault_code = 0, + .calibration_complete = false, + }; +} + +// avs_status_enable(status: AvsStatus, config: AvsConfig) -> AvsStatus +// Enable AVS controller +pub fn avs_status_enable(status: AvsStatus, config: AvsConfig) -> AvsStatus { + var ch0 = status.channel0; + var ch1 = status.channel1; + + if (config.enable_dual_channel) { + ch0 = avs_channel_enable(ch0, config.target_mv); + ch1 = avs_channel_enable(ch1, config.target_mv); + } else { + ch0 = avs_channel_enable(ch0, config.target_mv); + } + + return AvsStatus { + .state = AvsState.enabled, + .mode = config.mode, + .channel0 = ch0, + .channel1 = ch1, + .fault_code = 0, + .calibration_complete = status.calibration_complete, + }; +} + +// avs_status_calibrate(status: AvsStatus) -> AvsStatus +// Enter calibration mode +pub fn avs_status_calibrate(status: AvsStatus) -> AvsStatus { + return AvsStatus { + .state = AvsState.calibrating, + .mode = status.mode, + .channel0 = status.channel0, + .channel1 = status.channel1, + .fault_code = status.fault_code, + .calibration_complete = false, + }; +} + +// avs_status_calibration_complete(status: AvsStatus) -> AvsStatus +// Mark calibration complete +pub fn avs_status_calibration_complete(status: AvsStatus) -> AvsStatus { + return AvsStatus { + .state = AvsState.enabled, + .mode = status.mode, + .channel0 = status.channel0, + .channel1 = status.channel1, + .fault_code = status.fault_code, + .calibration_complete = true, + }; +} + +// avs_status_fault(status: AvsStatus, fault_code: u8) -> AvsStatus +// Mark fault condition +pub fn avs_status_fault(status: AvsStatus, fault_code: u8) -> AvsStatus { + return AvsStatus { + .state = AvsState.fault, + .mode = status.mode, + .channel0 = status.channel0, + .channel1 = status.channel1, + .fault_code = fault_code, + .calibration_complete = status.calibration_complete, + }; +} + +// ============================================================================ +// Pin Functions +// ============================================================================ + +// avs_control_pins_start() -> u8 +// Get starting pin index for control pins +pub fn avs_control_pins_start() -> u8 { + return CONTROL_PIN_OFFSET; +} + +// avs_control_pins_end() -> u8 +// Get ending pin index for control pins (primary) +pub fn avs_control_pins_end() -> u8 { + return CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT - 1; +} + +// avs_control_pins_secondary_start() -> u8 +// Get starting pin index for secondary channel +pub fn avs_control_pins_secondary_start() -> u8 { + return CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT; +} + +// avs_control_pins_secondary_end() -> u8 +// Get ending pin index for secondary channel +pub fn avs_control_pins_secondary_end() -> u8 { + return avs_control_pins_secondary_start() + CONTROL_PIN_COUNT - 1; +} + +// avs_is_control_pin(pin_index: u8) -> bool +// Check if pin is a control pin (either channel) +pub fn avs_is_control_pin(pin_index: u8) -> bool { + return avs_is_control_pin_primary(pin_index) or avs_is_control_pin_secondary(pin_index); +} + +// avs_is_control_pin_primary(pin_index: u8) -> bool +// Check if pin is a primary control pin +pub fn avs_is_control_pin_primary(pin_index: u8) -> bool { + return pin_index >= avs_control_pins_start() and pin_index <= avs_control_pins_end(); +} + +// avs_is_control_pin_secondary(pin_index: u8) -> bool +// Check if pin is a secondary control pin +pub fn avs_is_control_pin_secondary(pin_index: u8) -> bool { + return pin_index >= avs_control_pins_secondary_start() and pin_index <= avs_control_pins_secondary_end(); +} + +// avs_control_pin_for_bit(bit_index: u8, channel: AvsChannel) -> u8 +// Get control pin for a bit index and channel +pub fn avs_control_pin_for_bit(bit_index: u8, channel: AvsChannel) -> u8 { + if (bit_index >= CONTROL_PIN_COUNT) { + bit_index = 0; + } + + if (channel == AvsChannel.channel1) { + return avs_control_pins_secondary_start() + bit_index; + } else { + return avs_control_pins_start() + bit_index; + } +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_avs_96_cmd(opcode: u8, level: u8, channel: u8) -> u16 +// Encode AVS 96-pin command +pub fn encode_avs_96_cmd(opcode: u8, level: u8, channel: u8) u16 { + // Format: [OP:8][LEVEL:7][CHANNEL:1] + const op_field : u16 = @as(u16, opcode) << 8; + const level_field : u16 = @as(u16, level & 0x7F) << 1; + const channel_field : u16 = @as(u16, channel & 0x01); + return op_field | level_field | channel_field; +} + +// decode_avs_96_cmd(encoded: u16) -> struct { opcode: u8, level: u8, channel: u8 } +// Decode AVS 96-pin command +pub fn decode_avs_96_cmd(encoded: u16) -> struct { opcode: u8, level: u8, channel: u8 } { + const opcode : u8 = @as(u8, @truncate((encoded >> 8) & 0xFF)); + const level : u8 = @as(u8, @truncate((encoded >> 1) & 0x7F)); + const channel : u8 = @as(u8, @truncate(encoded & 0x01)); + return .{ .opcode = opcode, .level = level, .channel = channel }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "pins_96_constant" { + try std.testing.expect(PINS_96 == 96); +} + +test "voltage_constants" { + try std.testing.expect(VOLTAGE_MIN_MV == 700); + try std.testing.expect(VOLTAGE_MAX_MV == 1300); + try std.testing.expect(VOLTAGE_STEP_MV == 5); + try std.testing.expect(VOLTAGE_LEVELS == 121); +} + +test "target_voltage_constant" { + try std.testing.expect(TARGET_MV == 1000); +} + +test "control_pin_constants" { + try std.testing.expect(CONTROL_PIN_OFFSET == 64); + try std.testing.expect(CONTROL_PIN_COUNT == 7); +} + +test "dual_channel_constant" { + assert DUAL_CHANNEL == true +} + +test "avs_voltage_from_level_zero" { + try std.testing.expect(avs_voltage_from_level(0) == VOLTAGE_MIN_MV); +} + +test "avs_voltage_from_level_max" { + given max_level = VOLTAGE_LEVELS - 1 + try std.testing.expect(avs_voltage_from_level(max_level) == VOLTAGE_MAX_MV); +} + +test "avs_level_from_voltage_target" { + given level = avs_level_from_voltage(TARGET_MV) + try std.testing.expect(avs_voltage_from_level(level) == TARGET_MV); +} + +test "avs_level_from_voltage_clamp_low" { + try std.testing.expect(avs_level_from_voltage(600) == 0); +} + +test "avs_level_from_voltage_clamp_high" { + try std.testing.expect(avs_level_from_voltage(1500) == VOLTAGE_LEVELS - 1); +} + +test "avs_control_code_from_level_max" { + given code = avs_control_code_from_level(127) + try std.testing.expect(code == 127); +} + +test "avs_control_code_from_level_clamp" { + given code = avs_control_code_from_level(200) + try std.testing.expect(code == 72 // 200 & 0x7F); +} + +test "avs_level_from_control_code_roundtrip" { + given level = 50 + try std.testing.expect(code = avs_control_code_from_level(level)); + try std.testing.expect(decoded = avs_level_from_control_code(code)); + try std.testing.expect(decoded == level); +} + +test "avs_voltage_valid_in_range" { + try std.testing.expect(avs_voltage_valid(750) == true); + try std.testing.expect(avs_voltage_valid(1000) == true); + try std.testing.expect(avs_voltage_valid(1250) == true); +} + +test "avs_voltage_valid_out_of_range" { + try std.testing.expect(avs_voltage_valid(600) == false); + try std.testing.expect(avs_voltage_valid(1400) == false); +} + +test "avs_voltage_in_hysteresis_true" { + try std.testing.expect(avs_voltage_in_hysteresis(1000, 1000, 15) == true); + try std.testing.expect(avs_voltage_in_hysteresis(1010, 1000, 15) == true); + try std.testing.expect(avs_voltage_in_hysteresis(990, 1000, 15) == true); +} + +test "avs_voltage_in_hysteresis_false" { + try std.testing.expect(avs_voltage_in_hysteresis(1030, 1000, 15) == false); + try std.testing.expect(avs_voltage_in_hysteresis(970, 1000, 15) == false); +} + +test "avs_config_init_structure" { + given config = avs_config_init() + try std.testing.expect(config.mode == AvsMode.automatic); + try std.testing.expect(config.target_mv == TARGET_MV); + try std.testing.expect(config.enable_dual_channel == false); +} + +test "avs_config_dual_structure" { + given config = avs_config_dual(950, 1050) + try std.testing.expect(config.mode == AvsMode.dual_independent); + try std.testing.expect(config.enable_dual_channel == true); +} + +test "avs_channel_init_structure" { + given channel = avs_channel_init(AvsChannel.channel0) + try std.testing.expect(channel.channel == AvsChannel.channel0); + try std.testing.expect(channel.enabled == false); +} + +test "avs_channel_enable" { + given channel = avs_channel_init(AvsChannel.channel0) + try std.testing.expect(result = avs_channel_enable(channel, 950)); + try std.testing.expect(result.enabled == true); + try std.testing.expect(result.target_mv == 950); +} + +test "avs_channel_adjust" { + given channel = avs_channel_init(AvsChannel.channel0) + try std.testing.expect(result = avs_channel_adjust(channel, 30)); + try std.testing.expect(result.level == 30); +} + +test "avs_status_init_structure" { + given status = avs_status_init() + try std.testing.expect(status.state == AvsState.disabled); + try std.testing.expect(status.calibration_complete == false); +} + +test "avs_status_enable" { + given status = avs_status_init() + try std.testing.expect(config = avs_config_init()); + try std.testing.expect(result = avs_status_enable(status, config)); + try std.testing.expect(result.state == AvsState.enabled); + try std.testing.expect(result.channel0.enabled == true); +} + +test "avs_status_enable_dual" { + given status = avs_status_init() + try std.testing.expect(config = avs_config_dual(950, 1050)); + try std.testing.expect(result = avs_status_enable(status, config)); + try std.testing.expect(result.channel0.enabled == true); + try std.testing.expect(result.channel1.enabled == true); +} + +test "avs_status_calibrate" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_calibrate(status)); + try std.testing.expect(result.state == AvsState.calibrating); + try std.testing.expect(result.calibration_complete == false); +} + +test "avs_status_calibration_complete" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_calibration_complete(status)); + try std.testing.expect(result.calibration_complete == true); +} + +test "avs_control_pins_start" { + try std.testing.expect(avs_control_pins_start() == CONTROL_PIN_OFFSET); +} + +test "avs_control_pins_end" { + try std.testing.expect(avs_control_pins_end() == CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT - 1); +} + +test "avs_control_pins_secondary_start" { + try std.testing.expect(avs_control_pins_secondary_start() == CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT); +} + +test "avs_is_control_pin_primary_true" { + try std.testing.expect(avs_is_control_pin_primary(64) == true); + try std.testing.expect(avs_is_control_pin_primary(70) == true); +} + +test "avs_is_control_pin_primary_false" { + try std.testing.expect(avs_is_control_pin_primary(63) == false); + try std.testing.expect(avs_is_control_pin_primary(71) == false); +} + +test "avs_is_control_pin_secondary_true" { + try std.testing.expect(avs_is_control_pin_secondary(71) == true); + try std.testing.expect(avs_is_control_pin_secondary(77) == true); +} + +test "avs_is_control_pin_secondary_false" { + try std.testing.expect(avs_is_control_pin_secondary(70) == false); + try std.testing.expect(avs_is_control_pin_secondary(78) == false); +} + +test "avs_is_control_pin_true" { + try std.testing.expect(avs_is_control_pin(64) == true); + try std.testing.expect(avs_is_control_pin(75) == true); +} + +test "avs_is_control_pin_false" { + try std.testing.expect(avs_is_control_pin(63) == false); + try std.testing.expect(avs_is_control_pin(78) == false); +} + +test "avs_control_pin_for_bit_primary" { + try std.testing.expect(avs_control_pin_for_bit(0, AvsChannel.channel0) == CONTROL_PIN_OFFSET); + try std.testing.expect(avs_control_pin_for_bit(6, AvsChannel.channel0) == CONTROL_PIN_OFFSET + 6); +} + +test "avs_control_pin_for_bit_secondary" { + try std.testing.expect(avs_control_pin_for_bit(0, AvsChannel.channel1) == avs_control_pins_secondary_start()); + try std.testing.expect(avs_control_pin_for_bit(6, AvsChannel.channel1) == avs_control_pins_secondary_start() + 6); +} + +test "encode_avs_96_cmd" { + given encoded = encode_avs_96_cmd(0x20, 30, 1) + try std.testing.expect((encoded >> 8) == 0x20); +} + +test "decode_avs_96_cmd" { + given decoded = decode_avs_96_cmd(0x203D) + try std.testing.expect(decoded.opcode == 0x20); + try std.testing.expect(decoded.level == 30); + try std.testing.expect(decoded.channel == 1); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant voltage_range_positive + assert VOLTAGE_MIN_MV > 0 and VOLTAGE_MAX_MV > VOLTAGE_MIN_MV + +invariant voltage_step_positive + assert VOLTAGE_STEP_MV > 0 + +invariant voltage_levels_calculated + try std.testing.expect(VOLTAGE_LEVELS == ((VOLTAGE_MAX_MV - VOLTAGE_MIN_MV) / VOLTAGE_STEP_MV) + 1); + +invariant target_voltage_in_range + assert avs_voltage_valid(TARGET_MV) + +invariant control_pin_count_seven + assert CONTROL_PIN_COUNT == 7 + +invariant control_pins_valid_range + assert avs_control_pins_secondary_end() < PINS_96 + +invariant dual_channel_enabled + assert DUAL_CHANNEL == true + +invariant avs_status_init_disabled + given status = avs_status_init() + try std.testing.expect(status.state == AvsState.disabled); + +invariant avs_status_calibrate_sets_state + given status = avs_status_init() + try std.testing.expect(result = avs_status_calibrate(status)); + try std.testing.expect(result.state == AvsState.calibrating); + +invariant avs_is_control_pin_primary_excludes_secondary + given pin = 70 // Last primary pin + assert avs_is_control_pin_primary(pin) == true + try std.testing.expect(avs_is_control_pin_secondary(pin) == false); + +invariant avs_is_control_pin_secondary_excludes_primary + given pin = 71 // First secondary pin + assert avs_is_control_pin_secondary(pin) == true + try std.testing.expect(avs_is_control_pin_primary(pin) == false); + +invariant avs_control_pin_for_bit_monotonic + try std.testing.expect(avs_control_pin_for_bit(3, AvsChannel.channel0) < avs_control_pin_for_bit(4, AvsChannel.channel0)); + +invariant avs_config_dual_enables_both_channels + given config = avs_config_dual(950, 1050) + assert config.enable_dual_channel == true + +invariant avs_config_init_single_channel + given config = avs_config_init() + assert config.enable_dual_channel == false + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench avs_voltage_from_level_latency + measure: nanoseconds to avs_voltage_from_level(40) + target: < 20ns + +bench avs_level_from_voltage_latency + measure: nanoseconds to avs_level_from_voltage(1000) + target: < 20ns + +bench avs_control_code_from_level_latency + measure: nanoseconds to avs_control_code_from_level(40) + target: < 15ns + +bench avs_voltage_valid_latency + measure: nanoseconds to avs_voltage_valid(1000) + target: < 15ns + +bench avs_config_init_latency + measure: nanoseconds to avs_config_init() + target: < 30ns + +bench avs_channel_init_latency + measure: nanoseconds to avs_channel_init(AvsChannel.channel0) + target: < 20ns + +bench avs_status_init_latency + measure: nanoseconds to avs_status_init() + target: < 30ns + +bench avs_status_enable_latency + measure: nanoseconds to avs_status_enable(avs_status_init(), avs_config_init()) + target: < 40ns + +bench avs_status_calibrate_latency + measure: nanoseconds to avs_status_calibrate(avs_status_init()) + target: < 20ns + +bench avs_is_control_pin_latency + measure: nanoseconds to avs_is_control_pin(70) + target: < 15ns + +bench avs_control_pin_for_bit_latency + measure: nanoseconds to avs_control_pin_for_bit(3, AvsChannel.channel0) + target: < 20ns + +bench encode_avs_96_cmd_latency + measure: nanoseconds to encode_avs_96_cmd(0x20, 30, 1) + target: < 20ns + +bench decode_avs_96_cmd_latency + measure: nanoseconds to decode_avs_96_cmd(0x203D) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/avs_reconf.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/avs_reconf.t27 new file mode 100644 index 0000000000..61d90cf865 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/avs_reconf.t27 @@ -0,0 +1,702 @@ +// SPDX-License-Identifier: Apache-2.0 +; avs_reconf.t27 — AVS Reconfiguration Module +; Dynamic AVS voltage/frequency reconfiguration support +; φ² + 1/φ² = 3 | TRINITY + +module avs-reconf; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const RECONF_OPCODE_BASE : u8 = 0xF0; // AVS reconf opcodes start at 0xF0 + +pub const RECONF_OP_SET_VOLTAGE : u8 = 0x00; +pub const RECONF_OP_SET_FREQUENCY : u8 = 0x01; +pub const RECONF_OP_SET_MODE : u8 = 0x02; +pub const RECONF_OP_RESET : u8 = 0x03; +pub const RECONF_OP_QUERY : u8 = 0x04; +pub const RECONF_OP_SAVE_CONFIG : u8 = 0x05; +pub const RECONF_OP_LOAD_CONFIG : u8 = 0x06; +pub const RECONF_OP_CALIBRATE : u8 = 0x07; +pub const RECONF_OP_MAX : u8 = 0x08; + +pub const RECONF_MODE_MANUAL : u8 = 0; +pub const RECONF_MODE_AUTOMATIC : u8 = 1; +pub const RECONF_MODE_PERFORMANCE : u8 = 2; +pub const RECONF_MODE_POWER_SAVING : u8 = 3; +pub const RECONF_MODE_ADAPTIVE : u8 = 4; +pub const RECONF_MODE_MAX : u8 = 5; + +pub const RECONF_STATE_IDLE : u8 = 0; +pub const RECONF_STATE_BUSY : u8 = 1; +pub const RECONF_STATE_ERROR : u8 = 2; +pub const RECONF_STATE_CALIBRATING : u8 = 3; + +pub const VOLTAGE_MIN_MV : u16 = 700; +pub const VOLTAGE_MAX_MV : u16 = 1300; +pub const FREQ_MIN_KHZ : u32 = 10000; +pub const FREQ_MAX_KHZ : u32 = 200000; + +pub const RECONF_TIMEOUT_MS : u16 = 1000; +pub const RECONF_RETRY_COUNT : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const ReconfOp = enum(u8) { + set_voltage = RECONF_OP_SET_VOLTAGE, + set_frequency = RECONF_OP_SET_FREQUENCY, + set_mode = RECONF_OP_SET_MODE, + reset = RECONF_OP_RESET, + query = RECONF_OP_QUERY, + save_config = RECONF_OP_SAVE_CONFIG, + load_config = RECONF_OP_LOAD_CONFIG, + calibrate = RECONF_OP_CALIBRATE, +} + +pub const ReconfMode = enum(u8) { + manual = RECONF_MODE_MANUAL, + automatic = RECONF_MODE_AUTOMATIC, + performance = RECONF_MODE_PERFORMANCE, + power_saving = RECONF_MODE_POWER_SAVING, + adaptive = RECONF_MODE_ADAPTIVE, +} + +pub const ReconfState = enum(u8) { + idle = RECONF_STATE_IDLE, + busy = RECONF_STATE_BUSY, + error = RECONF_STATE_ERROR, + calibrating = RECONF_STATE_CALIBRATING, +} + +pub const ReconfConfig = struct { + mode : ReconfMode, + voltage_mv : u16, + freq_khz : u32, + auto_scale : bool, + min_voltage_mv : u16, + max_voltage_mv : u16, + min_freq_khz : u32, + max_freq_khz : u32, +} + +pub const ReconfStatus = struct { + state : ReconfState, + current_voltage_mv : u16, + current_freq_khz : u32, + last_op : ReconfOp, + error_code : u8, + retry_count : u8, +} + +pub const ReconfCommand = struct { + op : ReconfOp, + param1 : u32, + param2 : u32, + timeout_ms : u16, +} + +pub const ReconfResult = struct { + success : bool, + error_code : u8, + cycles_taken : u32, +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// avs_reconf_config_init() -> ReconfConfig +// Initialize AVS reconfiguration config +pub fn avs_reconf_config_init() -> ReconfConfig { + return ReconfConfig { + .mode = ReconfMode.manual, + .voltage_mv = 1000, + .freq_khz = 100000, + .auto_scale = false, + .min_voltage_mv = VOLTAGE_MIN_MV, + .max_voltage_mv = VOLTAGE_MAX_MV, + .min_freq_khz = FREQ_MIN_KHZ, + .max_freq_khz = FREQ_MAX_KHZ, + }; +} + +// avs_reconf_config_performance() -> ReconfConfig +// Create performance-oriented config +pub fn avs_reconf_config_performance() -> ReconfConfig { + return ReconfConfig { + .mode = ReconfMode.performance, + .voltage_mv = VOLTAGE_MAX_MV, + .freq_khz = FREQ_MAX_KHZ, + .auto_scale = true, + .min_voltage_mv = VOLTAGE_MIN_MV + 100, + .max_voltage_mv = VOLTAGE_MAX_MV, + .min_freq_khz = FREQ_MAX_KHZ / 2, + .max_freq_khz = FREQ_MAX_KHZ, + }; +} + +// avs_reconf_config_power_saving() -> ReconfConfig +// Create power-saving config +pub fn avs_reconf_config_power_saving() -> ReconfConfig { + return ReconfConfig { + .mode = ReconfMode.power_saving, + .voltage_mv = VOLTAGE_MIN_MV + 50, + .freq_khz = FREQ_MIN_KHZ * 2, + .auto_scale = true, + .min_voltage_mv = VOLTAGE_MIN_MV, + .max_voltage_mv = VOLTAGE_MIN_MV + 200, + .min_freq_khz = FREQ_MIN_KHZ, + .max_freq_khz = FREQ_MIN_KHZ * 5, + }; +} + +// avs_reconf_config_adaptive() -> ReconfConfig +// Create adaptive config +pub fn avs_reconf_config_adaptive() -> ReconfConfig { + return ReconfConfig { + .mode = ReconfMode.adaptive, + .voltage_mv = 1000, + .freq_khz = 100000, + .auto_scale = true, + .min_voltage_mv = VOLTAGE_MIN_MV, + .max_voltage_mv = VOLTAGE_MAX_MV, + .min_freq_khz = FREQ_MIN_KHZ, + .max_freq_khz = FREQ_MAX_KHZ, + }; +} + +// ============================================================================ +// Validation Functions +// ============================================================================ + +// avs_reconf_voltage_valid(voltage_mv: u16) -> bool +// Check if voltage is valid +pub fn avs_reconf_voltage_valid(voltage_mv: u16) -> bool { + return voltage_mv >= VOLTAGE_MIN_MV and voltage_mv <= VOLTAGE_MAX_MV; +} + +// avs_reconf_freq_valid(freq_khz: u32) -> bool +// Check if frequency is valid +pub fn avs_reconf_freq_valid(freq_khz: u32) -> bool { + return freq_khz >= FREQ_MIN_KHZ and freq_khz <= FREQ_MAX_KHZ; +} + +// avs_reconf_config_valid(config: ReconfConfig) -> bool +// Check if config is valid +pub fn avs_reconf_config_valid(config: ReconfConfig) -> bool { + return avs_reconf_voltage_valid(config.voltage_mv) + try std.testing.expect(avs_reconf_freq_valid(config.freq_khz)); + try std.testing.expect(config.min_voltage_mv < config.max_voltage_mv); + try std.testing.expect(config.min_freq_khz < config.max_freq_khz;); +} + +// ============================================================================ +// Status Functions +// ============================================================================ + +// avs_reconf_status_init() -> ReconfStatus +// Initialize reconfiguration status +pub fn avs_reconf_status_init() -> ReconfStatus { + return ReconfStatus { + .state = ReconfState.idle, + .current_voltage_mv = VOLTAGE_MIN_MV, + .current_freq_khz = FREQ_MIN_KHZ, + .last_op = ReconfOp.reset, + .error_code = 0, + .retry_count = 0, + }; +} + +// avs_reconf_status_start_op(status: ReconfStatus, op: ReconfOp) -> ReconfStatus +// Start a reconfiguration operation +pub fn avs_reconf_status_start_op(status: ReconfStatus, op: ReconfOp) -> ReconfStatus { + return ReconfStatus { + .state = ReconfState.busy, + .current_voltage_mv = status.current_voltage_mv, + .current_freq_khz = status.current_freq_khz, + .last_op = op, + .error_code = 0, + .retry_count = status.retry_count, + }; +} + +// avs_reconf_status_complete(status: ReconfStatus, result: ReconfResult) -> ReconfStatus +// Complete a reconfiguration operation +pub fn avs_reconf_status_complete(status: ReconfStatus, result: ReconfResult) -> ReconfStatus { + var new_state = if (result.success) ReconfState.idle else ReconfState.error; + var new_retry_count = if (result.success) 0 else status.retry_count + 1; + + return ReconfStatus { + .state = new_state, + .current_voltage_mv = status.current_voltage_mv, + .current_freq_khz = status.current_freq_khz, + .last_op = status.last_op, + .error_code = if (result.success) 0 else result.error_code, + .retry_count = new_retry_count, + }; +} + +// avs_reconf_status_update_voltage(status: ReconfStatus, voltage_mv: u16) -> ReconfStatus +// Update voltage in status +pub fn avs_reconf_status_update_voltage(status: ReconfStatus, voltage_mv: u16) -> ReconfStatus { + return ReconfStatus { + .state = status.state, + .current_voltage_mv = voltage_mv, + .current_freq_khz = status.current_freq_khz, + .last_op = status.last_op, + .error_code = status.error_code, + .retry_count = status.retry_count, + }; +} + +// avs_reconf_status_update_freq(status: ReconfStatus, freq_khz: u32) -> ReconfStatus +// Update frequency in status +pub fn avs_reconf_status_update_freq(status: ReconfStatus, freq_khz: u32) -> ReconfStatus { + return ReconfStatus { + .state = status.state, + .current_voltage_mv = status.current_voltage_mv, + .current_freq_khz = freq_khz, + .last_op = status.last_op, + .error_code = status.error_code, + .retry_count = status.retry_count, + }; +} + +// ============================================================================ +// Command Functions +// ============================================================================ + +// avs_reconf_command_init() -> ReconfCommand +// Initialize reconfiguration command +pub fn avs_reconf_command_init() -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.reset, + .param1 = 0, + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// avs_reconf_command_set_voltage(voltage_mv: u16) -> ReconfCommand +// Create voltage set command +pub fn avs_reconf_command_set_voltage(voltage_mv: u16) -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.set_voltage, + .param1 = @as(u32, voltage_mv), + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// avs_reconf_command_set_freq(freq_khz: u32) -> ReconfCommand +// Create frequency set command +pub fn avs_reconf_command_set_freq(freq_khz: u32) -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.set_frequency, + .param1 = freq_khz, + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// avs_reconf_command_set_mode(mode: ReconfMode) -> ReconfCommand +// Create mode set command +pub fn avs_reconf_command_set_mode(mode: ReconfMode) -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.set_mode, + .param1 = @as(u32, mode), + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// avs_reconf_command_calibrate() -> ReconfCommand +// Create calibration command +pub fn avs_reconf_command_calibrate() -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.calibrate, + .param1 = 0, + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS * 5, // Calibration takes longer + }; +} + +// avs_reconf_command_save_config(config: ReconfConfig) -> ReconfCommand +// Create save config command +pub fn avs_reconf_command_save_config(config: ReconfConfig) -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.save_config, + .param1 = @as(u32, config.voltage_mv), + .param2 = config.freq_khz, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// ============================================================================ +// Result Functions +// ============================================================================ + +// avs_reconf_result_success() -> ReconfResult +// Create success result +pub fn avs_reconf_result_success(cycles: u32) -> ReconfResult { + return ReconfResult { + .success = true, + .error_code = 0, + .cycles_taken = cycles, + }; +} + +// avs_reconf_result_error(error_code: u8, cycles: u32) -> ReconfResult +// Create error result +pub fn avs_reconf_result_error(error_code: u8, cycles: u32) -> ReconfResult { + return ReconfResult { + .success = false, + .error_code = error_code, + .cycles_taken = cycles, + }; +} + +// avs_reconf_should_retry(status: ReconfStatus) -> bool +// Check if should retry failed operation +pub fn avs_reconf_should_retry(status: ReconfStatus) -> bool { + return status.state == ReconfState.error and status.retry_count < RECONF_RETRY_COUNT; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_avs_reconf_cmd(op: u8, param1: u16, param2: u16) -> u32 +// Encode AVS reconfiguration command +pub fn encode_avs_reconf_cmd(op: u8, param1: u16, param2: u16) u32 { + // Format: [OP:8][PARAM1:12][PARAM2:12] + const op_field : u32 = @as(u32, op) << 24; + const param1_field : u32 = @as(u32, param1 & 0xFFF) << 12; + const param2_field : u32 = @as(u32, param2 & 0xFFF); + return op_field | param1_field | param2_field; +} + +// decode_avs_reconf_cmd(encoded: u32) -> struct { op: u8, param1: u16, param2: u16 } +// Decode AVS reconfiguration command +pub fn decode_avs_reconf_cmd(encoded: u32) -> struct { op: u8, param1: u16, param2: u16 } { + const op : u8 = @as(u8, @truncate((encoded >> 24) & 0xFF)); + const param1 : u16 = @as(u16, @truncate((encoded >> 12) & 0xFFF)); + const param2 : u16 = @as(u16, @truncate(encoded & 0xFFF)); + return .{ .op = op, .param1 = param1, .param2 = param2 }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "reconf_opcode_base" { + try std.testing.expect(RECONF_OPCODE_BASE == 0xF0); +} + +test "reconf_op_constants" { + try std.testing.expect(RECONF_OP_SET_VOLTAGE == 0); + try std.testing.expect(RECONF_OP_SET_FREQUENCY == 1); + try std.testing.expect(RECONF_OP_SET_MODE == 2); + try std.testing.expect(RECONF_OP_CALIBRATE == 7); +} + +test "reconf_mode_constants" { + try std.testing.expect(RECONF_MODE_MANUAL == 0); + try std.testing.expect(RECONF_MODE_PERFORMANCE == 2); + try std.testing.expect(RECONF_MODE_POWER_SAVING == 3); + try std.testing.expect(RECONF_MODE_ADAPTIVE == 4); +} + +test "reconf_state_constants" { + try std.testing.expect(RECONF_STATE_IDLE == 0); + try std.testing.expect(RECONF_STATE_BUSY == 1); + try std.testing.expect(RECONF_STATE_ERROR == 2); +} + +test "voltage_freq_constants" { + try std.testing.expect(VOLTAGE_MIN_MV == 700); + try std.testing.expect(VOLTAGE_MAX_MV == 1300); + try std.testing.expect(FREQ_MIN_KHZ == 10000); + try std.testing.expect(FREQ_MAX_KHZ == 200000); +} + +test "avs_reconf_config_init_structure" { + given config = avs_reconf_config_init() + try std.testing.expect(config.mode == ReconfMode.manual); + try std.testing.expect(config.voltage_mv == 1000); + try std.testing.expect(config.freq_khz == 100000); +} + +test "avs_reconf_config_performance_structure" { + given config = avs_reconf_config_performance() + try std.testing.expect(config.mode == ReconfMode.performance); + try std.testing.expect(config.voltage_mv == VOLTAGE_MAX_MV); + try std.testing.expect(config.auto_scale == true); +} + +test "avs_reconf_config_power_saving_structure" { + given config = avs_reconf_config_power_saving() + try std.testing.expect(config.mode == ReconfMode.power_saving); + try std.testing.expect(config.voltage_mv < 1000); + try std.testing.expect(config.auto_scale == true); +} + +test "avs_reconf_config_adaptive_structure" { + given config = avs_reconf_config_adaptive() + try std.testing.expect(config.mode == ReconfMode.adaptive); + try std.testing.expect(config.auto_scale == true); +} + +test "avs_reconf_voltage_valid_true" { + try std.testing.expect(avs_reconf_voltage_valid(800) == true); + try std.testing.expect(avs_reconf_voltage_valid(1000) == true); + try std.testing.expect(avs_reconf_voltage_valid(1200) == true); +} + +test "avs_reconf_voltage_valid_false" { + try std.testing.expect(avs_reconf_voltage_valid(600) == false); + try std.testing.expect(avs_reconf_voltage_valid(1400) == false); +} + +test "avs_reconf_freq_valid_true" { + try std.testing.expect(avs_reconf_freq_valid(20000) == true); + try std.testing.expect(avs_reconf_freq_valid(100000) == true); + try std.testing.expect(avs_reconf_freq_valid(150000) == true); +} + +test "avs_reconf_freq_valid_false" { + try std.testing.expect(avs_reconf_freq_valid(5000) == false); + try std.testing.expect(avs_reconf_freq_valid(300000) == false); +} + +test "avs_reconf_config_valid_true" { + given config = avs_reconf_config_init() + try std.testing.expect(avs_reconf_config_valid(config) == true); +} + +test "avs_reconf_config_valid_false" { + given config = ReconfConfig{.mode = ReconfMode.manual, .voltage_mv = 600, .freq_khz = 100000, .auto_scale = false, .min_voltage_mv = 700, .max_voltage_mv = 1300, .min_freq_khz = 10000, .max_freq_khz = 200000} + try std.testing.expect(avs_reconf_config_valid(config) == false); +} + +test "avs_reconf_status_init_structure" { + given status = avs_reconf_status_init() + try std.testing.expect(status.state == ReconfState.idle); + try std.testing.expect(status.current_voltage_mv == VOLTAGE_MIN_MV); +} + +test "avs_reconf_status_start_op" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_status_start_op(status, ReconfOp.set_voltage)); + try std.testing.expect(result.state == ReconfState.busy); + try std.testing.expect(result.last_op == ReconfOp.set_voltage); +} + +test "avs_reconf_status_complete_success" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_result_success(1000)); + try std.testing.expect(new_status = avs_reconf_status_complete(status, result)); + try std.testing.expect(new_status.state == ReconfState.idle); + try std.testing.expect(new_status.error_code == 0); +} + +test "avs_reconf_status_complete_error" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_result_error(1, 500)); + try std.testing.expect(new_status = avs_reconf_status_complete(status, result)); + try std.testing.expect(new_status.state == ReconfState.error); + try std.testing.expect(new_status.error_code == 1); + try std.testing.expect(new_status.retry_count == 1); +} + +test "avs_reconf_status_update_voltage" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_status_update_voltage(status, 1100)); + try std.testing.expect(result.current_voltage_mv == 1100); +} + +test "avs_reconf_status_update_freq" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_status_update_freq(status, 150000)); + try std.testing.expect(result.current_freq_khz == 150000); +} + +test "avs_reconf_command_set_voltage_structure" { + given cmd = avs_reconf_command_set_voltage(1050) + try std.testing.expect(cmd.op == ReconfOp.set_voltage); + try std.testing.expect(cmd.param1 == 1050); +} + +test "avs_reconf_command_set_freq_structure" { + given cmd = avs_reconf_command_set_freq(120000) + try std.testing.expect(cmd.op == ReconfOp.set_frequency); + try std.testing.expect(cmd.param1 == 120000); +} + +test "avs_reconf_command_set_mode_structure" { + given cmd = avs_reconf_command_set_mode(ReconfMode.performance) + try std.testing.expect(cmd.op == ReconfOp.set_mode); + try std.testing.expect(cmd.param1 == 2); +} + +test "avs_reconf_command_calibrate_structure" { + given cmd = avs_reconf_command_calibrate() + try std.testing.expect(cmd.op == ReconfOp.calibrate); + try std.testing.expect(cmd.timeout_ms > RECONF_TIMEOUT_MS); +} + +test "avs_reconf_result_success_structure" { + given result = avs_reconf_result_success(500) + try std.testing.expect(result.success == true); + try std.testing.expect(result.cycles_taken == 500); +} + +test "avs_reconf_result_error_structure" { + given result = avs_reconf_result_error(2, 300) + try std.testing.expect(result.success == false); + try std.testing.expect(result.error_code == 2); +} + +test "avs_reconf_should_retry_true" { + given status = ReconfStatus{.state = ReconfState.error, .current_voltage_mv = 1000, .current_freq_khz = 100000, .last_op = ReconfOp.set_voltage, .error_code = 1, .retry_count = 1} + try std.testing.expect(avs_reconf_should_retry(status) == true); +} + +test "avs_reconf_should_retry_false_max_retries" { + given status = ReconfStatus{.state = ReconfState.error, .current_voltage_mv = 1000, .current_freq_khz = 100000, .last_op = ReconfOp.set_voltage, .error_code = 1, .retry_count = 3} + try std.testing.expect(avs_reconf_should_retry(status) == false); +} + +test "avs_reconf_should_retry_false_idle" { + given status = avs_reconf_status_init() + try std.testing.expect(avs_reconf_should_retry(status) == false); +} + +test "encode_avs_reconf_cmd" { + given encoded = encode_avs_reconf_cmd(0x00, 0x3E8, 0x2710) + try std.testing.expect((encoded >> 24) == 0x00); +} + +test "decode_avs_reconf_cmd" { + given decoded = decode_avs_reconf_cmd(0x003E8271) + try std.testing.expect(decoded.op == 0); + try std.testing.expect(decoded.param1 == 0x3E8); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant voltage_range_positive + assert VOLTAGE_MIN_MV > 0 and VOLTAGE_MAX_MV > VOLTAGE_MIN_MV + +invariant freq_range_positive + assert FREQ_MIN_KHZ > 0 and FREQ_MAX_KHZ > FREQ_MIN_KHZ + +invariant reconf_timeout_positive + assert RECONF_TIMEOUT_MS > 0 + +invariant reconf_retry_count_positive + assert RECONF_RETRY_COUNT > 0 + +invariant reconf_config_init_valid + given config = avs_reconf_config_init() + assert avs_reconf_config_valid(config) + +invariant reconf_config_performance_max_voltage + given config = avs_reconf_config_performance() + assert config.voltage_mv == VOLTAGE_MAX_MV + +invariant reconf_config_power_saving_low_voltage + given config = avs_reconf_config_power_saving() + assert config.voltage_mv < 1000 + +invariant reconf_status_init_idle + given status = avs_reconf_status_init() + try std.testing.expect(status.state == ReconfState.idle); + +invariant reconf_status_start_op_busy + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_status_start_op(status, ReconfOp.set_voltage)); + try std.testing.expect(result.state == ReconfState.busy); + +invariant reconf_result_success_no_error + given result = avs_reconf_result_success(100) + assert result.success == true and result.error_code == 0 + +invariant reconf_result_error_not_success + given result = avs_reconf_result_error(1, 100) + assert result.success == false and result.error_code > 0 + +invariant reconf_should_retry_requires_error + given idle = avs_reconf_status_init() + assert avs_reconf_should_retry(idle) == false + +invariant reconf_config_performance_auto_scale + given config = avs_reconf_config_performance() + assert config.auto_scale == true + +invariant reconf_config_power_saving_auto_scale + given config = avs_reconf_config_power_saving() + assert config.auto_scale == true + +invariant reconf_command_calibrate_longer_timeout + given normal = avs_reconf_command_init() + try std.testing.expect(cal = avs_reconf_command_calibrate()); + assert cal.timeout_ms > normal.timeout_ms + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench avs_reconf_config_init_latency + measure: nanoseconds to avs_reconf_config_init() + target: < 30ns + +bench avs_reconf_voltage_valid_latency + measure: nanoseconds to avs_reconf_voltage_valid(1000) + target: < 15ns + +bench avs_reconf_freq_valid_latency + measure: nanoseconds to avs_reconf_freq_valid(100000) + target: < 15ns + +bench avs_reconf_config_valid_latency + measure: nanoseconds to avs_reconf_config_valid(avs_reconf_config_init()) + target: < 30ns + +bench avs_reconf_status_init_latency + measure: nanoseconds to avs_reconf_status_init() + target: < 30ns + +bench avs_reconf_status_start_op_latency + measure: nanoseconds to avs_reconf_status_start_op(avs_reconf_status_init(), ReconfOp.set_voltage) + target: < 20ns + +bench avs_reconf_status_complete_latency + measure: nanoseconds to avs_reconf_status_complete(avs_reconf_status_init(), avs_reconf_result_success(100)) + target: < 20ns + +bench avs_reconf_command_set_voltage_latency + measure: nanoseconds to avs_reconf_command_set_voltage(1000) + target: < 20ns + +bench avs_reconf_should_retry_latency + measure: nanoseconds to avs_reconf_should_retry(avs_reconf_status_init()) + target: < 15ns + +bench avs_reconf_result_success_latency + measure: nanoseconds to avs_reconf_result_success(500) + target: < 15ns + +bench encode_avs_reconf_cmd_latency + measure: nanoseconds to encode_avs_reconf_cmd(0x00, 1000, 10000) + target: < 20ns + +bench decode_avs_reconf_cmd_latency + measure: nanoseconds to decode_avs_reconf_cmd(0x003E8271) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/dfs_gate.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/dfs_gate.t27 new file mode 100644 index 0000000000..520c8bc16e --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/dfs_gate.t27 @@ -0,0 +1,600 @@ +// SPDX-License-Identifier: Apache-2.0 +; dfs_gate.t27 — Sacred Opcode 0xE7: Depth-First Search Gate +; Hardware acceleration for DFS traversal and pattern matching +; φ² + 1/φ² = 3 | TRINITY + +module sacred-dfs_gate; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_DFS_GATE : u8 = 0xE7; + +pub const MAX_STACK_DEPTH : u8 = 16; +pub const MAX_NODES : u16 = 256; +pub const NODE_ADDR_BITS : u8 = 8; + +pub const VISIT_UNVISITED : u8 = 0; +pub const VISIT_VISITING : u8 = 1; +pub const VISIT_VISITED : u8 = 2; + +pub const DFS_MODE_STANDARD : u8 = 0; +pub const DFS_MODE_DEPTH_LIMITED : u8 = 1; +pub const DFS_MODE_PATTERN_MATCH : u8 = 2; +pub const DFS_MODE_PRUNE_SUBOPTIMAL : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const DfsNode = struct { + address : u8, + value : u16, + children : [4]u8, // Up to 4 child addresses + child_count : u8, + visited : u8, + depth : u8, +} + +pub const DfsStack = struct { + nodes : [MAX_STACK_DEPTH]u8, + top : u8, +} + +pub const DfsConfig = struct { + mode : u8, + depth_limit : u8, + pattern_mask : u16, + prune_threshold : u8, +} + +pub const DfsState = struct { + current_node : u8, + visited_count : u16, + max_depth : u8, + cycles : u32, +} + +pub const DfsResult = struct { + found : bool, + target_node : u8, + depth : u8, + nodes_visited : u16, + pattern_matches : u16, +} + +// ============================================================================ +// Stack Functions +// ============================================================================ + +// dfs_stack_init() -> DfsStack +// Initialize empty DFS stack +pub fn dfs_stack_init() DfsStack { + return DfsStack { + .nodes = [_]u8{0} ** MAX_STACK_DEPTH, + .top = 0, + }; +} + +// dfs_stack_push(stack: DfsStack, node: u8) -> DfsStack +// Push node onto DFS stack +pub fn dfs_stack_push(stack: DfsStack, node: u8) DfsStack { + if (stack.top >= MAX_STACK_DEPTH) { + return stack; // Stack overflow + } + var result = stack; + result.nodes[stack.top] = node; + result.top += 1; + return result; +} + +// dfs_stack_pop(stack: DfsStack) -> struct { stack: DfsStack, node: u8, valid: bool } +// Pop node from DFS stack +pub fn dfs_stack_pop(stack: DfsStack) struct { stack: DfsStack, node: u8, valid: bool } { + if (stack.top == 0) { + return .{ .stack = stack, .node = 0, .valid = false }; + } + var result = stack; + result.top -= 1; + const node = result.nodes[result.top]; + result.nodes[result.top] = 0; + return .{ .stack = result, .node = node, .valid = true }; +} + +// dfs_stack_peek(stack: DfsStack) -> struct { node: u8, valid: bool } +// Peek at top of DFS stack +pub fn dfs_stack_peek(stack: DfsStack) struct { node: u8, valid: bool } { + if (stack.top == 0) { + return .{ .node = 0, .valid = false }; + } + return .{ .node = stack.nodes[stack.top - 1], .valid = true }; +} + +// dfs_stack_is_empty(stack: DfsStack) -> bool +// Check if stack is empty +pub fn dfs_stack_is_empty(stack: DfsStack) bool { + return stack.top == 0; +} + +// dfs_stack_depth(stack: DfsStack) -> u8 +// Get current stack depth +pub fn dfs_stack_depth(stack: DfsStack) -> u8 { + return stack.top; +} + +// ============================================================================ +// Node Functions +// ============================================================================ + +// dfs_node_init(address: u8, value: u16) -> DfsNode +// Initialize DFS node +pub fn dfs_node_init(address: u8, value: u16) DfsNode { + return DfsNode { + .address = address, + .value = value, + .children = [_]u8{255} ** 4, // 255 = invalid + .child_count = 0, + .visited = VISIT_UNVISITED, + .depth = 0, + }; +} + +// dfs_node_add_child(node: DfsNode, child_addr: u8) -> DfsNode +// Add child to node +pub fn dfs_node_add_child(node: DfsNode, child_addr: u8) DfsNode { + if (node.child_count >= 4) { + return node; // Too many children + } + var result = node; + result.children[node.child_count] = child_addr; + result.child_count += 1; + return result; +} + +// dfs_node_has_child(node: DfsNode, child_addr: u8) -> bool +// Check if node has specific child +pub fn dfs_node_has_child(node: DfsNode, child_addr: u8) bool { + for (0..node.child_count) |i| { + if (node.children[i] == child_addr) { + return true; + } + } + return false; +} + +// dfs_node_mark_visiting(node: DfsNode) -> DfsNode +// Mark node as being visited +pub fn dfs_node_mark_visiting(node: DfsNode) DfsNode { + var result = node; + result.visited = VISIT_VISITING; + return result; +} + +// dfs_node_mark_visited(node: DfsNode) -> DfsNode +// Mark node as visited +pub fn dfs_node_mark_visited(node: DfsNode) DfsNode { + var result = node; + result.visited = VISIT_VISITED; + return result; +} + +// dfs_node_is_visited(node: DfsNode) -> bool +// Check if node is fully visited +pub fn dfs_node_is_visited(node: DfsNode) bool { + return node.visited == VISIT_VISITED; +} + +// dfs_node_is_visiting(node: DfsNode) -> bool +// Check if node is currently being visited +pub fn dfs_node_is_visiting(node: DfsNode) bool { + return node.visited == VISIT_VISITING; +} + +// ============================================================================ +// DFS Traversal Functions +// ============================================================================ + +// dfs_traverse(graph: [MAX_NODES]DfsNode, start_addr: u8, target_value: u16, config: DfsConfig) -> DfsResult +// Perform DFS traversal to find target value +pub fn dfs_traverse(graph: [MAX_NODES]DfsNode, start_addr: u8, target_value: u16, config: DfsConfig) DfsResult { + var stack = dfs_stack_init(); + stack = dfs_stack_push(stack, start_addr); + + var visited_count : u16 = 0; + var max_depth : u8 = 0; + var pattern_matches : u16 = 0; + + var current_depth : u8 = 0; + var nodes_visited : u16 = 0; + + while (not dfs_stack_is_empty(stack)) { + const peek = dfs_stack_peek(stack); + + if (not peek.valid) { + break; + } + + const node_idx = peek.node; + var node = graph[@as(u8, @intCast(node_idx))]; + + // Check depth limit + if (config.mode == DFS_MODE_DEPTH_LIMITED and current_depth >= config.depth_limit) { + const pop_result = dfs_stack_pop(stack); + if (pop_result.valid) { + stack = pop_result.stack; + current_depth -= 1; + } + continue; + } + + // Check if visiting for first time + if (node.visited == VISIT_UNVISITED) { + nodes_visited += 1; + node = dfs_node_mark_visiting(node); + graph[@as(u8, @intCast(node_idx))] = node; + + // Check if target found + if (node.value == target_value) { + return DfsResult { + .found = true, + .target_node = node_idx, + .depth = current_depth, + .nodes_visited = nodes_visited, + .pattern_matches = pattern_matches, + }; + } + + // Check pattern match + if (config.mode == DFS_MODE_PATTERN_MATCH) { + if ((node.value & config.pattern_mask) == (target_value & config.pattern_mask)) { + pattern_matches += 1; + } + } + + // Prune suboptimal nodes + if (config.mode == DFS_MODE_PRUNE_SUBOPTIMAL) { + if (node.value < config.prune_threshold) { + const pop_result = dfs_stack_pop(stack); + if (pop_result.valid) { + stack = pop_result.stack; + current_depth -= 1; + } + continue; + } + } + + // Push children onto stack (reverse order for correct DFS) + if (node.child_count > 0) { + var i : u8 = node.child_count; + while (i > 0) { + i -= 1; + const child_addr = node.children[i]; + const child = graph[@as(u8, @intCast(child_addr))]; + if (child.visited == VISIT_UNVISITED) { + child.depth = current_depth + 1; + if (child.depth > max_depth) { + max_depth = child.depth; + } + stack = dfs_stack_push(stack, child_addr); + } + } + current_depth += 1; + } + } else { + // Node already visited, mark complete and pop + node = dfs_node_mark_visited(node); + graph[@as(u8, @intCast(node_idx))] = node; + visited_count += 1; + + const pop_result = dfs_stack_pop(stack); + if (pop_result.valid) { + stack = pop_result.stack; + if (current_depth > 0) { + current_depth -= 1; + } + } + } + } + + return DfsResult { + .found = false, + .target_node = 0, + .depth = max_depth, + .nodes_visited = nodes_visited, + .pattern_matches = pattern_matches, + }; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_dfs_gate(mode: u8, start_addr: u8, target: u16) -> u32 +// Encode DFS gate instruction +pub fn encode_dfs_gate(mode: u8, start_addr: u8, target: u16) u32 { + // Format: [OP:8][MODE:2][START:8][TARGET:14] + const op : u32 = @as(u32, OP_DFS_GATE) << 24; + const mode_field : u32 = @as(u32, mode & 0x03) << 22; + const start_field : u32 = @as(u32, start_addr) << 14; + const target_field : u32 = @as(u32, target & 0x3FFF); + return op | mode_field | start_field | target_field; +} + +// decode_dfs_gate(encoded: u32) -> struct { mode: u8, start_addr: u8, target: u16 } +// Decode DFS gate instruction +pub fn decode_dfs_gate(encoded: u32) struct { mode: u8, start_addr: u8, target: u16 } { + const mode : u8 = @as(u8, @truncate((encoded >> 22) & 0x03)); + const start_addr : u8 = @as(u8, @truncate((encoded >> 14) & 0xFF)); + const target : u16 = @as(u16, @truncate(encoded & 0x3FFF)); + return .{ .mode = mode, .start_addr = start_addr, .target = target }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "max_stack_depth_sixteen" { + try std.testing.expect(MAX_STACK_DEPTH == 16); +} + +test "max_nodes_256" { + try std.testing.expect(MAX_NODES == 256); +} + +test "node_addr_bits_eight" { + try std.testing.expect(NODE_ADDR_BITS == 8); +} + +test "visit_unvisited_zero" { + assert VISIT_UNVISITED == 0 +} + +test "visit_visited_two" { + assert VISIT_VISITED == 2 +} + +test "dfs_stack_init_empty" { + given stack = dfs_stack_init() + try std.testing.expect(dfs_stack_is_empty(stack) == true); + try std.testing.expect(dfs_stack_depth(stack) == 0); +} + +test "dfs_stack_push_pop" { + given stack = dfs_stack_init() + try std.testing.expect(pushed = dfs_stack_push(stack, 10)); + try std.testing.expect(popped = dfs_stack_pop(pushed)); + try std.testing.expect(popped.valid == true); + try std.testing.expect(popped.node == 10); + try std.testing.expect(dfs_stack_is_empty(popped.stack) == true); +} + +test "dfs_stack_push_multiple" { + given stack = dfs_stack_init() + try std.testing.expect(result = dfs_stack_push(dfs_stack_push(dfs_stack_push(stack, 1), 2), 3)); + try std.testing.expect(popped = dfs_stack_pop(result)); + try std.testing.expect(popped.node == 3); + try std.testing.expect(dfs_stack_depth(popped.stack) == 2); +} + +test "dfs_stack_peek" { + given stack = dfs_stack_init() + try std.testing.expect(pushed = dfs_stack_push(stack, 10)); + try std.testing.expect(peeked = dfs_stack_peek(pushed)); + try std.testing.expect(peeked.valid == true); + try std.testing.expect(peeked.node == 10); + try std.testing.expect(dfs_stack_depth(pushed) == 1); +} + +test "dfs_stack_peek_empty" { + given stack = dfs_stack_init() + try std.testing.expect(peeked = dfs_stack_peek(stack)); + try std.testing.expect(peeked.valid == false); +} + +test "dfs_stack_depth" { + given stack = dfs_stack_init() + try std.testing.expect(result = dfs_stack_push(dfs_stack_push(dfs_stack_push(stack, 1), 2), 3)); + try std.testing.expect(dfs_stack_depth(result) == 3); +} + +test "dfs_node_init" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(node.address == 10); + try std.testing.expect(node.value == 0x1234); + try std.testing.expect(node.visited == VISIT_UNVISITED); + try std.testing.expect(node.child_count == 0); +} + +test "dfs_node_add_child" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(result = dfs_node_add_child(node, 20)); + try std.testing.expect(result.child_count == 1); + try std.testing.expect(result.children[0] == 20); +} + +test "dfs_node_add_multiple_children" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(result = dfs_node_add_child(dfs_node_add_child(dfs_node_add_child(node, 20), 21), 22)); + try std.testing.expect(result.child_count == 3); +} + +test "dfs_node_add_child_overflow" { + given node = DfsNode{.address = 10, .value = 0x1234, .children = [_]u8{20, 21, 22, 23}, .child_count = 4, .visited = 0, .depth = 0} + try std.testing.expect(result = dfs_node_add_child(node, 24)); + try std.testing.expect(result.child_count == 4 // No change); +} + +test "dfs_node_has_child_true" { + given node = dfs_node_add_child(dfs_node_init(10, 0x1234), 20) + try std.testing.expect(dfs_node_has_child(node, 20) == true); +} + +test "dfs_node_has_child_false" { + given node = dfs_node_add_child(dfs_node_init(10, 0x1234), 20) + try std.testing.expect(dfs_node_has_child(node, 21) == false); +} + +test "dfs_node_mark_visiting" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(result = dfs_node_mark_visiting(node)); + try std.testing.expect(result.visited == VISIT_VISITING); +} + +test "dfs_node_mark_visited" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(result = dfs_node_mark_visited(node)); + try std.testing.expect(result.visited == VISIT_VISITED); +} + +test "dfs_node_is_visited_true" { + given node = dfs_node_mark_visited(dfs_node_init(10, 0x1234)) + try std.testing.expect(dfs_node_is_visited(node) == true); +} + +test "dfs_node_is_visited_false" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(dfs_node_is_visited(node) == false); +} + +test "dfs_node_is_visiting_true" { + given node = dfs_node_mark_visiting(dfs_node_init(10, 0x1234)) + try std.testing.expect(dfs_node_is_visiting(node) == true); +} + +test "dfs_traverse_found" { + given graph = [_]DfsNode{dfs_node_add_child(dfs_node_add_child(dfs_node_init(0, 0x1234), 1), 2)} ++ [_]DfsNode{dfs_node_init(1, 0x5678), .children = [_]u8{3}, 255, 255, 255], .child_count = 1, .visited = 0, .depth = 0} ++ [_]DfsNode{dfs_node_init(2, 0x5678), .children = [_]u8{255}, 255, 255, 255], .child_count = 0, .visited = 0, .depth = 0} ++ [_]DfsNode{dfs_node_init(3, 0x5678), .children = [_]u8{255}, 255, 255, 255], .child_count = 0, .visited = 0, .depth = 0} ++ [_]DfsNode{} ** 252 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0x5678, config)); + try std.testing.expect(result.found == true); + try std.testing.expect(result.target_node == 1); +} + +test "dfs_traverse_not_found" { + given graph = [_]DfsNode{dfs_node_init(0, 0x1234), .children = [_]u8{1}, 255, 255, 255], .child_count = 1, .visited = 0, .depth = 0} ++ [_]DfsNode{dfs_node_init(1, 0x2345), .children = [_]u8{2}, 255, 255, 255], .child_count = 1, .visited = 0, .depth = 0} ++ [_]DfsNode{dfs_node_init(2, 0x3456), .children = [_]u8{255}, 255, 255, 255], .child_count = 0, .visited = 0, .depth = 0} ++ [_]DfsNode{} ** 253 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0xFFFF, config)); + try std.testing.expect(result.found == false); +} + +test "dfs_traverse_depth_limited" { + given graph = [_]DfsNode{dfs_node_add_child(dfs_node_init(0, 0x1234), 1)} ++ [_]DfsNode{dfs_node_add_child(dfs_node_init(1, 0x5678), 2)} ++ [_]DfsNode{dfs_node_add_child(dfs_node_init(2, 0x5678), 3)} ++ [_]DfsNode{} ** 253 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_DEPTH_LIMITED, .depth_limit = 1, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0x5678, config)); + try std.testing.expect(result.found == false); + try std.testing.expect(result.depth <= config.depth_limit); +} + +test "encode_dfs_gate" { + given encoded = encode_dfs_gate(1, 0x20, 0x1234) + try std.testing.expect((encoded >> 24) == OP_DFS_GATE); +} + +test "decode_dfs_gate" { + given decoded = decode_dfs_gate(0xE7080A34) + try std.testing.expect(decoded.mode == 1); + try std.testing.expect(decoded.start_addr == 0x20); + try std.testing.expect(decoded.target == 0x0A34); +} + +test "opcode_constant" { + try std.testing.expect(OP_DFS_GATE == 0xE7); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant max_stack_depth_sixteen + assert MAX_STACK_DEPTH == 16 + +invariant max_nodes_256 + assert MAX_NODES == 256 + +invariant node_addr_bits_eight + try std.testing.expect(NODE_ADDR_BITS == 8); + +invariant visit_state_values + try std.testing.expect(VISIT_UNVISITED < VISIT_VISITING and VISIT_VISITING < VISIT_VISITED); + +invariant dfs_mode_values + try std.testing.expect(DFS_MODE_STANDARD <= DFS_MODE_PRUNE_SUBOPTIMAL and DFS_MODE_PRUNE_SUBOPTIMAL <= 3); + +invariant dfs_stack_init_empty + given stack = dfs_stack_init() + assert dfs_stack_is_empty(stack) == true + +invariant dfs_stack_push_increases_depth + given stack = dfs_stack_init() + try std.testing.expect(initial_depth = dfs_stack_depth(stack)); + try std.testing.expect(pushed = dfs_stack_push(stack, 10)); + assert dfs_stack_depth(pushed) == initial_depth + 1 + +invariant dfs_stack_pop_decreases_depth + given stack = dfs_stack_init() + try std.testing.expect(pushed = dfs_stack_push(stack, 10)); + try std.testing.expect(popped = dfs_stack_pop(pushed)); + try std.testing.expect(popped.valid == true); + assert dfs_stack_depth(popped.stack) < dfs_stack_depth(pushed) + +invariant dfs_stack_depth_bound + try std.testing.expect(dfs_stack_depth(dfs_stack_push(dfs_stack_push(dfs_stack_push(dfs_stack_init(), 1), 2), 3)) == 3); + +invariant dfs_stack_depth_max_bound + var stack = dfs_stack_init(); + for (0..MAX_STACK_DEPTH) |_| { + stack = dfs_stack_push(stack, @as(u8, @truncate(0))); + } + try std.testing.expect(dfs_stack_depth(stack) <= MAX_STACK_DEPTH); + +invariant dfs_node_children_bound + try std.testing.expect(dfs_node_add_child(dfs_node_add_child(dfs_node_add_child(dfs_node_add_child(dfs_node_init(10, 0x1234), 20), 21), 22).child_count <= 4); + +invariant dfs_node_visited_sequence + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(visiting = dfs_node_mark_visiting(node)); + try std.testing.expect(visited = dfs_node_mark_visiting(visiting)); + assert node.visited != visited.visited + +invariant dfs_traverse_found_has_valid_target + given graph = [_]DfsNode{dfs_node_add_child(dfs_node_init(0, 0x1234), 1)} ++ [_]DfsNode{dfs_node_init(1, 0x5678), .children = [_]u8{255}, 255, 255, 255], .child_count = 0, .visited = 0, .depth = 0} ++ [_]DfsNode{} ** 254 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0x5678, config)); + try std.testing.expect(result.found implies (result.target_node < MAX_NODES)); + +invariant dfs_traverse_nodes_visited_bound + given graph = [_]DfsNode{} ** 256 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0xFFFF, config)); + try std.testing.expect(result.nodes_visited <= MAX_NODES); + +invariant dfs_traverse_depth_bound + given graph = [_]DfsNode{} ** 256 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_DEPTH_LIMITED, .depth_limit = 5, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0xFFFF, config)); + try std.testing.expect(result.depth <= config.depth_limit); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench dfs_stack_push_latency + measure: nanoseconds to dfs_stack_push(dfs_stack_init(), 10) + target: < 30ns + +bench dfs_stack_pop_latency + measure: nanoseconds to dfs_stack_pop(dfs_stack_push(dfs_stack_init(), 10)) + target: < 30ns + +bench dfs_traverse_latency + measure: nanoseconds to dfs_traverse([_]DfsNode{} ** 256, 0, 0x5678, .{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}) + target: < 1000ns + +bench encode_dfs_gate_latency + measure: nanoseconds to encode_dfs_gate(1, 0x20, 0x1234) + target: < 30ns + +bench decode_dfs_gate_latency + measure: nanoseconds to decode_dfs_gate(0xE7080A34) + target: < 30ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/drowsy_ret.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/drowsy_ret.t27 new file mode 100644 index 0000000000..e42b5b08a9 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/drowsy_ret.t27 @@ -0,0 +1,716 @@ +// SPDX-License-Identifier: Apache-2.0 + +module sacred-drowsy_ret; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_DROWSY_RET : u8 = 0xEC; + +pub const DROWSY_STATE_BITS : u8 = 3; +pub const RET_REASON_BITS : u8 = 3; + +pub const DROWSY_ACTIVE : u8 = 0; +pub const DROWSY_LIGHT : u8 = 1; +pub const DROWSY_DEEP : u8 = 2; +pub const DROWSY_OFF : u8 = 3; +pub const DROWSY_HIBERNATE : u8 = 4; +pub const DROWSY_CRITICAL : u8 = 5; +pub const DROWSY_MAX : u8 = 6; + +pub const RET_WAKE : u8 = 0; +pub const RET_TIMER : u8 = 1; +pub const RET_INTERRUPT : u8 = 2; +pub const RET_ERROR : u8 = 3; +pub const RET_POWER : u8 = 4; +pub const RET_MANUAL : u8 = 5; +pub const RET_MAX : u8 = 6; + +pub const WAKE_CYCLES : u32 = 100; // Cycles to wake from light drowsy +pub const DEEP_WAKE_CYCLES : u32 = 1000; // Cycles to wake from deep drowsy + +// ============================================================================ +// Types +// ============================================================================ + +pub const DrowsyState = enum(u8) { + active = DROWSY_ACTIVE, + light = DROWSY_LIGHT, + deep = DROWSY_DEEP, + off = DROWSY_OFF, + hibernate = DROWSY_HIBERNATE, + critical = DROWSY_CRITICAL, +} + +pub const RetReason = enum(u8) { + wake = RET_WAKE, + timer = RET_TIMER, + interrupt = RET_INTERRUPT, + error = RET_ERROR, + power = RET_POWER, + manual = RET_MANUAL, +} + +pub const DrowsyConfig = struct { + enable_light : bool, + enable_deep : bool, + light_threshold_ms : u32, + deep_threshold_ms : u32, + wake_irq_enable : bool, +} + +pub const DrowsyStatus = struct { + state : DrowsyState, + last_wake_cycle : u32, + drowsy_duration : u32, + wake_count : u32, + last_ret_reason : RetReason, +} + +pub const WakeCondition = struct { + irq_pending : bool, + timer_expired : bool, + power_good : bool, + manual_wake : bool, +} + +// ============================================================================ +// State Functions +// ============================================================================ + +// drowsy_is_active(state: DrowsyState) -> bool +// Check if in active state +pub fn drowsy_is_active(state: DrowsyState) bool { + return state == DrowsyState.active; +} + +// drowsy_is_drowsy(state: DrowsyState) -> bool +// Check if in any drowsy state +pub fn drowsy_is_drowsy(state: DrowsyState) bool { + return state == DrowsyState.light or state == DrowsyState.deep; +} + +// drowsy_is_off(state: DrowsyState) -> bool +// Check if device is off +pub fn drowsy_is_off(state: DrowsyState) bool { + return state == DrowsyState.off or state == DrowsyState.hibernate; +} + +// drowsy_is_critical(state: DrowsyState) -> bool +// Check if in critical state +pub fn drowsy_is_critical(state: DrowsyState) bool { + return state == DrowsyState.critical; +} + +// drowsy_can_enter_light(state: DrowsyState) -> bool +// Check if can enter light drowsy +pub fn drowsy_can_enter_light(state: DrowsyState) bool { + return state == DrowsyState.active; +} + +// drowsy_can_enter_deep(state: DrowsyState) -> bool +// Check if can enter deep drowsy +pub fn drowsy_can_enter_deep(state: DrowsyState) bool { + return state == DrowsyState.light; +} + +// drowsy_wake_cycles(state: DrowsyState) -> u32 +// Get wake cycles for state +pub fn drowsy_wake_cycles(state: DrowsyState) -> u32 { + switch (state) { + DrowsyState.light => return WAKE_CYCLES, + DrowsyState.deep => return DEEP_WAKE_CYCLES, + else => return 0, + } +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// drowsy_config_init() -> DrowsyConfig +// Initialize drowsy config +pub fn drowsy_config_init() -> DrowsyConfig { + return DrowsyConfig { + .enable_light = true, + .enable_deep = true, + .light_threshold_ms = 100, + .deep_threshold_ms = 1000, + .wake_irq_enable = true, + }; +} + +// drowsy_config_disable() -> DrowsyConfig +// Disable drowsy mode +pub fn drowsy_config_disable() -> DrowsyConfig { + return DrowsyConfig { + .enable_light = false, + .enable_deep = false, + .light_threshold_ms = 0, + .deep_threshold_ms = 0, + .wake_irq_enable = false, + }; +} + +// drowsy_config_light_only() -> DrowsyConfig +// Enable only light drowsy +pub fn drowsy_config_light_only(threshold_ms: u32) -> DrowsyConfig { + return DrowsyConfig { + .enable_light = true, + .enable_deep = false, + .light_threshold_ms = threshold_ms, + .deep_threshold_ms = 0, + .wake_irq_enable = true, + }; +} + +// ============================================================================ +// Status Functions +// ============================================================================ + +// drowsy_status_init() -> DrowsyStatus +// Initialize drowsy status +pub fn drowsy_status_init() -> DrowsyStatus { + return DrowsyStatus { + .state = DrowsyState.active, + .last_wake_cycle = 0, + .drowsy_duration = 0, + .wake_count = 0, + .last_ret_reason = RetReason.manual, + }; +} + +// drowsy_status_enter_light(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus +// Enter light drowsy state +pub fn drowsy_status_enter_light(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus { + if (not drowsy_can_enter_light(status.state)) { + return status; + } + return DrowsyStatus { + .state = DrowsyState.light, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = status.last_ret_reason, + }; +} + +// drowsy_status_enter_deep(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus +// Enter deep drowsy state +pub fn drowsy_status_enter_deep(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus { + if (not drowsy_can_enter_deep(status.state)) { + return status; + } + return DrowsyStatus { + .state = DrowsyState.deep, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = status.last_ret_reason, + }; +} + +// drowsy_status_wake(status: DrowsyStatus, reason: RetReason, current_cycle: u32) -> DrowsyStatus +// Wake from drowsy state +pub fn drowsy_status_wake(status: DrowsyStatus, reason: RetReason, current_cycle: u32) -> DrowsyStatus { + return DrowsyStatus { + .state = DrowsyState.active, + .last_wake_cycle = current_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count + 1, + .last_ret_reason = reason, + }; +} + +// drowsy_status_enter_critical(status: DrowsyStatus) -> DrowsyStatus +// Enter critical state (emergency) +pub fn drowsy_status_enter_critical(status: DrowsyStatus) -> DrowsyStatus { + return DrowsyStatus { + .state = DrowsyState.critical, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = RetReason.error, + }; +} + +// drowsy_status_enter_off(status: DrowsyStatus) -> DrowsyStatus +// Enter off state +pub fn drowsy_status_enter_off(status: DrowsyStatus) -> DrowsyStatus { + return DrowsyStatus { + .state = DrowsyState.off, + .last_wake_cycle = 0, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = RetReason.power, + }; +} + +// ============================================================================ +// Wake Condition Functions +// ============================================================================ + +// drowsy_wake_condition_init() -> WakeCondition +// Initialize wake condition +pub fn drowsy_wake_condition_init() -> WakeCondition { + return WakeCondition { + .irq_pending = false, + .timer_expired = false, + .power_good = true, + .manual_wake = false, + }; +} + +// drowsy_should_wake(condition: WakeCondition) -> bool +// Check if should wake from drowsy +pub fn drowsy_should_wake(condition: WakeCondition) -> bool { + return condition.irq_pending or condition.timer_expired or condition.manual_wake; +} + +// drowsy_can_wake_critical(condition: WakeCondition) -> bool +// Check if can wake from critical state +pub fn drowsy_can_wake_critical(condition: WakeCondition) -> bool { + return condition.power_good and condition.manual_wake; +} + +// drowsy_wake_from_condition(condition: WakeCondition) -> RetReason +// Determine wake reason from condition +pub fn drowsy_wake_from_condition(condition: WakeCondition) -> RetReason { + if (condition.irq_pending) { + return RetReason.interrupt; + } else if (condition.timer_expired) { + return RetReason.timer; + } else if (condition.manual_wake) { + return RetReason.manual; + } else if (not condition.power_good) { + return RetReason.power; + } else { + return RetReason.wake; + } +} + +// ============================================================================ +// Drowsy Duration Tracking +// ============================================================================ + +// drowsy_update_duration(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus +// Update drowsy duration tracking +pub fn drowsy_update_duration(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus { + if (not drowsy_is_drowsy(status.state)) { + return DrowsyStatus { + .state = status.state, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = status.last_ret_reason, + }; + } + + const elapsed = if (status.last_wake_cycle > 0) current_cycle - status.last_wake_cycle else 0; + return DrowsyStatus { + .state = status.state, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = status.drowsy_duration + 1, + .wake_count = status.wake_count, + .last_ret_reason = status.last_ret_reason, + }; +} + +// drowsy_check_threshold(status: DrowsyStatus, config: DrowsyConfig) -> bool +// Check if should transition to deeper state +pub fn drowsy_check_threshold(status: DrowsyStatus, config: DrowsyConfig) -> bool { + if (status.state == DrowsyState.light and config.enable_deep) { + // Simplified: convert cycles to approximate ms + const threshold = status.drowsy_duration / 100; + return threshold >= (config.deep_threshold_ms / config.light_threshold_ms); + } + return false; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_drowsy_ret(reason: u8, state: u8, duration: u8) -> u32 +// Encode drowsy return instruction +pub fn encode_drowsy_ret(reason: u8, state: u8, duration: u8) u32 { + // Format: [OP:8][REASON:3][STATE:3][DURATION:16][PADDING:2] + const op : u32 = @as(u32, OP_DROWSY_RET) << 24; + const reason_field : u32 = @as(u32, reason & 0x07) << 21; + const state_field : u32 = @as(u32, state & 0x07) << 18; + const duration_field : u32 = @as(u32, duration & 0xFFFF) << 2; + return op | reason_field | state_field | duration_field; +} + +// decode_drowsy_ret(encoded: u32) -> struct { reason: u8, state: u8, duration: u16 } +// Decode drowsy return instruction +pub fn decode_drowsy_ret(encoded: u32) -> struct { reason: u8, state: u8, duration: u16 } { + const reason : u8 = @as(u8, @truncate((encoded >> 21) & 0x07)); + const state : u8 = @as(u8, @truncate((encoded >> 18) & 0x07)); + const duration : u16 = @as(u16, @truncate((encoded >> 2) & 0xFFFF)); + return .{ .reason = reason, .state = state, .duration = duration }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "drowsy_state_constants" { + try std.testing.expect(DROWSY_ACTIVE == 0); + try std.testing.expect(DROWSY_LIGHT == 1); + try std.testing.expect(DROWSY_DEEP == 2); +} + +test "ret_reason_constants" { + try std.testing.expect(RET_WAKE == 0); + try std.testing.expect(RET_TIMER == 1); + try std.testing.expect(RET_INTERRUPT == 2); + try std.testing.expect(RET_ERROR == 3); +} + +test "wake_cycles_constants" { + try std.testing.expect(WAKE_CYCLES == 100); + try std.testing.expect(DEEP_WAKE_CYCLES == 1000); +} + +test "drowsy_is_active_true" { + try std.testing.expect(drowsy_is_active(DrowsyState.active) == true); +} + +test "drowsy_is_active_false" { + try std.testing.expect(drowsy_is_active(DrowsyState.light) == false); +} + +test "drowsy_is_drowsy_true" { + try std.testing.expect(drowsy_is_drowsy(DrowsyState.light) == true); + try std.testing.expect(drowsy_is_drowsy(DrowsyState.deep) == true); +} + +test "drowsy_is_drowsy_false" { + try std.testing.expect(drowsy_is_drowsy(DrowsyState.active) == false); + try std.testing.expect(drowsy_is_drowsy(DrowsyState.off) == false); +} + +test "drowsy_is_off_true" { + try std.testing.expect(drowsy_is_off(DrowsyState.off) == true); + try std.testing.expect(drowsy_is_off(DrowsyState.hibernate) == true); +} + +test "drowsy_is_off_false" { + try std.testing.expect(drowsy_is_off(DrowsyState.active) == false); + try std.testing.expect(drowsy_is_off(DrowsyState.light) == false); +} + +test "drowsy_is_critical_true" { + try std.testing.expect(drowsy_is_critical(DrowsyState.critical) == true); +} + +test "drowsy_is_critical_false" { + try std.testing.expect(drowsy_is_critical(DrowsyState.active) == false); +} + +test "drowsy_can_enter_light_true" { + try std.testing.expect(drowsy_can_enter_light(DrowsyState.active) == true); +} + +test "drowsy_can_enter_light_false" { + try std.testing.expect(drowsy_can_enter_light(DrowsyState.light) == false); +} + +test "drowsy_can_enter_deep_true" { + try std.testing.expect(drowsy_can_enter_deep(DrowsyState.light) == true); +} + +test "drowsy_can_enter_deep_false" { + try std.testing.expect(drowsy_can_enter_deep(DrowsyState.active) == false); + try std.testing.expect(drowsy_can_enter_deep(DrowsyState.deep) == false); +} + +test "drowsy_wake_cycles_light" { + try std.testing.expect(drowsy_wake_cycles(DrowsyState.light) == WAKE_CYCLES); +} + +test "drowsy_wake_cycles_deep" { + try std.testing.expect(drowsy_wake_cycles(DrowsyState.deep) == DEEP_WAKE_CYCLES); +} + +test "drowsy_config_init_structure" { + given config = drowsy_config_init() + try std.testing.expect(config.enable_light == true); + try std.testing.expect(config.enable_deep == true); + try std.testing.expect(config.wake_irq_enable == true); +} + +test "drowsy_config_disable_structure" { + given config = drowsy_config_disable() + try std.testing.expect(config.enable_light == false); + try std.testing.expect(config.enable_deep == false); + try std.testing.expect(config.wake_irq_enable == false); +} + +test "drowsy_config_light_only_structure" { + given config = drowsy_config_light_only(50) + try std.testing.expect(config.enable_light == true); + try std.testing.expect(config.enable_deep == false); + try std.testing.expect(config.light_threshold_ms == 50); +} + +test "drowsy_status_init_structure" { + given status = drowsy_status_init() + try std.testing.expect(status.state == DrowsyState.active); + try std.testing.expect(status.wake_count == 0); +} + +test "drowsy_status_enter_light" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_enter_light(status, 100)); + try std.testing.expect(result.state == DrowsyState.light); +} + +test "drowsy_status_enter_light_reject" { + given status = DrowsyStatus{.state = DrowsyState.deep, .last_wake_cycle = 0, .drowsy_duration = 0, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_status_enter_light(status, 100)); + try std.testing.expect(result.state == DrowsyState.deep); +} + +test "drowsy_status_enter_deep" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 0, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_status_enter_deep(status, 100)); + try std.testing.expect(result.state == DrowsyState.deep); +} + +test "drowsy_status_wake" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_wake(status, RetReason.interrupt, 200)); + try std.testing.expect(result.state == DrowsyState.active); + try std.testing.expect(result.wake_count == 1); + try std.testing.expect(result.last_ret_reason == RetReason.interrupt); +} + +test "drowsy_status_enter_critical" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_enter_critical(status)); + try std.testing.expect(result.state == DrowsyState.critical); + try std.testing.expect(result.last_ret_reason == RetReason.error); +} + +test "drowsy_status_enter_off" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_enter_off(status)); + try std.testing.expect(result.state == DrowsyState.off); + try std.testing.expect(result.last_ret_reason == RetReason.power); +} + +test "drowsy_wake_condition_init" { + given cond = drowsy_wake_condition_init() + try std.testing.expect(cond.irq_pending == false); + try std.testing.expect(cond.power_good == true); +} + +test "drowsy_should_wake_irq" { + given cond = WakeCondition{.irq_pending = true, .timer_expired = false, .power_good = true, .manual_wake = false} + try std.testing.expect(drowsy_should_wake(cond) == true); +} + +test "drowsy_should_wake_none" { + given cond = drowsy_wake_condition_init() + try std.testing.expect(drowsy_should_wake(cond) == false); +} + +test "drowsy_can_wake_critical_true" { + given cond = WakeCondition{.irq_pending = false, .timer_expired = false, .power_good = true, .manual_wake = true} + try std.testing.expect(drowsy_can_wake_critical(cond) == true); +} + +test "drowsy_can_wake_critical_no_power" { + given cond = WakeCondition{.irq_pending = false, .timer_expired = false, .power_good = false, .manual_wake = true} + try std.testing.expect(drowsy_can_wake_critical(cond) == false); +} + +test "drowsy_wake_from_condition_irq" { + given cond = WakeCondition{.irq_pending = true, .timer_expired = false, .power_good = true, .manual_wake = false} + try std.testing.expect(reason = drowsy_wake_from_condition(cond)); + try std.testing.expect(reason == RetReason.interrupt); +} + +test "drowsy_wake_from_condition_timer" { + given cond = WakeCondition{.irq_pending = false, .timer_expired = true, .power_good = true, .manual_wake = false} + try std.testing.expect(reason = drowsy_wake_from_condition(cond)); + try std.testing.expect(reason == RetReason.timer); +} + +test "drowsy_update_duration_active" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_update_duration(status, 100)); + try std.testing.expect(result.drowsy_duration == 0); +} + +test "drowsy_update_duration_light" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 0, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_update_duration(status, 100)); + try std.testing.expect(result.drowsy_duration == 1); +} + +test "drowsy_update_duration_accumulates" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 5, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_update_duration(status, 100)); + try std.testing.expect(result.drowsy_duration == 6); +} + +test "drowsy_update_duration_resets_on_active" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 10, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(active_status = DrowsyStatus{.state = DrowsyState.active, .last_wake_cycle = 0, .drowsy_duration = 5, .wake_count = 0, .last_ret_reason = RetReason.manual}); + try std.testing.expect(result = drowsy_update_duration(active_status, 100)); + try std.testing.expect(result.drowsy_duration == 0); +} + +test "drowsy_check_threshold_not_reached" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 5, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(config = drowsy_config_init()); + try std.testing.expect(drowsy_check_threshold(status, config) == false); +} + +test "drowsy_check_threshold_reached" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 500, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(config = drowsy_config_init()); + try std.testing.expect(drowsy_check_threshold(status, config) == true); +} + +test "encode_drowsy_ret" { + given encoded = encode_drowsy_ret(1, 2, 0x1234) + try std.testing.expect((encoded >> 24) == OP_DROWSY_RET); +} + +test "decode_drowsy_ret" { + given decoded = decode_drowsy_ret(0xEC481234) + try std.testing.expect(decoded.reason == 1); + try std.testing.expect(decoded.state == 2); + try std.testing.expect(decoded.duration == 0x1234); +} + +test "opcode_constant" { + try std.testing.expect(OP_DROWSY_RET == 0xEC); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant drowsy_state_range + try std.testing.expect(DROWSY_ACTIVE >= 0 and DROWSY_CRITICAL <= 5); + +invariant ret_reason_range + try std.testing.expect(RET_WAKE >= 0 and RET_MAX <= 6); + +invariant wake_cycles_positive + assert WAKE_CYCLES > 0 and DEEP_WAKE_CYCLES > 0 + +invariant deep_wake_larger_than_light + assert DEEP_WAKE_CYCLES > WAKE_CYCLES + +invariant drowsy_is_drowsy_excludes_active_off + given state = DrowsyState.active + assert drowsy_is_drowsy(state) == false + try std.testing.expect(drowsy_is_off(DrowsyState.light) == false); + +invariant drowsy_is_off_excludes_active_drowsy + given state = DrowsyState.active + assert drowsy_is_off(state) == false + try std.testing.expect(drowsy_is_off(DrowsyState.light) == false); + +invariant drowsy_config_init_enables_light + given config = drowsy_config_init() + try std.testing.expect(config.enable_light == true); + +invariant drowsy_config_enable_both_or_neither + given config1 = drowsy_config_init() + try std.testing.expect(config2 = drowsy_config_light_only(100)); + try std.testing.expect((config1.enable_light and config1.enable_deep) or (not config1.enable_light and not config1.enable_deep)); + or (config2.enable_light and not config2.enable_deep) + +invariant drowsy_status_init_active + given status = drowsy_status_init() + try std.testing.expect(status.state == DrowsyState.active); + +invariant drowsy_status_wake_increases_count + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_wake(status, RetReason.manual, 100)); + try std.testing.expect(result.wake_count == status.wake_count + 1); + +invariant drowsy_status_wake_sets_active + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 100, .wake_count = 5, .last_ret_reason = RetReason.timer} + try std.testing.expect(result = drowsy_status_wake(status, RetReason.interrupt, 200)); + try std.testing.expect(result.state == DrowsyState.active); + +invariant drowsy_status_enter_critical_reason + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_enter_critical(status)); + try std.testing.expect(result.last_ret_reason == RetReason.error); + +invariant drowsy_wake_condition_init_defaults + given cond = drowsy_wake_condition_init() + try std.testing.expect(cond.irq_pending == false and cond.timer_expired == false and cond.manual_wake == false); + +invariant drowsy_should_wake_false_for_default + given cond = drowsy_wake_condition_init() + try std.testing.expect(drowsy_should_wake(cond) == false); + +invariant drowsy_wake_from_condition_no_match + given cond = drowsy_wake_condition_init() + try std.testing.expect(reason = drowsy_wake_from_condition(cond)); + try std.testing.expect(reason == RetReason.wake); + +invariant drowsy_update_duration_zero_for_non_drowsy + given status = DrowsyStatus{.state = DrowsyState.active, .last_wake_cycle = 0, .drowsy_duration = 10, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_update_duration(status, 100)); + try std.testing.expect(result.drowsy_duration == 0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench drowsy_is_active_latency + measure: nanoseconds to drowsy_is_active(DrowsyState.active) + target: < 10ns + +bench drowsy_is_drowsy_latency + measure: nanoseconds to drowsy_is_drowsy(DrowsyState.light) + target: < 10ns + +bench drowsy_wake_cycles_latency + measure: nanoseconds to drowsy_wake_cycles(DrowsyState.light) + target: < 20ns + +bench drowsy_config_init_latency + measure: nanoseconds to drowsy_config_init() + target: < 30ns + +bench drowsy_status_init_latency + measure: nanoseconds to drowsy_status_init() + target: < 30ns + +bench drowsy_status_enter_light_latency + measure: nanoseconds to drowsy_status_enter_light(drowsy_status_init(), 100) + target: < 30ns + +bench drowsy_status_wake_latency + measure: nanoseconds to drowsy_status_wake(drowsy_status_init(), RetReason.interrupt, 200) + target: < 30ns + +bench drowsy_should_wake_latency + measure: nanoseconds to drowsy_should_wake(drowsy_wake_condition_init()) + target: < 20ns + +bench drowsy_update_duration_latency + measure: nanoseconds to drowsy_update_duration(drowsy_status_init(), 100) + target: < 30ns + +bench encode_drowsy_ret_latency + measure: nanoseconds to encode_drowsy_ret(1, 2, 0x1234) + target: < 30ns + +bench decode_drowsy_ret_latency + measure: nanoseconds to decode_drowsy_ret(0xEC481234) + target: < 30ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/fbb_active_path.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/fbb_active_path.t27 new file mode 100644 index 0000000000..4a401de031 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/fbb_active_path.t27 @@ -0,0 +1,756 @@ +// SPDX-License-Identifier: Apache-2.0 +; fbb_active_path.t27 — FPGA Feedback Bridge Active Path +; Active path management for FPGA feedback bridge +; φ² + 1/φ² = 3 | TRINITY + +module fbb-active-path; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const FBB_OPCODE_BASE : u8 = 0xD0; // FBB opcodes start at 0xD0 + +pub const FBB_OP_PATH_SELECT : u8 = 0x00; +pub const FBB_OP_PATH_ENABLE : u8 = 0x01; +pub const FBB_OP_PATH_DISABLE : u8 = 0x02; +pub const FBB_OP_PATH_STATUS : u8 = 0x03; +pub const FBB_OP_PATH_RESET : u8 = 0x04; +pub const FBB_OP_PATH_BYPASS : u8 = 0x05; +pub const FBB_OP_PATH_LOOPBACK : u8 = 0x06; +pub const FBB_OP_PATH_CONFIG : u8 = 0x07; +pub const FBB_OP_MAX : u8 = 0x08; + +pub const FBB_PATH_COUNT : u8 = 8; +pub const FBB_MAX_PATHS : u8 = 8; + +pub const FBB_PATH_STATE_DISABLED : u8 = 0; +pub const FBB_PATH_STATE_ENABLED : u8 = 1; +pub const FBB_PATH_STATE_ACTIVE : u8 = 2; +pub const FBB_PATH_STATE_ERROR : u8 = 3; +pub const FBB_PATH_STATE_BYPASSED : u8 = 4; +pub const FBB_PATH_STATE_MAX : u8 = 5; + +pub const FBB_LATENCY_MIN_CYCLES : u8 = 1; +pub const FBB_LATENCY_MAX_CYCLES : u8 = 16; +pub const FBB_DEFAULT_LATENCY : u8 = 4; + +pub const FBB_BANDWIDTH_MIN_MBPS : u32 = 100; +pub const FBB_BANDWIDTH_MAX_MBPS : u32 = 10000; +pub const FBB_DEFAULT_BANDWIDTH : u32 = 1000; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FbbOp = enum(u8) { + path_select = FBB_OP_PATH_SELECT, + path_enable = FBB_OP_PATH_ENABLE, + path_disable = FBB_OP_PATH_DISABLE, + path_status = FBB_OP_PATH_STATUS, + path_reset = FBB_OP_PATH_RESET, + path_bypass = FBB_OP_PATH_BYPASS, + path_loopback = FBB_OP_PATH_LOOPBACK, + path_config = FBB_OP_PATH_CONFIG, +} + +pub const FbbPathState = enum(u8) { + disabled = FBB_PATH_STATE_DISABLED, + enabled = FBB_PATH_STATE_ENABLED, + active = FBB_PATH_STATE_ACTIVE, + error = FBB_PATH_STATE_ERROR, + bypassed = FBB_PATH_STATE_BYPASSED, +} + +pub const FbbPathConfig = struct { + path_index : u8, + enabled : bool, + latency_cycles : u8, + bandwidth_mbps : u32, + priority : u8, + auto_bypass : bool, +} + +pub const FbbPathStatus = struct { + path_index : u8, + state : FbbPathState, + active : bool, + error_code : u8, + throughput_mbps : u32, + latency_measured : u16, +} + +pub const FbbActivePath = struct { + active_path : u8, + bypass_path : u8, + loopback_enabled : bool, + paths : [FBB_MAX_PATHS]FbbPathStatus, +} + +pub const FbbCommand = struct { + op : FbbOp, + path_index : u8, + param1 : u16, + param2 : u16, +} + +// ============================================================================ +// Path Config Functions +// ============================================================================ + +// fbb_path_config_init(index: u8) -> FbbPathConfig +// Initialize path config +pub fn fbb_path_config_init(index: u8) -> FbbPathConfig { + return FbbPathConfig { + .path_index = index & 0x07, + .enabled = false, + .latency_cycles = FBB_DEFAULT_LATENCY, + .bandwidth_mbps = FBB_DEFAULT_BANDWIDTH, + .priority = 4, + .auto_bypass = false, + }; +} + +// fbb_path_config_low_latency(index: u8) -> FbbPathConfig +// Create low latency path config +pub fn fbb_path_config_low_latency(index: u8) -> FbbPathConfig { + return FbbPathConfig { + .path_index = index & 0x07, + .enabled = false, + .latency_cycles = FBB_LATENCY_MIN_CYCLES, + .bandwidth_mbps = FBB_DEFAULT_BANDWIDTH, + .priority = 7, + .auto_bypass = false, + }; +} + +// fbb_path_config_high_bandwidth(index: u8) -> FbbPathConfig +// Create high bandwidth path config +pub fn fbb_path_config_high_bandwidth(index: u8) -> FbbPathConfig { + return FbbPathConfig { + .path_index = index & 0x07, + .enabled = false, + .latency_cycles = 8, + .bandwidth_mbps = FBB_BANDWIDTH_MAX_MBPS, + .priority = 3, + .auto_bypass = false, + }; +} + +// fbb_path_config_enable(config: FbbPathConfig) -> FbbPathConfig +// Enable path in config +pub fn fbb_path_config_enable(config: FbbPathConfig) -> FbbPathConfig { + return FbbPathConfig { + .path_index = config.path_index, + .enabled = true, + .latency_cycles = config.latency_cycles, + .bandwidth_mbps = config.bandwidth_mbps, + .priority = config.priority, + .auto_bypass = config.auto_bypass, + }; +} + +// ============================================================================ +// Path Status Functions +// ============================================================================ + +// fbb_path_status_init(index: u8) -> FbbPathStatus +// Initialize path status +pub fn fbb_path_status_init(index: u8) -> FbbPathStatus { + return FbbPathStatus { + .path_index = index & 0x07, + .state = FbbPathState.disabled, + .active = false, + .error_code = 0, + .throughput_mbps = 0, + .latency_measured = 0, + }; +} + +// fbb_path_status_enable(status: FbbPathStatus) -> FbbPathStatus +// Enable path +pub fn fbb_path_status_enable(status: FbbPathStatus) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = FbbPathState.enabled, + .active = false, + .error_code = 0, + .throughput_mbps = status.throughput_mbps, + .latency_measured = status.latency_measured, + }; +} + +// fbb_path_status_activate(status: FbbPathStatus) -> FbbPathStatus +// Activate path +pub fn fbb_path_status_activate(status: FbbPathStatus) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = FbbPathState.active, + .active = true, + .error_code = 0, + .throughput_mbps = status.throughput_mbps, + .latency_measured = status.latency_measured, + }; +} + +// fbb_path_status_bypass(status: FbbPathStatus) -> FbbPathStatus +// Bypass path +pub fn fbb_path_status_bypass(status: FbbPathStatus) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = FbbPathState.bypassed, + .active = false, + .error_code = 0, + .throughput_mbps = 0, + .latency_measured = 0, + }; +} + +// fbb_path_status_error(status: FbbPathStatus, error_code: u8) -> FbbPathStatus +// Set error status +pub fn fbb_path_status_error(status: FbbPathStatus, error_code: u8) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = FbbPathState.error, + .active = false, + .error_code = error_code, + .throughput_mbps = 0, + .latency_measured = 0, + }; +} + +// fbb_path_status_update_metrics(status: FbbPathStatus, throughput: u32, latency: u16) -> FbbPathStatus +// Update path metrics +pub fn fbb_path_status_update_metrics(status: FbbPathStatus, throughput: u32, latency: u16) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = status.state, + .active = status.active, + .error_code = status.error_code, + .throughput_mbps = throughput, + .latency_measured = latency, + }; +} + +// ============================================================================ +// Active Path Functions +// ============================================================================ + +// fbb_active_path_init() -> FbbActivePath +// Initialize active path +pub fn fbb_active_path_init() -> FbbActivePath { + var paths : [FBB_MAX_PATHS]FbbPathStatus = undefined; + for (0..FBB_MAX_PATHS) |i| { + paths[i] = fbb_path_status_init(@as(u8, @truncate(i))); + } + return FbbActivePath { + .active_path = 0, + .bypass_path = 0xFF, // No bypass + .loopback_enabled = false, + .paths = paths, + }; +} + +// fbb_active_path_select(active: FbbActivePath, path_index: u8) -> FbbActivePath +// Select active path +pub fn fbb_active_path_select(active: FbbActivePath, path_index: u8) -> FbbActivePath { + const idx = path_index & 0x07; + var new_paths = active.paths; + + // Deactivate current active path + new_paths[active.active_path] = fbb_path_status_enable(new_paths[active.active_path]); + + // Activate new path + new_paths[idx] = fbb_path_status_activate(new_paths[idx]); + + return FbbActivePath { + .active_path = idx, + .bypass_path = active.bypass_path, + .loopback_enabled = active.loopback_enabled, + .paths = new_paths, + }; +} + +// fbb_active_path_enable_bypass(active: FbbActivePath, bypass_index: u8) -> FbbActivePath +// Enable bypass path +pub fn fbb_active_path_enable_bypass(active: FbbActivePath, bypass_index: u8) -> FbbActivePath { + const idx = bypass_index & 0x07; + var new_paths = active.paths; + new_paths[idx] = fbb_path_status_bypass(new_paths[idx]); + + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = idx, + .loopback_enabled = active.loopback_enabled, + .paths = new_paths, + }; +} + +// fbb_active_path_disable_bypass(active: FbbActivePath) -> FbbActivePath +// Disable bypass path +pub fn fbb_active_path_disable_bypass(active: FbbActivePath) -> FbbActivePath { + var new_paths = active.paths; + + // Restore bypassed path + if (active.bypass_path != 0xFF) { + new_paths[active.bypass_path] = fbb_path_status_enable(new_paths[active.bypass_path]); + } + + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = 0xFF, + .loopback_enabled = active.loopback_enabled, + .paths = new_paths, + }; +} + +// fbb_active_path_enable_loopback(active: FbbActivePath) -> FbbActivePath +// Enable loopback +pub fn fbb_active_path_enable_loopback(active: FbbActivePath) -> FbbActivePath { + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = active.bypass_path, + .loopback_enabled = true, + .paths = active.paths, + }; +} + +// fbb_active_path_disable_loopback(active: FbbActivePath) -> FbbActivePath +// Disable loopback +pub fn fbb_active_path_disable_loopback(active: FbbActivePath) -> FbbActivePath { + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = active.bypass_path, + .loopback_enabled = false, + .paths = active.paths, + }; +} + +// fbb_active_path_get_path(active: FbbActivePath, path_index: u8) -> FbbPathStatus +// Get path status +pub fn fbb_active_path_get_path(active: FbbActivePath, path_index: u8) -> FbbPathStatus { + const idx = path_index & 0x07; + return active.paths[idx]; +} + +// fbb_active_path_update_path_metrics(active: FbbActivePath, path_index: u8, throughput: u32, latency: u16) -> FbbActivePath +// Update path metrics +pub fn fbb_active_path_update_path_metrics(active: FbbActivePath, path_index: u8, throughput: u32, latency: u16) -> FbbActivePath { + const idx = path_index & 0x07; + var new_paths = active.paths; + new_paths[idx] = fbb_path_status_update_metrics(new_paths[idx], throughput, latency); + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = active.bypass_path, + .loopback_enabled = active.loopback_enabled, + .paths = new_paths, + }; +} + +// ============================================================================ +// Validation Functions +// ============================================================================ + +// fbb_path_index_valid(index: u8) -> bool +// Check if path index is valid +pub fn fbb_path_index_valid(index: u8) -> bool { + return index < FBB_PATH_COUNT; +} + +// fbb_latency_valid(latency: u8) -> bool +// Check if latency is valid +pub fn fbb_latency_valid(latency: u8) -> bool { + return latency >= FBB_LATENCY_MIN_CYCLES and latency <= FBB_LATENCY_MAX_CYCLES; +} + +// fbb_bandwidth_valid(bandwidth: u32) -> bool +// Check if bandwidth is valid +pub fn fbb_bandwidth_valid(bandwidth: u32) -> bool { + return bandwidth >= FBB_BANDWIDTH_MIN_MBPS and bandwidth <= FBB_BANDWIDTH_MAX_MBPS; +} + +// fbb_path_config_valid(config: FbbPathConfig) -> bool +// Check if path config is valid +pub fn fbb_path_config_valid(config: FbbPathConfig) -> bool { + return fbb_path_index_valid(config.path_index) + try std.testing.expect(fbb_latency_valid(config.latency_cycles)); + try std.testing.expect(fbb_bandwidth_valid(config.bandwidth_mbps);); +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_fbb_cmd(op: u8, path: u8, param1: u8, param2: u8) -> u32 +// Encode FBB command +pub fn encode_fbb_cmd(op: u8, path: u8, param1: u8, param2: u8) u32 { + // Format: [OP:8][PATH:3][PARAM1:8][PARAM2:8][RES:5] + const op_field : u32 = @as(u32, op) << 24; + const path_field : u32 = @as(u32, path & 0x07) << 21; + const param1_field : u32 = @as(u32, param1) << 13; + const param2_field : u32 = @as(u32, param2) << 5; + return op_field | path_field | param1_field | param2_field; +} + +// decode_fbb_cmd(encoded: u32) -> struct { op: u8, path: u8, param1: u8, param2: u8 } +// Decode FBB command +pub fn decode_fbb_cmd(encoded: u32) -> struct { op: u8, path: u8, param1: u8, param2: u8 } { + const op : u8 = @as(u8, @truncate((encoded >> 24) & 0xFF)); + const path : u8 = @as(u8, @truncate((encoded >> 21) & 0x07)); + const param1 : u8 = @as(u8, @truncate((encoded >> 13) & 0xFF)); + const param2 : u8 = @as(u8, @truncate((encoded >> 5) & 0xFF)); + return .{ .op = op, .path = path, .param1 = param1, .param2 = param2 }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fbb_opcode_base" { + try std.testing.expect(FBB_OPCODE_BASE == 0xD0); +} + +test "fbb_op_constants" { + try std.testing.expect(FBB_OP_PATH_SELECT == 0); + try std.testing.expect(FBB_OP_PATH_ENABLE == 1); + try std.testing.expect(FBB_OP_PATH_DISABLE == 2); + try std.testing.expect(FBB_OP_PATH_BYPASS == 5); +} + +test "fbb_path_state_constants" { + try std.testing.expect(FBB_PATH_STATE_DISABLED == 0); + try std.testing.expect(FBB_PATH_STATE_ENABLED == 1); + try std.testing.expect(FBB_PATH_STATE_ACTIVE == 2); + try std.testing.expect(FBB_PATH_STATE_BYPASSED == 4); +} + +test "fbb_path_count" { + try std.testing.expect(FBB_PATH_COUNT == 8); + try std.testing.expect(FBB_MAX_PATHS == 8); +} + +test "latency_constants" { + try std.testing.expect(FBB_LATENCY_MIN_CYCLES == 1); + try std.testing.expect(FBB_LATENCY_MAX_CYCLES == 16); + try std.testing.expect(FBB_DEFAULT_LATENCY == 4); +} + +test "bandwidth_constants" { + try std.testing.expect(FBB_BANDWIDTH_MIN_MBPS == 100); + try std.testing.expect(FBB_BANDWIDTH_MAX_MBPS == 10000); + try std.testing.expect(FBB_DEFAULT_BANDWIDTH == 1000); +} + +test "fbb_path_config_init_structure" { + given config = fbb_path_config_init(0) + try std.testing.expect(config.path_index == 0); + try std.testing.expect(config.enabled == false); + try std.testing.expect(config.latency_cycles == FBB_DEFAULT_LATENCY); +} + +test "fbb_path_config_low_latency_structure" { + given config = fbb_path_config_low_latency(1) + try std.testing.expect(config.path_index == 1); + try std.testing.expect(config.latency_cycles == FBB_LATENCY_MIN_CYCLES); + try std.testing.expect(config.priority == 7); +} + +test "fbb_path_config_high_bandwidth_structure" { + given config = fbb_path_config_high_bandwidth(2) + try std.testing.expect(config.path_index == 2); + try std.testing.expect(config.bandwidth_mbps == FBB_BANDWIDTH_MAX_MBPS); +} + +test "fbb_path_config_enable" { + given config = fbb_path_config_init(0) + try std.testing.expect(result = fbb_path_config_enable(config)); + try std.testing.expect(result.enabled == true); +} + +test "fbb_path_status_init_structure" { + given status = fbb_path_status_init(0) + try std.testing.expect(status.path_index == 0); + try std.testing.expect(status.state == FbbPathState.disabled); + try std.testing.expect(status.active == false); +} + +test "fbb_path_status_enable" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_enable(status)); + try std.testing.expect(result.state == FbbPathState.enabled); + try std.testing.expect(result.active == false); +} + +test "fbb_path_status_activate" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_activate(status)); + try std.testing.expect(result.state == FbbPathState.active); + try std.testing.expect(result.active == true); +} + +test "fbb_path_status_bypass" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_bypass(status)); + try std.testing.expect(result.state == FbbPathState.bypassed); + try std.testing.expect(result.active == false); +} + +test "fbb_path_status_error" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_error(status, 1)); + try std.testing.expect(result.state == FbbPathState.error); + try std.testing.expect(result.error_code == 1); +} + +test "fbb_path_status_update_metrics" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_update_metrics(status, 5000, 100)); + try std.testing.expect(result.throughput_mbps == 5000); + try std.testing.expect(result.latency_measured == 100); +} + +test "fbb_active_path_init_structure" { + given active = fbb_active_path_init() + try std.testing.expect(active.active_path == 0); + try std.testing.expect(active.bypass_path == 0xFF); + try std.testing.expect(active.loopback_enabled == false); +} + +test "fbb_active_path_select" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_select(active, 3)); + try std.testing.expect(result.active_path == 3); + try std.testing.expect(result.paths[3].active == true); +} + +test "fbb_active_path_enable_bypass" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_enable_bypass(active, 5)); + try std.testing.expect(result.bypass_path == 5); + try std.testing.expect(result.paths[5].state == FbbPathState.bypassed); +} + +test "fbb_active_path_disable_bypass" { + given active = fbb_active_path_init() + try std.testing.expect(with_bypass = fbb_active_path_enable_bypass(active, 5)); + try std.testing.expect(result = fbb_active_path_disable_bypass(with_bypass)); + try std.testing.expect(result.bypass_path == 0xFF); +} + +test "fbb_active_path_enable_loopback" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_enable_loopback(active)); + try std.testing.expect(result.loopback_enabled == true); +} + +test "fbb_active_path_disable_loopback" { + given active = fbb_active_path_init() + try std.testing.expect(with_loopback = fbb_active_path_enable_loopback(active)); + try std.testing.expect(result = fbb_active_path_disable_loopback(with_loopback)); + try std.testing.expect(result.loopback_enabled == false); +} + +test "fbb_active_path_get_path" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_get_path(active, 2)); + try std.testing.expect(result.path_index == 2); +} + +test "fbb_active_path_update_path_metrics" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_update_path_metrics(active, 2, 3000, 50)); + try std.testing.expect(result.paths[2].throughput_mbps == 3000); +} + +test "fbb_path_index_valid_true" { + try std.testing.expect(fbb_path_index_valid(0) == true); + try std.testing.expect(fbb_path_index_valid(7) == true); +} + +test "fbb_path_index_valid_false" { + try std.testing.expect(fbb_path_index_valid(8) == false); + try std.testing.expect(fbb_path_index_valid(255) == false); +} + +test "fbb_latency_valid_true" { + try std.testing.expect(fbb_latency_valid(1) == true); + try std.testing.expect(fbb_latency_valid(10) == true); + try std.testing.expect(fbb_latency_valid(16) == true); +} + +test "fbb_latency_valid_false" { + try std.testing.expect(fbb_latency_valid(0) == false); + try std.testing.expect(fbb_latency_valid(17) == false); +} + +test "fbb_bandwidth_valid_true" { + try std.testing.expect(fbb_bandwidth_valid(100) == true); + try std.testing.expect(fbb_bandwidth_valid(5000) == true); + try std.testing.expect(fbb_bandwidth_valid(10000) == true); +} + +test "fbb_bandwidth_valid_false" { + try std.testing.expect(fbb_bandwidth_valid(50) == false); + try std.testing.expect(fbb_bandwidth_valid(20000) == false); +} + +test "fbb_path_config_valid_true" { + given config = fbb_path_config_init(0) + try std.testing.expect(fbb_path_config_valid(config) == true); +} + +test "fbb_path_config_valid_false_invalid_latency" { + given config = FbbPathConfig{.path_index = 0, .enabled = false, .latency_cycles = 0, .bandwidth_mbps = 1000, .priority = 4, .auto_bypass = false} + try std.testing.expect(fbb_path_config_valid(config) == false); +} + +test "encode_fbb_cmd" { + given encoded = encode_fbb_cmd(0x00, 3, 10, 20) + try std.testing.expect((encoded >> 24) == 0x00); +} + +test "decode_fbb_cmd" { + given decoded = decode_fbb_cmd(0x001A14A0) + try std.testing.expect(decoded.op == 0); + try std.testing.expect(decoded.path == 3); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fbb_path_count_positive + assert FBB_PATH_COUNT > 0 + try std.testing.expect(FBB_MAX_PATHS == FBB_PATH_COUNT); + +invariant latency_range_positive + assert FBB_LATENCY_MIN_CYCLES > 0 and FBB_LATENCY_MAX_CYCLES >= FBB_LATENCY_MIN_CYCLES + +invariant bandwidth_range_positive + assert FBB_BANDWIDTH_MIN_MBPS > 0 and FBB_BANDWIDTH_MAX_MBPS >= FBB_BANDWIDTH_MIN_MBPS + +invariant default_latency_in_range + assert fbb_latency_valid(FBB_DEFAULT_LATENCY) + +invariant default_bandwidth_in_range + assert fbb_bandwidth_valid(FBB_DEFAULT_BANDWIDTH) + +invariant fbb_path_config_init_valid + given config = fbb_path_config_init(0) + assert fbb_path_config_valid(config) + +invariant fbb_path_config_low_latency_min_latency + given config = fbb_path_config_low_latency(0) + assert config.latency_cycles == FBB_LATENCY_MIN_CYCLES + +invariant fbb_path_config_high_bandwidth_max_bandwidth + given config = fbb_path_config_high_bandwidth(0) + assert config.bandwidth_mbps == FBB_BANDWIDTH_MAX_MBPS + +invariant fbb_path_status_init_disabled + given status = fbb_path_status_init(0) + try std.testing.expect(status.state == FbbPathState.disabled); + +invariant fbb_path_status_enable_enabled_not_active + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_enable(status)); + try std.testing.expect(result.state == FbbPathState.enabled and result.active == false); + +invariant fbb_path_status_activate_active + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_activate(status)); + try std.testing.expect(result.state == FbbPathState.active and result.active == true); + +invariant fbb_path_status_bypass_bypassed_not_active + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_bypass(status)); + try std.testing.expect(result.state == FbbPathState.bypassed and result.active == false); + +invariant fbb_active_path_init_no_bypass + given active = fbb_active_path_init() + try std.testing.expect(active.bypass_path == 0xFF); + +invariant fbb_active_path_select_updates_active + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_select(active, 5)); + try std.testing.expect(result.active_path == 5); + +invariant fbb_active_path_enable_loopback_sets_flag + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_enable_loopback(active)); + try std.testing.expect(result.loopback_enabled == true); + +invariant fbb_active_path_disable_loopback_clears_flag + given active = fbb_active_path_init() + try std.testing.expect(with_loopback = fbb_active_path_enable_loopback(active)); + try std.testing.expect(result = fbb_active_path_disable_loopback(with_loopback)); + try std.testing.expect(result.loopback_enabled == false); + +invariant fbb_path_index_valid_range + assert fbb_path_index_valid(0) and not fbb_path_index_valid(FBB_PATH_COUNT) + +invariant fbb_latency_valid_range + assert fbb_latency_valid(FBB_LATENCY_MIN_CYCLES) + try std.testing.expect(fbb_latency_valid(FBB_LATENCY_MAX_CYCLES)); + try std.testing.expect(not fbb_latency_valid(FBB_LATENCY_MAX_CYCLES + 1)); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fbb_path_config_init_latency + measure: nanoseconds to fbb_path_config_init(0) + target: < 20ns + +bench fbb_path_config_low_latency_latency + measure: nanoseconds to fbb_path_config_low_latency(0) + target: < 20ns + +bench fbb_path_config_high_bandwidth_latency + measure: nanoseconds to fbb_path_config_high_bandwidth(0) + target: < 20ns + +bench fbb_path_status_init_latency + measure: nanoseconds to fbb_path_status_init(0) + target: < 20ns + +bench fbb_path_status_enable_latency + measure: nanoseconds to fbb_path_status_enable(fbb_path_status_init(0)) + target: < 15ns + +bench fbb_path_status_activate_latency + measure: nanoseconds to fbb_path_status_activate(fbb_path_status_init(0)) + target: < 15ns + +bench fbb_active_path_init_latency + measure: nanoseconds to fbb_active_path_init() + target: < 50ns + +bench fbb_active_path_select_latency + measure: nanoseconds to fbb_active_path_select(fbb_active_path_init(), 3) + target: < 50ns + +bench fbb_active_path_enable_loopback_latency + measure: nanoseconds to fbb_active_path_enable_loopback(fbb_active_path_init()) + target: < 15ns + +bench fbb_path_index_valid_latency + measure: nanoseconds to fbb_path_index_valid(5) + target: < 10ns + +bench fbb_latency_valid_latency + measure: nanoseconds to fbb_latency_valid(10) + target: < 10ns + +bench fbb_bandwidth_valid_latency + measure: nanoseconds to fbb_bandwidth_valid(5000) + target: < 15ns + +bench encode_fbb_cmd_latency + measure: nanoseconds to encode_fbb_cmd(0x00, 3, 10, 20) + target: < 20ns + +bench decode_fbb_cmd_latency + measure: nanoseconds to decode_fbb_cmd(0x001A14A0) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/fp8_e4m3.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/fp8_e4m3.t27 new file mode 100644 index 0000000000..06fb882f2d --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/fp8_e4m3.t27 @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: Apache-2.0 +; fp8_e4m3.t27 — FP8 E4M3 8-bit Floating Point +; 8-bit float with 4 exponent, 3 mantissa bits (no implicit leading bit) +; OCP FP8 format optimized for neural network training +; Range: ~-240 to ~240, precision: ~6-7 significant bits +; φ² + 1/φ² = 3 | TRINITY + +module triformat-fp8_e4m3; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 4; +pub const MANT_BITS : u8 = 3; + +pub const SIGN_SHIFT : u8 = 7; +pub const EXP_SHIFT : u8 = 3; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u8 = 0x80; // 1 << 7 +pub const EXP_MASK : u8 = 0x78; // 0b01111000 +pub const MANT_MASK : u8 = 0x07; // 0b00000111 + +pub const EXP_MAX : u8 = 15; // All ones in 4 bits +pub const EXP_MIN : u8 = 0; +pub const EXP_BIAS : u8 = 7; // Exponent bias for FP8 E4M3 + +pub const MANT_DIVISOR : u8 = 8; // 2^3 + +// FP8 E4M3 special values +pub const FP8_ZERO_POS : u8 = 0x00; +pub const FP8_ZERO_NEG : u8 = 0x80; +pub const FP8_NAN_POS : u8 = 0x7F; // All exp + mant != 0 +pub const FP8_NAN_NEG : u8 = 0xFF; +pub const FP8_INF_POS : u8 = 0x78; // All exp + mant = 0 +pub const FP8_INF_NEG : u8 = 0xF8; + +// Subnormal exponent +pub const SUBNORMAL_EXP : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FP8E4M3 = u8; // 8-bit value + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// fp8_extract_sign(fp8: FP8E4M3) -> i8 +// Extract sign bit (bit 7) +// Returns: 0 for positive, -1 for negative +pub fn fp8_extract_sign(fp8: FP8E4M3) i8 { + const bit = (fp8 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// fp8_extract_exponent(fp8: FP8E4M3) -> u8 +// Extract exponent bits (bits 6-3) +// Returns: 0-15 +pub fn fp8_extract_exponent(fp8: FP8E4M3) u8 { + return (fp8 >> EXP_SHIFT) & 0x0F; +} + +// fp8_extract_mantissa(fp8: FP8E4M3) -> u8 +// Extract mantissa bits (bits 2-0) +// Returns: 0-7 +pub fn fp8_extract_mantissa(fp8: FP8E4M3) u8 { + return fp8 & MANT_MASK; +} + +// fp8_from_components(sign: i8, exp: u8, mant: u8) -> FP8E4M3 +// Assemble FP8 from sign, exponent, mantissa +pub fn fp8_from_components(sign: i8, exp: u8, mant: u8) FP8E4M3 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(FP8E4M3, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(FP8E4M3, @intCast(exp & 0x0F)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// fp8_is_zero(fp8: FP8E4M3) -> bool +// Check if FP8 is zero (positive or negative) +pub fn fp8_is_zero(fp8: FP8E4M3) bool { + return fp8 == FP8_ZERO_POS or fp8 == FP8_ZERO_NEG; +} + +// fp8_is_special(fp8: FP8E4M3) -> bool +// Check if FP8 is Inf or NaN (exp == 15) +pub fn fp8_is_special(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX; +} + +// fp8_is_inf(fp8: FP8E4M3) -> bool +// Check if FP8 is infinity (exp == 15, mant == 0) +pub fn fp8_is_inf(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) == 0; +} + +// fp8_is_nan(fp8: FP8E4M3) -> bool +// Check if FP8 is NaN (exp == 15, mant != 0) +pub fn fp8_is_nan(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) != 0; +} + +// fp8_is_subnormal(fp8: FP8E4M3) -> bool +// Check if FP8 is subnormal (exp == 0, mant != 0) +pub fn fp8_is_subnormal(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == 0 and fp8_extract_mantissa(fp8) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// fp8_encode_f32(value: f32) -> FP8E4M3 +// Encode IEEE 754 single precision to FP8 E4M3 +// Round-to-nearest, ties to even +pub fn fp8_encode_f32(value: f32) FP8E4M3 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return if (value < 0.0) FP8_NAN_NEG else FP8_NAN_POS; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i16 = @as(i16, @intCast((f32_bits >> 23) & 0xFF)) - 127; + var f32_mant: u32 = f32_bits & 0x007FFFFF; + + // Handle very small values (subnormals) + if (f32_exp <= @as(i16, -EXP_BIAS)) { + // Below FP8 minimum normal - try to encode as subnormal + const subnormal_shift = @as(i16, -EXP_BIAS) - f32_exp + 1; + if (subnormal_shift > 3 + 23) { + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + var mant = f32_mant >> 23; + if (subnormal_shift > 0) { + const shift_amount = @as(u5, @intCast(subnormal_shift)); + if (shift_amount <= 24) { + mant = (f32_mant | 0x00800000) >> shift_amount; + } + } + return fp8_from_components(sign, 0, @as(u8, @truncate(mant)) & 0x07); + } + + // Convert exp from f32 bias (127) to FP8 bias (7) + var fp8_exp = @as(i16, f32_exp + EXP_BIAS); + + // Clamp exponent + if (fp8_exp >= EXP_MAX) { + return if (sign < 0) FP8_INF_NEG else FP8_INF_POS; + } else if (fp8_exp < 1) { + // Underflow to zero (could be subnormal, simplified) + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Extract mantissa and scale to 3 bits + // f32 mantissa is 24 bits (including implicit 1), FP8 needs 3 bits + // Shift right by 21 bits (24 - 3 = 21) + var mant = f32_mant >> 21; + + // Round-to-nearest with ties to even + const discarded = f32_mant & 0x1FFFFF; // Lower 21 bits + if ((discarded & 0x100000) != 0) { + // Halfway or more + if ((discarded & 0x0FFFFF) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (fp8_exp < @as(i16, EXP_MAX - 1)) { + fp8_exp += 1; + } + } + } + } + + return fp8_from_components(sign, @as(u8, @intCast(fp8_exp)), @as(u8, mant)); +} + +// fp8_decode_f32(fp8: FP8E4M3) -> f32 +// Decode FP8 E4M3 to IEEE 754 single precision +pub fn fp8_decode_f32(fp8: FP8E4M3) f32 { + // Handle zero + if (fp8_is_zero(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (fp8_is_nan(fp8)) { + return std.math.nan(f32); + } + + // Handle Infinity + if (fp8_is_inf(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -std.math.inf(f32) else std.math.inf(f32); + } + + // Extract components + const sign = fp8_extract_sign(fp8); + const exp = fp8_extract_exponent(fp8); + const mant = fp8_extract_mantissa(fp8); + + var f32_value: f32 = undefined; + + // Handle subnormal + if (exp == 0) { + // Subnormal: value = mant * 2^(-6) = mant * 2^(1-bias) + const bias_adjusted: i8 = 1 - @as(i8, EXP_BIAS); + const mant_f32 = @as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR)); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } else { + // Normal: value = (1 + mant/8) * 2^(exp - 7) + const bias_adjusted = @as(i16, exp) - @as(i16, EXP_BIAS); + const mant_f32 = 1.0 + (@as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR))); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } + + return if (sign < 0) -f32_value else f32_value; +} + +// fp8_encode_f64(value: f64) -> FP8E4M3 +// Encode IEEE 754 double precision to FP8 E4M3 +pub fn fp8_encode_f64(value: f64) FP8E4M3 { + return fp8_encode_f32(@as(f32, @floatCast(value))); +} + +// fp8_decode_f64(fp8: FP8E4M3) -> f64 +// Decode FP8 E4M3 to IEEE 754 double precision +pub fn fp8_decode_f64(fp8: FP8E4M3) f64 { + const f32_val = fp8_decode_f32(fp8); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// fp8_add(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Add two FP8 values +pub fn fp8_add(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa + fb); +} + +// fp8_sub(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Subtract two FP8 values +pub fn fp8_sub(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa - fb); +} + +// fp8_mul(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Multiply two FP8 values +pub fn fp8_mul(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa * fb); +} + +// fp8_div(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Divide two FP8 values +pub fn fp8_div(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fb = fp8_decode_f32(b); + if (fb == 0.0) { + const fa = fp8_decode_f32(a); + return if (fa < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + const fa = fp8_decode_f32(a); + return fp8_encode_f32(fa / fb); +} + +// fp8_sqrt(value: FP8E4M3) -> FP8E4M3 +// Square root of FP8 value +pub fn fp8_sqrt(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + if (fv < 0.0) { + return FP8_NAN_POS; + } + return fp8_encode_f32(std.math.sqrt(fv)); +} + +// fp8_rsqrt(value: FP8E4M3) -> FP8E4M3 +// Reciprocal square root (1/sqrt(x)) +pub fn fp8_rsqrt(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + if (fv <= 0.0) { + return FP8_INF_POS; + } + return fp8_encode_f32(1.0 / std.math.sqrt(fv)); +} + +// fp8_abs(value: FP8E4M3) -> FP8E4M3 +// Absolute value of FP8 +pub fn fp8_abs(value: FP8E4M3) FP8E4M3 { + return value & ~SIGN_MASK; +} + +// fp8_neg(value: FP8E4M3) -> FP8E4M3 +// Negate FP8 +pub fn fp8_neg(value: FP8E4M3) FP8E4M3 { + return value ^ SIGN_MASK; +} + +// fp8_is_equal(a: FP8E4M3, b: FP8E4M3) -> bool +// Check if two FP8 values are equal +pub fn fp8_is_equal(a: FP8E4M3, b: FP8E4M3) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + if (fp8_is_zero(a) and fp8_is_zero(b)) { + return true; + } + return a == b; +} + +// fp8_is_greater(a: FP8E4M3, b: FP8E4M3) -> bool +// Check if a > b +pub fn fp8_is_greater(a: FP8E4M3, b: FP8E4M3) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + const sign_a = fp8_extract_sign(a); + const sign_b = fp8_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return fp8_neg(a) > fp8_neg(b); + } + return a > b; +} + +// fp8_max(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Return the larger of two FP8 values +pub fn fp8_max(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + return if (fp8_is_greater(a, b)) a else b; +} + +// fp8_min(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Return the smaller of two FP8 values +pub fn fp8_min(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + return if (fp8_is_greater(a, b)) b else a; +} + +// fp8_clamp(value: FP8E4M3, min: FP8E4M3, max: FP8E4M3) -> FP8E4M3 +// Clamp value between min and max +pub fn fp8_clamp(value: FP8E4M3, min: FP8E4M3, max: FP8E4M3) FP8E4M3 { + return fp8_min(fp8_max(value, min), max); +} + +// fp8_lerp(a: FP8E4M3, b: FP8E4M3, t: f32) -> FP8E4M3 +// Linear interpolation between a and b +pub fn fp8_lerp(a: FP8E4M3, b: FP8E4M3, t: f32) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const result = fa + (fb - fa) * t; + return fp8_encode_f32(result); +} + +// fp8_fma(a: FP8E4M3, b: FP8E4M3, c: FP8E4M3) -> FP8E4M3 +// Fused multiply-add: a * b + c +pub fn fp8_fma(a: FP8E4M3, b: FP8E4M3, c: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const fc = fp8_decode_f32(c); + return fp8_encode_f32(fa * fb + fc); +} + +// fp8_scale(value: FP8E4M3, scale: f32) -> FP8E4M3 +// Scale FP8 value by scalar +pub fn fp8_scale(value: FP8E4M3, scale: f32) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * scale); +} + +// fp8_relu(value: FP8E4M3) -> FP8E4M3 +// ReLU activation: max(0, x) +pub fn fp8_relu(value: FP8E4M3) FP8E4M3 { + return if (fp8_extract_sign(value) < 0) FP8_ZERO_POS else value; +} + +// fp8_sigmoid(value: FP8E4M3) -> FP8E4M3 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn fp8_sigmoid(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// fp8_tanh(value: FP8E4M3) -> FP8E4M3 +// Tanh activation +pub fn fp8_tanh(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(std.math.tanh(fv)); +} + +// fp8_gelu(value: FP8E4M3) -> FP8E4M3 +// GELU activation +pub fn fp8_gelu(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + const sqrt_2_over_pi = 0.7978845608; + const cube_coeff = 0.044715; + const tanh_arg = fv * (1.0 + cube_coeff * fv * fv); + return fp8_encode_f32(0.5 * fv * (1.0 + std.math.tanh(sqrt_2_over_pi * tanh_arg))); +} + +// fp8_swish(value: FP8E4M3) -> FP8E4M3 +// Swish activation: x * sigmoid(x) +pub fn fp8_swish(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * (1.0 / (1.0 + std.math.exp(-fv)))); +} + +// fp8_quantize_to_int4(value: FP8E4M3) -> i8 +// Quantize FP8 to Int4 (range [-8, 7]) +pub fn fp8_quantize_to_int4(value: FP8E4M3) i8 { + const fv = fp8_decode_f32(value); + const scaled = fv * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// fp8_quantize_to_int8(value: FP8E4M3) -> i8 +// Quantize FP8 to Int8 (range [-128, 127]) +pub fn fp8_quantize_to_int8(value: FP8E4M3) i8 { + const fv = fp8_decode_f32(value); + const scaled = fv * 127.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 127) { + result = 127; + } else if (result < -128) { + result = -128; + } + return @as(i8, @intCast(result)); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fp8_extract_sign_positive" { + given value = 0x3C + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "fp8_extract_sign_negative" { + given value = 0xBC + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "fp8_extract_exponent_max" { + given value = 0x78 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == EXP_MAX); +} + +test "fp8_extract_exponent_zero" { + given value = 0x07 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == 0); +} + +test "fp8_extract_mantissa_max" { + given value = 0x07 + try std.testing.expect(mant = fp8_extract_mantissa(value)); + try std.testing.expect(mant == MANT_MASK); +} + +test "fp8_is_zero_positive" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_POS) == true); +} + +test "fp8_is_zero_negative" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_NEG) == true); +} + +test "fp8_is_zero_nonzero" { + try std.testing.expect(fp8_is_zero(0x01) == false); +} + +test "fp8_is_inf_positive" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true); +} + +test "fp8_is_inf_negative" { + try std.testing.expect(fp8_is_inf(FP8_INF_NEG) == true); +} + +test "fp8_is_nan_positive" { + try std.testing.expect(fp8_is_nan(FP8_NAN_POS) == true); +} + +test "fp8_is_nan_negative" { + try std.testing.expect(fp8_is_nan(FP8_NAN_NEG) == true); +} + +test "fp8_is_inf_not_nan" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true and fp8_is_nan(FP8_INF_POS) == false); +} + +test "fp8_is_special_inf" { + try std.testing.expect(fp8_is_special(FP8_INF_POS) == true); +} + +test "fp8_is_special_nan" { + try std.testing.expect(fp8_is_special(FP8_NAN_POS) == true); +} + +test "fp8_is_subnormal_true" { + given value = 0x01 + try std.testing.expect(fp8_is_subnormal(value) == true); +} + +test "fp8_is_subnormal_false_normal" { + given value = 0x08 + try std.testing.expect(fp8_is_subnormal(value) == false); +} + +test "fp8_is_subnormal_false_zero" { + try std.testing.expect(fp8_is_subnormal(FP8_ZERO_POS) == false); +} + +test "fp8_encode_f32_zero" { + given fp = fp8_encode_f32(0.0) + try std.testing.expect(fp == FP8_ZERO_POS); +} + +test "fp8_encode_f32_negative_zero" { + given fp = fp8_encode_f32(-0.0) + try std.testing.expect(fp == FP8_ZERO_NEG); +} + +test "fp8_encode_f32_one" { + given fp = fp8_encode_f32(1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 1.0) < 0.05); +} + +test "fp8_encode_f32_negative_one" { + given fp = fp8_encode_f32(-1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded + 1.0) < 0.05); +} + +test "fp8_encode_f32_two" { + given fp = fp8_encode_f32(2.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 2.0) < 0.1); +} + +test "fp8_encode_f32_half" { + given fp = fp8_encode_f32(0.5) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 0.5) < 0.05); +} + +test "fp8_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.1); +} + +test "fp8_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.1); +} + +test "fp8_encode_f32_nan" { + given fp = fp8_encode_f32(std.math.nan(f32)) + try std.testing.expect(fp8_is_nan(fp) == true); +} + +test "fp8_encode_f32_inf" { + given fp = fp8_encode_f32(std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_encode_f32_neg_inf" { + given fp = fp8_encode_f32(-std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_decode_f32_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_POS) == 0.0); +} + +test "fp8_decode_f32_negative_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_NEG) == -0.0); +} + +test "fp8_decode_f32_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_POS)) == true); +} + +test "fp8_decode_f32_neg_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_NEG)) == true); +} + +test "fp8_decode_f32_nan" { + try std.testing.expect(std.math.isNan(fp8_decode_f32(FP8_NAN_POS)) == true); +} + +test "fp8_add_simple" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(result = fp8_add(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.15); +} + +test "fp8_sub_simple" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(result = fp8_sub(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.15); +} + +test "fp8_mul_simple" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(result = fp8_mul(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 6.0) < 0.3); +} + +test "fp8_div_simple" { + given a = fp8_encode_f32(6.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.15); +} + +test "fp8_div_by_zero_positive" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_POS); +} + +test "fp8_div_by_zero_negative" { + given a = fp8_encode_f32(-5.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_NEG); +} + +test "fp8_sqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.1); +} + +test "fp8_sqrt_negative" { + given value = fp8_encode_f32(-4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(fp8_is_nan(result) == true); +} + +test "fp8_rsqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_rsqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_abs_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(result = fp8_abs(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_abs_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(abs_val = fp8_abs(value)); + try std.testing.expect(fp8_extract_sign(abs_val) == 0); +} + +test "fp8_neg_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) < 0); +} + +test "fp8_neg_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) == 0); +} + +test "fp8_is_equal_true" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(fp8_is_equal(value, value) == true); +} + +test "fp8_is_equal_false" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(fp8_is_equal(a, b) == false); +} + +test "fp8_is_equal_nan" { + try std.testing.expect(fp8_is_equal(FP8_NAN_POS, FP8_NAN_POS) == false); +} + +test "fp8_is_equal_zero" { + try std.testing.expect(fp8_is_equal(FP8_ZERO_POS, FP8_ZERO_NEG) == true); +} + +test "fp8_is_greater_positive" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(fp8_is_greater(a, b) == true); +} + +test "fp8_max_returns_larger" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 4.0); +} + +test "fp8_min_returns_smaller" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 4.0); +} + +test "fp8_lerp_zero" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, a) == true); +} + +test "fp8_lerp_one" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, b) == true); +} + +test "fp8_fma_simple" { + given a = fp8_encode_f32(2.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(c = fp8_encode_f32(1.0)); + try std.testing.expect(result = fp8_fma(a, b, c)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 7.0) < 0.3); +} + +test "fp8_relu_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_relu_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_relu_zero" { + given result = fp8_relu(FP8_ZERO_POS) + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_sigmoid_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.05); +} + +test "fp8_sigmoid_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.95); +} + +test "fp8_sigmoid_negative" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 0.05); +} + +test "fp8_tanh_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded) < 0.05); +} + +test "fp8_tanh_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.95); +} + +test "fp8_tanh_negative" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= -0.95); +} + +test "fp8_gelu_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_gelu(value)); + try std.testing.expect(fp8_extract_sign(result) == 0); +} + +test "fp8_swish_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_swish_positive" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded > 0.0); +} + +test "fp8_quantize_to_int4_max" { + given value = fp8_encode_f32(2.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "fp8_quantize_to_int4_min" { + given value = fp8_encode_f32(-2.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "fp8_quantize_to_int8_max" { + given value = fp8_encode_f32(2.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result == 127); +} + +test "fp8_quantize_to_int8_min" { + given value = fp8_encode_f32(-2.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result == -128); +} + +test "fp8_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 7); + try std.testing.expect(mant = 4); + try std.testing.expect(fp = fp8_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = fp8_extract_sign(fp)); + try std.testing.expect(extracted_exp = fp8_extract_exponent(fp)); + try std.testing.expect(extracted_mant = fp8_extract_mantissa(fp)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fp8_bits_constant + assert BITS == 8 + +invariant fp8_sign_bits_one + assert SIGN_BITS == 1 + +invariant fp8_exp_bits_four + assert EXP_BITS == 4 + +invariant fp8_mant_bits_three + assert MANT_BITS == 3 + +invariant fp8_exp_bias_seven + assert EXP_BIAS == 7 + +invariant fp8_exp_max_all_ones + assert EXP_MAX == 0x0F + +invariant fp8_sign_mask_correct + assert SIGN_MASK == 0x80 + +invariant fp8_exp_mask_correct + assert EXP_MASK == 0x78 + +invariant fp8_mant_mask_correct + assert MANT_MASK == 0x07 + +invariant fp8_masks_sum_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFF + +invariant fp8_zero_pos_no_sign + assert (FP8_ZERO_POS & SIGN_MASK) == 0 + +invariant fp8_zero_neg_has_sign + assert (FP8_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant fp8_inf_pos_max_exp + assert (FP8_INF_POS & EXP_MASK) == EXP_MASK + +invariant fp8_inf_pos_no_mant + assert (FP8_INF_POS & MANT_MASK) == 0 + +invariant fp8_nan_has_mantissa + assert (FP8_NAN_POS & MANT_MASK) != 0 + +invariant fp8_abs_removes_sign + assert fp8_abs(FP8_ZERO_NEG) == FP8_ZERO_POS + +invariant fp8_neg_toggles_sign + assert (fp8_neg(FP8_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant fp8_add_zero_identity + given value = fp8_encode_f32(5.0) + assert fp8_is_equal(fp8_add(value, FP8_ZERO_POS), value) == true + +invariant fp8_sub_zero_identity + given value = fp8_encode_f32(5.0) + assert fp8_is_equal(fp8_sub(value, FP8_ZERO_POS), value) == true + +invariant fp8_mul_one_identity + given value = fp8_encode_f32(5.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_mul(value, one), value) == true or fp8_magnitude(value) > 0.5 + +invariant fp8_div_one_identity + given value = fp8_encode_f32(5.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_div(value, one), value) == true + +invariant fp8_max_ge_both + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(fp8_is_greater(result, a) == true and fp8_is_greater(result, b) == true or fp8_is_equal(result, b) == true); + +invariant fp8_min_le_both + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(fp8_is_greater(a, result) == true or fp8_is_equal(a, result) == true); + +invariant fp8_relu_non_negative + given result = fp8_relu(fp8_encode_f32(5.0)) + try std.testing.expect(fp8_extract_sign(result) == 0); + +invariant fp8_lerp_zero_start + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + assert fp8_is_equal(fp8_lerp(a, b, 0.0), a) == true + +invariant fp8_lerp_one_end + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + assert fp8_is_equal(fp8_lerp(a, b, 1.0), b) == true + +invariant fp8_sqrt_square_close + given value = fp8_encode_f32(4.0) + try std.testing.expect(sqrt_val = fp8_sqrt(value)); + try std.testing.expect(sq_val = fp8_mul(sqrt_val, sqrt_val)); + try std.testing.expect(decoded = fp8_decode_f32(sq_val)); + try std.testing.expect(abs(decoded - 4.0) < 0.5); + +invariant fp8_quantize_to_int4_in_range + given result = fp8_quantize_to_int4(fp8_encode_f32(1.0)) + assert result >= -8 and result <= 7 + +invariant fp8_quantize_to_int8_in_range + given result = fp8_quantize_to_int8(fp8_encode_f32(1.0)) + assert result >= -128 and result <= 127 + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fp8_encode_f32_latency + measure: nanoseconds to fp8_encode_f32(1.5) + target: < 50ns + +bench fp8_decode_f32_latency + measure: nanoseconds to fp8_decode_f32(0x3C) + target: < 50ns + +bench fp8_add_latency + measure: nanoseconds to fp8_add(0x3C, 0x48) + target: < 100ns + +bench fp8_mul_latency + measure: nanoseconds to fp8_mul(0x3C, 0x3C) + target: < 100ns + +bench fp8_sqrt_latency + measure: nanoseconds to fp8_sqrt(0x30) + target: < 100ns + +bench fp8_sigmoid_latency + measure: nanoseconds to fp8_sigmoid(0x30) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/fp8_e5m2.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/fp8_e5m2.t27 new file mode 100644 index 0000000000..e46e52429f --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/fp8_e5m2.t27 @@ -0,0 +1,1212 @@ +// SPDX-License-Identifier: Apache-2.0 +; fp8_e5m2.t27 — FP8 E5M2 8-bit Floating Point +; 8-bit float with 5 exponent, 2 mantissa bits (with implicit leading bit) +; OCP FP8 format optimized for inference and wide dynamic range +; Range: ~-57k to ~57k, precision: ~3-4 significant bits +; φ² + 1/φ² = 3 | TRINITY + +module triformat-fp8_e5m2; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 5; +pub const MANT_BITS : u8 = 2; + +pub const SIGN_SHIFT : u8 = 7; +pub const EXP_SHIFT : u8 = 2; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u8 = 0x80; // 1 << 7 +pub const EXP_MASK : u8 = 0x7C; // 0b01111100 +pub const MANT_MASK : u8 = 0x03; // 0b00000011 + +pub const EXP_MAX : u8 = 31; // All ones in 5 bits +pub const EXP_MIN : u8 = 0; +pub const EXP_BIAS : u8 = 15; // Exponent bias for FP8 E5M2 + +pub const MANT_DIVISOR : u8 = 4; // 2^2 + +// FP8 E5M2 special values +pub const FP8_ZERO_POS : u8 = 0x00; +pub const FP8_ZERO_NEG : u8 = 0x80; +pub const FP8_NAN_POS : u8 = 0x7F; // All exp + mant != 0 +pub const FP8_NAN_NEG : u8 = 0xFF; +pub const FP8_INF_POS : u8 = 0x7C; // All exp + mant = 0 +pub const FP8_INF_NEG : u8 = 0xFC; + +// Subnormal exponent +pub const SUBNORMAL_EXP : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FP8E5M2 = u8; // 8-bit value + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// fp8_extract_sign(fp8: FP8E5M2) -> i8 +// Extract sign bit (bit 7) +// Returns: 0 for positive, -1 for negative +pub fn fp8_extract_sign(fp8: FP8E5M2) i8 { + const bit = (fp8 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// fp8_extract_exponent(fp8: FP8E5M2) -> u8 +// Extract exponent bits (bits 6-2) +// Returns: 0-31 +pub fn fp8_extract_exponent(fp8: FP8E5M2) u8 { + return (fp8 >> EXP_SHIFT) & 0x1F; +} + +// fp8_extract_mantissa(fp8: FP8E5M2) -> u8 +// Extract mantissa bits (bits 1-0) +// Returns: 0-3 +pub fn fp8_extract_mantissa(fp8: FP8E5M2) u8 { + return fp8 & MANT_MASK; +} + +// fp8_from_components(sign: i8, exp: u8, mant: u8) -> FP8E5M2 +// Assemble FP8 from sign, exponent, mantissa +pub fn fp8_from_components(sign: i8, exp: u8, mant: u8) FP8E5M2 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(FP8E5M2, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(FP8E5M2, @intCast(exp & 0x1F)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// fp8_is_zero(fp8: FP8E5M2) -> bool +// Check if FP8 is zero (positive or negative) +pub fn fp8_is_zero(fp8: FP8E5M2) bool { + return fp8 == FP8_ZERO_POS or fp8 == FP8_ZERO_NEG; +} + +// fp8_is_special(fp8: FP8E5M2) -> bool +// Check if FP8 is Inf or NaN (exp == 31) +pub fn fp8_is_special(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX; +} + +// fp8_is_inf(fp8: FP8E5M2) -> bool +// Check if FP8 is infinity (exp == 31, mant == 0) +pub fn fp8_is_inf(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) == 0; +} + +// fp8_is_nan(fp8: FP8E5M2) -> bool +// Check if FP8 is NaN (exp == 31, mant != 0) +pub fn fp8_is_nan(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) != 0; +} + +// fp8_is_subnormal(fp8: FP8E5M2) -> bool +// Check if FP8 is subnormal (exp == 0, mant != 0) +pub fn fp8_is_subnormal(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == 0 and fp8_extract_mantissa(fp8) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// fp8_encode_f32(value: f32) -> FP8E5M2 +// Encode IEEE 754 single precision to FP8 E5M2 +// Round-to-nearest, ties to even +pub fn fp8_encode_f32(value: f32) FP8E5M2 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return if (value < 0.0) FP8_NAN_NEG else FP8_NAN_POS; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i16 = @as(i16, @intCast((f32_bits >> 23) & 0xFF)) - 127; + var f32_mant: u32 = f32_bits & 0x007FFFFF; + + // Handle very small values (subnormals) + if (f32_exp <= @as(i16, -EXP_BIAS)) { + // Below FP8 minimum normal - try to encode as subnormal + const subnormal_shift = @as(i16, -EXP_BIAS) - f32_exp + 1; + if (subnormal_shift > 2 + 23) { + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + var mant = f32_mant >> 23; + if (subnormal_shift > 0) { + const shift_amount = @as(u5, @intCast(subnormal_shift)); + if (shift_amount <= 24) { + mant = (f32_mant | 0x00800000) >> shift_amount; + } + } + return fp8_from_components(sign, 0, @as(u8, @truncate(mant)) & 0x03); + } + + // Convert exp from f32 bias (127) to FP8 bias (15) + var fp8_exp = @as(i16, f32_exp + EXP_BIAS); + + // Clamp exponent + if (fp8_exp >= EXP_MAX) { + return if (sign < 0) FP8_INF_NEG else FP8_INF_POS; + } else if (fp8_exp < 1) { + // Underflow to zero (could be subnormal, simplified) + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Extract mantissa and scale to 2 bits + // f32 mantissa is 24 bits (including implicit 1), FP8 needs 2 bits + // Shift right by 22 bits (24 - 2 = 22) + var mant = f32_mant >> 22; + + // Round-to-nearest with ties to even + const discarded = f32_mant & 0x003FFFFF; // Lower 22 bits + if ((discarded & 0x00200000) != 0) { + // Halfway or more + if ((discarded & 0x001FFFFF) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (fp8_exp < @as(i16, EXP_MAX - 1)) { + fp8_exp += 1; + } + } + } + } + + return fp8_from_components(sign, @as(u8, @intCast(fp8_exp)), @as(u8, mant)); +} + +// fp8_decode_f32(fp8: FP8E5M2) -> f32 +// Decode FP8 E5M2 to IEEE 754 single precision +pub fn fp8_decode_f32(fp8: FP8E5M2) f32 { + // Handle zero + if (fp8_is_zero(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (fp8_is_nan(fp8)) { + return std.math.nan(f32); + } + + // Handle Infinity + if (fp8_is_inf(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -std.math.inf(f32) else std.math.inf(f32); + } + + // Extract components + const sign = fp8_extract_sign(fp8); + const exp = fp8_extract_exponent(fp8); + const mant = fp8_extract_mantissa(fp8); + + var f32_value: f32 = undefined; + + // Handle subnormal + if (exp == 0) { + // Subnormal: value = mant * 2^(-14) = mant * 2^(1-bias) + const bias_adjusted: i8 = 1 - @as(i8, EXP_BIAS); + const mant_f32 = @as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR)); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } else { + // Normal: value = (1 + mant/4) * 2^(exp - 15) + const bias_adjusted = @as(i16, exp) - @as(i16, EXP_BIAS); + const mant_f32 = 1.0 + (@as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR))); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } + + return if (sign < 0) -f32_value else f32_value; +} + +// fp8_encode_f64(value: f64) -> FP8E5M2 +// Encode IEEE 754 double precision to FP8 E5M2 +pub fn fp8_encode_f64(value: f64) FP8E5M2 { + return fp8_encode_f32(@as(f32, @floatCast(value))); +} + +// fp8_decode_f64(fp8: FP8E5M2) -> f64 +// Decode FP8 E5M2 to IEEE 754 double precision +pub fn fp8_decode_f64(fp8: FP8E5M2) f64 { + const f32_val = fp8_decode_f32(fp8); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// fp8_add(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Add two FP8 values +pub fn fp8_add(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa + fb); +} + +// fp8_sub(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Subtract two FP8 values +pub fn fp8_sub(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa - fb); +} + +// fp8_mul(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Multiply two FP8 values +pub fn fp8_mul(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa * fb); +} + +// fp8_div(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Divide two FP8 values +pub fn fp8_div(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fb = fp8_decode_f32(b); + if (fb == 0.0) { + const fa = fp8_decode_f32(a); + return if (fa < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + const fa = fp8_decode_f32(a); + return fp8_encode_f32(fa / fb); +} + +// fp8_sqrt(value: FP8E5M2) -> FP8E5M2 +// Square root of FP8 value +pub fn fp8_sqrt(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + if (fv < 0.0) { + return FP8_NAN_POS; + } + return fp8_encode_f32(std.math.sqrt(fv)); +} + +// fp8_rsqrt(value: FP8E5M2) -> FP8E5M2 +// Reciprocal square root (1/sqrt(x)) +pub fn fp8_rsqrt(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + if (fv <= 0.0) { + return FP8_INF_POS; + } + return fp8_encode_f32(1.0 / std.math.sqrt(fv)); +} + +// fp8_abs(value: FP8E5M2) -> FP8E5M2 +// Absolute value of FP8 +pub fn fp8_abs(value: FP8E5M2) FP8E5M2 { + return value & ~SIGN_MASK; +} + +// fp8_neg(value: FP8E5M2) -> FP8E5M2 +// Negate FP8 +pub fn fp8_neg(value: FP8E5M2) FP8E5M2 { + return value ^ SIGN_MASK; +} + +// fp8_is_equal(a: FP8E5M2, b: FP8E5M2) -> bool +// Check if two FP8 values are equal +pub fn fp8_is_equal(a: FP8E5M2, b: FP8E5M2) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + if (fp8_is_zero(a) and fp8_is_zero(b)) { + return true; + } + return a == b; +} + +// fp8_is_greater(a: FP8E5M2, b: FP8E5M2) -> bool +// Check if a > b +pub fn fp8_is_greater(a: FP8E5M2, b: FP8E5M2) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + const sign_a = fp8_extract_sign(a); + const sign_b = fp8_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return fp8_neg(a) > fp8_neg(b); + } + return a > b; +} + +// fp8_max(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Return the larger of two FP8 values +pub fn fp8_max(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + return if (fp8_is_greater(a, b)) a else b; +} + +// fp8_min(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Return the smaller of two FP8 values +pub fn fp8_min(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + return if (fp8_is_greater(a, b)) b else a; +} + +// fp8_clamp(value: FP8E5M2, min: FP8E5M2, max: FP8E5M2) -> FP8E5M2 +// Clamp value between min and max +pub fn fp8_clamp(value: FP8E5M2, min: FP8E5M2, max: FP8E5M2) FP8E5M2 { + return fp8_min(fp8_max(value, min), max); +} + +// fp8_lerp(a: FP8E5M2, b: FP8E5M2, t: f32) -> FP8E5M2 +// Linear interpolation between a and b +pub fn fp8_lerp(a: FP8E5M2, b: FP8E5M2, t: f32) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const result = fa + (fb - fa) * t; + return fp8_encode_f32(result); +} + +// fp8_fma(a: FP8E5M2, b: FP8E5M2, c: FP8E5M2) -> FP8E5M2 +// Fused multiply-add: a * b + c +pub fn fp8_fma(a: FP8E5M2, b: FP8E5M2, c: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const fc = fp8_decode_f32(c); + return fp8_encode_f32(fa * fb + fc); +} + +// fp8_scale(value: FP8E5M2, scale: f32) -> FP8E5M2 +// Scale FP8 value by scalar +pub fn fp8_scale(value: FP8E5M2, scale: f32) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * scale); +} + +// fp8_relu(value: FP8E5M2) -> FP8E5M2 +// ReLU activation: max(0, x) +pub fn fp8_relu(value: FP8E5M2) FP8E5M2 { + return if (fp8_extract_sign(value) < 0) FP8_ZERO_POS else value; +} + +// fp8_sigmoid(value: FP8E5M2) -> FP8E5M2 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn fp8_sigmoid(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// fp8_tanh(value: FP8E5M2) -> FP8E5M2 +// Tanh activation +pub fn fp8_tanh(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(std.math.tanh(fv)); +} + +// fp8_gelu(value: FP8E5M2) -> FP8E5M2 +// GELU activation +pub fn fp8_gelu(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + const sqrt_2_over_pi = 0.7978845608; + const cube_coeff = 0.044715; + const tanh_arg = fv * (1.0 + cube_coeff * fv * fv); + return fp8_encode_f32(0.5 * fv * (1.0 + std.math.tanh(sqrt_2_over_pi * tanh_arg))); +} + +// fp8_swish(value: FP8E5M2) -> FP8E5M2 +// Swish activation: x * sigmoid(x) +pub fn fp8_swish(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * (1.0 / (1.0 + std.math.exp(-fv)))); +} + +// fp8_quantize_to_int4(value: FP8E5M2) -> i8 +// Quantize FP8 to Int4 (range [-8, 7]) +pub fn fp8_quantize_to_int4(value: FP8E5M2) i8 { + const fv = fp8_decode_f32(value); + // E5M2 has wider range, so scale differently + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// fp8_quantize_to_int8(value: FP8E5M2) -> i8 +// Quantize FP8 to Int8 (range [-128, 127]) +pub fn fp8_quantize_to_int8(value: FP8E5M2) i8 { + const fv = fp8_decode_f32(value); + // E5M2 has wider range, so scale differently + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 127.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 127) { + result = 127; + } else if (result < -128) { + result = -128; + } + return @as(i8, @intCast(result)); +} + +// fp8_from_f32_scaled(value: f32, scale: f32) -> FP8E5M2 +// Encode f32 to FP8 with custom scale factor +// Useful for quantization with specific dynamic range +pub fn fp8_from_f32_scaled(value: f32, scale: f32) FP8E5M2 { + return fp8_encode_f32(value * scale); +} + +// fp8_to_f32_scaled(value: FP8E5M2, scale: f32) -> f32 +// Decode FP8 to f32 with custom scale factor +pub fn fp8_to_f32_scaled(value: FP8E5M2, scale: f32) f32 { + return fp8_decode_f32(value) / scale; +} + +// fp8_dot_product(a: []const FP8E5M2, b: []const FP8E5M2) -> FP8E5M2 +// Dot product of two FP8 arrays (fixed size 8 for example) +// Returns accumulated FP8 value +pub fn fp8_dot_product(a: []const FP8E5M2, b: []const FP8E5M2) FP8E5M2 { + var sum: f32 = 0.0; + for (a, 0..) |val_a, i| { + if (i >= b.len) break; + const fa = fp8_decode_f32(val_a); + const fb = fp8_decode_f32(b[i]); + sum += fa * fb; + } + return fp8_encode_f32(sum); +} + +// fp8_vector_add(a: []const FP8E5M2, b: []const FP8E5M2, result: []FP8E5M2) -> void +// Vector addition: result[i] = a[i] + b[i] +pub fn fp8_vector_add(a: []const FP8E5M2, b: []const FP8E5M2, result: []FP8E5M2) void { + const len = @min(a.len, b.len); + for (0..len) |i| { + result[i] = fp8_add(a[i], b[i]); + } +} + +// fp8_vector_scale(vec: []const FP8E5M2, scale: FP8E5M2, result: []FP8E5M2) -> void +// Vector scale: result[i] = vec[i] * scale +pub fn fp8_vector_scale(vec: []const FP8E5M2, scale: FP8E5M2, result: []FP8E5M2) void { + const scale_f = fp8_decode_f32(scale); + for (vec, 0..) |val, i| { + result[i] = fp8_encode_f32(fp8_decode_f32(val) * scale_f); + } +} + +// fp8_vector_sum(vec: []const FP8E5M2) -> FP8E5M2 +// Sum all elements of a vector +pub fn fp8_vector_sum(vec: []const FP8E5M2) FP8E5M2 { + var sum: f32 = 0.0; + for (vec) |val| { + sum += fp8_decode_f32(val); + } + return fp8_encode_f32(sum); +} + +// fp8_vector_mean(vec: []const FP8E5M2) -> FP8E5M2 +// Mean of all elements of a vector +pub fn fp8_vector_mean(vec: []const FP8E5M2) FP8E5M2 { + if (vec.len == 0) { + return FP8_ZERO_POS; + } + const sum = fp8_vector_sum(vec); + return fp8_div(sum, fp8_from_f32(@as(f32, @floatFromInt(vec.len)))); +} + +// fp8_softmax(vec: []const FP8E5M2, result: []FP8E5M2) -> void +// Softmax activation: result[i] = exp(vec[i]) / sum(exp(vec)) +pub fn fp8_softmax(vec: []const FP8E5M2, result: []FP8E5M2) void { + // Find max for numerical stability + var max_val: f32 = -std.math.inf(f32); + for (vec) |val| { + const fv = fp8_decode_f32(val); + if (fv > max_val) { + max_val = fv; + } + } + + // Compute exp values and sum + var sum_exp: f32 = 0.0; + for (vec, 0..) |val, i| { + const fv = fp8_decode_f32(val); + const exp_val = std.math.exp(fv - max_val); + result[i] = fp8_encode_f32(exp_val); + sum_exp += exp_val; + } + + // Normalize + const inv_sum = 1.0 / sum_exp; + for (result, 0..) |val, i| { + const fv = fp8_decode_f32(val); + result[i] = fp8_encode_f32(fv * inv_sum); + } +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fp8_extract_sign_positive" { + given value = 0x3C + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "fp8_extract_sign_negative" { + given value = 0xBC + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "fp8_extract_exponent_max" { + given value = 0x7C + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == EXP_MAX); +} + +test "fp8_extract_exponent_zero" { + given value = 0x03 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == 0); +} + +test "fp8_extract_mantissa_max" { + given value = 0x03 + try std.testing.expect(mant = fp8_extract_mantissa(value)); + try std.testing.expect(mant == MANT_MASK); +} + +test "fp8_is_zero_positive" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_POS) == true); +} + +test "fp8_is_zero_negative" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_NEG) == true); +} + +test "fp8_is_zero_nonzero" { + try std.testing.expect(fp8_is_zero(0x01) == false); +} + +test "fp8_is_inf_positive" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true); +} + +test "fp8_is_inf_negative" { + try std.testing.expect(fp8_is_inf(FP8_INF_NEG) == true); +} + +test "fp8_is_nan_positive" { + try std.testing.expect(fp8_is_nan(FP8_NAN_POS) == true); +} + +test "fp8_is_nan_negative" { + try std.testing.expect(fp8_is_nan(FP8_NAN_NEG) == true); +} + +test "fp8_is_inf_not_nan" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true and fp8_is_nan(FP8_INF_POS) == false); +} + +test "fp8_is_special_inf" { + try std.testing.expect(fp8_is_special(FP8_INF_POS) == true); +} + +test "fp8_is_special_nan" { + try std.testing.expect(fp8_is_special(FP8_NAN_POS) == true); +} + +test "fp8_is_subnormal_true" { + given value = 0x01 + try std.testing.expect(fp8_is_subnormal(value) == true); +} + +test "fp8_is_subnormal_false_normal" { + given value = 0x04 + try std.testing.expect(fp8_is_subnormal(value) == false); +} + +test "fp8_is_subnormal_false_zero" { + try std.testing.expect(fp8_is_subnormal(FP8_ZERO_POS) == false); +} + +test "fp8_encode_f32_zero" { + given fp = fp8_encode_f32(0.0) + try std.testing.expect(fp == FP8_ZERO_POS); +} + +test "fp8_encode_f32_negative_zero" { + given fp = fp8_encode_f32(-0.0) + try std.testing.expect(fp == FP8_ZERO_NEG); +} + +test "fp8_encode_f32_one" { + given fp = fp8_encode_f32(1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 1.0) < 0.15); +} + +test "fp8_encode_f32_negative_one" { + given fp = fp8_encode_f32(-1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded + 1.0) < 0.15); +} + +test "fp8_encode_f32_ten" { + given fp = fp8_encode_f32(10.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 10.0) < 1.0); +} + +test "fp8_encode_f32_hundred" { + given fp = fp8_encode_f32(100.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 100.0) < 10.0); +} + +test "fp8_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "fp8_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "fp8_encode_f32_nan" { + given fp = fp8_encode_f32(std.math.nan(f32)) + try std.testing.expect(fp8_is_nan(fp) == true); +} + +test "fp8_encode_f32_inf" { + given fp = fp8_encode_f32(std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_encode_f32_neg_inf" { + given fp = fp8_encode_f32(-std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_decode_f32_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_POS) == 0.0); +} + +test "fp8_decode_f32_negative_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_NEG) == -0.0); +} + +test "fp8_decode_f32_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_POS)) == true); +} + +test "fp8_decode_f32_neg_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_NEG)) == true); +} + +test "fp8_decode_f32_nan" { + try std.testing.expect(std.math.isNan(fp8_decode_f32(FP8_NAN_POS)) == true); +} + +test "fp8_add_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(20.0)); + try std.testing.expect(result = fp8_add(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 30.0) < 3.0); +} + +test "fp8_sub_simple" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(30.0)); + try std.testing.expect(result = fp8_sub(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 3.0); +} + +test "fp8_mul_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(result = fp8_mul(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 15.0); +} + +test "fp8_div_simple" { + given a = fp8_encode_f32(100.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "fp8_div_by_zero_positive" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_POS); +} + +test "fp8_div_by_zero_negative" { + given a = fp8_encode_f32(-50.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_NEG); +} + +test "fp8_sqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.3); +} + +test "fp8_sqrt_negative" { + given value = fp8_encode_f32(-4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(fp8_is_nan(result) == true); +} + +test "fp8_rsqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_rsqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_abs_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(result = fp8_abs(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_abs_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(abs_val = fp8_abs(value)); + try std.testing.expect(fp8_extract_sign(abs_val) == 0); +} + +test "fp8_neg_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) < 0); +} + +test "fp8_neg_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) == 0); +} + +test "fp8_is_equal_true" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(fp8_is_equal(value, value) == true); +} + +test "fp8_is_equal_false" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(20.0)); + try std.testing.expect(fp8_is_equal(a, b) == false); +} + +test "fp8_is_equal_nan" { + try std.testing.expect(fp8_is_equal(FP8_NAN_POS, FP8_NAN_POS) == false); +} + +test "fp8_is_equal_zero" { + try std.testing.expect(fp8_is_equal(FP8_ZERO_POS, FP8_ZERO_NEG) == true); +} + +test "fp8_is_greater_positive" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(30.0)); + try std.testing.expect(fp8_is_greater(a, b) == true); +} + +test "fp8_max_returns_larger" { + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 40.0); +} + +test "fp8_min_returns_smaller" { + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 40.0); +} + +test "fp8_lerp_zero" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, a) == true); +} + +test "fp8_lerp_one" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, b) == true); +} + +test "fp8_fma_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(c = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_fma(a, b, c)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 105.0) < 15.0); +} + +test "fp8_scale_up" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(scale = 10.0); + try std.testing.expect(result = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 1.0); +} + +test "fp8_scale_down" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(scale = 0.1); + try std.testing.expect(result = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "fp8_relu_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_relu_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_relu_zero" { + given result = fp8_relu(FP8_ZERO_POS) + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_sigmoid_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_sigmoid_positive_large" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "fp8_sigmoid_negative_large" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 0.1); +} + +test "fp8_tanh_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded) < 0.1); +} + +test "fp8_tanh_positive_large" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "fp8_tanh_negative_large" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= -0.9); +} + +test "fp8_gelu_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_gelu(value)); + try std.testing.expect(fp8_extract_sign(result) == 0); +} + +test "fp8_swish_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_swish_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded > 0.0); +} + +test "fp8_quantize_to_int4_max" { + given value = fp8_encode_f32(100.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "fp8_quantize_to_int4_min" { + given value = fp8_encode_f32(-100.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "fp8_quantize_to_int8_max" { + given value = fp8_encode_f32(200.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result >= 100); +} + +test "fp8_quantize_to_int8_min" { + given value = fp8_encode_f32(-200.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result <= -100); +} + +test "fp8_from_f32_scaled_up" { + given value = 1.0 + try std.testing.expect(scale = 100.0); + try std.testing.expect(result = fp8_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 10.0); +} + +test "fp8_from_f32_scaled_down" { + given value = 100.0 + try std.testing.expect(scale = 0.01); + try std.testing.expect(result = fp8_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "fp8_to_f32_scaled_up" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(scale = 0.01); + try std.testing.expect(result = fp8_to_f32_scaled(value, scale)); + try std.testing.expect(abs(result - 0.01) < 0.005); +} + +test "fp8_dot_product_simple" { + given a = [_]FP8E5M2{fp8_encode_f32(1.0), fp8_encode_f32(2.0), fp8_encode_f32(3.0), fp8_encode_f32(4.0)} + try std.testing.expect(b = [_]FP8E5M2{fp8_encode_f32(2.0), fp8_encode_f32(2.0), fp8_encode_f32(2.0), fp8_encode_f32(2.0)}); + try std.testing.expect(result = fp8_dot_product(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "fp8_vector_sum_simple" { + given vec = [_]FP8E5M2{fp8_encode_f32(1.0), fp8_encode_f32(2.0), fp8_encode_f32(3.0), fp8_encode_f32(4.0)} + try std.testing.expect(result = fp8_vector_sum(vec)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 2.0); +} + +test "fp8_vector_mean_simple" { + given vec = [_]FP8E5M2{fp8_encode_f32(2.0), fp8_encode_f32(4.0), fp8_encode_f32(6.0), fp8_encode_f32(8.0)} + try std.testing.expect(result = fp8_vector_mean(vec)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 5.0) < 1.0); +} + +test "fp8_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 15); + try std.testing.expect(mant = 2); + try std.testing.expect(fp = fp8_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = fp8_extract_sign(fp)); + try std.testing.expect(extracted_exp = fp8_extract_exponent(fp)); + try std.testing.expect(extracted_mant = fp8_extract_mantissa(fp)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fp8_bits_constant + assert BITS == 8 + +invariant fp8_sign_bits_one + assert SIGN_BITS == 1 + +invariant fp8_exp_bits_five + assert EXP_BITS == 5 + +invariant fp8_mant_bits_two + assert MANT_BITS == 2 + +invariant fp8_exp_bias_fifteen + assert EXP_BIAS == 15 + +invariant fp8_exp_max_all_ones + assert EXP_MAX == 0x1F + +invariant fp8_sign_mask_correct + assert SIGN_MASK == 0x80 + +invariant fp8_exp_mask_correct + assert EXP_MASK == 0x7C + +invariant fp8_mant_mask_correct + assert MANT_MASK == 0x03 + +invariant fp8_masks_sum_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFF + +invariant fp8_zero_pos_no_sign + assert (FP8_ZERO_POS & SIGN_MASK) == 0 + +invariant fp8_zero_neg_has_sign + assert (FP8_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant fp8_inf_pos_max_exp + assert (FP8_INF_POS & EXP_MASK) == EXP_MASK + +invariant fp8_inf_pos_no_mant + assert (FP8_INF_POS & MANT_MASK) == 0 + +invariant fp8_nan_has_mantissa + assert (FP8_NAN_POS & MANT_MASK) != 0 + +invariant fp8_abs_removes_sign + assert fp8_abs(FP8_ZERO_NEG) == FP8_ZERO_POS + +invariant fp8_neg_toggles_sign + assert (fp8_neg(FP8_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant fp8_add_zero_identity + given value = fp8_encode_f32(50.0) + assert fp8_is_equal(fp8_add(value, FP8_ZERO_POS), value) == true + +invariant fp8_sub_zero_identity + given value = fp8_encode_f32(50.0) + assert fp8_is_equal(fp8_sub(value, FP8_ZERO_POS), value) == true + +invariant fp8_mul_one_identity + given value = fp8_encode_f32(50.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_mul(value, one), value) == true or fp8_magnitude(value) > 10.0 + +invariant fp8_div_one_identity + given value = fp8_encode_f32(50.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_div(value, one), value) == true + +invariant fp8_max_ge_both + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(fp8_is_greater(result, a) == true and fp8_is_greater(result, b) == true or fp8_is_equal(result, b) == true); + +invariant fp8_min_le_both + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(fp8_is_greater(a, result) == true or fp8_is_equal(a, result) == true); + +invariant fp8_relu_non_negative + given result = fp8_relu(fp8_encode_f32(50.0)) + try std.testing.expect(fp8_extract_sign(result) == 0); + +invariant fp8_lerp_zero_start + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + assert fp8_is_equal(fp8_lerp(a, b, 0.0), a) == true + +invariant fp8_lerp_one_end + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + assert fp8_is_equal(fp8_lerp(a, b, 1.0), b) == true + +invariant fp8_sqrt_square_close + given value = fp8_encode_f32(4.0) + try std.testing.expect(sqrt_val = fp8_sqrt(value)); + try std.testing.expect(sq_val = fp8_mul(sqrt_val, sqrt_val)); + try std.testing.expect(decoded = fp8_decode_f32(sq_val)); + try std.testing.expect(abs(decoded - 4.0) < 0.5); + +invariant fp8_quantize_to_int4_in_range + given result = fp8_quantize_to_int4(fp8_encode_f32(50.0)) + assert result >= -8 and result <= 7 + +invariant fp8_quantize_to_int8_in_range + given result = fp8_quantize_to_int8(fp8_encode_f32(50.0)) + assert result >= -128 and result <= 127 + +invariant fp8_scale_roundtrip + given value = fp8_encode_f32(50.0) + try std.testing.expect(scale = 2.0); + try std.testing.expect(scaled = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_to_f32_scaled(scaled, scale)); + try std.testing.expect(abs(decoded - fp8_decode_f32(value)) < 5.0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fp8_encode_f32_latency + measure: nanoseconds to fp8_encode_f32(10.5) + target: < 50ns + +bench fp8_decode_f32_latency + measure: nanoseconds to fp8_decode_f32(0x3C) + target: < 50ns + +bench fp8_add_latency + measure: nanoseconds to fp8_add(0x3C, 0x48) + target: < 100ns + +bench fp8_mul_latency + measure: nanoseconds to fp8_mul(0x3C, 0x3C) + target: < 100ns + +bench fp8_sqrt_latency + measure: nanoseconds to fp8_sqrt(0x30) + target: < 100ns + +bench fp8_sigmoid_latency + measure: nanoseconds to fp8_sigmoid(0x30) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/gf128.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/gf128.t27 new file mode 100644 index 0000000000..d343873ad2 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/gf128.t27 @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf128.t27 +// GoldenFloat128 - 128-bit φ-structured floating point with extended range +// NUMERIC-STANDARD-001 Agent 5 (P1) +// Paper §6.7: Extended range for high-dynamic-range applications + +module GF128 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // GF128 bit layout: [S(1) | EEE...EEE(48) | MMM...MMM(79)] + // S: 1 bit (sign) + // E: 48 bits (exponent) - phi-optimized + // M: 79 bits (mantissa) - ultra-high precision + // + // φ-ratio: exp/mant = 48/79 ≈ 0.608 (phi_distance = 0.010) + // Good phi approximation for ultra-high precision + + const BITS : u8 = 128; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 48; + const MANT_BITS : u8 = 79; + + // Bias for exponent (2^47 - 1 = 140737488355327) + const EXP_BIAS : i64 = 140737488355327; + + // φ-ratio: exp/mant = 48/79 = 0.608 (good phi approximation) + const PHI_DISTANCE : f64 = 0.009696042799364053; + + // 2. GoldenFloat128 Type + + struct GF128 { + low : u64, // Lower 64 bits (mantissa bits 0-63) + high : u64, // Upper 64 bits [sign(1) exp(48) mant(15)] + } + + // 3. Encoding/Decoding + + // Encode f64 to GF128 + fn encode(value: f64) -> GF128 { + if (value == 0.0) { + return GF128{ .low = 0, .high = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2_128(abs_val) as i64; + const exp_biased = exp_unbiased + EXP_BIAS; + + // Clamp exponent (28 bits max = 268435455) + const exp_clamped = if (exp_biased > 268435455) { 268435455 } else if (exp_biased < 0) { 0 } else { exp_biased }; + + // Extract mantissa (99 bits) + const mant_low = extract_mantissa_128_low(abs_val, exp_unbiased); + const mant_high = extract_mantissa_128_high(abs_val, exp_unbiased); + + return GF128{ + .high = ((sign << 63) | (exp_clamped << 35) | mant_high), + .low = mant_low + }; + } + + // Decode GF128 to f64 + fn decode(gf: GF128) -> f64 { + const sign = (gf.high >> 63) as u8; + const exp_biased = (gf.high >> 35) & 0xFFFFFFF; + + // Zero + if (exp_biased == 0 && gf.high == 0 && gf.low == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS + 1 + } else { + (exp_biased as i64) - EXP_BIAS + }; + + // Mantissa (99 bits, with implicit 1 for normalized) + const mant_low = gf.low; + const mant_high = (gf.high & 0x7FFFFFFFF); + + const mant_normalized = if (exp_biased == 0) { + mant_to_f64_128(mant_low, mant_high) / 633825300114114700748351602688.0 + } else { + 1.0 + mant_to_f64_128(mant_low, mant_high) / 633825300114114700748351602688.0 + }; + + const value = mant_normalized * pow2_128(exp_unbiased as f64); + + if (sign != 0) { + return -value; + } + return value; + } + + // 4. Format Properties + + fn max_value() -> f64 { + // Max normalized: mant ≈ 2.0, exp = 134217727 + const mant_max = 1.9999999999999998; + const exp_max = (1i64 << 28) - 1 - EXP_BIAS; + return mant_max * pow2_128(exp_max as f64); + } + + fn min_positive() -> f64 { + // Min subnormal: mant ≈ 0, exp = -134217726 + const mant_min = 1.0 / 633825300114114700748351602688.0; + const exp_min = -EXP_BIAS + 1; + return mant_min * pow2_128(exp_min as f64); + } + + fn epsilon() -> f64 { + // Smallest representable difference at 1.0 + return 1.0 / 633825300114114700748351602688.0; + } + + fn dynamic_range() -> f64 { + // Ratio of max to min positive + return max_value() / min_positive(); + } + + // 5. Validation + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF128"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + fn is_extended_range() -> bool { + return EXTENDED_RANGE_MODE; + } + + // 6. Use Cases + + // GF128 is optimal for: + // - High dynamic range imaging (HDR) + // - Astronomical calculations (planetary to quantum scales) + // - Extended precision physics simulations + // - Financial modeling requiring extreme range + // - Scientific computing with exceptional precision + + // Memory: 128 bits = 16 bytes (4x FP32, same as FP128) + const MEMORY_RATIO_VS_FP32 : f32 = 128.0 / 32.0; // 4.0 + + // 7. Helper Functions + + fn floor_log2_128(x: f64) -> i64 { + if (x <= 0.0) { return -9223372036854775808; } + let exp : i64 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa_128_low(value: f64, exp: i64) -> u64 { + // Lower 64 bits of mantissa + const normalized = value / pow2_128(exp as f64); + const frac = normalized - 1.0; + const mant_full = (frac * 633825300114114700748351602689.0) as u128; + return (mant_full & 0xFFFFFFFFFFFFFFFF) as u64; + } + + fn extract_mantissa_128_high(value: f64, exp: i64) -> u64 { + // Upper 35 bits of mantissa + const normalized = value / pow2_128(exp as f64); + const frac = normalized - 1.0; + const mant_full = (frac * 633825300114114700748351602689.0) as u128; + return ((mant_full >> 64) & 0x7FFFFFFFF) as u64; + } + + fn mant_to_f64_128(low: u64, high: u64) -> f64 { + const mant_u128 = ((high as u128) << 64) | (low as u128); + return mant_u128 as f64; + } + + fn pow2_128(exp: f64) -> f64 { + if (exp == 0.0) { return 1.0; } + if (exp < 0.0) { return 1.0 / pow2_128(-exp); } + + let result = 1.0; + let base = 2.0; + let e = exp as i64; + + while (e > 0) { + if (e % 2 == 1) { + result = result * base; + } + base = base * base; + e = e / 2; + } + return result; + } + + // TDD-Inside-Spec: Tests and Invariants for GF128 + + test gf128_decode_zero + given gf = GF128{ .low = 0, .high = 0 } + when value = decode(gf) + then value == 0.0 + + test gf128_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf128_encode_positive_value + given original = 1.5 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 1.5) < 1e-10 + + test gf128_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf128_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf128_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf128_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf128_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 4.0) < 0.01 + + test gf128_validate_format_success + given valid = validate_format() + then valid == true + + test gf128_dynamic_range_enormous + given range = dynamic_range() + then range > 1e300 + + test gf128_is_extended_range_true + given extended = is_extended_range() + then extended == true + + test gf128_encode_large_value + given original = 1e150 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded / original - 1.0) < 0.1 + + test gf128_encode_small_value + given original = 1e-150 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded / original - 1.0) < 0.1 + + test gf128_negative_encoding + given original = -2.5 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded < 0.0 and abs(decoded - (-2.5)) < 1e-10 + + test gf128_sign_bit_negative + given value = -1.0 + and encoded = encode(value) + and sign_bit = (encoded.high >> 63) as u8 + then sign_bit == 1 + + test gf128_sign_bit_positive + given value = 1.0 + and encoded = encode(value) + and sign_bit = (encoded.high >> 63) as u8 + then sign_bit == 0 + + test gf128_phi_ratio_low + given ratio = (EXP_BITS as f64) / (MANT_BITS as f64) + then ratio < 0.3 // Extended range mode + + invariant gf128_bits_constant + assert BITS == 128 + + invariant gf128_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf128_exp_bits_is_forty_eight + assert EXP_BITS == 48 + + invariant gf128_mant_bits_is_seventy_nine + assert MANT_BITS == 79 + + invariant gf128_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf128_phi_distance_within_tolerance + assert PHI_DISTANCE < 0.34 + + invariant gf128_exp_bias_positive + assert EXP_BIAS > 0 + + invariant gf128_exp_bias_is_140737488355327 + assert EXP_BIAS == 140737488355327 + + invariant gf128_extended_range_mode_true + assert EXTENDED_RANGE_MODE == true + + invariant gf128_dynamic_range_enormous + assert dynamic_range() > 1e300 + + invariant gf128_epsilon_less_than_min_positive + assert epsilon() <= min_positive() + + invariant gf128_exp_mant_ratio_is_phi_approx + given ratio = (EXP_BITS as f64) / (MANT_BITS as f64) + then abs(ratio - 0.608) < 0.01 + + invariant gf128_high_low_consistency + given gf = GF128{ .low = 0xFFFFFFFFFFFFFFFF, .high = 0x7FFFFFFFF } + then decode(gf) > 0.0 + + bench gf128_encode_latency + measure: nanoseconds to encode(1.0) + target: < 400ns + + bench gf128_decode_latency + measure: nanoseconds to decode(encode(1.5)) + target: < 200ns + + bench gf128_pow2_latency + measure: nanoseconds to pow2_128(10.0) + target: < 150ns + + bench gf128_encode_decode_roundtrip + measure: nanoseconds to encode(1.0) and decode(encode(1.0)) + target: < 600ns + + bench gf128_dynamic_range_calc + measure: nanoseconds to dynamic_range() + target: < 300ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/gf16_to_fp16.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/gf16_to_fp16.t27 new file mode 100644 index 0000000000..132324be5a --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/gf16_to_fp16.t27 @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: Apache-2.0 + +module gf16-to-fp16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const GF16_SIGN_BITS : u8 = 1; +pub const GF16_EXP_BITS : u8 = 6; +pub const GF16_MANT_BITS : u8 = 9; +pub const GF16_BIAS : i8 = 31; + +pub const FP16_SIGN_BITS : u8 = 1; +pub const FP16_EXP_BITS : u8 = 5; +pub const FP16_MANT_BITS : u8 = 10; +pub const FP16_BIAS : i8 = 15; + +pub const MAX_EXP_DIFF : i8 = 6; // Maximum exponent difference without loss +pub const MANTissa_MASK_GF16 : u16 = 0x01FF; +pub const MANTissa_MASK_FP16 : u16 = 0x03FF; + +// ============================================================================ +// Types +// ============================================================================ + +pub const Gf16 = packed struct { + sign : u1, + exp : u6, + mant : u9, +} + +pub const Fp16 = packed struct { + sign : u1, + exp : u5, + mant : u10, +} + +pub const ConversionResult = struct { + value : u16, + overflow : bool, + underflow : bool, + lost_precision : bool, +} + +pub const ConversionConfig = struct { + round_mode : u8, + overflow_mode : u8, + enable_saturation : bool, +} + +// ============================================================================ +// GF16 Functions +// ============================================================================ + +// gf16_from_bits(bits: u16) -> Gf16 +// Extract GF16 from 16-bit representation +pub fn gf16_from_bits(bits: u16) -> Gf16 { + return Gf16 { + .sign = @as(u1, @truncate((bits >> 15) & 0x01)), + .exp = @as(u6, @truncate((bits >> 9) & 0x3F)), + .mant = @as(u9, @truncate(bits & 0x01FF)), + }; +} + +// gf16_to_bits(gf: Gf16) -> u16 +// Convert GF16 to 16-bit representation +pub fn gf16_to_bits(gf: Gf16) -> u16 { + const sign_field : u16 = @as(u16, gf.sign) << 15; + const exp_field : u16 = @as(u16, gf.exp) << 9; + const mant_field : u16 = @as(u16, gf.mant); + return sign_field | exp_field | mant_field; +} + +// gf16_get_exp_biased(gf: Gf16) -> i8 +// Get exponent with bias applied +pub fn gf16_get_exp_biased(gf: Gf16) -> i8 { + return @as(i8, @bitCast(gf.exp)) - GF16_BIAS; +} + +// gf16_is_zero(gf: Gf16) -> bool +// Check if GF16 is zero +pub fn gf16_is_zero(gf: Gf16) -> bool { + return gf.exp == 0 and gf.mant == 0; +} + +// gf16_is_infinity(gf: Gf16) -> bool +// Check if GF16 is infinity +pub fn gf16_is_infinity(gf: Gf16) -> bool { + return gf.exp == 0x3F and gf.mant == 0; +} + +// gf16_is_nan(gf: Gf16) -> bool +// Check if GF16 is NaN +pub fn gf16_is_nan(gf: Gf16) -> bool { + return gf.exp == 0x3F and gf.mant != 0; +} + +// ============================================================================ +// FP16 Functions +// ============================================================================ + +// fp16_from_bits(bits: u16) -> Fp16 +// Extract FP16 from 16-bit representation +pub fn fp16_from_bits(bits: u16) -> Fp16 { + return Fp16 { + .sign = @as(u1, @truncate((bits >> 15) & 0x01)), + .exp = @as(u5, @truncate((bits >> 10) & 0x1F)), + .mant = @as(u10, @truncate(bits & 0x03FF)), + }; +} + +// fp16_to_bits(fp: Fp16) -> u16 +// Convert FP16 to 16-bit representation +pub fn fp16_to_bits(fp: Fp16) -> u16 { + const sign_field : u16 = @as(u16, fp.sign) << 15; + const exp_field : u16 = @as(u16, fp.exp) << 10; + const mant_field : u16 = @as(u16, fp.mant); + return sign_field | exp_field | mant_field; +} + +// fp16_get_exp_biased(fp: Fp16) -> i8 +// Get exponent with bias applied +pub fn fp16_get_exp_biased(fp: Fp16) -> i8 { + if (fp.exp == 0) { + return -14; // Denormal + } else if (fp.exp == 0x1F) { + return 16; // Infinity/NaN + } + return @as(i8, @bitCast(fp.exp)) - FP16_BIAS; +} + +// fp16_is_zero(fp: Fp16) -> bool +// Check if FP16 is zero +pub fn fp16_is_zero(fp: Fp16) -> bool { + return fp.exp == 0 and fp.mant == 0; +} + +// fp16_is_infinity(fp: Fp16) -> bool +// Check if FP16 is infinity +pub fn fp16_is_infinity(fp: Fp16) -> bool { + return fp.exp == 0x1F and fp.mant == 0; +} + +// fp16_is_nan(fp: Fp16) -> bool +// Check if FP16 is NaN +pub fn fp16_is_nan(fp: Fp16) -> bool { + return fp.exp == 0x1F and fp.mant != 0; +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// conversion_config_init() -> ConversionConfig +// Initialize conversion config +pub fn conversion_config_init() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, // Round to nearest + .overflow_mode = 0, // Clamp + .enable_saturation = true, + }; +} + +// conversion_config_no_saturation() -> ConversionConfig +// Create config without saturation +pub fn conversion_config_no_saturation() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, + .overflow_mode = 1, // Overflow to infinity + .enable_saturation = false, + }; +} + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// gf16_to_fp16_raw(gf: Gf16) -> Fp16 +// Convert GF16 to FP16 (raw, no overflow/underflow handling) +pub fn gf16_to_fp16_raw(gf: Gf16) -> Fp16 { + var exp_i8 = gf16_get_exp_biased(gf); + + if (gf16_is_zero(gf)) { + return Fp16{.sign = gf.sign, .exp = 0, .mant = 0}; + } else if (gf16_is_infinity(gf)) { + return Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 0}; + } else if (gf16_is_nan(gf)) { + return Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 1}; // Quiet NaN + } + + // Adjust exponent bias: GF16 bias 31 -> FP16 bias 15 + exp_i8 = exp_i8 + GF16_BIAS - FP16_BIAS; + + var exp : u5 = 0; + var mant : u10 = 0; + var lost_precision = false; + + if (exp_i8 <= -15) { + // Underflow to zero + exp = 0; + mant = 0; + lost_precision = true; + } else if (exp_i8 >= 16) { + // Overflow to infinity + exp = 0x1F; + mant = 0; + } else { + exp = @as(u5, @truncate(@as(u8, @bitCast(exp_i8)) & 0x1F)); + // Extend mantissa from 9 to 10 bits + mant = @as(u10, gf.mant) << 1; + } + + return Fp16{.sign = gf.sign, .exp = exp, .mant = mant}; +} + +// gf16_to_fp16_with_result(gf: Gf16, config: ConversionConfig) -> ConversionResult +// Convert GF16 to FP16 with full result +pub fn gf16_to_fp16_with_result(gf: Gf16, config: ConversionConfig) -> ConversionResult { + var result : ConversionResult = undefined; + + if (gf16_is_zero(gf)) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0, .mant = 0}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } else if (gf16_is_infinity(gf)) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 0}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } else if (gf16_is_nan(gf)) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 1}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } + + var exp_i8 = gf16_get_exp_biased(gf); + exp_i8 = exp_i8 + GF16_BIAS - FP16_BIAS; + + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + + var exp : u5 = 0; + var mant : u10 = 0; + + if (exp_i8 <= -15) { + // Underflow + if (config.enable_saturation) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0, .mant = 0}); + } else { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0, .mant = 1}); // Smallest denormal + } + result.underflow = true; + result.lost_precision = true; + return result; + } else if (exp_i8 >= 16) { + // Overflow + if (config.overflow_mode == 0 or config.enable_saturation) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0x1E, .mant = 0x3FF}); // Max finite + } else { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 0}); // Infinity + } + result.overflow = true; + return result; + } else { + exp = @as(u5, @truncate(@as(u8, @bitCast(exp_i8)) & 0x1F)); + mant = @as(u10, gf.mant) << 1; + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = exp, .mant = mant}); + return result; + } +} + +// gf16_to_fp16(gf: Gf16) -> Fp16 +// Convert GF16 to FP16 +pub fn gf16_to_fp16(gf: Gf16) -> Fp16 { + const result = gf16_to_fp16_with_result(gf, conversion_config_init()); + return fp16_from_bits(result.value); +} + +// gf16_bits_to_fp16_bits(gf_bits: u16) -> u16 +// Convert GF16 bits to FP16 bits +pub fn gf16_bits_to_fp16_bits(gf_bits: u16) -> u16 { + const gf = gf16_from_bits(gf_bits); + const fp = gf16_to_fp16(gf); + return fp16_to_bits(fp); +} + +// gf16_bits_to_fp16_bits_with_result(gf_bits: u16, config: ConversionConfig) -> ConversionResult +// Convert GF16 bits to FP16 bits with full result +pub fn gf16_bits_to_fp16_bits_with_result(gf_bits: u16, config: ConversionConfig) -> ConversionResult { + const gf = gf16_from_bits(gf_bits); + return gf16_to_fp16_with_result(gf, config); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf16_bits_constants" { + try std.testing.expect(GF16_SIGN_BITS == 1); + try std.testing.expect(GF16_EXP_BITS == 6); + try std.testing.expect(GF16_MANT_BITS == 9); + try std.testing.expect(GF16_BIAS == 31); +} + +test "fp16_bits_constants" { + try std.testing.expect(FP16_SIGN_BITS == 1); + try std.testing.expect(FP16_EXP_BITS == 5); + try std.testing.expect(FP16_MANT_BITS == 10); + try std.testing.expect(FP16_BIAS == 15); +} + +test "mantissa_masks" { + try std.testing.expect(MANTissa_MASK_GF16 == 0x01FF); + try std.testing.expect(MANTissa_MASK_FP16 == 0x03FF); +} + +test "gf16_from_bits_to_bits_roundtrip" { + given bits = 0x1234 + try std.testing.expect(gf = gf16_from_bits(bits)); + try std.testing.expect(result = gf16_to_bits(gf)); + try std.testing.expect(result == bits); +} + +test "gf16_from_bits_structure" { + given gf = gf16_from_bits(0x8000) + try std.testing.expect(gf.sign == 1); + try std.testing.expect(gf.exp == 0); + try std.testing.expect(gf.mant == 0); +} + +test "fp16_from_bits_to_bits_roundtrip" { + given bits = 0x3C00 + try std.testing.expect(fp = fp16_from_bits(bits)); + try std.testing.expect(result = fp16_to_bits(fp)); + try std.testing.expect(result == bits); +} + +test "gf16_is_zero_true" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(gf16_is_zero(gf) == true); +} + +test "gf16_is_zero_false" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(gf16_is_zero(gf) == false); +} + +test "gf16_is_infinity_true" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(gf16_is_infinity(gf) == true); +} + +test "gf16_is_infinity_false" { + given gf = Gf16{.sign = 0, .exp = 0x3E, .mant = 0} + try std.testing.expect(gf16_is_infinity(gf) == false); +} + +test "gf16_is_nan_true" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(gf16_is_nan(gf) == true); +} + +test "fp16_is_zero_true" { + given fp = Fp16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp16_is_zero(fp) == true); +} + +test "fp16_is_infinity_true" { + given fp = Fp16{.sign = 0, .exp = 0x1F, .mant = 0} + try std.testing.expect(fp16_is_infinity(fp) == true); +} + +test "conversion_config_init_structure" { + given config = conversion_config_init() + try std.testing.expect(config.round_mode == 0); + try std.testing.expect(config.enable_saturation == true); +} + +test "conversion_config_no_saturation_structure" { + given config = conversion_config_no_saturation() + try std.testing.expect(config.enable_saturation == false); +} + +test "gf16_to_fp16_zero" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.exp == 0); + try std.testing.expect(fp.mant == 0); +} + +test "gf16_to_fp16_infinity" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.exp == 0x1F); + try std.testing.expect(fp.mant == 0); +} + +test "gf16_to_fp16_nan" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.exp == 0x1F); + try std.testing.expect(fp.mant == 1); +} + +test "gf16_to_fp16_preserves_sign" { + given gf_pos = Gf16{.sign = 1, .exp = 1, .mant = 0} + try std.testing.expect(gf_neg = Gf16{.sign = 0, .exp = 1, .mant = 0}); + try std.testing.expect(fp_pos = gf16_to_fp16(gf_pos)); + try std.testing.expect(fp_neg = gf16_to_fp16(gf_neg)); + try std.testing.expect(fp_pos.sign == 1 and fp_neg.sign == 0); +} + +test "gf16_to_fp16_with_result_zero" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.overflow == false); + try std.testing.expect(result.underflow == false); + try std.testing.expect(result.lost_precision == false); +} + +test "gf16_bits_to_fp16_bits_zero" { + given config = conversion_config_init() + try std.testing.expect(gf16_bits_to_fp16_bits(0x0000, config) == 0x0000); +} + +test "gf16_bits_to_fp16_bits_roundtrip" { + given config = conversion_config_init() + // Normal value + try std.testing.expect(gf_bits = 0x2000 // exp=32, mant=0 -> value ~1.0 in GF16); + try std.testing.expect(fp_bits = gf16_bits_to_fp16_bits(gf_bits)); + try std.testing.expect((fp_bits & 0x8000) == (gf_bits & 0x8000) // Sign preserved); +} + +test "gf16_to_fp16_with_result_underflow" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 1} // Very small value + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.underflow == true or result.lost_precision == true); +} + +test "gf16_to_fp16_with_result_overflow" { + given gf = Gf16{.sign = 0, .exp = 62, .mant = 0} // Very large value + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.overflow == true); +} + +test "gf16_to_fp16_with_result_saturation" { + given gf = Gf16{.sign = 0, .exp = 62, .mant = 0} // Very large value + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.value != 0x7C00 // Not infinity, but max finite); +} + +test "gf16_to_fp16_with_result_no_saturation_overflow" { + given gf = Gf16{.sign = 0, .exp = 62, .mant = 0} // Very large value + try std.testing.expect(config = conversion_config_no_saturation()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.value == 0x7C00 // Infinity); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf16_bit_count + assert GF16_SIGN_BITS + GF16_EXP_BITS + GF16_MANT_BITS == 16 + +invariant fp16_bit_count + assert FP16_SIGN_BITS + FP16_EXP_BITS + FP16_MANT_BITS == 16 + +invariant gf16_exp_width + assert GF16_EXP_BITS == 6 + +invariant fp16_exp_width + assert FP16_EXP_BITS == 5 + +invariant gf16_mant_width + assert GF16_MANT_BITS == 9 + +invariant fp16_mant_width + assert FP16_MANT_BITS == 10 + +invariant mantissa_masks_correct + assert MANTissa_MASK_GF16 == (@as(u16, 1) << GF16_MANT_BITS) - 1 + try std.testing.expect(MANTissa_MASK_FP16 == (@as(u16, 1) << FP16_MANT_BITS) - 1); + +invariant gf16_from_bits_roundtrip + given bits = 0x5678 + assert gf16_to_bits(gf16_from_bits(bits)) == bits + +invariant fp16_from_bits_roundtrip + given bits = 0x4567 + assert fp16_to_bits(fp16_from_bits(bits)) == bits + +invariant gf16_zero_exp_zero_mant_zero + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + assert gf16_is_zero(gf) and gf.exp == 0 and gf.mant == 0 + +invariant gf16_infinity_exp_max_mant_zero + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + assert gf16_is_infinity(gf) and not gf16_is_nan(gf) + +invariant fp16_infinity_exp_max_mant_zero + given fp = Fp16{.sign = 0, .exp = 0x1F, .mant = 0} + assert fp16_is_infinity(fp) and not fp16_is_nan(fp) + +invariant conversion_config_init_saturation_enabled + given config = conversion_config_init() + assert config.enable_saturation == true + +invariant conversion_config_no_saturation_disabled + given config = conversion_config_no_saturation() + assert config.enable_saturation == false + +invariant gf16_to_fp16_zero_preserves + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.sign == gf.sign); + +invariant gf16_to_fp16_infinity_preserves_sign + given gf_pos = Gf16{.sign = 1, .exp = 0x3F, .mant = 0} + try std.testing.expect(fp_pos = gf16_to_fp16(gf_pos)); + try std.testing.expect(fp_pos.sign == 1); + +invariant gf16_to_fp16_nan_preserves_sign + given gf = Gf16{.sign = 1, .exp = 0x3F, .mant = 1} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.sign == gf.sign); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf16_from_bits_latency + measure: nanoseconds to gf16_from_bits(0x1234) + target: < 20ns + +bench gf16_to_bits_latency + measure: nanoseconds to gf16_to_bits(gf16_from_bits(0x1234)) + target: < 20ns + +bench fp16_from_bits_latency + measure: nanoseconds to fp16_from_bits(0x3C00) + target: < 20ns + +bench fp16_to_bits_latency + measure: nanoseconds to fp16_to_bits(fp16_from_bits(0x3C00)) + target: < 20ns + +bench gf16_to_fp16_latency + measure: nanoseconds to gf16_to_fp16(gf16_from_bits(0x4000)) + target: < 30ns + +bench gf16_to_fp16_with_result_latency + measure: nanoseconds to gf16_to_fp16_with_result(gf16_from_bits(0x4000), conversion_config_init()) + target: < 40ns + +bench gf16_bits_to_fp16_bits_latency + measure: nanoseconds to gf16_bits_to_fp16_bits(0x4000) + target: < 30ns + +bench conversion_config_init_latency + measure: nanoseconds to conversion_config_init() + target: < 15ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/gf16_to_posit16.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/gf16_to_posit16.t27 new file mode 100644 index 0000000000..6f0189efe3 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/gf16_to_posit16.t27 @@ -0,0 +1,664 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf16_to_posit16.t27 — GF16 to Posit16 Converter +; GoldenFloat16 to Posit type 16 (unum 1.0) format conversion +; φ² + 1/φ² = 3 | TRINITY + +module gf16-to-posit16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const GF16_BITS : u8 = 16; +pub const GF16_SIGN_BIT : u8 = 15; +pub const GF16_EXP_BITS : u8 = 6; +pub const GF16_MANT_BITS : u8 = 9; +pub const GF16_BIAS : i8 = 31; + +pub const POSIT16_BITS : u8 = 16; +pub const POSIT16_ES : u8 = 1; // Exponent scale for posit16 +pub const POSIT16_MAX_EXP : i8 = 7; // Maximum useed exponent for ES=1 + +pub const POSIT16_REGIME_BITS : u8 = 2; +pub const POSIT16_MANT_BITS : u8 = 12; // 14 bits total - 2 regime - ES + +pub const MAX_EXP_DIFF : i8 = 5; + +// ============================================================================ +// Types +// ============================================================================ + +pub const Gf16 = packed struct { + sign : u1, + exp : u6, + mant : u9, +} + +pub const Posit16 = struct { + bits : u16, +} + +pub const PositComponents = struct { + sign : bool, + regime : i8, + exponent : u8, + mantissa : u16, +} + +pub const ConversionResult = struct { + bits : u16, + overflow : bool, + underflow : bool, + inexact : bool, +} + +pub const ConversionConfig = struct { + round_mode : u8, + clamp_overflow : bool, +} + +// ============================================================================ +// GF16 Functions +// ============================================================================ + +// gf16_from_bits(bits: u16) -> Gf16 +// Extract GF16 from 16-bit representation +pub fn gf16_from_bits(bits: u16) -> Gf16 { + return Gf16 { + .sign = @as(u1, @truncate((bits >> 15) & 0x01)), + .exp = @as(u6, @truncate((bits >> 9) & 0x3F)), + .mant = @as(u9, @truncate(bits & 0x01FF)), + }; +} + +// gf16_to_bits(gf: Gf16) -> u16 +// Convert GF16 to 16-bit representation +pub fn gf16_to_bits(gf: Gf16) -> u16 { + const sign_field : u16 = @as(u16, gf.sign) << 15; + const exp_field : u16 = @as(u16, gf.exp) << 9; + const mant_field : u16 = @as(u16, gf.mant); + return sign_field | exp_field | mant_field; +} + +// gf16_is_zero(gf: Gf16) -> bool +// Check if GF16 is zero +pub fn gf16_is_zero(gf: Gf16) -> bool { + return gf.exp == 0 and gf.mant == 0; +} + +// gf16_is_subnormal(gf: Gf16) -> bool +// Check if GF16 is subnormal +pub fn gf16_is_subnormal(gf: Gf16) -> bool { + return gf.exp == 0 and gf.mant != 0; +} + +// gf16_is_normal(gf: Gf16) -> bool +// Check if GF16 is normal +pub fn gf16_is_normal(gf: Gf16) -> bool { + return gf.exp != 0 and gf.exp != 0x3F; +} + +// gf16_is_infinity(gf: Gf16) -> bool +// Check if GF16 is infinity +pub fn gf16_is_infinity(gf: Gf16) -> bool { + return gf.exp == 0x3F and gf.mant == 0; +} + +// gf16_is_nan(gf: Gf16) -> bool +// Check if GF16 is NaN +pub fn gf16_is_nan(gf: Gf16) -> bool { + return gf.exp == 0x3F and gf.mant != 0; +} + +// gf16_get_exp_biased(gf: Gf16) -> i8 +// Get exponent with bias applied +pub fn gf16_get_exp_biased(gf: Gf16) -> i8 { + return @as(i8, @bitCast(gf.exp)) - GF16_BIAS; +} + +// ============================================================================ +// Posit16 Functions +// ============================================================================ + +// posit16_from_bits(bits: u16) -> Posit16 +// Create Posit16 from bits +pub fn posit16_from_bits(bits: u16) -> Posit16 { + return Posit16{.bits = bits}; +} + +// posit16_to_bits(p: Posit16) -> u16 +// Get bits from Posit16 +pub fn posit16_to_bits(p: Posit16) -> u16 { + return p.bits; +} + +// posit16_is_zero(p: Posit16) -> bool +// Check if posit16 is zero +pub fn posit16_is_zero(p: Posit16) -> bool { + return p.bits == 0x0000; +} + +// posit16_is_one(p: Posit16) -> bool +// Check if posit16 is 1.0 +pub fn posit16_is_one(p: Posit16) -> bool { + return p.bits == 0x4000; +} + +// posit16_is_neg_one(p: Posit16) -> bool +// Check if posit16 is -1.0 +pub fn posit16_is_neg_one(p: Posit16) -> bool { + return p.bits == 0xC000; +} + +// posit16_is_naR(p: Posit16) -> bool +// Check if posit16 is NaR (Not a Real) +pub fn posit16_is_naR(p: Posit16) -> bool { + return p.bits == 0x8000; +} + +// posit16_get_sign(p: Posit16) -> bool +// Get sign bit +pub fn posit16_get_sign(p: Posit16) -> bool { + return (p.bits >> 15) == 1; +} + +// posit16_decode(p: Posit16) -> PositComponents +// Decode posit16 into components +pub fn posit16_decode(p: Posit16) -> PositComponents { + var bits = p.bits; + var sign = false; + + if (bits & 0x8000 != 0) { + sign = true; + bits = ~bits + 1; // Two's complement + } + + if (bits == 0) { + return PositComponents{.sign = sign, .regime = 0, .exponent = 0, .mantissa = 0}; + } + + var regime = 0; + var regime_bits : u8 = 0; + + // Find regime + if (bits & 0x4000 != 0) { + // Positive regime: starts with 10 + while (bits & 0x4000 != 0) { + regime += 1; + if (regime > 16) break; + bits <<= 1; + regime_bits += 1; + } + } else { + // Negative regime: starts with 01 + while (bits & 0x4000 == 0) { + regime -= 1; + if (regime < -16) break; + bits <<= 1; + regime_bits += 1; + } + } + + bits <<= 1; // Skip last regime bit + + // Extract exponent + var exponent : u8 = 0; + var exp_bit : u8 = 0; + while (exp_bit < POSIT16_ES and bits != 0) { + if (bits & 0x8000 != 0) { + exponent |= (@as(u8, 1) << (POSIT16_ES - 1 - exp_bit)); + } + bits <<= 1; + exp_bit += 1; + } + + // Extract mantissa + var mantissa : u16 = 0; + var mant_bit : u8 = 0; + while (mant_bit < POSIT16_MANT_BITS and bits != 0) { + if (bits & 0x8000 != 0) { + mantissa |= (@as(u16, 1) << (POSIT16_MANT_BITS - 1 - mant_bit)); + } + bits <<= 1; + mant_bit += 1; + } + + return PositComponents{ + .sign = sign, + .regime = regime, + .exponent = exponent, + .mantissa = mantissa, + }; +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// conversion_config_init() -> ConversionConfig +// Initialize conversion config +pub fn conversion_config_init() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, // Round to nearest + .clamp_overflow = true, + }; +} + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// gf16_to_posit16_raw(gf: Gf16) -> Posit16 +// Convert GF16 to Posit16 (raw) +pub fn gf16_to_posit16_raw(gf: Gf16) -> Posit16 { + if (gf16_is_zero(gf)) { + return posit16_from_bits(@as(u16, @bitCast(gf.sign)) << 15); + } else if (gf16_is_infinity(gf)) { + // Positive infinity maps to NaR + return posit16_from_bits(@as(u16, @bitCast(gf.sign)) << 15); + } else if (gf16_is_nan(gf)) { + return posit16_from_bits(0x8000); // NaR + } + + var exp_i8 = gf16_get_exp_biased(gf); + var sign = gf.sign == 1; + + // Map GF16 exponent to posit regime/exponent + // GF16 bias 31, ES=1, so exponent range is -30 to 30 + // Posit16 regime range: -14 to 14 (2-bit regime) + var regime : i8 = 0; + var exponent : u8 = 0; + + if (exp_i8 > POSIT16_MAX_EXP) { + // Use larger regime + regime = exp_i8 - POSIT16_MAX_EXP; + exponent = 0; + } else if (exp_i8 < -POSIT16_MAX_EXP) { + // Use negative regime + regime = exp_i8 + POSIT16_MAX_EXP; + exponent = 0; + } else { + regime = 0; + exponent = @as(u8, @truncate(@as(u8, @bitCast(exp_i8 + POSIT16_MAX_EXP)))); + } + + // Clamp regime to valid range + if (regime > 14) regime = 14; + if (regime < -14) regime = -14; + + // Build posit bits + var result_bits : u16 = 0; + + // Sign + if (sign) result_bits = 0x8000; + + // Regime encoding + var temp_bits : u16 = if (regime >= 0) 0x4000 else 0x2000; + var regime_count = if (regime >= 0) regime else -regime; + + for (0..@as(usize, @intCast(regime_count))) |_| { + if (regime >= 0) { + result_bits |= temp_bits; + if (temp_bits != 0x8000) temp_bits >>= 1; + } else { + result_bits |= temp_bits; + if (temp_bits != 0) temp_bits >>= 1; + } + } + + // Exponent (ES bits) + result_bits |= @as(u16, exponent) << (14 - POSIT16_ES); + + // Mantissa (9 to 12 bits, pad with zeros) + var mant : u16 = @as(u16, gf.mant); + mant <<= 3; // 9 bits -> 12 bits + result_bits |= mant & 0x0FFF; + + return posit16_from_bits(result_bits); +} + +// gf16_to_posit16_with_result(gf: Gf16, config: ConversionConfig) -> ConversionResult +// Convert GF16 to Posit16 with full result +pub fn gf16_to_posit16_with_result(gf: Gf16, config: ConversionConfig) -> ConversionResult { + var result : ConversionResult = undefined; + + if (gf16_is_zero(gf)) { + result.bits = @as(u16, @bitCast(gf.sign)) << 15; + result.overflow = false; + result.underflow = false; + result.inexact = false; + return result; + } else if (gf16_is_infinity(gf) or gf16_is_nan(gf)) { + result.bits = 0x8000; + result.overflow = false; + result.underflow = false; + result.inexact = true; + return result; + } + + const posit = gf16_to_posit16_raw(gf); + result.bits = posit16_to_bits(posit); + + // Check for overflow/underflow based on exponent + var exp_i8 = gf16_get_exp_biased(gf); + result.overflow = exp_i8 > 30; + result.underflow = exp_i8 < -30; + + // GF16 mantissa is 9 bits, posit16 mantissa is 12 bits (no precision loss) + result.inexact = false; + + if (result.overflow and config.clamp_overflow) { + result.bits = if (gf.sign == 1) 0xC000 else 0x7FFF; // Clamp to max + } + + return result; +} + +// gf16_to_posit16(gf: Gf16) -> Posit16 +// Convert GF16 to Posit16 +pub fn gf16_to_posit16(gf: Gf16) -> Posit16 { + return gf16_to_posit16_raw(gf); +} + +// gf16_bits_to_posit16_bits(gf_bits: u16) -> u16 +// Convert GF16 bits to Posit16 bits +pub fn gf16_bits_to_posit16_bits(gf_bits: u16) -> u16 { + const gf = gf16_from_bits(gf_bits); + const posit = gf16_to_posit16(gf); + return posit16_to_bits(posit); +} + +// gf16_bits_to_posit16_bits_with_result(gf_bits: u16, config: ConversionConfig) -> ConversionResult +// Convert GF16 bits to Posit16 bits with full result +pub fn gf16_bits_to_posit16_bits_with_result(gf_bits: u16, config: ConversionConfig) -> ConversionResult { + const gf = gf16_from_bits(gf_bits); + return gf16_to_posit16_with_result(gf, config); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf16_bits_constants" { + try std.testing.expect(GF16_BITS == 16); + try std.testing.expect(GF16_EXP_BITS == 6); + try std.testing.expect(GF16_MANT_BITS == 9); + try std.testing.expect(GF16_BIAS == 31); +} + +test "posit16_constants" { + try std.testing.expect(POSIT16_BITS == 16); + try std.testing.expect(POSIT16_ES == 1); + try std.testing.expect(POSIT16_MAX_EXP == 7); +} + +test "posit16_regime_mant_bits" { + try std.testing.expect(POSIT16_REGIME_BITS == 2); + try std.testing.expect(POSIT16_MANT_BITS == 12); +} + +test "gf16_from_bits_roundtrip" { + given bits = 0x1234 + try std.testing.expect(gf = gf16_from_bits(bits)); + try std.testing.expect(result = gf16_to_bits(gf)); + try std.testing.expect(result == bits); +} + +test "gf16_is_zero_true" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(gf16_is_zero(gf) == true); +} + +test "gf16_is_subnormal_true" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(gf16_is_subnormal(gf) == true); +} + +test "gf16_is_normal_true" { + given gf = Gf16{.sign = 0, .exp = 32, .mant = 0} + try std.testing.expect(gf16_is_normal(gf) == true); +} + +test "gf16_is_infinity_true" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(gf16_is_infinity(gf) == true); +} + +test "gf16_is_nan_true" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(gf16_is_nan(gf) == true); +} + +test "posit16_from_bits_roundtrip" { + given bits = 0x4000 + try std.testing.expect(p = posit16_from_bits(bits)); + try std.testing.expect(result = posit16_to_bits(p)); + try std.testing.expect(result == bits); +} + +test "posit16_is_zero_true" { + given p = posit16_from_bits(0x0000) + try std.testing.expect(posit16_is_zero(p) == true); +} + +test "posit16_is_one_true" { + given p = posit16_from_bits(0x4000) + try std.testing.expect(posit16_is_one(p) == true); +} + +test "posit16_is_neg_one_true" { + given p = posit16_from_bits(0xC000) + try std.testing.expect(posit16_is_neg_one(p) == true); +} + +test "posit16_is_naR_true" { + given p = posit16_from_bits(0x8000) + try std.testing.expect(posit16_is_naR(p) == true); +} + +test "posit16_get_sign_positive" { + given p = posit16_from_bits(0x4000) + try std.testing.expect(posit16_get_sign(p) == false); +} + +test "posit16_get_sign_negative" { + given p = posit16_from_bits(0xC000) + try std.testing.expect(posit16_get_sign(p) == true); +} + +test "posit16_decode_one" { + given p = posit16_from_bits(0x4000) + try std.testing.expect(comp = posit16_decode(p)); + try std.testing.expect(comp.sign == false); + try std.testing.expect(comp.regime == 0); + try std.testing.expect(comp.exponent == 0); + try std.testing.expect(comp.mantissa == 0); +} + +test "conversion_config_init_structure" { + given config = conversion_config_init() + try std.testing.expect(config.round_mode == 0); + try std.testing.expect(config.clamp_overflow == true); +} + +test "gf16_to_posit16_zero" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_zero(p) == true); +} + +test "gf16_to_posit16_infinity" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_naR(p) == true); +} + +test "gf16_to_posit16_nan" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_naR(p) == true); +} + +test "gf16_to_posit16_preserves_sign" { + given gf_pos = Gf16{.sign = 1, .exp = 32, .mant = 0} + try std.testing.expect(gf_neg = Gf16{.sign = 0, .exp = 32, .mant = 0}); + try std.testing.expect(p_pos = gf16_to_posit16(gf_pos)); + try std.testing.expect(p_neg = gf16_to_posit16(gf_neg)); + try std.testing.expect(posit16_get_sign(p_pos) == true and posit16_get_sign(p_neg) == false); +} + +test "gf16_bits_to_posit16_bits_zero" { + try std.testing.expect(gf16_bits_to_posit16_bits(0x0000) == 0x0000); +} + +test "gf16_bits_to_posit16_bits_sign_preserved" { + given gf_pos = 0x8000 // Negative zero + try std.testing.expect(gf_neg = 0x0000 // Positive zero); + try std.testing.expect((gf16_bits_to_posit16_bits(gf_pos) & 0x8000) != 0); + try std.testing.expect((gf16_bits_to_posit16_bits(gf_neg) & 0x8000) == 0); +} + +test "gf16_to_posit16_with_result_zero" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_posit16_with_result(gf, config)); + try std.testing.expect(result.overflow == false); + try std.testing.expect(result.underflow == false); + try std.testing.expect(result.inexact == false); +} + +test "gf16_to_posit16_with_result_infinity" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_posit16_with_result(gf, config)); + try std.testing.expect(result.inexact == true); + try std.testing.expect(result.bits == 0x8000); +} + +test "gf16_to_posit16_with_result_overflow_clamped" { + given gf = Gf16{.sign = 0, .exp = 0x3E, .mant = 0x1FF} // Large value + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_posit16_with_result(gf, config)); + try std.testing.expect(result.overflow == true); + try std.testing.expect(result.bits != 0x8000 // Clamped, not NaR); +} + +test "gf16_to_posit16_with_result_overflow_not_clamped" { + given gf = Gf16{.sign = 0, .exp = 0x3E, .mant = 0x1FF} + try std.testing.expect(config = ConversionConfig{.round_mode = 0, .clamp_overflow = false}); + try std.testing.expect(result = gf16_to_posit16_with_result(gf, config)); + try std.testing.expect(result.overflow == true); + try std.testing.expect(result.bits == 0x8000 // NaR); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf16_bits_total + assert GF16_BITS == 16 + try std.testing.expect(GF16_SIGN_BIT == GF16_BITS - 1); + +invariant gf16_exp_mant_sum + assert GF16_EXP_BITS + GF16_MANT_BITS == GF16_BITS - 1 + +invariant posit16_bits_total + assert POSIT16_BITS == 16 + +invariant posit16_es_value + assert POSIT16_ES == 1 + +invariant posit16_regime_mant_sum + assert POSIT16_REGIME_BITS + POSIT16_MANT_BITS == POSIT16_BITS - POSIT16_ES - 1 + +invariant gf16_from_bits_roundtrip + given bits = 0x5678 + assert gf16_to_bits(gf16_from_bits(bits)) == bits + +invariant posit16_from_bits_roundtrip + given bits = 0x4000 + assert posit16_to_bits(posit16_from_bits(bits)) == bits + +invariant gf16_zero_exp_zero_mant_zero + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + assert gf16_is_zero(gf) and not gf16_is_subnormal(gf) + +invariant gf16_subnormal_exp_zero_mant_nonzero + given gf = Gf16{.sign = 0, .exp = 0, .mant = 1} + assert gf16_is_subnormal(gf) and not gf16_is_zero(gf) + +invariant gf16_normal_exp_nonzero + given gf = Gf16{.sign = 0, .exp = 1, .mant = 0} + assert gf16_is_normal(gf) and not gf16_is_zero(gf) + +invariant posit16_zero_bits_zero + given p = posit16_from_bits(0x0000) + assert posit16_is_zero(p) and posit16_get_sign(p) == false + +invariant posit16_one_bits_one + given p = posit16_from_bits(0x4000) + assert posit16_is_one(p) and not posit16_is_zero(p) + +invariant posit16_naR_bits_naR + given p = posit16_from_bits(0x8000) + assert posit16_is_naR(p) + +invariant conversion_config_init_clamp_enabled + given config = conversion_config_init() + assert config.clamp_overflow == true + +invariant gf16_to_posit16_zero_preserves + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_zero(p) and not posit16_get_sign(p)); + +invariant gf16_to_posit16_infinity_to_naR + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_naR(p)); + +invariant gf16_to_posit16_nan_to_naR + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_naR(p)); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf16_from_bits_latency + measure: nanoseconds to gf16_from_bits(0x4000) + target: < 20ns + +bench gf16_to_bits_latency + measure: nanoseconds to gf16_to_bits(gf16_from_bits(0x4000)) + target: < 20ns + +bench posit16_from_bits_latency + measure: nanoseconds to posit16_from_bits(0x4000) + target: < 20ns + +bench posit16_to_bits_latency + measure: nanoseconds to posit16_to_bits(posit16_from_bits(0x4000)) + target: < 15ns + +bench posit16_decode_latency + measure: nanoseconds to posit16_decode(posit16_from_bits(0x4000)) + target: < 50ns + +bench gf16_to_posit16_latency + measure: nanoseconds to gf16_to_posit16(gf16_from_bits(0x4000)) + target: < 40ns + +bench gf16_to_posit16_with_result_latency + measure: nanoseconds to gf16_to_posit16_with_result(gf16_from_bits(0x4000), conversion_config_init()) + target: < 50ns + +bench gf16_bits_to_posit16_bits_latency + measure: nanoseconds to gf16_bits_to_posit16_bits(0x4000) + target: < 40ns + +bench conversion_config_init_latency + measure: nanoseconds to conversion_config_init() + target: < 15ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/gf256.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/gf256.t27 new file mode 100644 index 0000000000..e57a0cdca8 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/gf256.t27 @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf256.t27 +// GoldenFloat256 0 256-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 11 (P1) + +module GF256 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // GF256 bit layout: [S(1) E(97) M(158)] + // S: 1 bit (sign) + // E: 97 bits (exponent) + // M: 158 bits (mantissa) + + const BITS : u8 = 256; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 97; + const MANT_BITS : u8 = 158; + + // Bias for exponent (2^(97-1) - 1 = 0x7FFFFFFFFFFFFFFF) + const EXP_BIAS_LO : u64 = 0xFFFFFFFFFFFFFFFF; + const EXP_BIAS_HI : u64 = 0x7F; + + // phi-ratio: exp/mant = 97/158 ≈ 0.614 (phi_distance = 0.004) + const PHI_DISTANCE : f64 = 0.00412087369321891; + + struct GF256 { + // 256-bit value (4 x u64) + raw_0 : u64, // bits 0-63 (mantissa low) + raw_1 : u64, // bits 64-127 (mantissa high + exp low) + raw_2 : u64, // bits 128-191 (exp mid + mant high) + raw_3 : u64, // bits 192-255 (sign + exp high) + } + + // Encode f64 to GF256 + fn encode(value: f64) -> GF256 { + if (value == 0.0) { + return GF256{ raw_0 = 0, raw_1 = 0, raw_2 = 0, raw_3 = 0 }; + } + + const sign = if (value < 0.0) { 1u64 } else { 0u64 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i128; + let exp_biased_lo = ((exp_unbiased + 158886469278522817) & 0xFFFFFFFFFFFFFFFF) as u64; + let exp_biased_hi = ((exp_unbiased >> 64) + 127) as u64; + + // Clamp exponent + if (exp_biased_hi > 0x7F) { + exp_biased_hi = 0x7F; + exp_biased_lo = 0xFFFFFFFFFFFFFFFF; + } + + // Extract mantissa (158 bits, split across 3 u64 words) + const normalized = abs_val / pow(2.0, exp_unbiased as f64); + const frac = normalized - 1.0; + const max_mant = (1u128 << MANT_BITS) - 1; + const mant = (frac * (max_mant + 1) as f64) as u128; + const clamped_mant = if (mant > max_mant) { max_mant } else { mant }; + + // Pack mantissa + const raw_0 = (clamped_mant & 0xFFFFFFFFFFFFFFFF) as u64; + const raw_1 = ((clamped_mant >> 64) & 0x3FFFFFFFFFFFFFFF) as u64; + + // Pack exponent (97 bits: 31 in raw_1, 64 in raw_2, 2 in raw_3) + const exp_m1 = (exp_biased_lo << 33) as u64; + const exp_m2 = (exp_biased_lo >> 31) | ((exp_biased_hi & 0x1) << 33); + + // Pack sign (bit 255) + const raw_3 = (sign << 63) | ((exp_biased_hi >> 1) & 0x7FFFFFFFFFFFFFFF); + + return GF256{ + raw_0 = raw_0, + raw_1 = raw_1 | exp_m1, + raw_2 = exp_m2, + raw_3 = raw_3, + }; + } + + // Decode GF256 to f64 + fn decode(gf: GF256) -> f64 { + const sign = (gf.raw_3 >> 63) as u8; + + // Extract exponent (97 bits) + const exp_lo_part = gf.raw_1 >> 33; + const exp_mid_part = (gf.raw_2 & 0x1FFFFFFFFFFFFFFF) as u64; + const exp_hi_part = (gf.raw_3 & 0x3FFFFFFFFFFFFFFF) as u64; + const exp_biased_lo = exp_lo_part | (exp_mid_part << 31); + const exp_biased_hi = exp_hi_part >> 62; + + const exp_biased_128 = ((exp_biased_hi as u128) << 64) | exp_biased_lo as u128; + + // Extract mantissa (158 bits) + const mant_lo = gf.raw_0; + const mant_mid = gf.raw_1 & 0x1FFFFFFFFFFFFFFF; + const mant = ((mant_mid as u128) << 64) | mant_lo as u128; + + // Zero + if (exp_biased_128 == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_bias_128 = ((EXP_BIAS_HI as u128) << 64) | EXP_BIAS_LO as u128; + const exp_unbiased = if (exp_biased_128 == 0) { + -(exp_bias_128 as i128) + 1 + } else { + (exp_biased_128 as i128) - exp_bias_128 as i128 + }; + + // Mantissa + const max_mant = (1u128 << MANT_BITS) - 1; + const mant_normalized = if (exp_biased_128 == 0) { + (mant as f64) / (max_mant + 1) as f64 + } else { + 1.0 + (mant as f64) / (max_mant + 1) as f64 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f64); + + if (sign != 0) { + return -value; + } + return value; + } + + // Format Properties + fn max_value() -> f64 { + const max_mant = (1u128 << MANT_BITS) - 1; + const mant_max = 1.0 + (max_mant as f64) / (max_mant + 1) as f64; + const exp_max = ((1i128 << EXP_BITS) - 1) as f64 - (EXP_BIAS_HI as f64 * 4294967296.0 + EXP_BIAS_LO as f64); + return mant_max * pow(2.0, exp_max); + } + + fn min_positive() -> f64 { + const max_mant = (1u128 << MANT_BITS) - 1; + const mant_min = 1.0 / (max_mant + 1) as f64; + const exp_min = -((EXP_BIAS_HI as f64 * 4294967296.0 + EXP_BIAS_LO as f64)) + 1.0; + return mant_min * pow(2.0, exp_min); + } + + fn epsilon() -> f64 { + const max_mant = (1u128 << MANT_BITS) - 1; + return 1.0 / (max_mant + 1) as f64; + } + + // Validation + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF256"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // Use Cases + // GF256 is optimal for: + // - Maximum precision computational chemistry + // - String theory simulations + // - Exascale climate modeling + // - Arbitrary-precision arithmetic bridge + + // Memory: 256 bits = 32 bytes (8x FP32) + const MEMORY_RATIO_VS_FP32 : f32 = 8.0; + + // Helper Functions + fn floor_log2(x: f64) -> i128 { + if (x <= 0.0) { return -170141183460469231731687303715884105728; } + let exp : i128 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn pow(base: f64, exp: f64) -> f64 { + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; + } + + const is_integer = exp == floor(exp); + + if (is_integer) { + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + fn ln_approx(x: f64) -> f64 { + if (x <= 0.0) { + return 0.0 / 0.0; + } + if (x == 1.0) { + return 0.0; + } + + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0); + } + + fn exp_approx(x: f64) -> f64 { + if (x == 0.0) { + return 1.0; + } + + let result = 1.0; + let term = 1.0; + let exp_x = x; + + for (i in 1..=15) { + term = term * exp_x / (i as f64); + result = result + term; + } + + return result; + } + + fn floor(x: f64) -> f64 { + let xi = x as i128; + if (x >= 0.0 || x == xi as f64) { + return xi as f64; + } + return (xi - 1) as f64; + } + + // TDD-Inside-Spec: Tests and Invariants for GF256 + + test gf256_decode_zero + given gf = GF256{ raw_0 = 0, raw_1 = 0, raw_2 = 0, raw_3 = 0 } + when value = decode(gf) + then value == 0.0 + + test gf256_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf256_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf256_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf256_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf256_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf256_phi_distance_good + given phi_dist = PHI_DISTANCE + then phi_dist < 0.01 + + test gf256_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 8.0) < 0.01 + + test gf256_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf256_bits_constant + assert BITS == 256 + + invariant gf256_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf256_exp_bits_is_97 + assert EXP_BITS == 97 + + invariant gf256_mant_bits_is_158 + assert MANT_BITS == 158 + + invariant gf256_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf256_phi_distance_below_threshold + assert PHI_DISTANCE < 0.01 + + invariant gf256_exp_mant_ratio_close_to_phi + assert abs((EXP_BITS as f64) / (MANT_BITS as f64) - 0.618) < 0.01 + + invariant gf256_has_max_precision + // GF256 provides extreme precision suitable for quantum computing + assert MANT_BITS > 100 + + bench gf256_encode_latency + measure: nanoseconds to encode(1.0) + target: < 1000ns + + bench gf256_decode_latency + measure: nanoseconds to decode(GF256{raw_0 = 0, raw_1 = 0, raw_2 = 0, raw_3 = 0}) + target: < 800ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/gf32_to_fp32.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/gf32_to_fp32.t27 new file mode 100644 index 0000000000..a33a7bdc1d --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/gf32_to_fp32.t27 @@ -0,0 +1,757 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf32_to_fp32.t27 — GF32 to FP32 Converter +; GoldenFloat32 to IEEE 754 binary32 format conversion +; φ² + 1/φ² = 3 | TRINITY + +module gf32-to-fp32; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const GF32_SIGN_BITS : u8 = 1; +pub const GF32_EXP_BITS : u8 = 12; +pub const GF32_MANT_BITS : u8 = 19; +pub const GF32_BIAS : i16 = 2047; + +pub const FP32_SIGN_BITS : u8 = 1; +pub const FP32_EXP_BITS : u8 = 8; +pub const FP32_MANT_BITS : u8 = 23; +pub const FP32_BIAS : i8 = 127; + +pub const EXP_BIAS_DIFF : i16 = GF32_BIAS - @as(i16, FP32_BIAS); + +pub const MANTissa_MASK_GF32 : u32 = 0x7FFFF; +pub const MANTissa_MASK_FP32 : u32 = 0x7FFFFF; + +// ============================================================================ +// Types +// ============================================================================ + +pub const Gf32 = packed struct { + sign : u1, + exp : u12, + mant : u19, +} + +pub const Fp32 = packed struct { + sign : u1, + exp : u8, + mant : u23, +} + +pub const ConversionResult = struct { + value : u32, + overflow : bool, + underflow : bool, + lost_precision : bool, +} + +pub const ConversionConfig = struct { + round_mode : u8, + overflow_mode : u8, + enable_saturation : bool, +} + +// ============================================================================ +// GF32 Functions +// ============================================================================ + +// gf32_from_bits(bits: u32) -> Gf32 +// Extract GF32 from 32-bit representation +pub fn gf32_from_bits(bits: u32) -> Gf32 { + return Gf32 { + .sign = @as(u1, @truncate((bits >> 31) & 0x01)), + .exp = @as(u12, @truncate((bits >> 19) & 0xFFF)), + .mant = @as(u19, @truncate(bits & 0x7FFFF)), + }; +} + +// gf32_to_bits(gf: Gf32) -> u32 +// Convert GF32 to 32-bit representation +pub fn gf32_to_bits(gf: Gf32) -> u32 { + const sign_field : u32 = @as(u32, gf.sign) << 31; + const exp_field : u32 = @as(u32, gf.exp) << 19; + const mant_field : u32 = @as(u32, gf.mant); + return sign_field | exp_field | mant_field; +} + +// gf32_get_exp_biased(gf: Gf32) -> i16 +// Get exponent with bias applied +pub fn gf32_get_exp_biased(gf: Gf32) -> i16 { + return @as(i16, @bitCast(gf.exp)) - GF32_BIAS; +} + +// gf32_is_zero(gf: Gf32) -> bool +// Check if GF32 is zero +pub fn gf32_is_zero(gf: Gf32) -> bool { + return gf.exp == 0 and gf.mant == 0; +} + +// gf32_is_subnormal(gf: Gf32) -> bool +// Check if GF32 is subnormal +pub fn gf32_is_subnormal(gf: Gf32) -> bool { + return gf.exp == 0 and gf.mant != 0; +} + +// gf32_is_normal(gf: Gf32) -> bool +// Check if GF32 is normal +pub fn gf32_is_normal(gf: Gf32) -> bool { + return gf.exp != 0 and gf.exp != 0xFFF; +} + +// gf32_is_infinity(gf: Gf32) -> bool +// Check if GF32 is infinity +pub fn gf32_is_infinity(gf: Gf32) -> bool { + return gf.exp == 0xFFF and gf.mant == 0; +} + +// gf32_is_nan(gf: Gf32) -> bool +// Check if GF32 is NaN +pub fn gf32_is_nan(gf: Gf32) -> bool { + return gf.exp == 0xFFF and gf.mant != 0; +} + +// gf32_is_qnan(gf: Gf32) -> bool +// Check if GF32 is quiet NaN +pub fn gf32_is_qnan(gf: Gf32) -> bool { + return gf32_is_nan(gf) and (gf.mant >> 18) == 1; +} + +// gf32_is_snan(gf: Gf32) -> bool +// Check if GF32 is signaling NaN +pub fn gf32_is_snan(gf: Gf32) -> bool { + return gf32_is_nan(gf) and (gf.mant >> 18) == 0; +} + +// ============================================================================ +// FP32 Functions +// ============================================================================ + +// fp32_from_bits(bits: u32) -> Fp32 +// Extract FP32 from 32-bit representation +pub fn fp32_from_bits(bits: u32) -> Fp32 { + return Fp32 { + .sign = @as(u1, @truncate((bits >> 31) & 0x01)), + .exp = @as(u8, @truncate((bits >> 23) & 0xFF)), + .mant = @as(u23, @truncate(bits & 0x7FFFFF)), + }; +} + +// fp32_to_bits(fp: Fp32) -> u32 +// Convert FP32 to 32-bit representation +pub fn fp32_to_bits(fp: Fp32) -> u32 { + const sign_field : u32 = @as(u32, fp.sign) << 31; + const exp_field : u32 = @as(u32, fp.exp) << 23; + const mant_field : u32 = @as(u32, fp.mant); + return sign_field | exp_field | mant_field; +} + +// fp32_get_exp_biased(fp: Fp32) -> i8 +// Get exponent with bias applied +pub fn fp32_get_exp_biased(fp: Fp32) -> i8 { + if (fp.exp == 0) { + return -126; // Denormal + } else if (fp.exp == 0xFF) { + return 128; // Infinity/NaN + } + return @as(i8, @bitCast(fp.exp)) - FP32_BIAS; +} + +// fp32_is_zero(fp: Fp32) -> bool +// Check if FP32 is zero +pub fn fp32_is_zero(fp: Fp32) -> bool { + return fp.exp == 0 and fp.mant == 0; +} + +// fp32_is_subnormal(fp: Fp32) -> bool +// Check if FP32 is subnormal +pub fn fp32_is_subnormal(fp: Fp32) -> bool { + return fp.exp == 0 and fp.mant != 0; +} + +// fp32_is_infinity(fp: Fp32) -> bool +// Check if FP32 is infinity +pub fn fp32_is_infinity(fp: Fp32) -> bool { + return fp.exp == 0xFF and fp.mant == 0; +} + +// fp32_is_nan(fp: Fp32) -> bool +// Check if FP32 is NaN +pub fn fp32_is_nan(fp: Fp32) -> bool { + return fp.exp == 0xFF and fp.mant != 0; +} + +// fp32_is_qnan(fp: Fp32) -> bool +// Check if FP32 is quiet NaN +pub fn fp32_is_qnan(fp: Fp32) -> bool { + return fp32_is_nan(fp) and (fp.mant >> 22) == 1; +} + +// fp32_is_snan(fp: Fp32) -> bool +// Check if FP32 is signaling NaN +pub fn fp32_is_snan(fp: Fp32) -> bool { + return fp32_is_nan(fp) and (fp.mant >> 22) == 0; +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// conversion_config_init() -> ConversionConfig +// Initialize conversion config +pub fn conversion_config_init() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, // Round to nearest + .overflow_mode = 0, // Clamp + .enable_saturation = true, + }; +} + +// conversion_config_no_saturation() -> ConversionConfig +// Create config without saturation +pub fn conversion_config_no_saturation() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, + .overflow_mode = 1, // Overflow to infinity + .enable_saturation = false, + }; +} + +// conversion_config_round_up() -> ConversionConfig +// Create config with round-up mode +pub fn conversion_config_round_up() -> ConversionConfig { + return ConversionConfig { + .round_mode = 1, // Round up + .overflow_mode = 0, + .enable_saturation = true, + }; +} + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// gf32_to_fp32_raw(gf: Gf32) -> Fp32 +// Convert GF32 to FP32 (raw, no overflow/underflow handling) +pub fn gf32_to_fp32_raw(gf: Gf32) -> Fp32 { + var exp_i16 = gf32_get_exp_biased(gf); + + if (gf32_is_zero(gf)) { + return Fp32{.sign = gf.sign, .exp = 0, .mant = 0}; + } else if (gf32_is_infinity(gf)) { + return Fp32{.sign = gf.sign, .exp = 0xFF, .mant = 0}; + } else if (gf32_is_nan(gf)) { + var mant_fp : u23 = @as(u23, gf.mant) << (FP32_MANT_BITS - GF32_MANT_BITS); + if (gf32_is_qnan(gf)) { + mant_fp |= (1 << (FP32_MANT_BITS - 1)); + } + return Fp32{.sign = gf.sign, .exp = 0xFF, .mant = mant_fp}; + } else if (gf32_is_subnormal(gf)) { + // Handle GF32 subnormal - convert to smallest normal FP32 + return Fp32{.sign = gf.sign, .exp = 1, .mant = 0}; + } + + // Adjust exponent bias: GF32 bias 2047 -> FP32 bias 127 + exp_i16 = exp_i16 + GF32_BIAS - @as(i16, FP32_BIAS); + + var exp : u8 = 0; + var mant : u23 = 0; + var lost_precision = false; + + if (exp_i16 <= -127) { + // Underflow to zero or denormal + if (exp_i16 == -127 and gf.mant != 0) { + // Smallest denormal + exp = 0; + mant = @as(u23, gf.mant) >> (127 + @as(u8, @bitCast(-exp_i16)) - GF32_MANT_BITS); + } else { + exp = 0; + mant = 0; + } + lost_precision = true; + } else if (exp_i16 >= 128) { + // Overflow to infinity + exp = 0xFF; + mant = 0; + } else { + exp = @as(u8, @truncate(@as(u8, @bitCast(exp_i16)) & 0xFF)); + // Extend mantissa from 19 to 23 bits + mant = @as(u23, gf.mant) << (FP32_MANT_BITS - GF32_MANT_BITS); + } + + return Fp32{.sign = gf.sign, .exp = exp, .mant = mant}; +} + +// gf32_to_fp32_with_result(gf: Gf32, config: ConversionConfig) -> ConversionResult +// Convert GF32 to FP32 with full result +pub fn gf32_to_fp32_with_result(gf: Gf32, config: ConversionConfig) -> ConversionResult { + var result : ConversionResult = undefined; + + if (gf32_is_zero(gf)) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0, .mant = 0}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } else if (gf32_is_infinity(gf)) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0xFF, .mant = 0}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } else if (gf32_is_nan(gf)) { + var mant_fp : u23 = @as(u23, gf.mant) << 4; + if (gf32_is_qnan(gf)) { + mant_fp |= 0x400000; // Quiet NaN bit + } + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0xFF, .mant = mant_fp}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } + + var exp_i16 = gf32_get_exp_biased(gf); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + + if (gf32_is_subnormal(gf)) { + result.underflow = true; + if (config.enable_saturation) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 1, .mant = 0}); + } else { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0, .mant = 1}); // Smallest denormal + } + return result; + } + + exp_i16 = exp_i16 + GF32_BIAS - @as(i16, FP32_BIAS); + + var exp : u8 = 0; + var mant : u23 = 0; + + if (exp_i16 <= -127) { + // Underflow + if (config.enable_saturation) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0, .mant = 0}); + } else { + // Try denormal + var denorm_exp = exp_i16 + 126; + if (denorm_exp >= -22) { + exp = 0; + mant = @as(u23, gf.mant) << (4 - @as(u5, @intCast(-denorm_exp)))); + } else { + exp = 0; + mant = 0; + } + } + result.underflow = true; + result.lost_precision = true; + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = exp, .mant = mant}); + return result; + } else if (exp_i16 >= 128) { + // Overflow + if (config.overflow_mode == 0 or config.enable_saturation) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0xFE, .mant = 0x7FFFFF}); // Max finite + } else { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0xFF, .mant = 0}); // Infinity + } + result.overflow = true; + return result; + } else { + exp = @as(u8, @truncate(@as(u8, @bitCast(exp_i16)) & 0xFF)); + mant = @as(u23, gf.mant) << 4; + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = exp, .mant = mant}); + return result; + } +} + +// gf32_to_fp32(gf: Gf32) -> Fp32 +// Convert GF32 to FP32 +pub fn gf32_to_fp32(gf: Gf32) -> Fp32 { + const result = gf32_to_fp32_with_result(gf, conversion_config_init()); + return fp32_from_bits(result.value); +} + +// gf32_bits_to_fp32_bits(gf_bits: u32) -> u32 +// Convert GF32 bits to FP32 bits +pub fn gf32_bits_to_fp32_bits(gf_bits: u32) -> u32 { + const gf = gf32_from_bits(gf_bits); + const fp = gf32_to_fp32(gf); + return fp32_to_bits(fp); +} + +// gf32_bits_to_fp32_bits_with_result(gf_bits: u32, config: ConversionConfig) -> ConversionResult +// Convert GF32 bits to FP32 bits with full result +pub fn gf32_bits_to_fp32_bits_with_result(gf_bits: u32, config: ConversionConfig) -> ConversionResult { + const gf = gf32_from_bits(gf_bits); + return gf32_to_fp32_with_result(gf, config); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf32_bits_constants" { + try std.testing.expect(GF32_SIGN_BITS == 1); + try std.testing.expect(GF32_EXP_BITS == 12); + try std.testing.expect(GF32_MANT_BITS == 19); + try std.testing.expect(GF32_BIAS == 2047); +} + +test "fp32_bits_constants" { + try std.testing.expect(FP32_SIGN_BITS == 1); + try std.testing.expect(FP32_EXP_BITS == 8); + try std.testing.expect(FP32_MANT_BITS == 23); + try std.testing.expect(FP32_BIAS == 127); +} + +test "exp_bias_diff" { + try std.testing.expect(EXP_BIAS_DIFF == 1920); +} + +test "mantissa_masks" { + try std.testing.expect(MANTissa_MASK_GF32 == 0x7FFFF); + try std.testing.expect(MANTissa_MASK_FP32 == 0x7FFFFF); +} + +test "gf32_from_bits_to_bits_roundtrip" { + given bits = 0x12345678 + try std.testing.expect(gf = gf32_from_bits(bits)); + try std.testing.expect(result = gf32_to_bits(gf)); + try std.testing.expect(result == bits); +} + +test "gf32_from_bits_structure" { + given gf = gf32_from_bits(0x80000000) + try std.testing.expect(gf.sign == 1); + try std.testing.expect(gf.exp == 0); + try std.testing.expect(gf.mant == 0); +} + +test "fp32_from_bits_to_bits_roundtrip" { + given bits = 0x3F800000 + try std.testing.expect(fp = fp32_from_bits(bits)); + try std.testing.expect(result = fp32_to_bits(fp)); + try std.testing.expect(result == bits); +} + +test "gf32_is_zero_true" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(gf32_is_zero(gf) == true); +} + +test "gf32_is_zero_false" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(gf32_is_zero(gf) == false); +} + +test "gf32_is_subnormal_true" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(gf32_is_subnormal(gf) == true); +} + +test "gf32_is_normal_true" { + given gf = Gf32{.sign = 0, .exp = 1, .mant = 0} + try std.testing.expect(gf32_is_normal(gf) == true); +} + +test "gf32_is_infinity_true" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0} + try std.testing.expect(gf32_is_infinity(gf) == true); +} + +test "gf32_is_nan_true" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 1} + try std.testing.expect(gf32_is_nan(gf) == true); +} + +test "gf32_is_qnan_true" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x40000} + try std.testing.expect(gf32_is_qnan(gf) == true); +} + +test "gf32_is_snan_true" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x20000} + try std.testing.expect(gf32_is_snan(gf) == true); +} + +test "fp32_is_zero_true" { + given fp = Fp32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp32_is_zero(fp) == true); +} + +test "fp32_is_infinity_true" { + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 0} + try std.testing.expect(fp32_is_infinity(fp) == true); +} + +test "fp32_is_nan_true" { + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 1} + try std.testing.expect(fp32_is_nan(fp) == true); +} + +test "fp32_is_qnan_true" { + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 0x400000} + try std.testing.expect(fp32_is_qnan(fp) == true); +} + +test "conversion_config_init_structure" { + given config = conversion_config_init() + try std.testing.expect(config.round_mode == 0); + try std.testing.expect(config.enable_saturation == true); +} + +test "conversion_config_no_saturation_structure" { + given config = conversion_config_no_saturation() + try std.testing.expect(config.enable_saturation == false); +} + +test "conversion_config_round_up_structure" { + given config = conversion_config_round_up() + try std.testing.expect(config.round_mode == 1); +} + +test "gf32_to_fp32_zero" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.exp == 0); + try std.testing.expect(fp.mant == 0); +} + +test "gf32_to_fp32_infinity" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.exp == 0xFF); + try std.testing.expect(fp.mant == 0); +} + +test "gf32_to_fp32_nan" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 1} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.exp == 0xFF); + try std.testing.expect(fp.mant != 0); +} + +test "gf32_to_fp32_preserves_sign" { + given gf_pos = Gf32{.sign = 1, .exp = 2048, .mant = 0} + try std.testing.expect(gf_neg = Gf32{.sign = 0, .exp = 2048, .mant = 0}); + try std.testing.expect(fp_pos = gf32_to_fp32(gf_pos)); + try std.testing.expect(fp_neg = gf32_to_fp32(gf_neg)); + try std.testing.expect(fp_pos.sign == 1 and fp_neg.sign == 0); +} + +test "gf32_to_fp32_with_result_zero" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.overflow == false); + try std.testing.expect(result.underflow == false); + try std.testing.expect(result.lost_precision == false); +} + +test "gf32_bits_to_fp32_bits_zero" { + given config = conversion_config_init() + try std.testing.expect(gf32_bits_to_fp32_bits(0x00000000, config) == 0x00000000); +} + +test "gf32_to_fp32_with_result_subnormal_underflow" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.underflow == true); +} + +test "gf32_to_fp32_with_result_overflow" { + given gf = Gf32{.sign = 0, .exp = 4094, .mant = 0} // Very large + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.overflow == true); +} + +test "gf32_to_fp32_with_result_saturation" { + given gf = Gf32{.sign = 0, .exp = 4094, .mant = 0} // Very large + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.value != 0x7F800000 // Not infinity, but max finite); +} + +test "gf32_to_fp32_with_result_no_saturation_overflow" { + given gf = Gf32{.sign = 0, .exp = 4094, .mant = 0} // Very large + try std.testing.expect(config = conversion_config_no_saturation()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.value == 0x7F800000 // Infinity); +} + +test "gf32_to_fp32_nan_qnan_preserves" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x40000} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect((result.value & 0x400000) != 0 // Quiet NaN bit set); +} + +test "gf32_to_fp32_nan_snan_preserves" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x20000} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect((result.value & 0x400000) == 0 // Quiet NaN bit not set); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf32_bit_count + assert GF32_SIGN_BITS + GF32_EXP_BITS + GF32_MANT_BITS == 32 + +invariant fp32_bit_count + assert FP32_SIGN_BITS + FP32_EXP_BITS + FP32_MANT_BITS == 32 + +invariant gf32_exp_width + assert GF32_EXP_BITS == 12 + +invariant fp32_exp_width + assert FP32_EXP_BITS == 8 + +invariant gf32_mant_width + assert GF32_MANT_BITS == 19 + +invariant fp32_mant_width + assert FP32_MANT_BITS == 23 + +invariant mantissa_masks_correct + assert MANTissa_MASK_GF32 == (@as(u32, 1) << GF32_MANT_BITS) - 1 + try std.testing.expect(MANTissa_MASK_FP32 == (@as(u32, 1) << FP32_MANT_BITS) - 1); + +invariant exp_bias_diff_positive + assert EXP_BIAS_DIFF > 0 + +invariant gf32_from_bits_roundtrip + given bits = 0x12345678 + assert gf32_to_bits(gf32_from_bits(bits)) == bits + +invariant fp32_from_bits_roundtrip + given bits = 0x3F800000 + assert fp32_to_bits(fp32_from_bits(bits)) == bits + +invariant gf32_zero_exp_zero_mant_zero + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + assert gf32_is_zero(gf) and not gf32_is_subnormal(gf) + +invariant gf32_subnormal_exp_zero_mant_nonzero + given gf = Gf32{.sign = 0, .exp = 0, .mant = 1} + assert gf32_is_subnormal(gf) and not gf32_is_zero(gf) + +invariant gf32_normal_exp_nonzero + given gf = Gf32{.sign = 0, .exp = 1, .mant = 0} + assert gf32_is_normal(gf) and not gf32_is_zero(gf) + +invariant gf32_infinity_exp_max_mant_zero + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0} + assert gf32_is_infinity(gf) and not gf32_is_nan(gf) + +invariant gf32_qnan_top_mant_bit + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x40000} + assert gf32_is_nan(gf) and gf32_is_qnan(gf) + +invariant gf32_snan_top_mant_bit_clear + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x20000} + assert gf32_is_nan(gf) and gf32_is_snan(gf) + +invariant fp32_infinity_exp_max_mant_zero + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 0} + assert fp32_is_infinity(fp) and not fp32_is_nan(fp) + +invariant fp32_qnan_top_mant_bit + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 0x400000} + assert fp32_is_nan(fp) and fp32_is_qnan(fp) + +invariant conversion_config_init_saturation_enabled + given config = conversion_config_init() + assert config.enable_saturation == true + +invariant conversion_config_no_saturation_disabled + given config = conversion_config_no_saturation() + assert config.enable_saturation == false + +invariant conversion_config_round_up_mode_one + given config = conversion_config_round_up() + assert config.round_mode == 1 + +invariant gf32_to_fp32_zero_preserves + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.sign == gf.sign); + +invariant gf32_to_fp32_infinity_preserves_sign + given gf_pos = Gf32{.sign = 1, .exp = 0xFFF, .mant = 0} + try std.testing.expect(fp_pos = gf32_to_fp32(gf_pos)); + try std.testing.expect(fp_pos.sign == 1); + +invariant gf32_to_fp32_nan_preserves_sign + given gf = Gf32{.sign = 1, .exp = 0xFFF, .mant = 1} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.sign == 1); + +invariant gf32_to_fp32_qnan_preserves_qnan + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x40000} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect((result.value & 0x400000) != 0); + +invariant gf32_to_fp32_snan_preserves_snan + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x20000} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect((result.value & 0x400000) == 0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf32_from_bits_latency + measure: nanoseconds to gf32_from_bits(0x3F800000) + target: < 20ns + +bench gf32_to_bits_latency + measure: nanoseconds to gf32_to_bits(gf32_from_bits(0x3F800000)) + target: < 20ns + +bench fp32_from_bits_latency + measure: nanoseconds to fp32_from_bits(0x3F800000) + target: < 20ns + +bench fp32_to_bits_latency + measure: nanoseconds to fp32_to_bits(fp32_from_bits(0x3F800000)) + target: < 20ns + +bench gf32_to_fp32_latency + measure: nanoseconds to gf32_to_fp32(gf32_from_bits(0x40000000)) + target: < 30ns + +bench gf32_to_fp32_with_result_latency + measure: nanoseconds to gf32_to_fp32_with_result(gf32_from_bits(0x40000000), conversion_config_init()) + target: < 40ns + +bench gf32_bits_to_fp32_bits_latency + measure: nanoseconds to gf32_bits_to_fp32_bits(0x40000000) + target: < 30ns + +bench conversion_config_init_latency + measure: nanoseconds to conversion_config_init() + target: < 15ns + +bench gf32_is_nan_latency + measure: nanoseconds to gf32_is_nan(gf32_from_bits(0x7FC00000)) + target: < 15ns + +bench gf32_is_qnan_latency + measure: nanoseconds to gf32_is_qnan(gf32_from_bits(0x7FC00000)) + target: < 15ns + +bench fp32_is_nan_latency + measure: nanoseconds to fp32_is_nan(fp32_from_bits(0x7FC00000)) + target: < 15ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/gf64.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/gf64.t27 new file mode 100644 index 0000000000..b472cef864 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/gf64.t27 @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf64.t27 +// GoldenFloat64 - 64-bit φ-structured floating point +// NUMERIC-STANDARD-001 Agent 4 (P1) + +module GF64 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // GF64 bit layout: [S(1) | EEE...EEE(24) | MMM...MMM(39)] + // S: 1 bit (sign) + // E: 24 bits (exponent) + // M: 39 bits (mantissa) + // + // φ-ratio: exp/mant = 24/39 ≈ 0.615 (phi_distance = 0.003) + // This is the BEST phi approximation in the entire GF family! + + const BITS : u8 = 64; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 24; + const MANT_BITS : u8 = 39; + + // Bias for exponent (2^23 - 1 = 8388607) + const EXP_BIAS : i32 = 8388607; + + // φ-ratio: exp/mant = 24/39 = 0.615 (phi_distance = 0.003) + const PHI_DISTANCE : f64 = 0.0028268468254629; + + // 2. GoldenFloat64 Type + + struct GF64 { + raw : u64, // 64-bit raw value + } + + // 3. Encoding/Decoding + + // Encode f32 to GF64 + fn encode(value: f32) -> GF64 { + if (value == 0.0) { + return GF64{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) using f64 for precision + const exp_unbiased = floor_log2_f64(abs_val as f64) as i32; + const exp_biased = (exp_unbiased + EXP_BIAS) as u64; + + // Clamp exponent (18 bits max = 262143) + const exp_clamped = if (exp_biased > 16777215) { 16777215 } else { exp_biased }; + + // Extract mantissa (45 bits) + const mant = extract_mantissa_64(abs_val as f64, exp_unbiased, MANT_BITS); + + return GF64{ + raw = (sign << 63) | (exp_clamped << MANT_BITS) | mant + }; + } + + // Decode GF64 to f32 + fn decode(gf: GF64) -> f64 { + const sign = (gf.raw >> 63) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0xFFFFFF) as u64; + + // Zero + if (exp_biased == 0 && (gf.raw & 0x7FFFFFFFFFFFFF) == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS + 1 + } else { + (exp_biased as i32) - EXP_BIAS + }; + + // Mantissa (with implicit 1 for normalized) + const mant_normalized = if (exp_biased == 0) { + (mant_u64_to_f64(gf.raw & 0x7FFFFFFFFFFFFF)) / 549755813888.0 + } else { + 1.0 + (mant_u64_to_f64(gf.raw & 0x7FFFFFFFFFFFFF)) / 549755813888.0 + }; + + const value = mant_normalized * pow2_64(exp_unbiased as f64); + + if (sign != 0) { + return -value; + } + return value; + } + + // 4. Format Properties + + fn max_value() -> f64 { + // Max normalized: mant ≈ 2.0, exp = 131071 + const mant_max = 1.9999999999999998; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow2_64(exp_max as f64); + } + + fn min_positive() -> f64 { + // Min subnormal: mant ≈ 0, exp = -131070 + const mant_min = 1.0 / 35184372088832.0; + const exp_min = -EXP_BIAS + 1; + return mant_min * pow2_64(exp_min as f64); + } + + fn epsilon() -> f64 { + // Smallest representable difference at 1.0 + return 1.0 / 35184372088832.0; + } + + // 5. Validation + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF64"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 6. Use Cases + + // GF64 is optimal for: + // - High-precision scientific computing + // - Financial calculations requiring accuracy + // - Extended range physics simulations + // - Double-precision alternatives with φ-optimization + + // Memory: 64 bits = 8 bytes (same as FP64, better range) + const MEMORY_RATIO_VS_FP32 : f32 = 64.0 / 32.0; // 2.0 + + // 7. Helper Functions + + fn floor_log2_f64(x: f64) -> i32 { + if (x <= 0.0) { return -2147483648; } + let exp : i32 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa_64(value: f64, exp: i32, mant_bits: u8) -> u64 { + const normalized = value / pow2_64(exp as f64); + const frac = normalized - 1.0; + const max_mant = (1 << mant_bits) - 1; + return (frac * (max_mant as f64 + 1.0)) as u64; + } + + fn mant_u64_to_f64(m: u64) -> f64 { + return m as f64; + } + + fn pow2_64(exp: f64) -> f64 { + if (exp == 0.0) { return 1.0; } + if (exp < 0.0) { return 1.0 / pow2_64(-exp); } + + let result = 1.0; + let base = 2.0; + let e = exp as i32; + + while (e > 0) { + if (e % 2 == 1) { + result = result * base; + } + base = base * base; + e = e / 2; + } + return result; + } + + // TDD-Inside-Spec: Tests and Invariants for GF64 + + test gf64_decode_zero + given gf = GF64{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf64_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf64_encode_positive_value + given original = 1.5 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 1.5) < 1e-6 + + test gf64_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf64_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf64_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf64_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf64_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 2.0) < 0.01 + + test gf64_validate_format_success + given valid = validate_format() + then valid == true + + test gf64_encode_large_value + given original = 1000000.0 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded / original - 1.0) < 0.01 + + test gf64_encode_small_value + given original = 0.000001 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded / original - 1.0) < 0.01 + + test gf64_negative_encoding + given original = -2.5 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded < 0.0 and abs(decoded - (-2.5)) < 1e-6 + + test gf64_sign_bit_negative + given value = -1.0 + and encoded = encode(value) + and sign_bit = (encoded.raw >> 63) as u8 + then sign_bit == 1 + + test gf64_sign_bit_positive + given value = 1.0 + and encoded = encode(value) + and sign_bit = (encoded.raw >> 63) as u8 + then sign_bit == 0 + + invariant gf64_bits_constant + assert BITS == 64 + + invariant gf64_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf64_exp_bits_is_24 + assert EXP_BITS == 24 + + invariant gf64_mant_bits_is_39 + assert MANT_BITS == 39 + + invariant gf64_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf64_phi_distance_best_in_family + assert PHI_DISTANCE < 0.005 + + invariant gf64_exp_bias_positive + assert EXP_BIAS > 0 + + invariant gf64_exp_bias_is_8388607 + assert EXP_BIAS == 8388607 + + invariant gf64_encode_preserves_sign_positive + given val = 1.5 + and enc = encode(val) + and sign = (enc.raw >> 63) as u8 + then sign == 0 + + invariant gf64_encode_preserves_sign_negative + given val = -1.5 + and enc = encode(val) + and sign = (enc.raw >> 63) as u8 + then sign == 1 + + invariant gf64_decode_zero_zero + assert decode(GF64{raw = 0}) == 0.0 + + invariant gf64_epsilon_less_than_min_positive + assert epsilon() <= min_positive() + + invariant gf64_exp_mant_ratio_is_0_4 + given ratio = (EXP_BITS as f64) / (MANT_BITS as f64) + then abs(ratio - 0.4) < 0.01 + + bench gf64_encode_latency + measure: nanoseconds to encode(1.0) + target: < 200ns + + bench gf64_decode_latency + measure: nanoseconds to decode(GF64{raw = encode(1.5).raw}) + target: < 100ns + + bench gf64_pow2_latency + measure: nanoseconds to pow2_64(10.0) + target: < 100ns + + bench gf64_encode_decode_roundtrip + measure: nanoseconds to encode(1.0) and decode(GF64{raw = encode(1.0).raw}) + target: < 300ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/holo_mux_x4.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/holo_mux_x4.t27 new file mode 100644 index 0000000000..908d2f9877 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/holo_mux_x4.t27 @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: Apache-2.0 +; holo_mux_x4.t27 — Sacred Opcode 0xE6: Holographic 4x Multiplexer +; Hardware multiplexer for holographic data paths with 4-way select +; φ² + 1/φ² = 3 | TRINITY + +module sacred-holo_mux_x4; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_HOLO_MUX_X4 : u8 = 0xE6; + +pub const NUM_INPUTS : u8 = 4; +pub const DATA_WIDTH : u8 = 16; +pub const SELECT_BITS : u8 = 2; + +pub const HOLO_LAYER_NORTH : u8 = 0; +pub const HOLO_LAYER_EAST : u8 = 1; +pub const HOLO_LAYER_SOUTH : u8 = 2; +pub const HOLO_LAYER_WEST : u8 = 3; + +pub const HOLO_PHASE_0 : u8 = 0; +pub const HOLO_PHASE_90 : u8 = 1; +pub const HOLO_PHASE_180 : u8 = 2; +pub const HOLO_PHASE_270 : u8 = 3; + +pub const HOLO_VALID : u8 = 1; +pub const HOLO_INVALID : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const HoloInput = struct { + data : u16, + layer : u8, + phase : u8, + valid : bool, + phi_score : u8, // φ-distance score (0-255, lower is better) +} + +pub const HoloMuxConfig = struct { + phi_optimized : bool, + priority_select : bool, + layer_frozen : bool, // LAYER-FROZEN gate + default_input : u8, +} + +pub const HoloOutput = struct { + data : u16, + selected_layer : u8, + selected_phase : u8, + valid : bool, + frozen : bool, // Whether output is from LAYER-FROZEN input +} + +pub const HoloMuxState = struct { + current_select : u8, + frozen_select : u8, + cycle_count : u32, +} + +// ============================================================================ +// Holo Input Functions +// ============================================================================ + +// holo_input_valid(input: HoloInput) -> bool +// Check if holo input is valid +pub fn holo_input_valid(input: HoloInput) bool { + return input.valid and input.layer < NUM_INPUTS; +} + +// holo_input_score(input: HoloInput, config: HoloMuxConfig) -> u16 +// Calculate selection score for input +pub fn holo_input_score(input: HoloInput, config: HoloMuxConfig) u16 { + if (not holo_input_valid(input)) { + return 0; + } + + var score : u16 = @as(u16, @intCast(input.data)); + + if (config.phi_optimized) { + // Prefer lower phi score + const phi_bonus = 255 - input.phi_score; + score += @as(u16, phi_bonus) * 256; + } + + if (config.priority_select) { + // Layer priority affects score + const priority_bonus = (@as(u8, 3) - input.layer) * 1000; + score += priority_bonus; + } + + return score; +} + +// ============================================================================ +// Holo Mux Functions +// ============================================================================ + +// holo_mux_select(inputs: [NUM_INPUTS]HoloInput, select: u8, config: HoloMuxConfig) -> HoloOutput +// Select holo input based on select bits +pub fn holo_mux_select(inputs: [NUM_INPUTS]HoloInput, select: u8, config: HoloMuxConfig) HoloOutput { + const selected_idx = select & 0x03; + + // LAYER-FROZEN: check if frozen mode is active + if (config.layer_frozen) { + const frozen_input = inputs[config.default_input]; + return HoloOutput { + .data = frozen_input.data, + .selected_layer = frozen_input.layer, + .selected_phase = frozen_input.phase, + .valid = holo_input_valid(frozen_input), + .frozen = true, + }; + } + + const selected = inputs[@as(u8, @intCast(selected_idx))]; + return HoloOutput { + .data = selected.data, + .selected_layer = selected.layer, + .selected_phase = selected.phase, + .valid = holo_input_valid(selected), + .frozen = false, + }; +} + +// holo_mux_best(inputs: [NUM_INPUTS]HoloInput, config: HoloMuxConfig) -> HoloOutput +// Select best holo input based on score +pub fn holo_mux_best(inputs: [NUM_INPUTS]HoloInput, config: HoloMuxConfig) -> HoloOutput { + if (config.layer_frozen) { + const frozen_input = inputs[config.default_input]; + return HoloOutput { + .data = frozen_input.data, + .selected_layer = frozen_input.layer, + .selected_phase = frozen_input.phase, + .valid = holo_input_valid(frozen_input), + .frozen = true, + }; + } + + var best_idx : u8 = 0; + var best_score : u16 = 0; + + for (inputs, 0..) |input, i| { + const score = holo_input_score(input, config); + if (score > best_score and holo_input_valid(input)) { + best_score = score; + best_idx = @as(u8, @intCast(i)); + } + } + + const selected = inputs[best_idx]; + return HoloOutput { + .data = selected.data, + .selected_layer = selected.layer, + .selected_phase = selected.phase, + .valid = holo_input_valid(selected), + .frozen = false, + }; +} + +// holo_mux_valid(inputs: [NUM_INPUTS]HoloInput) -> u8 +// Get bitmask of valid inputs +pub fn holo_mux_valid(inputs: [NUM_INPUTS]HoloInput) -> u8 { + var mask : u8 = 0; + for (inputs, 0..) |input, i| { + if (holo_input_valid(input)) { + mask |= (@as(u8, 1) << i); + } + } + return mask; +} + +// holo_mux_count_valid(inputs: [NUM_INPUTS]HoloInput) -> u8 +// Count valid inputs +pub fn holo_mux_count_valid(inputs: [NUM_INPUTS]HoloInput) -> u8 { + var count : u8 = 0; + for (inputs) |input| { + if (holo_input_valid(input)) { + count += 1; + } + } + return count; +} + +// holo_mux_any_valid(inputs: [NUM_INPUTS]HoloInput) -> bool +// Check if any input is valid +pub fn holo_mux_any_valid(inputs: [NUM_INPUTS]HoloInput) -> bool { + for (inputs) |input| { + if (holo_input_valid(input)) { + return true; + } + } + return false; +} + +// holo_mux_all_valid(inputs: [NUM_INPUTS]HoloInput) -> bool +// Check if all inputs are valid +pub fn holo_mux_all_valid(inputs: [NUM_INPUTS]HoloInput) -> bool { + for (inputs) |input| { + if (not holo_input_valid(input)) { + return false; + } + } + return true; +} + +// ============================================================================ +// Holo Layer Functions +// ============================================================================ + +// holo_layer_is_north(layer: u8) -> bool +pub fn holo_layer_is_north(layer: u8) bool { + return layer == HOLO_LAYER_NORTH; +} + +// holo_layer_is_east(layer: u8) -> bool +pub fn holo_layer_is_east(layer: u8) bool { + return layer == HOLO_LAYER_EAST; +} + +// holo_layer_is_south(layer: u8) -> bool +pub fn holo_layer_is_south(layer: u8) bool { + return layer == HOLO_LAYER_SOUTH; +} + +// holo_layer_is_west(layer: u8) -> bool +pub fn holo_layer_is_west(layer: u8) bool { + return layer == HOLO_LAYER_WEST; +} + +// holo_opposite_layer(layer: u8) -> u8 +// Get opposite layer (N<->S, E<->W) +pub fn holo_opposite_layer(layer: u8) -> u8 { + switch (layer) { + HOLO_LAYER_NORTH => return HOLO_LAYER_SOUTH, + HOLO_LAYER_SOUTH => return HOLO_LAYER_NORTH, + HOLO_LAYER_EAST => return HOLO_LAYER_WEST, + HOLO_LAYER_WEST => return HOLO_LAYER_EAST, + else => return layer, + } +} + +// ============================================================================ +// Holo Phase Functions +// ============================================================================ + +// holo_phase_90_deg(phase: u8) -> u8 +// Rotate phase by 90 degrees +pub fn holo_phase_90_deg(phase: u8) -> u8 { + return (phase + 1) % 4; +} + +// holo_phase_180_deg(phase: u8) -> u8 +// Rotate phase by 180 degrees +pub fn holo_phase_180_deg(phase: u8) -> u8 { + return (phase + 2) % 4; +} + +// holo_phase_270_deg(phase: u8) -> u8 +// Rotate phase by 270 degrees +pub fn holo_phase_270_deg(phase: u8) -> u8 { + return (phase + 3) % 4; +} + +// holo_phase_opposite(phase: u8) -> u8 +// Get opposite phase +pub fn holo_phase_opposite(phase: u8) -> u8 { + return (phase + 2) % 4; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_holo_mux_x4(layer: u8, phase: u8, frozen: bool) -> u16 +// Encode holographic mux instruction +pub fn encode_holo_mux_x4(layer: u8, phase: u8, frozen: bool) u16 { + // Format: [OP:8][LAYER:2][PHASE:2][FROZEN:1][RES:3] + const op : u16 = @as(u16, OP_HOLO_MUX_X4) << 8; + const layer_field : u16 = @as(u16, layer & 0x03) << 6; + const phase_field : u16 = @as(u16, phase & 0x03) << 4; + const frozen_field : u16 = if (frozen) (@as(u16, 1) << 3) else 0; + return op | layer_field | phase_field | frozen_field; +} + +// decode_holo_mux_x4(encoded: u16) -> struct { layer: u8, phase: u8, frozen: bool } +// Decode holographic mux instruction +pub fn decode_holo_mux_x4(encoded: u16) struct { layer: u8, phase: u8, frozen: bool } { + const layer : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const phase : u8 = @as(u8, @truncate((encoded >> 4) & 0x03)); + const frozen : bool = ((encoded >> 3) & 1) != 0; + return .{ .layer = layer, .phase = phase, .frozen = frozen }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "num_inputs_four" { + try std.testing.expect(NUM_INPUTS == 4); +} + +test "data_width_sixteen" { + try std.testing.expect(DATA_WIDTH == 16); +} + +test "select_bits_two" { + try std.testing.expect(SELECT_BITS == 2); +} + +test "holo_layer_constants" { + try std.testing.expect(HOLO_LAYER_NORTH == 0); + try std.testing.expect(HOLO_LAYER_EAST == 1); + try std.testing.expect(HOLO_LAYER_SOUTH == 2); + try std.testing.expect(HOLO_LAYER_WEST == 3); +} + +test "holo_phase_constants" { + try std.testing.expect(HOLO_PHASE_0 == 0); + try std.testing.expect(HOLO_PHASE_90 == 1); + try std.testing.expect(HOLO_PHASE_180 == 2); + try std.testing.expect(HOLO_PHASE_270 == 3); +} + +test "holo_input_valid_true" { + given input = HoloInput{.data = 0x1234, .layer = 0, .phase = 0, .valid = true, .phi_score = 100} + try std.testing.expect(holo_input_valid(input) == true); +} + +test "holo_input_valid_false_invalid_layer" { + given input = HoloInput{.data = 0x1234, .layer = 4, .phase = 0, .valid = true, .phi_score = 100} + try std.testing.expect(holo_input_valid(input) == false); +} + +test "holo_input_valid_false_invalid_flag" { + given input = HoloInput{.data = 0x1234, .layer = 0, .phase = 0, .valid = false, .phi_score = 100} + try std.testing.expect(holo_input_valid(input) == false); +} + +test "holo_mux_select_north" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 100}, + } + try std.testing.expect(config = HoloMuxConfig{.phi_optimized = false, .priority_select = false, .layer_frozen = false, .default_input = 0}); + try std.testing.expect(output = holo_mux_select(inputs, 0, config)); + try std.testing.expect(output.data == 0x1000); + try std.testing.expect(output.selected_layer == 0); + try std.testing.expect(output.frozen == false); +} + +test "holo_mux_select_east" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 100}, + } + try std.testing.expect(config = HoloMuxConfig{.phi_optimized = false, .priority_select = false, .layer_frozen = false, .default_input = 0}); + try std.testing.expect(output = holo_mux_select(inputs, 1, config)); + try std.testing.expect(output.data == 0x2000); + try std.testing.expect(output.selected_layer == 1); +} + +test "holo_mux_frozen" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 100}, + } + try std.testing.expect(config = HoloMuxConfig{.phi_optimized = false, .priority_select = false, .layer_frozen = true, .default_input = 2}); + try std.testing.expect(output = holo_mux_select(inputs, 1, config)); + try std.testing.expect(output.data == 0x3000); + try std.testing.expect(output.selected_layer == 2); + try std.testing.expect(output.frozen == true); +} + +test "holo_mux_best_phi_optimized" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 50}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 200}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 10}, + } + try std.testing.expect(config = HoloMuxConfig{.phi_optimized = true, .priority_select = false, .layer_frozen = false, .default_input = 0}); + try std.testing.expect(output = holo_mux_best(inputs, config)); + try std.testing.expect(output.selected_layer == 3 // Best phi score); +} + +test "holo_mux_valid_mask" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(mask = holo_mux_valid(inputs)); + try std.testing.expect(mask == 0b00000101); +} + +test "holo_mux_count_valid" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(holo_mux_count_valid(inputs) == 2); +} + +test "holo_mux_any_valid_true" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(holo_mux_any_valid(inputs) == true); +} + +test "holo_mux_any_valid_false" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(holo_mux_any_valid(inputs) == false); +} + +test "holo_mux_all_valid_true" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 100}, + } + try std.testing.expect(holo_mux_all_valid(inputs) == true); +} + +test "holo_layer_is_north" { + try std.testing.expect(holo_layer_is_north(0) == true); + try std.testing.expect(holo_layer_is_north(1) == false); +} + +test "holo_layer_is_south" { + try std.testing.expect(holo_layer_is_south(2) == true); + try std.testing.expect(holo_layer_is_south(0) == false); +} + +test "holo_opposite_layer" { + try std.testing.expect(holo_opposite_layer(HOLO_LAYER_NORTH) == HOLO_LAYER_SOUTH); + try std.testing.expect(holo_opposite_layer(HOLO_LAYER_EAST) == HOLO_LAYER_WEST); +} + +test "holo_phase_90_deg" { + try std.testing.expect(holo_phase_90_deg(0) == 1); + try std.testing.expect(holo_phase_90_deg(1) == 2); + try std.testing.expect(holo_phase_90_deg(3) == 0); +} + +test "holo_phase_180_deg" { + try std.testing.expect(holo_phase_180_deg(0) == 2); + try std.testing.expect(holo_phase_180_deg(1) == 3); + try std.testing.expect(holo_phase_180_deg(2) == 0); +} + +test "holo_phase_opposite" { + try std.testing.expect(holo_phase_opposite(0) == 2); + try std.testing.expect(holo_phase_opposite(1) == 3); + try std.testing.expect(holo_phase_opposite(2) == 0); + try std.testing.expect(holo_phase_opposite(3) == 1); +} + +test "encode_holo_mux_x4" { + given encoded = encode_holo_mux_x4(0x02, 0x01, true) + try std.testing.expect((encoded >> 8) == OP_HOLO_MUX_X4); +} + +test "decode_holo_mux_x4" { + given decoded = decode_holo_mux_x4(0xE6A8) + try std.testing.expect(decoded.layer == 0x02); + try std.testing.expect(decoded.phase == 0x01); + try std.testing.expect(decoded.frozen == true); +} + +test "opcode_constant" { + try std.testing.expect(OP_HOLO_MUX_X4 == 0xE6); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant num_inputs_four + assert NUM_INPUTS == 4 + +invariant data_width_sixteen + assert DATA_WIDTH == 16 + +invariant select_bits_two + assert SELECT_BITS == 2 + +invariant holo_layer_values + try std.testing.expect(HOLO_LAYER_NORTH >= 0 and HOLO_LAYER_WEST <= 3); + +invariant holo_phase_values + try std.testing.expect(HOLO_PHASE_0 >= 0 and HOLO_PHASE_270 <= 3); + +invariant holo_valid_one + assert HOLO_VALID == 1 + +invariant holo_invalid_zero + assert HOLO_INVALID == 0 + +invariant holo_phase_90_deg_cyclic + assert holo_phase_90_deg(3) == 0 + +invariant holo_phase_180_deg_cyclic + assert holo_phase_180_deg(2) == 0 + +invariant holo_opposite_layer_twice + assert holo_opposite_layer(holo_opposite_layer(HOLO_LAYER_NORTH)) == HOLO_LAYER_NORTH + +invariant holo_phase_opposite_twice + assert holo_phase_opposite(holo_phase_opposite(0)) == 0 + +invariant holo_mux_count_valid_bound + given inputs = [_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4 + assert holo_mux_count_valid(inputs) <= NUM_INPUTS + +invariant holo_mux_any_implies_count_positive + given inputs = [_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4 + try std.testing.expect(any_valid = holo_mux_any_valid(inputs)); + try std.testing.expect(count = holo_mux_count_valid(inputs)); + assert not any_valid or count > 0 + +invariant holo_mux_all_valid_equals_count + given inputs = [_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4 + assert holo_mux_all_valid(inputs) == (holo_mux_count_valid(inputs) == NUM_INPUTS) + +invariant holo_mux_valid_mask_bits + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(mask = holo_mux_valid(inputs)); + try std.testing.expect(mask < (@as(u8, 1) << NUM_INPUTS)); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench holo_mux_select_latency + measure: nanoseconds to holo_mux_select([_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4, 2, .{.phi_optimized = false, .priority_select = false, .layer_frozen = false, .default_input = 0}) + target: < 50ns + +bench holo_mux_best_latency + measure: nanoseconds to holo_mux_best([_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4, .{.phi_optimized = true, .priority_select = false, .layer_frozen = false, .default_input = 0}) + target: < 100ns + +bench holo_mux_count_valid_latency + measure: nanoseconds to holo_mux_count_valid([_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4) + target: < 50ns + +bench encode_holo_mux_x4_latency + measure: nanoseconds to encode_holo_mux_x4(0x02, 0x01, true) + target: < 20ns + +bench decode_holo_mux_x4_latency + measure: nanoseconds to decode_holo_mux_x4(0xE6A8) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/int4.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/int4.t27 new file mode 100644 index 0000000000..6d4cab516d --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/int4.t27 @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/int4.t27 +// Int4 - 4-bit signed integer +// NUMERIC-STANDARD-001 Agent 9 (P1) + +module Int4 { + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // Int4 bit layout: S(3) - 4-bit two's complement + // S: 4 bits (signed 4-bit integer) + // + // Range: -8 to 7 + // 16 possible values + + const BITS : u8 = 4; + + // 2. Int4 Type + + struct Int4 { + raw : u4, // 4-bit raw value (two's complement) + } + + // 3. Encoding/Decoding + + // Encode i8 to Int4 (clamp to range) + fn encode(value: i8) -> Int4 { + const clamped = if (value > 7) { 7 } else if (value < -8) { -8 } else { value }; + // Two's complement: negative values are stored as 2^4 + value + let raw = clamped as u4; + if (clamped < 0) { + raw = (16 + clamped) as u4; + } + return Int4{ raw = raw }; + } + + // Decode Int4 to i8 + fn decode(i: Int4) -> i8 { + const raw = i.raw as i8; + // Two's complement: if sign bit set, negative + if (raw >= 8) { + return raw - 16; + } + return raw; + } + + // 4. Format Properties + + fn min_value() -> i8 { + return -8; + } + + fn max_value() -> i8 { + return 7; + } + + fn range() -> i8 { + return max_value() - min_value(); + } + + // 5. Validation + + fn validate_format() -> bool { + return BITS == 4; + } + + // 6. Use Cases + + // Int4 is optimal for: + // - Extreme quantization (87.5% smaller than INT8) + // - Binary classification (0/1) + // - Ternary classification (-1, 0, +1) + // - Activation masks + // - Control signals + + // Memory: 4 bits = 0.5 bytes (8x INT32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 4.0 / 32.0; // 0.125 + + // 7. Arithmetic Operations + + fn add(a: Int4, b: Int4) -> Int4 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a + val_b; + return encode(result); + } + + fn sub(a: Int4, b: Int4) -> Int4 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a - val_b; + return encode(result); + } + + fn mul(a: Int4, b: Int4) -> Int4 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a * val_b; + return encode(result); + } + + fn neg(i: Int4) -> Int4 { + return encode(-decode(i)); + } + + fn abs(i: Int4) -> Int4 { + const val = decode(i); + return encode(if (val < 0) { -val } else { val }); + } + + // 8. Comparison Operations + + fn eq(a: Int4, b: Int4) -> bool { + return decode(a) == decode(b); + } + + fn ne(a: Int4, b: Int4) -> bool { + return decode(a) != decode(b); + } + + fn lt(a: Int4, b: Int4) -> bool { + return decode(a) < decode(b); + } + + fn le(a: Int4, b: Int4) -> bool { + return decode(a) <= decode(b); + } + + fn gt(a: Int4, b: Int4) -> bool { + return decode(a) > decode(b); + } + + fn ge(a: Int4, b: Int4) -> bool { + return decode(a) >= decode(b); + } + + // TDD-Inside-Spec: Tests and Invariants for Int4 + + test int4_decode_zero + given i = Int4{ raw = 0 } + when value = decode(i) + then value == 0 + + test int4_encode_zero_roundtrip + given original = 0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test int4_encode_positive_values + for (vals) |vals| + then encode(vals[i]).raw == vals[i] for vals[i] in {0, 1, 2, 3, 4, 5, 6, 7} + + test int4_decode_positive_values + for (vals) |vals| + then decode(Int4{ raw = vals[i] }) == vals[i] for vals[i] in {0, 1, 2, 3, 4, 5, 6, 7} + + test int4_encode_negative_values + given vals = [-1, -2, -3, -4, -5, -6, -7, -8] + then encode(vals[i]).raw == (16 + vals[i]) for vals[i] in vals + + test int4_decode_negative_values + given enc = [15, 14, 13, 12, 11, 10, 9, 8] + then decode(Int4{ raw = enc[i] }) == enc[i] - 16 for enc[i] in enc + + test int4_bits_constant + then BITS == 4 + + test int4_max_value + given max_val = max_value() + then max_val == 7 + + test int4_min_value + given min_val = min_value() + then min_val == -8 + + test int4_range + given range_val = range() + then range_val == 15 + + test int4_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.125) < 0.01 + + test int4_validate_format_success + given valid = validate_format() + then valid == true + + test int4_add_positive + given a = Int4{ raw = 3 } + and b = Int4{ raw = 4 } + and result = add(a, b) + then decode(result) == 7 + + test int4_add_overflow + given a = Int4{ raw = 4 } + and b = Int4{ raw = 4 } + and result = add(a, b) + then decode(result) == 7 + + test int4_sub_positive + given a = Int4{ raw = 7 } + and b = Int4{ raw = 3 } + and result = sub(a, b) + then decode(result) == 4 + + test int4_sub_underflow + given a = Int4{ raw = 1 } + and b = Int4{ raw = 5 } + and result = sub(a, b) + then decode(result) == -4 + + test int4_mul_positive + given a = Int4{ raw = 2 } + and b = Int4{ raw = 3 } + and result = mul(a, b) + then decode(result) == 6 + + test int4_mul_negative + given a = Int4{ raw = 3 } + and b = encode(-2) + and result = mul(a, b) + then decode(result) == -6 + + test int4_neg_positive + given i = Int4{ raw = 5 } + and result = neg(i) + then decode(result) == -5 + + test int4_neg_negative + given i = encode(-3) + and result = neg(i) + then decode(result) == 3 + + test int4_abs_positive + given i = Int4{ raw = 5 } + and result = abs(i) + then decode(result) == 5 + + test int4_abs_negative + given i = encode(-3) + and result = abs(i) + then decode(result) == 3 + + test int4_eq_true + given a = Int4{ raw = 3 } + and b = Int4{ raw = 3 } + then eq(a, b) == true + + test int4_eq_false + given a = Int4{ raw = 3 } + and b = Int4{ raw = 4 } + then eq(a, b) == false + + test int4_lt_true + given a = Int4{ raw = 2 } + and b = Int4{ raw = 5 } + then lt(a, b) == true + + test int4_lt_false + given a = Int4{ raw = 5 } + and b = Int4{ raw = 2 } + then lt(a, b) == false + + test int4_encode_clamps_above_max + given original = 10 + and encoded = encode(original) + then decode(encoded) == 7 + + test int4_encode_clamps_below_min + given original = -10 + and encoded = encode(original) + then decode(encoded) == -8 + + invariant int4_bits_constant + assert BITS == 4 + + invariant int4_max_value_is_7 + assert max_value() == 7 + + invariant int4_min_value_is_neg_8 + assert min_value() == -8 + + invariant int4_range_is_15 + assert range() == 15 + + invariant int4_encode_preserves_zero + assert encode(0).raw == 0 + + invariant int4_decode_preserves_zero + assert decode(Int4{raw = 0}) == 0 + + invariant int4_add_is_commutative + given a = 3 and b = 2 + then decode(add(encode(a), encode(b))) == decode(add(encode(b), encode(a))) + + invariant int4_zero_add_identity + given x = 5 + then decode(add(encode(x), encode(0))) == x + + invariant int4_neg_neg_is_identity + given x = 5 + then decode(neg(neg(encode(x)))) == x + + invariant int4_mul_by_neg_one_is_neg + given x = 5 + then decode(mul(encode(x), encode(-1))) == -x + + invariant int4_abs_is_non_negative + for (vals) |vals| + then decode(abs(encode(vals[i]))) >= 0 for vals[i] in vals + + bench int4_encode_latency + measure: nanoseconds to encode(5) + target: < 20ns + + bench int4_decode_latency + measure: nanoseconds to decode(Int4{ raw = 5 }) + target: < 10ns + + bench int4_encode_decode_roundtrip + measure: nanoseconds to encode(5) and decode(encode(5)) + target: < 30ns + + bench int4_add_latency + measure: nanoseconds to add(Int4{ raw = 3 }, Int4{ raw = 4 }) + target: < 30ns + + bench int4_mul_latency + measure: nanoseconds to mul(Int4{ raw = 3 }, Int4{ raw = 2 }) + target: < 40ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/int8.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/int8.t27 new file mode 100644 index 0000000000..c8daff4905 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/int8.t27 @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/int8.t27 +// Int8 - 8-bit signed integer +// NUMERIC-STANDARD-001 Agent 10 (P1) + +module Int8 { + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // Int8 bit layout: S(7) - 8-bit two's complement + // S: 8 bits (signed 8-bit integer) + // + // Range: -128 to 127 + // 256 possible values + + const BITS : u8 = 8; + + // 2. Int8 Type + + struct Int8 { + raw : u8, // 8-bit raw value (two's complement) + } + + // 3. Encoding/Decoding + + // Encode i32 to Int8 (clamp to range) + fn encode(value: i32) -> Int8 { + const clamped = if (value > 127) { 127 } else if (value < -128) { -128 } else { value }; + // Two's complement: negative values are stored as 2^8 + value + let raw = clamped as u8; + if (clamped < 0) { + raw = (256 + clamped) as u8; + } + return Int8{ raw = raw }; + } + + // Decode Int8 to i32 + fn decode(i: Int8) -> i32 { + const raw = i.raw as i32; + // Two's complement: if sign bit set, negative + if (raw >= 128) { + return raw - 256; + } + return raw; + } + + // 4. Format Properties + + fn min_value() -> i32 { + return -128; + } + + fn max_value() -> i32 { + return 127; + } + + fn range() -> i32 { + return max_value() - min_value(); + } + + fn num_values() -> i32 { + return 256; + } + + // 5. Validation + + fn validate_format() -> bool { + return BITS == 8; + } + + // 6. Use Cases + + // Int8 is optimal for: + // - Standard integer quantization (75% smaller than INT32) + // - Neural network weight quantization + // - Activation caching + // - General-purpose integer arithmetic + // - Control and status registers + + // Memory: 8 bits = 1 byte (4x INT32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 8.0 / 32.0; // 0.25 + + // 7. Arithmetic Operations + + fn add(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a + val_b; + return encode(result); + } + + fn sub(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a - val_b; + return encode(result); + } + + fn mul(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a * val_b; + return encode(result); + } + + fn div(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = if (val_b != 0) { val_a / val_b } else { 0 }; + return encode(result); + } + + fn mod(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = if (val_b != 0) { val_a % val_b } else { 0 }; + return encode(result); + } + + fn neg(i: Int8) -> Int8 { + return encode(-decode(i)); + } + + fn abs(i: Int8) -> Int8 { + const val = decode(i); + return encode(if (val < 0) { -val } else { val }); + } + + // 8. Comparison Operations + + fn eq(a: Int8, b: Int8) -> bool { + return decode(a) == decode(b); + } + + fn ne(a: Int8, b: Int8) -> bool { + return decode(a) != decode(b); + } + + fn lt(a: Int8, b: Int8) -> bool { + return decode(a) < decode(b); + } + + fn le(a: Int8, b: Int8) -> bool { + return decode(a) <= decode(b); + } + + fn gt(a: Int8, b: Int8) -> bool { + return decode(a) > decode(b); + } + + fn ge(a: Int8, b: Int8) -> bool { + return decode(a) >= decode(b); + } + + // 9. Bitwise Operations + + fn and(a: Int8, b: Int8) -> Int8 { + return Int8{ raw = a.raw & b.raw }; + } + + fn or(a: Int8, b: Int8) -> Int8 { + return Int8{ raw = a.raw | b.raw }; + } + + fn xor(a: Int8, b: Int8) -> Int8 { + return Int8{ raw = a.raw ^ b.raw }; + } + + fn not(i: Int8) -> Int8 { + return Int8{ raw = i.raw ^ 0xFF }; + } + + fn shl(a: Int8, shift: u8) -> Int8 { + const clamped_shift = if (shift > 7) { 7 } else { shift }; + return Int8{ raw = (a.raw << clamped_shift) & 0xFF }; + } + + fn shr(a: Int8, shift: u8) -> Int8 { + const clamped_shift = if (shift > 7) { 7 } else { shift }; + // Arithmetic shift for negative numbers + const val = decode(a); + const result = val >> (clamped_shift as i32); + return encode(result); + } + + // TDD-Inside-Spec: Tests and Invariants for Int8 + + test int8_decode_zero + given i = Int8{ raw = 0 } + when value = decode(i) + then value == 0 + + test int8_encode_zero_roundtrip + given original = 0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test int8_encode_positive_values + for (vals) |vals| + then encode(vals[i]).raw == vals[i] for vals[i] in {0, 1, 2, 50, 100, 127} + + test int8_decode_positive_values + for (vals) |vals| + then decode(Int8{ raw = vals[i] }) == vals[i] for vals[i] in {0, 1, 2, 50, 100, 127} + + test int8_encode_negative_values + given vals = [-1, -2, -50, -100, -127, -128] + then encode(vals[i]).raw == (256 + vals[i]) for vals[i] in vals + + test int8_decode_negative_values + given enc = [255, 254, 206, 156, 129, 128] + then decode(Int8{ raw = enc[i] }) == enc[i] - 256 for enc[i] in enc + + test int8_bits_constant + then BITS == 8 + + test int8_max_value + given max_val = max_value() + then max_val == 127 + + test int8_min_value + given min_val = min_value() + then min_val == -128 + + test int8_range + given range_val = range() + then range_val == 255 + + test int8_num_values + given num = num_values() + then num == 256 + + test int8_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.25) < 0.01 + + test int8_validate_format_success + given valid = validate_format() + then valid == true + + test int8_add_positive + given a = Int8{ raw = 50 } + and b = Int8{ raw = 60 } + and result = add(a, b) + then decode(result) == 110 + + test int8_add_overflow + given a = Int8{ raw = 100 } + and b = Int8{ raw = 50 } + and result = add(a, b) + then decode(result) == 127 + + test int8_sub_positive + given a = Int8{ raw = 100 } + and b = Int8{ raw = 30 } + and result = sub(a, b) + then decode(result) == 70 + + test int8_sub_underflow + given a = Int8{ raw = 50 } + and b = encode(100) + and result = sub(a, b) + then decode(result) == -50 + + test int8_mul_positive + given a = Int8{ raw = 10 } + and b = Int8{ raw = 12 } + and result = mul(a, b) + then decode(result) == 120 + + test int8_mul_overflow + given a = Int8{ raw = 20 } + and b = Int8{ raw = 20 } + and result = mul(a, b) + then decode(result) == 127 + + test int8_div_positive + given a = Int8{ raw = 100 } + and b = Int8{ raw = 25 } + and result = div(a, b) + then decode(result) == 4 + + test int8_div_by_zero + given a = Int8{ raw = 100 } + and b = Int8{ raw = 0 } + and result = div(a, b) + then decode(result) == 0 + + test int8_mod_positive + given a = Int8{ raw = 13 } + and b = Int8{ raw = 5 } + and result = mod(a, b) + then decode(result) == 3 + + test int8_mod_by_zero + given a = Int8{ raw = 13 } + and b = Int8{ raw = 0 } + and result = mod(a, b) + then decode(result) == 0 + + test int8_neg_positive + given i = Int8{ raw = 50 } + and result = neg(i) + then decode(result) == -50 + + test int8_neg_negative + given i = encode(-50) + and result = neg(i) + then decode(result) == 50 + + test int8_neg_zero + given i = Int8{ raw = 0 } + and result = neg(i) + then decode(result) == 0 + + test int8_abs_positive + given i = Int8{ raw = 50 } + and result = abs(i) + then decode(result) == 50 + + test int8_abs_negative + given i = encode(-50) + and result = abs(i) + then decode(result) == 50 + + test int8_abs_zero + given i = Int8{ raw = 0 } + and result = abs(i) + then decode(result) == 0 + + test int8_eq_true + given a = Int8{ raw = 50 } + and b = Int8{ raw = 50 } + then eq(a, b) == true + + test int8_eq_false + given a = Int8{ raw = 50 } + and b = Int8{ raw = 51 } + then eq(a, b) == false + + test int8_lt_true + given a = Int8{ raw = 20 } + and b = Int8{ raw = 50 } + then lt(a, b) == true + + test int8_lt_false + given a = Int8{ raw = 50 } + and b = Int8{ raw = 20 } + then lt(a, b) == false + + test int8_and_operation + given a = Int8{ raw = 0x55 } + and b = Int8{ raw = 0xAA } + and result = and(a, b) + then result.raw == 0x00 + + test int8_or_operation + given a = Int8{ raw = 0x55 } + and b = Int8{ raw = 0xAA } + and result = or(a, b) + then result.raw == 0xFF + + test int8_xor_operation + given a = Int8{ raw = 0x55 } + and b = Int8{ raw = 0xAA } + and result = xor(a, b) + then result.raw == 0xFF + + test int8_not_operation + given i = Int8{ raw = 0x55 } + and result = not(i) + then result.raw == 0xAA + + test int8_shl_left + given a = Int8{ raw = 0x01 } + and result = shl(a, 3) + then result.raw == 0x08 + + test int8_shl_clamp + given a = Int8{ raw = 0x01 } + and result = shl(a, 10) + then result.raw == 0x80 + + test int8_shr_right + given a = Int8{ raw = 0x80 } + and result = shr(a, 2) + then decode(result) == -32 + + test int8_encode_clamps_above_max + given original = 200 + and encoded = encode(original) + then decode(encoded) == 127 + + test int8_encode_clamps_below_min + given original = -200 + and encoded = encode(original) + then decode(encoded) == -128 + + test int8_all_values_encode_decode + for (i in 0..255) + then decode(encode(i - 128)) == (i - 128) + + invariant int8_bits_constant + assert BITS == 8 + + invariant int8_max_value_is_127 + assert max_value() == 127 + + invariant int8_min_value_is_neg_128 + assert min_value() == -128 + + invariant int8_range_is_255 + assert range() == 255 + + invariant int8_num_values_is_256 + assert num_values() == 256 + + invariant int8_encode_preserves_zero + assert encode(0).raw == 0 + + invariant int8_decode_preserves_zero + assert decode(Int8{raw = 0}) == 0 + + invariant int8_add_is_commutative + given a = 50 and b = 30 + then decode(add(encode(a), encode(b))) == decode(add(encode(b), encode(a))) + + invariant int8_zero_add_identity + for (x in {0, 50, 100, 127}) + then decode(add(encode(x), encode(0))) == x + + invariant int8_sub_self_is_zero + for (x in {0, 50, 100, 127}) + then decode(sub(encode(x), encode(x))) == 0 + + invariant int8_mul_by_neg_one_is_neg + for (x in {0, 50, 100, 127}) + then decode(mul(encode(x), encode(-1))) == -x + + invariant int8_mul_by_zero_is_zero + for (x in {0, 50, 100, 127}) + then decode(mul(encode(x), encode(0))) == 0 + + invariant int8_mul_by_one_is_identity + for (x in {0, 50, 100, 127}) + then decode(mul(encode(x), encode(1))) == x + + invariant int8_neg_neg_is_identity + for (x in {0, 50, 100, 127}) + then decode(neg(neg(encode(x)))) == x + + invariant int8_abs_is_non_negative + for (x in {-128..127}) + then decode(abs(encode(x))) >= 0 + + invariant int8_abs_preserves_zero + assert decode(abs(Int8{raw = 0})) == 0 + + invariant int8_shl_zero_is_zero + for (shift in {0, 1, 7}) + then shl(Int8{raw = 1}, shift).raw == 1 << shift + + invariant int8_shr_by_max_is_zero_or_neg_one + given i = Int8{ raw = 1 } + then shr(i, 7).raw == 0 + + bench int8_encode_latency + measure: nanoseconds to encode(50) + target: < 20ns + + bench int8_decode_latency + measure: nanoseconds to decode(Int8{ raw = 50 }) + target: < 10ns + + bench int8_encode_decode_roundtrip + measure: nanoseconds to encode(50) and decode(encode(50)) + target: < 30ns + + bench int8_add_latency + measure: nanoseconds to add(Int8{ raw = 30 }, Int8{ raw = 40 }) + target: < 30ns + + bench int8_mul_latency + measure: nanoseconds to mul(Int8{ raw = 10 }, Int8{ raw = 10 }) + target: < 30ns + + bench int8_div_latency + measure: nanoseconds to div(Int8{ raw = 100 }, Int8{ raw = 10 }) + target: < 30ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/lane_l_precheck.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/lane_l_precheck.t27 new file mode 100644 index 0000000000..f5462d9980 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/lane_l_precheck.t27 @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: Apache-2.0 +; lane_l_precheck.t27 — Sacred Opcode 0xDF: LUT Lookup Precheck +; Hardware pre-check for LUT lookup operations +; Validates lane readiness, checks LUT access permissions, and prepares address +; φ² + 1/φ² = 3 | TRINITY + +module sacred-lane_precheck; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_LUT_LOOKUP : u8 = 0xDF; + +pub const LANE_COUNT : u8 = 8; +pub const LUT_DEPTH : u16 = 256; +pub const LUT_ADDR_BITS : u8 = 8; +pub const LANE_ID_BITS : u8 = 3; + +pub const LANE_IDLE : u8 = 0; +pub const LANE_READY : u8 = 1; +pub const LANE_BUSY : u8 = 2; +pub const LANE_ERROR : u8 = 3; + +pub const CHECK_PASS : u8 = 1; +pub const CHECK_FAIL : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const LaneStatus = enum(u8) { + idle = LANE_IDLE, + ready = LANE_READY, + busy = LANE_BUSY, + error = LANE_ERROR, +} + +pub const PrecheckFlags = packed struct(u8) { + lut_enabled : u1, + lane_valid : u1, + address_valid : u1, + permission_ok : u1, + reserved : u4, +} + +pub const PrecheckResult = struct { + lane_id : u8, + status : LaneStatus, + flags : PrecheckFlags, + lut_address : u8, + error_code : u8, +} + +pub const LaneConfig = struct { + enabled : bool, + lut_base : u8, + lut_mask : u8, + priority : u8, +} + +// ============================================================================ +// Lane Status Functions +// ============================================================================ + +// lane_is_valid(lane_id: u8) -> bool +// Check if lane ID is valid +pub fn lane_is_valid(lane_id: u8) bool { + return lane_id < LANE_COUNT; +} + +// lane_is_ready(status: LaneStatus) -> bool +// Check if lane is ready for operation +pub fn lane_is_ready(status: LaneStatus) bool { + return status == LaneStatus.ready; +} + +// lane_is_busy(status: LaneStatus) -> bool +// Check if lane is busy +pub fn lane_is_busy(status: LaneStatus) bool { + return status == LaneStatus.busy; +} + +// lane_is_error(status: LaneStatus) -> bool +// Check if lane is in error state +pub fn lane_is_error(status: LaneStatus) bool { + return status == LaneStatus.error; +} + +// lane_is_idle(status: LaneStatus) -> bool +// Check if lane is idle +pub fn lane_is_idle(status: LaneStatus) bool { + return status == LaneStatus.idle; +} + +// ============================================================================ +// Precheck Functions +// ============================================================================ + +// precheck_lane(lane_id: u8, lut_addr: u8, config: LaneConfig) -> PrecheckResult +// Perform precheck on a lane for LUT lookup +pub fn precheck_lane(lane_id: u8, lut_addr: u8, config: LaneConfig) PrecheckResult { + var flags : PrecheckFlags = undefined; + flags.lut_enabled = if (config.enabled) 1 else 0; + flags.lane_valid = if (lane_is_valid(lane_id)) 1 else 0; + flags.address_valid = if (lut_addr < LUT_DEPTH) 1 else 0; + flags.permission_ok = if (config.enabled and ((lut_addr & config.lut_mask) < config.lut_mask)) 1 else 0; + flags.reserved = 0; + + var status = LaneStatus.ready; + var error_code : u8 = 0; + + if (flags.lane_valid == 0) { + status = LaneStatus.error; + error_code = 1; // Invalid lane ID + } else if (flags.address_valid == 0) { + status = LaneStatus.error; + error_code = 2; // Invalid address + } else if (flags.permission_ok == 0) { + status = LaneStatus.error; + error_code = 3; // Permission denied + } else if (flags.lut_enabled == 0) { + status = LaneStatus.error; + error_code = 4; // LUT not enabled + } + + return PrecheckResult { + .lane_id = lane_id, + .status = status, + .flags = flags, + .lut_address = if (status == LaneStatus.ready) lut_addr else 0, + .error_code = error_code, + }; +} + +// precheck_all_lanes(lane_bits: u8, lut_addr: u8, configs: [LANE_COUNT]LaneConfig) -> [LANE_COUNT]PrecheckResult +// Precheck all lanes simultaneously +pub fn precheck_all_lanes(lane_bits: u8, lut_addr: u8, configs: [LANE_COUNT]LaneConfig) [LANE_COUNT]PrecheckResult { + var results : [LANE_COUNT]PrecheckResult = undefined; + for (0..LANE_COUNT) |i| { + if ((lane_bits >> i) & 1 != 0) { + results[i] = precheck_lane(@as(u8, @intCast(i)), lut_addr, configs[i]); + } else { + results[i] = PrecheckResult { + .lane_id = @as(u8, @intCast(i)), + .status = LaneStatus.idle, + .flags = PrecheckFlags {.lut_enabled = 0, .lane_valid = 1, .address_valid = 0, .permission_ok = 0, .reserved = 0}, + .lut_address = 0, + .error_code = 0, + }; + } + } + return results; +} + +// precheck_pass(result: PrecheckResult) -> bool +// Check if precheck passed +pub fn precheck_pass(result: PrecheckResult) bool { + return result.status == LaneStatus.ready and result.flags.permission_ok != 0; +} + +// precheck_fail(result: PrecheckResult) -> bool +// Check if precheck failed +pub fn precheck_fail(result: PrecheckResult) bool { + return result.status == LaneStatus.error or result.flags.permission_ok == 0; +} + +// precheck_any_pass(results: [LANE_COUNT]PrecheckResult) -> bool +// Check if any lane precheck passed +pub fn precheck_any_pass(results: [LANE_COUNT]PrecheckResult) bool { + for (results) |result| { + if (precheck_pass(result)) { + return true; + } + } + return false; +} + +// precheck_all_pass(results: [LANE_COUNT]PrecheckResult) -> bool +// Check if all lane prechecks passed +pub fn precheck_all_pass(results: [LANE_COUNT]PrecheckResult) bool { + for (results) |result| { + if (precheck_fail(result)) { + return false; + } + } + return true; +} + +// precheck_count_passed(results: [LANE_COUNT]PrecheckResult) -> u8 +// Count number of lanes that passed precheck +pub fn precheck_count_passed(results: [LANE_COUNT]PrecheckResult) u8 { + var count : u8 = 0; + for (results) |result| { + if (precheck_pass(result)) { + count += 1; + } + } + return count; +} + +// precheck_get_ready_lanes(results: [LANE_COUNT]PrecheckResult) -> u8 +// Get bitmask of ready lanes +pub fn precheck_get_ready_lanes(results: [LANE_COUNT]PrecheckResult) -> u8 { + var mask : u8 = 0; + for (results, 0..) |result, i| { + if (precheck_pass(result)) { + mask |= (@as(u8, 1) << i); + } + } + return mask; +} + +// ============================================================================ +// LUT Address Functions +// ============================================================================ + +// lut_address_valid(addr: u8, config: LaneConfig) -> bool +// Check if LUT address is valid for given config +pub fn lut_address_valid(addr: u8, config: LaneConfig) bool { + if (addr >= LUT_DEPTH) { + return false; + } + if (not config.enabled) { + return false; + } + return (addr & config.lut_mask) < config.lut_mask; +} + +// lut_address_masked(addr: u8, config: LaneConfig) -> u8 +// Get masked LUT address +pub fn lut_address_masked(addr: u8, config: LaneConfig) -> u8 { + return addr & config.lut_mask; +} + +// lut_address_offset(addr: u8, base: u8) -> u8 +// Get offset address from base +pub fn lut_address_offset(addr: u8, base: u8) -> u8 { + if (addr >= base) { + return addr - base; + } + return 0; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_lut_lookup(lane_bits: u8, lut_addr: u8) -> u16 +// Encode LUT lookup instruction +pub fn encode_lut_lookup(lane_bits: u8, lut_addr: u8) u16 { + // Format: [OP:8][LANE:3][ADDR:5] + const op : u16 = @as(u16, OP_LUT_LOOKUP) << 8; + const lane : u16 = @as(u16, lane_bits & 0x07) << 5; + const addr : u16 = @as(u16, lut_addr & 0x1F); + return op | lane | addr; +} + +// decode_lut_lookup(encoded: u16) -> struct { lane_bits: u8, lut_addr: u8 } +// Decode LUT lookup instruction +pub fn decode_lut_lookup(encoded: u16) struct { lane_bits: u8, lut_addr: u8 } { + const lane_bits : u8 = @as(u8, @truncate((encoded >> 5) & 0x07)); + const lut_addr : u8 = @as(u8, @truncate(encoded & 0x1F)); + return .{ .lane_bits = lane_bits, .lut_addr = lut_addr }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "lane_is_valid_true" { + try std.testing.expect(lane_is_valid(0) == true); + try std.testing.expect(lane_is_valid(7) == true); +} + +test "lane_is_valid_false" { + try std.testing.expect(lane_is_valid(8) == false); + try std.testing.expect(lane_is_valid(255) == false); +} + +test "lane_is_ready_true" { + try std.testing.expect(lane_is_ready(LaneStatus.ready) == true); +} + +test "lane_is_ready_false" { + try std.testing.expect(lane_is_ready(LaneStatus.idle) == false); + try std.testing.expect(lane_is_ready(LaneStatus.busy) == false); + try std.testing.expect(lane_is_ready(LaneStatus.error) == false); +} + +test "lane_is_busy_true" { + try std.testing.expect(lane_is_busy(LaneStatus.busy) == true); +} + +test "lane_is_error_true" { + try std.testing.expect(lane_is_error(LaneStatus.error) == true); +} + +test "precheck_lane_pass" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 128, config)); + try std.testing.expect(precheck_pass(result) == true); + try std.testing.expect(result.error_code == 0); +} + +test "precheck_lane_invalid_lane" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(8, 128, config)); + try std.testing.expect(precheck_fail(result) == true); + try std.testing.expect(result.error_code == 1); +} + +test "precheck_lane_invalid_address" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 256, config)); + try std.testing.expect(precheck_fail(result) == true); + try std.testing.expect(result.error_code == 2); +} + +test "precheck_lane_permission_denied" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 0x0F, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 0x80, config)); + try std.testing.expect(precheck_fail(result) == true); + try std.testing.expect(result.error_code == 3); +} + +test "precheck_lane_lut_disabled" { + given config = LaneConfig{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 128, config)); + try std.testing.expect(precheck_fail(result) == true); + try std.testing.expect(result.error_code == 4); +} + +test "precheck_any_pass_true" { + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000011, 128, configs)); + try std.testing.expect(precheck_any_pass(results) == true); +} + +test "precheck_any_pass_false" { + given configs = [_]LaneConfig{ + .{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000011, 128, configs)); + try std.testing.expect(precheck_any_pass(results) == false); +} + +test "precheck_all_pass_true" { + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000011, 128, configs)); + try std.testing.expect(precheck_all_pass(results) == true); +} + +test "precheck_count_passed" { + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000111, 128, configs)); + try std.testing.expect(precheck_count_passed(results) == 2); +} + +test "precheck_get_ready_lanes" { + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000111, 128, configs)); + try std.testing.expect(precheck_get_ready_lanes(results) == 0b00000101); +} + +test "lut_address_valid_true" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(lut_address_valid(128, config) == true); +} + +test "lut_address_valid_disabled" { + given config = LaneConfig{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(lut_address_valid(128, config) == false); +} + +test "lut_address_valid_masked" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 0x0F, .priority = 0} + try std.testing.expect(lut_address_valid(0x08, config) == true); + try std.testing.expect(lut_address_valid(0x80, config) == false); +} + +test "lut_address_masked_value" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 0x0F, .priority = 0} + try std.testing.expect(lut_address_masked(0xAB, config) == 0x0B); +} + +test "lut_address_offset" { + try std.testing.expect(lut_address_offset(20, 10) == 10); + try std.testing.expect(lut_address_offset(5, 10) == 0); +} + +test "encode_lut_lookup" { + given encoded = encode_lut_lookup(0b00000101, 0x10) + try std.testing.expect((encoded >> 8) == OP_LUT_LOOKUP); +} + +test "decode_lut_lookup" { + given decoded = decode_lut_lookup(0xDF50) + try std.testing.expect(decoded.lane_bits == 0b00000010); + try std.testing.expect(decoded.lut_addr == 0x10); +} + +test "opcode_constant" { + try std.testing.expect(OP_LUT_LOOKUP == 0xDF); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant lane_count_eight + assert LANE_COUNT == 8 + +invariant lut_depth_256 + assert LUT_DEPTH == 256 + +invariant lut_addr_bits_eight + assert LUT_ADDR_BITS == 8 + +invariant lane_id_bits_three + assert LANE_ID_BITS == 3 + +invariant check_pass_one + assert CHECK_PASS == 1 + +invariant check_fail_zero + assert CHECK_FAIL == 0 + +invariant lane_valid_range + assert lane_is_valid(0) == true and lane_is_valid(7) == true and lane_is_valid(8) == false + +invariant precheck_pass_and_fail_opposite + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 128, config)); + assert precheck_pass(result) != precheck_fail(result) + +invariant ready_lane_not_busy + try std.testing.expect(lane_is_ready(LaneStatus.ready) != lane_is_busy(LaneStatus.ready)); + +invariant error_lane_not_ready + try std.testing.expect(lane_is_error(LaneStatus.error) != lane_is_ready(LaneStatus.error)); + +invariant precheck_count_bound + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0xFF, 128, configs)); + assert precheck_count_passed(results) <= LANE_COUNT + +invariant ready_mask_bound + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0xFF, 128, configs)); + try std.testing.expect(precheck_get_ready_lanes(results) < (@as(u8, 1) << LANE_COUNT)); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench precheck_lane_latency + measure: nanoseconds to precheck_lane(0, 128, .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}) + target: < 50ns + +bench precheck_all_lanes_latency + measure: nanoseconds to precheck_all_lanes(0xFF, 128, [_]LaneConfig{.{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}} ** 8) + target: < 200ns + +bench encode_lut_lookup_latency + measure: nanoseconds to encode_lut_lookup(0b00000101, 0x10) + target: < 20ns + +bench decode_lut_lookup_latency + measure: nanoseconds to decode_lut_lookup(0xDF50) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/lut_npu_81_entry.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/lut_npu_81_entry.t27 new file mode 100644 index 0000000000..9aa2002684 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/lut_npu_81_entry.t27 @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: Apache-2.0 +; lut_npu_81_entry.t27 — Sacred Opcode 0xE3: LUT NPU 81-Entry Lookup +; Hardware LUT for NPU operations with 81 entries (9×9 transform) +; φ² + 1/φ² = 3 | TRINITY + +module sacred-lut_npu_81_entry; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_LUT_NPU : u8 = 0xE3; + +pub const LUT_SIZE : u8 = 81; // 9×9 = 81 entries +pub const LUT_ADDR_BITS : u8 = 7; // 7 bits for 0-80 +pub const LUT_VALUE_BITS : u8 = 16; + +pub const ENTRY_VALID : u8 = 1; +pub const ENTRY_INVALID : u8 = 0; + +pub const PHI_CONST_BASE : u8 = 0; +pub const CONST_LUT_END : u8 = 80; + +// ============================================================================ +// Types +// ============================================================================ + +pub const LutEntry = struct { + address : u8, + value : u16, + valid : bool, + phi_related : bool, +} + +pub const LutConfig = struct { + read_only : bool, + phi_optimized : bool, + cache_enabled : bool, +} + +pub const NpuOperation = enum(u8) { + lookup = 0, + transform = 1, + phi_project = 2, + sparse_reduce = 3, + quantize = 4, + dequantize = 5, + composite = 6, + custom = 7, +} + +pub const Transform9x9 = struct { + grid : [9][9]i8, + sparsity : u8, // 0-100% + phi_score : u16, // φ-distance from ideal +} + +// ============================================================================ +// LUT Entry Functions +// ============================================================================ + +// lut_entry_valid(entry: LutEntry) -> bool +// Check if LUT entry is valid +pub fn lut_entry_valid(entry: LutEntry) bool { + return entry.valid and entry.address < LUT_SIZE; +} + +// lut_entry_address_valid(addr: u8) -> bool +// Check if address is valid +pub fn lut_entry_address_valid(addr: u8) bool { + return addr < LUT_SIZE; +} + +// create_lut_entry(addr: u8, value: u16, phi_related: bool) -> LutEntry +// Create a LUT entry +pub fn create_lut_entry(addr: u8, value: u16, phi_related: bool) LutEntry { + return LutEntry { + .address = addr, + .value = value, + .valid = lut_entry_address_valid(addr), + .phi_related = phi_related, + }; +} + +// ============================================================================ +// NPU Operation Functions +// ============================================================================ + +// npu_transform_9x9(grid: [9][9]i8) -> Transform9x9 +// Analyze 9×9 transform +pub fn npu_transform_9x9(grid: [9][9]i8) Transform9x9 { + var zero_count : u8 = 0; + var phi_score : u16 = 0; + + // Count zeros and compute phi score + for (grid, 0..) |row, i| { + for (row, 0..) |val, j| { + if (val == 0) { + zero_count += 1; + } + // Simple phi score based on center-weight + const center_dist = if (i > 4) i - 4 else 4 - i; + const center_dist_j = if (j > 4) j - 4 else 4 - j; + phi_score += @as(u16, @intCast(val)) * @as(u16, @intCast(5 - center_dist - center_dist_j)); + } + } + + const sparsity = (zero_count * 100) / 81; + + return Transform9x9 { + .grid = grid, + .sparsity = sparsity, + .phi_score = phi_score, + }; +} + +// transform_is_sparse(transform: Transform9x9) -> bool +// Check if transform is sparse (>50% zeros) +pub fn transform_is_sparse(transform: Transform9x9) bool { + return transform.sparsity > 50; +} + +// transform_phi_optimized(transform: Transform9x9) -> bool +// Check if transform is phi-optimized (high phi score) +pub fn transform_phi_optimized(transform: Transform9x9) bool { + return transform.phi_score > 100; +} + +// ============================================================================ +// LUT Lookup Functions +// ============================================================================ + +// lut_lookup_base(lut: [LUT_SIZE]LutEntry, addr: u8) -> LutEntry +// Base LUT lookup +pub fn lut_lookup_base(lut: [LUT_SIZE]LutEntry, addr: u8) LutEntry { + if (not lut_entry_address_valid(addr)) { + return LutEntry{.address = 255, .value = 0, .valid = false, .phi_related = false}; + } + return lut[@as(u8, @intCast(addr))]; +} + +// lut_lookup_phi(lut: [LUT_SIZE]LutEntry, addr: u8) -> LutEntry +// Phi-optimized LUT lookup +pub fn lut_lookup_phi(lut: [LUT_SIZE]LutEntry, addr: u8) LutEntry { + if (not lut_entry_address_valid(addr)) { + return LutEntry{.address = 255, .value = 0, .valid = false, .phi_related = false}; + } + + const entry = lut[@as(u8, @intCast(addr))]; + if (entry.valid and entry.phi_related) { + return entry; + } + + // Fallback: search for nearest phi-related entry + for (0..LUT_SIZE) |i| { + const e = lut[i]; + if (e.valid and e.phi_related) { + return e; + } + } + + return entry; +} + +// lut_lookup_range(lut: [LUT_SIZE]LutEntry, start_addr: u8, count: u8) -> [LUT_SIZE]LutEntry +// Lookup range of entries +pub fn lut_lookup_range(lut: [LUT_SIZE]LutEntry, start_addr: u8, count: u8) [LUT_SIZE]LutEntry { + var result : [LUT_SIZE]LutEntry = undefined; + + for (0..LUT_SIZE) |i| { + if (i < count and lut_entry_address_valid(start_addr + i)) { + result[i] = lut[@as(u8, @intCast(start_addr + i))]; + } else { + result[i] = LutEntry{.address = 255, .value = 0, .valid = false, .phi_related = false}; + } + } + + return result; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_lut_npu(op: NpuOperation, addr: u8) -> u16 +// Encode LUT NPU instruction +pub fn encode_lut_npu(op: NpuOperation, addr: u8) u16 { + // Format: [OP:8][OPCODE:3][ADDR:5] + const op_field : u16 = @as(u16, OP_LUT_NPU) << 8; + const opcode_field : u16 = @as(u16, @intFromEnum(op)) << 5; + const addr_field : u16 = @as(u16, addr & 0x1F); + return op_field | opcode_field | addr_field; +} + +// decode_lut_npu(encoded: u16) -> struct { op: NpuOperation, addr: u8 } +// Decode LUT NPU instruction +pub fn decode_lut_npu(encoded: u16) struct { op: NpuOperation, addr: u8 } { + const opcode_val : u8 = @as(u8, @truncate((encoded >> 5) & 0x07)); + const addr : u8 = @as(u8, @truncate(encoded & 0x1F)); + + var op : NpuOperation = undefined; + switch (opcode_val) { + 0 => op = NpuOperation.lookup, + 1 => op = NpuOperation.transform, + 2 => op = NpuOperation.phi_project, + 3 => op = NpuOperation.sparse_reduce, + 4 => op = NpuOperation.quantize, + 5 => op = NpuOperation.dequantize, + 6 => op = NpuOperation.composite, + else => op = NpuOperation.custom, + } + + return .{ .op = op, .addr = addr }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "lut_size_eighty_one" { + try std.testing.expect(LUT_SIZE == 81); +} + +test "lut_entry_address_valid_true" { + try std.testing.expect(lut_entry_address_valid(0) == true); + try std.testing.expect(lut_entry_address_valid(80) == true); +} + +test "lut_entry_address_valid_false" { + try std.testing.expect(lut_entry_address_valid(81) == false); + try std.testing.expect(lut_entry_address_valid(255) == false); +} + +test "lut_entry_valid_true" { + given entry = LutEntry{.address = 40, .value = 0x1234, .valid = true, .phi_related = false} + try std.testing.expect(lut_entry_valid(entry) == true); +} + +test "lut_entry_valid_false_invalid_addr" { + given entry = LutEntry{.address = 255, .value = 0x1234, .valid = true, .phi_related = false} + try std.testing.expect(lut_entry_valid(entry) == false); +} + +test "lut_entry_valid_false_invalid_flag" { + given entry = LutEntry{.address = 40, .value = 0x1234, .valid = false, .phi_related = false} + try std.testing.expect(lut_entry_valid(entry) == false); +} + +test "create_lut_entry_valid" { + given entry = create_lut_entry(40, 0x1234, true) + try std.testing.expect(entry.valid == true); + try std.testing.expect(entry.phi_related == true); + try std.testing.expect(entry.value == 0x1234); +} + +test "create_lut_entry_invalid" { + given entry = create_lut_entry(81, 0x1234, true) + try std.testing.expect(entry.valid == false); +} + +test "npu_transform_9x9_count" { + given grid = [_][9]i8{ + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{1} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + } + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform.sparsity == 88); +} + +test "transform_is_sparse_true" { + given grid = [_][9]i8{ + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{1} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + } + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform_is_sparse(transform) == true); +} + +test "transform_is_sparse_false" { + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform_is_sparse(transform) == false); +} + +test "transform_phi_optimized_true" { + given grid = [_][9]i8{ + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{5} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + } + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform_phi_optimized(transform) == true); +} + +test "transform_phi_optimized_false" { + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform_phi_optimized(transform) == false); +} + +test "lut_lookup_base_valid" { + given lut = [_]LutEntry{.{.address = 40, .value = 0x1234, .valid = true, .phi_related = false}} ** 81 + try std.testing.expect(entry = lut_lookup_base(lut, 40)); + try std.testing.expect(entry.value == 0x1234); + try std.testing.expect(entry.valid == true); +} + +test "lut_lookup_base_invalid_addr" { + given lut = [_]LutEntry{.{.address = 40, .value = 0x1234, .valid = true, .phi_related = false}} ** 81 + try std.testing.expect(entry = lut_lookup_base(lut, 81)); + try std.testing.expect(entry.valid == false); +} + +test "encode_lut_npu" { + given encoded = encode_lut_npu(NpuOperation.phi_project, 0x10) + try std.testing.expect((encoded >> 8) == OP_LUT_NPU); +} + +test "decode_lut_npu" { + given decoded = decode_lut_npu(0xE350) + try std.testing.expect(decoded.op == NpuOperation.phi_project); + try std.testing.expect(decoded.addr == 0x10); +} + +test "opcode_constant" { + try std.testing.expect(OP_LUT_NPU == 0xE3); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant lut_size_eighty_one + assert LUT_SIZE == 81 + +invariant lut_addr_bits_seven + assert LUT_ADDR_BITS == 7 + +invariant lut_value_bits_sixteen + assert LUT_VALUE_BITS == 16 + +invariant phi_const_base_zero + assert PHI_CONST_BASE == 0 + +invariant const_lut_end_eighty + assert CONST_LUT_END == 80 + +invariant entry_valid_one + assert ENTRY_VALID == 1 + +invariant entry_invalid_zero + assert ENTRY_INVALID == 0 + +invariant lut_address_bound + assert lut_entry_address_valid(80) == true and lut_entry_address_valid(81) == false + +invariant transform_sparsity_bound + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform.sparsity <= 100); + +invariant transform_sparsity_dense + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform.sparsity == 0); + +invariant phi_score_non_negative + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform.phi_score >= 0); + +invariant npu_operation_enum_values + try std.testing.expect(@intFromEnum(NpuOperation.lookup) == 0); + try std.testing.expect(@intFromEnum(NpuOperation.custom) == 7); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench npu_transform_9x9_latency + measure: nanoseconds to npu_transform_9x9([_] [9]i8{[_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5} ** 9}) + target: < 500ns + +bench lut_lookup_base_latency + measure: nanoseconds to lut_lookup_base([_]LutEntry{.{.address = 40, .value = 0x1234, .valid = true, .phi_related = false}} ** 81, 40) + target: < 50ns + +bench lut_lookup_phi_latency + measure: nanoseconds to lut_lookup_phi([_]LutEntry{.{.address = 40, .value = 0x1234, .valid = true, .phi_related = true}} ** 81, 40) + target: < 100ns + +bench encode_lut_npu_latency + measure: nanoseconds to encode_lut_npu(NpuOperation.phi_project, 0x10) + target: < 20ns + +bench decode_lut_npu_latency + measure: nanoseconds to decode_lut_npu(0xE350) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/nf4.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/nf4.t27 new file mode 100644 index 0000000000..7d67c8028e --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/nf4.t27 @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/nf4.t27 +// NormalFloat4 - 4-bit normalized float quantization format (Google) +// NUMERIC-STANDARD-001 Agent 11 (P1) +// +// NF4 format: +// - Represents normalized values in [0, 1] +// - Uses a 4-bit normalized representation +// - Optimal for weight quantization where most values are near zero + +module NF4 { + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // NF4 bit layout: N(4) + // N: 4 bits (normalized float value) + // + // Range: 0 to 1 (normalized) + // 16 possible quantization levels + // + // NF4 quantization is used in: + // - QLoRA (Quantized Low-Rank Adapter) + // - GPTQ (GPT Quantization) + // - Neural network weight compression + + const BITS : u8 = 4; + const LEVELS : u8 = 16; + + // 2. NF4 Type + + struct NF4 { + raw : u4, // 4-bit normalized value + } + + // 3. Encoding/Decoding + + // Encode f32 to NF4 (normalized quantization) + fn encode(value: f32) -> NF4 { + // Clamp to [0, 1] range + const clamped = if (value < 0.0) { 0.0 } else if (value > 1.0) { 1.0 } else { value }; + + // Quantize to 4 bits (0-15) + let quantized = (clamped * 15.0) as u8; + if (quantized > 15) { quantized = 15; } + + return NF4{ raw = quantized }; + } + + // Decode NF4 to f32 + fn decode(nf: NF4) -> f64 { + // Dequantize: value = raw / 15 + return (nf.raw as f64) / 15.0; + } + + // 4. Format Properties + + fn min_value() -> f64 { + return 0.0; + } + + fn max_value() -> f64 { + return 1.0; + } + + fn epsilon() -> f64 { + // Smallest representable difference: 1/15 + return 1.0 / 15.0; + } + + fn quantization_error(value: f32, nf: NF4) -> f32 { + const decoded = decode(nf) as f32; + return abs(value - decoded); + } + + // 5. Validation + + fn validate_format() -> bool { + return BITS == 4 && LEVELS == 16; + } + + // 6. Use Cases + + // NF4 is optimal for: + // - Neural network weight quantization (4-bit) + // - QLoRA low-rank adapters + // - GPTQ post-training quantization + // - Embedding table compression + // - Attention weight storage + // - Binary-aware quantization + + // Memory: 4 bits = 0.5 bytes (8x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 4.0 / 32.0; // 0.125 + + // 7. Helper Functions + + fn abs(x: f32) -> f32 { + if (x < 0.0) { -x } else { x }; + } + + fn quantization_error_pct(value: f32) -> f32 { + const encoded = encode(value); + const decoded = decode(encoded) as f32; + if (value == 0.0) { return 0.0; } + return abs(decoded - value) / abs(value) * 100.0; + } + + // 8. Comparison Operations + + fn eq(a: NF4, b: NF4) -> bool { + return a.raw == b.raw; + } + + fn ne(a: NF4, b: NF4) -> bool { + return a.raw != b.raw; + } + + fn lt(a: NF4, b: NF4) -> bool { + return decode(a) < decode(b); + } + + fn le(a: NF4, b: NF4) -> bool { + return decode(a) <= decode(b); + } + + fn gt(a: NF4, b: NF4) -> bool { + return decode(a) > decode(b); + } + + fn ge(a: NF4, b: NF4) -> bool { + return decode(a) >= decode(b); + } + + // TDD-Inside-Spec: Tests and Invariants for NF4 + + test nf4_decode_zero + given nf = NF4{ raw = 0 } + when value = decode(nf) + then value == 0.0 + + test nf4_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test nf4_decode_max + given nf = NF4{ raw = 15 } + when value = decode(nf) + then value == 1.0 + + test nf4_encode_max_roundtrip + given original = 1.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test nf4_encode_clamp_below_zero + given original = -0.5 + and encoded = encode(original) + then encoded.raw == 0 + + test nf4_encode_clamp_above_one + given original = 1.5 + and encoded = encode(original) + then encoded.raw == 15 + + test nf4_quantize_mid_values + given mid = 0.5 + and encoded = encode(mid) + then encoded.raw == 7 or encoded.raw == 8 + + test nf4_decode_preserves_linearity + given nf1 = NF4{ raw = 5 } + and nf2 = NF4{ raw = 10 } + and decoded1 = decode(nf1) + and decoded2 = decode(nf2) + then abs(decoded2 - 2.0 * decoded1) < 0.1 + + test nf4_bits_constant + then BITS == 4 + + test nf4_levels_constant + then LEVELS == 16 + + test nf4_min_value + given min_val = min_value() + then min_val == 0.0 + + test nf4_max_value + given max_val = max_value() + then max_val == 1.0 + + test nf4_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test nf4_epsilon_correct + then abs(epsilon() - 1.0 / 15.0) < 0.001 + + test nf4_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.125) < 0.01 + + test nf4_validate_format_success + given valid = validate_format() + then valid == true + + test nf4_all_levels_encode_decode + for (i in 0..15) + then abs(decode(NF4{ raw = i }) - (i as f64) / 15.0) < 0.01 + + test nf4_quantization_error_small_mid_range + given value = 0.5 + and error = quantization_error(value, encode(value)) + then error < 0.05 + + test nf4_quantization_error_zero + given value = 0.0 + and error = quantization_error(value, encode(value)) + then error == 0.0 + + test nf4_quantization_error_one + given value = 1.0 + and error = quantization_error(value, encode(value)) + then error == 0.0 + + test nf4_eq_true + given a = NF4{ raw = 7 } + and b = NF4{ raw = 7 } + then eq(a, b) == true + + test nf4_eq_false + given a = NF4{ raw = 7 } + and b = NF4{ raw = 8 } + then eq(a, b) == false + + test nf4_lt_true + given a = NF4{ raw = 3 } + and b = NF4{ raw = 7 } + then lt(a, b) == true + + test nf4_ge_true + given a = NF4{ raw = 10 } + and b = NF4{ raw = 5 } + then ge(a, b) == true + + invariant nf4_bits_constant + assert BITS == 4 + + invariant nf4_levels_constant + assert LEVELS == 16 + + invariant nf4_min_is_zero + assert min_value() == 0.0 + + invariant nf4_max_is_one + assert max_value() == 1.0 + + invariant nf4_encode_preserves_zero + assert encode(0.0).raw == 0 + + invariant nf4_encode_preserves_one + assert encode(1.0).raw == 15 + + invariant nf4_decode_min_is_zero + assert decode(NF4{ raw = 0 }) == 0.0 + + invariant nf4_decode_max_is_one + assert decode(NF4{ raw = 15 }) == 1.0 + + invariant nf4_all_values_in_range + for (i in 0..15) + then decode(NF4{ raw = i }) >= 0.0 and decode(NF4{ raw = i }) <= 1.0 + + invariant nf4_epsilon_is_step_size + given val = decode(NF4{ raw = 1 }) + and next_val = decode(NF4{ raw = 2 }) + then abs(next_val - val) == epsilon() + + invariant nf4_encode_monotonic + given x1 = 0.3 and x2 = 0.6 + then encode(x1).raw <= encode(x2).raw + + invariant nf4_decode_monotonic + for (i in 0..14) + then decode(NF4{ raw = i }) <= decode(NF4{ raw = i + 1 }) + + invariant nf4_quantization_error_bounds + for (i in 0..15) + then quantization_error(0.0, NF4{ raw = 0 }) == 0.0 + + invariant nf4_symmetric_quantization + given x = 0.5 + then quantization_error_pct(x) < 50.0 + + bench nf4_encode_latency + measure: nanoseconds to encode(0.5) + target: < 20ns + + bench nf4_decode_latency + measure: nanoseconds to decode(NF4{ raw = 7 }) + target: < 10ns + + bench nf4_encode_decode_roundtrip + measure: nanoseconds to encode(0.5) and decode(encode(0.5)) + target: < 30ns + + bench nf4_quantization_error_latency + measure: nanoseconds to quantization_error(0.5, NF4{ raw = 7 }) + target: < 50ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/null_pe.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/null_pe.t27 new file mode 100644 index 0000000000..f5fcb0f90a --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/null_pe.t27 @@ -0,0 +1,568 @@ +// SPDX-License-Identifier: Apache-2.0 +; null_pe.t27 — Sacred Opcode 0xEA: Null Processing Element +; Hardware null PE (processing element) for sparse acceleration +; φ² + 1/φ² = 3 | TRINITY + +module sacred-null_pe; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_NULL_PE : u8 = 0xEA; + +pub const PE_COUNT : u8 = 4; +pub const DATA_WIDTH : u8 = 16; + +pub const PE_STATE_IDLE : u8 = 0; +pub const PE_STATE_ACTIVE : u8 = 1; +pub const PE_STATE_WAITING : u8 = 2; +pub const PE_STATE_ERROR : u8 = 3; + +pub const NULL_MODE_SKIP : u8 = 0; +pub const NULL_MODE_ZERO : u8 = 1; +pub const NULL_MODE_IDENTITY : u8 = 2; +pub const NULL_MODE_CUSTOM : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const PeState = enum(u8) { + idle = PE_STATE_IDLE, + active = PE_STATE_ACTIVE, + waiting = PE_STATE_WAITING, + error = PE_STATE_ERROR, +} + +pub const PeConfig = struct { + mode : u8, + enabled : bool, + bypass_enabled : bool, +} + +pub const PeStatus = struct { + state : PeState, + cycle_count : u32, + op_count : u32, +} + +pub const NullPeResult = struct { + valid : bool, + output_data : u16, + pe_mask : u8, // Bitmask of which PEs are null + skip_count : u8, +} + +pub const PeArray = struct { + configs : [PE_COUNT]PeConfig, + statuses : [PE_COUNT]PeStatus, +} + +// ============================================================================ +// PE Status Functions +// ============================================================================ + +// pe_is_active(status: PeStatus) -> bool +// Check if PE is active +pub fn pe_is_active(status: PeStatus) bool { + return status.state == PeState.active; +} + +// pe_is_idle(status: PeStatus) -> bool +// Check if PE is idle +pub fn pe_is_idle(status: PeStatus) -> bool { + return status.state == PeState.idle; +} + +// pe_is_waiting(status: PeStatus) -> bool +// Check if PE is waiting +pub fn pe_is_waiting(status: PeStatus) bool { + return status.state == PeState.waiting; +} + +// pe_is_error(status: PeStatus) -> bool +// Check if PE is in error state +pub fn pe_is_error(status: PeStatus) bool { + return status.state == PeState.error; +} + +// pe_set_state(status: PeStatus, state: PeState) -> PeStatus +// Set PE state +pub fn pe_set_state(status: PeStatus, state: PeState) PeStatus { + return PeStatus { + .state = state, + .cycle_count = if (state == PeState.active) status.cycle_count + 1 else status.cycle_count, + .op_count = if (state == PeState.active) status.op_count + 1 else status.op_count, + }; +} + +// ============================================================================ +// PE Config Functions +// ============================================================================ + +// pe_config_enabled(config: PeConfig) -> bool +// Check if PE config enables operation +pub fn pe_config_enabled(config: PeConfig) -> bool { + return config.enabled and not config.bypass_enabled; +} + +// pe_is_null(config: PeConfig) -> bool +// Check if PE is in null mode +pub fn pe_is_null(config: PeConfig) -> bool { + return config.mode == NULL_MODE_SKIP or config.mode == NULL_MODE_ZERO; +} + +// pe_is_identity(config: PeConfig) -> bool +// Check if PE is in identity mode +pub fn pe_is_identity(config: PeConfig) -> bool { + return config.mode == NULL_MODE_IDENTITY; +} + +// ============================================================================ +// Null PE Operations +// ============================================================================ + +// null_pe_skip(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult +// Null PE operation (skip processing) +pub fn null_pe_skip(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult { + var skip_count : u8 = 0; + var output_data : u16 = input; + + for (configs, 0..) |config, i| { + if (((pe_mask >> i) & 1) != 0 and pe_is_null(config)) { + skip_count += 1; + } + } + + return NullPeResult { + .valid = skip_count > 0, + .output_data = if (skip_count == PE_COUNT) 0 else output_data, + .pe_mask = pe_mask, + .skip_count = skip_count, + }; +} + +// null_pe_zero(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult +// Null PE operation (zero output) +pub fn null_pe_zero(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult { + var skip_count : u8 = 0; + + for (configs, 0..) |config, i| { + if (((pe_mask >> i) & 1) != 0 and pe_is_null(config)) { + skip_count += 1; + } + } + + return NullPeResult { + .valid = true, + .output_data = 0, + .pe_mask = pe_mask, + .skip_count = skip_count, + }; +} + +// null_pe_identity(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult +// Null PE operation (identity pass-through) +pub fn null_pe_identity(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult { + var active_count : u8 = 0; + + for (configs, 0..) |config, i| { + if (((pe_mask >> i) & 1) != 0 and pe_is_identity(config)) { + active_count += 1; + } + } + + return NullPeResult { + .valid = active_count > 0, + .output_data = input, + .pe_mask = pe_mask, + .skip_count = PE_COUNT - active_count, + }; +} + +// null_pe_execute(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult +// Execute null PE operation based on config +pub fn null_pe_execute(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult { + var mode : u8 = NULL_MODE_SKIP; + + // Determine primary mode from active PEs + for (configs, 0..) |config, i| { + if (((pe_mask >> i) & 1) != 0 and config.enabled) { + mode = config.mode; + break; + } + } + + switch (mode) { + NULL_MODE_SKIP => { + return null_pe_skip(input, pe_mask, configs); + }, + NULL_MODE_ZERO => { + return null_pe_zero(input, pe_mask, configs); + }, + NULL_MODE_IDENTITY => { + return null_pe_identity(input, pe_mask, configs); + }, + NULL_MODE_CUSTOM => { + // Custom: return zero for null PEs + return null_pe_zero(input, pe_mask, configs); + }, + else => { + return null_pe_skip(input, pe_mask, configs); + }, + } +} + +// ============================================================================ +// PE Array Functions +// ============================================================================ + +// pe_array_init() -> PeArray +// Initialize PE array +pub fn pe_array_init() -> PeArray { + var configs : [PE_COUNT]PeConfig = undefined; + var statuses : [PE_COUNT]PeStatus = undefined; + + for (0..PE_COUNT) |i| { + configs[i] = PeConfig{.mode = NULL_MODE_SKIP, .enabled = false, .bypass_enabled = false}; + statuses[i] = PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0}; + } + + return PeArray{.configs = configs, .statuses = statuses}; +} + +// pe_array_enable(pe_array: PeArray, pe_mask: u8) -> PeArray +// Enable PEs based on mask +pub fn pe_array_enable(pe_array: PeArray, pe_mask: u8) -> PeArray { + var result = pe_array; + + for (0..PE_COUNT) |i| { + if ((pe_mask >> i) & 1 != 0) { + result.configs[i].enabled = true; + } else { + result.configs[i].enabled = false; + } + } + + return result; +} + +// pe_array_count_active(pe_array: PeArray) -> u8 +// Count active PEs +pub fn pe_array_count_active(pe_array: PeArray) -> u8 { + var count : u8 = 0; + for (pe_array.configs) |config| { + if (config.enabled and not config.bypass_enabled) { + count += 1; + } + } + return count; +} + +// pe_array_count_null(pe_array: PeArray) -> u8 +// Count null PEs +pub fn pe_array_count_null(pe_array: PeArray) -> u8 { + var count : u8 = 0; + for (pe_array.configs) |config| { + if (pe_is_null(config)) { + count += 1; + } + } + return count; +} + +// pe_array_get_null_mask(pe_array: PeArray) -> u8 +// Get bitmask of null PEs +pub fn pe_array_get_null_mask(pe_array: PeArray) -> u8 { + var mask : u8 = 0; + for (pe_array.configs, 0..) |config, i| { + if (pe_is_null(config)) { + mask |= (@as(u8, 1) << i); + } + } + return mask; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_null_pe(pe_mask: u8, mode: u8) -> u16 +// Encode null PE instruction +pub fn encode_null_pe(pe_mask: u8, mode: u8) u16 { + // Format: [OP:8][MODE:2][MASK:4][RES:2] + const op : u16 = @as(u16, OP_NULL_PE) << 8; + const mode_field : u16 = @as(u16, mode & 0x03) << 6; + const mask_field : u16 = @as(u16, pe_mask & 0x0F) << 2; + return op | mode_field | mask_field; +} + +// decode_null_pe(encoded: u16) -> struct { pe_mask: u8, mode: u8 } +// Decode null PE instruction +pub fn decode_null_pe(encoded: u16) -> struct { pe_mask: u8, mode: u8 } { + const mode : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const pe_mask : u8 = @as(u8, @truncate((encoded >> 2) & 0x0F)); + return .{ .pe_mask = pe_mask, .mode = mode }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "pe_count_four" { + try std.testing.expect(PE_COUNT == 4); +} + +test "data_width_sixteen" { + try std.testing.expect(DATA_WIDTH == 16); +} + +test "null_mode_constants" { + try std.testing.expect(NULL_MODE_SKIP == 0); + try std.testing.expect(NULL_MODE_ZERO == 1); + try std.testing.expect(NULL_MODE_IDENTITY == 2); + try std.testing.expect(NULL_MODE_CUSTOM == 3); +} + +test "pe_state_constants" { + try std.testing.expect(PE_STATE_IDLE == 0); + try std.testing.expect(PE_STATE_ACTIVE == 1); + try std.testing.expect(PE_STATE_WAITING == 2); + try std.testing.expect(PE_STATE_ERROR == 3); +} + +test "pe_is_active_true" { + given status = PeStatus{.state = PeState.active, .cycle_count = 10, .op_count = 5} + try std.testing.expect(pe_is_active(status) == true); +} + +test "pe_is_active_false" { + given status = PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} + try std.testing.expect(pe_is_active(status) == false); +} + +test "pe_is_idle_true" { + given status = PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} + try std.testing.expect(pe_is_idle(status) == true); +} + +test "pe_set_state_increments" { + given status = PeStatus{.state = PeState.idle, .cycle_count = 10, .op_count = 5} + try std.testing.expect(result = pe_set_state(status, PeState.active)); + try std.testing.expect(result.cycle_count == 11); + try std.testing.expect(result.op_count == 6); +} + +test "pe_config_enabled_true" { + given config = PeConfig{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false} + try std.testing.expect(pe_config_enabled(config) == true); +} + +test "pe_config_enabled_disabled" { + given config = PeConfig{.mode = NULL_MODE_SKIP, .enabled = false, .bypass_enabled = false} + try std.testing.expect(pe_config_enabled(config) == false); +} + +test "pe_config_enabled_bypass" { + given config = PeConfig{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = true} + try std.testing.expect(pe_config_enabled(config) == false); +} + +test "pe_is_null_skip" { + try std.testing.expect(pe_is_null(.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}) == true); + try std.testing.expect(pe_is_null(.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}) == true); +} + +test "pe_is_null_not" { + try std.testing.expect(pe_is_null(.{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}) == false); +} + +test "null_pe_skip_some" { + given configs = [_]PeConfig{ + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + } + try std.testing.expect(result = null_pe_skip(0x1234, 0b1010, configs)); + try std.testing.expect(result.skip_count == 3); +} + +test "null_pe_zero_always_zero" { + given configs = [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(result = null_pe_zero(0x1234, 0b1111, configs)); + try std.testing.expect(result.output_data == 0); + try std.testing.expect(result.valid == true); +} + +test "null_pe_identity_pass" { + given configs = [_]PeConfig{.{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(result = null_pe_identity(0x1234, 0b1111, configs)); + try std.testing.expect(result.output_data == 0x1234); +} + +test "null_pe_execute_skip" { + given configs = [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(result = null_pe_execute(0x1234, 0b1111, configs)); + try std.testing.expect(result.output_data == 0x1234); +} + +test "null_pe_execute_zero" { + given configs = [_]PeConfig{.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(result = null_pe_execute(0x1234, 0b1111, configs)); + try std.testing.expect(result.output_data == 0); +} + +test "pe_array_init" { + given array = pe_array_init() + try std.testing.expect(pe_array_count_active(array) == 0); + try std.testing.expect(pe_array_count_null(array) == 0); +} + +test "pe_array_enable" { + given array = pe_array_init() + try std.testing.expect(result = pe_array_enable(array, 0b1010)); + try std.testing.expect(result.configs[0].enabled == false); + try std.testing.expect(result.configs[1].enabled == true); + try std.testing.expect(result.configs[2].enabled == false); + try std.testing.expect(result.configs[3].enabled == true); +} + +test "pe_array_count_active" { + given array = pe_array_enable(pe_array_init(), 0b1111) + try std.testing.expect(pe_array_count_active(array) == 4); +} + +test "pe_array_count_null" { + given configs = [_]PeConfig{ + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = false, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + } + try std.testing.expect(array = PeArray{.configs = configs, .statuses = [_]PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} ** 4}); + try std.testing.expect(pe_array_count_null(array) == 2); +} + +test "pe_array_get_null_mask" { + given configs = [_]PeConfig{ + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = false, .bypass_enabled = false}, + .{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}, + } + try std.testing.expect(array = PeArray{.configs = configs, .statuses = [_]PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} ** 4}); + try std.testing.expect(mask = pe_array_get_null_mask(array)); + try std.testing.expect(mask == 0b1011); +} + +test "encode_null_pe" { + given encoded = encode_null_pe(0b1010, 1) + try std.testing.expect((encoded >> 8) == OP_NULL_PE); +} + +test "decode_null_pe" { + given decoded = decode_null_pe(0xEA5A) + try std.testing.expect(decoded.pe_mask == 0b1010); + try std.testing.expect(decoded.mode == 1); +} + +test "opcode_constant" { + try std.testing.expect(OP_NULL_PE == 0xEA); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant pe_count_four + assert PE_COUNT == 4 + +invariant data_width_sixteen + assert DATA_WIDTH == 16 + +invariant null_mode_values + try std.testing.expect(NULL_MODE_SKIP >= 0 and NULL_MODE_CUSTOM <= 3); + +invariant pe_state_values + try std.testing.expect(PE_STATE_IDLE >= 0 and PE_STATE_ERROR <= 3); + +invariant pe_set_state_increments + given status = PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} + try std.testing.expect(result = pe_set_state(status, PeState.active)); + try std.testing.expect(result.cycle_count == status.cycle_count + 1); + try std.testing.expect(result.op_count == status.op_count + 1); + +invariant pe_set_state_preserves_when_idle + given status = PeStatus{.state = PeState.active, .cycle_count = 10, .op_count = 5} + try std.testing.expect(result = pe_set_state(status, PeState.idle)); + try std.testing.expect(result.cycle_count == status.cycle_count); + try std.testing.expect(result.op_count == status.op_count); + +invariant null_pe_skip_valid + given configs = [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(null_pe_skip(0x1234, 0, configs).valid == false); + try std.testing.expect(null_pe_skip(0x1234, 0b1111, configs).skip_count == 4); + +invariant null_pe_zero_always_valid + given configs = [_]PeConfig{.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(null_pe_zero(0x1234, 0, configs).valid == true); + +invariant null_pe_zero_output + given configs = [_]PeConfig{.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(null_pe_zero(0x1234, 0b1111, configs).output_data == 0); + +invariant null_pe_identity_preserves + given configs = [_]PeConfig{.{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(null_pe_identity(0x1234, 0, configs).output_data == 0x1234); + +invariant pe_array_count_bounds + given array = pe_array_enable(pe_array_init(), 0xFF) + try std.testing.expect(pe_array_count_active(array) <= PE_COUNT); + try std.testing.expect(pe_array_count_null(array) <= PE_COUNT); + +invariant pe_array_get_null_mask_bound + try std.testing.expect(pe_array_get_null_mask(pe_array_init()) < (@as(u8, 1) << PE_COUNT)); + +invariant pe_array_count_all + given configs = [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(array = PeArray{.configs = configs, .statuses = [_]PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} ** 4}); + try std.testing.expect(pe_array_count_active(array) + pe_array_count_null(array) == PE_COUNT); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench null_pe_skip_latency + measure: nanoseconds to null_pe_skip(0x1234, 0b1111, [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4) + target: < 50ns + +bench null_pe_zero_latency + measure: nanoseconds to null_pe_zero(0x1234, 0b1111, [_]PeConfig{.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}} ** 4) + target: < 50ns + +bench null_pe_identity_latency + measure: nanoseconds to null_pe_identity(0x1234, 0b1111, [_]PeConfig{.{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}} ** 4) + target: < 50ns + +bench null_pe_execute_latency + measure: nanoseconds to null_pe_execute(0x1234, 0b1111, [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4) + target: < 100ns + +bench pe_array_count_active_latency + measure: nanoseconds to pe_array_count_active(pe_array_enable(pe_array_init(), 0xFF)) + target: < 50ns + +bench pe_array_get_null_mask_latency + measure: nanoseconds to pe_array_get_null_mask(pe_array_enable(pe_array_init(), 0xFF)) + target: < 50ns + +bench encode_null_pe_latency + measure: nanoseconds to encode_null_pe(0b1010, 1) + target: < 20ns + +bench decode_null_pe_latency + measure: nanoseconds to decode_null_pe(0xEA5A) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/posit16.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/posit16.t27 new file mode 100644 index 0000000000..d868ba60e8 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/posit16.t27 @@ -0,0 +1,979 @@ +// SPDX-License-Identifier: Apache-2.0 +; posit16.t27 — Posit Type 16 (Type-2 with ES=1, unum 1.0 format) +; 16-bit posit format with 1 exponent bit (ES=1) and 0 useed bits +; Alternative name: posit<16,1> +; Range: ~-3.8e4 to ~3.8e4, precision: ~1-2 significant bits at extremes, ~10 at 1.0 +; φ² + 1/φ² = 3 | TRINITY + +module triformat-posit16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 16; +pub const ES : u8 = 1; // Exponent size bits +pub const NBITS : u8 = BITS - ES; // Number of bits excluding ES +pub const USEED_BITS : u8 = 0; // No useed bits (type-2) + +pub const SIGN_SHIFT : u8 = 15; +pub const REGIME_SHIFT : u8 = 14; + +pub const SIGN_MASK : u16 = 0x8000; // 1 << 15 +pub const REGIME_MASK : u16 = 0x7FFF; // Lower 15 bits + +// Special values +pub const POS16_ZERO : u16 = 0x0000; // Positive zero +pub const POS16_NAR : u16 = 0x8000; // Not a Real (NaR) + +// Maximum regime runs +pub const MAX_REGIME : i16 = 6; // Based on NBITS=15 and regime encoding + +// ============================================================================ +// Types +// ============================================================================ + +pub const Posit16 = u16; // 16-bit posit value + +// Posit16 components struct +pub struct PositComponents { + sign : bool, // true = negative + regime : i8, // Regime value (can be negative or positive) + exponent : u8, // Exponent (0 or 1 for ES=1) + fraction : u16, // Fraction/mantissa bits + scale : i16, // Combined scale factor = 2^(useed*regime + exponent) +} + +// ============================================================================ +// Regime Extraction Functions +// ============================================================================ + +// posit16_extract_regime(pos: Posit16) -> i8 +// Extract regime value from posit +// Regime is encoded as a run of bits (same as sign) followed by opposite bit +pub fn posit16_extract_regime(pos: Posit16) i8 { + // Remove sign bit + var remaining_bits : u16 = pos & REGIME_MASK; + + // Count leading bits that match the first bit (regime run) + var regime_run : i8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Regime value: if first_bit=1, regime = run-1; if first_bit=0, regime = -run + return if (first_bit == 1) regime_run - 1 else -regime_run; +} + +// posit16_extract_exponent(pos: Posit16) -> u8 +// Extract exponent value from posit +pub fn posit16_extract_exponent(pos: Posit16) i8 { + var remaining_bits : u16 = pos & REGIME_MASK; + + // Find regime run length + var regime_run : u8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Skip regime bits and the terminating bit + const exponent_bit : u8 = if (regime_run + 1 < NBITS) + @truncate(@as(u8, @intCast((remaining_bits >> (NBITS - 1 - regime_run - 1))) & 1)) + else + 0; + + return if (exponent_bit == 1) 1 else 0; +} + +// posit16_extract_fraction(pos: Posit16) -> u16 +// Extract fraction bits from posit +pub fn posit16_extract_fraction(pos: Posit16) u16 { + var remaining_bits : u16 = pos & REGIME_MASK; + + // Find regime run length + var regime_run : u8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Skip regime bits, terminating bit, and exponent bit (ES=1) + const skip_bits : u8 = regime_run + 1 + ES; + const fraction_bits : u8 = if (skip_bits >= NBITS) 0 else NBITS - skip_bits; + + // Extract fraction bits + var fraction : u16 = 0; + if (fraction_bits > 0 and skip_bits < NBITS) { + fraction = remaining_bits & ((@as(u16, 1) << fraction_bits) - 1); + } + + return fraction; +} + +// posit16_extract_sign(pos: Posit16) -> bool +// Extract sign bit (true = negative) +pub fn posit16_extract_sign(pos: Posit16) bool { + return (pos & SIGN_MASK) != 0; +} + +// posit16_extract_components(pos: Posit16) -> PositComponents +// Extract all components from posit +pub fn posit16_extract_components(pos: Posit16) PositComponents { + const sign = posit16_extract_sign(pos); + const regime = posit16_extract_regime(pos); + const exponent = posit16_extract_exponent(pos); + const fraction = posit16_extract_fraction(pos); + + // Calculate scale: useed = 2^(2^ES) = 2^2 = 4 + // scale = 2^(useed * regime + exponent) + const useed_exp : i8 = 1 << ES; // 2^ES = 2^1 = 2 + const useed : i16 = 1 << useed_exp; // 2^2 = 4 + const scale = (useed * @as(i16, regime)) + @as(i16, exponent); + + return PositComponents { + .sign = sign, + .regime = regime, + .exponent = @as(u8, @intCast(exponent)), + .fraction = fraction, + .scale = scale, + }; +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// posit16_is_zero(pos: Posit16) -> bool +// Check if posit is zero (positive or negative) +pub fn posit16_is_zero(pos: Posit16) bool { + return pos == POS16_ZERO; +} + +// posit16_is_nar(pos: Posit16) -> bool +// Check if posit is Not-a-Real (NaR) +pub fn posit16_is_nar(pos: Posit16) bool { + return pos == POS16_NAR; +} + +// posit16_is_special(pos: Posit16) -> bool +// Check if posit is a special value (zero or NaR) +pub fn posit16_is_special(pos: Posit16) bool { + return posit16_is_zero(pos) or posit16_is_nar(pos); +} + +// posit16_is_finite(pos: Posit16) -> bool +// Check if posit is finite (not NaR) +pub fn posit16_is_finite(pos: Posit16) bool { + return not posit16_is_nar(pos); +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// posit16_encode_f32(value: f32) -> Posit16 +// Encode IEEE 754 single precision to Posit16 +pub fn posit16_encode_f32(value: f32) Posit16 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) POS16_NAR else POS16_ZERO; + } + + // Handle NaN + if (std.math.isNan(value)) { + return POS16_NAR; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) POS16_NAR else POS16_NAR; + } + + // Extract sign + const is_negative = value < 0.0; + const abs_value = if (is_negative) -value else value; + + // Convert to f64 for more precision during computation + const value_f64 : f64 = @as(f64, @floatCast(abs_value)); + + // Calculate useed = 2^(2^ES) = 4 for ES=1 + const useed : f64 = 4.0; + + // Calculate log2 and determine regime + const log2_val = std.math.log(value_f64) / std.math.log(2.0); + + // Regime: largest k such that 2^(k*useed) <= value < 2^((k+1)*useed) + var k : i16 = @intFromFloat(@floor(log2_val / std.math.log(useed))); + + // Clamp regime + if (k > MAX_REGIME) { + k = MAX_REGIME; + } else if (k < -MAX_REGIME - 1) { + k = -MAX_REGIME - 1; + } + + // Calculate exponent + var exp : i8 = 0; + var remaining = value_f64 / std.math.pow(f64, useed, @as(f64, @floatFromInt(k))); + + // Normalize remaining value to [1, 2) for fraction extraction + while (remaining >= 2.0 and exp < ((@as(i8, 1) << ES) - 1)) { + remaining /= 2.0; + exp += 1; + } + + // Extract fraction bits + const fraction_bits : u8 = @as(u8, @intCast(NBITS - @as(u8, @intCast(@abs(k) + 1)) - ES)); + var fraction : u16 = 0; + var frac_val = remaining - 1.0; // Remove implicit 1 + + if (fraction_bits > 0) { + for (0..fraction_bits) |i| { + frac_val *= 2.0; + if (frac_val >= 1.0) { + fraction |= (@as(u16, 1) << (fraction_bits - 1 - i)); + frac_val -= 1.0; + } + } + } + + // Encode bits + var result : u16 = 0; + + // Set sign bit + if (is_negative) { + result |= SIGN_MASK; + } + + // Encode regime + // For k >= 0: run of k+1 ones followed by zero + // For k < 0: run of -k zeros followed by one + const regime_bits_remaining : u8 = if (fraction_bits > 0) + @as(u8, @intCast(@as(i16, @abs(k)) + 1)) + else + NBITS - ES; + + const regime_start = SIGN_SHIFT - 1 - regime_bits_remaining; + if (k >= 0) { + // k+1 ones followed by zero (if space permits) + for (0..@as(u8, @intCast(k + 1))) |i| { + if (regime_start - i >= 0) { + result |= (@as(u16, 1) << (regime_start - i)); + } + } + } else { + // -k zeros followed by one + const one_pos = regime_start - @as(u8, @intCast(-k)); + if (one_pos >= 0) { + result |= (@as(u16, 1) << one_pos); + } + } + + // Encode exponent + if (ES > 0 and fraction_bits + ES > 0) { + const exp_pos = @as(u8, @intCast(NBITS - 1)) - regime_bits_remaining; + if (exp_pos >= 0) { + result |= (@as(u16, exp) << exp_pos); + } + } + + // Encode fraction + const fraction_pos = @as(u8, @intCast(NBITS - 1)) - regime_bits_remaining - ES; + if (fraction_bits > 0 and fraction_pos >= fraction_bits) { + result |= (fraction << (fraction_pos - fraction_bits + 1)); + } + + return result; +} + +// posit16_decode_f32(pos: Posit16) -> f32 +// Decode Posit16 to IEEE 754 single precision +pub fn posit16_decode_f32(pos: Posit16) f32 { + // Handle zero + if (posit16_is_zero(pos)) { + return 0.0; + } + + // Handle NaR + if (posit16_is_nar(pos)) { + return std.math.nan(f32); + } + + // Extract components + const comps = posit16_extract_components(pos); + + // Calculate value + // value = (-1)^sign * 2^scale * (1 + fraction/2^fraction_bits) + const useed : f64 = 4.0; // 2^(2^1) = 4 + const scale_value = std.math.pow(f64, useed, @as(f64, @floatFromInt(comps.scale))); + + // Calculate fraction contribution + const fraction_bits : u8 = @as(u8, @intCast(NBITS - @as(u8, @intCast(@abs(comps.regime) + 1)) - ES)); + const fraction_value = if (fraction_bits > 0) + @as(f64, @floatFromInt(comps.fraction)) / @as(f64, @floatFromInt(@as(u16, 1) << fraction_bits)) + else + 0.0; + + const magnitude = scale_value * (1.0 + fraction_value); + + return if (comps.sign) -@as(f32, @floatCast(magnitude)) else @as(f32, @floatCast(magnitude)); +} + +// posit16_encode_f64(value: f64) -> Posit16 +// Encode IEEE 754 double precision to Posit16 +pub fn posit16_encode_f64(value: f64) Posit16 { + return posit16_encode_f32(@as(f32, @floatCast(value))); +} + +// posit16_decode_f64(pos: Posit16) -> f64 +// Decode Posit16 to IEEE 754 double precision +pub fn posit16_decode_f64(pos: Posit16) f64 { + const f32_val = posit16_decode_f32(pos); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// posit16_add(a: Posit16, b: Posit16) -> Posit16 +// Add two posit values +pub fn posit16_add(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a)) { + return b; + } + if (posit16_is_zero(b)) { + return a; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa + fb); +} + +// posit16_sub(a: Posit16, b: Posit16) -> Posit16 +// Subtract two posit values +pub fn posit16_sub(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa - fb); +} + +// posit16_mul(a: Posit16, b: Posit16) -> Posit16 +// Multiply two posit values +pub fn posit16_mul(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a) or posit16_is_zero(b)) { + return POS16_ZERO; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa * fb); +} + +// posit16_div(a: Posit16, b: Posit16) -> Posit16 +// Divide two posit values +pub fn posit16_div(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a)) { + return POS16_ZERO; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa / fb); +} + +// posit16_abs(value: Posit16) -> Posit16 +// Absolute value of posit +pub fn posit16_abs(value: Posit16) Posit16 { + return value & ~SIGN_MASK; +} + +// posit16_neg(value: Posit16) -> Posit16 +// Negate posit +pub fn posit16_neg(value: Posit16) Posit16 { + return value ^ SIGN_MASK; +} + +// posit16_is_equal(a: Posit16, b: Posit16) -> bool +// Check if two posit values are equal +pub fn posit16_is_equal(a: Posit16, b: Posit16) bool { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return false; + } + if (posit16_is_zero(a) and posit16_is_zero(b)) { + return true; + } + return a == b; +} + +// posit16_is_greater(a: Posit16, b: Posit16) -> bool +// Check if a > b +pub fn posit16_is_greater(a: Posit16, b: Posit16) bool { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return false; + } + const sign_a = posit16_extract_sign(a); + const sign_b = posit16_extract_sign(b); + if (sign_a != sign_b) { + return not sign_a; // Positive > negative + } + if (sign_a) { + // Both negative, compare magnitudes (inverse) + return a < b; + } + return a > b; +} + +// posit16_max(a: Posit16, b: Posit16) -> Posit16 +// Return the larger of two posit values +pub fn posit16_max(a: Posit16, b: Posit16) Posit16 { + return if (posit16_is_greater(a, b)) a else b; +} + +// posit16_min(a: Posit16, b: Posit16) -> Posit16 +// Return the smaller of two posit values +pub fn posit16_min(a: Posit16, b: Posit16) Posit16 { + return if (posit16_is_greater(a, b)) b else a; +} + +// posit16_sqrt(value: Posit16) -> Posit16 +// Square root of posit value +pub fn posit16_sqrt(value: Posit16) Posit16 { + if (posit16_is_nar(value)) { + return POS16_NAR; + } + if (posit16_extract_sign(value)) { + return POS16_NAR; + } + if (posit16_is_zero(value)) { + return POS16_ZERO; + } + + const fv = posit16_decode_f32(value); + return posit16_encode_f32(std.math.sqrt(fv)); +} + +// posit16_lerp(a: Posit16, b: Posit16, t: f32) -> Posit16 +// Linear interpolation between a and b +pub fn posit16_lerp(a: Posit16, b: Posit16, t: f32) Posit16 { + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + const result = fa + (fb - fa) * t; + return posit16_encode_f32(result); +} + +// posit16_scale(value: Posit16, scale: f32) -> Posit16 +// Scale posit value by scalar +pub fn posit16_scale(value: Posit16, scale: f32) Posit16 { + const fv = posit16_decode_f32(value); + return posit16_encode_f32(fv * scale); +} + +// posit16_relu(value: Posit16) -> Posit16 +// ReLU activation: max(0, x) +pub fn posit16_relu(value: Posit16) Posit16 { + return if (posit16_extract_sign(value)) POS16_ZERO else value; +} + +// posit16_sigmoid(value: Posit16) -> Posit16 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn posit16_sigmoid(value: Posit16) Posit16 { + const fv = posit16_decode_f32(value); + return posit16_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// posit16_quantize_to_int4(value: Posit16) -> i8 +// Quantize posit to Int4 (range [-8, 7]) +pub fn posit16_quantize_to_int4(value: Posit16) i8 { + const fv = posit16_decode_f32(value); + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// posit16_from_f32_scaled(value: f32, scale: f32) -> Posit16 +// Encode f32 to posit with custom scale factor +pub fn posit16_from_f32_scaled(value: f32, scale: f32) Posit16 { + return posit16_encode_f32(value * scale); +} + +// posit16_to_f32_scaled(value: Posit16, scale: f32) -> f32 +// Decode posit to f32 with custom scale factor +pub fn posit16_to_f32_scaled(value: Posit16, scale: f32) f32 { + return posit16_decode_f32(value) / scale; +} + +// posit16_dot_product(a: []const Posit16, b: []const Posit16) -> Posit16 +// Dot product of two posit arrays +pub fn posit16_dot_product(a: []const Posit16, b: []const Posit16) Posit16 { + var sum: f32 = 0.0; + for (a, 0..) |val_a, i| { + if (i >= b.len) break; + const fa = posit16_decode_f32(val_a); + const fb = posit16_decode_f32(b[i]); + sum += fa * fb; + } + return posit16_encode_f32(sum); +} + +// posit16_vector_sum(vec: []const Posit16) -> Posit16 +// Sum all elements of a vector +pub fn posit16_vector_sum(vec: []const Posit16) Posit16 { + var sum: f32 = 0.0; + for (vec) |val| { + sum += posit16_decode_f32(val); + } + return posit16_encode_f32(sum); +} + +// posit16_vector_mean(vec: []const Posit16) -> Posit16 +// Mean of all elements of a vector +pub fn posit16_vector_mean(vec: []const Posit16) Posit16 { + if (vec.len == 0) { + return POS16_ZERO; + } + const sum = posit16_vector_sum(vec); + return posit16_div(sum, posit16_encode_f32(@as(f32, @floatFromInt(vec.len)))); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "posit16_is_zero_true" { + try std.testing.expect(posit16_is_zero(POS16_ZERO) == true); +} + +test "posit16_is_zero_false" { + try std.testing.expect(posit16_is_zero(0x0001) == false); +} + +test "posit16_is_nar_true" { + try std.testing.expect(posit16_is_nar(POS16_NAR) == true); +} + +test "posit16_is_nar_false" { + try std.testing.expect(posit16_is_nar(POS16_ZERO) == false); +} + +test "posit16_is_special_zero" { + try std.testing.expect(posit16_is_special(POS16_ZERO) == true); +} + +test "posit16_is_special_nar" { + try std.testing.expect(posit16_is_special(POS16_NAR) == true); +} + +test "posit16_is_special_normal" { + try std.testing.expect(posit16_is_special(0x4000) == false); +} + +test "posit16_extract_sign_positive" { + given value = 0x4000 + try std.testing.expect(sign = posit16_extract_sign(value)); + try std.testing.expect(sign == false); +} + +test "posit16_extract_sign_negative" { + given value = 0xC000 + try std.testing.expect(sign = posit16_extract_sign(value)); + try std.testing.expect(sign == true); +} + +test "posit16_encode_f32_zero" { + given pos = posit16_encode_f32(0.0) + try std.testing.expect(pos == POS16_ZERO); +} + +test "posit16_encode_f32_one" { + given pos = posit16_encode_f32(1.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "posit16_encode_f32_negative_one" { + given pos = posit16_encode_f32(-1.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded + 1.0) < 0.1); +} + +test "posit16_encode_f32_ten" { + given pos = posit16_encode_f32(10.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - 10.0) < 2.0); +} + +test "posit16_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(pos = posit16_encode_f32(original)); + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - original) < 0.3); +} + +test "posit16_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(pos = posit16_encode_f32(original)); + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - original) < 0.3); +} + +test "posit16_decode_f32_zero" { + try std.testing.expect(posit16_decode_f32(POS16_ZERO) == 0.0); +} + +test "posit16_decode_f32_nar" { + try std.testing.expect(std.math.isNan(posit16_decode_f32(POS16_NAR)) == true); +} + +test "posit16_add_simple" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(20.0)); + try std.testing.expect(result = posit16_add(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 30.0) < 5.0); +} + +test "posit16_sub_simple" { + given a = posit16_encode_f32(50.0) + try std.testing.expect(b = posit16_encode_f32(30.0)); + try std.testing.expect(result = posit16_sub(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "posit16_mul_simple" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(10.0)); + try std.testing.expect(result = posit16_mul(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 20.0); +} + +test "posit16_div_simple" { + given a = posit16_encode_f32(100.0) + try std.testing.expect(b = posit16_encode_f32(10.0)); + try std.testing.expect(result = posit16_div(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "posit16_sqrt_four" { + given value = posit16_encode_f32(4.0) + try std.testing.expect(result = posit16_sqrt(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.3); +} + +test "posit16_sqrt_negative" { + given value = posit16_encode_f32(-4.0) + try std.testing.expect(result = posit16_sqrt(value)); + try std.testing.expect(posit16_is_nar(result) == true); +} + +test "posit16_abs_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(result = posit16_abs(value)); + try std.testing.expect(posit16_is_equal(result, value) == true); +} + +test "posit16_abs_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(abs_val = posit16_abs(value)); + try std.testing.expect(posit16_extract_sign(abs_val) == false); +} + +test "posit16_neg_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(neg_val = posit16_neg(value)); + try std.testing.expect(posit16_extract_sign(neg_val) == true); +} + +test "posit16_neg_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(neg_val = posit16_neg(value)); + try std.testing.expect(posit16_extract_sign(neg_val) == false); +} + +test "posit16_is_equal_true" { + given value = posit16_encode_f32(10.0) + try std.testing.expect(posit16_is_equal(value, value) == true); +} + +test "posit16_is_equal_false" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(20.0)); + try std.testing.expect(posit16_is_equal(a, b) == false); +} + +test "posit16_is_equal_nar" { + try std.testing.expect(posit16_is_equal(POS16_NAR, POS16_NAR) == false); +} + +test "posit16_is_greater_positive" { + given a = posit16_encode_f32(50.0) + try std.testing.expect(b = posit16_encode_f32(30.0)); + try std.testing.expect(posit16_is_greater(a, b) == true); +} + +test "posit16_max_returns_larger" { + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_max(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded >= 35.0); +} + +test "posit16_min_returns_smaller" { + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_min(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded <= 45.0); +} + +test "posit16_relu_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(result = posit16_relu(value)); + try std.testing.expect(posit16_is_equal(result, value) == true); +} + +test "posit16_relu_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(result = posit16_relu(value)); + try std.testing.expect(result == POS16_ZERO); +} + +test "posit16_sigmoid_zero" { + given value = POS16_ZERO + try std.testing.expect(result = posit16_sigmoid(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "posit16_sigmoid_positive_large" { + given value = posit16_encode_f32(10.0) + try std.testing.expect(result = posit16_sigmoid(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "posit16_quantize_to_int4_max" { + given value = posit16_encode_f32(100.0) + try std.testing.expect(result = posit16_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "posit16_quantize_to_int4_min" { + given value = posit16_encode_f32(-100.0) + try std.testing.expect(result = posit16_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "posit16_from_f32_scaled_up" { + given value = 1.0 + try std.testing.expect(scale = 100.0); + try std.testing.expect(result = posit16_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 20.0); +} + +test "posit16_dot_product_simple" { + given a = [_]Posit16{posit16_encode_f32(1.0), posit16_encode_f32(2.0), posit16_encode_f32(3.0), posit16_encode_f32(4.0)} + try std.testing.expect(b = [_]Posit16{posit16_encode_f32(2.0), posit16_encode_f32(2.0), posit16_encode_f32(2.0), posit16_encode_f32(2.0)}); + try std.testing.expect(result = posit16_dot_product(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "posit16_vector_sum_simple" { + given vec = [_]Posit16{posit16_encode_f32(1.0), posit16_encode_f32(2.0), posit16_encode_f32(3.0), posit16_encode_f32(4.0)} + try std.testing.expect(result = posit16_vector_sum(vec)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "posit16_vector_mean_simple" { + given vec = [_]Posit16{posit16_encode_f32(2.0), posit16_encode_f32(4.0), posit16_encode_f32(6.0), posit16_encode_f32(8.0)} + try std.testing.expect(result = posit16_vector_mean(vec)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 5.0) < 1.0); +} + +test "posit16_lerp_zero" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = posit16_lerp(a, b, t)); + try std.testing.expect(posit16_is_equal(result, a) == true); +} + +test "posit16_lerp_one" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = posit16_lerp(a, b, t)); + try std.testing.expect(posit16_is_equal(result, b) == true); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant posit16_bits_constant + assert BITS == 16 + +invariant posit16_es_one + assert ES == 1 + +invariant posit16_nbits_fifteen + assert NBITS == 15 + +invariant posit16_zero_no_sign + assert (POS16_ZERO & SIGN_MASK) == 0 + +invariant posit16_nar_has_sign + assert (POS16_NAR & SIGN_MASK) != 0 + +invariant posit16_zero_nar_distinct + assert POS16_ZERO != POS16_NAR + +invariant posit16_abs_removes_sign + assert posit16_abs(POS16_NAR) != POS16_NAR // NaR preserves sign pattern + +invariant posit16_neg_zero_is_zero + assert posit16_neg(POS16_ZERO) == POS16_ZERO + +invariant posit16_neg_nar_is_nar + assert posit16_neg(POS16_NAR) == POS16_NAR + +invariant posit16_add_zero_identity + given value = posit16_encode_f32(50.0) + assert posit16_is_equal(posit16_add(value, POS16_ZERO), value) == true + +invariant posit16_sub_zero_identity + given value = posit16_encode_f32(50.0) + assert posit16_is_equal(posit16_sub(value, POS16_ZERO), value) == true + +invariant posit16_mul_zero_zero + given value = posit16_encode_f32(50.0) + assert posit16_mul(value, POS16_ZERO) == POS16_ZERO + +invariant posit16_div_nar_propagates + given value = posit16_encode_f32(50.0) + assert posit16_is_nar(posit16_div(POS16_NAR, value)) == true + +invariant posit16_relu_non_negative + given result = posit16_relu(posit16_encode_f32(50.0)) + try std.testing.expect(posit16_extract_sign(result) == false); + +invariant posit16_sqrt_non_negative + given result = posit16_sqrt(posit16_encode_f32(4.0)) + try std.testing.expect(posit16_extract_sign(result) == false); + +invariant posit16_quantize_to_int4_in_range + given result = posit16_quantize_to_int4(posit16_encode_f32(50.0)) + assert result >= -8 and result <= 7 + +invariant posit16_scale_roundtrip + given value = posit16_encode_f32(50.0) + try std.testing.expect(scale = 2.0); + try std.testing.expect(scaled = posit16_scale(value, scale)); + try std.testing.expect(decoded = posit16_to_f32_scaled(scaled, scale)); + try std.testing.expect(abs(decoded - posit16_decode_f32(value)) < 5.0); + +invariant posit16_max_ge_both + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_max(a, b)); + try std.testing.expect(posit16_is_greater(result, a) == true or posit16_is_equal(result, a) == true); + +invariant posit16_min_le_both + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_min(a, b)); + try std.testing.expect(posit16_is_greater(b, result) == true or posit16_is_equal(b, result) == true); + +invariant posit16_lerp_bounded + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result_half = posit16_lerp(a, b, 0.5)); + try std.testing.expect(decoded_a = posit16_decode_f32(a)); + try std.testing.expect(decoded_b = posit16_decode_f32(b)); + try std.testing.expect(decoded_half = posit16_decode_f32(result_half)); + try std.testing.expect(decoded_half > decoded_a and decoded_half < decoded_b); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench posit16_encode_f32_latency + measure: nanoseconds to posit16_encode_f32(10.5) + target: < 50ns + +bench posit16_decode_f32_latency + measure: nanoseconds to posit16_decode_f32(0x4000) + target: < 50ns + +bench posit16_add_latency + measure: nanoseconds to posit16_add(0x4000, 0x4800) + target: < 100ns + +bench posit16_mul_latency + measure: nanoseconds to posit16_mul(0x4000, 0x4000) + target: < 100ns + +bench posit16_sqrt_latency + measure: nanoseconds to posit16_sqrt(0x4000) + target: < 100ns + +bench posit16_sigmoid_latency + measure: nanoseconds to posit16_sigmoid(0x4000) + target: < 200ns + +bench posit16_relu_latency + measure: nanoseconds to posit16_relu(0x4800) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/purkinje_thermal_gate.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/purkinje_thermal_gate.t27 new file mode 100644 index 0000000000..1c814329e1 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/purkinje_thermal_gate.t27 @@ -0,0 +1,704 @@ +// SPDX-License-Identifier: Apache-2.0 +; purkinje_thermal_gate.t27 — Purkinje Thermal Gate +; Thermally-gated activation inspired by Purkinje neural dynamics +; φ² + 1/φ² = 3 | TRINITY + +module purkinje-thermal-gate; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const PTG_OPCODE : u8 = 0xF4; // Purkinje Thermal Gate opcode + +pub const TEMP_MIN_C : i8 = -40; // -40°C minimum +pub const TEMP_MAX_C : i8 = 125; // 125°C maximum +pub const TEMP_NOMINAL_C : i8 = 25; // 25°C nominal + +pub const TEMP_THRESHOLD_LOW_C : i8 = 0; // 0°C low threshold +pub const TEMP_THRESHOLD_HIGH_C : i8 = 85; // 85°C high threshold +pub const TEMP_CRITICAL_C : i8 = 100; // 100°C critical + +pub const TEMP_HYSTERESIS_C : i8 = 5; // 5°C hysteresis +pub const TEMP_SETTLING_MS : u16 = 100; // 100ms settling time + +pub const GATE_STATE_CLOSED : u8 = 0; +pub const GATE_STATE_OPEN : u8 = 1; +pub const GATE_STATE_THROTTLING : u8 = 2; +pub const GATE_STATE_ERROR : u8 = 3; + +pub const THROTTLE_LEVEL_NONE : u8 = 0; +pub const THROTTLE_LEVEL_LOW : u8 = 1; +pub const THROTTLE_LEVEL_MED : u8 = 2; +pub const THROTTLE_LEVEL_HIGH : u8 = 3; +pub const THROTTLE_LEVEL_MAX : u8 = 4; + +// ============================================================================ +// Types +// ============================================================================ + +pub const GateState = enum(u8) { + closed = GATE_STATE_CLOSED, + open = GATE_STATE_OPEN, + throttling = GATE_STATE_THROTTLING, + error = GATE_STATE_ERROR, +} + +pub const ThrottleLevel = enum(u8) { + none = THROTTLE_LEVEL_NONE, + low = THROTTLE_LEVEL_LOW, + medium = THROTTLE_LEVEL_MED, + high = THROTTLE_LEVEL_HIGH, +} + +pub const ThermalState = struct { + temp_c : i8, + gate_state : GateState, + throttle_level : ThrottleLevel, + error_code : u8, +} + +pub const GateConfig = struct { + low_threshold_c : i8, + high_threshold_c : i8, + critical_temp_c : i8, + hysteresis_c : i8, + auto_throttle : bool, +} + +pub const GateStatus = struct { + current_state : GateState, + temp_measured_c : i8, + throttle_level : ThrottleLevel, + cycle_count : u32, + fault_count : u8, +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// ptg_config_init() -> GateConfig +// Initialize Purkinje thermal gate config +pub fn ptg_config_init() -> GateConfig { + return GateConfig { + .low_threshold_c = TEMP_THRESHOLD_LOW_C, + .high_threshold_c = TEMP_THRESHOLD_HIGH_C, + .critical_temp_c = TEMP_CRITICAL_C, + .hysteresis_c = TEMP_HYSTERESIS_C, + .auto_throttle = true, + }; +} + +// ptg_config_strict() -> GateConfig +// Create strict thermal gate config (lower thresholds) +pub fn ptg_config_strict() -> GateConfig { + return GateConfig { + .low_threshold_c = 0, + .high_threshold_c = 70, + .critical_temp_c = 90, + .hysteresis_c = 3, + .auto_throttle = true, + }; +} + +// ptg_config_permissive() -> GateConfig +// Create permissive thermal gate config (higher thresholds) +pub fn ptg_config_permissive() -> GateConfig { + return GateConfig { + .low_threshold_c = -20, + .high_threshold_c = 100, + .critical_temp_c = 115, + .hysteresis_c = 10, + .auto_throttle = true, + }; +} + +// ptg_config_manual() -> GateConfig +// Create manual config (no auto throttle) +pub fn ptg_config_manual() -> GateConfig { + return GateConfig { + .low_threshold_c = TEMP_THRESHOLD_LOW_C, + .high_threshold_c = TEMP_THRESHOLD_HIGH_C, + .critical_temp_c = TEMP_CRITICAL_C, + .hysteresis_c = TEMP_HYSTERESIS_C, + .auto_throttle = false, + }; +} + +// ============================================================================ +// Temperature Validation +// ============================================================================ + +// ptg_temp_valid(temp_c: i8) -> bool +// Check if temperature is valid +pub fn ptg_temp_valid(temp_c: i8) -> bool { + return temp_c >= TEMP_MIN_C and temp_c <= TEMP_MAX_C; +} + +// ptg_temp_critical(temp_c: i8, config: GateConfig) -> bool +// Check if temperature is critical +pub fn ptg_temp_critical(temp_c: i8, config: GateConfig) -> bool { + return temp_c >= config.critical_temp_c; +} + +// ptg_temp_high(temp_c: i8, config: GateConfig) -> bool +// Check if temperature is high (above high threshold) +pub fn ptg_temp_high(temp_c: i8, config: GateConfig) -> bool { + return temp_c >= config.high_threshold_c; +} + +// ptg_temp_low(temp_c: i8, config: GateConfig) -> bool +// Check if temperature is low (below low threshold) +pub fn ptg_temp_low(temp_c: i8, config: GateConfig) -> bool { + return temp_c < config.low_threshold_c; +} + +// ptg_temp_in_hysteresis(temp_c: i8, threshold: i8, hysteresis: i8) -> bool +// Check if temperature is within hysteresis band +pub fn ptg_temp_in_hysteresis(temp_c: i8, threshold: i8, hysteresis: i8) -> bool { + if (temp_c >= threshold) { + return (temp_c - threshold) <= hysteresis; + } else { + return (threshold - temp_c) <= hysteresis; + } +} + +// ============================================================================ +// Gate State Functions +// ============================================================================ + +// ptg_thermal_state_init() -> ThermalState +// Initialize thermal state +pub fn ptg_thermal_state_init() -> ThermalState { + return ThermalState { + .temp_c = TEMP_NOMINAL_C, + .gate_state = GateState.open, + .throttle_level = ThrottleLevel.none, + .error_code = 0, + }; +} + +// ptg_gate_state_from_temp(temp_c: i8, config: GateConfig) -> GateState +// Determine gate state from temperature +pub fn ptg_gate_state_from_temp(temp_c: i8, config: GateConfig) -> GateState { + if (not ptg_temp_valid(temp_c)) { + return GateState.error; + } else if (ptg_temp_critical(temp_c, config)) { + return GateState.error; + } else if (ptg_temp_high(temp_c, config)) { + return GateState.throttling; + } else if (ptg_temp_low(temp_c, config)) { + return GateState.closed; + } else { + return GateState.open; + } +} + +// ptg_throttle_from_temp(temp_c: i8, config: GateConfig) -> ThrottleLevel +// Determine throttle level from temperature +pub fn ptg_throttle_from_temp(temp_c: i8, config: GateConfig) -> ThrottleLevel { + if (ptg_temp_critical(temp_c, config)) { + return ThrottleLevel.high; + } else if (temp_c >= config.high_threshold_c + 20) { + return ThrottleLevel.high; + } else if (temp_c >= config.high_threshold_c + 10) { + return ThrottleLevel.medium; + } else if (temp_c >= config.high_threshold_c) { + return ThrottleLevel.low; + } else { + return ThrottleLevel.none; + } +} + +// ptg_thermal_state_update(thermal: ThermalState, temp_c: i8, config: GateConfig) -> ThermalState +// Update thermal state with new temperature +pub fn ptg_thermal_state_update(thermal: ThermalState, temp_c: i8, config: GateConfig) -> ThermalState { + var new_state = thermal; + new_state.temp_c = temp_c; + new_state.gate_state = ptg_gate_state_from_temp(temp_c, config); + + if (config.auto_throttle) { + new_state.throttle_level = ptg_throttle_from_temp(temp_c, config); + } + + if (new_state.gate_state == GateState.error) { + new_state.error_code = 1; // Thermal error + } else if (new_state.gate_state != GateState.error) { + new_state.error_code = 0; + } + + return new_state; +} + +// ============================================================================ +// Gate Status Functions +// ============================================================================ + +// ptg_gate_status_init() -> GateStatus +// Initialize gate status +pub fn ptg_gate_status_init() -> GateStatus { + return GateStatus { + .current_state = GateState.open, + .temp_measured_c = TEMP_NOMINAL_C, + .throttle_level = ThrottleLevel.none, + .cycle_count = 0, + .fault_count = 0, + }; +} + +// ptg_gate_status_open(status: GateStatus) -> GateStatus +// Open the gate +pub fn ptg_gate_status_open(status: GateStatus) -> GateStatus { + return GateStatus { + .current_state = GateState.open, + .temp_measured_c = status.temp_measured_c, + .throttle_level = ThrottleLevel.none, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count, + }; +} + +// ptg_gate_status_close(status: GateStatus) -> GateStatus +// Close the gate +pub fn ptg_gate_status_close(status: GateStatus) -> GateStatus { + return GateStatus { + .current_state = GateState.closed, + .temp_measured_c = status.temp_measured_c, + .throttle_level = ThrottleLevel.high, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count, + }; +} + +// ptg_gate_status_throttle(status: GateStatus, level: ThrottleLevel) -> GateStatus +// Set gate to throttling state +pub fn ptg_gate_status_throttle(status: GateStatus, level: ThrottleLevel) -> GateStatus { + return GateStatus { + .current_state = GateState.throttling, + .temp_measured_c = status.temp_measured_c, + .throttle_level = level, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count, + }; +} + +// ptg_gate_status_error(status: GateStatus) -> GateStatus +// Set gate to error state +pub fn ptg_gate_status_error(status: GateStatus) -> GateStatus { + return GateStatus { + .current_state = GateState.error, + .temp_measured_c = status.temp_measured_c, + .throttle_level = ThrottleLevel.high, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count + 1, + }; +} + +// ptg_gate_status_update_temp(status: GateStatus, temp_c: i8) -> GateStatus +// Update measured temperature +pub fn ptg_gate_status_update_temp(status: GateStatus, temp_c: i8) -> GateStatus { + return GateStatus { + .current_state = status.current_state, + .temp_measured_c = temp_c, + .throttle_level = status.throttle_level, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count, + }; +} + +// ptg_gate_status_increment_cycle(status: GateStatus) -> GateStatus +// Increment cycle counter +pub fn ptg_gate_status_increment_cycle(status: GateStatus) -> GateStatus { + return GateStatus { + .current_state = status.current_state, + .temp_measured_c = status.temp_measured_c, + .throttle_level = status.throttle_level, + .cycle_count = status.cycle_count + 1, + .fault_count = status.fault_count, + }; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_ptg_cmd(opcode: u8, temp_c: i8, threshold: u8) -> u16 +// Encode Purkinje thermal gate command +pub fn encode_ptg_cmd(opcode: u8, temp_c: i8, threshold: u8) u16 { + // Format: [OP:8][TEMP_SIGNED:7][THRESHOLD:1] + const op_field : u16 = @as(u16, opcode) << 8; + const temp_field : u16 = @as(u16, @as(u8, @bitCast(temp_c))) << 1; + const thresh_field : u16 = @as(u16, threshold & 0x01); + return op_field | temp_field | thresh_field; +} + +// decode_ptg_cmd(encoded: u16) -> struct { opcode: u8, temp_c: i8, threshold: u8 } +// Decode Purkinje thermal gate command +pub fn decode_ptg_cmd(encoded: u16) -> struct { opcode: u8, temp_c: i8, threshold: u8 } { + const opcode : u8 = @as(u8, @truncate((encoded >> 8) & 0xFF)); + const temp_u8 : u8 = @as(u8, @truncate((encoded >> 1) & 0x7F)); + const temp_c : i8 = @bitCast(temp_u8); + const threshold : u8 = @as(u8, @truncate(encoded & 0x01)); + return .{ .opcode = opcode, .temp_c = temp_c, .threshold = threshold }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "ptg_opcode_constant" { + try std.testing.expect(PTG_OPCODE == 0xF4); +} + +test "temp_constants" { + try std.testing.expect(TEMP_MIN_C == -40); + try std.testing.expect(TEMP_MAX_C == 125); + try std.testing.expect(TEMP_NOMINAL_C == 25); +} + +test "temp_threshold_constants" { + try std.testing.expect(TEMP_THRESHOLD_LOW_C == 0); + try std.testing.expect(TEMP_THRESHOLD_HIGH_C == 85); + try std.testing.expect(TEMP_CRITICAL_C == 100); +} + +test "temp_hysteresis_constant" { + try std.testing.expect(TEMP_HYSTERESIS_C == 5); +} + +test "gate_state_constants" { + try std.testing.expect(GATE_STATE_CLOSED == 0); + try std.testing.expect(GATE_STATE_OPEN == 1); + try std.testing.expect(GATE_STATE_THROTTLING == 2); + try std.testing.expect(GATE_STATE_ERROR == 3); +} + +test "throttle_level_constants" { + try std.testing.expect(THROTTLE_LEVEL_NONE == 0); + try std.testing.expect(THROTTLE_LEVEL_LOW == 1); + try std.testing.expect(THROTTLE_LEVEL_MED == 2); + try std.testing.expect(THROTTLE_LEVEL_HIGH == 3); +} + +test "ptg_config_init_structure" { + given config = ptg_config_init() + try std.testing.expect(config.low_threshold_c == TEMP_THRESHOLD_LOW_C); + try std.testing.expect(config.high_threshold_c == TEMP_THRESHOLD_HIGH_C); + try std.testing.expect(config.auto_throttle == true); +} + +test "ptg_config_strict_structure" { + given config = ptg_config_strict() + try std.testing.expect(config.high_threshold_c == 70); + try std.testing.expect(config.critical_temp_c == 90); +} + +test "ptg_config_permissive_structure" { + given config = ptg_config_permissive() + try std.testing.expect(config.high_threshold_c == 100); + try std.testing.expect(config.critical_temp_c == 115); +} + +test "ptg_config_manual_structure" { + given config = ptg_config_manual() + try std.testing.expect(config.auto_throttle == false); +} + +test "ptg_temp_valid_true" { + try std.testing.expect(ptg_temp_valid(0) == true); + try std.testing.expect(ptg_temp_valid(25) == true); + try std.testing.expect(ptg_temp_valid(100) == true); +} + +test "ptg_temp_valid_false" { + try std.testing.expect(ptg_temp_valid(-50) == false); + try std.testing.expect(ptg_temp_valid(150) == false); +} + +test "ptg_temp_critical_true" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_critical(105, config) == true); + try std.testing.expect(ptg_temp_critical(100, config) == true); +} + +test "ptg_temp_critical_false" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_critical(90, config) == false); +} + +test "ptg_temp_high_true" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_high(90, config) == true); + try std.testing.expect(ptg_temp_high(85, config) == true); +} + +test "ptg_temp_high_false" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_high(80, config) == false); +} + +test "ptg_temp_low_true" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_low(-10, config) == true); + try std.testing.expect(ptg_temp_low(-1, config) == true); +} + +test "ptg_temp_low_false" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_low(0, config) == false); + try std.testing.expect(ptg_temp_low(10, config) == false); +} + +test "ptg_temp_in_hysteresis_true" { + try std.testing.expect(ptg_temp_in_hysteresis(85, 85, 5) == true); + try std.testing.expect(ptg_temp_in_hysteresis(87, 85, 5) == true); + try std.testing.expect(ptg_temp_in_hysteresis(83, 85, 5) == true); +} + +test "ptg_temp_in_hysteresis_false" { + try std.testing.expect(ptg_temp_in_hysteresis(95, 85, 5) == false); + try std.testing.expect(ptg_temp_in_hysteresis(75, 85, 5) == false); +} + +test "ptg_thermal_state_init_structure" { + given thermal = ptg_thermal_state_init() + try std.testing.expect(thermal.temp_c == TEMP_NOMINAL_C); + try std.testing.expect(thermal.gate_state == GateState.open); +} + +test "ptg_gate_state_from_temp_open" { + given config = ptg_config_init() + try std.testing.expect(ptg_gate_state_from_temp(50, config) == GateState.open); +} + +test "ptg_gate_state_from_temp_throttling" { + given config = ptg_config_init() + try std.testing.expect(ptg_gate_state_from_temp(90, config) == GateState.throttling); +} + +test "ptg_gate_state_from_temp_closed" { + given config = ptg_config_init() + try std.testing.expect(ptg_gate_state_from_temp(-10, config) == GateState.closed); +} + +test "ptg_gate_state_from_temp_error" { + given config = ptg_config_init() + try std.testing.expect(ptg_gate_state_from_temp(105, config) == GateState.error); +} + +test "ptg_throttle_from_temp_none" { + given config = ptg_config_init() + try std.testing.expect(ptg_throttle_from_temp(50, config) == ThrottleLevel.none); +} + +test "ptg_throttle_from_temp_low" { + given config = ptg_config_init() + try std.testing.expect(ptg_throttle_from_temp(90, config) == ThrottleLevel.low); +} + +test "ptg_throttle_from_temp_medium" { + given config = ptg_config_init() + try std.testing.expect(ptg_throttle_from_temp(100, config) == ThrottleLevel.medium); +} + +test "ptg_throttle_from_temp_high" { + given config = ptg_config_init() + try std.testing.expect(ptg_throttle_from_temp(110, config) == ThrottleLevel.high); +} + +test "ptg_thermal_state_update" { + given thermal = ptg_thermal_state_init() + try std.testing.expect(config = ptg_config_init()); + try std.testing.expect(result = ptg_thermal_state_update(thermal, 90, config)); + try std.testing.expect(result.temp_c == 90); + try std.testing.expect(result.gate_state == GateState.throttling); +} + +test "ptg_gate_status_init_structure" { + given status = ptg_gate_status_init() + try std.testing.expect(status.current_state == GateState.open); + try std.testing.expect(status.fault_count == 0); +} + +test "ptg_gate_status_open" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_open(status)); + try std.testing.expect(result.current_state == GateState.open); + try std.testing.expect(result.throttle_level == ThrottleLevel.none); +} + +test "ptg_gate_status_close" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_close(status)); + try std.testing.expect(result.current_state == GateState.closed); + try std.testing.expect(result.throttle_level == ThrottleLevel.high); +} + +test "ptg_gate_status_throttle" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_throttle(status, ThrottleLevel.medium)); + try std.testing.expect(result.current_state == GateState.throttling); + try std.testing.expect(result.throttle_level == ThrottleLevel.medium); +} + +test "ptg_gate_status_error" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_error(status)); + try std.testing.expect(result.current_state == GateState.error); + try std.testing.expect(result.fault_count == 1); +} + +test "ptg_gate_status_update_temp" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_update_temp(status, 75)); + try std.testing.expect(result.temp_measured_c == 75); +} + +test "ptg_gate_status_increment_cycle" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_increment_cycle(status)); + try std.testing.expect(result.cycle_count == 1); +} + +test "encode_ptg_cmd" { + given encoded = encode_ptg_cmd(0x10, 25, 1) + try std.testing.expect((encoded >> 8) == 0x10); +} + +test "decode_ptg_cmd" { + given decoded = decode_ptg_cmd(0x1033) + try std.testing.expect(decoded.opcode == 0x10); + try std.testing.expect(decoded.temp_c == 25); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant temp_range_valid + assert TEMP_MIN_C < TEMP_MAX_C + try std.testing.expect(TEMP_NOMINAL_C > TEMP_MIN_C); + try std.testing.expect(TEMP_NOMINAL_C < TEMP_MAX_C); + +invariant temp_thresholds_ordered + assert TEMP_THRESHOLD_LOW_C < TEMP_THRESHOLD_HIGH_C + try std.testing.expect(TEMP_THRESHOLD_HIGH_C < TEMP_CRITICAL_C); + +invariant temp_hysteresis_positive + assert TEMP_HYSTERESIS_C > 0 + +invariant ptg_config_init_valid + given config = ptg_config_init() + assert config.low_threshold_c < config.high_threshold_c + try std.testing.expect(config.high_threshold_c < config.critical_temp_c); + +invariant ptg_config_strict_lower_thresholds + given strict = ptg_config_strict() + try std.testing.expect(normal = ptg_config_init()); + assert strict.high_threshold_c < normal.high_threshold_c + try std.testing.expect(strict.critical_temp_c < normal.critical_temp_c); + +invariant ptg_config_permissive_higher_thresholds + given perm = ptg_config_permissive() + try std.testing.expect(normal = ptg_config_init()); + assert perm.high_threshold_c > normal.high_threshold_c + try std.testing.expect(perm.critical_temp_c > normal.critical_temp_c); + +invariant ptg_config_manual_no_auto_throttle + given config = ptg_config_manual() + assert config.auto_throttle == false + +invariant ptg_thermal_state_init_open + given thermal = ptg_thermal_state_init() + try std.testing.expect(thermal.gate_state == GateState.open); + +invariant ptg_gate_state_from_temp_nominal_open + given config = ptg_config_init() + try std.testing.expect(state = ptg_gate_state_from_temp(TEMP_NOMINAL_C, config)); + try std.testing.expect(state == GateState.open); + +invariant ptg_throttle_from_temp_nominal_none + given config = ptg_config_init() + try std.testing.expect(level = ptg_throttle_from_temp(TEMP_NOMINAL_C, config)); + try std.testing.expect(level == ThrottleLevel.none); + +invariant ptg_gate_status_init_open + given status = ptg_gate_status_init() + try std.testing.expect(status.current_state == GateState.open); + +invariant ptg_gate_status_error_increments_fault + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_error(status)); + try std.testing.expect(result.fault_count == 1); + +invariant ptg_gate_status_increment_cycle_increments + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_increment_cycle(status)); + try std.testing.expect(result.cycle_count == 1); + +invariant ptg_gate_status_close_high_throttle + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_close(status)); + try std.testing.expect(result.throttle_level == ThrottleLevel.high); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench ptg_config_init_latency + measure: nanoseconds to ptg_config_init() + target: < 20ns + +bench ptg_temp_valid_latency + measure: nanoseconds to ptg_temp_valid(25) + target: < 10ns + +bench ptg_temp_critical_latency + measure: nanoseconds to ptg_temp_critical(100, ptg_config_init()) + target: < 15ns + +bench ptg_temp_high_latency + measure: nanoseconds to ptg_temp_high(90, ptg_config_init()) + target: < 15ns + +bench ptg_temp_low_latency + measure: nanoseconds to ptg_temp_low(-10, ptg_config_init()) + target: < 15ns + +bench ptg_temp_in_hysteresis_latency + measure: nanoseconds to ptg_temp_in_hysteresis(87, 85, 5) + target: < 15ns + +bench ptg_thermal_state_init_latency + measure: nanoseconds to ptg_thermal_state_init() + target: < 20ns + +bench ptg_gate_state_from_temp_latency + measure: nanoseconds to ptg_gate_state_from_temp(50, ptg_config_init()) + target: < 20ns + +bench ptg_throttle_from_temp_latency + measure: nanoseconds to ptg_throttle_from_temp(90, ptg_config_init()) + target: < 20ns + +bench ptg_thermal_state_update_latency + measure: nanoseconds to ptg_thermal_state_update(ptg_thermal_state_init(), 90, ptg_config_init()) + target: < 30ns + +bench ptg_gate_status_init_latency + measure: nanoseconds to ptg_gate_status_init() + target: < 20ns + +bench ptg_gate_status_open_latency + measure: nanoseconds to ptg_gate_status_open(ptg_gate_status_init()) + target: < 15ns + +bench encode_ptg_cmd_latency + measure: nanoseconds to encode_ptg_cmd(0x10, 25, 1) + target: < 20ns + +bench decode_ptg_cmd_latency + measure: nanoseconds to decode_ptg_cmd(0x1033) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/sparse_mask.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/sparse_mask.t27 new file mode 100644 index 0000000000..49c8fb1357 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/sparse_mask.t27 @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: Apache-2.0 +; sparse_mask.t27 — Sacred Opcode 0xE8: Sparse Mask (Sparse Skip 2) +; Hardware for generating and applying sparse tensor masks +; φ² + 1/φ² = 3 | TRINITY + +module sacred-sparse_mask; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_SPARSE_MASK : u8 = 0xE8; + +pub const MASK_SIZE : u8 = 16; +pub const SPARSE_THRESHOLD_RATIO : u8 = 50; // 50% sparsity threshold + +pub const MASK_MODE_GEN : u8 = 0; +pub const MASK_MODE_APPLY : u8 = 1; +pub const MASK_MODE_COMBINE : u8 = 2; +pub const MASK_MODE_INVERT : u8 = 3; + +pub const MASK_TYPE_DENSITY : u8 = 0; +pub const MASK_TYPE_PATTERN : u8 = 1; +pub const MASK_TYPE_RANDOM : u8 = 2; +pub const MASK_TYPE_PHI : u8 = 3; // φ-optimized mask + +// ============================================================================ +// Types +// ============================================================================ + +pub const SparseMask = struct { + bits : u16, + density : u8, // 0-100%, percentage of set bits + pattern_type : u8, +} + +pub const MaskConfig = struct { + mode : u8, + mask_type : u8, + threshold : u8, + phi_optimized : bool, +} + +pub const MaskOp = struct { + result : SparseMask, + input_mask : u16, + output_mask : u16, + operation_count : u8, +} + +pub const Vector16 = struct { + values : [MASK_SIZE]i8, + mask : u16, +} + +// ============================================================================ +// Mask Generation Functions +// ============================================================================ + +// mask_count_set_bits(mask: u16) -> u8 +// Count number of set bits in mask +pub fn mask_count_set_bits(mask: u16) -> u8 { + var count : u8 = 0; + var m = mask; + while (m != 0) { + count += 1; + m &= m - 1; + } + return count; +} + +// mask_count_clear_bits(mask: u16) -> u8 +// Count number of clear bits in mask +pub fn mask_count_clear_bits(mask: u16) -> u8 { + return MASK_SIZE - mask_count_set_bits(mask); +} + +// mask_density(mask: u16) -> u8 +// Calculate mask density (0-100%) +pub fn mask_density(mask: u16) -> u8 { + const set_bits = mask_count_set_bits(mask); + return (set_bits * 100) / MASK_SIZE; +} + +// mask_is_sparse(mask: u16) -> bool +// Check if mask is sparse (< threshold) +pub fn mask_is_sparse(mask: u16) -> bool { + return mask_density(mask) < SPARSE_THRESHOLD_RATIO; +} + +// mask_is_dense(mask: u16) -> bool +// Check if mask is dense (>= threshold) +pub fn mask_is_dense(mask: u16) -> bool { + return mask_density(mask) >= SPARSE_THRESHOLD_RATIO; +} + +// mask_generate_density(density: u8) -> u16 +// Generate mask with specific density (0-100%) +pub fn mask_generate_density(density: u8) -> u16 { + const target_bits = (density * MASK_SIZE) / 100; + var mask : u16 = 0; + var i : u8 = 0; + + // Set first target_bits bits + while (i < target_bits) { + mask |= (@as(u16, 1) << i); + i += 1; + } + + return mask; +} + +// mask_generate_phi() -> u16 +// Generate φ-optimized mask (approximate golden ratio distribution) +pub fn mask_generate_phi() -> u16 { + // φ ≈ 1.618, use pattern based on Fibonacci sequence + // Set bits at positions: 0, 1, 2, 3, 5, 8, 13 (first few Fib numbers) + var mask : u16 = 0; + mask |= (@as(u16, 1) << 0); + mask |= (@as(u16, 1) << 1); + mask |= (@as(u16, 1) << 2); + mask |= (@as(u16, 1) << 3); + mask |= (@as(u16, 1) << 5); + mask |= (@as(u16, 1) << 8); + mask |= (@as(u16, 1) << 13); + return mask; +} + +// mask_generate_pattern(pattern: u8) -> u16 +// Generate mask with repeating pattern +pub fn mask_generate_pattern(pattern: u8) -> u16 { + var mask : u16 = 0; + const pattern_bits : u8 = 4; + + for (0..MASK_SIZE) |i| { + if ((pattern >> (i % pattern_bits)) & 1 != 0) { + mask |= (@as(u16, 1) << i); + } + } + + return mask; +} + +// mask_generate_random(seed: u16) -> u16 +// Generate pseudo-random mask using LFSR +pub fn mask_generate_random(seed: u16) -> u16 { + var lfsr = seed; + var mask : u16 = 0; + + for (0..MASK_SIZE) |i| { + const bit = (lfsr >> 0) & 1; + if (bit != 0) { + mask |= (@as(u16, 1) << i); + } + // LFSR feedback (X^16 + X^14 + X^13 + X^11) + const feedback = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5)) & 1; + lfsr = (lfsr >> 1) | (feedback << 15); + } + + return mask; +} + +// ============================================================================ +// Mask Operation Functions +// ============================================================================ + +// mask_generate(config: MaskConfig, seed: u16) -> SparseMask +// Generate mask based on config +pub fn mask_generate(config: MaskConfig, seed: u16) -> SparseMask { + var mask : u16 = 0; + var pattern_type : u8 = config.mask_type; + + switch (config.mask_type) { + MASK_TYPE_DENSITY => { + mask = mask_generate_density(config.threshold); + }, + MASK_TYPE_PATTERN => { + mask = mask_generate_pattern(config.threshold); + }, + MASK_TYPE_RANDOM => { + mask = mask_generate_random(seed); + }, + MASK_TYPE_PHI => { + if (config.phi_optimized) { + mask = mask_generate_phi(); + } else { + mask = mask_generate_pattern(0x05); // Default pattern + } + }, + else => { + mask = mask_generate_pattern(0x0F); + }, + } + + return SparseMask { + .bits = mask, + .density = mask_density(mask), + .pattern_type = pattern_type, + }; +} + +// mask_apply(vector: Vector16, mask: SparseMask) -> Vector16 +// Apply mask to vector (zero out masked elements) +pub fn mask_apply(vector: Vector16, mask: SparseMask) -> Vector16 { + var result : Vector16 = undefined; + result.mask = mask.bits; + + for (vector.values, 0..) |val, i| { + if ((mask.bits >> i) & 1 != 0) { + result.values[i] = val; + } else { + result.values[i] = 0; + } + } + + return result; +} + +// mask_combine(mask1: u16, mask2: u16, operation: u8) -> u16 +// Combine two masks +pub fn mask_combine(mask1: u16, mask2: u16, operation: u8) -> u16 { + switch (operation) { + 0 => return mask1 & mask2, // AND + 1 => return mask1 | mask2, // OR + 2 => return mask1 ^ mask2, // XOR + else => return mask1 & ~mask2, // NAND + } +} + +// mask_invert(mask: u16) -> u16 +// Invert mask +pub fn mask_invert(mask: u16) -> u16 { + return ~mask & ((@as(u16, 1) << MASK_SIZE) - 1); +} + +// mask_complement(mask: SparseMask) -> SparseMask +// Get complement of mask (inverse) +pub fn mask_complement(mask: SparseMask) -> SparseMask { + const inverted = mask_invert(mask.bits); + return SparseMask { + .bits = inverted, + .density = mask_density(inverted), + .pattern_type = mask.pattern_type, + }; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_sparse_mask(mode: u8, mask_type: u8, seed: u8) -> u16 +// Encode sparse mask instruction +pub fn encode_sparse_mask(mode: u8, mask_type: u8, seed: u8) u16 { + // Format: [OP:8][MODE:2][TYPE:2][SEED:4] + const op : u16 = @as(u16, OP_SPARSE_MASK) << 8; + const mode_field : u16 = @as(u16, mode & 0x03) << 6; + const type_field : u16 = @as(u16, mask_type & 0x03) << 4; + const seed_field : u16 = @as(u16, seed & 0x0F); + return op | mode_field | type_field | seed_field; +} + +// decode_sparse_mask(encoded: u16) -> struct { mode: u8, mask_type: u8, seed: u8 } +// Decode sparse mask instruction +pub fn decode_sparse_mask(encoded: u16) struct { mode: u8, mask_type: u8, seed: u8 } { + const mode : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const mask_type : u8 = @as(u8, @truncate((encoded >> 4) & 0x03)); + const seed : u8 = @as(u8, @truncate(encoded & 0x0F)); + return .{ .mode = mode, .mask_type = mask_type, .seed = seed }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "mask_size_sixteen" { + try std.testing.expect(MASK_SIZE == 16); +} + +test "sparse_threshold_fifty" { + try std.testing.expect(SPARSE_THRESHOLD_RATIO == 50); +} + +test "mask_mode_gen" { + assert MASK_MODE_GEN == 0 +} + +test "mask_mode_apply" { + assert MASK_MODE_APPLY == 1 +} + +test "mask_type_phi" { + assert MASK_TYPE_PHI == 3 +} + +test "mask_count_set_bits_all" { + try std.testing.expect(mask_count_set_bits(0xFFFF) == 16); +} + +test "mask_count_set_bits_none" { + try std.testing.expect(mask_count_set_bits(0x0000) == 0); +} + +test "mask_count_set_bits_half" { + try std.testing.expect(mask_count_set_bits(0x00FF) == 8); +} + +test "mask_count_clear_bits_all" { + try std.testing.expect(mask_count_clear_bits(0xFFFF) == 0); +} + +test "mask_count_clear_bits_none" { + try std.testing.expect(mask_count_clear_bits(0x0000) == 16); +} + +test "mask_density_zero" { + try std.testing.expect(mask_density(0x0000) == 0); +} + +test "mask_density_fifty" { + try std.testing.expect(mask_density(0x00FF) == 50); +} + +test "mask_density_full" { + try std.testing.expect(mask_density(0xFFFF) == 100); +} + +test "mask_is_sparse_true" { + try std.testing.expect(mask_is_sparse(0x000F) == true); + try std.testing.expect(mask_is_sparse(0x003F) == true); +} + +test "mask_is_sparse_false" { + try std.testing.expect(mask_is_sparse(0x00FF) == false); + try std.testing.expect(mask_is_sparse(0xFFFF) == false); +} + +test "mask_is_dense_true" { + try std.testing.expect(mask_is_dense(0x00FF) == true); + try std.testing.expect(mask_is_dense(0xFFFF) == true); +} + +test "mask_is_dense_false" { + try std.testing.expect(mask_is_dense(0x000F) == false); + try std.testing.expect(mask_is_dense(0x003F) == false); +} + +test "mask_generate_density_zero" { + try std.testing.expect(mask_generate_density(0) == 0); +} + +test "mask_generate_density_full" { + try std.testing.expect(mask_generate_density(100) == 0xFFFF); +} + +test "mask_generate_density_fifty" { + try std.testing.expect(mask_count_set_bits(mask_generate_density(50)) == 8); +} + +test "mask_generate_phi" { + given mask = mask_generate_phi() + try std.testing.expect(mask_count_set_bits(mask) == 7); + try std.testing.expect(mask_density(mask) == 43 // 7/16 ≈ 43%); +} + +test "mask_generate_pattern_alternating" { + try std.testing.expect(mask_generate_pattern(0x05) == 0xAAAA); +} + +test "mask_generate_pattern_ones" { + try std.testing.expect(mask_generate_pattern(0x0F) == 0xFFFF); +} + +test "mask_generate_pattern_zeros" { + try std.testing.expect(mask_generate_pattern(0x00) == 0x0000); +} + +test "mask_generate_random_seeded" { + given mask1 = mask_generate_random(0x1234) + try std.testing.expect(mask2 = mask_generate_random(0x1234)); + try std.testing.expect(mask1 == mask2); +} + +test "mask_generate_random_different" { + given mask1 = mask_generate_random(0x1234) + try std.testing.expect(mask2 = mask_generate_random(0x4321)); + try std.testing.expect(mask1 != mask2); +} + +test "mask_generate_dense" { + given config = MaskConfig{.mode = MASK_MODE_GEN, .mask_type = MASK_TYPE_DENSITY, .threshold = 80, .phi_optimized = false} + try std.testing.expect(mask = mask_generate(config, 0)); + try std.testing.expect(mask.density >= 75); +} + +test "mask_generate_sparse" { + given config = MaskConfig{.mode = MASK_MODE_GEN, .mask_type = MASK_TYPE_DENSITY, .threshold = 20, .phi_optimized = false} + try std.testing.expect(mask = mask_generate(config, 0)); + try std.testing.expect(mask.density <= 30); +} + +test "mask_apply_zero_mask" { + given vector = Vector16{.values = [_]i8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, .mask = 0xFFFF} + try std.testing.expect(mask_obj = SparseMask{.bits = 0x0000, .density = 0, .pattern_type = 0}); + try std.testing.expect(result = mask_apply(vector, mask_obj)); + try std.testing.expect(result.values[0] == 0); + try std.testing.expect(result.values[15] == 0); +} + +test "mask_apply_full_mask" { + given vector = Vector16{.values = [_]i8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, .mask = 0x0000} + try std.testing.expect(mask_obj = SparseMask{.bits = 0xFFFF, .density = 100, .pattern_type = 0}); + try std.testing.expect(result = mask_apply(vector, mask_obj)); + try std.testing.expect(result.values[0] == 1); + try std.testing.expect(result.values[15] == 16); +} + +test "mask_combine_and" { + try std.testing.expect(mask_combine(0xFF00, 0xF0F0, 0) == 0xF000); +} + +test "mask_combine_or" { + try std.testing.expect(mask_combine(0xFF00, 0xF0F0, 1) == 0xFFF0); +} + +test "mask_combine_xor" { + try std.testing.expect(mask_combine(0xFF00, 0xF0F0, 2) == 0x0FF0); +} + +test "mask_combine_nand" { + try std.testing.expect(mask_combine(0xFF00, 0xF0F0, 3) == 0x0FFF); +} + +test "mask_invert_full" { + try std.testing.expect(mask_invert(0xFFFF) == 0); +} + +test "mask_invert_zero" { + try std.testing.expect(mask_invert(0) == 0xFFFF); +} + +test "mask_complement_sparse_to_dense" { + given mask = SparseMask{.bits = 0x000F, .density = 25, .pattern_type = 0} + try std.testing.expect(result = mask_complement(mask)); + try std.testing.expect(result.density == 75); +} + +test "encode_sparse_mask" { + given encoded = encode_sparse_mask(1, 2, 0x0A) + try std.testing.expect((encoded >> 8) == OP_SPARSE_MASK); +} + +test "decode_sparse_mask" { + given decoded = decode_sparse_mask(0xE86A) + try std.testing.expect(decoded.mode == 1); + try std.testing.expect(decoded.mask_type == 2); + try std.testing.expect(decoded.seed == 0x0A); +} + +test "opcode_constant" { + try std.testing.expect(OP_SPARSE_MASK == 0xE8); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant mask_size_sixteen + assert MASK_SIZE == 16 + +invariant sparse_threshold_fifty + assert SPARSE_THRESHOLD_RATIO == 50 + +invariant mask_count_bits_sum + given mask = 0xAA55 + assert mask_count_set_bits(mask) + mask_count_clear_bits(mask) == MASK_SIZE + +invariant mask_density_range + try std.testing.expect(mask_density(0) == 0 and mask_density(0xFFFF) == 100); + +invariant mask_is_sparse_inverse_dense + given mask = 0x0F0F + assert mask_is_sparse(mask) != mask_is_dense(mask) + +invariant mask_is_complement_inverse + given mask = 0x1234 + try std.testing.expect(inverted = mask_invert(mask)); + try std.testing.expect(mask_invert(inverted) == mask); + +invariant mask_complement_density + given mask = SparseMask{.bits = 0x0F0F, .density = 50, .pattern_type = 0} + try std.testing.expect(result = mask_complement(mask)); + try std.testing.expect(result.density + mask.density == 100); + +invariant mask_combine_and_less_than_or + given m1 = 0xAAAA + try std.testing.expect(m2 = 0x5555); + try std.testing.expect(combined_and = mask_combine(m1, m2, 0)); + try std.testing.expect(combined_or = mask_combine(m1, m2, 1)); + try std.testing.expect(combined_and <= m1 and combined_and <= m2); + try std.testing.expect(combined_or >= m1 and combined_or >= m2); + +invariant mask_generate_phi_consistent + given mask1 = mask_generate_phi() + try std.testing.expect(mask2 = mask_generate_phi()); + try std.testing.expect(mask1 == mask2); + +invariant mask_generate_density_range + try std.testing.expect(mask_density(mask_generate_density(0)) == 0); + try std.testing.expect(mask_density(mask_generate_density(100)) == 100); + +invariant mask_apply_preserves_unmasked + given vector = Vector16{.values = [_]i8{1, 2, 3, 4} ** 4, .mask = 0x0000} + try std.testing.expect(mask_obj = SparseMask{.bits = 0x00FF, .density = 50, .pattern_type = 0}); + try std.testing.expect(result = mask_apply(vector, mask_obj)); + try std.testing.expect(result.values[0] == 1); + try std.testing.expect(result.values[7] == 8); + +invariant mask_apply_masks_zero + given vector = Vector16{.values = [_]i8{1, 2, 3, 4} ** 4, .mask = 0x0000} + try std.testing.expect(mask_obj = SparseMask{.bits = 0xFF00, .density = 50, .pattern_type = 0}); + try std.testing.expect(result = mask_apply(vector, mask_obj)); + try std.testing.expect(result.values[0] == 0); + try std.testing.expect(result.values[8] == 0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench mask_count_set_bits_latency + measure: nanoseconds to mask_count_set_bits(0xAAAA) + target: < 30ns + +bench mask_density_latency + measure: nanoseconds to mask_density(0x1234) + target: < 50ns + +bench mask_generate_phi_latency + measure: nanoseconds to mask_generate_phi() + target: < 50ns + +bench mask_generate_random_latency + measure: nanoseconds to mask_generate_random(0x1234) + target: < 100ns + +bench mask_apply_latency + measure: nanoseconds to mask_apply(Vector16{.values = [_]i8{1} ** 16, .mask = 0}, SparseMask{.bits = 0x00FF, .density = 50, .pattern_type = 0}) + target: < 100ns + +bench encode_sparse_mask_latency + measure: nanoseconds to encode_sparse_mask(1, 2, 0x0A) + target: < 20ns + +bench decode_sparse_mask_latency + measure: nanoseconds to decode_sparse_mask(0xE86A) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/sparse_skip.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/sparse_skip.t27 new file mode 100644 index 0000000000..cd5620c6b4 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/sparse_skip.t27 @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: Apache-2.0 +; sparse_skip.t27 — Sacred Opcode 0xE1: Sparse Skip Operation +; Hardware acceleration for sparse tensor operations with zero-skipping +; φ² + 1/φ² = 3 | TRINITY + +module sacred-sparse_skip; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_SPARSE_SKIP : u8 = 0xE1; + +pub const VECTOR_SIZE : u8 = 16; +pub const ZERO_THRESHOLD : u8 = 1; // Values <= this are considered zero +pub const SPARSE_TOLERANCE : u8 = 5; // Percentage tolerance + +pub const SKIP_NONE : u8 = 0; +pub const SKIP_ZERO : u8 = 1; +pub const SKIP_THRESHOLD : u8 = 2; +pub const SKIP_ALL : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const SkipMode = enum(u8) { + none = SKIP_NONE, + zero = SKIP_ZERO, + threshold = SKIP_THRESHOLD, + all = SKIP_ALL, +} + +pub const SparsePattern = struct { + is_sparse : bool, + zero_count : u8, + non_zero_count : u8, + sparsity_ratio : u8, // 0-255, represents 0-100% +} + +pub const SkipResult = struct { + mode : SkipMode, + skipped_count : u8, + processed_count : u8, + mask : u16, // Bitmask of non-zero elements +} + +pub const SparseVector = struct { + values : [VECTOR_SIZE]i8, + mask : u16, + zero_count : u8, +} + +// ============================================================================ +// Zero Detection Functions +// ============================================================================ + +// is_zero(value: i8) -> bool +// Check if value is zero +pub fn is_zero(value: i8) bool { + return value == 0; +} + +// is_below_threshold(value: i8, threshold: i8) -> bool +// Check if value is below threshold +pub fn is_below_threshold(value: i8, threshold: i8) bool { + return if (value >= 0) value < threshold else value > -threshold; +} + +// count_zeros(vector: [VECTOR_SIZE]i8) -> u8 +// Count zeros in a vector +pub fn count_zeros(vector: [VECTOR_SIZE]i8) u8 { + var count : u8 = 0; + for (vector) |val| { + if (is_zero(val)) { + count += 1; + } + } + return count; +} + +// count_non_zeros(vector: [VECTOR_SIZE]i8) -> u8 +// Count non-zeros in a vector +pub fn count_non_zeros(vector: [VECTOR_SIZE]i8) u8 { + var count : u8 = 0; + for (vector) |val| { + if (not is_zero(val)) { + count += 1; + } + } + return count; +} + +// ============================================================================ +// Sparse Pattern Functions +// ============================================================================ + +// analyze_sparsity(vector: [VECTOR_SIZE]i8) -> SparsePattern +// Analyze sparsity pattern of a vector +pub fn analyze_sparsity(vector: [VECTOR_SIZE]i8) SparsePattern { + const zeros = count_zeros(vector); + const non_zeros = count_non_zeros(vector); + const sparsity_ratio = if (VECTOR_SIZE > 0) + (zeros * 100) / VECTOR_SIZE + else + 0; + + const is_sparse = sparsity_ratio > 50; // More than 50% zeros + + return SparsePattern { + .is_sparse = is_sparse, + .zero_count = zeros, + .non_zero_count = non_zeros, + .sparsity_ratio = @as(u8, @truncate(sparsity_ratio)), + }; +} + +// should_skip_sparsity(pattern: SparsePattern) -> bool +// Determine if vector should be skipped based on sparsity +pub fn should_skip_sparsity(pattern: SparsePattern) bool { + return pattern.is_sparse and pattern.sparsity_ratio > SPARSE_TOLERANCE; +} + +// ============================================================================ +// Skip Result Functions +// ============================================================================ + +// skip_zeros(vector: [VECTOR_SIZE]i8) -> SkipResult +// Skip zero values in vector +pub fn skip_zeros(vector: [VECTOR_SIZE]i8) SkipResult { + var mask : u16 = 0; + var skipped : u8 = 0; + + for (vector, 0..) |val, i| { + if (not is_zero(val)) { + mask |= (@as(u16, 1) << i); + } else { + skipped += 1; + } + } + + return SkipResult { + .mode = SkipMode.zero, + .skipped_count = skipped, + .processed_count = @as(u8, @truncate(VECTOR_SIZE - skipped)), + .mask = mask, + }; +} + +// skip_threshold(vector: [VECTOR_SIZE]i8, threshold: i8) -> SkipResult +// Skip values below threshold +pub fn skip_threshold(vector: [VECTOR_SIZE]i8, threshold: i8) SkipResult { + var mask : u16 = 0; + var skipped : u8 = 0; + + for (vector, 0..) |val, i| { + if (not is_below_threshold(val, threshold)) { + mask |= (@as(u16, 1) << i); + } else { + skipped += 1; + } + } + + return SkipResult { + .mode = SkipMode.threshold, + .skipped_count = skipped, + .processed_count = @as(u8, @truncate(VECTOR_SIZE - skipped)), + .mask = mask, + }; +} + +// skip_all(vector: [VECTOR_SIZE]i8) -> SkipResult +// Skip all processing +pub fn skip_all(vector: [VECTOR_SIZE]i8) SkipResult { + return SkipResult { + .mode = SkipMode.all, + .skipped_count = VECTOR_SIZE, + .processed_count = 0, + .mask = 0, + }; +} + +// skip_none(vector: [VECTOR_SIZE]i8) -> SkipResult +// Process all elements without skipping +pub fn skip_none(vector: [VECTOR_SIZE]i8) SkipResult { + var mask : u16 = 0; + for (0..VECTOR_SIZE) |i| { + mask |= (@as(u16, 1) << i); + } + + return SkipResult { + .mode = SkipMode.none, + .skipped_count = 0, + .processed_count = VECTOR_SIZE, + .mask = mask, + }; +} + +// ============================================================================ +// Sparse Vector Functions +// ============================================================================ + +// create_sparse_vector(vector: [VECTOR_SIZE]i8) -> SparseVector +// Create sparse vector representation +pub fn create_sparse_vector(vector: [VECTOR_SIZE]i8) SparseVector { + var mask : u16 = 0; + var zero_count : u8 = 0; + + for (vector, 0..) |val, i| { + if (not is_zero(val)) { + mask |= (@as(u16, 1) << i); + } else { + zero_count += 1; + } + } + + return SparseVector { + .values = vector, + .mask = mask, + .zero_count = zero_count, + }; +} + +// get_non_zero_elements(sparse: SparseVector) -> [VECTOR_SIZE]i8 +// Get only non-zero elements (zero-padded) +pub fn get_non_zero_elements(sparse: SparseVector) [VECTOR_SIZE]i8 { + var result : [VECTOR_SIZE]i8 = undefined; + var idx : u8 = 0; + + for (sparse.values, 0..) |val, i| { + if ((sparse.mask >> i) & 1 != 0) { + result[idx] = val; + idx += 1; + } + } + + // Zero-fill remaining + while (idx < VECTOR_SIZE) { + result[idx] = 0; + idx += 1; + } + + return result; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_sparse_skip(mode: u8, vector_addr: u8) -> u16 +// Encode sparse skip instruction +pub fn encode_sparse_skip(mode: u8, vector_addr: u8) u16 { + // Format: [OP:8][MODE:2][ADDR:6] + const op : u16 = @as(u16, OP_SPARSE_SKIP) << 8; + const mode_field : u16 = @as(u16, mode & 0x03) << 6; + const addr_field : u16 = @as(u16, vector_addr & 0x3F); + return op | mode_field | addr_field; +} + +// decode_sparse_skip(encoded: u16) -> struct { mode: u8, vector_addr: u8 } +// Decode sparse skip instruction +pub fn decode_sparse_skip(encoded: u16) struct { mode: u8, vector_addr: u8 } { + const mode : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const vector_addr : u8 = @as(u8, @truncate(encoded & 0x3F)); + return .{ .mode = mode, .vector_addr = vector_addr }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "is_zero_true" { + try std.testing.expect(is_zero(0) == true); +} + +test "is_zero_false" { + try std.testing.expect(is_zero(1) == false); + try std.testing.expect(is_zero(-1) == false); +} + +test "is_below_threshold_positive" { + try std.testing.expect(is_below_threshold(3, 5) == true); + try std.testing.expect(is_below_threshold(7, 5) == false); +} + +test "is_below_threshold_negative" { + try std.testing.expect(is_below_threshold(-3, 5) == true); + try std.testing.expect(is_below_threshold(-7, 5) == false); +} + +test "count_zeros_empty" { + given vector = [_]i8{0} ** 16 + try std.testing.expect(count_zeros(vector) == 16); +} + +test "count_zeros_none" { + given vector = [_]i8{1} ** 16 + try std.testing.expect(count_zeros(vector) == 0); +} + +test "count_zeros_mixed" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(count_zeros(vector) == 8); +} + +test "count_non_zeros_mixed" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(count_non_zeros(vector) == 8); +} + +test "analyze_sparsity_dense" { + given vector = [_]i8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(pattern.is_sparse == false); + try std.testing.expect(pattern.zero_count == 0); +} + +test "analyze_sparsity_sparse" { + given vector = [_]i8{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(pattern.is_sparse == true); + try std.testing.expect(pattern.zero_count == 15); +} + +test "should_skip_sparsity_true" { + given vector = [_]i8{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(should_skip_sparsity(pattern) == true); +} + +test "should_skip_sparsity_false" { + given vector = [_]i8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(should_skip_sparsity(pattern) == false); +} + +test "skip_zeros_mixed" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(result = skip_zeros(vector)); + try std.testing.expect(result.skipped_count == 8); + try std.testing.expect(result.processed_count == 8); + try std.testing.expect(result.mask == 0xAAAA); +} + +test "skip_threshold_mixed" { + given vector = [_]i8{1, 0, 2, 0, 10, 0, 4, 0, 5, 0, 20, 0, 7, 0, 8, 0} + try std.testing.expect(result = skip_threshold(vector, 5)); + try std.testing.expect(result.skipped_count == 5); +} + +test "skip_all" { + given vector = [_]i8{1} ** 16 + try std.testing.expect(result = skip_all(vector)); + try std.testing.expect(result.skipped_count == 16); + try std.testing.expect(result.processed_count == 0); + try std.testing.expect(result.mask == 0); +} + +test "skip_none" { + given vector = [_]i8{1} ** 16 + try std.testing.expect(result = skip_none(vector)); + try std.testing.expect(result.skipped_count == 0); + try std.testing.expect(result.processed_count == 16); + try std.testing.expect(result.mask == 0xFFFF); +} + +test "create_sparse_vector" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(sparse = create_sparse_vector(vector)); + try std.testing.expect(sparse.mask == 0xAAAA); + try std.testing.expect(sparse.zero_count == 8); +} + +test "get_non_zero_elements" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(sparse = create_sparse_vector(vector)); + try std.testing.expect(non_zeros = get_non_zero_elements(sparse)); + try std.testing.expect(non_zeros[0] == 1); + try std.testing.expect(non_zeros[1] == 2); + try std.testing.expect(non_zeros[2] == 3); + try std.testing.expect(non_zeros[7] == 0); +} + +test "encode_sparse_skip" { + given encoded = encode_sparse_skip(1, 0x20) + try std.testing.expect((encoded >> 8) == OP_SPARSE_SKIP); +} + +test "decode_sparse_skip" { + given decoded = decode_sparse_skip(0xE150) + try std.testing.expect(decoded.mode == 1); + try std.testing.expect(decoded.vector_addr == 0x10); +} + +test "opcode_constant" { + try std.testing.expect(OP_SPARSE_SKIP == 0xE1); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant vector_size_sixteen + assert VECTOR_SIZE == 16 + +invariant zero_threshold_one + assert ZERO_THRESHOLD == 1 + +invariant sparse_tolerance_five + assert SPARSE_TOLERANCE == 5 + +invariant skip_mode_none_zero + assert SKIP_NONE == 0 + +invariant skip_mode_all_three + assert SKIP_ALL == 3 + +invariant zero_count_plus_non_zero_equals_size + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + assert count_zeros(vector) + count_non_zeros(vector) == VECTOR_SIZE + +invariant skip_all_skips_all + try std.testing.expect(skip_all([_]i8{1} ** 16).skipped_count == VECTOR_SIZE); + +invariant skip_none_skips_none + try std.testing.expect(skip_none([_]i8{1} ** 16).skipped_count == 0); + +invariant processed_plus_skipped_equals_size + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0} + try std.testing.expect(result = skip_zeros(vector)); + assert result.skipped_count + result.processed_count == VECTOR_SIZE + +invariant sparsity_ratio_bound + given vector = [_]i8{1} ** 16 + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(pattern.sparsity_ratio <= 100); + +invariant mask_bits_processed_equals_processed_count + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(result = skip_zeros(vector)); + var mask_bits : u8 = 0 + for (0..VECTOR_SIZE) |i| { + if ((result.mask >> i) & 1 != 0) { + mask_bits += 1; + } + } + assert mask_bits == result.processed_count + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench count_zeros_latency + measure: nanoseconds to count_zeros([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}) + target: < 100ns + +bench analyze_sparsity_latency + measure: nanoseconds to analyze_sparsity([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}) + target: < 150ns + +bench skip_zeros_latency + measure: nanoseconds to skip_zeros([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}) + target: < 150ns + +bench skip_threshold_latency + measure: nanoseconds to skip_threshold([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}, 5) + target: < 150ns + +bench create_sparse_vector_latency + measure: nanoseconds to create_sparse_vector([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}) + target: < 150ns + +bench encode_sparse_skip_latency + measure: nanoseconds to encode_sparse_skip(1, 0x20) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/spec_exit.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/spec_exit.t27 new file mode 100644 index 0000000000..d716d9c3d6 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/spec_exit.t27 @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: Apache-2.0 +; spec_exit.t27 — Sacred Opcode 0xEB: Speculative Exit +; Hardware for speculative exit and recovery +; φ² + 1/φ² = 3 | TRINITY + +module sacred-spec_exit; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_SPEC_EXIT : u8 = 0xEB; + +pub const MAX_NESTING_DEPTH : u8 = 8; +pub const EXIT_REASON_BITS : u8 = 3; + +pub const EXIT_SUCCESS : u8 = 0; +pub const EXIT_SPEC_FAIL : u8 = 1; +pub const EXIT_TIMEOUT : u8 = 2; +pub const EXIT_EXCEPTION : u8 = 3; +pub const EXIT_RESOURCE : u8 = 4; +pub const EXIT_ABORT : u8 = 5; +pub const EXIT_RECOVER : u8 = 6; +pub const EXIT_MAX : u8 = 7; + +pub const STATE_RUNNING : u8 = 0; +pub const STATE_SPECULATING : u8 = 1; +pub const STATE_EXITING : u8 = 2; +pub const STATE_EXITED : u8 = 3; +pub const STATE_RECOVERING : u8 = 4; +pub const STATE_ERROR : u8 = 5; + +// ============================================================================ +// Types +// ============================================================================ + +pub const ExitReason = enum(u8) { + success = EXIT_SUCCESS, + spec_fail = EXIT_SPEC_FAIL, + timeout = EXIT_TIMEOUT, + exception = EXIT_EXCEPTION, + resource = EXIT_RESOURCE, + abort = EXIT_ABORT, + recover = EXIT_RECOVER, +} + +pub const SpecState = enum(u8) { + running = STATE_RUNNING, + speculating = STATE_SPECULATING, + exiting = STATE_EXITING, + exited = STATE_EXITED, + recovering = STATE_RECOVERING, + error = STATE_ERROR, +} + +pub const SpecExit = struct { + reason : ExitReason, + depth : u8, + checkpoint_addr : u16, + recovery_possible : bool, +} + +pub const SpecContext = struct { + state : SpecState, + current_depth : u8, + spec_count : u32, + exit_count : u32, +} + +pub const SpecConfig = struct { + max_depth : u8, + timeout_cycles : u32, + enable_recovery : bool, + checkpoint_interval : u8, +} + +// ============================================================================ +// Spec State Functions +// ============================================================================ + +// spec_state_is_running(state: SpecState) -> bool +// Check if state is running +pub fn spec_state_is_running(state: SpecState) bool { + return state == SpecState.running; +} + +// spec_state_is_speculating(state: SpecState) -> bool +// Check if state is speculating +pub fn spec_state_is_speculating(state: SpecState) -> bool { + return state == SpecState.speculating; +} + +// spec_state_is_exiting(state: SpecState) -> bool +// Check if state is exiting +pub fn spec_state_is_exiting(state: SpecState) -> bool { + return state == SpecState.exiting; +} + +// spec_state_is_exited(state: SpecState) -> bool +// Check if state has exited +pub fn spec_state_is_exited(state: SpecState) -> bool { + return state == SpecState.exited; +} + +// spec_state_is_recovering(state: SpecState) -> bool +// Check if state is recovering +pub fn spec_state_is_recovering(state: SpecState) -> bool { + return state == SpecState.recovering; +} + +// spec_state_is_terminal(state: SpecState) -> bool +// Check if state is terminal (no further execution) +pub fn spec_state_is_terminal(state: SpecState) bool { + return state == SpecState.exited or state == SpecState.error; +} + +// spec_state_can_speculate(state: SpecState) -> bool +// Check if can start speculation +pub fn spec_state_can_speculate(state: SpecState) -> bool { + return state == SpecState.running or state == SpecState.recovering; +} + +// ============================================================================ +// Exit Functions +// ============================================================================ + +// spec_exit_success(depth: u8, checkpoint: u16) -> SpecExit +// Create successful exit +pub fn spec_exit_success(depth: u8, checkpoint: u16) -> SpecExit { + return SpecExit { + .reason = ExitReason.success, + .depth = depth, + .checkpoint_addr = checkpoint, + .recovery_possible = true, + }; +} + +// spec_exit_fail(reason: ExitReason, depth: u8, checkpoint: u16, can_recover: bool) -> SpecExit +// Create failure exit +pub fn spec_exit_fail(reason: ExitReason, depth: u8, checkpoint: u16, can_recover: bool) -> SpecExit { + return SpecExit { + .reason = reason, + .depth = depth, + .checkpoint_addr = checkpoint, + .recovery_possible = can_recover, + }; +} + +// spec_exit_timeout(depth: u8, checkpoint: u16) -> SpecExit +// Create timeout exit +pub fn spec_exit_timeout(depth: u8, checkpoint: u16) -> SpecExit { + return spec_exit_fail(ExitReason.timeout, depth, checkpoint, true); +} + +// spec_exit_exception(depth: u8, checkpoint: u16) -> SpecExit +// Create exception exit +pub fn spec_exit_exception(depth: u8, checkpoint: u16) -> SpecExit { + return spec_exit_fail(ExitReason.exception, depth, checkpoint, false); +} + +// spec_exit_resource(depth: u8, checkpoint: u16) -> SpecExit +// Create resource exit +pub fn spec_exit_resource(depth: u8, checkpoint: u16) -> SpecExit { + return spec_exit_fail(ExitReason.resource, depth, checkpoint, true); +} + +// spec_exit_abort(depth: u8) -> SpecExit +// Create abort exit (no recovery) +pub fn spec_exit_abort(depth: u8) -> SpecExit { + return SpecExit { + .reason = ExitReason.abort, + .depth = depth, + .checkpoint_addr = 0, + .recovery_possible = false, + }; +} + +// spec_exit_recover(depth: u8, checkpoint: u16) -> SpecExit +// Create recover exit +pub fn spec_exit_recover(depth: u8, checkpoint: u16) -> SpecExit { + return SpecExit { + .reason = ExitReason.recover, + .depth = depth, + .checkpoint_addr = checkpoint, + .recovery_possible = true, + }; +} + +// ============================================================================ +// Spec Context Functions +// ============================================================================ + +// spec_context_init() -> SpecContext +// Initialize speculation context +pub fn spec_context_init() -> SpecContext { + return SpecContext { + .state = SpecState.running, + .current_depth = 0, + .spec_count = 0, + .exit_count = 0, + }; +} + +// spec_context_enter_speculation(context: SpecContext, depth_limit: u8) -> SpecContext +// Enter speculation +pub fn spec_context_enter_speculation(context: SpecContext, depth_limit: u8) -> SpecContext { + if (not spec_state_can_speculate(context.state)) { + return context; + } + if (context.current_depth >= depth_limit) { + return context; + } + + return SpecContext { + .state = SpecState.speculating, + .current_depth = context.current_depth + 1, + .spec_count = context.spec_count + 1, + .exit_count = context.exit_count, + }; +} + +// spec_context_exit(context: SpecContext, exit: SpecExit) -> SpecContext +// Handle exit from speculation +pub fn spec_context_exit(context: SpecContext, exit: SpecExit) -> SpecContext { + var new_state = context.state; + + if (exit.reason == ExitReason.success or exit.reason == ExitReason.recover) { + new_state = SpecState.exiting; + } else if (exit.recovery_possible) { + new_state = SpecState.recovering; + } else { + new_state = SpecState.error; + } + + return SpecContext { + .state = new_state, + .current_depth = if (new_state == SpecState.recovering) exit.depth else context.current_depth, + .spec_count = context.spec_count + 1, + .exit_count = context.exit_count + 1, + }; +} + +// spec_context_complete(context: SpecContext) -> SpecContext +// Complete speculation +pub fn spec_context_complete(context: SpecContext) -> SpecContext { + return SpecContext { + .state = SpecState.exited, + .current_depth = 0, + .spec_count = context.spec_count + 1, + .exit_count = context.exit_count, + }; +} + +// spec_context_check_timeout(context: SpecContext, config: SpecConfig, current_cycle: u32) -> bool +// Check for timeout +pub fn spec_context_check_timeout(context: SpecContext, config: SpecConfig, current_cycle: u32) -> bool { + if (config.timeout_cycles == 0) { + return false; + } + return (context.spec_count * 100) >= config.timeout_cycles; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_spec_exit(reason: u8, depth: u8, checkpoint: u16) -> u32 +// Encode speculative exit instruction +pub fn encode_spec_exit(reason: u8, depth: u8, checkpoint: u16) u32 { + // Format: [OP:8][REASON:3][DEPTH:5][CHECKPOINT:16] + const op : u32 = @as(u32, OP_SPEC_EXIT) << 24; + const reason_field : u32 = @as(u32, reason & 0x07) << 21; + const depth_field : u32 = @as(u32, depth & 0x1F) << 16; + const checkpoint_field : u32 = @as(u32, checkpoint & 0xFFFF); + return op | reason_field | depth_field | checkpoint_field; +} + +// decode_spec_exit(encoded: u32) -> struct { reason: u8, depth: u8, checkpoint: u16 } +// Decode speculative exit instruction +pub fn decode_spec_exit(encoded: u32) -> struct { reason: u8, depth: u8, checkpoint: u16 } { + const reason : u8 = @as(u8, @truncate((encoded >> 21) & 0x07)); + const depth : u8 = @as(u8, @truncate((encoded >> 16) & 0x1F)); + const checkpoint : u16 = @as(u16, @truncate(encoded & 0xFFFF)); + return .{ .reason = reason, .depth = depth, .checkpoint = checkpoint }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "max_nesting_depth_eight" { + try std.testing.expect(MAX_NESTING_DEPTH == 8); +} + +test "exit_reason_constants" { + try std.testing.expect(EXIT_SUCCESS == 0); + try std.testing.expect(EXIT_ABORT == 5); + try std.testing.expect(EXIT_RECOVER == 6); +} + +test "state_constants" { + try std.testing.expect(STATE_RUNNING == 0); + try std.testing.expect(STATE_EXITED == 3); + try std.testing.expect(STATE_ERROR == 5); +} + +test "spec_state_is_running_true" { + try std.testing.expect(spec_state_is_running(SpecState.running) == true); +} + +test "spec_state_is_running_false" { + try std.testing.expect(spec_state_is_running(SpecState.speculating) == false); +} + +test "spec_state_is_terminal_true" { + try std.testing.expect(spec_state_is_terminal(SpecState.exited) == true); + try std.testing.expect(spec_is_terminal(SpecState.error) == true); +} + +test "spec_state_is_terminal_false" { + try std.testing.expect(spec_state_is_terminal(SpecState.running) == false); +} + +test "spec_state_can_speculate_true" { + try std.testing.expect(spec_state_can_speculate(SpecState.running) == true); + try std.testing.expect(spec_state_can_speculate(SpecState.recovering) == true); +} + +test "spec_state_can_speculate_false" { + try std.testing.expect(spec_state_can_speculate(SpecState.exiting) == false); + try std.testing.expect(spec_state_can_speculate(SpecState.error) == false); +} + +test "spec_exit_success_structure" { + given exit = spec_exit_success(5, 0x1000) + try std.testing.expect(exit.reason == ExitReason.success); + try std.testing.expect(exit.depth == 5); + try std.testing.expect(exit.recovery_possible == true); +} + +test "spec_exit_fail_structure" { + given exit = spec_exit_fail(ExitReason.exception, 3, 0x2000, true) + try std.testing.expect(exit.reason == ExitReason.exception); + try std.testing.expect(exit.recovery_possible == true); +} + +test "spec_exit_abort_no_recovery" { + given exit = spec_exit_abort(5) + try std.testing.expect(exit.reason == ExitReason.abort); + try std.testing.expect(exit.recovery_possible == false); +} + +test "spec_exit_recover_structure" { + given exit = spec_exit_recover(3, 0x3000) + try std.testing.expect(exit.reason == ExitReason.recover); + try std.testing.expect(exit.recovery_possible == true); +} + +test "spec_context_init_structure" { + given context = spec_context_init() + try std.testing.expect(context.state == SpecState.running); + try std.testing.expect(context.current_depth == 0); + try std.testing.expect(context.spec_count == 0); +} + +test "spec_context_enter_speculation" { + given context = spec_context_init() + try std.testing.expect(result = spec_context_enter_speculation(context, 8)); + try std.testing.expect(result.state == SpecState.speculating); + try std.testing.expect(result.current_depth == 1); +} + +test "spec_context_enter_depth_limit" { + given context = spec_context_init() + try std.testing.expect(deep = spec_context_enter_speculation(spec_context_enter_speculation(context, 8), 8)); + try std.testing.expect(deep.current_depth == 2); +} + +test "spec_context_enter_depth_limit_exceeded" { + given context = SpecContext{.state = SpecState.running, .current_depth = 8, .spec_count = 0, .exit_count = 0} + try std.testing.expect(result = spec_context_enter_speculation(context, 8)); + try std.testing.expect(result.current_depth == 8); +} + +test "spec_context_exit_success" { + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_success(5, 0x1000)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(result.state == SpecState.exiting); +} + +test "spec_context_exit_fail_recover" { + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_fail(ExitReason.exception, 3, 0x2000, true)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(result.state == SpecState.recovering); +} + +test "spec_context_exit_fail_no_recover" { + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_fail(ExitReason.abort, 3, 0, false)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(result.state == SpecState.error); +} + +test "spec_context_complete" { + given context = spec_context_init() + try std.testing.expect(result = spec_context_complete(context)); + try std.testing.expect(result.state == SpecState.exited); + try std.testing.expect(result.current_depth == 0); +} + +test "spec_context_check_timeout_true" { + given context = SpecContext{.state = SpecState.speculating, .current_depth = 3, .spec_count = 10, .exit_count = 0} + try std.testing.expect(config = SpecConfig{.max_depth = 8, .timeout_cycles = 500, .enable_recovery = true, .checkpoint_interval = 1}); + try std.testing.expect(spec_context_check_timeout(context, config, 1000) == true); +} + +test "spec_context_check_timeout_false" { + given context = SpecContext{.state = SpecState.speculating, .current_depth = 3, .spec_count = 1, .exit_count = 0} + try std.testing.expect(config = SpecConfig{.max_depth = 8, .timeout_cycles = 500, .enable_recovery = true, .checkpoint_interval = 1}); + try std.testing.expect(spec_context_check_timeout(context, config, 1000) == false); +} + +test "encode_spec_exit" { + given encoded = encode_spec_exit(1, 5, 0x1234) + try std.testing.expect((encoded >> 24) == OP_SPEC_EXIT); +} + +test "decode_spec_exit" { + given decoded = decode_spec_exit(0xEB081234) + try std.testing.expect(decoded.reason == 1); + try std.testing.expect(decoded.depth == 5); + try std.testing.expect(decoded.checkpoint == 0x1234); +} + +test "opcode_constant" { + try std.testing.expect(OP_SPEC_EXIT == 0xEB); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant max_nesting_depth_eight + assert MAX_NESTING_DEPTH == 8 + +invariant exit_reason_values + try std.testing.expect(EXIT_SUCCESS >= 0 and EXIT_MAX <= 7); + +invariant state_values + try std.testing.expect(STATE_RUNNING >= 0 and STATE_ERROR <= 5); + +invariant spec_exit_success_recoverable + given exit = spec_exit_success(0, 0x1000) + try std.testing.expect(exit.recovery_possible == true); + +invariant spec_exit_abort_not_recoverable + given exit = spec_exit_abort(5) + try std.testing.expect(exit.recovery_possible == false); + +invariant spec_exit_depth_bound + try std.testing.expect(spec_exit_abort(0).depth == 0); + try std.testing.expect(spec_exit_abort(MAX_NESTING_DEPTH).depth == MAX_NESTING_DEPTH); + +invariant spec_context_init_zero_depth + given context = spec_context_init() + try std.testing.expect(context.current_depth == 0); + +invariant spec_context_enter_increases_depth + given context = spec_context_init() + try std.testing.expect(result = spec_context_enter_speculation(context, 8)); + try std.testing.expect(result.current_depth == context.current_depth + 1); + +invariant spec_context_exit_preserves_reason + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_fail(ExitReason.exception, 3, 0x2000, true)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(exit.reason == ExitReason.exception); + +invariant spec_context_complete_zero_depth + given context = SpecContext{.state = SpecState.running, .current_depth = 5, .spec_count = 10, .exit_count = 0} + try std.testing.expect(result = spec_context_complete(context)); + try std.testing.expect(result.current_depth == 0); + +invariant spec_context_complete_exited_state + given context = spec_context_init() + try std.testing.expect(result = spec_context_complete(context)); + try std.testing.expect(result.state == SpecState.exited); + +invariant spec_context_check_timeout_no_timeout + given config = SpecConfig{.max_depth = 8, .timeout_cycles = 0, .enable_recovery = true, .checkpoint_interval = 1} + try std.testing.expect(context = SpecContext{.state = SpecState.speculating, .current_depth = 3, .spec_count = 1000, .exit_count = 0}); + try std.testing.expect(spec_context_check_timeout(context, config, 1000) == false); + +invariant spec_context_enter_speculate_requires_running_or_recovering + given context = SpecContext{.state = SpecState.exiting, .current_depth = 0, .spec_count = 0, .exit_count = 0} + try std.testing.expect(spec_context_enter_speculation(context, 8).current_depth == 0); + +invariant spec_context_exit_increases_exit_count + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_success(0, 0x1000)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(result.exit_count == context.exit_count + 1); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench spec_exit_success_latency + measure: nanoseconds to spec_exit_success(5, 0x1000) + target: < 30ns + +bench spec_exit_fail_latency + measure: nanoseconds to spec_exit_fail(ExitReason.exception, 3, 0x2000, true) + target: < 30ns + +bench spec_exit_abort_latency + measure: nanoseconds to spec_exit_abort(5) + target: < 30ns + +bench spec_context_enter_speculation_latency + measure: nanoseconds to spec_context_enter_speculation(spec_context_init(), 8) + target: < 50ns + +bench spec_context_exit_latency + measure: nanoseconds to spec_context_exit(spec_context_init(), spec_exit_success(5, 0x1000)) + target: < 50ns + +bench spec_context_check_timeout_latency + measure: nanoseconds to spec_context_check_timeout(.{.state = SpecState.speculating, .current_depth = 3, .spec_count = 10, .exit_count = 0}, .{.max_depth = 8, .timeout_cycles = 500, .enable_recovery = true, .checkpoint_interval = 1}, 1000) + target: < 30ns + +bench encode_spec_exit_latency + measure: nanoseconds to encode_spec_exit(1, 5, 0x1234) + target: < 30ns + +bench decode_spec_exit_latency + measure: nanoseconds to decode_spec_exit(0xEB081234) + target: < 30ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/stoch_round.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/stoch_round.t27 new file mode 100644 index 0000000000..7bb7a55f45 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/stoch_round.t27 @@ -0,0 +1,575 @@ +// SPDX-License-Identifier: Apache-2.0 +; stoch_round.t27 — Sacred Opcode 0xE9: Stochastic Rounding +; Hardware stochastic rounding for quantization +; φ² + 1/φ² = 3 | TRINITY + +module sacred-stoch_round; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_STOCH_ROUND : u8 = 0xE9; + +pub const RANDOM_BITS : u8 = 8; +pub const ROUND_MODE_NEAREST : u8 = 0; +pub const ROUND_MODE_STOCHASTIC : u8 = 1; +pub const ROUND_MODE_TRUNCATE : u8 = 2; +pub const ROUND_MODE_CEIL : u8 = 3; + +pub const INT4_BITS : u8 = 4; +pub const INT8_BITS : u8 = 8; + +pub const INT4_MAX : i8 = 7; +pub const INT4_MIN : i8 = -8; +pub const INT8_MAX : i8 = 127; +pub const INT8_MIN : i8 = -128; + +// ============================================================================ +// Types +// ============================================================================ + +pub const RoundConfig = struct { + mode : u8, + target_bits : u8, + seed : u16, +} + +pub const RoundResult = struct { + value : i16, + original : i16, + rounded : bool, + overflow : bool, +} + +pub const RngState = struct { + state : u16, +} + +// ============================================================================ +// RNG Functions (XorShift LFSR) +// ============================================================================ + +// rng_init(seed: u16) -> RngState +// Initialize RNG with seed +pub fn rng_init(seed: u16) RngState { + return RngState{.state = if (seed == 0) 1 else seed}; +} + +// rng_next(state: RngState) -> struct { state: RngState, value: u8 } +// Get next random byte +pub fn rng_next(state: RngState) -> struct { state: RngState, value: u8 } { + var s = state.state; + s ^= s >> 7; + s ^= s << 9; + s ^= s >> 8; + return .{ .state = RngState{.state = s}, .value = @as(u8, @truncate(s)) }; +} + +// rng_next_range(state: RngState, max: u8) -> struct { state: RngState, value: u8 } +// Get next random value in range [0, max) +pub fn rng_next_range(state: RngState, max: u8) -> struct { state: RngState, value: u8 } { + const next_result = rng_next(state); + const scaled = (@as(u16, next_result.value) * @as(u16, max)) >> 8; + return .{ .state = next_result.state, .value = @as(u8, @truncate(scaled)) }; +} + +// ============================================================================ +// Rounding Functions +// ============================================================================ + +// round_nearest(value: i16, bits: u8) -> RoundResult +// Round to nearest +pub fn round_nearest(value: i16, bits: u8) -> RoundResult { + const shift = 16 - bits; + var result : i16 = 0; + + if (shift >= 0) { + const abs_val = if (value < 0) -value else value; + const rounding = (abs_val + (1 << (shift - 1))) >> shift; + result = if (value < 0) -rounding else rounding; + } else { + result = value << (-shift); + } + + // Check for overflow + var overflow = false; + const max_val : i16 = (@as(i16, 1) << (bits - 1)) - 1; + const min_val : i16 = -(@as(i16, 1) << (bits - 1)); + + if (result > max_val) { + result = max_val; + overflow = true; + } else if (result < min_val) { + result = min_val; + overflow = true; + } + + return RoundResult { + .value = result, + .original = value, + .rounded = result != value, + .overflow = overflow, + }; +} + +// round_stochastic(value: i16, bits: u8, state: RngState) -> struct { result: RoundResult, new_state: RngState } +// Stochastic rounding +pub fn round_stochastic(value: i16, bits: u8, state: RngState) -> struct { result: RoundResult, new_state: RngState } { + const shift = 16 - bits; + var result : i16 = 0; + + if (shift <= 0) { + result = value << (-shift); + return .{ .result = RoundResult{.value = result, .original = value, .rounded = false, .overflow = false}, .new_state = state }; + } + + const abs_val = if (value < 0) -value else value; + const integer_part = abs_val >> shift; + const fractional_part = abs_val & ((@as(i16, 1) << shift) - 1); + + // Generate random value and compare + const max_frac = @as(i16, 1) << shift; + const frac_scaled : i16 = @as(i16, @intCast(fractional_part)) * 256; + const threshold = (frac_scaled + (max_frac / 2)) / max_frac; + + const rng_result = rng_next(state); + const rand_val : i16 = @as(i16, @intCast(rng_result.value)); + + var rounded_int = integer_part; + if (rand_val >= threshold and fractional_part != 0) { + rounded_int += 1; + } + + result = if (value < 0) -rounded_int else rounded_int; + + // Check for overflow + var overflow = false; + const max_val : i16 = (@as(i16, 1) << (bits - 1)) - 1; + const min_val : i16 = -(@as(i16, 1) << (bits - 1)); + + if (result > max_val) { + result = max_val; + overflow = true; + } else if (result < min_val) { + result = min_val; + overflow = true; + } + + return .{ .result = RoundResult{.value = result, .original = value, .rounded = result != (abs_val >> shift), .overflow = overflow}, .new_state = rng_result.state }; +} + +// round_truncate(value: i16, bits: u8) -> RoundResult +// Truncate (round toward zero) +pub fn round_truncate(value: i16, bits: u8) -> RoundResult { + const shift = 16 - bits; + var result : i16 = 0; + + if (shift >= 0) { + result = value >> shift; + } else { + result = value << (-shift); + } + + // Check for overflow + var overflow = false; + const max_val : i16 = (@as(i16, 1) << (bits - 1)) - 1; + const min_val : i16 = -(@as(i16, 1) << (bits - 1)); + + if (result > max_val) { + result = max_val; + overflow = true; + } else if (result < min_val) { + result = min_val; + overflow = true; + } + + return RoundResult { + .value = result, + .original = value, + .rounded = result != value, + .overflow = overflow, + }; +} + +// round_ceil(value: i16, bits: u8) -> RoundResult +// Ceil (round toward positive infinity) +pub fn round_ceil(value: i16, bits: u8) -> RoundResult { + const shift = 16 - bits; + var result : i16 = 0; + + if (shift >= 0) { + const abs_val = if (value < 0) -value else value; + const frac_part = abs_val & ((@as(i16, 1) << shift) - 1); + var base = abs_val >> shift; + if (value >= 0 and frac_part != 0) { + base += 1; + } + result = if (value < 0) -base else base; + } else { + result = value << (-shift); + } + + // Check for overflow + var overflow = false; + const max_val : i16 = (@as(i16, 1) << (bits - 1)) - 1; + const min_val : i16 = -(@as(i16, 1) << (bits - 1)); + + if (result > max_val) { + result = max_val; + overflow = true; + } else if (result < min_val) { + result = min_val; + overflow = true; + } + + return RoundResult { + .value = result, + .original = value, + .rounded = true, + .overflow = overflow, + }; +} + +// ============================================================================ +// Config-Based Rounding +// ============================================================================ + +// round_with_config(value: i16, config: RoundConfig, state: RngState) -> struct { result: RoundResult, new_state: RngState } +// Round using config +pub fn round_with_config(value: i16, config: RoundConfig, state: RngState) -> struct { result: RoundResult, new_state: RngState } { + switch (config.mode) { + ROUND_MODE_NEAREST => { + return .{ .result = round_nearest(value, config.target_bits), .new_state = state }; + }, + ROUND_MODE_STOCHASTIC => { + return round_stochastic(value, config.target_bits, state); + }, + ROUND_MODE_TRUNCATE => { + return .{ .result = round_truncate(value, config.target_bits), .new_state = state }; + }, + ROUND_MODE_CEIL => { + return .{ .result = round_ceil(value, config.target_bits), .new_state = state }; + }, + else => { + return .{ .result = RoundResult{.value = 0, .original = value, .rounded = false, .overflow = true}, .new_state = state }; + }, + } +} + +// ============================================================================ +// Vector Rounding +// ============================================================================ + +// round_vector_nearest(values: [8]i16, bits: u8) -> [8]RoundResult +// Round vector to nearest +pub fn round_vector_nearest(values: [8]i16, bits: u8) [8]RoundResult { + var results : [8]RoundResult = undefined; + for (values, 0..) |val, i| { + results[i] = round_nearest(val, bits); + } + return results; +} + +// round_vector_stochastic(values: [8]i16, bits: u8, state: RngState) -> struct { results: [8]RoundResult, new_state: RngState } +// Round vector stochastically +pub fn round_vector_stochastic(values: [8]i16, bits: u8, state: RngState) -> struct { results: [8]RoundResult, new_state: RngState } { + var results : [8]RoundResult = undefined; + var current_state = state; + + for (values, 0..) |val, i| { + const round_result = round_stochastic(val, bits, current_state); + results[i] = round_result.result; + current_state = round_result.new_state; + } + + return .{ .results = results, .new_state = current_state }; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_stoch_round(mode: u8, target_bits: u8, seed: u8) -> u16 +// Encode stochastic rounding instruction +pub fn encode_stoch_round(mode: u8, target_bits: u8, seed: u8) u16 { + // Format: [OP:8][MODE:2][BITS:4][SEED:2] + const op : u16 = @as(u16, OP_STOCH_ROUND) << 8; + const mode_field : u16 = @as(u16, mode & 0x03) << 6; + const bits_field : u16 = @as(u16, target_bits & 0x0F) << 2; + const seed_field : u16 = @as(u16, seed & 0x03); + return op | mode_field | bits_field | seed_field; +} + +// decode_stoch_round(encoded: u16) -> struct { mode: u8, target_bits: u8, seed: u8 } +// Decode stochastic rounding instruction +pub fn decode_stoch_round(encoded: u16) struct { mode: u8, target_bits: u8, seed: u8 } { + const mode : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const target_bits : u8 = @as(u8, @truncate((encoded >> 2) & 0x0F)); + const seed : u8 = @as(u8, @truncate(encoded & 0x03)); + return .{ .mode = mode, .target_bits = target_bits, .seed = seed }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "round_mode_constants" { + try std.testing.expect(ROUND_MODE_NEAREST == 0); + try std.testing.expect(ROUND_MODE_STOCHASTIC == 1); + try std.testing.expect(ROUND_MODE_TRUNCATE == 2); + try std.testing.expect(ROUND_MODE_CEIL == 3); +} + +test "int4_bounds" { + try std.testing.expect(INT4_MAX == 7 and INT4_MIN == -8); +} + +test "int8_bounds" { + try std.testing.expect(INT8_MAX == 127 and INT8_MIN == -128); +} + +test "round_nearest_basic" { + given result = round_nearest(10, 4) + try std.testing.expect(result.value == 1); +} + +test "round_nearest_negative" { + given result = round_nearest(-10, 4) + try std.testing.expect(result.value == -1); +} + +test "round_nearest_overflow" { + given result = round_nearest(0x7FFF, 8) + try std.testing.expect(result.value == INT8_MAX); + try std.testing.expect(result.overflow == true); +} + +test "round_nearest_underflow" { + given result = round_nearest(-0x8000, 8) + try std.testing.expect(result.value == INT8_MIN); + try std.testing.expect(result.overflow == true); +} + +test "round_nearest_no_change" { + given result = round_nearest(0x1000, 8) + try std.testing.expect(result.value == 0x10); + try std.testing.expect(result.rounded == false); +} + +test "round_truncate_basic" { + given result = round_truncate(10, 4) + try std.testing.expect(result.value == 0); +} + +test "round_truncate_negative" { + given result = round_truncate(-10, 4) + try std.testing.expect(result.value == 0); +} + +test "round_truncate_fractional" { + given result = round_truncate(15, 4) + try std.testing.expect(result.value == 0); +} + +test "round_ceil_basic" { + given result = round_ceil(10, 4) + try std.testing.expect(result.value == 1); +} + +test "round_ceil_negative" { + given result = round_ceil(-10, 4) + try std.testing.expect(result.value == 0); +} + +test "round_ceil_fractional" { + given result = round_ceil(1, 4) + try std.testing.expect(result.value == 1); +} + +test "round_stochastic_seeded" { + given state = RngState{.state = 0x1234} + try std.testing.expect(round1 = round_stochastic(10, 4, state)); + try std.testing.expect(state2 = RngState{.state = 0x1234}); + try std.testing.expect(round2 = round_stochastic(10, 4, state2)); + try std.testing.expect(round1.result.value == round2.result.value); + try std.testing.expect(round1.new_state == round2.new_state); +} + +test "round_stochastic_reproducible" { + given state = RngState{.state = 0x1234} + try std.testing.expect(round1 = round_stochastic(10, 4, state)); + try std.testing.expect(round2 = round_stochastic(10, 4, round1.new_state)); + try std.testing.expect(round2.result.value != round1.result.value or round2.new_state != round1.new_state); +} + +test "round_vector_nearest" { + given results = round_vector_nearest([_]i16{10, 20, 30, 40, 50, 60, 70, 80}, 4) + try std.testing.expect(results[0].value == 1); + try std.testing.expect(results[7].value == 5); +} + +test "round_vector_stochastic" { + given state = RngState{.state = 0x1234} + try std.testing.expect(round_result = round_vector_stochastic([_]i16{10, 20, 30, 40}, 4, state)); + try std.testing.expect(round_result.new_state.state != 0x1234); +} + +test "rng_init_default" { + given state = rng_init(0) + try std.testing.expect(state.state == 1); +} + +test "rng_init_with_seed" { + given state = rng_init(0x1234) + try std.testing.expect(state.state == 0x1234); +} + +test "rng_next_range" { + given state = rng_init(0x1234) + try std.testing.expect(result = rng_next_range(state, 100)); + try std.testing.expect(result.value <= 100); +} + +test "round_with_config_nearest" { + given config = RoundConfig{.mode = ROUND_MODE_NEAREST, .target_bits = 8, .seed = 0} + try std.testing.expect(state = RngState{.state = 1}); + try std.testing.expect(result = round_with_config(10, config, state)); + try std.testing.expect(result.result.value == 10); +} + +test "round_with_config_stochastic" { + given config = RoundConfig{.mode = ROUND_MODE_STOCHASTIC, .target_bits = 4, .seed = 0} + try std.testing.expect(state = RngState{.state = 1}); + try std.testing.expect(result = round_with_config(10, config, state)); + try std.testing.expect(result.result.value <= INT4_MAX); +} + +test "encode_stoch_round" { + given encoded = encode_stoch_round(1, 4, 2) + try std.testing.expect((encoded >> 8) == OP_STOCH_ROUND); +} + +test "decode_stoch_round" { + given decoded = decode_stoch_round(0xE932) + try std.testing.expect(decoded.mode == 1); + try std.testing.expect(decoded.target_bits == 4); + try std.testing.expect(decoded.seed == 2); +} + +test "opcode_constant" { + try std.testing.expect(OP_STOCH_ROUND == 0xE9); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant random_bits_eight + assert RANDOM_BITS == 8 + +invariant int4_bits_four + assert INT4_BITS == 4 + +invariant int8_bits_eight + assert INT8_BITS == 8 + +invariant int4_max_min_relation + try std.testing.expect(INT4_MAX == -INT4_MIN - 1); + +invariant int8_max_min_relation + try std.testing.expect(INT8_MAX == -INT8_MIN - 1); + +invariant round_mode_values + try std.testing.expect(ROUND_MODE_NEAREST >= 0 and ROUND_MODE_CEIL <= 3); + +invariant round_nearest_range + given result = round_nearest(0, 4) + try std.testing.expect(result.value >= -8 and result.value <= 7); + +invariant round_truncate_range + given result = round_truncate(0, 4) + try std.testing.expect(result.value >= -8 and result.value <= 7); + +invariant round_ceil_range + given result = round_ceil(0, 4) + try std.testing.expect(result.value >= -8 and result.value <= 7); + +invariant round_stochastic_range + given state = RngState{.state = 1} + try std.testing.expect(result = round_stochastic(0, 4, state)); + try std.testing.expect(result.result.value >= INT4_MIN and result.result.value <= INT4_MAX); + +invariant round_nearest_preserves_sign + given result_pos = round_nearest(100, 4) + try std.testing.expect(result_neg = round_nearest(-100, 4)); + try std.testing.expect(result_pos.value >= 0 and result_neg.value <= 0); + +invariant round_truncate_preserves_sign + given result_pos = round_truncate(100, 4) + try std.testing.expect(result_neg = round_truncate(-100, 4)); + try std.testing.expect(result_pos.value >= 0 and result_neg.value <= 0); + +invariant round_ceil_non_negative + given result = round_ceil(0, 4) + try std.testing.expect(result.value >= 0); + +invariant rng_next_state_changes + given state = RngState{.state = 0x1234} + try std.testing.expect(result = rng_next(state)); + try std.testing.expect(result.state.state != state.state); + +invariant rng_next_range_value_bound + given state = RngState{.state = 1} + try std.testing.expect(result = rng_next_range(state, 100)); + try std.testing.expect(result.value <= 100); + +invariant round_vector_nearest_length + given results = round_vector_nearest([_]i16{0} ** 8, 4) + try std.testing.expect(results.len == 8); + +invariant round_vector_stochastic_length + given state = RngState{.state = 1} + try std.testing.expect(result = round_vector_stochastic([_]i16{0} ** 8, 4, state)); + try std.testing.expect(result.results.len == 8); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench round_nearest_latency + measure: nanoseconds to round_nearest(100, 4) + target: < 50ns + +bench round_stochastic_latency + measure: nanoseconds to round_stochastic(100, 4, RngState{.state = 1}) + target: < 100ns + +bench round_truncate_latency + measure: nanoseconds to round_truncate(100, 4) + target: < 50ns + +bench round_ceil_latency + measure: nanoseconds to round_ceil(100, 4) + target: < 50ns + +bench rng_next_latency + measure: nanoseconds to rng_next(RngState{.state = 0x1234}) + target: < 30ns + +bench round_vector_nearest_latency + measure: nanoseconds to round_vector_nearest([_]i16{10} ** 8, 4) + target: < 200ns + +bench round_vector_stochastic_latency + measure: nanoseconds to round_vector_stochastic([_]i16{10} ** 8, 4, RngState{.state = 1}) + target: < 500ns + +bench encode_stoch_round_latency + measure: nanoseconds to encode_stoch_round(1, 4, 2) + target: < 20ns + +bench decode_stoch_round_latency + measure: nanoseconds to decode_stoch_round(0xE932) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/fpga/subth_clk.t27 b/apps/website/public/t27/files/chips/euler/specs/fpga/subth_clk.t27 new file mode 100644 index 0000000000..4d3fdd57cc --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/fpga/subth_clk.t27 @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: Apache-2.0 +; subth_clk.t27 — Sacred Opcode 0xE5: Sub-threshold Clock Gating +; Hardware for sub-threshold clock gating for power reduction +; φ² + 1/φ² = 3 | TRINITY + +module sacred-subth_clk; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_SUBTH_CLK : u8 = 0xE5; + +pub const NUM_CLK_DOMAINS : u8 = 8; +pub const SUBTH_THRESHOLD_MV : u16 = 400; // 400mV threshold +pub const NORMAL_VOLTAGE_MV : u16 = 1100; // 1.1V normal + +pub const CLK_GATE_OFF : u8 = 0; +pub const CLK_GATE_ON : u8 = 1; +pub const CLK_GATE_AUTO : u8 = 2; + +pub const POWER_STATE_ACTIVE : u8 = 0; +pub const POWER_STATE_IDLE : u8 = 1; +pub const POWER_STATE_SLEEP : u8 = 2; +pub const POWER_STATE_DEEP_SLEEP : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const ClkGateMode = enum(u8) { + off = CLK_GATE_OFF, + on = CLK_GATE_ON, + auto = CLK_GATE_AUTO, +} + +pub const PowerState = enum(u8) { + active = POWER_STATE_ACTIVE, + idle = POWER_STATE_IDLE, + sleep = POWER_STATE_SLEEP, + deep_sleep = POWER_STATE_DEEP_SLEEP, +} + +pub const ClkDomain = struct { + id : u8, + enabled : bool, + gate_mode : ClkGateMode, + activity_count : u16, + last_active_cycle : u32, +} + +pub const SubthConfig = struct { + threshold_mv : u16, + hysteresis_mv : u16, + enable_subth : bool, + transition_cycles : u8, // Cycles for voltage transition +} + +pub const PowerMetrics = struct { + current_voltage_mv : u16, + current_power_mw : u16, + activity_ratio : u8, // 0-100% + clock_gated_domains : u8, +} + +// ============================================================================ +// Clk Domain Functions +// ============================================================================ + +// clk_domain_enabled(domain: ClkDomain) -> bool +// Check if clock domain is enabled +pub fn clk_domain_enabled(domain: ClkDomain) bool { + return domain.enabled and domain.gate_mode != ClkGateMode.off; +} + +// clk_domain_gate_active(domain: ClkDomain) -> bool +// Check if clock gating is active +pub fn clk_domain_gate_active(domain: ClkDomain) bool { + return domain.gate_mode == ClkGateMode.off or + (domain.gate_mode == ClkGateMode.auto and domain.activity_count == 0); +} + +// clk_domain_is_idle(domain: ClkDomain, current_cycle: u32, idle_threshold: u32) -> bool +// Check if domain is idle +pub fn clk_domain_is_idle(domain: ClkDomain, current_cycle: u32, idle_threshold: u32) bool { + return domain.activity_count == 0 and + (current_cycle - domain.last_active_cycle) > idle_threshold; +} + +// clk_domain_pulse(domain: ClkDomain, current_cycle: u32) -> ClkDomain +// Record activity pulse on domain +pub fn clk_domain_pulse(domain: ClkDomain, current_cycle: u32) ClkDomain { + return ClkDomain { + .id = domain.id, + .enabled = domain.enabled, + .gate_mode = domain.gate_mode, + .activity_count = domain.activity_count + 1, + .last_active_cycle = current_cycle, + }; +} + +// clk_domain_decay(domain: ClkDomain) -> ClkDomain +// Decay activity count +pub fn clk_domain_decay(domain: ClkDomain) ClkDomain { + const new_count = if (domain.activity_count > 0) domain.activity_count - 1 else 0; + return ClkDomain { + .id = domain.id, + .enabled = domain.enabled, + .gate_mode = domain.gate_mode, + .activity_count = new_count, + .last_active_cycle = domain.last_active_cycle, + }; +} + +// ============================================================================ +// Sub-threshold Functions +// ============================================================================ + +// is_subthreshold(voltage_mv: u16, config: SubthConfig) -> bool +// Check if voltage is in sub-threshold region +pub fn is_subthreshold(voltage_mv: u16, config: SubthConfig) bool { + if (not config.enable_subth) { + return false; + } + return voltage_mv < config.threshold_mv; +} + +// voltage_within_range(voltage_mv: u16, target_mv: u16, tolerance_mv: u16) -> bool +// Check if voltage is within tolerance of target +pub fn voltage_within_range(voltage_mv: u16, target_mv: u16, tolerance_mv: u16) bool { + if (voltage_mv >= target_mv) { + return (voltage_mv - target_mv) <= tolerance_mv; + } + return (target_mv - voltage_mv) <= tolerance_mv; +} + +// subth_power_estimate(voltage_mv: u16) -> u16 +// Estimate power at given voltage (quadratic scaling) +pub fn subth_power_estimate(voltage_mv: u16) -> u16 { + // Power scales with V^2, normalize to 1.1V = 100mW (arbitrary baseline) + const ratio = @as(f32, @floatFromInt(voltage_mv)) / 1100.0; + const power_baseline : f32 = 100.0; + const estimated : f32 = power_baseline * ratio * ratio; + return @as(u16, @intFromFloat(@floor(estimated))); +} + +// power_savings_ratio(normal_mv: u16, subth_mv: u16) -> u8 +// Calculate power savings ratio (0-100%) +pub fn power_savings_ratio(normal_mv: u16, subth_mv: u16) -> u8 { + const normal_power = subth_power_estimate(normal_mv); + const subth_power = subth_power_estimate(subth_mv); + + if (normal_power == 0) { + return 0; + } + + const saved = normal_power - subth_power; + const ratio = (saved * 100) / normal_power; + + return @as(u8, @truncate(ratio)); +} + +// ============================================================================ +// Power State Functions +// ============================================================================ + +// power_state_can_gate(state: PowerState) -> bool +// Check if power state allows clock gating +pub fn power_state_can_gate(state: PowerState) bool { + return state != PowerState.active; +} + +// power_state_deeper(a: PowerState, b: PowerState) -> PowerState +// Return deeper power state +pub fn power_state_deeper(a: PowerState, b: PowerState) PowerState { + if (@intFromEnum(a) > @intFromEnum(b)) { + return a; + } + return b; +} + +// power_state_sleep_ok(domains: [NUM_CLK_DOMAINS]ClkDomain, current_cycle: u32) -> bool +// Check if all domains can go to sleep +pub fn power_state_sleep_ok(domains: [NUM_CLK_DOMAINS]ClkDomain, current_cycle: u32) bool { + for (domains) |domain| { + if (not clk_domain_is_idle(domain, current_cycle, 100)) { + return false; + } + } + return true; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_subth_clk(domain_id: u8, mode: u8) -> u16 +// Encode sub-threshold clock instruction +pub fn encode_subth_clk(domain_id: u8, mode: u8) u16 { + // Format: [OP:8][DOMAIN:3][MODE:2][RES:3] + const op : u16 = @as(u16, OP_SUBTH_CLK) << 8; + const domain_field : u16 = @as(u16, domain_id & 0x07) << 5; + const mode_field : u16 = @as(u16, mode & 0x03) << 3; + return op | domain_field | mode_field; +} + +// decode_subth_clk(encoded: u16) -> struct { domain_id: u8, mode: u8 } +// Decode sub-threshold clock instruction +pub fn decode_subth_clk(encoded: u16) struct { domain_id: u8, mode: u8 } { + const domain_id : u8 = @as(u8, @truncate((encoded >> 5) & 0x07)); + const mode : u8 = @as(u8, @truncate((encoded >> 3) & 0x03)); + return .{ .domain_id = domain_id, .mode = mode }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "num_clk_domains_eight" { + try std.testing.expect(NUM_CLK_DOMAINS == 8); +} + +test "subth_threshold_four_hundred" { + try std.testing.expect(SUBTH_THRESHOLD_MV == 400); +} + +test "normal_voltage_eleven_hundred" { + try std.testing.expect(NORMAL_VOLTAGE_MV == 1100); +} + +test "clk_gate_mode_off" { + try std.testing.expect(@intFromEnum(ClkGateMode.off) == 0); +} + +test "clk_gate_mode_on" { + try std.testing.expect(@intFromEnum(ClkGateMode.on) == 1); +} + +test "clk_gate_mode_auto" { + try std.testing.expect(@intFromEnum(ClkGateMode.auto) == 2); +} + +test "power_state_active" { + try std.testing.expect(@intFromEnum(PowerState.active) == 0); +} + +test "power_state_deep_sleep" { + try std.testing.expect(@intFromEnum(PowerState.deep_sleep) == 3); +} + +test "clk_domain_enabled_true" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_enabled(domain) == true); +} + +test "clk_domain_enabled_false_gate_off" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.off, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_enabled(domain) == false); +} + +test "clk_domain_enabled_false_disabled" { + given domain = ClkDomain{.id = 0, .enabled = false, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_enabled(domain) == false); +} + +test "clk_domain_gate_active_off" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.off, .activity_count = 10, .last_active_cycle = 0} + try std.testing.expect(clk_domain_gate_active(domain) == true); +} + +test "clk_domain_gate_active_auto_idle" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.auto, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_gate_active(domain) == true); +} + +test "clk_domain_gate_active_auto_active" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.auto, .activity_count = 10, .last_active_cycle = 0} + try std.testing.expect(clk_domain_gate_active(domain) == false); +} + +test "clk_domain_is_idle_true" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_is_idle(domain, 200, 100) == true); +} + +test "clk_domain_is_idle_false_active" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 10, .last_active_cycle = 0} + try std.testing.expect(clk_domain_is_idle(domain, 200, 100) == false); +} + +test "clk_domain_is_idle_false_recent" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 150} + try std.testing.expect(clk_domain_is_idle(domain, 200, 100) == false); +} + +test "clk_domain_pulse" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_pulse(domain, 100)); + try std.testing.expect(result.activity_count == 1); + try std.testing.expect(result.last_active_cycle == 100); +} + +test "clk_domain_decay" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 5, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_decay(domain)); + try std.testing.expect(result.activity_count == 4); +} + +test "clk_domain_decay_zero" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_decay(domain)); + try std.testing.expect(result.activity_count == 0); +} + +test "is_subthreshold_true" { + given config = SubthConfig{.threshold_mv = 400, .hysteresis_mv = 20, .enable_subth = true, .transition_cycles = 10} + try std.testing.expect(is_subthreshold(300, config) == true); +} + +test "is_subthreshold_false_disabled" { + given config = SubthConfig{.threshold_mv = 400, .hysteresis_mv = 20, .enable_subth = false, .transition_cycles = 10} + try std.testing.expect(is_subthreshold(300, config) == false); +} + +test "is_subthreshold_false_high_voltage" { + given config = SubthConfig{.threshold_mv = 400, .hysteresis_mv = 20, .enable_subth = true, .transition_cycles = 10} + try std.testing.expect(is_subthreshold(500, config) == false); +} + +test "voltage_within_range_true" { + try std.testing.expect(voltage_within_range(1100, 1100, 50) == true); +} + +test "voltage_within_range_low" { + try std.testing.expect(voltage_within_range(1060, 1100, 50) == true); +} + +test "voltage_within_range_high" { + try std.testing.expect(voltage_within_range(1140, 1100, 50) == true); +} + +test "voltage_within_range_false" { + try std.testing.expect(voltage_within_range(1000, 1100, 50) == false); +} + +test "subth_power_estimate_normal" { + try std.testing.expect(subth_power_estimate(1100) == 100); +} + +test "subth_power_estimate_half" { + try std.testing.expect(subth_power_estimate(550) == 25); +} + +test "subth_power_estimate_quarter" { + try std.testing.expect(subth_power_estimate(275) == 6); +} + +test "power_savings_ratio_fifty_percent" { + given result = power_savings_ratio(1100, 777) + try std.testing.expect(result == 50); +} + +test "power_savings_ratio_seventy_five_percent" { + given result = power_savings_ratio(1100, 550) + try std.testing.expect(result == 75); +} + +test "power_state_can_gate_active" { + try std.testing.expect(power_state_can_gate(PowerState.active) == false); +} + +test "power_state_can_gate_idle" { + try std.testing.expect(power_state_can_gate(PowerState.idle) == true); +} + +test "power_state_can_gate_sleep" { + try std.testing.expect(power_state_can_gate(PowerState.sleep) == true); +} + +test "power_state_deeper" { + try std.testing.expect(power_state_deeper(PowerState.idle, PowerState.deep_sleep) == PowerState.deep_sleep); + try std.testing.expect(power_state_deeper(PowerState.deep_sleep, PowerState.idle) == PowerState.deep_sleep); +} + +test "power_state_sleep_ok_true" { + given domains = [_]ClkDomain{ + .{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}, + .{.id = 1, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}, + } ** 8 + try std.testing.expect(power_state_sleep_ok(domains, 200) == true); +} + +test "power_state_sleep_ok_false" { + given domains = [_]ClkDomain{ + .{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 10, .last_active_cycle = 0}, + .{.id = 1, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}, + } ** 8 + try std.testing.expect(power_state_sleep_ok(domains, 200) == false); +} + +test "encode_subth_clk" { + given encoded = encode_subth_clk(0x05, 0x02) + try std.testing.expect((encoded >> 8) == OP_SUBTH_CLK); +} + +test "decode_subth_clk" { + given decoded = decode_subth_clk(0xE5A8) + try std.testing.expect(decoded.domain_id == 0x05); + try std.testing.expect(decoded.mode == 0x02); +} + +test "opcode_constant" { + try std.testing.expect(OP_SUBTH_CLK == 0xE5); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant num_clk_domains_eight + assert NUM_CLK_DOMAINS == 8 + +invariant subth_threshold_bound + assert SUBTH_THRESHOLD_MV < NORMAL_VOLTAGE_MV + +invariant power_estimate_positive + try std.testing.expect(subth_power_estimate(100) >= 0); + +invariant power_savings_bound + try std.testing.expect(power_savings_ratio(1100, 550) <= 100); + +invariant power_state_enum_range + try std.testing.expect(@intFromEnum(PowerState.active) >= 0 and @intFromEnum(PowerState.deep_sleep) <= 3); + +invariant clk_gate_mode_enum_range + try std.testing.expect(@intFromEnum(ClkGateMode.off) >= 0 and @intFromEnum(ClkGateMode.auto) <= 2); + +invariant clk_domain_pulse_increases_activity + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_pulse(domain, 100)); + assert result.activity_count > domain.activity_count + +invariant clk_domain_decay_decreases_or_same + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 5, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_decay(domain)); + assert result.activity_count <= domain.activity_count + +invariant power_savings_symmetric + assert power_savings_ratio(1100, 550) == power_savings_ratio(550, 1100) + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench clk_domain_is_idle_latency + measure: nanoseconds to clk_domain_is_idle(.{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}, 200, 100) + target: < 50ns + +bench is_subthreshold_latency + measure: nanoseconds to is_subthreshold(350, .{.threshold_mv = 400, .hysteresis_mv = 20, .enable_subth = true, .transition_cycles = 10}) + target: < 50ns + +bench subth_power_estimate_latency + measure: nanoseconds to subth_power_estimate(550) + target: < 50ns + +bench power_state_sleep_ok_latency + measure: nanoseconds to power_state_sleep_ok([_]ClkDomain{.{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}} ** 8, 200) + target: < 100ns + +bench encode_subth_clk_latency + measure: nanoseconds to encode_subth_clk(0x05, 0x02) + target: < 20ns + +bench decode_subth_clk_latency + measure: nanoseconds to decode_subth_clk(0xE5A8) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/binary16.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/binary16.t27 new file mode 100644 index 0000000000..bb2b71e71f --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/binary16.t27 @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/binary16.t27 +// Binary16 - Binary packed 16-bit format (3 bits per integer) +// NUMERIC-STANDARD-001 Agent 13 (P1) +// +// Binary16 format: +// - Packs 5 signed integers (3 bits each) into 16 bits +// - Each integer range: -4 to 3 +// - Total: 5 * 3 = 15 bits + 1 padding bit +// - Optimal for binary neural networks +// - Extreme quantization for ternary/-1/0/+1 operations + +module Binary16 { + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // Binary16 bit layout: [P(1) I0(3) I1(3) I2(3) I3(3) I4(3)] + // P: 1 bit (padding/unused) + // I0-I4: 3 bits each (signed two's complement integer) + // + // 5 integers stored, each using 3 bits: + // Range per integer: -4 to 3 + // Storage: Two's complement in 3 bits + // + // Use cases: + // - Binary neural networks + // - Ternary weight quantization + // - Compact activation storage + // - Sparse mask encoding + + const BITS : u8 = 16; + const INT_BITS : u8 = 3; + const NUM_INTS : u8 = 5; + const PADDING_BITS : u8 = 1; + + // 2. Binary16 Type + + struct Binary16 { + raw : u16, // 16-bit packed value + } + + // 3. Encoding/Decoding + + // Get a specific integer from the packed value + fn get_int(b: Binary16, index: u8) -> i8 { + if (index >= NUM_INTS) { return 0; } + // Extract 3-bit value at position index + const bit_offset = index * INT_BITS + PADDING_BITS; + const mask = 0x07; + const raw_val = ((b.raw >> bit_offset) & mask) as u8; + + // Convert 3-bit two's complement to i8 + if (raw_val >= 4) { + return (raw_val as i8) - 8; + } + return raw_val as i8; + } + + // Set a specific integer in the packed value + fn set_int(b: Binary16, index: u8, value: i8) -> Binary16 { + if (index >= NUM_INTS) { return b; } + + // Clamp value to 3-bit range + const clamped = if (value > 3) { 3 } else if (value < -4) { -4 } else { value }; + + // Convert i8 to 3-bit two's complement + let raw_val : u8 = clamped as u8; + if (clamped < 0) { + raw_val = raw_val + 8; + } + + // Clear and set the 3 bits + const bit_offset = index * INT_BITS + PADDING_BITS; + const mask = 0x07; + const cleared = b.raw & (~(mask << bit_offset) as u16); + const packed = cleared | ((raw_val as u16) << bit_offset); + + return Binary16{ raw = packed }; + } + + // Encode array of 5 i8 values to Binary16 + fn encode(values: [i8; NUM_INTS]) -> Binary16 { + let result = Binary16{ raw = 0 }; + for (i in 0..NUM_INTS) { + result = set_int(result, i, values[i]); + } + return result; + } + + // Decode Binary16 to array of 5 i8 values + fn decode(b: Binary16) -> [i8; NUM_INTS] { + let result = [0; NUM_INTS]; + for (i in 0..NUM_INTS) { + result[i] = get_int(b, i); + } + return result; + } + + // 4. Format Properties + + fn min_value() -> i8 { + return -4; + } + + fn max_value() -> i8 { + return 3; + } + + fn range() -> i8 { + return max_value() - min_value(); + } + + fn num_values_per_int() -> i8 { + return 8; + } + + // 5. Validation + + fn validate_format() -> bool { + return (BITS == 16) && + (INT_BITS == 3) && + (NUM_INTS == 5) && + (PADDING_BITS == 1); + } + + // 6. Use Cases + + // Binary16 is optimal for: + // - Binary neural networks (weights in {-1, 0, +1}) + // - Ternary quantization + // - Compact activation masks + // - Sparse representation encoding + // - Extreme memory compression (8x FP32) + + // Memory: 16 bits = 2 bytes (16x FP32 in same space, stores 5 integers) + const MEMORY_RATIO_VS_FP32 : f32 = 16.0 / 32.0; // 0.5 (for 16-bit word) + + // 7. Arithmetic Operations (element-wise) + + fn add(a: Binary16, b: Binary16, index: u8) -> Binary16 { + const val_a = get_int(a, index); + const val_b = get_int(b, index); + const result = val_a + val_b; + return set_int(a, index, result); + } + + fn sub(a: Binary16, b: Binary16, index: u8) -> Binary16 { + const val_a = get_int(a, index); + const val_b = get_int(b, index); + const result = val_a - val_b; + return set_int(a, index, result); + } + + fn neg(b: Binary16, index: u8) -> Binary16 { + const val = get_int(b, index); + return set_int(b, index, -val); + } + + // 8. Comparison Operations (element-wise) + + fn eq(a: Binary16, b: Binary16) -> bool { + return a.raw == b.raw; + } + + fn eq_at(a: Binary16, b: Binary16, index: u8) -> bool { + return get_int(a, index) == get_int(b, index); + } + + fn lt(a: Binary16, b: Binary16, index: u8) -> bool { + return get_int(a, index) < get_int(b, index); + } + + // 9. Special Operations for BNNs + + // Count non-zero values (useful for sparsity metrics) + fn count_nonzero(b: Binary16) -> u8 { + let count : u8 = 0; + for (i in 0..NUM_INTS) { + if (get_int(b, i) != 0) { + count = count + 1; + } + } + return count; + } + + // Check if any value is positive + fn any_positive(b: Binary16) -> bool { + for (i in 0..NUM_INTS) { + if (get_int(b, i) > 0) { + return true; + } + } + return false; + } + + // Check if any value is negative + fn any_negative(b: Binary16) -> bool { + for (i in 0..NUM_INTS) { + if (get_int(b, i) < 0) { + return true; + } + } + return false; + } + + // TDD-Inside-Spec: Tests and Invariants for Binary16 + + test binary16_decode_zero + given b = Binary16{ raw = 0 } + when decoded = decode(b) + then decoded == [0, 0, 0, 0, 0] + + test binary16_encode_zero_roundtrip + given original = [0, 0, 0, 0, 0] + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test binary16_encode_positive_values + given vals = [0, 1, 2, 3, 1] + and encoded = encode(vals) + then decode(encoded) == vals + + test binary16_encode_negative_values + given vals = [-1, -2, -3, -4, -1] + and encoded = encode(vals) + then decode(encoded) == vals + + test binary16_get_int_at_index + given b = encode([1, 2, 3, -1, -2]) + then get_int(b, 0) == 1 and get_int(b, 2) == 3 and get_int(b, 3) == -1 + + test binary16_set_int_at_index + given b = encode([0, 0, 0, 0, 0]) + and result = set_int(b, 2, 3) + then get_int(result, 2) == 3 + + test binary16_set_int_preserves_others + given b = encode([1, 2, 0, -1, -2]) + and result = set_int(b, 2, 3) + then get_int(result, 0) == 1 and get_int(result, 1) == 2 and get_int(result, 3) == -1 + + test binary16_bits_constant + then BITS == 16 + + test binary16_int_bits_constant + then INT_BITS == 3 + + test binary16_num_ints_constant + then NUM_INTS == 5 + + test binary16_padding_bits_constant + then PADDING_BITS == 1 + + test binary16_min_value + then min_value() == -4 + + test binary16_max_value + then max_value() == 3 + + test binary16_range + then range() == 7 + + test binary16_num_values_per_int + then num_values_per_int() == 8 + + test binary16_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.5) < 0.01 + + test binary16_validate_format_success + given valid = validate_format() + then valid == true + + test binary16_clamp_positive + given b = encode([0, 0, 0, 0, 0]) + and result = set_int(b, 0, 10) + then get_int(result, 0) == 3 + + test binary16_clamp_negative + given b = encode([0, 0, 0, 0, 0]) + and result = set_int(b, 0, -10) + then get_int(result, 0) == -4 + + test binary16_add_positive + given a = encode([1, 0, 0, 0, 0]) + and b = encode([2, 0, 0, 0, 0]) + and result = add(a, b, 0) + then get_int(result, 0) == 3 + + test binary16_add_overflow + given a = encode([3, 0, 0, 0, 0]) + and b = encode([2, 0, 0, 0, 0]) + and result = add(a, b, 0) + then get_int(result, 0) == 3 + + test binary16_sub_positive + given a = encode([3, 0, 0, 0, 0]) + and b = encode([1, 0, 0, 0, 0]) + and result = sub(a, b, 0) + then get_int(result, 0) == 2 + + test binary16_neg_positive + given b = encode([2, 0, 0, 0, 0]) + and result = neg(b, 0) + then get_int(result, 0) == -2 + + test binary16_eq_true + given a = encode([1, 2, 3, -1, -2]) + and b = encode([1, 2, 3, -1, -2]) + then eq(a, b) == true + + test binary16_eq_false + given a = encode([1, 2, 3, -1, -2]) + and b = encode([1, 2, 0, -1, -2]) + then eq(a, b) == false + + test binary16_eq_at_true + given a = encode([1, 2, 3, -1, -2]) + and b = encode([0, 0, 3, 0, 0]) + then eq_at(a, b, 2) == true + + test binary16_lt_true + given a = encode([1, 0, 0, 0, 0]) + and b = encode([3, 0, 0, 0, 0]) + then lt(a, b, 0) == true + + test binary16_count_nonzero_zero + given b = encode([0, 0, 0, 0, 0]) + then count_nonzero(b) == 0 + + test binary16_count_nonzero_some + given b = encode([0, 1, 0, -1, 0]) + then count_nonzero(b) == 2 + + test binary16_count_nonzero_all + given b = encode([1, 2, 3, -1, -2]) + then count_nonzero(b) == 5 + + test binary16_any_positive_true + given b = encode([0, 1, -1, 0, 0]) + then any_positive(b) == true + + test binary16_any_positive_false + given b = encode([0, 0, -1, -2, 0]) + then any_positive(b) == false + + test binary16_any_negative_true + given b = encode([0, 1, -1, 0, 0]) + then any_negative(b) == true + + test binary16_any_negative_false + given b = encode([0, 1, 2, 3, 0]) + then any_negative(b) == false + + test binary16_all_indices_range + given b = encode([0, 0, 0, 0, 0]) + and result = set_int(set_int(set_int(set_int(set_int(b, 0, -4), 1, -3), 2, -2), 3, -1), 4, 0) + then get_int(result, 0) == -4 and get_int(result, 4) == 0 + + invariant binary16_bits_constant + assert BITS == 16 + + invariant binary16_int_bits_is_three + assert INT_BITS == 3 + + invariant binary16_num_ints_is_five + assert NUM_INTS == 5 + + invariant binary16_min_value_is_neg_four + assert min_value() == -4 + + invariant binary16_max_value_is_three + assert max_value() == 3 + + invariant binary16_range_is_seven + assert range() == 7 + + invariant binary16_encode_decode_roundtrip + given vals = [1, 2, 3, -1, -2] + then decode(encode(vals)) == vals + + invariant binary16_get_set_consistency + given b = encode([0, 0, 0, 0, 0]) + and val = 2 + then get_int(set_int(b, 3, val), 3) == val + + invariant binary16_count_nonzero_in_range + for (b in {encode([0,0,0,0,0]), encode([1,0,0,0,0]), encode([1,2,3,-1,-2])}) + then count_nonzero(b) >= 0 and count_nonzero(b) <= 5 + + invariant binary16_packing_efficiency + assert NUM_INTS * INT_BITS + PADDING_BITS == BITS + + bench binary16_encode_latency + measure: nanoseconds to encode([1, 2, 3, -1, -2]) + target: < 50ns + + bench binary16_decode_latency + measure: nanoseconds to decode(encode([1, 2, 3, -1, -2])) + target: < 40ns + + bench binary16_get_int_latency + measure: nanoseconds to get_int(encode([1, 2, 3, -1, -2]), 2) + target: < 20ns + + bench binary16_set_int_latency + measure: nanoseconds to set_int(encode([0, 0, 0, 0, 0]), 2, 3) + target: < 30ns + + bench binary16_count_nonzero_latency + measure: nanoseconds to count_nonzero(encode([1, 2, 0, -1, 0])) + target: < 50ns + + bench binary16_eq_latency + measure: nanoseconds to eq(encode([1, 2, 3, -1, -2]), encode([1, 2, 3, -1, -2])) + target: < 10ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/formats.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/formats.t27 new file mode 100644 index 0000000000..90cda34b16 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/formats.t27 @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/numeric/formats.t27 +// Format Conversion Utilities - GF16, f32, ternary encoding +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Formats { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; + use numeric::gf16; + + // ======================================================================== + // 1. GF16 Bit Layout Constants + // ======================================================================== + // + // GF16 bit layout (as specified in whitepaper): + // [S(1) E(6) M(9)] = [15:15][14:9][8:0] + // + // - Sign: bit 15 (0x8000) + // - Exponent: bits 14-9 (0x7E00), bias = 31 + // - Mantissa: bits 8-0 (0x01FF) + // + // Range: 2^-31 to 2^32 + // ======================================================================== + + pub const SignMask : u16 = 0x8000; + pub const ExpMask : u16 = 0x7E00; + pub const MantMask : u16 = 0x01FF; + + pub const ExpShift : u5 = 9; + pub const SignShift : u4 = 15; + pub const Bias : i32 = 31; + + pub const ExpMax : u16 = 63; + pub const ExpMin : u16 = 0; + + // ======================================================================== + // 2. GF16 0 f32 (decode) + // ======================================================================== + // + // Converts GF16 encoding to IEEE 754 binary32 floating point. + // Handles signed zero, denormals, normals, infinities, and NaN. + // ======================================================================== + + // gf16_to_f32(x: u16) -> gf16 + // Decode GF16 to f32 + // + // Algorithm: + // 1. Extract sign (bit 15) + // 2. Extract exponent (bits 14-9) and mantissa (bits 8-0) + // 3. Handle special cases: + // - e=0, m=0: signed zero + // - e=0, m!=0: denormal (subnormal) + // - e=ExpMax, m=0: +/- infinity + // - e=ExpMax, m!=0: NaN + // 4. Normal case: + // value = (-1)^s * (1 + m/2^9) * 2^(e - Bias) + // + // Complexity: O(1) + pub fn gf16_to_f32(x: u16) -> gf16; + + // ======================================================================== + // 3. f32 1 GF16 (encode, round-to-nearest) + // ======================================================================== + // + // Converts IEEE 754 binary32 floating point to GF16 encoding. + // Handles signed zero, special cases, overflow, and underflow. + // + // Algorithm: + // 1. Handle signed zero explicitly (sign bit preserved) + // 2. Handle special cases (Inf, NaN) + // 3. Get exponent and mantissa via frexp: abs = m * 2^e, m in [0.5, 1] + // 4. Normalize: want 1.x * 2^(E - Bias), frexp gives m in [0.5, 1] + // 5. Mantissa: (m - 1.0) * 2^9, round to nearest + // 6. Check underflow/overflow + // + // Complexity: O(1) + pub fn f32_to_gf16(a: f32) -> u16; + + // ======================================================================== + // 4. Ternary Quantization + // ======================================================================== + // + // Ternary quantization: maps f32 to ternary {-1, 0, +1} + // Threshold: |w| > 0.5 -> +/-1, else -> 0 + // + // WHY: Enables efficient ternary representation of continuous values + // Useful for VSA (Vector Symbolic Architecture) operations + // ======================================================================== + + // f32_to_ternary(x: f32) -> Trit + // Quantize f32 to ternary {-1, 0, +1} + // + // Algorithm: + // - If x > 0.5: return +1 + // - If x < -0.5: return -1 + // - Otherwise: return 0 + // + // Complexity: O(1) + pub fn f32_to_ternary(x: f32) -> Trit; + + // ternary_to_f32(t: Trit) -> gf16 + // Convert ternary to f32 + // + // Mapping: -1 -> -1.0, 0 -> 0.0, +1 -> 1.0 + // Complexity: O(1) + pub fn ternary_to_f32(t: Trit) -> gf16; + + // ======================================================================== + // 5. Format Enum + // ======================================================================== + // + // Comprehensive format support: + // - IEEE Floating Point: fp32, fp16, bf16, fp8_e4m3, fp8_e5m2 + // - GoldenFloat: gf4, gf8, gf12, gf16 (PRIMARY), gf20, gf24, gf32, gf64, gf128, gf256 + // - Integer: int4, int8 + // - Quantization: nf4, binary16, ternary, posit16 + // ======================================================================== + + pub const Format = enum(u8) { + // IEEE Floating Point formats + fp32, + fp16, + bf16, + fp8_e4m3, + fp8_e5m2, + + // GoldenFloat family (φ-optimized) + gf4, + gf8, + gf12, + gf16, + gf20, + gf24, + gf32, + gf64, + gf128, + gf256, + + // Integer formats + int4, + int8, + + // Quantization formats + nf4, + binary16, + ternary, + + // Posit type + posit16, + }; + + // format_bytes(fmt: Format) -> usize + // Returns byte size for each format + // + // Byte sizes: + // - fp32: 4 bytes (32 bits) + // - fp16, bf16, gf16, int8: 2 bytes (16 bits) + // - fp8_e4m3, fp8_e5m2, gf8, int4: 1 byte (8 bits) + // - gf4: 0.5 bytes (4 bits, packed) + // - nf4: 0.5 bytes (4 bits, packed) + // - binary16: 2 bytes (16 bits, stores 5 ints) + // - gf12: 1.5 bytes (12 bits) + // - gf20: 2.5 bytes (20 bits) + // - gf24: 3 bytes (24 bits) + // - gf32: 4 bytes (32 bits) + // - gf64: 8 bytes (64 bits) + // - gf128: 16 bytes (128 bits) + // - gf256: 32 bytes (256 bits) + // - ternary: 1 byte (2 bits packed) + // - posit16: 2 bytes (16 bits) + // + // Complexity: O(1) + pub fn format_bytes(fmt: Format) -> usize; + + // ======================================================================== + // 6. Quantization Utility + // ======================================================================== + + // quantize_value(x: f32, fmt: Format) -> gf16 + // Quantize f32 to target format + // + // Complexity: O(1) + pub fn quantize_value(x: f32, fmt: Format) -> gf16; + + // ======================================================================== + // TDD - Tests + // ======================================================================== + + test gf16_to_f32_zero_positive + // Verify: zero encodes to zero + given x: u16 = 0 + when result = gf16_to_f32(x) + then gf16.to_f64(result) == 0.0 + + test gf16_to_f32_zero_negative + // Verify: negative zero encodes to -0 + given x: u16 = 0x8000 + when result = gf16_to_f32(x) + then gf16.to_f64(result) == -0.0 + + test gf16_to_f32_denormal + // Verify: denormal value decodes to small positive + given x: u16 = 0x0080 + when result = gf16_to_f32(x) + and val = gf16.to_f64(result) + then val > 0.0 and val < 1.0 + + test gf16_to_f32_normal_one + // Verify: 1.0 encodes correctly + given x: u16 = 0x3C00 + when result = gf16_to_f32(x) + and decoded = gf16.to_f64(result) + then decoded == 1.0 + + test gf16_to_f32_positive_inf + // Verify: positive infinity encodes correctly + given x: u16 = 0x7E00 + when result = gf16_to_f32(x) + and decoded = gf16.to_f64(result) + then decoded == std.math.inf(f32) + + test gf16_to_f32_negative_inf + // Verify: negative infinity encodes correctly + given x: u16 = 0xFE00 + when result = gf16_to_f32(x) + and decoded = gf16.to_f64(result) + then decoded == -std.math.inf(f32) + + test gf16_to_f32_nan + // Verify: NaN encodes to NaN + given x: u16 = 0x7F01 + when result = gf16_to_f32(x) + then result != result // NaN check + + test f32_to_gf16_zero_positive + // Verify: +0 encodes to 0 + given a: f32 = 0.0 + when result = f32_to_gf16(a) + then result == 0 + + test f32_to_gf16_zero_negative + // Verify: -0 encodes to 0x8000 + given a: f32 = -0.0 + when result = f32_to_gf16(a) + then result == 0x8000 + + test f32_to_gf16_one + // Verify: 1.0 encodes and roundtrips correctly + given a: f32 = 1.0 + and encoded = f32_to_gf16(a) + and decoded = gf16_to_f32(encoded) + and recovered = gf16.to_f64(decoded) + then recovered >= 0.99 and recovered <= 1.01 + + test f32_to_gf16_inf_positive + // Verify: +Inf encodes to 0x7E00 + given a: f32 = std.math.inf(f32) + when result = f32_to_gf16(a) + then result == 0x7E00 + + test f32_to_gf16_inf_negative + // Verify: -Inf encodes to 0xFE00 + given a: f32 = -std.math.inf(f32) + when result = f32_to_gf16(a) + then result == 0xFE00 + + test f32_to_gf16_nan + // Verify: NaN encodes to 0x7F01 + given a: f32 = std.math.nan(f32) + when result = f32_to_gf16(a) + then result == 0x7F01 + + test f32_to_ternary_positive + // Verify: 1.0 quantizes to pos + given a: f32 = 1.0 + when result = f32_to_ternary(a) + then result == .pos + + test f32_to_ternary_zero + // Verify: 0.0 quantizes to zero + given a: f32 = 0.0 + when result = f32_to_ternary(a) + then result == .zero + + test f32_to_ternary_negative + // Verify: -1.0 quantizes to neg + given a: f32 = -1.0 + when result = f32_to_ternary(a) + then result == .neg + + test f32_to_ternary_threshold + // Verify: 0.6 quantizes to pos (above 0.5 threshold) + given a: f32 = 0.6 + when result = f32_to_ternary(a) + then result == .pos + + test f32_to_ternary_negative_threshold + // Verify: -0.6 quantizes to neg (below -0.5 threshold) + given a: f32 = -0.6 + when result = f32_to_ternary(a) + then result == .neg + + test ternary_to_f32_positive + // Verify: pos maps to 1.0 + given t: Trit = .pos + when result = ternary_to_f32(t) + and result_val = gf16.to_f64(result) + then result_val == 1.0 + + test ternary_to_f32_zero + // Verify: zero maps to 0.0 + given t: Trit = .zero + when result = ternary_to_f32(t) + and result_val = gf16.to_f64(result) + then result_val == 0.0 + + test ternary_to_f32_negative + // Verify: neg maps to -1.0 + given t: Trit = .neg + when result = ternary_to_f32(t) + and result_val = gf16.to_f64(result) + then result_val == -1.0 + + test format_bytes_fp32 + // Verify: fp32 is 4 bytes + when result = format_bytes(.fp32) + then result == 4 + + test format_bytes_fp16 + // Verify: fp16 is 2 bytes + when result = format_bytes(.fp16) + then result == 2 + + test format_bytes_bf16 + // Verify: bf16 is 2 bytes + when result = format_bytes(.bf16) + then result == 2 + + test format_bytes_fp8_e4m3 + // Verify: fp8_e4m3 is 1 byte + when result = format_bytes(.fp8_e4m3) + then result == 1 + + test format_bytes_fp8_e5m2 + // Verify: fp8_e5m2 is 1 byte + when result = format_bytes(.fp8_e5m2) + then result == 1 + + test format_bytes_gf4 + // Verify: gf4 is 0.5 bytes (packed) + when result = format_bytes(.gf4) + then result == 0 + + test format_bytes_gf8 + // Verify: gf8 is 1 byte + when result = format_bytes(.gf8) + then result == 1 + + test format_bytes_gf16 + // Verify: gf16 is 2 bytes + when result = format_bytes(.gf16) + then result == 2 + + test format_bytes_gf32 + // Verify: gf32 is 4 bytes + when result = format_bytes(.gf32) + then result == 4 + + test format_bytes_gf64 + // Verify: gf64 is 8 bytes + when result = format_bytes(.gf64) + then result == 8 + + test format_bytes_gf128 + // Verify: gf128 is 16 bytes + when result = format_bytes(.gf128) + then result == 16 + + test format_bytes_gf256 + // Verify: gf256 is 32 bytes + when result = format_bytes(.gf256) + then result == 32 + + test format_bytes_int4 + // Verify: int4 is 0.5 bytes (packed) + when result = format_bytes(.int4) + then result == 0 + + test format_bytes_int8 + // Verify: int8 is 1 byte + when result = format_bytes(.int8) + then result == 1 + + test format_bytes_nf4 + // Verify: nf4 is 0.5 bytes (packed) + when result = format_bytes(.nf4) + then result == 0 + + test format_bytes_binary16 + // Verify: binary16 is 2 bytes + when result = format_bytes(.binary16) + then result == 2 + + test format_bytes_ternary + // Verify: ternary is 1 byte (packed) + when result = format_bytes(.ternary) + then result == 1 + + test format_bytes_posit16 + // Verify: posit16 is 2 bytes + when result = format_bytes(.posit16) + then result == 2 + + test quantize_value_fp32 + // Verify: quantizing to fp32 preserves value + given x: f32 = 1.5 + and result = quantize_value(x, .fp32) + and result_val = gf16.to_f64(result) + then result_val >= 1.49 and result_val <= 1.51 + + test quantize_value_ternary + // Verify: quantizing to ternary gives +1 + given x: f32 = 1.5 + and result = quantize_value(x, .ternary) + and expected = ternary_to_f32(.pos) + then result == expected + + test quantize_value_int8 + // Verify: quantizing to int8 clamps within range + given x: f32 = 200.0 + and result = quantize_value(x, .int8) + and result_val = gf16.to_f64(result) + then result_val <= 127.0 + + test quantize_value_nf4 + // Verify: quantizing to nf4 clamps to [0, 1] + given x: f32 = 1.5 + and result = quantize_value(x, .nf4) + and result_val = gf16.to_f64(result) + then result_val <= 1.0 + + // ======================================================================== + // TDD - Invariants + // ======================================================================== + + invariant gf16_to_f32_preserves_zero + // Zero should decode to zero + assert gf16.to_f64(gf16_to_f32(0)) == 0.0; + assert gf16.to_f64(gf16_to_f32(0x8000)) == -0.0; + + invariant gf16_to_f32_preserves_infinity + // Infinity should decode to infinity + assert gf16.to_f64(gf16_to_f32(0x7E00)) == std.math.inf(f32); + assert gf16.to_f64(gf16_to_f32(0xFE00)) == -std.math.inf(f32); + + invariant f32_to_gf16_roundtrip_loss + // Roundtrip should be within tolerance for normal values + const original = gf16.from_f64(1.5); + const encoded = f32_to_gf16(gf16.to_f64(original)); + const decoded = gf16_to_f32(encoded); + const error = gf16.abs(gf16.sub(original, decoded)); + assert gf16.to_f64(error) < gf16.from_f64(0.01); + + invariant ternary_quantization_symmetric + // Quantization threshold is symmetric + const p = f32_to_ternary(0.5); + const n = f32_to_ternary(-0.5); + const p_decoded = ternary_to_f32(p); + const n_decoded = ternary_to_f32(n); + assert gf16.to_f64(p_decoded) == -gf16.to_f64(n_decoded); + + invariant ternary_to_f32_is_inverse + // ternary_to_f32 is inverse of f32_to_ternary + const values = [_]f32{ -1.0, -0.5, 0.0, 0.5, 1.0 }; + for (values) |v| { + const t = f32_to_ternary(v); + const recovered = ternary_to_f32(t); + const diff = gf16.abs(gf16.sub(v, recovered)); + assert gf16.eq(v, recovered) or (gf16.to_f64(diff) < gf16.from_f64(0.01)); + } + + invariant format_bytes_positive + // All format byte sizes should be positive + assert format_bytes(.fp32) > 0; + assert format_bytes(.fp16) > 0; + assert format_bytes(.ternary) > 0; + + // ======================================================================== + // TDD - Benchmarks + // ======================================================================== + + bench gf16_to_f32_latency + // Measure: cycles for gf16_to_f32 conversion + // Target: < 50 cycles (simple bit extraction + lookup) + @setEvalBranchQuota(10000); + var result : gf16; + const test_value: u16 = 0x3C00; + for (0..1000) |_| { + result = gf16_to_f32(test_value); + } + _ = result; + + bench f32_to_gf16_latency + // Measure: cycles for f32_to_gf16 conversion + // Target: < 100 cycles (frexp + bit packing) + @setEvalBranchQuota(10000); + var result : u16; + const test_value: f32 = 1.5; + for (0..1000) |_| { + result = f32_to_gf16(test_value); + } + _ = result; + + bench f32_to_ternary_latency + // Measure: cycles for f32_to_ternary conversion + // Target: < 10 cycles (single comparison) + @setEvalBranchQuota(10000); + var result : Trit; + const test_value: f32 = 0.75; + for (0..1000) |_| { + result = f32_to_ternary(test_value); + } + _ = result; + + bench ternary_to_f32_latency + // Measure: cycles for ternary_to_f32 conversion + // Target: < 10 cycles (simple switch) + @setEvalBranchQuota(10000); + var result : gf16; + const test_trit: Trit = .pos; + for (0..1000) |_| { + result = ternary_to_f32(test_trit); + } + _ = result; +} diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/fp8_e4m3.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/fp8_e4m3.t27 new file mode 100644 index 0000000000..06fb882f2d --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/fp8_e4m3.t27 @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: Apache-2.0 +; fp8_e4m3.t27 — FP8 E4M3 8-bit Floating Point +; 8-bit float with 4 exponent, 3 mantissa bits (no implicit leading bit) +; OCP FP8 format optimized for neural network training +; Range: ~-240 to ~240, precision: ~6-7 significant bits +; φ² + 1/φ² = 3 | TRINITY + +module triformat-fp8_e4m3; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 4; +pub const MANT_BITS : u8 = 3; + +pub const SIGN_SHIFT : u8 = 7; +pub const EXP_SHIFT : u8 = 3; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u8 = 0x80; // 1 << 7 +pub const EXP_MASK : u8 = 0x78; // 0b01111000 +pub const MANT_MASK : u8 = 0x07; // 0b00000111 + +pub const EXP_MAX : u8 = 15; // All ones in 4 bits +pub const EXP_MIN : u8 = 0; +pub const EXP_BIAS : u8 = 7; // Exponent bias for FP8 E4M3 + +pub const MANT_DIVISOR : u8 = 8; // 2^3 + +// FP8 E4M3 special values +pub const FP8_ZERO_POS : u8 = 0x00; +pub const FP8_ZERO_NEG : u8 = 0x80; +pub const FP8_NAN_POS : u8 = 0x7F; // All exp + mant != 0 +pub const FP8_NAN_NEG : u8 = 0xFF; +pub const FP8_INF_POS : u8 = 0x78; // All exp + mant = 0 +pub const FP8_INF_NEG : u8 = 0xF8; + +// Subnormal exponent +pub const SUBNORMAL_EXP : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FP8E4M3 = u8; // 8-bit value + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// fp8_extract_sign(fp8: FP8E4M3) -> i8 +// Extract sign bit (bit 7) +// Returns: 0 for positive, -1 for negative +pub fn fp8_extract_sign(fp8: FP8E4M3) i8 { + const bit = (fp8 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// fp8_extract_exponent(fp8: FP8E4M3) -> u8 +// Extract exponent bits (bits 6-3) +// Returns: 0-15 +pub fn fp8_extract_exponent(fp8: FP8E4M3) u8 { + return (fp8 >> EXP_SHIFT) & 0x0F; +} + +// fp8_extract_mantissa(fp8: FP8E4M3) -> u8 +// Extract mantissa bits (bits 2-0) +// Returns: 0-7 +pub fn fp8_extract_mantissa(fp8: FP8E4M3) u8 { + return fp8 & MANT_MASK; +} + +// fp8_from_components(sign: i8, exp: u8, mant: u8) -> FP8E4M3 +// Assemble FP8 from sign, exponent, mantissa +pub fn fp8_from_components(sign: i8, exp: u8, mant: u8) FP8E4M3 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(FP8E4M3, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(FP8E4M3, @intCast(exp & 0x0F)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// fp8_is_zero(fp8: FP8E4M3) -> bool +// Check if FP8 is zero (positive or negative) +pub fn fp8_is_zero(fp8: FP8E4M3) bool { + return fp8 == FP8_ZERO_POS or fp8 == FP8_ZERO_NEG; +} + +// fp8_is_special(fp8: FP8E4M3) -> bool +// Check if FP8 is Inf or NaN (exp == 15) +pub fn fp8_is_special(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX; +} + +// fp8_is_inf(fp8: FP8E4M3) -> bool +// Check if FP8 is infinity (exp == 15, mant == 0) +pub fn fp8_is_inf(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) == 0; +} + +// fp8_is_nan(fp8: FP8E4M3) -> bool +// Check if FP8 is NaN (exp == 15, mant != 0) +pub fn fp8_is_nan(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) != 0; +} + +// fp8_is_subnormal(fp8: FP8E4M3) -> bool +// Check if FP8 is subnormal (exp == 0, mant != 0) +pub fn fp8_is_subnormal(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == 0 and fp8_extract_mantissa(fp8) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// fp8_encode_f32(value: f32) -> FP8E4M3 +// Encode IEEE 754 single precision to FP8 E4M3 +// Round-to-nearest, ties to even +pub fn fp8_encode_f32(value: f32) FP8E4M3 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return if (value < 0.0) FP8_NAN_NEG else FP8_NAN_POS; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i16 = @as(i16, @intCast((f32_bits >> 23) & 0xFF)) - 127; + var f32_mant: u32 = f32_bits & 0x007FFFFF; + + // Handle very small values (subnormals) + if (f32_exp <= @as(i16, -EXP_BIAS)) { + // Below FP8 minimum normal - try to encode as subnormal + const subnormal_shift = @as(i16, -EXP_BIAS) - f32_exp + 1; + if (subnormal_shift > 3 + 23) { + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + var mant = f32_mant >> 23; + if (subnormal_shift > 0) { + const shift_amount = @as(u5, @intCast(subnormal_shift)); + if (shift_amount <= 24) { + mant = (f32_mant | 0x00800000) >> shift_amount; + } + } + return fp8_from_components(sign, 0, @as(u8, @truncate(mant)) & 0x07); + } + + // Convert exp from f32 bias (127) to FP8 bias (7) + var fp8_exp = @as(i16, f32_exp + EXP_BIAS); + + // Clamp exponent + if (fp8_exp >= EXP_MAX) { + return if (sign < 0) FP8_INF_NEG else FP8_INF_POS; + } else if (fp8_exp < 1) { + // Underflow to zero (could be subnormal, simplified) + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Extract mantissa and scale to 3 bits + // f32 mantissa is 24 bits (including implicit 1), FP8 needs 3 bits + // Shift right by 21 bits (24 - 3 = 21) + var mant = f32_mant >> 21; + + // Round-to-nearest with ties to even + const discarded = f32_mant & 0x1FFFFF; // Lower 21 bits + if ((discarded & 0x100000) != 0) { + // Halfway or more + if ((discarded & 0x0FFFFF) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (fp8_exp < @as(i16, EXP_MAX - 1)) { + fp8_exp += 1; + } + } + } + } + + return fp8_from_components(sign, @as(u8, @intCast(fp8_exp)), @as(u8, mant)); +} + +// fp8_decode_f32(fp8: FP8E4M3) -> f32 +// Decode FP8 E4M3 to IEEE 754 single precision +pub fn fp8_decode_f32(fp8: FP8E4M3) f32 { + // Handle zero + if (fp8_is_zero(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (fp8_is_nan(fp8)) { + return std.math.nan(f32); + } + + // Handle Infinity + if (fp8_is_inf(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -std.math.inf(f32) else std.math.inf(f32); + } + + // Extract components + const sign = fp8_extract_sign(fp8); + const exp = fp8_extract_exponent(fp8); + const mant = fp8_extract_mantissa(fp8); + + var f32_value: f32 = undefined; + + // Handle subnormal + if (exp == 0) { + // Subnormal: value = mant * 2^(-6) = mant * 2^(1-bias) + const bias_adjusted: i8 = 1 - @as(i8, EXP_BIAS); + const mant_f32 = @as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR)); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } else { + // Normal: value = (1 + mant/8) * 2^(exp - 7) + const bias_adjusted = @as(i16, exp) - @as(i16, EXP_BIAS); + const mant_f32 = 1.0 + (@as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR))); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } + + return if (sign < 0) -f32_value else f32_value; +} + +// fp8_encode_f64(value: f64) -> FP8E4M3 +// Encode IEEE 754 double precision to FP8 E4M3 +pub fn fp8_encode_f64(value: f64) FP8E4M3 { + return fp8_encode_f32(@as(f32, @floatCast(value))); +} + +// fp8_decode_f64(fp8: FP8E4M3) -> f64 +// Decode FP8 E4M3 to IEEE 754 double precision +pub fn fp8_decode_f64(fp8: FP8E4M3) f64 { + const f32_val = fp8_decode_f32(fp8); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// fp8_add(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Add two FP8 values +pub fn fp8_add(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa + fb); +} + +// fp8_sub(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Subtract two FP8 values +pub fn fp8_sub(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa - fb); +} + +// fp8_mul(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Multiply two FP8 values +pub fn fp8_mul(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa * fb); +} + +// fp8_div(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Divide two FP8 values +pub fn fp8_div(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fb = fp8_decode_f32(b); + if (fb == 0.0) { + const fa = fp8_decode_f32(a); + return if (fa < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + const fa = fp8_decode_f32(a); + return fp8_encode_f32(fa / fb); +} + +// fp8_sqrt(value: FP8E4M3) -> FP8E4M3 +// Square root of FP8 value +pub fn fp8_sqrt(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + if (fv < 0.0) { + return FP8_NAN_POS; + } + return fp8_encode_f32(std.math.sqrt(fv)); +} + +// fp8_rsqrt(value: FP8E4M3) -> FP8E4M3 +// Reciprocal square root (1/sqrt(x)) +pub fn fp8_rsqrt(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + if (fv <= 0.0) { + return FP8_INF_POS; + } + return fp8_encode_f32(1.0 / std.math.sqrt(fv)); +} + +// fp8_abs(value: FP8E4M3) -> FP8E4M3 +// Absolute value of FP8 +pub fn fp8_abs(value: FP8E4M3) FP8E4M3 { + return value & ~SIGN_MASK; +} + +// fp8_neg(value: FP8E4M3) -> FP8E4M3 +// Negate FP8 +pub fn fp8_neg(value: FP8E4M3) FP8E4M3 { + return value ^ SIGN_MASK; +} + +// fp8_is_equal(a: FP8E4M3, b: FP8E4M3) -> bool +// Check if two FP8 values are equal +pub fn fp8_is_equal(a: FP8E4M3, b: FP8E4M3) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + if (fp8_is_zero(a) and fp8_is_zero(b)) { + return true; + } + return a == b; +} + +// fp8_is_greater(a: FP8E4M3, b: FP8E4M3) -> bool +// Check if a > b +pub fn fp8_is_greater(a: FP8E4M3, b: FP8E4M3) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + const sign_a = fp8_extract_sign(a); + const sign_b = fp8_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return fp8_neg(a) > fp8_neg(b); + } + return a > b; +} + +// fp8_max(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Return the larger of two FP8 values +pub fn fp8_max(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + return if (fp8_is_greater(a, b)) a else b; +} + +// fp8_min(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Return the smaller of two FP8 values +pub fn fp8_min(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + return if (fp8_is_greater(a, b)) b else a; +} + +// fp8_clamp(value: FP8E4M3, min: FP8E4M3, max: FP8E4M3) -> FP8E4M3 +// Clamp value between min and max +pub fn fp8_clamp(value: FP8E4M3, min: FP8E4M3, max: FP8E4M3) FP8E4M3 { + return fp8_min(fp8_max(value, min), max); +} + +// fp8_lerp(a: FP8E4M3, b: FP8E4M3, t: f32) -> FP8E4M3 +// Linear interpolation between a and b +pub fn fp8_lerp(a: FP8E4M3, b: FP8E4M3, t: f32) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const result = fa + (fb - fa) * t; + return fp8_encode_f32(result); +} + +// fp8_fma(a: FP8E4M3, b: FP8E4M3, c: FP8E4M3) -> FP8E4M3 +// Fused multiply-add: a * b + c +pub fn fp8_fma(a: FP8E4M3, b: FP8E4M3, c: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const fc = fp8_decode_f32(c); + return fp8_encode_f32(fa * fb + fc); +} + +// fp8_scale(value: FP8E4M3, scale: f32) -> FP8E4M3 +// Scale FP8 value by scalar +pub fn fp8_scale(value: FP8E4M3, scale: f32) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * scale); +} + +// fp8_relu(value: FP8E4M3) -> FP8E4M3 +// ReLU activation: max(0, x) +pub fn fp8_relu(value: FP8E4M3) FP8E4M3 { + return if (fp8_extract_sign(value) < 0) FP8_ZERO_POS else value; +} + +// fp8_sigmoid(value: FP8E4M3) -> FP8E4M3 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn fp8_sigmoid(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// fp8_tanh(value: FP8E4M3) -> FP8E4M3 +// Tanh activation +pub fn fp8_tanh(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(std.math.tanh(fv)); +} + +// fp8_gelu(value: FP8E4M3) -> FP8E4M3 +// GELU activation +pub fn fp8_gelu(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + const sqrt_2_over_pi = 0.7978845608; + const cube_coeff = 0.044715; + const tanh_arg = fv * (1.0 + cube_coeff * fv * fv); + return fp8_encode_f32(0.5 * fv * (1.0 + std.math.tanh(sqrt_2_over_pi * tanh_arg))); +} + +// fp8_swish(value: FP8E4M3) -> FP8E4M3 +// Swish activation: x * sigmoid(x) +pub fn fp8_swish(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * (1.0 / (1.0 + std.math.exp(-fv)))); +} + +// fp8_quantize_to_int4(value: FP8E4M3) -> i8 +// Quantize FP8 to Int4 (range [-8, 7]) +pub fn fp8_quantize_to_int4(value: FP8E4M3) i8 { + const fv = fp8_decode_f32(value); + const scaled = fv * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// fp8_quantize_to_int8(value: FP8E4M3) -> i8 +// Quantize FP8 to Int8 (range [-128, 127]) +pub fn fp8_quantize_to_int8(value: FP8E4M3) i8 { + const fv = fp8_decode_f32(value); + const scaled = fv * 127.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 127) { + result = 127; + } else if (result < -128) { + result = -128; + } + return @as(i8, @intCast(result)); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fp8_extract_sign_positive" { + given value = 0x3C + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "fp8_extract_sign_negative" { + given value = 0xBC + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "fp8_extract_exponent_max" { + given value = 0x78 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == EXP_MAX); +} + +test "fp8_extract_exponent_zero" { + given value = 0x07 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == 0); +} + +test "fp8_extract_mantissa_max" { + given value = 0x07 + try std.testing.expect(mant = fp8_extract_mantissa(value)); + try std.testing.expect(mant == MANT_MASK); +} + +test "fp8_is_zero_positive" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_POS) == true); +} + +test "fp8_is_zero_negative" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_NEG) == true); +} + +test "fp8_is_zero_nonzero" { + try std.testing.expect(fp8_is_zero(0x01) == false); +} + +test "fp8_is_inf_positive" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true); +} + +test "fp8_is_inf_negative" { + try std.testing.expect(fp8_is_inf(FP8_INF_NEG) == true); +} + +test "fp8_is_nan_positive" { + try std.testing.expect(fp8_is_nan(FP8_NAN_POS) == true); +} + +test "fp8_is_nan_negative" { + try std.testing.expect(fp8_is_nan(FP8_NAN_NEG) == true); +} + +test "fp8_is_inf_not_nan" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true and fp8_is_nan(FP8_INF_POS) == false); +} + +test "fp8_is_special_inf" { + try std.testing.expect(fp8_is_special(FP8_INF_POS) == true); +} + +test "fp8_is_special_nan" { + try std.testing.expect(fp8_is_special(FP8_NAN_POS) == true); +} + +test "fp8_is_subnormal_true" { + given value = 0x01 + try std.testing.expect(fp8_is_subnormal(value) == true); +} + +test "fp8_is_subnormal_false_normal" { + given value = 0x08 + try std.testing.expect(fp8_is_subnormal(value) == false); +} + +test "fp8_is_subnormal_false_zero" { + try std.testing.expect(fp8_is_subnormal(FP8_ZERO_POS) == false); +} + +test "fp8_encode_f32_zero" { + given fp = fp8_encode_f32(0.0) + try std.testing.expect(fp == FP8_ZERO_POS); +} + +test "fp8_encode_f32_negative_zero" { + given fp = fp8_encode_f32(-0.0) + try std.testing.expect(fp == FP8_ZERO_NEG); +} + +test "fp8_encode_f32_one" { + given fp = fp8_encode_f32(1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 1.0) < 0.05); +} + +test "fp8_encode_f32_negative_one" { + given fp = fp8_encode_f32(-1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded + 1.0) < 0.05); +} + +test "fp8_encode_f32_two" { + given fp = fp8_encode_f32(2.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 2.0) < 0.1); +} + +test "fp8_encode_f32_half" { + given fp = fp8_encode_f32(0.5) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 0.5) < 0.05); +} + +test "fp8_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.1); +} + +test "fp8_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.1); +} + +test "fp8_encode_f32_nan" { + given fp = fp8_encode_f32(std.math.nan(f32)) + try std.testing.expect(fp8_is_nan(fp) == true); +} + +test "fp8_encode_f32_inf" { + given fp = fp8_encode_f32(std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_encode_f32_neg_inf" { + given fp = fp8_encode_f32(-std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_decode_f32_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_POS) == 0.0); +} + +test "fp8_decode_f32_negative_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_NEG) == -0.0); +} + +test "fp8_decode_f32_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_POS)) == true); +} + +test "fp8_decode_f32_neg_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_NEG)) == true); +} + +test "fp8_decode_f32_nan" { + try std.testing.expect(std.math.isNan(fp8_decode_f32(FP8_NAN_POS)) == true); +} + +test "fp8_add_simple" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(result = fp8_add(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.15); +} + +test "fp8_sub_simple" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(result = fp8_sub(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.15); +} + +test "fp8_mul_simple" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(result = fp8_mul(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 6.0) < 0.3); +} + +test "fp8_div_simple" { + given a = fp8_encode_f32(6.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.15); +} + +test "fp8_div_by_zero_positive" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_POS); +} + +test "fp8_div_by_zero_negative" { + given a = fp8_encode_f32(-5.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_NEG); +} + +test "fp8_sqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.1); +} + +test "fp8_sqrt_negative" { + given value = fp8_encode_f32(-4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(fp8_is_nan(result) == true); +} + +test "fp8_rsqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_rsqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_abs_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(result = fp8_abs(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_abs_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(abs_val = fp8_abs(value)); + try std.testing.expect(fp8_extract_sign(abs_val) == 0); +} + +test "fp8_neg_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) < 0); +} + +test "fp8_neg_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) == 0); +} + +test "fp8_is_equal_true" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(fp8_is_equal(value, value) == true); +} + +test "fp8_is_equal_false" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(fp8_is_equal(a, b) == false); +} + +test "fp8_is_equal_nan" { + try std.testing.expect(fp8_is_equal(FP8_NAN_POS, FP8_NAN_POS) == false); +} + +test "fp8_is_equal_zero" { + try std.testing.expect(fp8_is_equal(FP8_ZERO_POS, FP8_ZERO_NEG) == true); +} + +test "fp8_is_greater_positive" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(fp8_is_greater(a, b) == true); +} + +test "fp8_max_returns_larger" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 4.0); +} + +test "fp8_min_returns_smaller" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 4.0); +} + +test "fp8_lerp_zero" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, a) == true); +} + +test "fp8_lerp_one" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, b) == true); +} + +test "fp8_fma_simple" { + given a = fp8_encode_f32(2.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(c = fp8_encode_f32(1.0)); + try std.testing.expect(result = fp8_fma(a, b, c)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 7.0) < 0.3); +} + +test "fp8_relu_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_relu_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_relu_zero" { + given result = fp8_relu(FP8_ZERO_POS) + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_sigmoid_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.05); +} + +test "fp8_sigmoid_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.95); +} + +test "fp8_sigmoid_negative" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 0.05); +} + +test "fp8_tanh_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded) < 0.05); +} + +test "fp8_tanh_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.95); +} + +test "fp8_tanh_negative" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= -0.95); +} + +test "fp8_gelu_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_gelu(value)); + try std.testing.expect(fp8_extract_sign(result) == 0); +} + +test "fp8_swish_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_swish_positive" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded > 0.0); +} + +test "fp8_quantize_to_int4_max" { + given value = fp8_encode_f32(2.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "fp8_quantize_to_int4_min" { + given value = fp8_encode_f32(-2.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "fp8_quantize_to_int8_max" { + given value = fp8_encode_f32(2.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result == 127); +} + +test "fp8_quantize_to_int8_min" { + given value = fp8_encode_f32(-2.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result == -128); +} + +test "fp8_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 7); + try std.testing.expect(mant = 4); + try std.testing.expect(fp = fp8_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = fp8_extract_sign(fp)); + try std.testing.expect(extracted_exp = fp8_extract_exponent(fp)); + try std.testing.expect(extracted_mant = fp8_extract_mantissa(fp)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fp8_bits_constant + assert BITS == 8 + +invariant fp8_sign_bits_one + assert SIGN_BITS == 1 + +invariant fp8_exp_bits_four + assert EXP_BITS == 4 + +invariant fp8_mant_bits_three + assert MANT_BITS == 3 + +invariant fp8_exp_bias_seven + assert EXP_BIAS == 7 + +invariant fp8_exp_max_all_ones + assert EXP_MAX == 0x0F + +invariant fp8_sign_mask_correct + assert SIGN_MASK == 0x80 + +invariant fp8_exp_mask_correct + assert EXP_MASK == 0x78 + +invariant fp8_mant_mask_correct + assert MANT_MASK == 0x07 + +invariant fp8_masks_sum_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFF + +invariant fp8_zero_pos_no_sign + assert (FP8_ZERO_POS & SIGN_MASK) == 0 + +invariant fp8_zero_neg_has_sign + assert (FP8_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant fp8_inf_pos_max_exp + assert (FP8_INF_POS & EXP_MASK) == EXP_MASK + +invariant fp8_inf_pos_no_mant + assert (FP8_INF_POS & MANT_MASK) == 0 + +invariant fp8_nan_has_mantissa + assert (FP8_NAN_POS & MANT_MASK) != 0 + +invariant fp8_abs_removes_sign + assert fp8_abs(FP8_ZERO_NEG) == FP8_ZERO_POS + +invariant fp8_neg_toggles_sign + assert (fp8_neg(FP8_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant fp8_add_zero_identity + given value = fp8_encode_f32(5.0) + assert fp8_is_equal(fp8_add(value, FP8_ZERO_POS), value) == true + +invariant fp8_sub_zero_identity + given value = fp8_encode_f32(5.0) + assert fp8_is_equal(fp8_sub(value, FP8_ZERO_POS), value) == true + +invariant fp8_mul_one_identity + given value = fp8_encode_f32(5.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_mul(value, one), value) == true or fp8_magnitude(value) > 0.5 + +invariant fp8_div_one_identity + given value = fp8_encode_f32(5.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_div(value, one), value) == true + +invariant fp8_max_ge_both + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(fp8_is_greater(result, a) == true and fp8_is_greater(result, b) == true or fp8_is_equal(result, b) == true); + +invariant fp8_min_le_both + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(fp8_is_greater(a, result) == true or fp8_is_equal(a, result) == true); + +invariant fp8_relu_non_negative + given result = fp8_relu(fp8_encode_f32(5.0)) + try std.testing.expect(fp8_extract_sign(result) == 0); + +invariant fp8_lerp_zero_start + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + assert fp8_is_equal(fp8_lerp(a, b, 0.0), a) == true + +invariant fp8_lerp_one_end + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + assert fp8_is_equal(fp8_lerp(a, b, 1.0), b) == true + +invariant fp8_sqrt_square_close + given value = fp8_encode_f32(4.0) + try std.testing.expect(sqrt_val = fp8_sqrt(value)); + try std.testing.expect(sq_val = fp8_mul(sqrt_val, sqrt_val)); + try std.testing.expect(decoded = fp8_decode_f32(sq_val)); + try std.testing.expect(abs(decoded - 4.0) < 0.5); + +invariant fp8_quantize_to_int4_in_range + given result = fp8_quantize_to_int4(fp8_encode_f32(1.0)) + assert result >= -8 and result <= 7 + +invariant fp8_quantize_to_int8_in_range + given result = fp8_quantize_to_int8(fp8_encode_f32(1.0)) + assert result >= -128 and result <= 127 + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fp8_encode_f32_latency + measure: nanoseconds to fp8_encode_f32(1.5) + target: < 50ns + +bench fp8_decode_f32_latency + measure: nanoseconds to fp8_decode_f32(0x3C) + target: < 50ns + +bench fp8_add_latency + measure: nanoseconds to fp8_add(0x3C, 0x48) + target: < 100ns + +bench fp8_mul_latency + measure: nanoseconds to fp8_mul(0x3C, 0x3C) + target: < 100ns + +bench fp8_sqrt_latency + measure: nanoseconds to fp8_sqrt(0x30) + target: < 100ns + +bench fp8_sigmoid_latency + measure: nanoseconds to fp8_sigmoid(0x30) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/fp8_e5m2.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/fp8_e5m2.t27 new file mode 100644 index 0000000000..e46e52429f --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/fp8_e5m2.t27 @@ -0,0 +1,1212 @@ +// SPDX-License-Identifier: Apache-2.0 +; fp8_e5m2.t27 — FP8 E5M2 8-bit Floating Point +; 8-bit float with 5 exponent, 2 mantissa bits (with implicit leading bit) +; OCP FP8 format optimized for inference and wide dynamic range +; Range: ~-57k to ~57k, precision: ~3-4 significant bits +; φ² + 1/φ² = 3 | TRINITY + +module triformat-fp8_e5m2; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 5; +pub const MANT_BITS : u8 = 2; + +pub const SIGN_SHIFT : u8 = 7; +pub const EXP_SHIFT : u8 = 2; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u8 = 0x80; // 1 << 7 +pub const EXP_MASK : u8 = 0x7C; // 0b01111100 +pub const MANT_MASK : u8 = 0x03; // 0b00000011 + +pub const EXP_MAX : u8 = 31; // All ones in 5 bits +pub const EXP_MIN : u8 = 0; +pub const EXP_BIAS : u8 = 15; // Exponent bias for FP8 E5M2 + +pub const MANT_DIVISOR : u8 = 4; // 2^2 + +// FP8 E5M2 special values +pub const FP8_ZERO_POS : u8 = 0x00; +pub const FP8_ZERO_NEG : u8 = 0x80; +pub const FP8_NAN_POS : u8 = 0x7F; // All exp + mant != 0 +pub const FP8_NAN_NEG : u8 = 0xFF; +pub const FP8_INF_POS : u8 = 0x7C; // All exp + mant = 0 +pub const FP8_INF_NEG : u8 = 0xFC; + +// Subnormal exponent +pub const SUBNORMAL_EXP : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FP8E5M2 = u8; // 8-bit value + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// fp8_extract_sign(fp8: FP8E5M2) -> i8 +// Extract sign bit (bit 7) +// Returns: 0 for positive, -1 for negative +pub fn fp8_extract_sign(fp8: FP8E5M2) i8 { + const bit = (fp8 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// fp8_extract_exponent(fp8: FP8E5M2) -> u8 +// Extract exponent bits (bits 6-2) +// Returns: 0-31 +pub fn fp8_extract_exponent(fp8: FP8E5M2) u8 { + return (fp8 >> EXP_SHIFT) & 0x1F; +} + +// fp8_extract_mantissa(fp8: FP8E5M2) -> u8 +// Extract mantissa bits (bits 1-0) +// Returns: 0-3 +pub fn fp8_extract_mantissa(fp8: FP8E5M2) u8 { + return fp8 & MANT_MASK; +} + +// fp8_from_components(sign: i8, exp: u8, mant: u8) -> FP8E5M2 +// Assemble FP8 from sign, exponent, mantissa +pub fn fp8_from_components(sign: i8, exp: u8, mant: u8) FP8E5M2 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(FP8E5M2, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(FP8E5M2, @intCast(exp & 0x1F)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// fp8_is_zero(fp8: FP8E5M2) -> bool +// Check if FP8 is zero (positive or negative) +pub fn fp8_is_zero(fp8: FP8E5M2) bool { + return fp8 == FP8_ZERO_POS or fp8 == FP8_ZERO_NEG; +} + +// fp8_is_special(fp8: FP8E5M2) -> bool +// Check if FP8 is Inf or NaN (exp == 31) +pub fn fp8_is_special(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX; +} + +// fp8_is_inf(fp8: FP8E5M2) -> bool +// Check if FP8 is infinity (exp == 31, mant == 0) +pub fn fp8_is_inf(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) == 0; +} + +// fp8_is_nan(fp8: FP8E5M2) -> bool +// Check if FP8 is NaN (exp == 31, mant != 0) +pub fn fp8_is_nan(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) != 0; +} + +// fp8_is_subnormal(fp8: FP8E5M2) -> bool +// Check if FP8 is subnormal (exp == 0, mant != 0) +pub fn fp8_is_subnormal(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == 0 and fp8_extract_mantissa(fp8) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// fp8_encode_f32(value: f32) -> FP8E5M2 +// Encode IEEE 754 single precision to FP8 E5M2 +// Round-to-nearest, ties to even +pub fn fp8_encode_f32(value: f32) FP8E5M2 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return if (value < 0.0) FP8_NAN_NEG else FP8_NAN_POS; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i16 = @as(i16, @intCast((f32_bits >> 23) & 0xFF)) - 127; + var f32_mant: u32 = f32_bits & 0x007FFFFF; + + // Handle very small values (subnormals) + if (f32_exp <= @as(i16, -EXP_BIAS)) { + // Below FP8 minimum normal - try to encode as subnormal + const subnormal_shift = @as(i16, -EXP_BIAS) - f32_exp + 1; + if (subnormal_shift > 2 + 23) { + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + var mant = f32_mant >> 23; + if (subnormal_shift > 0) { + const shift_amount = @as(u5, @intCast(subnormal_shift)); + if (shift_amount <= 24) { + mant = (f32_mant | 0x00800000) >> shift_amount; + } + } + return fp8_from_components(sign, 0, @as(u8, @truncate(mant)) & 0x03); + } + + // Convert exp from f32 bias (127) to FP8 bias (15) + var fp8_exp = @as(i16, f32_exp + EXP_BIAS); + + // Clamp exponent + if (fp8_exp >= EXP_MAX) { + return if (sign < 0) FP8_INF_NEG else FP8_INF_POS; + } else if (fp8_exp < 1) { + // Underflow to zero (could be subnormal, simplified) + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Extract mantissa and scale to 2 bits + // f32 mantissa is 24 bits (including implicit 1), FP8 needs 2 bits + // Shift right by 22 bits (24 - 2 = 22) + var mant = f32_mant >> 22; + + // Round-to-nearest with ties to even + const discarded = f32_mant & 0x003FFFFF; // Lower 22 bits + if ((discarded & 0x00200000) != 0) { + // Halfway or more + if ((discarded & 0x001FFFFF) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (fp8_exp < @as(i16, EXP_MAX - 1)) { + fp8_exp += 1; + } + } + } + } + + return fp8_from_components(sign, @as(u8, @intCast(fp8_exp)), @as(u8, mant)); +} + +// fp8_decode_f32(fp8: FP8E5M2) -> f32 +// Decode FP8 E5M2 to IEEE 754 single precision +pub fn fp8_decode_f32(fp8: FP8E5M2) f32 { + // Handle zero + if (fp8_is_zero(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (fp8_is_nan(fp8)) { + return std.math.nan(f32); + } + + // Handle Infinity + if (fp8_is_inf(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -std.math.inf(f32) else std.math.inf(f32); + } + + // Extract components + const sign = fp8_extract_sign(fp8); + const exp = fp8_extract_exponent(fp8); + const mant = fp8_extract_mantissa(fp8); + + var f32_value: f32 = undefined; + + // Handle subnormal + if (exp == 0) { + // Subnormal: value = mant * 2^(-14) = mant * 2^(1-bias) + const bias_adjusted: i8 = 1 - @as(i8, EXP_BIAS); + const mant_f32 = @as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR)); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } else { + // Normal: value = (1 + mant/4) * 2^(exp - 15) + const bias_adjusted = @as(i16, exp) - @as(i16, EXP_BIAS); + const mant_f32 = 1.0 + (@as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR))); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } + + return if (sign < 0) -f32_value else f32_value; +} + +// fp8_encode_f64(value: f64) -> FP8E5M2 +// Encode IEEE 754 double precision to FP8 E5M2 +pub fn fp8_encode_f64(value: f64) FP8E5M2 { + return fp8_encode_f32(@as(f32, @floatCast(value))); +} + +// fp8_decode_f64(fp8: FP8E5M2) -> f64 +// Decode FP8 E5M2 to IEEE 754 double precision +pub fn fp8_decode_f64(fp8: FP8E5M2) f64 { + const f32_val = fp8_decode_f32(fp8); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// fp8_add(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Add two FP8 values +pub fn fp8_add(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa + fb); +} + +// fp8_sub(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Subtract two FP8 values +pub fn fp8_sub(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa - fb); +} + +// fp8_mul(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Multiply two FP8 values +pub fn fp8_mul(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa * fb); +} + +// fp8_div(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Divide two FP8 values +pub fn fp8_div(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fb = fp8_decode_f32(b); + if (fb == 0.0) { + const fa = fp8_decode_f32(a); + return if (fa < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + const fa = fp8_decode_f32(a); + return fp8_encode_f32(fa / fb); +} + +// fp8_sqrt(value: FP8E5M2) -> FP8E5M2 +// Square root of FP8 value +pub fn fp8_sqrt(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + if (fv < 0.0) { + return FP8_NAN_POS; + } + return fp8_encode_f32(std.math.sqrt(fv)); +} + +// fp8_rsqrt(value: FP8E5M2) -> FP8E5M2 +// Reciprocal square root (1/sqrt(x)) +pub fn fp8_rsqrt(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + if (fv <= 0.0) { + return FP8_INF_POS; + } + return fp8_encode_f32(1.0 / std.math.sqrt(fv)); +} + +// fp8_abs(value: FP8E5M2) -> FP8E5M2 +// Absolute value of FP8 +pub fn fp8_abs(value: FP8E5M2) FP8E5M2 { + return value & ~SIGN_MASK; +} + +// fp8_neg(value: FP8E5M2) -> FP8E5M2 +// Negate FP8 +pub fn fp8_neg(value: FP8E5M2) FP8E5M2 { + return value ^ SIGN_MASK; +} + +// fp8_is_equal(a: FP8E5M2, b: FP8E5M2) -> bool +// Check if two FP8 values are equal +pub fn fp8_is_equal(a: FP8E5M2, b: FP8E5M2) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + if (fp8_is_zero(a) and fp8_is_zero(b)) { + return true; + } + return a == b; +} + +// fp8_is_greater(a: FP8E5M2, b: FP8E5M2) -> bool +// Check if a > b +pub fn fp8_is_greater(a: FP8E5M2, b: FP8E5M2) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + const sign_a = fp8_extract_sign(a); + const sign_b = fp8_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return fp8_neg(a) > fp8_neg(b); + } + return a > b; +} + +// fp8_max(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Return the larger of two FP8 values +pub fn fp8_max(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + return if (fp8_is_greater(a, b)) a else b; +} + +// fp8_min(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Return the smaller of two FP8 values +pub fn fp8_min(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + return if (fp8_is_greater(a, b)) b else a; +} + +// fp8_clamp(value: FP8E5M2, min: FP8E5M2, max: FP8E5M2) -> FP8E5M2 +// Clamp value between min and max +pub fn fp8_clamp(value: FP8E5M2, min: FP8E5M2, max: FP8E5M2) FP8E5M2 { + return fp8_min(fp8_max(value, min), max); +} + +// fp8_lerp(a: FP8E5M2, b: FP8E5M2, t: f32) -> FP8E5M2 +// Linear interpolation between a and b +pub fn fp8_lerp(a: FP8E5M2, b: FP8E5M2, t: f32) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const result = fa + (fb - fa) * t; + return fp8_encode_f32(result); +} + +// fp8_fma(a: FP8E5M2, b: FP8E5M2, c: FP8E5M2) -> FP8E5M2 +// Fused multiply-add: a * b + c +pub fn fp8_fma(a: FP8E5M2, b: FP8E5M2, c: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const fc = fp8_decode_f32(c); + return fp8_encode_f32(fa * fb + fc); +} + +// fp8_scale(value: FP8E5M2, scale: f32) -> FP8E5M2 +// Scale FP8 value by scalar +pub fn fp8_scale(value: FP8E5M2, scale: f32) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * scale); +} + +// fp8_relu(value: FP8E5M2) -> FP8E5M2 +// ReLU activation: max(0, x) +pub fn fp8_relu(value: FP8E5M2) FP8E5M2 { + return if (fp8_extract_sign(value) < 0) FP8_ZERO_POS else value; +} + +// fp8_sigmoid(value: FP8E5M2) -> FP8E5M2 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn fp8_sigmoid(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// fp8_tanh(value: FP8E5M2) -> FP8E5M2 +// Tanh activation +pub fn fp8_tanh(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(std.math.tanh(fv)); +} + +// fp8_gelu(value: FP8E5M2) -> FP8E5M2 +// GELU activation +pub fn fp8_gelu(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + const sqrt_2_over_pi = 0.7978845608; + const cube_coeff = 0.044715; + const tanh_arg = fv * (1.0 + cube_coeff * fv * fv); + return fp8_encode_f32(0.5 * fv * (1.0 + std.math.tanh(sqrt_2_over_pi * tanh_arg))); +} + +// fp8_swish(value: FP8E5M2) -> FP8E5M2 +// Swish activation: x * sigmoid(x) +pub fn fp8_swish(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * (1.0 / (1.0 + std.math.exp(-fv)))); +} + +// fp8_quantize_to_int4(value: FP8E5M2) -> i8 +// Quantize FP8 to Int4 (range [-8, 7]) +pub fn fp8_quantize_to_int4(value: FP8E5M2) i8 { + const fv = fp8_decode_f32(value); + // E5M2 has wider range, so scale differently + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// fp8_quantize_to_int8(value: FP8E5M2) -> i8 +// Quantize FP8 to Int8 (range [-128, 127]) +pub fn fp8_quantize_to_int8(value: FP8E5M2) i8 { + const fv = fp8_decode_f32(value); + // E5M2 has wider range, so scale differently + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 127.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 127) { + result = 127; + } else if (result < -128) { + result = -128; + } + return @as(i8, @intCast(result)); +} + +// fp8_from_f32_scaled(value: f32, scale: f32) -> FP8E5M2 +// Encode f32 to FP8 with custom scale factor +// Useful for quantization with specific dynamic range +pub fn fp8_from_f32_scaled(value: f32, scale: f32) FP8E5M2 { + return fp8_encode_f32(value * scale); +} + +// fp8_to_f32_scaled(value: FP8E5M2, scale: f32) -> f32 +// Decode FP8 to f32 with custom scale factor +pub fn fp8_to_f32_scaled(value: FP8E5M2, scale: f32) f32 { + return fp8_decode_f32(value) / scale; +} + +// fp8_dot_product(a: []const FP8E5M2, b: []const FP8E5M2) -> FP8E5M2 +// Dot product of two FP8 arrays (fixed size 8 for example) +// Returns accumulated FP8 value +pub fn fp8_dot_product(a: []const FP8E5M2, b: []const FP8E5M2) FP8E5M2 { + var sum: f32 = 0.0; + for (a, 0..) |val_a, i| { + if (i >= b.len) break; + const fa = fp8_decode_f32(val_a); + const fb = fp8_decode_f32(b[i]); + sum += fa * fb; + } + return fp8_encode_f32(sum); +} + +// fp8_vector_add(a: []const FP8E5M2, b: []const FP8E5M2, result: []FP8E5M2) -> void +// Vector addition: result[i] = a[i] + b[i] +pub fn fp8_vector_add(a: []const FP8E5M2, b: []const FP8E5M2, result: []FP8E5M2) void { + const len = @min(a.len, b.len); + for (0..len) |i| { + result[i] = fp8_add(a[i], b[i]); + } +} + +// fp8_vector_scale(vec: []const FP8E5M2, scale: FP8E5M2, result: []FP8E5M2) -> void +// Vector scale: result[i] = vec[i] * scale +pub fn fp8_vector_scale(vec: []const FP8E5M2, scale: FP8E5M2, result: []FP8E5M2) void { + const scale_f = fp8_decode_f32(scale); + for (vec, 0..) |val, i| { + result[i] = fp8_encode_f32(fp8_decode_f32(val) * scale_f); + } +} + +// fp8_vector_sum(vec: []const FP8E5M2) -> FP8E5M2 +// Sum all elements of a vector +pub fn fp8_vector_sum(vec: []const FP8E5M2) FP8E5M2 { + var sum: f32 = 0.0; + for (vec) |val| { + sum += fp8_decode_f32(val); + } + return fp8_encode_f32(sum); +} + +// fp8_vector_mean(vec: []const FP8E5M2) -> FP8E5M2 +// Mean of all elements of a vector +pub fn fp8_vector_mean(vec: []const FP8E5M2) FP8E5M2 { + if (vec.len == 0) { + return FP8_ZERO_POS; + } + const sum = fp8_vector_sum(vec); + return fp8_div(sum, fp8_from_f32(@as(f32, @floatFromInt(vec.len)))); +} + +// fp8_softmax(vec: []const FP8E5M2, result: []FP8E5M2) -> void +// Softmax activation: result[i] = exp(vec[i]) / sum(exp(vec)) +pub fn fp8_softmax(vec: []const FP8E5M2, result: []FP8E5M2) void { + // Find max for numerical stability + var max_val: f32 = -std.math.inf(f32); + for (vec) |val| { + const fv = fp8_decode_f32(val); + if (fv > max_val) { + max_val = fv; + } + } + + // Compute exp values and sum + var sum_exp: f32 = 0.0; + for (vec, 0..) |val, i| { + const fv = fp8_decode_f32(val); + const exp_val = std.math.exp(fv - max_val); + result[i] = fp8_encode_f32(exp_val); + sum_exp += exp_val; + } + + // Normalize + const inv_sum = 1.0 / sum_exp; + for (result, 0..) |val, i| { + const fv = fp8_decode_f32(val); + result[i] = fp8_encode_f32(fv * inv_sum); + } +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fp8_extract_sign_positive" { + given value = 0x3C + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "fp8_extract_sign_negative" { + given value = 0xBC + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "fp8_extract_exponent_max" { + given value = 0x7C + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == EXP_MAX); +} + +test "fp8_extract_exponent_zero" { + given value = 0x03 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == 0); +} + +test "fp8_extract_mantissa_max" { + given value = 0x03 + try std.testing.expect(mant = fp8_extract_mantissa(value)); + try std.testing.expect(mant == MANT_MASK); +} + +test "fp8_is_zero_positive" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_POS) == true); +} + +test "fp8_is_zero_negative" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_NEG) == true); +} + +test "fp8_is_zero_nonzero" { + try std.testing.expect(fp8_is_zero(0x01) == false); +} + +test "fp8_is_inf_positive" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true); +} + +test "fp8_is_inf_negative" { + try std.testing.expect(fp8_is_inf(FP8_INF_NEG) == true); +} + +test "fp8_is_nan_positive" { + try std.testing.expect(fp8_is_nan(FP8_NAN_POS) == true); +} + +test "fp8_is_nan_negative" { + try std.testing.expect(fp8_is_nan(FP8_NAN_NEG) == true); +} + +test "fp8_is_inf_not_nan" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true and fp8_is_nan(FP8_INF_POS) == false); +} + +test "fp8_is_special_inf" { + try std.testing.expect(fp8_is_special(FP8_INF_POS) == true); +} + +test "fp8_is_special_nan" { + try std.testing.expect(fp8_is_special(FP8_NAN_POS) == true); +} + +test "fp8_is_subnormal_true" { + given value = 0x01 + try std.testing.expect(fp8_is_subnormal(value) == true); +} + +test "fp8_is_subnormal_false_normal" { + given value = 0x04 + try std.testing.expect(fp8_is_subnormal(value) == false); +} + +test "fp8_is_subnormal_false_zero" { + try std.testing.expect(fp8_is_subnormal(FP8_ZERO_POS) == false); +} + +test "fp8_encode_f32_zero" { + given fp = fp8_encode_f32(0.0) + try std.testing.expect(fp == FP8_ZERO_POS); +} + +test "fp8_encode_f32_negative_zero" { + given fp = fp8_encode_f32(-0.0) + try std.testing.expect(fp == FP8_ZERO_NEG); +} + +test "fp8_encode_f32_one" { + given fp = fp8_encode_f32(1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 1.0) < 0.15); +} + +test "fp8_encode_f32_negative_one" { + given fp = fp8_encode_f32(-1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded + 1.0) < 0.15); +} + +test "fp8_encode_f32_ten" { + given fp = fp8_encode_f32(10.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 10.0) < 1.0); +} + +test "fp8_encode_f32_hundred" { + given fp = fp8_encode_f32(100.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 100.0) < 10.0); +} + +test "fp8_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "fp8_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "fp8_encode_f32_nan" { + given fp = fp8_encode_f32(std.math.nan(f32)) + try std.testing.expect(fp8_is_nan(fp) == true); +} + +test "fp8_encode_f32_inf" { + given fp = fp8_encode_f32(std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_encode_f32_neg_inf" { + given fp = fp8_encode_f32(-std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_decode_f32_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_POS) == 0.0); +} + +test "fp8_decode_f32_negative_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_NEG) == -0.0); +} + +test "fp8_decode_f32_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_POS)) == true); +} + +test "fp8_decode_f32_neg_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_NEG)) == true); +} + +test "fp8_decode_f32_nan" { + try std.testing.expect(std.math.isNan(fp8_decode_f32(FP8_NAN_POS)) == true); +} + +test "fp8_add_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(20.0)); + try std.testing.expect(result = fp8_add(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 30.0) < 3.0); +} + +test "fp8_sub_simple" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(30.0)); + try std.testing.expect(result = fp8_sub(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 3.0); +} + +test "fp8_mul_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(result = fp8_mul(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 15.0); +} + +test "fp8_div_simple" { + given a = fp8_encode_f32(100.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "fp8_div_by_zero_positive" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_POS); +} + +test "fp8_div_by_zero_negative" { + given a = fp8_encode_f32(-50.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_NEG); +} + +test "fp8_sqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.3); +} + +test "fp8_sqrt_negative" { + given value = fp8_encode_f32(-4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(fp8_is_nan(result) == true); +} + +test "fp8_rsqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_rsqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_abs_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(result = fp8_abs(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_abs_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(abs_val = fp8_abs(value)); + try std.testing.expect(fp8_extract_sign(abs_val) == 0); +} + +test "fp8_neg_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) < 0); +} + +test "fp8_neg_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) == 0); +} + +test "fp8_is_equal_true" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(fp8_is_equal(value, value) == true); +} + +test "fp8_is_equal_false" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(20.0)); + try std.testing.expect(fp8_is_equal(a, b) == false); +} + +test "fp8_is_equal_nan" { + try std.testing.expect(fp8_is_equal(FP8_NAN_POS, FP8_NAN_POS) == false); +} + +test "fp8_is_equal_zero" { + try std.testing.expect(fp8_is_equal(FP8_ZERO_POS, FP8_ZERO_NEG) == true); +} + +test "fp8_is_greater_positive" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(30.0)); + try std.testing.expect(fp8_is_greater(a, b) == true); +} + +test "fp8_max_returns_larger" { + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 40.0); +} + +test "fp8_min_returns_smaller" { + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 40.0); +} + +test "fp8_lerp_zero" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, a) == true); +} + +test "fp8_lerp_one" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, b) == true); +} + +test "fp8_fma_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(c = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_fma(a, b, c)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 105.0) < 15.0); +} + +test "fp8_scale_up" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(scale = 10.0); + try std.testing.expect(result = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 1.0); +} + +test "fp8_scale_down" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(scale = 0.1); + try std.testing.expect(result = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "fp8_relu_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_relu_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_relu_zero" { + given result = fp8_relu(FP8_ZERO_POS) + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_sigmoid_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_sigmoid_positive_large" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "fp8_sigmoid_negative_large" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 0.1); +} + +test "fp8_tanh_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded) < 0.1); +} + +test "fp8_tanh_positive_large" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "fp8_tanh_negative_large" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= -0.9); +} + +test "fp8_gelu_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_gelu(value)); + try std.testing.expect(fp8_extract_sign(result) == 0); +} + +test "fp8_swish_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_swish_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded > 0.0); +} + +test "fp8_quantize_to_int4_max" { + given value = fp8_encode_f32(100.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "fp8_quantize_to_int4_min" { + given value = fp8_encode_f32(-100.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "fp8_quantize_to_int8_max" { + given value = fp8_encode_f32(200.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result >= 100); +} + +test "fp8_quantize_to_int8_min" { + given value = fp8_encode_f32(-200.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result <= -100); +} + +test "fp8_from_f32_scaled_up" { + given value = 1.0 + try std.testing.expect(scale = 100.0); + try std.testing.expect(result = fp8_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 10.0); +} + +test "fp8_from_f32_scaled_down" { + given value = 100.0 + try std.testing.expect(scale = 0.01); + try std.testing.expect(result = fp8_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "fp8_to_f32_scaled_up" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(scale = 0.01); + try std.testing.expect(result = fp8_to_f32_scaled(value, scale)); + try std.testing.expect(abs(result - 0.01) < 0.005); +} + +test "fp8_dot_product_simple" { + given a = [_]FP8E5M2{fp8_encode_f32(1.0), fp8_encode_f32(2.0), fp8_encode_f32(3.0), fp8_encode_f32(4.0)} + try std.testing.expect(b = [_]FP8E5M2{fp8_encode_f32(2.0), fp8_encode_f32(2.0), fp8_encode_f32(2.0), fp8_encode_f32(2.0)}); + try std.testing.expect(result = fp8_dot_product(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "fp8_vector_sum_simple" { + given vec = [_]FP8E5M2{fp8_encode_f32(1.0), fp8_encode_f32(2.0), fp8_encode_f32(3.0), fp8_encode_f32(4.0)} + try std.testing.expect(result = fp8_vector_sum(vec)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 2.0); +} + +test "fp8_vector_mean_simple" { + given vec = [_]FP8E5M2{fp8_encode_f32(2.0), fp8_encode_f32(4.0), fp8_encode_f32(6.0), fp8_encode_f32(8.0)} + try std.testing.expect(result = fp8_vector_mean(vec)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 5.0) < 1.0); +} + +test "fp8_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 15); + try std.testing.expect(mant = 2); + try std.testing.expect(fp = fp8_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = fp8_extract_sign(fp)); + try std.testing.expect(extracted_exp = fp8_extract_exponent(fp)); + try std.testing.expect(extracted_mant = fp8_extract_mantissa(fp)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fp8_bits_constant + assert BITS == 8 + +invariant fp8_sign_bits_one + assert SIGN_BITS == 1 + +invariant fp8_exp_bits_five + assert EXP_BITS == 5 + +invariant fp8_mant_bits_two + assert MANT_BITS == 2 + +invariant fp8_exp_bias_fifteen + assert EXP_BIAS == 15 + +invariant fp8_exp_max_all_ones + assert EXP_MAX == 0x1F + +invariant fp8_sign_mask_correct + assert SIGN_MASK == 0x80 + +invariant fp8_exp_mask_correct + assert EXP_MASK == 0x7C + +invariant fp8_mant_mask_correct + assert MANT_MASK == 0x03 + +invariant fp8_masks_sum_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFF + +invariant fp8_zero_pos_no_sign + assert (FP8_ZERO_POS & SIGN_MASK) == 0 + +invariant fp8_zero_neg_has_sign + assert (FP8_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant fp8_inf_pos_max_exp + assert (FP8_INF_POS & EXP_MASK) == EXP_MASK + +invariant fp8_inf_pos_no_mant + assert (FP8_INF_POS & MANT_MASK) == 0 + +invariant fp8_nan_has_mantissa + assert (FP8_NAN_POS & MANT_MASK) != 0 + +invariant fp8_abs_removes_sign + assert fp8_abs(FP8_ZERO_NEG) == FP8_ZERO_POS + +invariant fp8_neg_toggles_sign + assert (fp8_neg(FP8_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant fp8_add_zero_identity + given value = fp8_encode_f32(50.0) + assert fp8_is_equal(fp8_add(value, FP8_ZERO_POS), value) == true + +invariant fp8_sub_zero_identity + given value = fp8_encode_f32(50.0) + assert fp8_is_equal(fp8_sub(value, FP8_ZERO_POS), value) == true + +invariant fp8_mul_one_identity + given value = fp8_encode_f32(50.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_mul(value, one), value) == true or fp8_magnitude(value) > 10.0 + +invariant fp8_div_one_identity + given value = fp8_encode_f32(50.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_div(value, one), value) == true + +invariant fp8_max_ge_both + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(fp8_is_greater(result, a) == true and fp8_is_greater(result, b) == true or fp8_is_equal(result, b) == true); + +invariant fp8_min_le_both + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(fp8_is_greater(a, result) == true or fp8_is_equal(a, result) == true); + +invariant fp8_relu_non_negative + given result = fp8_relu(fp8_encode_f32(50.0)) + try std.testing.expect(fp8_extract_sign(result) == 0); + +invariant fp8_lerp_zero_start + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + assert fp8_is_equal(fp8_lerp(a, b, 0.0), a) == true + +invariant fp8_lerp_one_end + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + assert fp8_is_equal(fp8_lerp(a, b, 1.0), b) == true + +invariant fp8_sqrt_square_close + given value = fp8_encode_f32(4.0) + try std.testing.expect(sqrt_val = fp8_sqrt(value)); + try std.testing.expect(sq_val = fp8_mul(sqrt_val, sqrt_val)); + try std.testing.expect(decoded = fp8_decode_f32(sq_val)); + try std.testing.expect(abs(decoded - 4.0) < 0.5); + +invariant fp8_quantize_to_int4_in_range + given result = fp8_quantize_to_int4(fp8_encode_f32(50.0)) + assert result >= -8 and result <= 7 + +invariant fp8_quantize_to_int8_in_range + given result = fp8_quantize_to_int8(fp8_encode_f32(50.0)) + assert result >= -128 and result <= 127 + +invariant fp8_scale_roundtrip + given value = fp8_encode_f32(50.0) + try std.testing.expect(scale = 2.0); + try std.testing.expect(scaled = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_to_f32_scaled(scaled, scale)); + try std.testing.expect(abs(decoded - fp8_decode_f32(value)) < 5.0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fp8_encode_f32_latency + measure: nanoseconds to fp8_encode_f32(10.5) + target: < 50ns + +bench fp8_decode_f32_latency + measure: nanoseconds to fp8_decode_f32(0x3C) + target: < 50ns + +bench fp8_add_latency + measure: nanoseconds to fp8_add(0x3C, 0x48) + target: < 100ns + +bench fp8_mul_latency + measure: nanoseconds to fp8_mul(0x3C, 0x3C) + target: < 100ns + +bench fp8_sqrt_latency + measure: nanoseconds to fp8_sqrt(0x30) + target: < 100ns + +bench fp8_sigmoid_latency + measure: nanoseconds to fp8_sigmoid(0x30) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/gf12.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/gf12.t27 new file mode 100644 index 0000000000..9629152355 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/gf12.t27 @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf12.t27 +// GoldenFloat12 0 12-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 4 (P1) + +module GF12 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF12 bit layout: [S|EEEE|MMM MMMM] + // S: 1 bit (sign) + // E: 4 bits (exponent) + // M: 7 bits (mantissa) + + const BITS : u8 = 12; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 4; + const MANT_BITS : u8 = 7; + + // Bias for exponent (2^(4-1) - 1 = 7) + const EXP_BIAS : u8 = 7; + + // 141-ratio: exp/mant = 4/7 142 0.571 (phi_distance = 0.047) + // This is the closest to 1/143 among all formats + const PHI_DISTANCE : f64 = 0.04660512288042107; + + // 144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 + // 2. GoldenFloat12 Type + // 209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 + + struct GF12 { + raw : u16, // 12-bit value stored in u16 + } + + // 282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 + // 3. Encoding/Decoding + // 347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 + + // Encode f32 to GF12 + fn encode(value: f32) -> GF12 { + if (value == 0.0) { + return GF12{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i8; + const exp_biased = (exp_unbiased + EXP_BIAS as i8) as u8; + + // Clamp exponent + const exp_clamped = clamp(exp_biased, 0, (1 << EXP_BITS) - 1); + + // Extract mantissa (7 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF12{ + raw = ((sign as u16) << 11) | ((exp_clamped as u16) << MANT_BITS) | (mant as u16) + }; + } + + // Decode GF12 to f32 + fn decode(gf: GF12) -> f32 { + const sign = (gf.raw >> 11) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x0F) as u8; + const mant = (gf.raw & 0x7F) as u8; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS as i8 + 1 + } else { + (exp_biased as i8) - EXP_BIAS as i8 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 128.0 + } else { + 1.0 + (mant as f32) / 128.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 + // 4. Format Properties + // 485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 + + fn max_value() -> f32 { + const mant_max = 1.0 + 127.0 / 128.0; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 128.0; + const exp_min = -EXP_BIAS as i8 + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 128.0; // 0.0078125 + } + + // 558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 + // 5. Validation + // 623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF12"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760 + // 6. Use Cases + // 761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833 + + // GF12 is optimal for: + // - Best 834-approximation (lowest phi_distance) + // - High-precision quantization + // - Critical path weights + // - Attention matrices + + // Memory: 12 bits = 1.5 bytes (~2.67x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 12.0 / 32.0; // 0.375 + + // 835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899 + // 7. Helper Functions + // 900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972 + + fn floor_log2(x: f32) -> i8 { + if (x <= 0.0) { return -128; } + let exp : i8 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i8, mant_bits: u8) -> u8 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u8; + } + + fn clamp(x: u8, min: u8, max: u8) -> u8 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF12 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 9739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075 + // TDD-Inside-Spec: Tests and Invariants for GF12 + // 1076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178 + + test gf12_decode_zero + given gf = GF12{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf12_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf12_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf12_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf12_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf12_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf12_phi_distance_lowest + given phi_dist = PHI_DISTANCE + then phi_dist < 0.05 + + test gf12_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.375) < 0.01 + + test gf12_validate_format_success + given valid = validate_format() + then valid == true + + test gf12_floor_log2_power_of_two + given log_result = floor_log2(8.0) + then log_result == 3 + + test gf12_extract_mantissa_in_range + given mant = extract_mantissa(1.5, 0, 7) + then mant < 128 + + invariant gf12_bits_constant + assert BITS == 12 + + invariant gf12_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf12_exp_bits_is_four + assert EXP_BITS == 4 + + invariant gf12_mant_bits_is_seven + assert MANT_BITS == 7 + + invariant gf12_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf12_phi_distance_below_threshold + assert PHI_DISTANCE < 0.05 + + invariant gf12_exp_bias_positive + assert EXP_BIAS > 0 + + test gf12_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf12_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf12_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf12_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf12_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf12_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf12_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf12_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf12_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf12_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf12_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf12_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf12_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf12_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf12_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf12_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf12_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf12_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf12_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf12_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf12_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf12_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf12_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf12_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf12_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + invariant gf12_floor_log2_non_negative_input + assert floor_log2(1.0) >= 0 + + invariant gf12_extract_mantissa_in_valid_range + assert extract_mantissa(1.0, 0, 7) < 128 + + bench gf12_encode_latency + measure: nanoseconds to encode(1.0) + target: < 150ns + + bench gf12_decode_latency + measure: nanoseconds to decode(GF12{raw = 1024}) + target: < 100ns +} diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/gf128.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/gf128.t27 new file mode 100644 index 0000000000..f32ee7a8a1 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/gf128.t27 @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf128.t27 — GoldenFloat128 Encode/Decode +; GF128: 128-bit floating point with 1 sign + 28 exponent + 99 mantissa +; Bit layout: [S(1) E(28) M(99)] = [127:127][126:99][98:0] +; Extended range format for high-precision scientific computing +; φ² + 1/φ² = 3 | TRINITY + +module triformat-gf128; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const SIGN_SHIFT : u8 = 127; +pub const EXP_SHIFT : u8 = 99; +pub const MANT_SHIFT : u8 = 0; + +// GF128 uses 128-bit representation +// Since Zig doesn't have u128 constants directly in all contexts, +// we use u64 pairs for mask constants + +pub const SIGN_MASK_HI : u64 = 0x8000000000000000; // 1 << 63 of high part +pub const SIGN_MASK_LO : u64 = 0x0000000000000000; + +pub const EXP_BITS : u8 = 28; +pub const MANT_BITS : u8 = 99; +pub const SIGN_BITS : u8 = 1; + +pub const EXP_MAX : u32 = 0x0FFFFFFF; // 268435455 (all ones in 28 bits) +pub const EXP_MIN : u32 = 0x00000000; + +pub const BIAS : i32 = 134217727; // Exponent bias for GF128: 2^(28-1) - 1 +pub const SPECIAL_EXP : u32 = 0x0FFFFFFF; // All ones = special (Inf/NaN) + +// MANT_DIVISOR = 2^99 is too large for u64, handled in functions + +pub const PHI_BIAS : u32 = 3932160; // Phi-optimized rounding bias + +// ============================================================================ +// Types +// ============================================================================ + +// GF128 represented as u128 in Zig 0.15+ +pub const GF128 = u128; + +// Helper struct for operations that need high/low parts +pub struct GF128Parts { + hi: u64, + lo: u64, +} + +// ============================================================================ +// Conversion Helpers +// ============================================================================ + +// gf128_to_parts(gf128: GF128) -> GF128Parts +// Split GF128 into high and low 64-bit parts +pub fn gf128_to_parts(gf128: GF128) GF128Parts { + return GF128Parts{ + .hi = @as(u64, @truncate(gf128 >> 64)), + .lo = @as(u64, @truncate(gf128)), + }; +} + +// gf128_from_parts(parts: GF128Parts) -> GF128 +// Combine high and low 64-bit parts into GF128 +pub fn gf128_from_parts(parts: GF128Parts) GF128 { + return (@as(GF128, parts.hi) << 64) | @as(GF128, parts.lo); +} + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// gf128_extract_sign(gf128: GF128) -> i8 +// Extract sign bit (bit 127) +// Returns: 0 for positive, -1 for negative +pub fn gf128_extract_sign(gf128: GF128) i8 { + const parts = gf128_to_parts(gf128); + const bit = (parts.hi >> 63) & 1; + return if (bit != 0) -1 else 0; +} + +// gf128_extract_exponent(gf128: GF128) -> u32 +// Extract exponent bits (bits 126-99) +// Returns: 0-268435455 +pub fn gf128_extract_exponent(gf128: GF128) u32 { + const parts = gf128_to_parts(gf128); + // Exponent spans bits 62-35 of high part (28 bits total) + // exp = (hi >> 35) & 0x0FFFFFFF + return @as(u32, @truncate((parts.hi >> 35) & 0x0FFFFFFF)); +} + +// gf128_extract_mantissa(gf128: GF128) -> u128 +// Extract mantissa bits (bits 98-0) +// Returns: 0-2^99-1 +pub fn gf128_extract_mantissa(gf128: GF128) u128 { + const parts = gf128_to_parts(gf128); + // Mantissa spans bits 34-0 of high part (35 bits) + all 64 bits of low part + // mant = ((hi & 0x00000007FFFFFFFF) << 64) | lo + const hi_mant = parts.hi & 0x00000007FFFFFFFF; + return (@as(u128, hi_mant) << 64) | @as(u128, parts.lo); +} + +// ============================================================================ +// Assembly Functions +// ============================================================================ + +// gf128_from_components(sign: i8, exp: u32, mant: u128) -> GF128 +// Assemble GF128 from sign, exponent, mantissa +pub fn gf128_from_components(sign: i8, exp: u32, mant: u128) GF128 { + const sign_bit = if (sign < 0) 1 else 0; + const clamped_exp = exp & EXP_MAX; + const clamped_mant = mant & (@as(u128, EXP_MAX) >> 1); // Mask for 99 bits (2^99 - 1) + + // exp goes at bits 126-99 (hi bits 62-35) + const hi_exp = @as(u64, @truncate(clamped_exp)) << 35; + // mant spans bits 98-0 (hi bits 34-0 + lo 64 bits) + const hi_mant = @as(u64, @truncate(clamped_mant >> 64)); + const lo_mant = @as(u64, @truncate(clamped_mant)); + + const hi = (@as(u64, @intCast(sign_bit)) << 63) | hi_exp | hi_mant; + const lo = lo_mant; + + return gf128_from_parts(GF128Parts{ .hi = hi, .lo = lo }); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +const GF128_ZERO_POS : GF128 = 0; +const GF128_ZERO_NEG : GF128 = (@as(GF128, 1) << 127); +const GF128_INF_POS : GF128 = (@as(GF128, EXP_MAX) << 99); +const GF128_INF_NEG : GF128 = GF128_INF_POS | (@as(GF128, 1) << 127); +const GF128_NAN : GF128 = GF128_INF_POS | 1; + +// gf128_is_zero(gf128: GF128) -> bool +// Check if GF128 is zero (positive or negative) +pub fn gf128_is_zero(gf128: GF128) bool { + return gf128 == GF128_ZERO_POS or gf128 == GF128_ZERO_NEG; +} + +// gf128_is_special(gf128: GF128) -> bool +// Check if GF128 is Inf or NaN (exp == 268435455) +pub fn gf128_is_special(gf128: GF128) bool { + return gf128_extract_exponent(gf128) == EXP_MAX; +} + +// gf128_is_inf(gf128: GF128) -> bool +// Check if GF128 is infinity (exp == max, mant == 0) +pub fn gf128_is_inf(gf128: GF128) bool { + return gf128_is_special(gf128) and gf128_extract_mantissa(gf128) == 0; +} + +// gf128_is_nan(gf128: GF128) -> bool +// Check if GF128 is NaN (exp == max, mant != 0) +pub fn gf128_is_nan(gf128: GF128) bool { + return gf128_is_special(gf128) and gf128_extract_mantissa(gf128) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// gf128_encode_f64(f64: f64) -> GF128 +// Encode IEEE 754 double precision to GF128 +pub fn gf128_encode_f64(value: f64) GF128 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) GF128_ZERO_NEG else GF128_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return GF128_NAN; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) GF128_INF_NEG else GF128_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f64 components + const f64_bits: u64 = @bitCast(abs_value); + var f64_exp: i32 = @as(i32, @intCast((f64_bits >> 52) & 0x7FF)) - 1023; + var f64_mant: u64 = f64_bits & 0x000FFFFFFFFFFFFF; + + // Convert exp from f64 bias (1023) to GF128 bias (134217727) + var gf128_exp = @as(u32, @intCast(f64_exp + BIAS)); + + // Clamp exponent + if (gf128_exp >= EXP_MAX) { + return if (sign < 0) GF128_INF_NEG else GF128_INF_POS; + } + + // Extend mantissa to 99 bits (f64 has 52 bits, pad with zeros) + const mant = @as(u128, f64_mant) << 47; // 52 + 47 = 99 + + return gf128_from_components(sign, gf128_exp, mant); +} + +// gf128_decode_f64(gf128: GF128) -> f64 +// Decode GF128 to IEEE 754 double precision +// Note: This may lose precision as f64 has only 52 mantissa bits +pub fn gf128_decode_f64(gf128: GF128) f64 { + // Handle zero + if (gf128_is_zero(gf128)) { + return if (gf128_extract_sign(gf128) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (gf128_is_nan(gf128)) { + return std.math.nan(f64); + } + + // Handle Infinity + if (gf128_is_inf(gf128)) { + return if (gf128_extract_sign(gf128) < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + // Extract components + const sign = gf128_extract_sign(gf128); + const exp = gf128_extract_exponent(gf128); + const mant = gf128_extract_mantissa(gf128); + + // Convert to f64 (may lose precision) + // Scale mantissa from 99 bits to 52 bits + const mant_f64 = @as(f64, @floatFromInt(mant >> 47)) / 4503599627370496.0; // 2^52 + const bias_adjusted = @as(i64, @intCast(exp)) - @as(i64, BIAS); + + if (bias_adjusted < -1022 or bias_adjusted > 1023) { + return if (sign < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + const value = mant_f64 * std.math.pow(f64, 2.0, @as(f64, @floatFromInt(bias_adjusted))); + return if (sign < 0) -value else value; +} + +// gf128_encode_f128(f128: f128) -> GF128 +// Encode IEEE 754 quad precision to GF128 (if available) +pub fn gf128_encode_f128(value: f128) GF128 { + // For systems without f128 support, fall back to f64 + // This is a placeholder - actual implementation would use f128 bit manipulation + const value_f64 = @as(f64, @floatCast(value)); + return gf128_encode_f64(value_f64); +} + +// gf128_decode_f128(gf128: GF128) -> f128 +// Decode GF128 to IEEE 754 quad precision (if available) +pub fn gf128_decode_f128(gf128: GF128) f128 { + const value_f64 = gf128_decode_f64(gf128); + return @as(f128, @floatCast(value_f64)); +} + +// ============================================================================ +// Arithmetic Operations (simplified via decode-encode) +// ============================================================================ + +// gf128_add(a: GF128, b: GF128) -> GF128 +pub fn gf128_add(a: GF128, b: GF128) GF128 { + return gf128_encode_f64(gf128_decode_f64(a) + gf128_decode_f64(b)); +} + +// gf128_sub(a: GF128, b: GF128) -> GF128 +pub fn gf128_sub(a: GF128, b: GF128) GF128 { + return gf128_encode_f64(gf128_decode_f64(a) - gf128_decode_f64(b)); +} + +// gf128_mul(a: GF128, b: GF128) -> GF128 +pub fn gf128_mul(a: GF128, b: GF128) GF128 { + return gf128_encode_f64(gf128_decode_f64(a) * gf128_decode_f64(b)); +} + +// gf128_div(a: GF128, b: GF128) -> GF128 +pub fn gf128_div(a: GF128, b: GF128) GF128 { + const fb = gf128_decode_f64(b); + if (fb == 0.0) { + const fa = gf128_decode_f64(a); + return if (fa < 0.0) GF128_INF_NEG else GF128_INF_POS; + } + return gf128_encode_f64(gf128_decode_f64(a) / fb); +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// gf128_abs(gf128: GF128) -> GF128 +pub fn gf128_abs(gf128: GF128) GF128 { + return gf128 & ~(@as(GF128, 1) << 127); +} + +// gf128_neg(gf128: GF128) -> GF128 +pub fn gf128_neg(gf128: GF128) GF128 { + return gf128 ^ (@as(GF128, 1) << 127); +} + +// gf128_is_equal(a: GF128, b: GF128) -> bool +pub fn gf128_is_equal(a: GF128, b: GF128) bool { + if (gf128_is_nan(a) or gf128_is_nan(b)) { + return false; + } + if (gf128_is_zero(a) and gf128_is_zero(b)) { + return true; + } + return a == b; +} + +// gf128_is_greater(a: GF128, b: GF128) -> bool +pub fn gf128_is_greater(a: GF128, b: GF128) bool { + if (gf128_is_nan(a) or gf128_is_nan(b)) { + return false; + } + const sign_a = gf128_extract_sign(a); + const sign_b = gf128_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return gf128_neg(a) > gf128_neg(b); + } + return a > b; +} + +// gf128_max(a: GF128, b: GF128) -> GF128 +pub fn gf128_max(a: GF128, b: GF128) GF128 { + return if (gf128_is_greater(a, b)) a else b; +} + +// gf128_min(a: GF128, b: GF128) -> GF128 +pub fn gf128_min(a: GF128, b: GF128) GF128 { + return if (gf128_is_greater(a, b)) b else a; +} + +// gf128_clamp(value: GF128, min: GF128, max: GF128) -> GF128 +pub fn gf128_clamp(value: GF128, min: GF128, max: GF128) GF128 { + return gf128_min(gf128_max(value, min), max); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf128_extract_sign_positive" { + given value = 0x00000000000000000000000000001234 + try std.testing.expect(sign = gf128_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "gf128_extract_sign_negative" { + given value = 0x80000000000000000000000000001234 + try std.testing.expect(sign = gf128_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "gf128_extract_exponent_middle" { + given value = 0x00008000000000000000000000000000 // exp = 134217728 + try std.testing.expect(exp = gf128_extract_exponent(value)); + try std.testing.expect(exp == 134217728); +} + +test "gf128_is_zero_positive" { + try std.testing.expect(gf128_is_zero(GF128_ZERO_POS) == true); +} + +test "gf128_is_zero_negative" { + try std.testing.expect(gf128_is_zero(GF128_ZERO_NEG) == true); +} + +test "gf128_is_inf_positive" { + try std.testing.expect(gf128_is_inf(GF128_INF_POS) == true); +} + +test "gf128_is_nan" { + try std.testing.expect(gf128_is_nan(GF128_NAN) == true); +} + +test "gf128_encode_f64_zero" { + given gf = gf128_encode_f64(0.0) + try std.testing.expect(gf == GF128_ZERO_POS); +} + +test "gf128_encode_f64_one" { + given gf = gf128_encode_f64(1.0) + try std.testing.expect(decoded = gf128_decode_f64(gf)); + try std.testing.expect(abs(decoded - 1.0) < 0.0001); +} + +test "gf128_encode_f64_negative_one" { + given gf = gf128_encode_f64(-1.0) + try std.testing.expect(decoded = gf128_decode_f64(gf)); + try std.testing.expect(abs(decoded + 1.0) < 0.0001); +} + +test "gf128_encode_f64_roundtrip" { + given original = 42.5 + try std.testing.expect(gf = gf128_encode_f64(original)); + try std.testing.expect(decoded = gf128_decode_f64(gf)); + try std.testing.expect(abs(decoded - original) < 0.001); +} + +test "gf128_add_simple" { + given a = gf128_encode_f64(1.0) + try std.testing.expect(b = gf128_encode_f64(2.0)); + try std.testing.expect(result = gf128_add(a, b)); + try std.testing.expect(decoded = gf128_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.01); +} + +test "gf128_mul_simple" { + given a = gf128_encode_f64(3.0) + try std.testing.expect(b = gf128_encode_f64(4.0)); + try std.testing.expect(result = gf128_mul(a, b)); + try std.testing.expect(decoded = gf128_decode_f64(result)); + try std.testing.expect(abs(decoded - 12.0) < 0.01); +} + +test "gf128_abs_positive" { + given value = gf128_encode_f64(5.0) + try std.testing.expect(abs_val = gf128_abs(value)); + try std.testing.expect(gf128_is_equal(abs_val, value) == true); +} + +test "gf128_abs_negative" { + given value = gf128_encode_f64(-5.0) + try std.testing.expect(abs_val = gf128_abs(value)); + try std.testing.expect(gf128_extract_sign(abs_val) == 0); +} + +test "gf128_neg" { + given value = gf128_encode_f64(5.0) + try std.testing.expect(neg_val = gf128_neg(value)); + try std.testing.expect(decoded = gf128_decode_f64(neg_val)); + try std.testing.expect(abs(decoded + 5.0) < 0.01); +} + +test "gf128_is_equal_nan" { + try std.testing.expect(gf128_is_equal(GF128_NAN, GF128_NAN) == false); +} + +test "gf128_is_greater_positive" { + given a = gf128_encode_f64(5.0) + try std.testing.expect(b = gf128_encode_f64(3.0)); + try std.testing.expect(gf128_is_greater(a, b) == true); +} + +test "gf128_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 134217728); + try std.testing.expect(mant = 0x8000000000000000000000000); + try std.testing.expect(gf = gf128_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = gf128_extract_sign(gf)); + try std.testing.expect(extracted_exp = gf128_extract_exponent(gf)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf128_bits_total + assert SIGN_BITS + EXP_BITS + MANT_BITS == 128 + +invariant gf128_bias_power_of_two_minus_one + assert BIAS + 1 == 134217728 // 2^27 + +invariant gf128_special_exp_all_ones + assert SPECIAL_EXP == EXP_MAX + +invariant gf128_zero_pos_no_sign + assert (GF128_ZERO_POS & (@as(GF128, 1) << 127)) == 0 + +invariant gf128_zero_neg_has_sign + assert (GF128_ZERO_NEG & (@as(GF128, 1) << 127)) != 0 + +invariant gf128_abs_removes_sign + assert gf128_abs(GF128_ZERO_NEG) == GF128_ZERO_POS + +invariant gf128_neg_toggles_sign + assert (gf128_neg(GF128_INF_POS) & (@as(GF128, 1) << 127)) != 0 + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf128_extract_sign_latency + measure: nanoseconds to gf128_extract_sign(0x123456789ABCDEF0123456789ABCDEF0) + target: < 15ns + +bench gf128_encode_f64_latency + measure: nanoseconds to gf128_encode_f64(123.456) + target: < 150ns + +bench gf128_decode_f64_latency + measure: nanoseconds to gf128_decode_f64(0x3FF0000000000000) + target: < 150ns + +bench gf128_add_latency + measure: nanoseconds to gf128_add(gf128_encode_f64(1.0), gf128_encode_f64(2.0)) + target: < 300ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/gf16.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/gf16.t27 new file mode 100644 index 0000000000..7a0eaefdeb --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/gf16.t27 @@ -0,0 +1,3436 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf16.t27 0 GoldenFloat16 Encode/Decode +; GF16: 16-bit floating point with 1 sign + 6 exponent + 9 mantissa +; Bit layout: [S(1) E(6) M(9)] = [15:15][14:9][8:0] +; 12 + 1/34 = 3 | TRINITY + +module triformat-gf16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const SIGN_SHIFT : u8 = 15; +pub const EXP_SHIFT : u8 = 9; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u16 = 0x8000; // 1 << 15 +pub const EXP_MASK : u16 = 0x7E00; // 0b111111 << 9 +pub const MANT_MASK : u16 = 0x01FF; // 0b111111111 + +pub const EXP_MAX : u8 = 0x3F; // 63 (all ones in 6 bits) +pub const EXP_MIN : u8 = 0x00; + +pub const BIAS : i8 = 31; // Exponent bias for GF16 +pub const SPECIAL_EXP : u8 = 0x3F; // All ones = special (Inf/NaN) + +pub const MANT_DIVISOR : u16 = 512; // 2^9 +pub const MANT_DIVISOR_SHIFT : u8 = 9; // log2(512) + +pub const PHI_BIAS : u16 = 60; // Phi-optimized rounding bias + +// GF16 special values +pub const GF16_ZERO_POS : u16 = 0x0000; +pub const GF16_ZERO_NEG : u16 = 0x8000; +pub const GF16_INF_POS : u16 = 0x7E00; +pub const GF16_INF_NEG : u16 = 0xFE00; +pub const GF16_NAN : u16 = 0xFE01; // Sign + all exp + mantissa != 0 + +// ============================================================================ +// Types +// ============================================================================ + +pub const GF16 = u16; + +// ============================================================================ +// Lookup Tables +// ============================================================================ + +// Powers of 2 for exponents 0-31 +pub const pow2_table : [32]u16 = [32]u16{ + 0x3C00, 0x3D00, 0x3D80, 0x3E00, 0x3E40, 0x3E80, 0x3EC0, 0x3F00, + 0x3F40, 0x3F80, 0x3FC0, 0x3FE0, 0x3FF0, 0x4000, 0x4040, 0x4080, + 0x40C0, 0x4100, 0x4140, 0x4180, 0x41C0, 0x4200, 0x4240, 0x4280, + 0x42C0, 0x4300, 0x4340, 0x4380, 0x43C0, 0x4400, 0x4440, 0x4480, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +// gf16_extract_sign(gf16: GF16) 5 i8 +// Extract sign bit (bit 15) +// Returns: 0 for positive, -1 for negative +pub fn gf16_extract_sign(gf16: GF16) i8 { + const bit = (gf16 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// gf16_extract_exponent(gf16: GF16) 6 i8 +// Extract exponent bits (bits 14-9) +// Returns: 0-63 +pub fn gf16_extract_exponent(gf16: GF16) i8 { + return @as(i8, @intCast((gf16 >> EXP_SHIFT) & EXP_MASK)); +} + +// gf16_extract_mantissa(gf16: GF16) 7 i16 +// Extract mantissa bits (bits 8-0) +// Returns: 0-511 +pub fn gf16_extract_mantissa(gf16: GF16) i16 { + return @as(i16, gf16 & MANT_MASK); +} + +// gf16_from_components(sign: i8, exp: i8, mant: i16) 8 GF16 +// Assemble GF16 from sign, exponent, mantissa +pub fn gf16_from_components(sign: i8, exp: i8, mant: i16) GF16 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(GF16, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(GF16, @intCast(exp)) << EXP_SHIFT) | + @as(GF16, @intCast(mant)); +} + +// gf16_is_zero(gf16: GF16) 9 bool +// Check if GF16 is zero (positive or negative) +pub fn gf16_is_zero(gf16: GF16) bool { + return gf16 == GF16_ZERO_POS or gf16 == GF16_ZERO_NEG; +} + +// gf16_is_special(gf16: GF16) 10 bool +// Check if GF16 is Inf or NaN (exp == 63) +pub fn gf16_is_special(gf16: GF16) bool { + return gf16_extract_exponent(gf16) == EXP_MAX; +} + +// gf16_encode_f32(f32: f32) 11 GF16 +// Encode IEEE 754 single precision to GF16 +// Round-to-nearest, ties to even +// Range: 2^-31 to 2^32 (normal), subnormals flushed to zero +pub fn gf16_encode_f32(value: f32) GF16 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) GF16_ZERO_NEG else GF16_ZERO_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i8 = @intCast((f32_bits >> 23) & 0xFF) - 127; + var f32_mant: u32 = f32_bits & 0x7FFFFF; + + // Convert exp from f32 bias (127) to GF16 bias (31) + // gf16_exp = f32_exp + 31 - 127 = f32_exp - 96 + var gf16_exp = f32_exp - 96; + + // Clamp exponent + if (gf16_exp < 0) { + gf16_exp = 0; // Underflow to zero + } else if (gf16_exp > EXP_MAX) { + gf16_exp = EXP_MAX; // Overflow to Inf + } + + // Extract mantissa and scale to 9 bits + // f32 mantissa is 23 bits, GF16 needs 9 bits + // Shift right by 14 bits (23 - 9 = 14) + var mant = @as(u16, @intCast(f32_mant >> 14)); + + // Round-to-nearest + const discarded = f32_mant & 0x3FFF; + if ((discarded & 0x2000) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (gf16_exp < EXP_MAX) { + gf16_exp += 1; + } + } + } + + return gf16_from_components(sign, gf16_exp, mant); +} + +// gf16_decode_to_f32(gf16: GF16) 12 f32 +// Decode GF16 to IEEE 754 single precision +pub fn gf16_decode_to_f32(gf16: GF16) f32 { + // Handle zero + if (gf16_is_zero(gf16)) { + const sign = gf16_extract_sign(gf16); + return if (sign < 0) -0.0 else 0.0; + } + + // Handle special values (Inf/NaN) + if (gf16_is_special(gf16)) { + const mant = gf16_extract_mantissa(gf16); + const sign = gf16_extract_sign(gf16); + if (mant == 0) { + // Infinity + return if (sign < 0) -std.math.inf(f32) else std.math.inf(f32); + } else { + // NaN + return std.math.nan(f32); + } + } + + // Normal number: value = (-1)^s * (1 + m/2^9) * 2^(e - 31) + const sign = gf16_extract_sign(gf16); + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + + const sign_mult = if (sign < 0) -1.0 else 1.0; + const mant_mult = 1.0 + @as(f32, @floatFromInt(mant)) / 512.0; + const exp_mult = @as(f32, @exp2(f32, @floatFromInt(exp - BIAS))); + + return sign_mult * mant_mult * exp_mult; +} + +// gf16_round_phi(value: f32) 13 GF16 +// Phi-optimized rounding for GF16 +// Uses golden ratio bias for rounding decisions instead of standard round-to-nearest +// Bias = (1/14 - 0.5) * scale, where 1/15 16 0.618 +// This improves numerical stability for sacred physics calculations +pub fn gf16_round_phi(value: f32) GF16 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) GF16_ZERO_NEG else GF16_ZERO_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i8 = @intCast((f32_bits >> 23) & 0xFF) - 127; + var f32_mant: u32 = f32_bits & 0x7FFFFF; + + // Convert exp from f32 bias (127) to GF16 bias (31) + var gf16_exp = f32_exp - 96; + + // Clamp exponent + if (gf16_exp < 0) { + gf16_exp = 0; + } else if (gf16_exp > EXP_MAX) { + gf16_exp = EXP_MAX; + } + + // Add implied 1 for normalization + const normalized_mant: u32 = f32_mant | 0x00800000; + + // Scale to 9 bits with phi bias + var mant = @as(u16, @intCast((normalized_mant >> 15) + PHI_BIAS)); + + // Check for overflow and adjust + if (mant > MANT_MASK) { + mant = 0; + if (gf16_exp < EXP_MAX) { + gf16_exp += 1; + } else { + gf16_exp = EXP_MAX; // Overflow to Inf + } + } + + return gf16_from_components(sign, gf16_exp, mant); +} + +// gf16_is_inf(gf16: GF16) 17 bool +// Check if GF16 represents infinity +pub fn gf16_is_inf(gf16: GF16) bool { + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + return (exp == EXP_MAX) and (mant == 0); +} + +// gf16_is_nan(gf16: GF16) 18 bool +// Check if GF16 represents NaN (Not a Number) +pub fn gf16_is_nan(gf16: GF16) bool { + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + return (exp == EXP_MAX) and (mant != 0); +} + +// gf16_is_negative(gf16: GF16) 19 bool +// Check if GF16 is negative (excluding negative zero) +pub fn gf16_is_negative(gf16: GF16) bool { + const sign = gf16_extract_sign(gf16); + return (sign < 0) and !gf16_is_zero(gf16); +} + +// gf16_is_positive(gf16: GF16) 20 bool +// Check if GF16 is positive (excluding positive zero) +pub fn gf16_is_positive(gf16: GF16) bool { + const sign = gf16_extract_sign(gf16); + return (sign >= 0) and !gf16_is_zero(gf16); +} + +// gf16_negate(gf16: GF16) 21 GF16 +// Negate a GF16 value (flip sign bit) +pub fn gf16_negate(gf16: GF16) GF16 { + return gf16 ^ SIGN_MASK; +} + +// gf16_abs(gf16: GF16) 22 GF16 +// Absolute value of GF16 (clear sign bit) +pub fn gf16_abs(gf16: GF16) GF16 { + return gf16 & ~SIGN_MASK; +} + +// gf16_copy_sign(gf16: GF16, sign_source: GF16) 23 GF16 +// Copy sign from sign_source to gf16 value +pub fn gf16_copy_sign(gf16: GF16, sign_source: GF16) GF16 { + const sign_mask = sign_source & SIGN_MASK; + const value_mask = gf16 & ~SIGN_MASK; + return value_mask | sign_mask; +} + +// gf16_max(a: GF16, b: GF16) 24 GF16 +// Return the greater of two GF16 values +pub fn gf16_max(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a)) return b; + if (gf16_is_nan(b)) return a; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + if (a_val >= b_val) return a else return b; +} + +// gf16_min(a: GF16, b: GF16) 25 GF16 +// Return the smaller of two GF16 values +pub fn gf16_min(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a)) return b; + if (gf16_is_nan(b)) return a; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + if (a_val <= b_val) return a else return b; +} + +// gf16_add(a: GF16, b: GF16) 26 GF16 +// Add two GF16 values (decode, add, re-encode) +// Returns NaN if either operand is NaN, Inf if overflow +pub fn gf16_add(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_inf(a) and gf16_is_inf(b)) { + // Inf + Inf = NaN (if same sign) + // Inf + (-Inf) = NaN + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + return if (a_sign == b_sign) a else GF16_NAN; + } + if (gf16_is_inf(a)) return a; + if (gf16_is_inf(b)) return b; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val + b_val; + + return gf16_encode_f32(result); +} + +// gf16_sub(a: GF16, b: GF16) 27 GF16 +// Subtract two GF16 values (decode, subtract, re-encode) +// Returns NaN if either operand is NaN, Inf if overflow +pub fn gf16_sub(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_inf(a) and gf16_is_inf(b)) { + // Inf - Inf = NaN + return GF16_NAN; + } + if (gf16_is_inf(a)) return a; + if (gf16_is_inf(b)) { + // -Inf + something = Inf (with sign flip) + return gf16_negate(b); + } + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val - b_val; + + return gf16_encode_f32(result); +} + +// gf16_mul(a: GF16, b: GF16) 28 GF16 +// Multiply two GF16 values (decode, multiply, re-encode) +// Returns NaN if either operand is NaN +pub fn gf16_mul(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_zero(a) or gf16_is_zero(b)) { + // 0 * x = 0, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_ZERO_NEG else GF16_ZERO_POS; + } + if (gf16_is_inf(a) or gf16_is_inf(b)) { + // Inf * non-zero = Inf, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_INF_NEG else GF16_INF_POS; + } + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val * b_val; + + return gf16_encode_f32(result); +} + +// gf16_div(a: GF16, b: GF16) 29 GF16 +// Divide two GF16 values (decode, divide, re-encode) +// Returns NaN if division by zero or either operand is NaN +// Returns Inf if numerator is Inf and denominator is finite non-zero +pub fn gf16_div(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_zero(b)) { + // Division by zero = Inf with sign of a + const a_sign = gf16_extract_sign(a); + return if (a_sign != 0) GF16_INF_NEG else GF16_INF_POS; + } + if (gf16_is_inf(a)) { + // Inf / finite = Inf, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_INF_NEG else GF16_INF_POS; + } + if (gf16_is_inf(b)) { + // Finite / Inf = 0, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_ZERO_NEG else GF16_ZERO_POS; + } + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val / b_val; + + return gf16_encode_f32(result); +} + +// gf16_fma(a: GF16, b: GF16, c: GF16) 30 GF16 +// Fused multiply-add: a * b + c with single rounding +// More accurate than separate mul and add +pub fn gf16_fma(a: GF16, b: GF16, c: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) return GF16_NAN; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const c_val = gf16_decode_to_f32(c); + + // Handle special cases + if (gf16_is_zero(a) or gf16_is_zero(b)) { + return gf16_add(c, gf16_encode_f32(0.0)); + } + + // Compute a * b + c + const product = a_val * b_val; + const result = product + c_val; + + return gf16_encode_f32(result); +} + +// gf16_sqrt(a: GF16) 31 GF16 +// Square root of GF16 value +// Returns NaN for negative values, Inf for infinity +pub fn gf16_sqrt(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a) and !gf16_is_negative(a)) return a; + if (gf16_is_inf(a)) return GF16_NAN; // -Inf sqrt = NaN + if (gf16_is_zero(a)) return a; + if (gf16_is_negative(a)) return GF16_NAN; + + const a_val = gf16_decode_to_f32(a); + const result = @sqrt(a_val); + + return gf16_encode_f32(result); +} + +// gf16_square(a: GF16) 32 GF16 +// Square of GF16 value +// Uses gf16_mul internally +pub fn gf16_square(a: GF16) GF16 { + return gf16_mul(a, a); +} + +// gf16_eq(a: GF16, b: GF16) 33 bool +// Equality comparison for GF16 +// NaN values are never equal to anything (including themselves) +pub fn gf16_eq(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + // For zero values, treat +0 and -0 as equal + if (gf16_is_zero(a) and gf16_is_zero(b)) return true; + return a == b; +} + +// gf16_ne(a: GF16, b: GF16) 34 bool +// Not-equal comparison for GF16 +// NaN values are not equal to anything (including themselves) +pub fn gf16_ne(a: GF16, b: GF16) bool { + return !gf16_eq(a, b); +} + +// gf16_lt(a: GF16, b: GF16) 35 bool +// Less-than comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_lt(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val < b_val; +} + +// gf16_le(a: GF16, b: GF16) 36 bool +// Less-than-or-equal comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_le(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val <= b_val; +} + +// gf16_gt(a: GF16, b: GF16) 37 bool +// Greater-than comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_gt(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val > b_val; +} + +// gf16_ge(a: GF16, b: GF16) 38 bool +// Greater-than-or-equal comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_ge(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val >= b_val; +} + +// gf16_floor(a: GF16) 39 GF16 +// Round down to the nearest integer (toward -inf) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_floor(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @floor(a_val); + + return gf16_encode_f32(result); +} + +// gf16_ceil(a: GF16) 40 GF16 +// Round up to the nearest integer (toward +inf) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_ceil(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @ceil(a_val); + + return gf16_encode_f32(result); +} + +// gf16_round(a: GF16) 41 GF16 +// Round to nearest integer, ties to even (IEEE 754 roundTiesToEven) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_round(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @round(a_val); + + return gf16_encode_f32(result); +} + +// gf16_trunc(a: GF16) 42 GF16 +// Round toward zero (truncate fractional part) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_trunc(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @trunc(a_val); + + return gf16_encode_f32(result); +} + +// gf16_fms(a: GF16, b: GF16, c: GF16) 43 GF16 +// Fused multiply-subtract: a * b - c with single rounding +// More accurate than separate mul and sub +// Useful for neural network backpropagation +pub fn gf16_fms(a: GF16, b: GF16, c: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) return GF16_NAN; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const c_val = gf16_decode_to_f32(c); + + // Handle special cases + if (gf16_is_zero(a) or gf16_is_zero(b)) { + return gf16_sub(gf16_encode_f32(0.0), c); + } + + // Compute a * b - c + const product = a_val * b_val; + const result = product - c_val; + + return gf16_encode_f32(result); +} + +// gf16_hypot(a: GF16, b: GF16) 44 GF16 +// Compute sqrt(a^2 + b^2) without overflow/underflow +// Returns NaN if either operand is NaN, Inf if both are Inf +// Useful for distance calculations, neural network normalization +pub fn gf16_hypot(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_inf(a) or gf16_is_inf(b)) return GF16_INF_POS; + + if (gf16_is_zero(a) and gf16_is_zero(b)) return GF16_ZERO_POS; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + // Standard algorithm to avoid overflow: scale by max(|a|, |b|) + const abs_a = @abs(a_val); + const abs_b = @abs(b_val); + const max_val = @max(abs_a, abs_b); + const min_val = @min(abs_a, abs_b); + + if (max_val == 0.0) return GF16_ZERO_POS; + + const ratio = min_val / max_val; + const result = max_val * @sqrt(1.0 + ratio * ratio); + + return gf16_encode_f32(result); +} + +// gf16_fmod(a: GF16, b: GF16) 45 GF16 +// Compute remainder of a / b (IEEE 754 style) +// Result has same sign as dividend (a) +// Returns NaN if divisor is zero or either operand is NaN +pub fn gf16_fmod(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_zero(b)) return GF16_NAN; + if (gf16_is_inf(a)) return GF16_NAN; + if (gf16_is_inf(b)) return a; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + // Handle zero dividend + if (a_val == 0.0) return a; + + const result = @mod(a_val, b_val); + + return gf16_encode_f32(result); +} + +// gf16_is_finite(gf16: GF16) 46 bool +// Check if GF16 value is finite (not NaN, not infinity) +pub fn gf16_is_finite(gf16: GF16) bool { + return !gf16_is_nan(gf16) and !gf16_is_inf(gf16); +} + +// gf16_is_normal(gf16: GF16) 47 bool +// Check if GF16 value is a normal (normalized) number +// Normal numbers have exponent in range [1, EXP_MAX-1] and are not zero +pub fn gf16_is_normal(gf16: GF16) bool { + if (gf16_is_zero(gf16) or gf16_is_nan(gf16) or gf16_is_inf(gf16)) { + return false; + } + + const exp = gf16_extract_exponent(gf16); + // GF16: exp = 0 is subnormal, exp = 31 is inf/nan, 1-30 is normal + return exp > 0 and exp < GF16_EXP_MAX; +} + +// gf16_is_subnormal(gf16: GF16) 48 bool +// Check if GF16 value is subnormal (denormal) +// Subnormal numbers have exponent = 0 and mantissa != 0 +pub fn gf16_is_subnormal(gf16: GF16) bool { + if (gf16_is_zero(gf16) or gf16_is_nan(gf16) or gf16_is_inf(gf16)) { + return false; + } + + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + + // Subnormal: exp = 0 and mantissa != 0 + return exp == 0 and mant != 0; +} + +// gf16_signbit(gf16: GF16) 49 bool +// Check if the sign bit is set (value is negative or negative zero) +// Returns true for negative values and negative zero +pub fn gf16_signbit(gf16: GF16) bool { + return (gf16 & GF16_SIGN_MASK) != 0; +} + +// gf16_sign(gf16: GF16) 50 i8 +// Return the sign of the GF16 value: -1 for negative, 0 for zero, +1 for positive +// Returns 0 for NaN (IEEE 754 specifies sign of NaN is undefined) +pub fn gf16_sign(gf16: GF16) i8 { + if (gf16_is_nan(gf16)) { + return 0; + } + + if (gf16_is_zero(gf16)) { + return 0; + } + + if (gf16_signbit(gf16)) { + return -1; + } else { + return 1; + } +} + +// gf16_clamp(x: GF16, min_val: GF16, max_val: GF16) 51 GF16 +// Clamp x to the range [min_val, max_val] +// Returns min_val if x < min_val, max_val if x > max_val, otherwise x +pub fn gf16_clamp(x: GF16, min_val: GF16, max_val: GF16) GF16 { + if (gf16_is_nan(x) or gf16_is_nan(min_val) or gf16_is_nan(max_val)) { + return GF16_NAN; + } + + // Decode for comparison + const x_decoded = gf16_decode_to_f32(x); + const min_decoded = gf16_decode_to_f32(min_val); + const max_decoded = gf16_decode_to_f32(max_val); + + if (x_decoded < min_decoded) { + return min_val; + } else if (x_decoded > max_decoded) { + return max_val; + } else { + return x; + } +} + +// gf16_lerp(a: GF16, b: GF16, t: GF16) 52 GF16 +// Linear interpolation: a + t * (b - a) +// Returns a when t=0, b when t=1, and interpolates for other values +pub fn gf16_lerp(a: GF16, b: GF16, t: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(t)) { + return GF16_NAN; + } + + // Decode to f32 for computation + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const t_val = gf16_decode_to_f32(t); + + // Compute: a + t * (b - a) + const result = a_val + t_val * (b_val - a_val); + + return gf16_encode_f32(result); +} + +// gf16_fnma(a: GF16, b: GF16, c: GF16) 53 GF16 +// Fused negative multiply-add: -(a * b) + c +// More accurate than computing gf16_sub(c, gf16_mul(a, b)) +pub fn gf16_fnma(a: GF16, b: GF16, c: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) { + return GF16_NAN; + } + + // Handle infinity cases + if (gf16_is_inf(a) or gf16_is_inf(b)) { + if (gf16_is_inf(c)) { + return GF16_NAN; + } + // -(inf * b) + c = -inf (with appropriate sign) + if (gf16_is_inf(a) or gf16_is_inf(b)) { + const sign_a = gf16_signbit(a); + const sign_b = gf16_signbit(b); + const result_sign = (sign_a != sign_b); // XOR for negative result + return if (result_sign) GF16_INF_NEG else GF16_INF_POS; + } + } + + if (gf16_is_inf(c)) { + return c; + } + + // Handle zero cases + if (gf16_is_zero(a) or gf16_is_zero(b)) { + return c; + } + + if (gf16_is_zero(c)) { + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const neg_product = -(a_val * b_val); + return gf16_encode_f32(neg_product); + } + + // Decode to f32 for computation + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const c_val = gf16_decode_to_f32(c); + + const result = -(a_val * b_val) + c_val; + + return gf16_encode_f32(result); +} + +// gf16_exp(x: GF16) 54 GF16 +// Compute e^x (exponential function) +// Uses Taylor series approximation for small values +// Returns Inf for very large positive inputs, 0 for very large negative inputs +pub fn gf16_exp(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_ZERO_POS; + return GF16_INF_POS; + } + + const x_val = gf16_decode_to_f32(x); + + // For large positive values, return Inf + if (x_val > 88.0) { // ln(MAX_FLOAT) for f32 + return GF16_INF_POS; + } + + // For large negative values, return 0 + if (x_val < -88.0) { + return GF16_ZERO_POS; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + x^4/4! + ... + // Use 5 terms for reasonable accuracy with GF16 precision + var result: f32 = 1.0; + var term: f32 = 1.0; + const num_terms: u32 = 5; + + for (0..num_terms) |i| { + if (i > 0) { + term *= x_val / @as(f32, @floatFromInt(i)); + result += term; + } + } + + return gf16_encode_f32(result); +} + +// gf16_log(x: GF16) 55 GF16 +// Compute natural logarithm ln(x) +// Returns NaN for x <= 0, Inf for very large x +pub fn gf16_log(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_NAN; + return GF16_INF_POS; + } + if (gf16_is_zero(x) or gf16_is_negative(x)) { + return GF16_NAN; + } + + const x_val = gf16_decode_to_f32(x); + + // For very large values, return Inf + if (x_val > 1.0e38) { + return GF16_INF_POS; + } + + // Use natural log from standard library + const result = @log(x_val); + + return gf16_encode_f32(result); +} + +// gf16_log2(x: GF16) 56 GF16 +// Compute base-2 logarithm log2(x) +pub fn gf16_log2(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_NAN; + return GF16_INF_POS; + } + if (gf16_is_zero(x) or gf16_is_negative(x)) { + return GF16_NAN; + } + + const x_val = gf16_decode_to_f32(x); + const result = @log2(x_val); + + return gf16_encode_f32(result); +} + +// gf16_log10(x: GF16) 57 GF16 +// Compute base-10 logarithm log10(x) +pub fn gf16_log10(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_NAN; + return GF16_INF_POS; + } + if (gf16_is_zero(x) or gf16_is_negative(x)) { + return GF16_NAN; + } + + const x_val = gf16_decode_to_f32(x); + const result = @log10(x_val); + + return gf16_encode_f32(result); +} + +// gf16_pow(base: GF16, exponent: GF16) 58 GF16 +// Compute base^exponent +// Handles various special cases: 0^0 = 1, 1^x = 1, x^0 = 1, etc. +pub fn gf16_pow(base: GF16, exponent: GF16) GF16 { + if (gf16_is_nan(base) or gf16_is_nan(exponent)) return GF16_NAN; + + // 0^0 = 1 (by convention) + if (gf16_is_zero(base) and gf16_is_zero(exponent)) return gf16_encode_f32(1.0); + + // 0^x = 0 for x > 0 + if (gf16_is_zero(base) and gf16_is_positive(exponent)) return GF16_ZERO_POS; + + // 0^x = Inf for x < 0 (division by zero) + if (gf16_is_zero(base) and gf16_is_negative(exponent)) return GF16_INF_POS; + + // 1^x = 1 for any finite x + const base_val = gf16_decode_to_f32(base); + if (base_val == 1.0 and !gf16_is_inf(exponent)) return gf16_encode_f32(1.0); + + // x^0 = 1 for any x != 0 + if (gf16_is_zero(exponent)) { + if (gf16_is_zero(base)) return GF16_NAN; + return gf16_encode_f32(1.0); + } + + // x^1 = x + const exp_val = gf16_decode_to_f32(exponent); + if (exp_val == 1.0) return base; + + // Use stdlib pow for general case + const result = @pow(base_val, exp_val); + + return gf16_encode_f32(result); +} + +// gf16_sin(x: GF16) 59 GF16 +// Compute sine function sin(x) where x is in radians +// Uses Taylor series approximation for small values +pub fn gf16_sin(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) return GF16_NAN; + + const x_val = gf16_decode_to_f32(x); + + // Taylor series: sin(x) = x - x^3/3! + x^5/5! - x^7/7! + ... + // Use 4 terms for reasonable accuracy + const x_sq = x_val * x_val; + const x_cub = x_sq * x_val; + const x_5 = x_cub * x_sq; + const x_7 = x_5 * x_sq; + + const term1 = x_val; + const term2 = -x_cub / 6.0; + const term3 = x_5 / 120.0; + const term4 = -x_7 / 5040.0; + + const result = term1 + term2 + term3 + term4; + + return gf16_encode_f32(result); +} + +// gf16_cos(x: GF16) 60 GF16 +// Compute cosine function cos(x) where x is in radians +// Uses Taylor series approximation for small values +pub fn gf16_cos(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) return GF16_NAN; + + const x_val = gf16_decode_to_f32(x); + + // Taylor series: cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + ... + // Use 4 terms for reasonable accuracy + const x_sq = x_val * x_val; + const x_4 = x_sq * x_sq; + const x_6 = x_4 * x_sq; + + const term0 = 1.0; + const term1 = -x_sq / 2.0; + const term2 = x_4 / 24.0; + const term3 = -x_6 / 720.0; + + const result = term0 + term1 + term2 + term3; + + return gf16_encode_f32(result); +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "gf16_roundtrip_phi" { + // Verify: encoding f32 PHI to GF16 and decoding back preserves value within tolerance + const PHI: f32 = 1.6180339887498948; + const encoded = gf16_encode_f32(PHI); + const decoded = gf16_decode_to_f32(encoded); + try std.testing.expectApproxEqAbs(PHI, decoded, 0.001); +} + +test "gf16_zero_encoding" { + // Verify: zero (positive and negative) encodes to correct GF16 patterns + try std.testing.expectEqual(@as(GF16, GF16_ZERO_POS), gf16_encode_f32(0.0)); + try std.testing.expectEqual(@as(GF16, GF16_ZERO_NEG), gf16_encode_f32(-0.0)); +} + +test "gf16_phi_roundtrip_high_precision" { + // Verify: PHI roundtrip with higher tolerance for golden ratio + const PHI: f32 = 1.6180339887498948; + const encoded = gf16_encode_f32(PHI); + const decoded = gf16_decode_to_f32(encoded); + try std.testing.expectApproxEqAbs(PHI, decoded, 0.01); +} + +test "gf16_inf_encoding" { + // Verify: overflow encodes to Inf correctly + const encoded = gf16_encode_f32(1.0e38); + try std.testing.expect(gf16_is_special(encoded)); + try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(encoded)); +} + +test "gf16_sign_extraction" { + try std.testing.expectEqual(@as(i8, -1), gf16_extract_sign(0x8000)); + try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(0x3C00)); + try std.testing.expectEqual(@as(i8, 1), gf16_extract_sign(0x8000)); + try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(0x3C00)); +} + +test "gf16_exponent_extraction" { + try std.testing.expectEqual(@as(i8, 0), gf16_extract_exponent(0x3C00)); + try std.testing.expectEqual(@as(i8, 1), gf16_extract_exponent(0x3D00)); +} + +test "gf16_mantissa_extraction" { + try std.testing.expectEqual(@as(i16, 0), gf16_extract_mantissa(0x3C00)); + try std.testing.expectEqual(@as(i16, 1), gf16_extract_mantissa(0x3C01)); + try std.testing.expectEqual(@as(i16, 511), gf16_extract_mantissa(0x3DFF)); +} + +test "gf16_zero_detection" { + try std.testing.expect(gf16_is_zero(0x0000)); + try std.testing.expect(gf16_is_zero(0x8000)); + try std.testing.expect(!gf16_is_zero(0x0001)); +} + +test "gf16_special_detection" { + try std.testing.expect(gf16_is_special(0x7E00)); + try std.testing.expect(gf16_is_special(0xFE01)); + try std.testing.expect(!gf16_is_special(0x3C00)); +} + +test "gf16_from_components" { + const result = gf16_from_components(0, 0, 0); + try std.testing.expectEqual(@as(GF16, 0x3C00), result); +} + +test "gf16_nan_encoding" { + const nan_val = gf16_from_components(0, 63, 1); + const decoded = gf16_decode_to_f32(nan_val); + try std.testing.expect(std.math.isNan(decoded)); +} + +test "gf16_round_phi_preserves_phi" { + const PHI: f32 = 1.6180339887498948; + const encoded = gf16_round_phi(PHI); + const decoded = gf16_decode_to_f32(encoded); + try std.testing.expectApproxEqAbs(PHI, decoded, 0.005); +} + +test "gf16_round_phi_zero" { + try std.testing.expectEqual(@as(GF16, GF16_ZERO_POS), gf16_round_phi(0.0)); + try std.testing.expectEqual(@as(GF16, GF16_ZERO_NEG), gf16_round_phi(-0.0)); +} + +test "gf16_round_phi_positive" { + try std.testing.expectApproxEqAbs(1.0, gf16_decode_to_f32(gf16_round_phi(1.0)), 0.01); + try std.testing.expectApproxEqAbs(2.0, gf16_decode_to_f32(gf16_round_phi(2.0)), 0.01); + try std.testing.expectApproxEqAbs(3.0, gf16_decode_to_f32(gf16_round_phi(3.0)), 0.01); +} + +test "gf16_round_phi_negative" { + try std.testing.expectApproxEqAbs(-1.0, gf16_decode_to_f32(gf16_round_phi(-1.0)), 0.01); + try std.testing.expectApproxEqAbs(-2.0, gf16_decode_to_f32(gf16_round_phi(-2.0)), 0.01); + const PHI: f32 = 1.6180339887498948; + try std.testing.expectApproxEqAbs(-PHI, gf16_decode_to_f32(gf16_round_phi(-PHI)), 0.01); +} + +test "gf16_pow2_table_consistency" { + try std.testing.expectEqual(@as(u16, 0x3C00), pow2_table[0]); // 2^0 = 1.0 + try std.testing.expectEqual(@as(u16, 0x3D00), pow2_table[1]); // 2^1 = 2.0 + try std.testing.expectEqual(@as(u16, 0x3D80), pow2_table[2]); // 2^2 = 4.0 +} + +test "gf16_exp_bias_identity" { + try std.testing.expectEqual(@as(i8, 31), BIAS); +} + +test "gf16_identity_encoding" { + // For GF16 representing 1.0: sign=0, exp=0, mant=0, raw value = 0x3C00 + try std.testing.expectEqual(@as(GF16, 0x3C00), gf16_from_components(0, 0, 0)); +} + +test "gf16_special_exp_all_ones" { + try std.testing.expectEqual(@as(u8, 0x3F), EXP_MAX); +} + +test "gf16_is_inf_positive" { + try std.testing.expect(gf16_is_inf(GF16_INF_POS)); + try std.testing.expect(!gf16_is_inf(GF16_ZERO_POS)); + try std.testing.expect(!gf16_is_inf(0x3C00)); +} + +test "gf16_is_inf_negative" { + try std.testing.expect(gf16_is_inf(GF16_INF_NEG)); + try std.testing.expect(!gf16_is_inf(GF16_ZERO_NEG)); +} + +test "gf16_is_nan_detection" { + try std.testing.expect(gf16_is_nan(GF16_NAN)); + try std.testing.expect(!gf16_is_nan(GF16_INF_POS)); + try std.testing.expect(!gf16_is_nan(GF16_ZERO_POS)); +} + +test "gf16_is_negative_detection" { + try std.testing.expect(gf16_is_negative(GF16_INF_NEG)); + try std.testing.expect(gf16_is_negative(gf16_encode_f32(-1.5))); + try std.testing.expect(!gf16_is_negative(GF16_ZERO_NEG)); // -0 is not considered "negative" + try std.testing.expect(!gf16_is_negative(GF16_INF_POS)); +} + +test "gf16_is_positive_detection" { + try std.testing.expect(gf16_is_positive(GF16_INF_POS)); + try std.testing.expect(gf16_is_positive(gf16_encode_f32(1.5))); + try std.testing.expect(!gf16_is_positive(GF16_ZERO_POS)); // +0 is not considered "positive" + try std.testing.expect(!gf16_is_positive(GF16_INF_NEG)); +} + +test "gf16_negate_sign_flip" { + const pos_one = gf16_encode_f32(1.0); + const neg_one = gf16_negate(pos_one); + const decoded = gf16_decode_to_f32(neg_one); + try std.testing.expectApproxEqAbs(-1.0, decoded, 0.01); +} + +test "gf16_negate_zero_stays_zero" { + try std.testing.expectEqual(GF16_ZERO_POS, gf16_negate(GF16_ZERO_POS)); + try std.testing.expectEqual(GF16_ZERO_NEG, gf16_negate(GF16_ZERO_NEG)); +} + +test "gf16_negate_double_negate" { + const original = gf16_encode_f32(1.5); + const negated = gf16_negate(original); + const double_negated = gf16_negate(negated); + const orig_decoded = gf16_decode_to_f32(original); + const double_decoded = gf16_decode_to_f32(double_negated); + try std.testing.expectApproxEqAbs(orig_decoded, double_decoded, 0.001); +} + +test "gf16_abs_clears_sign" { + const neg_value = gf16_encode_f32(-2.5); + const abs_value = gf16_abs(neg_value); + const decoded = gf16_decode_to_f32(abs_value); + try std.testing.expectApproxEqAbs(2.5, decoded, 0.01); +} + +test "gf16_abs_positive_unchanged" { + const pos_value = gf16_encode_f32(3.5); + const abs_value = gf16_abs(pos_value); + try std.testing.expectEqual(pos_value, abs_value); +} + +test "gf16_abs_zero_unchanged" { + try std.testing.expectEqual(GF16_ZERO_POS, gf16_abs(GF16_ZERO_POS)); + try std.testing.expectEqual(GF16_ZERO_POS, gf16_abs(GF16_ZERO_NEG)); +} + +test "gf16_copy_sign_from_negative" { + const pos_value = gf16_encode_f32(2.5); + const neg_source = gf16_encode_f32(-1.0); + const result = gf16_copy_sign(pos_value, neg_source); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.5, decoded, 0.01); +} + +test "gf16_copy_sign_from_positive" { + const neg_value = gf16_encode_f32(-2.5); + const pos_source = gf16_encode_f32(1.0); + const result = gf16_copy_sign(neg_value, pos_source); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.5, decoded, 0.01); +} + +test "gf16_max_returns_greater" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(5.0); + const result = gf16_max(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.01); +} + +test "gf16_max_equal_values" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(3.0); + const result = gf16_max(a, b); + try std.testing.expectEqual(a, result); +} + +test "gf16_max_with_nan" { + const a = gf16_encode_f32(2.0); + const nan_val = GF16_NAN; + const result = gf16_max(a, nan_val); + try std.testing.expectEqual(a, result); +} + +test "gf16_min_returns_smaller" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(5.0); + const result = gf16_min(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.01); +} + +test "gf16_min_equal_values" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(3.0); + const result = gf16_min(a, b); + try std.testing.expectEqual(a, result); +} + +test "gf16_min_with_nan" { + const a = gf16_encode_f32(2.0); + const nan_val = GF16_NAN; + const result = gf16_min(a, nan_val); + try std.testing.expectEqual(a, result); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant gf16_identity_encoding { + // For GF16 representing 1.0: sign=0, exp=0, mant=0, raw value = 0x3C00 + @compileAssert(gf16_from_components(0, 0, 0) == 0x3C00); +} + +invariant gf16_sign_mask_bit_position { + // SIGN_MASK = 0x8000 has bit 15 set (MSB) + @compileAssert(SIGN_MASK == 0x8000); +} + +invariant gf16_exp_mask_range { + // EXP_MASK = 0x7E00 covers bits 14-9 (6 bits for exponent) + @compileAssert(EXP_MASK == 0x7E00); +} + +invariant gf16_mant_mask_range { + // MANT_MASK = 0x01FF covers bits 8-0 (9 bits for mantissa) + @compileAssert(MANT_MASK == 0x01FF); +} + +invariant gf16_exp_bias_identity { + // BIAS = 31, so unbiased exp = encoded_exp - 31 + @compileAssert(BIAS == 31); +} + +invariant gf16_roundtrip_symmetry { + // For all normal values x: |decode(encode(x)) - x| < epsilon + @compileAssert(true); +} + +invariant gf16_zero_uniqueness { + // Both 0x0000 and 0x8000 represent zero (positive/negative) + @compileAssert(GF16_ZERO_POS == 0x0000); + @compileAssert(GF16_ZERO_NEG == 0x8000); +} + +invariant gf16_special_exp_all_ones { + // EXP_MAX = 0x3F (63) all ones indicates Inf/NaN + @compileAssert(EXP_MAX == 0x3F); +} + +invariant gf16_pow2_table_consistency { + // pow2_table[n] encodes 2^n for n = 0 to 31 + @compileAssert(pow2_table.len == 32); +} + +invariant gf16_mantissa_implicit_one { + // For normal numbers: actual mantissa = 1 + (stored_mant / 512) + @compileAssert(MANT_DIVISOR == 512); +} + +invariant gf16_phi_bias_positive { + // PHI_BIAS = 60 > 0 + @compileAssert(PHI_BIAS > 0); +} + +invariant gf16_phi_bias_less_than_mantissa_scale { + // PHI_BIAS = 60 < 512 (MANT_DIVISOR) + @compileAssert(PHI_BIAS < MANT_DIVISOR); +} + +invariant gf16_round_phi_preserves_sign { + // For all x: sign(gf16_round_phi(x)) = sign(x) + @compileAssert(true); +} + +invariant gf16_inf_exp_all_ones_mant_zero { + // Infinity: exp = 63, mant = 0 + @compileAssert(gf16_extract_exponent(GF16_INF_POS) == EXP_MAX); + @compileAssert(gf16_extract_mantissa(GF16_INF_POS) == 0); +} + +invariant gf16_nan_exp_all_ones_mant_nonzero { + // NaN: exp = 63, mant != 0 + @compileAssert(gf16_extract_exponent(GF16_NAN) == EXP_MAX); + @compileAssert(gf16_extract_mantissa(GF16_NAN) != 0); +} + +invariant gf16_negate_flips_sign_bit { + // gf16_negate(x) = x ^ 0x8000 + @compileAssert(gf16_negate(0x3C00) == 0xBC00); + @compileAssert(gf16_negate(0xBC00) == 0x3C00); +} + +invariant gf16_negate_involutive { + // gf16_negate(gf16_negate(x)) = x + @compileAssert(true); +} + +invariant gf16_abs_clears_sign_bit { + // gf16_abs(x) = x & ~0x8000 + @compileAssert(gf16_abs(0xBC00) == 0x3C00); + @compileAssert(gf16_abs(0x3C00) == 0x3C00); +} + +invariant gf16_abs_non_negative { + // gf16_abs(x) is always non-negative (sign bit cleared) + @compileAssert((gf16_abs(0xBC00) & SIGN_MASK) == 0); +} + +invariant gf16_copy_sign_preserves_sign_source { + // sign(gf16_copy_sign(x, s)) = sign(s) + @compileAssert(true); +} + +invariant gf16_copy_sign_preserves_magnitude { + // |gf16_copy_sign(x, s)| = |x| + @compileAssert(true); +} + +invariant gf16_max_idempotent { + // gf16_max(x, x) = x + @compileAssert(true); +} + +invariant gf16_min_idempotent { + // gf16_min(x, x) = x + @compileAssert(true); +} + +invariant gf16_max_commutative { + // gf16_max(a, b) = gf16_max(b, a) + @compileAssert(true); +} + +invariant gf16_min_commutative { + // gf16_min(a, b) = gf16_min(b, a) + @compileAssert(true); +} + +invariant gf16_is_inf_and_is_nan_exclusive { + // A value cannot be both Inf and NaN + @compileAssert(!gf16_is_inf(GF16_NAN)); + @compileAssert(!gf16_is_nan(GF16_INF_POS)); +} + +invariant gf16_add_zero_identity { + // gf16_add(x, 0) = gf16_add(0, x) = x (approximately, due to encoding) + @compileAssert(true); +} + +invariant gf16_mul_zero_annihilates { + // gf16_mul(x, 0) = gf16_mul(0, x) = 0 + @compileAssert(true); +} + +invariant gf16_mul_one_identity { + // gf16_mul(x, 1) = gf16_mul(1, x) = x (approximately) + @compileAssert(true); +} + +invariant gf16_negate_involutive { + // gf16_negate(gf16_negate(x)) = x + @compileAssert(true); +} + +invariant gf16_add_commutative { + // gf16_add(a, b) = gf16_add(b, a) + @compileAssert(true); +} + +invariant gf16_mul_commutative { + // gf16_mul(a, b) = gf16_mul(b, a) + @compileAssert(true); +} + +invariant gf16_div_by_one_identity { + // gf16_div(x, 1) = x (approximately) + @compileAssert(true); +} + +invariant gf16_sqrt_non_negative { + // gf16_sqrt(x) >= 0 for all x >= 0 + @compileAssert(true); +} + +invariant gf16_sqrt_of_square_less_than_or_equal { + // gf16_sqrt(gf16_square(x)) <= x for all x >= 0 + @compileAssert(true); +} + +invariant gf16_fma_distributive_approximation { + // gf16_fma(a, b, c) 61 gf16_add(gf16_mul(a, b), c) + // Not exact due to encoding rounding + @compileAssert(true); +} + +invariant gf16_square_positive { + // gf16_square(x) >= 0 for all x + @compileAssert(true); +} + +invariant gf16_eq_reflexive_for_non_nan { + // For all x != NaN: gf16_eq(x, x) = true + @compileAssert(true); +} + +invariant gf16_ne_irreflexive_for_non_nan { + // For all x != NaN: gf16_ne(x, x) = false + @compileAssert(true); +} + +invariant gf16_lt_and_gt_mutually_exclusive { + // For all a, b: not (gf16_lt(a, b) and gf16_gt(a, b)) + @compileAssert(true); +} + +invariant gf16_le_and_ge_mutually_inclusive { + // For all a, b: gf16_le(a, b) or gf16_ge(a, b) (for non-NaN) + @compileAssert(true); +} + +invariant gf16_lt_implies_le { + // For all a, b: gf16_lt(a, b) implies gf16_le(a, b) + @compileAssert(true); +} + +invariant gf16_gt_implies_ge { + // For all a, b: gf16_gt(a, b) implies gf16_ge(a, b) + @compileAssert(true); +} + +invariant gf16_eq_implies_le_and_ge { + // For all a, b: gf16_eq(a, b) implies gf16_le(a, b) and gf16_ge(a, b) + @compileAssert(true); +} + +invariant gf16_ne_nan_is_true { + // gf16_ne(NaN, NaN) = true per IEEE 754 + @compileAssert(true); +} + +invariant gf16_lt_nan_is_false { + // gf16_lt(NaN, x) = false for all x + @compileAssert(true); +} + +invariant gf16_gt_nan_is_false { + // gf16_gt(NaN, x) = false for all x + @compileAssert(true); +} + +invariant gf16_floor_yields_integer { + // For all x != NaN, Inf: floor(gf16_floor(x)) = gf16_floor(x) + @compileAssert(true); +} + +invariant gf16_ceil_yields_integer { + // For all x != NaN, Inf: ceil(gf16_ceil(x)) = gf16_ceil(x) + @compileAssert(true); +} + +invariant gf16_round_yields_integer { + // For all x != NaN, Inf: round(gf16_round(x)) = gf16_round(x) + @compileAssert(true); +} + +invariant gf16_trunc_yields_integer { + // For all x != NaN, Inf: trunc(gf16_trunc(x)) = gf16_trunc(x) + @compileAssert(true); +} + +invariant gf16_floor_le_value { + // For all x: floor(x) <= x + @compileAssert(true); +} + +invariant gf16_ceil_ge_value { + // For all x: ceil(x) >= x + @compileAssert(true); +} + +invariant gf16_round_closest_integer { + // For all x: |round(x) - x| <= 0.5 + @compileAssert(true); +} + +invariant gf16_trunc_magnitude_less_or_equal { + // For all x: |trunc(x)| <= |x| + @compileAssert(true); +} + +invariant gf16_trunc_positive_equals_floor { + // For all x >= 0: trunc(x) = floor(x) + @compileAssert(true); +} + +invariant gf16_trunc_negative_equals_ceil { + // For all x <= 0: trunc(x) = ceil(x) + @compileAssert(true); +} + +invariant gf16_fms_related_to_fma { + // gf16_fms(a, b, c) = gf16_fma(a, b, -c) (approximately, due to encoding) + @compileAssert(true); +} + +invariant gf16_fms_with_zero_subtractand { + // gf16_fms(a, b, 0) = gf16_mul(a, b) (approximately) + @compileAssert(true); +} + +invariant gf16_hypot_non_negative { + // For all a, b: gf16_hypot(a, b) >= 0 + @compileAssert(true); +} + +invariant gf16_hypot_symmetric { + // For all a, b: gf16_hypot(a, b) = gf16_hypot(b, a) + @compileAssert(true); +} + +invariant gf16_hypot_pythagorean_identity { + // For all a, b: hypot(a, b)^2 = a^2 + b^2 (approximately, due to encoding) + @compileAssert(true); +} + +invariant gf16_hypot_ge_max_input { + // For all a, b: gf16_hypot(a, b) >= max(|a|, |b|) + @compileAssert(true); +} + +invariant gf16_hypot_zero_with_zeros { + // gf16_hypot(0, 0) = 0 + @compileAssert(true); +} + +invariant gf16_fmod_result_sign_matches_dividend { + // For all a, b where b != 0: sign(gf16_fmod(a, b)) = sign(a) + @compileAssert(true); +} + +invariant gf16_fmod_less_than_divisor { + // For all a, b where b > 0: |gf16_fmod(a, b)| < |b| + @compileAssert(true); +} + +invariant gf16_fmod_with_divisible_values { + // For all a, b where a = k*b: gf16_fmod(a, b) = 0 + @compileAssert(true); +} + +invariant gf16_is_finite_excludes_inf_nan { + // gf16_is_finite(x) = true implies !gf16_is_inf(x) and !gf16_is_nan(x) + @compileAssert(true); +} + +invariant gf16_is_normal_implies_finite { + // gf16_is_normal(x) = true implies gf16_is_finite(x) + @compileAssert(true); +} + +invariant gf16_is_subnormal_implies_finite { + // gf16_is_subnormal(x) = true implies gf16_is_finite(x) + @compileAssert(true); +} + +invariant gf16_is_normal_and_subnormal_mutually_exclusive { + // gf16_is_normal(x) and gf16_is_subnormal(x) cannot both be true + @compileAssert(true); +} + +invariant gf16_zero_neither_normal_nor_subnormal { + // gf16_is_zero(x) = true implies !gf16_is_normal(x) and !gf16_is_subnormal(x) + @compileAssert(true); +} + +invariant gf16_classification_exhaustive { + // For all x: (is_finite and (is_normal or is_subnormal or is_zero)) or is_inf or is_nan + @compileAssert(true); +} + +invariant gf16_signbit_positive_no_signbit { + // gf16_signbit(x) = false for x >= 0 (including +0 and +inf) + @compileAssert(true); +} + +invariant gf16_signbit_negative_has_signbit { + // gf16_signbit(x) = true for x < 0 (including -0 and -inf) + @compileAssert(true); +} + +invariant gf16_sign_positive_returns_one { + // For x > 0 and x is not NaN: gf16_sign(x) = 1 + @compileAssert(true); +} + +invariant gf16_sign_negative_returns_minus_one { + // For x < 0 and x is not NaN: gf16_sign(x) = -1 + @compileAssert(true); +} + +invariant gf16_sign_zero_returns_zero { + // For x = 0 (positive or negative): gf16_sign(x) = 0 + @compileAssert(true); +} + +invariant gf16_sign_nan_returns_zero { + // For NaN: gf16_sign(x) = 0 (sign of NaN is undefined) + @compileAssert(true); +} + +invariant gf16_clamp_in_range_returns_value { + // For x in [min, max]: gf16_clamp(x, min, max) = x + @compileAssert(true); +} + +invariant gf16_clamp_below_min_returns_min { + // For x < min: gf16_clamp(x, min, max) = min + @compileAssert(true); +} + +invariant gf16_clamp_above_max_returns_max { + // For x > max: gf16_clamp(x, min, max) = max + @compileAssert(true); +} + +invariant gf16_lerp_t_zero_returns_a { + // gf16_lerp(a, b, 0) = a + @compileAssert(true); +} + +invariant gf16_lerp_t_one_returns_b { + // gf16_lerp(a, b, 1) = b + @compileAssert(true); +} + +invariant gf16_lerp_monotonic { + // For fixed a < b: gf16_lerp(a, b, t) is monotonic in t + @compileAssert(true); +} + +invariant gf16_fnma_equals_neg_mul_plus_c { + // gf16_fnma(a, b, c) = -(a*b) + c (approximately, with better precision) + @compileAssert(true); +} + +invariant gf16_fnma_zero_multiplier_returns_c { + // gf16_fnma(0, b, c) = c + @compileAssert(true); +} + +invariant gf16_exp_zero_returns_one { + // gf16_exp(0) = 1 + @compileAssert(true); +} + +invariant gf16_exp_positive_greater_than_one { + // gf16_exp(x) > 1 for x > 0 + @compileAssert(true); +} + +invariant gf16_exp_negative_between_zero_and_one { + // 0 < gf16_exp(x) < 1 for x < 0 + @compileAssert(true); +} + +invariant gf16_log_one_returns_zero { + // gf16_log(1) = 0 + @compileAssert(true); +} + +invariant gf16_log_zero_or_negative_nan { + // gf16_log(x) = NaN for x <= 0 + @compileAssert(true); +} + +invariant gf16_pow_zero_to_zero_returns_one { + // gf16_pow(0, 0) = 1 (by convention) + @compileAssert(true); +} + +invariant gf16_pow_any_to_zero_returns_one { + // gf16_pow(x, 0) = 1 for x != 0 + @compileAssert(true); +} + +invariant gf16_pow_one_to_any_returns_one { + // gf16_pow(1, x) = 1 for finite x + @compileAssert(true); +} + +invariant gf16_sin_zero_returns_zero { + // gf16_sin(0) = 0 + @compileAssert(true); +} + +invariant gf16_cos_zero_returns_one { + // gf16_cos(0) = 1 + @compileAssert(true); +} + +invariant gf16_trig_identity_approx { + // sin^2(x) + cos^2(x) 62 1 for reasonable x values + @compileAssert(true); +} + +test "gf16_add_positive_values" { + const a = gf16_encode_f32(1.5); + const b = gf16_encode_f32(2.5); + const result = gf16_add(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.1); +} + +test "gf16_add_negative_values" { + const a = gf16_encode_f32(-1.5); + const b = gf16_encode_f32(-2.5); + const result = gf16_add(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-4.0, decoded, 0.1); +} + +test "gf16_add_opposite_values" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(-2.0); + const result = gf16_add(a, b); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_add_with_zero" { + const a = gf16_encode_f32(3.5); + const zero = gf16_encode_f32(0.0); + const result1 = gf16_add(a, zero); + const result2 = gf16_add(zero, a); + const decoded1 = gf16_decode_to_f32(result1); + const decoded2 = gf16_decode_to_f32(result2); + try std.testing.expectApproxEqAbs(3.5, decoded1, 0.05); + try std.testing.expectApproxEqAbs(3.5, decoded2, 0.05); +} + +test "gf16_sub_positive_values" { + const a = gf16_encode_f32(5.0); + const b = gf16_encode_f32(2.0); + const result = gf16_sub(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_sub_negative_result" { + const a = gf16_encode_f32(1.0); + const b = gf16_encode_f32(3.0); + const result = gf16_sub(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_sub_with_zero" { + const a = gf16_encode_f32(2.5); + const zero = gf16_encode_f32(0.0); + const result1 = gf16_sub(a, zero); + const result2 = gf16_sub(zero, a); + const decoded1 = gf16_decode_to_f32(result1); + const decoded2 = gf16_decode_to_f32(result2); + try std.testing.expectApproxEqAbs(2.5, decoded1, 0.05); + try std.testing.expectApproxEqAbs(-2.5, decoded2, 0.05); +} + +test "gf16_mul_positive_values" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const result = gf16_mul(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(6.0, decoded, 0.1); +} + +test "gf16_mul_negative_positive" { + const a = gf16_encode_f32(-2.0); + const b = gf16_encode_f32(3.0); + const result = gf16_mul(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-6.0, decoded, 0.1); +} + +test "gf16_mul_with_zero" { + const a = gf16_encode_f32(5.0); + const zero = gf16_encode_f32(0.0); + const result1 = gf16_mul(a, zero); + const result2 = gf16_mul(zero, a); + try std.testing.expect(gf16_is_zero(result1)); + try std.testing.expect(gf16_is_zero(result2)); +} + +test "gf16_mul_by_one" { + const a = gf16_encode_f32(3.5); + const one = gf16_encode_f32(1.0); + const result = gf16_mul(a, one); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.5, decoded, 0.05); +} + +test "gf16_div_positive_values" { + const a = gf16_encode_f32(6.0); + const b = gf16_encode_f32(3.0); + const result = gf16_div(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_div_negative_result" { + const a = gf16_encode_f32(6.0); + const b = gf16_encode_f32(-3.0); + const result = gf16_div(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_div_by_one" { + const a = gf16_encode_f32(2.5); + const one = gf16_encode_f32(1.0); + const result = gf16_div(a, one); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.5, decoded, 0.05); +} + +test "gf16_div_zero_by_value" { + const zero = gf16_encode_f32(0.0); + const a = gf16_encode_f32(5.0); + const result = gf16_div(zero, a); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_div_value_by_zero" { + const a = gf16_encode_f32(5.0); + const zero = gf16_encode_f32(0.0); + const result = gf16_div(a, zero); + try std.testing.expect(gf16_is_inf(result)); +} + +test "gf16_div_inf_by_finite" { + const inf = GF16_INF_POS; + const a = gf16_encode_f32(5.0); + const result = gf16_div(inf, a); + try std.testing.expect(gf16_is_inf(result)); +} + +test "gf16_fma_basic" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(4.0); + const result = gf16_fma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.2); +} + +test "gf16_fma_with_zero" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const zero = gf16_encode_f32(0.0); + const result = gf16_fma(a, b, zero); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(6.0, decoded, 0.15); +} + +test "gf16_sqrt_positive" { + const a = gf16_encode_f32(4.0); + const result = gf16_sqrt(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.05); +} + +test "gf16_sqrt_of_one" { + const a = gf16_encode_f32(1.0); + const result = gf16_sqrt(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.05); +} + +test "gf16_sqrt_of_zero" { + const zero = gf16_encode_f32(0.0); + const result = gf16_sqrt(zero); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_sqrt_negative_nan" { + const neg = gf16_encode_f32(-4.0); + const result = gf16_sqrt(neg); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_square_of_two" { + const a = gf16_encode_f32(2.0); + const result = gf16_square(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.1); +} + +test "gf16_square_of_zero" { + const zero = gf16_encode_f32(0.0); + const result = gf16_square(zero); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_square_of_negative" { + const neg = gf16_encode_f32(-2.0); + const result = gf16_square(neg); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.1); +} + +test "gf16_add_commutative" { + const a = gf16_encode_f32(1.5); + const b = gf16_encode_f32(2.5); + const result1 = gf16_add(a, b); + const result2 = gf16_add(b, a); + try std.testing.expectEqual(result1, result2); +} + +test "gf16_mul_commutative" { + const a = gf16_encode_f32(1.5); + const b = gf16_encode_f32(2.5); + const result1 = gf16_mul(a, b); + const result2 = gf16_mul(b, a); + try std.testing.expectEqual(result1, result2); +} + +test "gf16_sqrt_square_roundtrip" { + const a = gf16_encode_f32(4.0); + const squared = gf16_square(a); + const rooted = gf16_sqrt(squared); + const decoded = gf16_decode_to_f32(rooted); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.2); +} + +test "gf16_eq_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(gf16_eq(a, b)); +} + +test "gf16_eq_different_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(3.5); + try std.testing.expect(!gf16_eq(a, b)); +} + +test "gf16_eq_pos_zero_eq_neg_zero" { + // IEEE 754: +0.0 == -0.0 is true + try std.testing.expect(gf16_eq(GF16_ZERO_POS, GF16_ZERO_NEG)); +} + +test "gf16_eq_nan_not_equal_nan" { + // NaN != NaN per IEEE 754 + try std.testing.expect(!gf16_eq(GF16_NAN, GF16_NAN)); +} + +test "gf16_eq_nan_not_equal_value" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_eq(GF16_NAN, value)); + try std.testing.expect(!gf16_eq(value, GF16_NAN)); +} + +test "gf16_ne_different_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(3.5); + try std.testing.expect(gf16_ne(a, b)); +} + +test "gf16_ne_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(!gf16_ne(a, b)); +} + +test "gf16_ne_nan_not_equal_nan" { + // NaN != NaN per IEEE 754 + try std.testing.expect(gf16_ne(GF16_NAN, GF16_NAN)); +} + +test "gf16_ne_nan_not_equal_value" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(gf16_ne(GF16_NAN, value)); + try std.testing.expect(gf16_ne(value, GF16_NAN)); +} + +test "gf16_lt_less_than" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(gf16_lt(a, b)); +} + +test "gf16_lt_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(!gf16_lt(a, b)); +} + +test "gf16_lt_greater_than" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(!gf16_lt(a, b)); +} + +test "gf16_lt_negative_positive" { + const neg = gf16_encode_f32(-2.0); + const pos = gf16_encode_f32(1.0); + try std.testing.expect(gf16_lt(neg, pos)); +} + +test "gf16_lt_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_lt(GF16_NAN, value)); + try std.testing.expect(!gf16_lt(value, GF16_NAN)); +} + +test "gf16_le_less_than_or_equal" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(gf16_le(a, b)); +} + +test "gf16_le_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(gf16_le(a, b)); +} + +test "gf16_le_greater_than" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(!gf16_le(a, b)); +} + +test "gf16_le_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_le(GF16_NAN, value)); + try std.testing.expect(!gf16_le(value, GF16_NAN)); +} + +test "gf16_gt_greater_than" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(gf16_gt(a, b)); +} + +test "gf16_gt_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(!gf16_gt(a, b)); +} + +test "gf16_gt_less_than" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(!gf16_gt(a, b)); +} + +test "gf16_gt_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_gt(GF16_NAN, value)); + try std.testing.expect(!gf16_gt(value, GF16_NAN)); +} + +test "gf16_ge_greater_than_or_equal" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(gf16_ge(a, b)); +} + +test "gf16_ge_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(gf16_ge(a, b)); +} + +test "gf16_ge_less_than" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(!gf16_ge(a, b)); +} + +test "gf16_ge_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_ge(GF16_NAN, value)); + try std.testing.expect(!gf16_ge(value, GF16_NAN)); +} + +test "gf16_comparison_consistency" { + // Verify: lt, le, gt, ge, eq, ne are mutually consistent + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + + // a < b implies a <= b and !a > b and !a >= b + try std.testing.expect(gf16_lt(a, b)); + try std.testing.expect(gf16_le(a, b)); + try std.testing.expect(!gf16_gt(a, b)); + try std.testing.expect(!gf16_ge(a, b)); +} + +test "gf16_floor_positive_value" { + const a = gf16_encode_f32(2.7); + const result = gf16_floor(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_floor_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_floor(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-3.0, decoded, 0.1); +} + +test "gf16_floor_integer" { + const a = gf16_encode_f32(5.0); + const result = gf16_floor(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.05); +} + +test "gf16_floor_zero" { + const pos_zero = gf16_encode_f32(0.0); + const neg_zero = gf16_encode_f32(-0.0); + try std.testing.expect(gf16_is_zero(gf16_floor(pos_zero))); + try std.testing.expect(gf16_is_zero(gf16_floor(neg_zero))); +} + +test "gf16_floor_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_floor(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_floor(GF16_INF_NEG)); +} + +test "gf16_floor_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_floor(GF16_NAN))); +} + +test "gf16_ceil_positive_value" { + const a = gf16_encode_f32(2.3); + const result = gf16_ceil(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_ceil_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_ceil(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_ceil_integer" { + const a = gf16_encode_f32(5.0); + const result = gf16_ceil(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.05); +} + +test "gf16_ceil_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_ceil(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_ceil(GF16_INF_NEG)); +} + +test "gf16_ceil_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_ceil(GF16_NAN))); +} + +test "gf16_round_half_up" { + const a = gf16_encode_f32(2.5); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); // roundTiesToEven +} + +test "gf16_round_positive_value" { + const a = gf16_encode_f32(2.7); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_round_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-3.0, decoded, 0.1); +} + +test "gf16_round_fractional_down" { + const a = gf16_encode_f32(2.3); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_round_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_round(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_round(GF16_INF_NEG)); +} + +test "gf16_round_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_round(GF16_NAN))); +} + +test "gf16_trunc_positive_value" { + const a = gf16_encode_f32(2.7); + const result = gf16_trunc(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_trunc_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_trunc(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_trunc_zero" { + const pos_zero = gf16_encode_f32(0.0); + const neg_zero = gf16_encode_f32(-0.0); + try std.testing.expect(gf16_is_zero(gf16_trunc(pos_zero))); + try std.testing.expect(gf16_is_zero(gf16_trunc(neg_zero))); +} + +test "gf16_trunc_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_trunc(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_trunc(GF16_INF_NEG)); +} + +test "gf16_trunc_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_trunc(GF16_NAN))); +} + +test "gf16_rounding_floor_vs_trunc_negative" { + // floor(-2.7) = -3.0, trunc(-2.7) = -2.0 + const a = gf16_encode_f32(-2.7); + const floored = gf16_decode_to_f32(gf16_floor(a)); + const truncated = gf16_decode_to_f32(gf16_trunc(a)); + try std.testing.expectApproxEqAbs(-3.0, floored, 0.1); + try std.testing.expectApproxEqAbs(-2.0, truncated, 0.1); +} + +test "gf16_rounding_ceil_vs_trunc_positive" { + // ceil(2.3) = 3.0, trunc(2.3) = 2.0 + const a = gf16_encode_f32(2.3); + const ceiled = gf16_decode_to_f32(gf16_ceil(a)); + const truncated = gf16_decode_to_f32(gf16_trunc(a)); + try std.testing.expectApproxEqAbs(3.0, ceiled, 0.1); + try std.testing.expectApproxEqAbs(2.0, truncated, 0.1); +} + +test "gf16_fms_basic" { + const a = gf16_encode_f32(5.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(2.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(13.0, decoded, 0.2); // 5*3 - 2 = 13 +} + +test "gf16_fms_with_zero_c" { + const a = gf16_encode_f32(4.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(0.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(12.0, decoded, 0.2); // 4*3 - 0 = 12 +} + +test "gf16_fms_negative_result" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-4.0, decoded, 0.2); // 2*3 - 10 = -4 +} + +test "gf16_fms_with_nan" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fms(a, b, GF16_NAN); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_fms_zero_a" { + const a = gf16_encode_f32(0.0); + const b = gf16_encode_f32(5.0); + const c = gf16_encode_f32(3.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-3.0, decoded, 0.2); // 0 - 3 = -3 +} + +test "gf16_hypot_pythagorean_triple" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(4.0); + const result = gf16_hypot(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); // sqrt(9 + 16) = 5 +} + +test "gf16_hypot_both_zero" { + try std.testing.expectEqual(GF16_ZERO_POS, gf16_hypot(GF16_ZERO_POS, GF16_ZERO_POS)); +} + +test "gf16_hypot_one_zero" { + const a = gf16_encode_f32(3.0); + const result = gf16_hypot(a, GF16_ZERO_POS); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.05); +} + +test "gf16_hypot_negative_inputs" { + const a = gf16_encode_f32(-3.0); + const b = gf16_encode_f32(-4.0); + const result = gf16_hypot(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); // sqrt(9 + 16) = 5 +} + +test "gf16_hypot_with_nan" { + const a = gf16_encode_f32(3.0); + const result = gf16_hypot(a, GF16_NAN); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_hypot_with_inf" { + const a = gf16_encode_f32(3.0); + try std.testing.expectEqual(GF16_INF_POS, gf16_hypot(a, GF16_INF_POS)); +} + +test "gf16_fmod_basic" { + const a = gf16_encode_f32(10.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fmod(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); // 10 % 3 = 1 +} + +test "gf16_fmod_exact_division" { + const a = gf16_encode_f32(12.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fmod(a, b); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_fmod_negative_dividend" { + const a = gf16_encode_f32(-10.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fmod(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-1.0, decoded, 0.1); // -10 % 3 = -1 (sign follows dividend) +} + +test "gf16_fmod_fractional" { + const a = gf16_encode_f32(5.5); + const b = gf16_encode_f32(2.0); + const result = gf16_fmod(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.5, decoded, 0.1); // 5.5 % 2 = 1.5 +} + +test "gf16_fmod_zero_divisor" { + const a = gf16_encode_f32(10.0); + const zero = gf16_encode_f32(0.0); + const result = gf16_fmod(a, zero); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_fmod_with_nan" { + const a = gf16_encode_f32(10.0); + const result = gf16_fmod(a, GF16_NAN); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_is_finite_normal_numbers" { + // Verify: normal numbers are finite + const n1 = gf16_encode_f32(1.0); + const n2 = gf16_encode_f32(-1.0); + const n3 = gf16_encode_f32(100.5); + try std.testing.expect(gf16_is_finite(n1)); + try std.testing.expect(gf16_is_finite(n2)); + try std.testing.expect(gf16_is_finite(n3)); +} + +test "gf16_is_finite_zero" { + // Verify: zero is finite + const z1 = gf16_encode_f32(0.0); + const z2 = gf16_encode_f32(-0.0); + try std.testing.expect(gf16_is_finite(z1)); + try std.testing.expect(gf16_is_finite(z2)); +} + +test "gf16_is_finite_false_for_inf" { + // Verify: infinity is not finite + const pos_inf = GF16_INF_POS; + const neg_inf = GF16_INF_NEG; + try std.testing.expect(!gf16_is_finite(pos_inf)); + try std.testing.expect(!gf16_is_finite(neg_inf)); +} + +test "gf16_is_finite_false_for_nan" { + // Verify: NaN is not finite + try std.testing.expect(!gf16_is_finite(GF16_NAN)); +} + +test "gf16_is_normal_true_for_normal" { + // Verify: normal numbers return true + const n1 = gf16_encode_f32(1.0); + const n2 = gf16_encode_f32(-2.5); + const n3 = gf16_encode_f32(100.0); + try std.testing.expect(gf16_is_normal(n1)); + try std.testing.expect(gf16_is_normal(n2)); + try std.testing.expect(gf16_is_normal(n3)); +} + +test "gf16_is_normal_false_for_zero" { + // Verify: zero is not normal + const z1 = gf16_encode_f32(0.0); + const z2 = gf16_encode_f32(-0.0); + try std.testing.expect(!gf16_is_normal(z1)); + try std.testing.expect(!gf16_is_normal(z2)); +} + +test "gf16_is_normal_false_for_inf" { + // Verify: infinity is not normal + try std.testing.expect(!gf16_is_normal(GF16_INF_POS)); + try std.testing.expect(!gf16_is_normal(GF16_INF_NEG)); +} + +test "gf16_is_normal_false_for_nan" { + // Verify: NaN is not normal + try std.testing.expect(!gf16_is_normal(GF16_NAN)); +} + +test "gf16_is_subnormal_true_for_subnormal" { + // Verify: subnormal (denormal) numbers return true + // Smallest subnormal in GF16: exp=0, mant=1 (approximately 2^-14 * 2^-9 = 2^-23) + // We'll check a value that decodes to subnormal + const sub = gf16_encode_f32(0.000001); + const decoded = gf16_decode_to_f32(sub); + // If the value rounds to subnormal, is_subnormal should be true + // This test depends on GF16 subnormal threshold (~6.1e-5) + const is_sub = gf16_is_subnormal(sub); + _ = decoded; + _ = is_sub; + // We just verify the function doesn't crash for now + try std.testing.expect(true); +} + +test "gf16_is_subnormal_false_for_normal" { + // Verify: normal numbers are not subnormal + const n1 = gf16_encode_f32(1.0); + const n2 = gf16_encode_f32(100.0); + try std.testing.expect(!gf16_is_subnormal(n1)); + try std.testing.expect(!gf16_is_subnormal(n2)); +} + +test "gf16_is_subnormal_false_for_zero" { + // Verify: zero is not subnormal (zero is a special case) + const z1 = gf16_encode_f32(0.0); + const z2 = gf16_encode_f32(-0.0); + try std.testing.expect(!gf16_is_subnormal(z1)); + try std.testing.expect(!gf16_is_subnormal(z2)); +} + +test "gf16_is_subnormal_false_for_special" { + // Verify: NaN and infinity are not subnormal + try std.testing.expect(!gf16_is_subnormal(GF16_NAN)); + try std.testing.expect(!gf16_is_subnormal(GF16_INF_POS)); + try std.testing.expect(!gf16_is_subnormal(GF16_INF_NEG)); +} + +test "gf16_classification_complete_coverage" { + // Verify: all GF16 values can be classified + // For any value, exactly one of these should be true: + // - is_finite and (is_normal or is_subnormal or is_zero) + // OR is_inf + // OR is_nan + + const test_values = [_]f32{ + 0.0, -0.0, 1.0, -1.0, 100.0, -100.0, + 0.0001, -0.0001, + }; + + for (test_values) |val| { + const gf = gf16_encode_f32(val); + const is_fin = gf16_is_finite(gf); + const is_inf = gf16_is_inf(gf); + const is_nan = gf16_is_nan(gf); + + // Exactly one of finite, inf, nan should be true + const count = @as(u8, @intFromBool(is_fin)) + + @as(u8, @intFromBool(is_inf)) + + @as(u8, @intFromBool(is_nan)); + try std.testing.expectEqual(@as(u8, 1), count); + } +} + +test "gf16_signbit_positive" { + // Verify: positive values have signbit = false + const val = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_signbit(val)); +} + +test "gf16_signbit_negative" { + // Verify: negative values have signbit = true + const val = gf16_encode_f32(-1.5); + try std.testing.expect(gf16_signbit(val)); +} + +test "gf16_signbit_positive_zero" { + // Verify: positive zero has signbit = false + const zero_pos = GF16_ZERO_POS; + try std.testing.expect(!gf16_signbit(zero_pos)); +} + +test "gf16_signbit_negative_zero" { + // Verify: negative zero has signbit = true + const zero_neg = GF16_ZERO_NEG; + try std.testing.expect(gf16_signbit(zero_neg)); +} + +test "gf16_signbit_infinity" { + // Verify: signbit is set for negative infinity, not for positive + try std.testing.expect(!gf16_signbit(GF16_INF_POS)); + try std.testing.expect(gf16_signbit(GF16_INF_NEG)); +} + +test "gf16_signbit_nan" { + // Verify: NaN can have signbit set or not (we check both cases) + // Most NaN implementations propagate signbit + const nan_with_sign = GF16_NAN | 0x8000; + try std.testing.expect(gf16_signbit(nan_with_sign)); +} + +test "gf16_sign_positive" { + // Verify: positive values return +1 + const v1 = gf16_encode_f32(1.0); + const v2 = gf16_encode_f32(100.5); + try std.testing.expectEqual(@as(i8, 1), gf16_sign(v1)); + try std.testing.expectEqual(@as(i8, 1), gf16_sign(v2)); +} + +test "gf16_sign_negative" { + // Verify: negative values return -1 + const v1 = gf16_encode_f32(-1.0); + const v2 = gf16_encode_f32(-100.5); + try std.testing.expectEqual(@as(i8, -1), gf16_sign(v1)); + try std.testing.expectEqual(@as(i8, -1), gf16_sign(v2)); +} + +test "gf16_sign_zero" { + // Verify: zero (positive or negative) returns 0 + try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_ZERO_POS)); + try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_ZERO_NEG)); +} + +test "gf16_sign_nan" { + // Verify: NaN returns 0 (IEEE 754 specifies sign of NaN is undefined) + try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_NAN)); +} + +test "gf16_sign_infinity" { + // Verify: positive infinity returns +1, negative returns -1 + try std.testing.expectEqual(@as(i8, 1), gf16_sign(GF16_INF_POS)); + try std.testing.expectEqual(@as(i8, -1), gf16_sign(GF16_INF_NEG)); +} + +test "gf16_sign_matches_signbit" { + // Verify: gf16_sign and gf16_signbit are consistent for non-zero values + const pos_val = gf16_encode_f32(5.5); + const neg_val = gf16_encode_f32(-5.5); + + try std.testing.expect(!gf16_signbit(pos_val) and gf16_sign(pos_val) > 0); + try std.testing.expect(gf16_signbit(neg_val) and gf16_sign(neg_val) < 0); +} + +test "gf16_clamp_in_range" { + // Verify: value within range is unchanged + const x = gf16_encode_f32(5.0); + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); +} + +test "gf16_clamp_below_min" { + // Verify: value below min returns min + const x = gf16_encode_f32(-5.0); + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.0, decoded, 0.1); +} + +test "gf16_clamp_above_max" { + // Verify: value above max returns max + const x = gf16_encode_f32(15.0); + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.1); +} + +test "gf16_clamp_with_nan" { + // Verify: NaN propagates + const x = GF16_NAN; + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_lerp_t_zero" { + // Verify: lerp with t=0 returns a + const a = gf16_encode_f32(10.0); + const b = gf16_encode_f32(20.0); + const t = gf16_encode_f32(0.0); + const result = gf16_lerp(a, b, t); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.1); +} + +test "gf16_lerp_t_one" { + // Verify: lerp with t=1 returns b + const a = gf16_encode_f32(10.0); + const b = gf16_encode_f32(20.0); + const t = gf16_encode_f32(1.0); + const result = gf16_lerp(a, b, t); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(20.0, decoded, 0.1); +} + +test "gf16_lerp_t_half" { + // Verify: lerp with t=0.5 returns midpoint + const a = gf16_encode_f32(0.0); + const b = gf16_encode_f32(10.0); + const t = gf16_encode_f32(0.5); + const result = gf16_lerp(a, b, t); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); +} + +test "gf16_lerp_with_nan" { + // Verify: NaN propagates + const a = GF16_NAN; + const b = gf16_encode_f32(20.0); + const t = gf16_encode_f32(0.5); + const result = gf16_lerp(a, b, t); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_fnma_basic" { + // Verify: fnma(a, b, c) = -(a*b) + c + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fnma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-(2.0 * 3.0) + 10.0, decoded, 0.1); // = 4.0 +} + +test "gf16_fnma_zero_multiplier" { + // Verify: fnma with zero multiplier returns c + const a = gf16_encode_f32(0.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fnma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.1); +} + +test "gf16_fnma_zero_addend" { + // Verify: fnma with c=0 returns -(a*b) + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(0.0); + const result = gf16_fnma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-(2.0 * 3.0), decoded, 0.1); // = -6.0 +} + +test "gf16_fnma_with_nan" { + // Verify: NaN propagates + const a = GF16_NAN; + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fnma(a, b, c); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_exp_zero" { + // Verify: e^0 = 1 + const x = gf16_encode_f32(0.0); + const result = gf16_exp(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); +} + +test "gf16_exp_one" { + // Verify: e^1 63 2.718 + const x = gf16_encode_f32(1.0); + const result = gf16_exp(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.718, decoded, 0.1); +} + +test "gf16_exp_negative" { + // Verify: e^-1 64 0.368 + const x = gf16_encode_f32(-1.0); + const result = gf16_exp(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.368, decoded, 0.05); +} + +test "gf16_exp_large_positive" { + // Verify: e^88 is very large (returns Inf) + const x = gf16_encode_f32(88.0); + const result = gf16_exp(x); + try std.testing.expect(gf16_is_inf(result)); +} + +test "gf16_log_one" { + // Verify: ln(1) = 0 + const x = gf16_encode_f32(1.0); + const result = gf16_log(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.0, decoded, 0.05); +} + +test "gf16_log_e" { + // Verify: ln(e) 65 1 + const e = gf16_encode_f32(2.71828); + const result = gf16_log(e); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); +} + +test "gf16_log_zero_or_negative" { + // Verify: ln(0) or ln(x<0) = NaN + const zero = gf16_encode_f32(0.0); + const neg = gf16_encode_f32(-1.0); + try std.testing.expect(gf16_is_nan(gf16_log(zero))); + try std.testing.expect(gf16_is_nan(gf16_log(neg))); +} + +test "gf16_log2_eight" { + // Verify: log2(8) = 3 + const x = gf16_encode_f32(8.0); + const result = gf16_log2(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_log10_ten" { + // Verify: log10(10) = 1 + const x = gf16_encode_f32(10.0); + const result = gf16_log10(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); +} + +test "gf16_pow_two_cubed" { + // Verify: 2^3 = 8 + const base = gf16_encode_f32(2.0); + const exp = gf16_encode_f32(3.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(8.0, decoded, 0.1); +} + +test "gf16_pow_zero_to_zero" { + // Verify: 0^0 = 1 (by convention) + const base = gf16_encode_f32(0.0); + const exp = gf16_encode_f32(0.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.01); +} + +test "gf16_pow_any_to_zero" { + // Verify: x^0 = 1 for x != 0 + const base = gf16_encode_f32(5.5); + const exp = gf16_encode_f32(0.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.01); +} + +test "gf16_pow_zero_to_positive" { + // Verify: 0^x = 0 for x > 0 + const base = gf16_encode_f32(0.0); + const exp = gf16_encode_f32(2.0); + const result = gf16_pow(base, exp); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_pow_one_to_any" { + // Verify: 1^x = 1 + const base = gf16_encode_f32(1.0); + const exp = gf16_encode_f32(5.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.01); +} + +test "gf16_sin_zero" { + // Verify: sin(0) = 0 + const x = gf16_encode_f32(0.0); + const result = gf16_sin(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.0, decoded, 0.05); +} + +test "gf16_sin_small_angle" { + // Verify: sin(66/6) 67 0.5 + const pi_six = gf16_encode_f32(3.14159 / 6.0); + const result = gf16_sin(pi_six); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.5, decoded, 0.05); +} + +test "gf16_cos_zero" { + // Verify: cos(0) = 1 + const x = gf16_encode_f32(0.0); + const result = gf16_cos(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.05); +} + +test "gf16_cos_small_angle" { + // Verify: cos(68/6) 69 0.866 + const pi_six = gf16_encode_f32(3.14159 / 6.0); + const result = gf16_cos(pi_six); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.866, decoded, 0.05); +} + +test "gf16_trig_identity" { + // Verify: sin^2(x) + cos^2(x) 70 1 for small x + const x = gf16_encode_f32(0.5); + const sin_val = gf16_decode_to_f32(gf16_sin(x)); + const cos_val = gf16_decode_to_f32(gf16_cos(x)); + const sum = sin_val * sin_val + cos_val * cos_val; + try std.testing.expectApproxEqAbs(1.0, sum, 0.1); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "gf16_encode_throughput" { + // Measure: gf16_encode_f32 calls per second + // Target: > 10M encodes/sec on typical hardware + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_encode_f32(1.5); + } + _ = result; +} + +bench "gf16_decode_throughput" { + // Measure: gf16_decode_to_f32 calls per second + // Target: > 10M decodes/sec on typical hardware + @setEvalBranchQuota(10000); + var result: f32 = 0; + for (0..1000) |_| { + result = gf16_decode_to_f32(0x3C00); + } + _ = result; +} + +bench "gf16_roundtrip_latency" { + // Measure: encode + decode latency in nanoseconds + // Target: < 100ns for typical values + @setEvalBranchQuota(10000); + var result: f32 = 0; + const value: f32 = 1.5; + for (0..1000) |_| { + result = gf16_decode_to_f32(gf16_encode_f32(value)); + } + _ = result; +} + +bench "gf16_round_phi_latency" { + // Measure: nanoseconds to gf16_round_phi(1.0) + // Target: < 200ns + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_round_phi(1.0); + } + _ = result; +} + +bench "gf16_extract_sign_latency" { + // Measure: nanoseconds to extract sign + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: i8 = 0; + for (0..1000) |_| { + result = gf16_extract_sign(0x8000); + } + _ = result; +} + +bench "gf16_extract_exponent_latency" { + // Measure: nanoseconds to extract exponent + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: i8 = 0; + for (0..1000) |_| { + result = gf16_extract_exponent(0x3C00); + } + _ = result; +} + +bench "gf16_is_inf_latency" { + // Measure: nanoseconds to check if infinity + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = gf16_is_inf(0x7E00); + } + _ = result; +} + +bench "gf16_is_nan_latency" { + // Measure: nanoseconds to check if NaN + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = gf16_is_nan(0xFE01); + } + _ = result; +} + +bench "gf16_negate_latency" { + // Measure: nanoseconds to negate + // Target: < 10ns (single XOR operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_negate(0x3C00); + } + _ = result; +} + +bench "gf16_abs_latency" { + // Measure: nanoseconds to compute absolute value + // Target: < 10ns (single AND operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_abs(0xBC00); + } + _ = result; +} + +bench "gf16_max_latency" { + // Measure: nanoseconds to compute max of two values + // Target: < 100ns (includes decode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_max(a, b); + } + _ = result; +} + +bench "gf16_min_latency" { + // Measure: nanoseconds to compute min of two values + // Target: < 100ns (includes decode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_min(a, b); + } + _ = result; +} + +bench "gf16_add_latency" { + // Measure: nanoseconds to add two values + // Target: < 200ns (includes decode + add + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_add(a, b); + } + _ = result; +} + +bench "gf16_sub_latency" { + // Measure: nanoseconds to subtract two values + // Target: < 200ns (includes decode + sub + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_sub(a, b); + } + _ = result; +} + +bench "gf16_mul_latency" { + // Measure: nanoseconds to multiply two values + // Target: < 200ns (includes decode + mul + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3D80; + for (0..1000) |_| { + result = gf16_mul(a, b); + } + _ = result; +} + +bench "gf16_div_latency" { + // Measure: nanoseconds to divide two values + // Target: < 300ns (includes decode + div + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + for (0..1000) |_| { + result = gf16_div(a, b); + } + _ = result; +} + +bench "gf16_sqrt_latency" { + // Measure: nanoseconds to compute square root + // Target: < 300ns (includes decode + sqrt + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_sqrt(a); + } + _ = result; +} + +bench "gf16_fma_latency" { + // Measure: nanoseconds for fused multiply-add + // Target: < 300ns (fused operation, more accurate than separate) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + const c: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_fma(a, b, c); + } + _ = result; +} + +bench "gf16_square_latency" { + // Measure: nanoseconds to square a value + // Target: < 200ns (uses mul internally) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_square(a); + } + _ = result; +} + +bench "gf16_eq_latency" { + // Measure: nanoseconds to compare equality + // Target: < 30ns (simple comparison with NaN check) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_eq(a, b); + } + _ = result; +} + +bench "gf16_ne_latency" { + // Measure: nanoseconds to compare not-equal + // Target: < 30ns (negation of eq) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_ne(a, b); + } + _ = result; +} + +bench "gf16_lt_latency" { + // Measure: nanoseconds to compare less-than + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_lt(a, b); + } + _ = result; +} + +bench "gf16_le_latency" { + // Measure: nanoseconds to compare less-than-or-equal + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_le(a, b); + } + _ = result; +} + +bench "gf16_gt_latency" { + // Measure: nanoseconds to compare greater-than + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_gt(a, b); + } + _ = result; +} + +bench "gf16_ge_latency" { + // Measure: nanoseconds to compare greater-than-or-equal + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_ge(a, b); + } + _ = result; +} + +bench "gf16_floor_latency" { + // Measure: nanoseconds to compute floor + // Target: < 200ns (includes decode + floor + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_floor(a); + } + _ = result; +} + +bench "gf16_ceil_latency" { + // Measure: nanoseconds to compute ceil + // Target: < 200ns (includes decode + ceil + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_ceil(a); + } + _ = result; +} + +bench "gf16_round_latency" { + // Measure: nanoseconds to compute round + // Target: < 200ns (includes decode + round + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_round(a); + } + _ = result; +} + +bench "gf16_trunc_latency" { + // Measure: nanoseconds to compute trunc + // Target: < 200ns (includes decode + trunc + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_trunc(a); + } + _ = result; +} + +bench "gf16_fms_latency" { + // Measure: nanoseconds for fused multiply-subtract + // Target: < 300ns (fused operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + const c: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_fms(a, b, c); + } + _ = result; +} + +bench "gf16_hypot_latency" { + // Measure: nanoseconds to compute hypotenuse + // Target: < 400ns (includes sqrt) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D80; + const b: GF16 = 0x3E00; + for (0..1000) |_| { + result = gf16_hypot(a, b); + } + _ = result; +} + +bench "gf16_fmod_latency" { + // Measure: nanoseconds to compute modulo + // Target: < 300ns (includes decode + mod + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3F00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_fmod(a, b); + } + _ = result; +} + +bench "gf16_is_finite_latency" { + // Measure: nanoseconds to check if value is finite + // Target: < 30ns (simple bit checks) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_is_finite(val); + } + _ = result; +} + +bench "gf16_is_normal_latency" { + // Measure: nanoseconds to check if value is normal + // Target: < 40ns (extraction + range check) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_is_normal(val); + } + _ = result; +} + +bench "gf16_is_subnormal_latency" { + // Measure: nanoseconds to check if value is subnormal + // Target: < 40ns (extraction + mantissa check) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x0001; + for (0..1000) |_| { + result = gf16_is_subnormal(val); + } + _ = result; +} + +bench "gf16_signbit_latency" { + // Measure: nanoseconds to check sign bit + // Target: < 5ns (single bit test) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x8000; + for (0..1000) |_| { + result = gf16_signbit(val); + } + _ = result; +} + +bench "gf16_sign_latency" { + // Measure: nanoseconds to get sign value + // Target: < 30ns (includes zero/nan/inf checks) + @setEvalBranchQuota(10000); + var result: i8 = 0; + const val: GF16 = 0xBC00; + for (0..1000) |_| { + result = gf16_sign(val); + } + _ = result; +} + +bench "gf16_clamp_latency" { + // Measure: nanoseconds to clamp value to range + // Target: < 200ns (includes decode + compare + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3F00; + const min_val: GF16 = 0x3C00; + const max_val: GF16 = 0x4800; + for (0..1000) |_| { + result = gf16_clamp(x, min_val, max_val); + } + _ = result; +} + +bench "gf16_lerp_latency" { + // Measure: nanoseconds to compute linear interpolation + // Target: < 300ns (includes decode + computation + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3C00; + const b: GF16 = 0x4800; + const t: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_lerp(a, b, t); + } + _ = result; +} + +bench "gf16_fnma_latency" { + // Measure: nanoseconds for fused negative multiply-add + // Target: < 300ns (fused operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + const c: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_fnma(a, b, c); + } + _ = result; +} + +bench "gf16_exp_latency" { + // Measure: nanoseconds to compute exponential + // Target: < 500ns (Taylor series) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_exp(x); + } + _ = result; +} + +bench "gf16_log_latency" { + // Measure: nanoseconds to compute natural log + // Target: < 300ns (includes decode + log + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3E00; + for (0..1000) |_| { + result = gf16_log(x); + } + _ = result; +} + +bench "gf16_pow_latency" { + // Measure: nanoseconds to compute power + // Target: < 400ns (includes decode + pow + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const base: GF16 = 0x3D00; + const exp: GF16 = 0x3D80; + for (0..1000) |_| { + result = gf16_pow(base, exp); + } + _ = result; +} + +bench "gf16_sin_latency" { + // Measure: nanoseconds to compute sine + // Target: < 500ns (Taylor series) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_sin(x); + } + _ = result; +} + +bench "gf16_cos_latency" { + // Measure: nanoseconds to compute cosine + // Target: < 500ns (Taylor series) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_cos(x); + } + _ = result; +} + + + + diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/gf20.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/gf20.t27 new file mode 100644 index 0000000000..5fec680c66 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/gf20.t27 @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf20.t27 +// GoldenFloat20 0 20-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 6 (P1) + +module GF20 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF20 bit layout: [S|EEE EEE|MMM MMMM MMMM MMM] + // S: 1 bit (sign) + // E: 7 bits (exponent) + // M: 12 bits (mantissa) + + const BITS : u8 = 20; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 7; + const MANT_BITS : u8 = 12; + + // Bias for exponent (2^(7-1) - 1 = 63) + const EXP_BIAS : u8 = 63; + + // 141-ratio: exp/mant = 7/12 142 0.583 (phi_distance = 0.035) + const PHI_DISTANCE : f64 = 0.03463264154356299; + + // 143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 + // 2. GoldenFloat20 Type + // 208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 + + struct GF20 { + raw : u32, // 20-bit value stored in u32 + } + + // 281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 + // 3. Encoding/Decoding + // 346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 + + // Encode f32 to GF20 + fn encode(value: f32) -> GF20 { + if (value == 0.0) { + return GF20{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i16; + const exp_biased = (exp_unbiased + EXP_BIAS as i16) as u8; + + // Clamp exponent + const exp_clamped = clamp(exp_biased, 0, (1 << EXP_BITS) - 1); + + // Extract mantissa (12 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF20{ + raw = ((sign as u32) << 19) | + ((exp_clamped as u32) << MANT_BITS) | + (mant as u32) + }; + } + + // Decode GF20 to f32 + fn decode(gf: GF20) -> f32 { + const sign = (gf.raw >> 19) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x7F) as u8; + const mant = (gf.raw & 0xFFF) as u16; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS as i16 + 1 + } else { + (exp_biased as i16) - EXP_BIAS as i16 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 4096.0 + } else { + 1.0 + (mant as f32) / 4096.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 + // 4. Format Properties + // 484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 + + fn max_value() -> f32 { + const mant_max = 1.0 + 4095.0 / 4096.0; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 4096.0; + const exp_min = -EXP_BIAS as i16 + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 4096.0; // 0.00024414 + } + + // 557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 + // 5. Validation + // 622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF20"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759 + // 6. Use Cases + // 760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832 + + // GF20 is optimal for: + // - High-precision ML training + // - Gradient accumulation + // - Scientific computing + // - Near-fp32 quality with 38% memory savings + + // Memory: 20 bits = 2.5 bytes (~1.6x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 20.0 / 32.0; // 0.625 + + // 833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897 + // 7. Helper Functions + // 898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970 + + fn floor_log2(x: f32) -> i16 { + if (x <= 0.0) { return -32768; } + let exp : i16 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i16, mant_bits: u8) -> u16 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1u16 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u16; + } + + fn clamp(x: u8, min: u8, max: u8) -> u8 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF20 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 97197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073 + // TDD-Inside-Spec: Tests and Invariants for GF20 + // 1074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176 + + test gf20_decode_zero + given gf = GF20{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf20_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf20_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf20_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf20_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf20_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf20_phi_distance_within_tolerance + given phi_dist = PHI_DISTANCE + then phi_dist < 0.04 + + test gf20_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.625) < 0.01 + + test gf20_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf20_bits_constant + assert BITS == 20 + + invariant gf20_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf20_exp_bits_is_seven + assert EXP_BITS == 7 + + invariant gf20_mant_bits_is_twelve + assert MANT_BITS == 12 + + invariant gf20_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf20_phi_distance_below_threshold + assert PHI_DISTANCE < 0.04 + + invariant gf20_exp_bias_positive + assert EXP_BIAS > 0 + + test gf20_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf20_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf20_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf20_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf20_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf20_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf20_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf20_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf20_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf20_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf20_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf20_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf20_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf20_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf20_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf20_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf20_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf20_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf20_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf20_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf20_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf20_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf20_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf20_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf20_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf20_encode_latency + measure: nanoseconds to encode(1.0) + target: < 200ns + + bench gf20_decode_latency + measure: nanoseconds to decode(GF20{raw = 524288}) + target: < 150ns +} diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/gf24.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/gf24.t27 new file mode 100644 index 0000000000..ffb9ff37bf --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/gf24.t27 @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf24.t27 +// GoldenFloat24 0 24-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 7 (P1) + +module GF24 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF24 bit layout: [S|EEEE EEEE|MMM MMMM MMMM MMMM MM] + // S: 1 bit (sign) + // E: 9 bits (exponent) + // M: 14 bits (mantissa) + + const BITS : u8 = 24; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 9; + const MANT_BITS : u8 = 14; + + // Bias for exponent (2^(9-1) - 1 = 255) + const EXP_BIAS : u16 = 255; + + // 141-ratio: exp/mant = 9/14 142 0.643 (phi_distance = 0.025) + const PHI_DISTANCE : f64 = 0.02482317991669112; + + // 143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 + // 2. GoldenFloat24 Type + // 208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 + + struct GF24 { + raw : u32, // 24-bit value stored in u32 + } + + // 281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 + // 3. Encoding/Decoding + // 346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 + + // Encode f32 to GF24 + fn encode(value: f32) -> GF24 { + if (value == 0.0) { + return GF24{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i16; + const exp_biased = (exp_unbiased + EXP_BIAS as i16) as u16; + + // Clamp exponent + const exp_clamped = clamp_u16(exp_biased, 0, (1u16 << EXP_BITS) - 1); + + // Extract mantissa (14 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF24{ + raw = ((sign as u32) << 23) | + ((exp_clamped as u32) << MANT_BITS) | + (mant as u32) + }; + } + + // Decode GF24 to f32 + fn decode(gf: GF24) -> f32 { + const sign = (gf.raw >> 23) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x1FF) as u16; + const mant = (gf.raw & 0x3FFF) as u16; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -(EXP_BIAS as i16) + 1 + } else { + (exp_biased as i16) - EXP_BIAS as i16 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 16384.0 + } else { + 1.0 + (mant as f32) / 16384.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 + // 4. Format Properties + // 484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 + + fn max_value() -> f32 { + const mant_max = 1.0 + 16383.0 / 16384.0; + const exp_max = (1i16 << EXP_BITS) - 1 - EXP_BIAS as i16; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 16384.0; + const exp_min = -(EXP_BIAS as i16) + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 16384.0; // 0.000061035 + } + + // 557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 + // 5. Validation + // 622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF24"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759 + // 6. Use Cases + // 760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832 + + // GF24 is optimal for: + // - Very high precision quantization + // - Critical numerical stability + // - Financial calculations + // - 25% memory savings vs FP32 + + // Memory: 24 bits = 3 bytes (~1.33x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 24.0 / 32.0; // 0.75 + + // 833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897 + // 7. Helper Functions + // 898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970 + + fn floor_log2(x: f32) -> i16 { + if (x <= 0.0) { return -32768; } + let exp : i16 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i16, mant_bits: u8) -> u16 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1u16 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u16; + } + + fn clamp_u16(x: u16, min: u16, max: u16) -> u16 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF24 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 97197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073 + // TDD-Inside-Spec: Tests and Invariants for GF24 + // 1074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176 + + test gf24_decode_zero + given gf = GF24{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf24_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf24_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf24_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf24_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf24_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf24_phi_distance_within_tolerance + given phi_dist = PHI_DISTANCE + then phi_dist < 0.03 + + test gf24_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.75) < 0.01 + + test gf24_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf24_bits_constant + assert BITS == 24 + + invariant gf24_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf24_exp_bits_is_nine + assert EXP_BITS == 9 + + invariant gf24_mant_bits_is_fourteen + assert MANT_BITS == 14 + + invariant gf24_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf24_phi_distance_below_threshold + assert PHI_DISTANCE < 0.03 + + invariant gf24_exp_bias_positive + assert EXP_BIAS > 0 + + test gf24_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf24_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf24_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf24_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf24_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf24_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf24_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf24_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf24_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf24_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf24_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf24_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf24_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf24_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf24_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf24_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf24_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf24_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf24_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf24_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf24_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf24_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf24_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf24_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf24_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf24_encode_latency + measure: nanoseconds to encode(1.0) + target: < 250ns + + bench gf24_decode_latency + measure: nanoseconds to decode(GF24{raw = 8388608}) + target: < 200ns +} diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/gf256.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/gf256.t27 new file mode 100644 index 0000000000..2d2facf950 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/gf256.t27 @@ -0,0 +1,595 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf256.t27 — GoldenFloat256 Encode/Decode +; GF256: 256-bit floating point with 1 sign + 32 exponent + 223 mantissa +; Bit layout: [S(1) E(32) M(223)] = [255:255][254:223][222:0] +; Maximum precision format for scientific computing and simulation +; φ² + 1/φ² = 3 | TRINITY + +module triformat-gf256; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const SIGN_SHIFT : u16 = 255; +pub const EXP_SHIFT : u16 = 223; +pub const MANT_SHIFT : u16 = 0; + +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 32; +pub const MANT_BITS : u8 = 223; + +pub const EXP_MAX : u32 = 0xFFFFFFFF; // 4294967295 (all ones in 32 bits) +pub const EXP_MIN : u32 = 0x00000000; + +pub const BIAS : i64 = 2147483647; // Exponent bias for GF256: 2^(32-1) - 1 +pub const SPECIAL_EXP : u32 = 0xFFFFFFFF; // All ones = special (Inf/NaN) + +pub const PHI_BIAS : u64 = 67108864; // Phi-optimized rounding bias + +// ============================================================================ +// Types +// ============================================================================ + +// GF256 represented as u256 (simulated via struct since Zig 0.15 doesn't have native u256) +pub struct GF256 { + parts: [4]u64, // parts[0] = LSB, parts[3] = MSB +} + +// ============================================================================ +// Conversion Helpers +// ============================================================================ + +// gf256_zero() -> GF256 +// Returns positive zero +pub fn gf256_zero() GF256 { + return GF256{ .parts = [_]u64{0, 0, 0, 0} }; +} + +// gf256_negative_zero() -> GF256 +// Returns negative zero +pub fn gf256_negative_zero() GF256 { + var result = gf256_zero(); + result.parts[3] |= 0x8000000000000000; // Set MSB of most significant part + return result; +} + +// gf256_inf_positive() -> GF256 +// Returns positive infinity +pub fn gf256_inf_positive() GF256 { + // Exp = all ones (0xFFFFFFFF), mant = 0 + // Exp at bits 254-223, so in parts[3] and parts[2] + return GF256{ + .parts = [_]u64{ + 0x0000000000000000, // parts[0] LSB + 0x0000000000000000, // parts[1] + 0xFFFFFFFF00000000, // parts[2]: exp bits 0-31 | mant bits 32-63 (0) + 0x00000000FFFFFFFF, // parts[3]: exp bits 32-63 | sign (0) + }, + }; +} + +// gf256_inf_negative() -> GF256 +// Returns negative infinity +pub fn gf256_inf_negative() GF256 { + var result = gf256_inf_positive(); + result.parts[3] |= 0x8000000000000000; // Set sign bit + return result; +} + +// gf256_nan() -> GF256 +// Returns NaN +pub fn gf256_nan() GF256 { + var result = gf256_inf_positive(); + result.parts[0] = 1; // Set mant LSB to make it NaN + return result; +} + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// gf256_extract_sign(gf256: GF256) -> i8 +// Extract sign bit (bit 255) +// Returns: 0 for positive, -1 for negative +pub fn gf256_extract_sign(gf256: GF256) i8 { + const bit = (gf256.parts[3] >> 63) & 1; + return if (bit != 0) -1 else 0; +} + +// gf256_extract_exponent(gf256: GF256) -> u32 +// Extract exponent bits (bits 254-223) +// Returns: 0-4294967295 +pub fn gf256_extract_exponent(gf256: GF256) u32 { + // Exponent spans bits 31-0 across parts[3] (bits 63-32) and parts[2] (bits 31-0) + // parts[3] bits 0-31 = exp bits 31-0 + // parts[2] bits 63-32 = exp bits 0-0? No, let's recalculate: + // Total bits: 256 = [S(255)] E(254:223) M(222:0) + // Exp is 32 bits at positions 254-223 + // parts[3] covers bits 255-192 + // parts[2] covers bits 191-128 + // parts[1] covers bits 127-64 + // parts[0] covers bits 63-0 + // Exp bits 254-223 map to: + // bits 254-192 = 62 bits -> parts[3] bits 62-0 (but exp is only 32 bits) + // Wait, exp is at 254:223 = 32 bits + // Bit 254 is in parts[3] at position (254 - 192) = 62 + // Bit 223 is in parts[3] at position (223 - 192) = 31 + // So exp is parts[3] >> 31 & 0xFFFFFFFF + return @as(u32, @truncate(gf256.parts[3] >> 31)); +} + +// gf256_extract_mantissa(gf256: GF256) -> GF256 +// Extract mantissa bits (bits 222-0) +// Returns: GF256 struct with mantissa in parts[0-2] and parts[3] bits 30-0 +pub fn gf256_extract_mantissa(gf256: GF256) GF256 { + var result = gf256; + // Clear sign bit and exponent bits + result.parts[3] &= 0x7FFFFFFF; // Clear sign + result.parts[3] &= 0x00000000; // Clear exp bits 31-0 in parts[3] + return result; +} + +// ============================================================================ +// Assembly Functions +// ============================================================================ + +// gf256_from_components(sign: i8, exp: u32, mant: GF256) -> GF256 +// Assemble GF256 from sign, exponent, mantissa +pub fn gf256_from_components(sign: i8, exp: u32, mant: GF256) GF256 { + var result = mant; + // Set sign + const sign_bit = if (sign < 0) 1 else 0; + if (sign_bit != 0) { + result.parts[3] |= 0x8000000000000000; + } else { + result.parts[3] &= 0x7FFFFFFFFFFFFFFF; + } + // Set exponent (bits 254-223 -> parts[3] bits 62-31) + result.parts[3] = (result.parts[3] & 0x7FFFFFFF) | (@as(u64, exp) << 31); + return result; +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// gf256_is_zero(gf256: GF256) -> bool +// Check if GF256 is zero (positive or negative) +pub fn gf256_is_zero(gf256: GF256) bool { + return gf256.parts[0] == 0 and gf256.parts[1] == 0 and + gf256.parts[2] == 0 and (gf256.parts[3] & 0x7FFFFFFFFFFFFFFF) == 0; +} + +// gf256_is_special(gf256: GF256) -> bool +// Check if GF256 is Inf or NaN (exp == 4294967295) +pub fn gf256_is_special(gf256: GF256) bool { + return gf256_extract_exponent(gf256) == EXP_MAX; +} + +// gf256_is_inf(gf256: GF256) -> bool +// Check if GF256 is infinity (exp == max, mant == 0) +pub fn gf256_is_inf(gf256: GF256) bool { + if (!gf256_is_special(gf256)) { + return false; + } + const mant = gf256_extract_mantissa(gf256); + return mant.parts[0] == 0 and mant.parts[1] == 0 and + mant.parts[2] == 0 and mant.parts[3] == 0; +} + +// gf256_is_nan(gf256: GF256) -> bool +// Check if GF256 is NaN (exp == max, mant != 0) +pub fn gf256_is_nan(gf256: GF256) bool { + if (!gf256_is_special(gf256)) { + return false; + } + const mant = gf256_extract_mantissa(gf256); + return mant.parts[0] != 0 or mant.parts[1] != 0 or + mant.parts[2] != 0 or mant.parts[3] != 0; +} + +// ============================================================================ +// Encode/Decode Functions (via f64 with reduced precision) +// ============================================================================ + +// gf256_encode_f64(f64: f64) -> GF256 +// Encode IEEE 754 double precision to GF256 +// Note: f64 has only 52 mantissa bits, so precision is not fully utilized +pub fn gf256_encode_f64(value: f64) GF256 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) gf256_negative_zero() else gf256_zero(); + } + + // Handle NaN + if (std.math.isNan(value)) { + return gf256_nan(); + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) gf256_inf_negative() else gf256_inf_positive(); + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f64 components + const f64_bits: u64 = @bitCast(abs_value); + var f64_exp: i64 = @as(i64, @intCast((f64_bits >> 52) & 0x7FF)) - 1023; + var f64_mant: u64 = f64_bits & 0x000FFFFFFFFFFFFF; + + // Convert exp from f64 bias (1023) to GF256 bias (2147483647) + var gf256_exp = @as(u32, @intCast(f64_exp + BIAS)); + + // Clamp exponent + if (gf256_exp >= EXP_MAX) { + return if (sign < 0) gf256_inf_negative() else gf256_inf_positive(); + } + + // Create mantissa struct with f64 mantissa in parts[0] + var mant: GF256 = undefined; + mant.parts[0] = f64_mant; + mant.parts[1] = 0; + mant.parts[2] = 0; + mant.parts[3] = 0; + + return gf256_from_components(sign, gf256_exp, mant); +} + +// gf256_decode_f64(gf256: GF256) -> f64 +// Decode GF256 to IEEE 754 double precision +// Note: This loses precision as f64 has only 52 mantissa bits +pub fn gf256_decode_f64(gf256: GF256) f64 { + // Handle zero + if (gf256_is_zero(gf256)) { + return if (gf256_extract_sign(gf256) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (gf256_is_nan(gf256)) { + return std.math.nan(f64); + } + + // Handle Infinity + if (gf256_is_inf(gf256)) { + return if (gf256_extract_sign(gf256) < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + // Extract components + const sign = gf256_extract_sign(gf256); + const exp = gf256_extract_exponent(gf256); + const mant = gf256_extract_mantissa(gf256); + + // Use only first 52 bits of mantissa + const mant_f64 = @as(f64, @floatFromInt(mant.parts[0])) / 4503599627370496.0; + const bias_adjusted = @as(i64, @intCast(exp)) - BIAS; + + if (bias_adjusted < -1022 or bias_adjusted > 1023) { + return if (sign < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + const value = mant_f64 * std.math.pow(f64, 2.0, @as(f64, @floatFromInt(bias_adjusted))); + return if (sign < 0) -value else value; +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// gf256_add(a: GF256, b: GF256) -> GF256 +pub fn gf256_add(a: GF256, b: GF256) GF256 { + return gf256_encode_f64(gf256_decode_f64(a) + gf256_decode_f64(b)); +} + +// gf256_sub(a: GF256, b: GF256) -> GF256 +pub fn gf256_sub(a: GF256, b: GF256) GF256 { + return gf256_encode_f64(gf256_decode_f64(a) - gf256_decode_f64(b)); +} + +// gf256_mul(a: GF256, b: GF256) -> GF256 +pub fn gf256_mul(a: GF256, b: GF256) GF256 { + return gf256_encode_f64(gf256_decode_f64(a) * gf256_decode_f64(b)); +} + +// gf256_div(a: GF256, b: GF256) -> GF256 +pub fn gf256_div(a: GF256, b: GF256) GF256 { + const fb = gf256_decode_f64(b); + if (fb == 0.0) { + const fa = gf256_decode_f64(a); + return if (fa < 0.0) gf256_inf_negative() else gf256_inf_positive(); + } + return gf256_encode_f64(gf256_decode_f64(a) / fb); +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// gf256_abs(gf256: GF256) -> GF256 +pub fn gf256_abs(gf256: GF256) GF256 { + var result = gf256; + result.parts[3] &= 0x7FFFFFFFFFFFFFFF; + return result; +} + +// gf256_neg(gf256: GF256) -> GF256 +pub fn gf256_neg(gf256: GF256) GF256 { + var result = gf256; + result.parts[3] ^= 0x8000000000000000; + return result; +} + +// gf256_is_equal(a: GF256, b: GF256) -> bool +pub fn gf256_is_equal(a: GF256, b: GF256) bool { + if (gf256_is_nan(a) or gf256_is_nan(b)) { + return false; + } + if (gf256_is_zero(a) and gf256_is_zero(b)) { + return true; + } + return a.parts[0] == b.parts[0] and a.parts[1] == b.parts[1] and + a.parts[2] == b.parts[2] and a.parts[3] == b.parts[3]; +} + +// gf256_is_greater(a: GF256, b: GF256) -> bool +pub fn gf256_is_greater(a: GF256, b: GF256) bool { + if (gf256_is_nan(a) or gf256_is_nan(b)) { + return false; + } + const sign_a = gf256_extract_sign(a); + const sign_b = gf256_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + // For negative numbers, reverse comparison + if (sign_a < 0) { + const neg_a = gf256_neg(a); + const neg_b = gf256_neg(b); + return neg_a.parts[3] > neg_b.parts[3] or + (neg_a.parts[3] == neg_b.parts[3] and neg_a.parts[2] > neg_b.parts[2]) or + (neg_a.parts[2] == neg_b.parts[2] and neg_a.parts[1] > neg_b.parts[1]) or + (neg_a.parts[1] == neg_b.parts[1] and neg_a.parts[0] > neg_b.parts[0]); + } + // Positive comparison + return a.parts[3] > b.parts[3] or + (a.parts[3] == b.parts[3] and a.parts[2] > b.parts[2]) or + (a.parts[2] == b.parts[2] and a.parts[1] > b.parts[1]) or + (a.parts[1] == b.parts[1] and a.parts[0] > b.parts[0]); +} + +// gf256_max(a: GF256, b: GF256) -> GF256 +pub fn gf256_max(a: GF256, b: GF256) GF256 { + return if (gf256_is_greater(a, b)) a else b; +} + +// gf256_min(a: GF256, b: GF256) -> GF256 +pub fn gf256_min(a: GF256, b: GF256) GF256 { + return if (gf256_is_greater(a, b)) b else a; +} + +// gf256_clamp(value: GF256, min: GF256, max: GF256) -> GF256 +pub fn gf256_clamp(value: GF256, min: GF256, max: GF256) GF256 { + return gf256_min(gf256_max(value, min), max); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf256_zero_is_zero" { + try std.testing.expect(gf256_is_zero(gf256_zero()) == true); +} + +test "gf256_negative_zero_is_zero" { + try std.testing.expect(gf256_is_zero(gf256_negative_zero()) == true); +} + +test "gf256_nonzero_is_not_zero" { + given value = gf256_encode_f64(1.0) + try std.testing.expect(gf256_is_zero(value) == false); +} + +test "gf256_inf_is_special" { + try std.testing.expect(gf256_is_special(gf256_inf_positive()) == true); +} + +test "gf256_nan_is_special" { + try std.testing.expect(gf256_is_special(gf256_nan()) == true); +} + +test "gf256_inf_positive" { + try std.testing.expect(gf256_is_inf(gf256_inf_positive()) == true); +} + +test "gf256_inf_negative" { + try std.testing.expect(gf256_is_inf(gf256_inf_negative()) == true); +} + +test "gf256_nan" { + try std.testing.expect(gf256_is_nan(gf256_nan()) == true); +} + +test "gf256_inf_not_nan" { + try std.testing.expect(gf256_is_nan(gf256_inf_positive()) == false); +} + +test "gf256_extract_sign_positive" { + given value = gf256_encode_f64(1.0) + try std.testing.expect(sign = gf256_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "gf256_extract_sign_negative" { + given value = gf256_encode_f64(-1.0) + try std.testing.expect(sign = gf256_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "gf256_encode_f64_zero" { + given gf = gf256_encode_f64(0.0) + try std.testing.expect(gf256_is_equal(gf, gf256_zero()) == true); +} + +test "gf256_encode_f64_one" { + given gf = gf256_encode_f64(1.0) + try std.testing.expect(decoded = gf256_decode_f64(gf)); + try std.testing.expect(abs(decoded - 1.0) < 0.0001); +} + +test "gf256_encode_f64_negative_one" { + given gf = gf256_encode_f64(-1.0) + try std.testing.expect(decoded = gf256_decode_f64(gf)); + try std.testing.expect(abs(decoded + 1.0) < 0.0001); +} + +test "gf256_encode_f64_roundtrip" { + given original = 42.5 + try std.testing.expect(gf = gf256_encode_f64(original)); + try std.testing.expect(decoded = gf256_decode_f64(gf)); + try std.testing.expect(abs(decoded - original) < 0.001); +} + +test "gf256_add_simple" { + given a = gf256_encode_f64(1.0) + try std.testing.expect(b = gf256_encode_f64(2.0)); + try std.testing.expect(result = gf256_add(a, b)); + try std.testing.expect(decoded = gf256_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.01); +} + +test "gf256_mul_simple" { + given a = gf256_encode_f64(3.0) + try std.testing.expect(b = gf256_encode_f64(4.0)); + try std.testing.expect(result = gf256_mul(a, b)); + try std.testing.expect(decoded = gf256_decode_f64(result)); + try std.testing.expect(abs(decoded - 12.0) < 0.01); +} + +test "gf256_abs_positive" { + given value = gf256_encode_f64(5.0) + try std.testing.expect(abs_val = gf256_abs(value)); + try std.testing.expect(gf256_is_equal(abs_val, value) == true); +} + +test "gf256_abs_negative" { + given value = gf256_encode_f64(-5.0) + try std.testing.expect(abs_val = gf256_abs(value)); + try std.testing.expect(gf256_extract_sign(abs_val) == 0); +} + +test "gf256_neg_positive" { + given value = gf256_encode_f64(5.0) + try std.testing.expect(neg_val = gf256_neg(value)); + try std.testing.expect(gf256_extract_sign(neg_val) < 0); +} + +test "gf256_neg_negative" { + given value = gf256_encode_f64(-5.0) + try std.testing.expect(neg_val = gf256_neg(value)); + try std.testing.expect(gf256_extract_sign(neg_val) == 0); +} + +test "gf256_is_equal_same" { + given value = gf256_encode_f64(1.0) + try std.testing.expect(gf256_is_equal(value, value) == true); +} + +test "gf256_is_equal_nan" { + try std.testing.expect(gf256_is_equal(gf256_nan(), gf256_nan()) == false); +} + +test "gf256_is_equal_zero" { + try std.testing.expect(gf256_is_equal(gf256_zero(), gf256_negative_zero()) == true); +} + +test "gf256_is_greater_positive" { + given a = gf256_encode_f64(5.0) + try std.testing.expect(b = gf256_encode_f64(3.0)); + try std.testing.expect(gf256_is_greater(a, b) == true); +} + +test "gf256_is_greater_negative" { + given a = gf256_encode_f64(-3.0) + try std.testing.expect(b = gf256_encode_f64(-5.0)); + try std.testing.expect(gf256_is_greater(a, b) == true); +} + +test "gf256_max_returns_larger" { + given a = gf256_encode_f64(3.0) + try std.testing.expect(b = gf256_encode_f64(5.0)); + try std.testing.expect(result = gf256_max(a, b)); + try std.testing.expect(decoded = gf256_decode_f64(result)); + try std.testing.expect(decoded >= 4.0); +} + +test "gf256_min_returns_smaller" { + given a = gf256_encode_f64(3.0) + try std.testing.expect(b = gf256_encode_f64(5.0)); + try std.testing.expect(result = gf256_min(a, b)); + try std.testing.expect(decoded = gf256_decode_f64(result)); + try std.testing.expect(decoded <= 4.0); +} + +test "gf256_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 2147483648); + try std.testing.expect(mant = gf256_zero()); + try std.testing.expect(mant.parts[0] = 0x8000000000000000); + try std.testing.expect(gf = gf256_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = gf256_extract_sign(gf)); + try std.testing.expect(extracted_exp = gf256_extract_exponent(gf)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf256_bits_total + assert SIGN_BITS + EXP_BITS + MANT_BITS == 256 + +invariant gf256_bias_power_of_two_minus_one + assert BIAS + 1 == 2147483648 // 2^31 + +invariant gf256_special_exp_all_ones + assert SPECIAL_EXP == EXP_MAX + +invariant gf256_zero_no_sign_mant + given zero = gf256_zero() + assert zero.parts[0] == 0 and zero.parts[1] == 0 and zero.parts[2] == 0 and (zero.parts[3] & 0x7FFFFFFFFFFFFFFF) == 0 + +invariant gf256_neg_zero_has_sign + given neg_zero = gf256_negative_zero() + assert (neg_zero.parts[3] & 0x8000000000000000) != 0 + +invariant gf256_abs_removes_sign + assert (gf256_abs(gf256_negative_zero()).parts[3] & 0x8000000000000000) == 0 + +invariant gf256_neg_toggles_sign + given pos = gf256_encode_f64(5.0) + try std.testing.expect(neg = gf256_neg(pos)); + assert (pos.parts[3] & 0x8000000000000000) != (neg.parts[3] & 0x8000000000000000) + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf256_extract_sign_latency + measure: nanoseconds to gf256_extract_sign(gf256_encode_f64(1.0)) + target: < 20ns + +bench gf256_encode_f64_latency + measure: nanoseconds to gf256_encode_f64(123.456) + target: < 200ns + +bench gf256_decode_f64_latency + measure: nanoseconds to gf256_decode_f64(gf256_encode_f64(123.456)) + target: < 200ns + +bench gf256_add_latency + measure: nanoseconds to gf256_add(gf256_encode_f64(1.0), gf256_encode_f64(2.0)) + target: < 400ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/gf32.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/gf32.t27 new file mode 100644 index 0000000000..a5e2e14a92 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/gf32.t27 @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf32.t27 +// GoldenFloat32 0 32-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 8 (P1) + +module GF32 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF32 bit layout: [S|EEEE EEEE EEEE|MMM MMMM MMMM MMMM MMMM MMM] + // S: 1 bit (sign) + // E: 12 bits (exponent) + // M: 19 bits (mantissa) + + const BITS : u8 = 32; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 12; + const MANT_BITS : u8 = 19; + + // Bias for exponent (2^(12-1) - 1 = 2047) + const EXP_BIAS : u16 = 2047; + + // 141-ratio: exp/mant = 12/19 142 0.632 (phi_distance = 0.014) + // This is the second-best 143-approximation after GF12 + const PHI_DISTANCE : f64 = 0.01354495894042812; + + // 144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 + // 2. GoldenFloat32 Type + // 209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 + + struct GF32 { + raw : u32, // 32-bit raw value + } + + // 282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 + // 3. Encoding/Decoding + // 347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 + + // Encode f32 to GF32 + fn encode(value: f32) -> GF32 { + if (value == 0.0) { + return GF32{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1u32 } else { 0u32 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i16; + const exp_biased = (exp_unbiased + EXP_BIAS as i16) as u16; + + // Clamp exponent + const exp_clamped = clamp_u16(exp_biased, 0, (1u16 << EXP_BITS) - 1); + + // Extract mantissa (19 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF32{ + raw = (sign << 31) | + ((exp_clamped as u32) << MANT_BITS) | + (mant as u32) + }; + } + + // Decode GF32 to f32 + fn decode(gf: GF32) -> f32 { + const sign = (gf.raw >> 31) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0xFFF) as u16; + const mant = (gf.raw & 0x7FFFF) as u32; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -(EXP_BIAS as i16) + 1 + } else { + (exp_biased as i16) - EXP_BIAS as i16 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 524288.0 + } else { + 1.0 + (mant as f32) / 524288.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 + // 4. Format Properties + // 485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 + + fn max_value() -> f32 { + const mant_max = 1.0 + 524287.0 / 524288.0; + const exp_max = (1i16 << EXP_BITS) - 1 - EXP_BIAS as i16; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 524288.0; + const exp_min = -(EXP_BIAS as i16) + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 524288.0; // 0.000001907 + } + + // 558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 + // 5. Validation + // 623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF32"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760 + // 6. Use Cases + // 761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833 + + // GF32 is optimal for: + // - Near-IEEE 754 precision with 834-optimized layout + // - 12-bit exponent (vs IEEE's 8-bit) for wider dynamic range + // - 19-bit mantissa (vs IEEE's 23-bit) - still good precision + // - Same memory footprint as FP32, better 835-ratio + + // Comparison with IEEE FP32: + // - IEEE: 1 sign, 8 exp, 23 mant 836 exp/mant = 0.348 (phi_distance = 0.270) + // - GF32: 1 sign, 12 exp, 19 mant 837 exp/mant = 0.632 (phi_distance = 0.014) + + // Memory: 32 bits = 4 bytes (same as FP32) + const MEMORY_RATIO_VS_FP32 : f32 = 1.0; + + // 838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902 + // 7. Helper Functions + // 903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975 + + fn floor_log2(x: f32) -> i16 { + if (x <= 0.0) { return -32768; } + let exp : i16 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i16, mant_bits: u8) -> u32 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1u32 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u32; + } + + fn clamp_u16(x: u16, min: u16, max: u16) -> u16 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF32 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 9769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078 + // TDD-Inside-Spec: Tests and Invariants for GF32 + // 1079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181 + + test gf32_decode_zero + given gf = GF32{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf32_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf32_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf32_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf32_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf32_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf32_phi_distance_near_optimal + given phi_dist = PHI_DISTANCE + then phi_dist < 0.015 + + test gf32_memory_ratio_equals_one + given ratio = MEMORY_RATIO_VS_FP32 + then ratio == 1.0 + + test gf32_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf32_bits_constant + assert BITS == 32 + + invariant gf32_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf32_exp_bits_is_twelve + assert EXP_BITS == 12 + + invariant gf32_mant_bits_is_nineteen + assert MANT_BITS == 19 + + invariant gf32_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf32_phi_distance_near_optimal + assert PHI_DISTANCE < 0.015 + + invariant gf32_exp_bias_positive + assert EXP_BIAS > 0 + + invariant gf32_exp_wider_than_ieee + assert EXP_BITS > 8 // IEEE FP32 has 8-bit exponent + + invariant gf32_mant_narrower_than_ieee + assert MANT_BITS < 23 // IEEE FP32 has 23-bit mantissa + + test gf32_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf32_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf32_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf32_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf32_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf32_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf32_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf32_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf32_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf32_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf32_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf32_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf32_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf32_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf32_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf32_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf32_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf32_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf32_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf32_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf32_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf32_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf32_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf32_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf32_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf32_encode_latency + measure: nanoseconds to encode(1.0) + target: < 300ns + + bench gf32_decode_latency + measure: nanoseconds to decode(GF32{raw = 1065353216}) + target: < 250ns +} diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/gf4.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/gf4.t27 new file mode 100644 index 0000000000..499b74e34f --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/gf4.t27 @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf4.t27 +// GoldenFloat4 0 4-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 2 (P1) + +module GF4 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF4 bit layout: [S|E|MM] + // S: 1 bit (sign) + // E: 1 bit (exponent) + // M: 2 bits (mantissa) + + const BITS : u8 = 4; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 1; + const MANT_BITS : u8 = 2; + + // Bias for exponent (0-biased for GF4) + const EXP_BIAS : u8 = 0; + + // 141-ratio: exp/mant = 1/2 = 0.5 (phi_distance = 0.118) + const PHI_DISTANCE : f64 = 0.1180339887498949; + + // 142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 + // 2. GoldenFloat4 Type + // 207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 + + struct GF4 { + raw : u4, // 4-bit raw value + } + + // 280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 + // 3. Encoding/Decoding + // 345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 + + // Encode f32 to GF4 + fn encode(value: f32) -> GF4 { + // Special cases + if (value == 0.0) { + return GF4{ raw = 0b0000 }; + } + if (value < 0.0) { + const pos = encode(-value).raw; + return GF4{ raw = pos | 0b1000 }; // Set sign bit + } + + // For GF4, quantize to available values + // Available positive values (mant * exp_scale): + // mant=0.00, exp=1.0 418 0.00 + // mant=0.25, exp=1.0 419 0.25 + // mant=0.50, exp=1.0 420 0.50 + // mant=0.75, exp=1.0 421 0.75 + // mant=0.00, exp=2.0 422 0.00 + // mant=0.25, exp=2.0 423 0.50 + // mant=0.50, exp=2.0 424 1.00 + // mant=0.75, exp=2.0 425 1.50 + + // Unique positive non-zero values: 0.25, 0.5, 0.75, 1.0, 1.5 + + if (value <= 0.375) { + // 0.25 + return GF4{ raw = 0b0001 }; + } else if (value <= 0.625) { + // 0.5 + return GF4{ raw = 0b0010 }; + } else if (value <= 0.875) { + // 0.75 + return GF4{ raw = 0b0011 }; + } else if (value <= 1.25) { + // 1.0 + return GF4{ raw = 0b0101 }; + } else { + // 1.5 (max) + return GF4{ raw = 0b0111 }; + } + } + + // Decode GF4 to f32 + fn decode(gf: GF4) -> f32 { + const sign_bit = (gf.raw & 0b1000) != 0; + const exp_bit = (gf.raw & 0b0100) != 0; + const mant_bits = gf.raw & 0b0011; + + // Zero + if (gf.raw == 0) { + return 0.0; + } + + // Decode mantissa (2 bits 426 values 0, 0.25, 0.5, 0.75) + const mant = (mant_bits as f32) / 4.0; + + // Decode exponent (1 bit 427 1.0 or 2.0) + const exp_scale = if (exp_bit) { 2.0 } else { 1.0 }; + + const value = mant * exp_scale; + + if (sign_bit) { + return -value; + } + return value; + } + + // 428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 + // 4. Format Properties + // 493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565 + + fn max_value() -> f32 { + // Max: mant=0.75, exp=2.0 566 1.5 + return 1.5; + } + + fn min_positive() -> f32 { + // Min positive: mant=0.25, exp=1.0 567 0.25 + return 0.25; + } + + fn epsilon() -> f32 { + // Smallest representable difference at 1.0 + return 0.25; + } + + // 568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632 + // 5. Validation + // 633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705 + + fn validate_format() -> bool { + // Check that we match the goldenfloat_family definition + const fmt = goldenfloat_family::get_format_by_name("GF4"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770 + // 6. Use Cases + // 771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843 + + // GF4 is optimal for: + // - Extreme compression (87.5% smaller than FP32) + // - Binary/ternary classification + // - Attention masks + // - Activation sparsity indicators + + // Memory: 4 bits = 0.5 bytes (8x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 4.0 / 32.0; // 0.125 + + // 844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946 + // TDD-Inside-Spec: Tests and Invariants for GF4 + // 94794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049 + + test gf4_decode_zero + given gf = GF4{ raw = 0b0000 } + when value = decode(gf) + then value == 0.0 + + test gf4_decode_positive_max + given gf = GF4{ raw = 0b0111 } + when value = decode(gf) + then value == 1.5 + + test gf4_decode_negative + given gf = GF4{ raw = 0b1001 } + when value = decode(gf) + then value < 0.0 + + test gf4_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf4_encode_0_25 + given original = 0.25 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.25) < 0.01 + + test gf4_encode_0_5 + given original = 0.5 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.5) < 0.01 + + test gf4_encode_0_75 + given original = 0.75 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.75) < 0.01 + + test gf4_encode_1_0 + given original = 1.0 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 1.0) < 0.01 + + test gf4_encode_1_5 + given original = 1.5 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 1.5) < 0.01 + + test gf4_encode_negative_values + given original = -0.5 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded < 0.0 and abs(decoded - (-0.5)) < 0.01 + + test gf4_encode_clamps_to_max + given original = 10.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded <= 1.5 + + test gf4_encode_quantization_small + given original = 0.3 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.25) < 0.01 + + test gf4_max_value_is_1_5 + given max_val = max_value() + then max_val == 1.5 + + test gf4_min_positive_is_0_25 + given min_pos = min_positive() + then min_pos == 0.25 + + test gf4_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf4_exp_mant_ratio_matches_phi_split + given ratio = (EXP_BITS as f64) / (MANT_BITS as f64) + and expected = 0.5 + then abs(ratio - expected) < 0.01 + + test gf4_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then ratio == 0.125 + + test gf4_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf4_bits_constant + assert BITS == 4 + + invariant gf4_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf4_exp_bits_is_one + assert EXP_BITS == 1 + + invariant gf4_mant_bits_is_two + assert MANT_BITS == 2 + + invariant gf4_max_value_positive + assert max_value() > 0.0 + + invariant gf4_min_positive_greater_than_zero + assert min_positive() > 0.0 + + invariant gf4_epsilon_positive + assert epsilon() > 0.0 + + invariant gf4_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf4_phi_distance_within_tolerance + assert PHI_DISTANCE < 0.12 + + invariant gf4_encode_decode_roundtrip + given encoded = encode(x) for x in {0.25, 0.5, 0.75, 1.0, 1.5} + when decoded = decode(encoded) + then abs(decoded - x) < 0.01 + + invariant gf4_encode_zero_returns_zero + assert encode(0.0).raw == 0b0000 + + invariant gf4_encode_positive_no_sign_bit + given result = encode(1.0) + when has_sign = (result.raw & 0b1000) != 0 + then has_sign == false + + invariant gf4_encode_negative_has_sign_bit + given result = encode(-1.0) + when has_sign = (result.raw & 0b1000) != 0 + then has_sign == true + + bench gf4_encode_latency + measure: nanoseconds to encode(1.0) + target: < 100ns + + bench gf4_decode_latency + measure: nanoseconds to decode(GF4{raw = 0b0101}) + target: < 50ns +} diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/gf64.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/gf64.t27 new file mode 100644 index 0000000000..cb86276ee1 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/gf64.t27 @@ -0,0 +1,764 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf64.t27 — GoldenFloat64 Encode/Decode +; GF64: 64-bit floating point with 1 sign + 18 exponent + 45 mantissa +; Bit layout: [S(1) E(18) M(45)] = [63:63][62:45][44:0] +; φ² + 1/φ² = 3 | TRINITY + +module triformat-gf64; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const SIGN_SHIFT : u8 = 63; +pub const EXP_SHIFT : u8 = 45; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u64 = 0x8000000000000000; // 1 << 63 +pub const EXP_MASK : u64 = 0x7FFF800000000000; // 0b111111111111111111 << 45 +pub const MANT_MASK : u64 = 0x00007FFFFFFFFFFF; // 0b111111111111111111111111111111111111111111111 + +pub const EXP_MAX : u16 = 0x3FFFF; // 262143 (all ones in 18 bits) +pub const EXP_MIN : u16 = 0x0000; + +pub const BIAS : i16 = 131071; // Exponent bias for GF64: 2^(18-1) - 1 +pub const SPECIAL_EXP : u16 = 0x3FFFF; // All ones = special (Inf/NaN) + +pub const MANT_DIVISOR : u64 = 35184372088832; // 2^45 +pub const MANT_DIVISOR_SHIFT : u8 = 45; + +pub const PHI_BIAS : u32 = 245760; // Phi-optimized rounding bias + +// GF64 special values +pub const GF64_ZERO_POS : u64 = 0x0000000000000000; +pub const GF64_ZERO_NEG : u64 = 0x8000000000000000; +pub const GF64_INF_POS : u64 = 0x7FFF800000000000; +pub const GF64_INF_NEG : u64 = 0xFFFF800000000000; +pub const GF64_NAN : u64 = 0xFFFF800000000001; // Sign + all exp + mantissa != 0 + +// ============================================================================ +// Types +// ============================================================================ + +pub const GF64 = u64; + +// ============================================================================ +// Functions +// ============================================================================ + +// gf64_extract_sign(gf64: GF64) -> i8 +// Extract sign bit (bit 63) +// Returns: 0 for positive, -1 for negative +pub fn gf64_extract_sign(gf64: GF64) i8 { + const bit = (gf64 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// gf64_extract_exponent(gf64: GF64) -> i16 +// Extract exponent bits (bits 62-45) +// Returns: 0-262143 +pub fn gf64_extract_exponent(gf64: GF64) i16 { + return @as(i16, @intCast((gf64 >> EXP_SHIFT) & (EXP_MAX))); +} + +// gf64_extract_mantissa(gf64: GF64) -> u64 +// Extract mantissa bits (bits 44-0) +// Returns: 0-2^45-1 +pub fn gf64_extract_mantissa(gf64: GF64) u64 { + return gf64 & MANT_MASK; +} + +// gf64_from_components(sign: i8, exp: i16, mant: u64) -> GF64 +// Assemble GF64 from sign, exponent, mantissa +pub fn gf64_from_components(sign: i8, exp: i16, mant: u64) GF64 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(GF64, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(GF64, @intCast(exp & EXP_MAX)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// gf64_is_zero(gf64: GF64) -> bool +// Check if GF64 is zero (positive or negative) +pub fn gf64_is_zero(gf64: GF64) bool { + return gf64 == GF64_ZERO_POS or gf64 == GF64_ZERO_NEG; +} + +// gf64_is_special(gf64: GF64) -> bool +// Check if GF64 is Inf or NaN (exp == 262143) +pub fn gf64_is_special(gf64: GF64) bool { + return gf64_extract_exponent(gf64) == @as(i16, @intCast(EXP_MAX)); +} + +// gf64_is_inf(gf64: GF64) -> bool +// Check if GF64 is infinity (exp == max, mant == 0) +pub fn gf64_is_inf(gf64: GF64) bool { + const exp = gf64_extract_exponent(gf64); + const mant = gf64_extract_mantissa(gf64); + return exp == @as(i16, @intCast(EXP_MAX)) and mant == 0; +} + +// gf64_is_nan(gf64: GF64) -> bool +// Check if GF64 is NaN (exp == max, mant != 0) +pub fn gf64_is_nan(gf64: GF64) bool { + const exp = gf64_extract_exponent(gf64); + const mant = gf64_extract_mantissa(gf64); + return exp == @as(i16, @intCast(EXP_MAX)) and mant != 0; +} + +// gf64_encode_f64(f64: f64) -> GF64 +// Encode IEEE 754 double precision to GF64 +// Round-to-nearest, ties to even +pub fn gf64_encode_f64(value: f64) GF64 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) GF64_ZERO_NEG else GF64_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return GF64_NAN; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) GF64_INF_NEG else GF64_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f64 components + const f64_bits: u64 = @bitCast(abs_value); + var f64_exp: i16 = @intCast((f64_bits >> 52) & 0x7FF) - 1023; + var f64_mant: u64 = f64_bits & 0x000FFFFFFFFFFFFF; + + // Convert exp from f64 bias (1023) to GF64 bias (131071) + var gf64_exp = f64_exp + BIAS; + + // Clamp exponent + if (gf64_exp < 0) { + // Underflow - could return subnormal, but flushing to zero for simplicity + return if (sign < 0) GF64_ZERO_NEG else GF64_ZERO_POS; + } else if (gf64_exp > @as(i16, @intCast(EXP_MAX - 1))) { + return if (sign < 0) GF64_INF_NEG else GF64_INF_POS; + } + + // Extract mantissa and scale to 45 bits + // f64 mantissa is 52 bits, GF64 needs 45 bits + // Shift right by 7 bits (52 - 45 = 7) + var mant = f64_mant >> 7; + + // Round-to-nearest with ties to even + const discarded = f64_mant & 0x7F; + if ((discarded & 0x40) != 0) { + // Round up + if ((discarded & 0x3F) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (gf64_exp < @as(i16, @intCast(EXP_MAX - 1))) { + gf64_exp += 1; + } + } + } + } + + return gf64_from_components(sign, gf64_exp, mant); +} + +// gf64_decode_f64(gf64: GF64) -> f64 +// Decode GF64 to IEEE 754 double precision +pub fn gf64_decode_f64(gf64: GF64) f64 { + // Handle zero + if (gf64_is_zero(gf64)) { + return if (gf64_extract_sign(gf64) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (gf64_is_nan(gf64)) { + return std.math.nan(f64); + } + + // Handle Infinity + if (gf64_is_inf(gf64)) { + return if (gf64_extract_sign(gf64) < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + // Extract components + const sign = gf64_extract_sign(gf64); + const exp = gf64_extract_exponent(gf64); + const mant = gf64_extract_mantissa(gf64); + + // Decode value: mantissa * 2^(exp - bias) + const bias_adjusted = @as(i64, exp) - @as(i64, BIAS); + const mant_f64 = @as(f64, @floatFromInt(mant)) / @as(f64, @floatFromInt(MANT_DIVISOR)); + const value = mant_f64 * std.math.pow(f64, 2.0, @as(f64, @floatFromInt(bias_adjusted))); + + return if (sign < 0) -value else value; +} + +// gf64_add(a: GF64, b: GF64) -> GF64 +// Add two GF64 values +pub fn gf64_add(a: GF64, b: GF64) GF64 { + const fa = gf64_decode_f64(a); + const fb = gf64_decode_f64(b); + return gf64_encode_f64(fa + fb); +} + +// gf64_sub(a: GF64, b: GF64) -> GF64 +// Subtract two GF64 values +pub fn gf64_sub(a: GF64, b: GF64) GF64 { + const fa = gf64_decode_f64(a); + const fb = gf64_decode_f64(b); + return gf64_encode_f64(fa - fb); +} + +// gf64_mul(a: GF64, b: GF64) -> GF64 +// Multiply two GF64 values +pub fn gf64_mul(a: GF64, b: GF64) GF64 { + const fa = gf64_decode_f64(a); + const fb = gf64_decode_f64(b); + return gf64_encode_f64(fa * fb); +} + +// gf64_div(a: GF64, b: GF64) -> GF64 +// Divide two GF64 values +pub fn gf64_div(a: GF64, b: GF64) GF64 { + const fb = gf64_decode_f64(b); + if (fb == 0.0) { + const fa = gf64_decode_f64(a); + return if (fa < 0.0) GF64_INF_NEG else GF64_INF_POS; + } + const fa = gf64_decode_f64(a); + return gf64_encode_f64(fa / fb); +} + +// gf64_abs(gf64: GF64) -> GF64 +// Absolute value of GF64 +pub fn gf64_abs(gf64: GF64) GF64 { + return gf64 & ~SIGN_MASK; +} + +// gf64_neg(gf64: GF64) -> GF64 +// Negate GF64 +pub fn gf64_neg(gf64: GF64) GF64 { + return gf64 ^ SIGN_MASK; +} + +// gf64_is_equal(a: GF64, b: GF64) -> bool +// Check if two GF64 values are equal +pub fn gf64_is_equal(a: GF64, b: GF64) bool { + // Handle NaN: NaN != NaN + if (gf64_is_nan(a) or gf64_is_nan(b)) { + return false; + } + // Handle zero: +0 == -0 + if (gf64_is_zero(a) and gf64_is_zero(b)) { + return true; + } + return a == b; +} + +// gf64_is_greater(a: GF64, b: GF64) -> bool +// Check if a > b +pub fn gf64_is_greater(a: GF64, b: GF64) bool { + if (gf64_is_nan(a) or gf64_is_nan(b)) { + return false; + } + // Extract signs for comparison + const sign_a = gf64_extract_sign(a); + const sign_b = gf64_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; // negative < positive + } + // Same sign: compare as unsigned (positive case) or reversed (negative case) + if (sign_a < 0) { + return (gf64_neg(a) > gf64_neg(b)); + } + return a > b; +} + +// gf64_max(a: GF64, b: GF64) -> GF64 +// Return the larger of two GF64 values +pub fn gf64_max(a: GF64, b: GF64) GF64 { + return if (gf64_is_greater(a, b)) a else b; +} + +// gf64_min(a: GF64, b: GF64) -> GF64 +// Return the smaller of two GF64 values +pub fn gf64_min(a: GF64, b: GF64) GF64 { + return if (gf64_is_greater(a, b)) b else a; +} + +// gf64_clamp(value: GF64, min: GF64, max: GF64) -> GF64 +// Clamp value between min and max +pub fn gf64_clamp(value: GF64, min: GF64, max: GF64) GF64 { + return gf64_min(gf64_max(value, min), max); +} + +// gf64_lerp(a: GF64, b: GF64, t: GF64) -> GF64 +// Linear interpolation between a and b +pub fn gf64_lerp(a: GF64, b: GF64, t: GF64) GF64 { + const ft = gf64_decode_f64(t); + const result = gf64_decode_f64(a) * (1.0 - ft) + gf64_decode_f64(b) * ft; + return gf64_encode_f64(result); +} + +// gf64_from_u32(value: u32) -> GF64 +// Convert unsigned 32-bit integer to GF64 +pub fn gf64_from_u32(value: u32) GF64 { + return gf64_encode_f64(@as(f64, @floatFromInt(value))); +} + +// gf64_to_u32(gf64: GF64) -> ?u32 +// Convert GF64 to unsigned 32-bit integer +// Returns null if value is NaN, Infinity, or out of range +pub fn gf64_to_u32(gf64: GF64) ?u32 { + if (gf64_is_nan(gf64) or gf64_is_inf(gf64)) { + return null; + } + const value = gf64_decode_f64(gf64); + if (value < 0.0 or value > @as(f64, @floatFromInt(std.math.maxInt(u32)))) { + return null; + } + return @as(u32, @intFromFloat(value)); +} + +// gf64_from_i32(value: i32) -> GF64 +// Convert signed 32-bit integer to GF64 +pub fn gf64_from_i32(value: i32) GF64 { + return gf64_encode_f64(@as(f64, @floatFromInt(value))); +} + +// gf64_to_i32(gf64: GF64) -> ?i32 +// Convert GF64 to signed 32-bit integer +// Returns null if value is NaN, Infinity, or out of range +pub fn gf64_to_i32(gf64: GF64) ?i32 { + if (gf64_is_nan(gf64) or gf64_is_inf(gf64)) { + return null; + } + const value = gf64_decode_f64(gf64); + if (value < @as(f64, @floatFromInt(std.math.minInt(i32))) or + value > @as(f64, @floatFromInt(std.math.maxInt(i32)))) { + return null; + } + return @as(i32, @intFromFloat(value)); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf64_extract_sign_positive" { + given value = 0x0000000000001234 + try std.testing.expect(sign = gf64_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "gf64_extract_sign_negative" { + given value = 0x8000000000001234 + try std.testing.expect(sign = gf64_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "gf64_extract_exponent_middle" { + given value = 0x0000800000000000 // exp = 131072 + try std.testing.expect(exp = gf64_extract_exponent(value)); + try std.testing.expect(exp == 131072); +} + +test "gf64_extract_mantissa_max" { + given value = 0x00007FFFFFFFFFFF + try std.testing.expect(mant = gf64_extract_mantissa(value)); + try std.testing.expect(mant == 0x00007FFFFFFFFFFF); +} + +test "gf64_is_zero_positive" { + try std.testing.expect(gf64_is_zero(GF64_ZERO_POS) == true); +} + +test "gf64_is_zero_negative" { + try std.testing.expect(gf64_is_zero(GF64_ZERO_NEG) == true); +} + +test "gf64_is_zero_nonzero" { + try std.testing.expect(gf64_is_zero(0x0000000000000001) == false); +} + +test "gf64_is_special_inf" { + try std.testing.expect(gf64_is_special(GF64_INF_POS) == true); +} + +test "gf64_is_special_nan" { + try std.testing.expect(gf64_is_special(GF64_NAN) == true); +} + +test "gf64_is_special_normal" { + try std.testing.expect(gf64_is_special(0x0000000000001234) == false); +} + +test "gf64_is_inf_positive" { + try std.testing.expect(gf64_is_inf(GF64_INF_POS) == true); +} + +test "gf64_is_inf_negative" { + try std.testing.expect(gf64_is_inf(GF64_INF_NEG) == true); +} + +test "gf64_is_inf_not_nan" { + try std.testing.expect(gf64_is_inf(GF64_NAN) == false); +} + +test "gf64_is_nan" { + try std.testing.expect(gf64_is_nan(GF64_NAN) == true); +} + +test "gf64_is_nan_not_inf" { + try std.testing.expect(gf64_is_nan(GF64_INF_POS) == false); +} + +test "gf64_encode_f64_zero" { + given gf = gf64_encode_f64(0.0) + try std.testing.expect(gf == GF64_ZERO_POS); +} + +test "gf64_encode_f64_negative_zero" { + given gf = gf64_encode_f64(-0.0) + try std.testing.expect(gf == GF64_ZERO_NEG); +} + +test "gf64_encode_f64_one" { + given gf = gf64_encode_f64(1.0) + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - 1.0) < 0.0001); +} + +test "gf64_encode_f64_negative_one" { + given gf = gf64_encode_f64(-1.0) + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded + 1.0) < 0.0001); +} + +test "gf64_encode_f64_two_pow_ten" { + given gf = gf64_encode_f64(1024.0) + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - 1024.0) < 1.0); +} + +test "gf64_encode_f64_pi" { + given gf = gf64_encode_f64(std.math.pi) + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - std.math.pi) < 0.001); +} + +test "gf64_decode_f64_roundtrip_positive" { + given original = 42.5 + try std.testing.expect(gf = gf64_encode_f64(original)); + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - original) < 0.001); +} + +test "gf64_decode_f64_roundtrip_negative" { + given original = -17.75 + try std.testing.expect(gf = gf64_encode_f64(original)); + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - original) < 0.001); +} + +test "gf64_add_simple" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(2.0)); + try std.testing.expect(result = gf64_add(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.01); +} + +test "gf64_sub_simple" { + given a = gf64_encode_f64(5.0) + try std.testing.expect(b = gf64_encode_f64(3.0)); + try std.testing.expect(result = gf64_sub(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.01); +} + +test "gf64_mul_simple" { + given a = gf64_encode_f64(3.0) + try std.testing.expect(b = gf64_encode_f64(4.0)); + try std.testing.expect(result = gf64_mul(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 12.0) < 0.01); +} + +test "gf64_div_simple" { + given a = gf64_encode_f64(12.0) + try std.testing.expect(b = gf64_encode_f64(4.0)); + try std.testing.expect(result = gf64_div(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.01); +} + +test "gf64_abs_positive" { + given value = gf64_encode_f64(5.0) + try std.testing.expect(abs_val = gf64_abs(value)); + try std.testing.expect(abs_val == value); +} + +test "gf64_abs_negative" { + given value = gf64_encode_f64(-5.0) + try std.testing.expect(abs_val = gf64_abs(value)); + try std.testing.expect(decoded = gf64_decode_f64(abs_val)); + try std.testing.expect(decoded > 0.0); +} + +test "gf64_neg" { + given value = gf64_encode_f64(5.0) + try std.testing.expect(neg_val = gf64_neg(value)); + try std.testing.expect(decoded = gf64_decode_f64(neg_val)); + try std.testing.expect(abs(decoded + 5.0) < 0.01); +} + +test "gf64_is_equal_same" { + given value = gf64_encode_f64(1.0) + try std.testing.expect(gf64_is_equal(value, value) == true); +} + +test "gf64_is_equal_different" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(2.0)); + try std.testing.expect(gf64_is_equal(a, b) == false); +} + +test "gf64_is_equal_nan" { + try std.testing.expect(gf64_is_equal(GF64_NAN, GF64_NAN) == false); +} + +test "gf64_is_equal_zero" { + try std.testing.expect(gf64_is_equal(GF64_ZERO_POS, GF64_ZERO_NEG) == true); +} + +test "gf64_is_greater_positive" { + given a = gf64_encode_f64(5.0) + try std.testing.expect(b = gf64_encode_f64(3.0)); + try std.testing.expect(gf64_is_greater(a, b) == true); +} + +test "gf64_is_greater_negative" { + given a = gf64_encode_f64(-3.0) + try std.testing.expect(b = gf64_encode_f64(-5.0)); + try std.testing.expect(gf64_is_greater(a, b) == true); +} + +test "gf64_is_greater_positive_vs_negative" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(-1.0)); + try std.testing.expect(gf64_is_greater(a, b) == true); +} + +test "gf64_max_returns_larger" { + given a = gf64_encode_f64(3.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(result = gf64_max(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(decoded >= 4.0); +} + +test "gf64_min_returns_smaller" { + given a = gf64_encode_f64(3.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(result = gf64_min(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(decoded <= 4.0); +} + +test "gf64_clamp_within_range" { + given value = gf64_encode_f64(3.0) + try std.testing.expect(min = gf64_encode_f64(0.0)); + try std.testing.expect(max = gf64_encode_f64(10.0)); + try std.testing.expect(result = gf64_clamp(value, min, max)); + try std.testing.expect(result == value); +} + +test "gf64_clamp_below_min" { + given value = gf64_encode_f64(-5.0) + try std.testing.expect(min = gf64_encode_f64(0.0)); + try std.testing.expect(max = gf64_encode_f64(10.0)); + try std.testing.expect(result = gf64_clamp(value, min, max)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(decoded >= 0.0); +} + +test "gf64_clamp_above_max" { + given value = gf64_encode_f64(15.0) + try std.testing.expect(min = gf64_encode_f64(0.0)); + try std.testing.expect(max = gf64_encode_f64(10.0)); + try std.testing.expect(result = gf64_clamp(value, min, max)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(decoded <= 10.0); +} + +test "gf64_lerp_zero" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(t = gf64_encode_f64(0.0)); + try std.testing.expect(result = gf64_lerp(a, b, t)); + try std.testing.expect(gf64_is_equal(result, a) == true); +} + +test "gf64_lerp_one" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(t = gf64_encode_f64(1.0)); + try std.testing.expect(result = gf64_lerp(a, b, t)); + try std.testing.expect(gf64_is_equal(result, b) == true); +} + +test "gf64_lerp_half" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(t = gf64_encode_f64(0.5)); + try std.testing.expect(result = gf64_lerp(a, b, t)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.1); +} + +test "gf64_from_u32_zero" { + given value = 0 + try std.testing.expect(gf = gf64_from_u32(value)); + try std.testing.expect(gf == GF64_ZERO_POS); +} + +test "gf64_from_u32_max" { + given value = std.math.maxInt(u32) + try std.testing.expect(gf = gf64_from_u32(value)); + try std.testing.expect(decoded = gf64_to_u32(gf)); + try std.testing.expect(decoded.? == value); +} + +test "gf64_to_u32_success" { + given gf = gf64_encode_f64(123.0) + try std.testing.expect(result = gf64_to_u32(gf)); + try std.testing.expect(result.? == 123); +} + +test "gf64_to_u32_negative" { + given gf = gf64_encode_f64(-1.0) + try std.testing.expect(result = gf64_to_u32(gf)); + try std.testing.expect(result == null); +} + +test "gf64_to_u32_nan" { + given result = gf64_to_u32(GF64_NAN) + try std.testing.expect(result == null); +} + +test "gf64_from_i32_zero" { + given value = 0 + try std.testing.expect(gf = gf64_from_i32(value)); + try std.testing.expect(gf == GF64_ZERO_POS); +} + +test "gf64_from_i32_negative" { + given value = -42 + try std.testing.expect(gf = gf64_from_i32(value)); + try std.testing.expect(decoded = gf64_to_i32(gf)); + try std.testing.expect(decoded.? == value); +} + +test "gf64_to_i32_positive" { + given gf = gf64_encode_f64(123.0) + try std.testing.expect(result = gf64_to_i32(gf)); + try std.testing.expect(result.? == 123); +} + +test "gf64_to_i32_negative" { + given gf = gf64_encode_f64(-123.0) + try std.testing.expect(result = gf64_to_i32(gf)); + try std.testing.expect(result.? == -123); +} + +test "gf64_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 131072); + try std.testing.expect(mant = 0x0000000000000800); + try std.testing.expect(gf = gf64_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = gf64_extract_sign(gf)); + try std.testing.expect(extracted_exp = gf64_extract_exponent(gf)); + try std.testing.expect(extracted_mant = gf64_extract_mantissa(gf)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf64_sign_mask_single_bit + assert (SIGN_MASK & (SIGN_MASK - 1)) == 0 + +invariant gf64_exp_mask_no_sign + assert (EXP_MASK & SIGN_MASK) == 0 + +invariant gf64_mant_mask_no_sign_exp + assert (MANT_MASK & (SIGN_MASK | EXP_MASK)) == 0 + +invariant gf64_masks_cover_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFFFFFFFFFFFFFFFF + +invariant gf64_bias_power_of_two_minus_one + assert BIAS + 1 == 131072 // 2^17 + +invariant gf64_special_exp_all_ones + assert SPECIAL_EXP == EXP_MAX + +invariant gf64_zero_pos_no_sign + assert (GF64_ZERO_POS & SIGN_MASK) == 0 + +invariant gf64_zero_neg_has_sign + assert (GF64_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant gf64_inf_pos_max_exp + assert (GF64_INF_POS & EXP_MASK) == EXP_MASK + +invariant gf64_nan_has_mantissa + assert (GF64_NAN & MANT_MASK) != 0 + +invariant gf64_abs_removes_sign + assert gf64_abs(GF64_ZERO_NEG) == GF64_ZERO_POS + +invariant gf64_neg_toggles_sign + assert (gf64_neg(GF64_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant gf64_from_u32_zero + assert gf64_from_u32(0) == GF64_ZERO_POS + +invariant gf64_from_i32_zero + assert gf64_from_i32(0) == GF64_ZERO_POS + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf64_extract_sign_latency + measure: nanoseconds to gf64_extract_sign(0x123456789ABCDEF0) + target: < 10ns + +bench gf64_extract_exponent_latency + measure: nanoseconds to gf64_extract_exponent(0x123456789ABCDEF0) + target: < 10ns + +bench gf64_encode_f64_latency + measure: nanoseconds to gf64_encode_f64(123.456) + target: < 100ns + +bench gf64_decode_f64_latency + measure: nanoseconds to gf64_decode_f64(0x3F80000000000000) + target: < 100ns + +bench gf64_add_latency + measure: nanoseconds to gf64_add(0x3F80000000000000, 0x4000000000000000) + target: < 200ns + +bench gf64_mul_latency + measure: nanoseconds to gf64_mul(0x3F80000000000000, 0x4000000000000000) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/gf8.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/gf8.t27 new file mode 100644 index 0000000000..d4f9f72ae6 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/gf8.t27 @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf8.t27 +// GoldenFloat8 0 8-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 3 (P1) + +module GF8 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF8 bit layout: [S|EEE|MMMM] + // S: 1 bit (sign) + // E: 3 bits (exponent) + // M: 4 bits (mantissa) + + const BITS : u8 = 8; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 3; + const MANT_BITS : u8 = 4; + + // Bias for exponent (2^(3-1) - 1 = 3) + const EXP_BIAS : u8 = 3; + + // 141-ratio: exp/mant = 3/4 = 0.75 + // 142-distance: math::constants::PHI_DISTANCE + const PHI_DISTANCE : f64 = 0.132; + + // 143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 + // 2. GoldenFloat8 Type + // 208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 + + struct GF8 { + raw : u8, // 8-bit raw value + } + + // 281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 + // 3. Encoding/Decoding + // 346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 + + // Encode f32 to GF8 + fn encode(value: f32) -> GF8 { + if (value == 0.0) { + return GF8{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i8; + const exp_biased = (exp_unbiased + EXP_BIAS as i8) as u8; + + // Clamp exponent + const exp_clamped = clamp(exp_biased, 0, (1 << EXP_BITS) - 1); + + // Extract mantissa (4 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF8{ + raw = (sign << 7) | (exp_clamped << MANT_BITS) | mant + }; + } + + // Test: gf8_phi_distance invariant + test gf8_phi_distance + given phi = 1.618033988749894848 // math::sacred_physics::PHI + when gf8_phi = GF8::encode(phi) + then gf8_phi.raw == 0b01000000 // |S|EEE (phi = math::constants::PHI_DISTANCE) + + // Decode GF8 to f32 + fn decode(gf: GF8) -> f32 { + const sign = (gf.raw >> 7) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x07) as u8; + const mant = (gf.raw & 0x0F) as u8; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent (with special case for subnormals) + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS as i8 + 1 + } else { + (exp_biased as i8) - EXP_BIAS as i8 + }; + + // Mantissa (with implicit 1 for normalized, 0 for subnormal) + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 16.0 + } else { + 1.0 + (mant as f32) / 16.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 + // 4. Format Properties + // 484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 + + fn max_value() -> f32 { + // Max normalized: mant=1.9375, exp=3 557 15.5 + const mant_max = 1.0 + 15.0 / 16.0; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + // Min subnormal: mant=1/16, exp=-2 558 0.0625 + const mant_min = 1.0 / 16.0; + const exp_min = -EXP_BIAS as i8 + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + // Smallest representable difference at 1.0 + return 1.0 / 16.0; // 0.0625 + } + + // 559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623 + // 5. Validation + // 624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF8"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761 + // 6. Use Cases + // 762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834 + + // GF8 is optimal for: + // - High compression (75% smaller than FP32) + // - Weight quantization for lightweight models + // - Activation caching + // - Intermediate feature maps + + // Memory: 8 bits = 1 byte (4x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 8.0 / 32.0; // 0.25 + + // 835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899 + // 7. Helper Functions + // 900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972 + + fn floor_log2(x: f32) -> i8 { + if (x <= 0.0) { return -128; } + let exp : i8 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i8, mant_bits: u8) -> u8 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u8; + } + + fn clamp(x: u8, min: u8, max: u8) -> u8 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF8 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs to maintain accuracy + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 9739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075 + // TDD-Inside-Spec: Tests and Invariants for GF8 + // 1076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178 + + test gf8_decode_zero + given gf = GF8{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf8_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf8_decode_positive_value + given gf = GF8{ raw = 0b01000000 } + when value = decode(gf) + then value > 0.0 + + test gf8_decode_negative_value + given gf = GF8{ raw = 0b10000000 } + when value = decode(gf) + then value < 0.0 + + test gf8_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf8_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf8_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf8_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf8_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then ratio == 0.25 + + test gf8_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf8_bits_constant + assert BITS == 8 + + invariant gf8_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf8_exp_bits_is_three + assert EXP_BITS == 3 + + invariant gf8_mant_bits_is_four + assert MANT_BITS == 4 + + invariant gf8_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf8_phi_distance_within_tolerance + assert PHI_DISTANCE < 0.14 + + invariant gf8_exp_bias_positive + assert EXP_BIAS > 0 + + test gf8_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf8_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf8_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf8_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf8_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf8_pow_phi_squared + given phi = 1.6180339887498948 as f32 + and result = pow(phi, 2.0) + and expected = phi * phi + then abs(result - expected) < 1e-5 + + test gf8_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf8_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf8_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf8_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf8_ln_approx_of_e_squared + given e = 2.718281828459045 as f32 + and result = ln_approx(e * e) + then abs(result - 2.0) < 0.02 + + test gf8_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf8_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf8_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf8_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf8_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf8_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf8_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf8_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf8_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf8_pow_multiply_exponents + given a = 2.0 + and b = 3.0 + assert abs(pow(pow(a, 2.0), b) - pow(a, 2.0 * b)) < 1e-5 + + invariant gf8_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf8_exp_ln_inversion + given x = 1.5 + and y = exp_approx(x) + then abs(ln_approx(y) - x) < 0.01 + + invariant gf8_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf8_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf8_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf8_pow_fractional_exponent + measure: nanoseconds to compute pow(4.0, 0.5) + target: < 1000ns + + bench gf8_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf8_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf8_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf8_encode_latency + measure: nanoseconds to encode(1.0) + target: < 100ns + + bench gf8_decode_latency + measure: nanoseconds to decode(GF8{raw = 64}) + target: < 50ns + + // Bench: GF8 weight quantization (NN weights from N(0, 0.1)) + bench gf8_weight_quantize + measure: nanoseconds to encode(0.1) and decode(GF8{raw = encode(0.1).raw}) + target: < 200ns + + // Invariant: GF8 phi_distance + invariant gf8_phi_distance + assert PHI_DISTANCE == 0.132 within 0.001 + // Rationale: exp/mant = 3/4 = 0.75, phi_distance = |0.75 - 0.618| = 0.132 +} diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/goldenfloat_family.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/goldenfloat_family.t27 new file mode 100644 index 0000000000..78e552c883 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/goldenfloat_family.t27 @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/goldenfloat_family.t27 +// GoldenFloat Family 0 1-structured floating point formats +// NUMERIC-STANDARD-001 2 Agent 1 (P0) + +module GoldenFloatFamily { + // Import sacred constants for 3-structured design + use math::constants; + use math::sacred_physics; + + // 4567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768 + // 1. GoldenFloatFormat 69 Canonical format descriptor + // 707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 + + struct GoldenFloatFormat { + name : string, // "GF4", "GF8", ..., "GF256" + bits : u8, // Total bits: 4, 8, 12, 16, 20, 24, 32, 64, 128, 256 + sign_bits : u8, // Always 1 + exp_bits : u8, // Exponent bits + mant_bits : u8, // Mantissa bits + exp_mant_ratio : f64, // exp / mantissa ratio + phi_distance : f64, // |exp/mant - 1/143| (lower = better) + is_primary : bool, // true only for GF16 + } + + // 144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 + // 2. GOLDEN_FLOAT_FAMILY 209 The canonical format registry + // 210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 + + // 283-ratio target: 1/284 285 0.618 + // exp/mant ratios closer to 0.618 are more "golden" + const PHI_RATIO_TARGET : f64 = sacred_physics::PHI_INV; + + // Format array: ordered by bits (4 → 256) + const GOLDEN_FLOAT_FAMILY : [10]GoldenFloatFormat = [ + // name, bits, S, E, M, ratio, phi_dist, primary + GoldenFloatFormat{ + name = "GF4", + bits = 4, + sign_bits = 1, + exp_bits = 1, + mant_bits = 2, + exp_mant_ratio = 0.5, + phi_distance = abs(0.5 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF8", + bits = 8, + sign_bits = 1, + exp_bits = 3, + mant_bits = 4, + exp_mant_ratio = 0.75, + phi_distance = abs(0.75 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF12", + bits = 12, + sign_bits = 1, + exp_bits = 4, + mant_bits = 7, + exp_mant_ratio = 0.5714285714285714, + phi_distance = abs(0.5714285714285714 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF16", + bits = 16, + sign_bits = 1, + exp_bits = 6, + mant_bits = 9, + exp_mant_ratio = 0.6666666666666667, + phi_distance = abs(0.6666666666666667 - PHI_RATIO_TARGET), + is_primary = true, // PRIMARY FORMAT + }, + GoldenFloatFormat{ + name = "GF20", + bits = 20, + sign_bits = 1, + exp_bits = 7, + mant_bits = 12, + exp_mant_ratio = 0.5833333333333333, + phi_distance = abs(0.5833333333333333 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF24", + bits = 24, + sign_bits = 1, + exp_bits = 9, + mant_bits = 14, + exp_mant_ratio = 0.6428571428571429, + phi_distance = abs(0.6428571428571429 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF32", + bits = 32, + sign_bits = 1, + exp_bits = 12, + mant_bits = 19, + exp_mant_ratio = 0.631578947368421, + phi_distance = abs(0.631578947368421 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF64", + bits = 64, + sign_bits = 1, + exp_bits = 24, + mant_bits = 39, + exp_mant_ratio = 0.6153846153846154, + phi_distance = abs(0.6153846153846154 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF128", + bits = 128, + sign_bits = 1, + exp_bits = 48, + mant_bits = 79, + exp_mant_ratio = 0.6075949367088608, + phi_distance = abs(0.6075949367088608 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF256", + bits = 256, + sign_bits = 1, + exp_bits = 97, + mant_bits = 158, + exp_mant_ratio = 0.6139240506329114, + phi_distance = abs(0.6139240506329114 - PHI_RATIO_TARGET), + is_primary = false, + }, + ]; + + // 287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 + // 3. Query functions + // 352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 + + fn get_format_by_name(name: string) -> Option { + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + if (fmt.name == name) { + return fmt; + } + } + return null; + } + + fn get_format_by_bits(bits: u8) -> Option { + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + if (fmt.bits == bits) { + return fmt; + } + } + return null; + } + + fn get_primary_format() -> GoldenFloatFormat { + return GOLDEN_FLOAT_FAMILY[3]; // GF16 at index 3 + } + + // 425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 + // 4. Verification functions + // 490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562 + + struct VerificationReport { + all_valid : bool, + primary_is_gf16 : bool, + phi_distances_ok : bool, + best_phi_format : string, + best_phi_distance : f64, + avg_phi_distance : f64, + } + + fn verify_golden_family() -> VerificationReport { + var primary_count : u8 = 0; + var best_dist : f64 = 1.0; + var best_name : string = ""; + var total_dist : f64 = 0.0; + var format_count : u8 = 0; + var all_names_unique : bool = true; + var all_bit_sums_valid : bool = true; + var all_phi_distances_non_negative : bool = true; + + // Check for duplicate names + var names_seen : [10]string = ["", "", "", "", "", "", "", "", "", ""]; + + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + format_count = format_count + 1; + + // Count primary formats (should be exactly 1) + if (fmt.is_primary) { + primary_count = primary_count + 1; + } + + // Track best phi distance + if (fmt.phi_distance < best_dist) { + best_dist = fmt.phi_distance; + best_name = fmt.name; + } + + total_dist = total_dist + fmt.phi_distance; + + // Check for duplicate names + for (const names_seen) |name| { + if (name != "" && name == fmt.name) { + all_names_unique = false; + } + } + names_seen[format_count - 1] = fmt.name; + + // Check that exp_bits + mant_bits + 1 = bits (sign bit) + if (fmt.exp_bits + fmt.mant_bits + 1 != fmt.bits) { + all_bit_sums_valid = false; + } + + // Check phi_distance is non-negative + if (fmt.phi_distance < 0.0) { + all_phi_distances_non_negative = false; + } + } + + const avg_dist = total_dist / 10.0; + + // All checks must pass + const all_checks_valid = + format_count == 10 && + all_names_unique && + all_bit_sums_valid && + all_phi_distances_non_negative && + primary_count == 1; + + return VerificationReport{ + all_valid = all_checks_valid, + primary_is_gf16 = (primary_count == 1) && (GOLDEN_FLOAT_FAMILY[3].is_primary), + phi_distances_ok = best_dist < 0.1, // All within 0.1 of 1/563 + best_phi_format = best_name, + best_phi_distance = best_dist, + avg_phi_distance = avg_dist, + }; + } + + // 564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628 + // 5. Utility functions + // 629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701 + + fn max_value(format: GoldenFloatFormat) -> f64 { + // Max value = (2 - 2^(-M)) * 2^(2^E - 1) + const mant_max = 2.0 - pow(2.0, -(format.mant_bits as f64)); + const exp_max = pow(2.0, format.exp_bits as f64) - 1.0; + return mant_max * pow(2.0, exp_max); + } + + fn min_positive(format: GoldenFloatFormat) -> f64 { + // Min positive = 2^(-M) * 2^(1 - bias) + const mant_min = pow(2.0, -(format.mant_bits as f64)); + const bias = pow(2.0, format.exp_bits as f64 - 1.0) - 1.0; + return mant_min * pow(2.0, 1.0 - bias); + } + + fn memory_efficiency(format: GoldenFloatFormat) -> f64 { + // Memory efficiency vs FP32 (1.0 = same, 0.5 = half size) + return format.bits as f64 / 32.0; + } + + // 702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804 + // TDD-Inside-Spec: Tests and Invariants for GoldenFloatFamily + // 805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907 + + test gffamily_get_format_by_name_gf16 + given fmt = get_format_by_name("GF16") + then fmt != null and fmt.?.name == "GF16" and fmt.?.bits == 16 + + test gffamily_get_format_by_bits_8 + given fmt = get_format_by_bits(8) + then fmt != null and fmt.?.name == "GF8" and fmt.?.bits == 8 + + test gffamily_get_primary_format_is_gf16 + given primary = get_primary_format() + then primary.name == "GF16" and primary.is_primary == true + + test gffamily_family_size_10 + given size = GOLDEN_FLOAT_FAMILY.len() + then size == 10 + + test gffamily_phi_ratio_target_is_phi_inverse + given target = PHI_RATIO_TARGET + and phi_inv = sacred_physics::PHI_INV + then abs(target - phi_inv) < 0.000001 + + test gffamily_gf4_has_correct_bit_counts + given fmt = get_format_by_name("GF4").? + then fmt.sign_bits == 1 and fmt.exp_bits == 1 and fmt.mant_bits == 2 + + test gffamily_gf32_has_correct_bit_counts + given fmt = get_format_by_name("GF32").? + then fmt.sign_bits == 1 and fmt.exp_bits == 12 and fmt.mant_bits == 19 + + test gffamily_only_gf16_is_primary + var count = 0 + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + if (fmt.is_primary) { count = count + 1; } + } + then count == 1 + + test gffamily_verify_primary_is_gf16 + given report = verify_golden_family() + then report.primary_is_gf16 == true + + test gffamily_phi_distances_within_tolerance + given report = verify_golden_family() + then report.phi_distances_ok == true + + test gffamily_best_phi_format_is_gf12 + given report = verify_golden_family() + then report.best_phi_format == "GF12" + + test gffamily_memory_efficiency_gf8 + given fmt = get_format_by_name("GF8").? + and eff = memory_efficiency(fmt) + then abs(eff - 0.25) < 0.01 + + test gffamily_memory_efficiency_gf16 + given fmt = get_format_by_name("GF16").? + and eff = memory_efficiency(fmt) + then abs(eff - 0.5) < 0.01 + + test gffamily_max_value_positive + given fmt = get_format_by_name("GF8").? + and max_val = max_value(fmt) + then max_val > 0.0 + + test gffamily_min_positive_greater_than_zero + given fmt = get_format_by_name("GF8").? + and min_pos = min_positive(fmt) + then min_pos > 0.0 + + test gffamily_get_format_by_unknown_name + given fmt = get_format_by_name("GF999") + then fmt == null + + test gffamily_get_format_by_unknown_bits + given fmt = get_format_by_bits(100) + then fmt == null + + test gffamily_verify_all_valid + given report = verify_golden_family() + then report.all_valid == true + + test gffamily_verify_format_count_is_10 + given report = verify_golden_family() + then report.all_valid == true // implies format_count == 10 + + test gffamily_verify_names_unique + given report = verify_golden_family() + then report.all_valid == true // implies names are unique + + test gffamily_verify_bit_sums_valid + given report = verify_golden_family() + then report.all_valid == true // implies bit sums are valid + + test gffamily_verify_phi_distances_non_negative + given report = verify_golden_family() + then report.all_valid == true // implies phi_distances are non-negative + + test gffamily_verify_exactly_one_primary + given report = verify_golden_family() + then report.all_valid == true // implies exactly 1 primary format + + test gffamily_best_phi_distance_is_small + given report = verify_golden_family() + then report.best_phi_distance < 0.05 + + test gffamily_avg_phi_distance_reasonable + given report = verify_golden_family() + and avg = report.avg_phi_distance + then avg > 0.0 and avg < 0.2 + + invariant gffamily_phi_ratio_target_positive + assert PHI_RATIO_TARGET > 0.0 + + invariant gffamily_phi_ratio_target_less_than_one + assert PHI_RATIO_TARGET < 1.0 + + invariant gffamily_family_size_constant + assert GOLDEN_FLOAT_FAMILY.len() == 10 + + invariant gffamily_gf4_at_index_0 + assert GOLDEN_FLOAT_FAMILY[0].name == "GF4" + + invariant gffamily_gf256_at_index_9 + assert GOLDEN_FLOAT_FAMILY[9].name == "GF256" + + invariant gffamily_gf128_at_index_8 + assert GOLDEN_FLOAT_FAMILY[8].name == "GF128" + + invariant gffamily_gf64_at_index_7 + assert GOLDEN_FLOAT_FAMILY[7].name == "GF64" + + invariant gffamily_gf32_at_index_6 + assert GOLDEN_FLOAT_FAMILY[6].name == "GF32" + + invariant gffamily_all_formats_have_sign_bits_1 + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + assert fmt.sign_bits == 1; + } + + invariant gffamily_all_formats_bits_sum_correct + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + assert fmt.sign_bits + fmt.exp_bits + fmt.mant_bits == fmt.bits; + } + + invariant gffamily_primary_is_gf16 + assert GOLDEN_FLOAT_FAMILY[3].is_primary == true + + invariant gffamily_phi_distances_non_negative + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + assert fmt.phi_distance >= 0.0; + } + + invariant gffamily_memory_efficiency_gf4 + assert abs(memory_efficiency(GOLDEN_FLOAT_FAMILY[0]) - 0.125) < 0.01 + + invariant gffamily_memory_efficiency_gf32 + assert abs(memory_efficiency(GOLDEN_FLOAT_FAMILY[6]) - 1.0) < 0.01 + + bench gffamily_get_format_by_name_latency + measure: nanoseconds to get_format_by_name("GF16") + target: < 100ns + + bench gffamily_get_format_by_bits_latency + measure: nanoseconds to get_format_by_bits(16) + target: < 100ns + + bench gffamily_get_primary_format_latency + measure: nanoseconds to get_primary_format() + target: < 50ns + + bench gffamily_verify_golden_family_latency + measure: nanoseconds to verify_golden_family() + target: < 500ns + + bench gffamily_memory_efficiency_latency + measure: nanoseconds to memory_efficiency(GOLDEN_FLOAT_FAMILY[3]) + target: < 100ns +} diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/int4.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/int4.t27 new file mode 100644 index 0000000000..7a49385b58 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/int4.t27 @@ -0,0 +1,766 @@ +// SPDX-License-Identifier: Apache-2.0 +; int4.t27 — Int4 Signed 4-bit Integer Quantization +; Range: -8 to 7 +; Used for ultra-low precision quantization in ML +; φ² + 1/φ² = 3 | TRINITY + +module triformat-int4; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 4; +pub const MIN : i8 = -8; +pub const MAX : i8 = 7; +pub const RANGE : u8 = 16; // 2^4 + +pub const MASK : u8 = 0x0F; // Lower 4 bits mask + +// ============================================================================ +// Types +// ============================================================================ + +pub const Int4 = i8; // Stored as i8, but only lower 4 bits used + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// int4_from_i8(value: i8) -> Int4 +// Convert i8 to Int4 with saturation +// Values < -8 clamp to -8, values > 7 clamp to 7 +pub fn int4_from_i8(value: i8) Int4 { + if (value < MIN) { + return MIN; + } else if (value > MAX) { + return MAX; + } + return value; +} + +// int4_from_i16(value: i16) -> Int4 +// Convert i16 to Int4 with saturation +pub fn int4_from_i16(value: i16) Int4 { + if (value < @as(i16, MIN)) { + return MIN; + } else if (value > @as(i16, MAX)) { + return MAX; + } + return @as(i8, @intCast(value)); +} + +// int4_from_i32(value: i32) -> Int4 +// Convert i32 to Int4 with saturation +pub fn int4_from_i32(value: i32) Int4 { + if (value < @as(i32, MIN)) { + return MIN; + } else if (value > @as(i32, MAX)) { + return MAX; + } + return @as(i8, @intCast(value)); +} + +// int4_from_f32(value: f32) -> Int4 +// Convert f32 to Int4 with rounding and saturation +// Round to nearest, ties to even +pub fn int4_from_f32(value: f32) Int4 { + if (std.math.isNan(value)) { + return 0; + } + if (value > @as(f32, @floatFromInt(MAX)) + 0.5) { + return MAX; + } + if (value < @as(f32, @floatFromInt(MIN)) - 0.5) { + return MIN; + } + // Round to nearest + const rounded = @as(i32, @intFromFloat(@round(value))); + return int4_from_i32(rounded); +} + +// int4_to_i8(value: Int4) -> i8 +// Convert Int4 to i8 (no-op, just returns value) +pub fn int4_to_i8(value: Int4) i8 { + return value; +} + +// int4_to_i16(value: Int4) -> i16 +// Convert Int4 to i16 (sign extension) +pub fn int4_to_i16(value: Int4) i16 { + // Sign extend from 4 bits to 16 bits + return @as(i16, value); +} + +// int4_to_i32(value: Int4) -> i32 +// Convert Int4 to i32 (sign extension) +pub fn int4_to_i32(value: Int4) i32 { + return @as(i32, value); +} + +// int4_to_f32(value: Int4) -> f32 +// Convert Int4 to f32 +pub fn int4_to_f32(value: Int4) f32 { + return @as(f32, @floatFromInt(value)); +} + +// int4_to_f64(value: Int4) -> f64 +// Convert Int4 to f64 +pub fn int4_to_f64(value: Int4) f64 { + return @as(f64, @floatFromInt(value)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// int4_add(a: Int4, b: Int4) -> Int4 +// Add two Int4 values with saturation +pub fn int4_add(a: Int4, b: Int4) Int4 { + const result = a + b; + if (result > MAX) { + return MAX; + } else if (result < MIN) { + return MIN; + } + return result; +} + +// int4_sub(a: Int4, b: Int4) -> Int4 +// Subtract two Int4 values with saturation +pub fn int4_sub(a: Int4, b: Int4) Int4 { + const result = a - b; + if (result > MAX) { + return MAX; + } else if (result < MIN) { + return MIN; + } + return result; +} + +// int4_mul(a: Int4, b: Int4) -> Int4 +// Multiply two Int4 values with saturation +pub fn int4_mul(a: Int4, b: Int4) Int4 { + const result = a * b; + if (result > MAX) { + return MAX; + } else if (result < MIN) { + return MIN; + } + return result; +} + +// int4_div(a: Int4, b: Int4) -> Int4 +// Divide two Int4 values with saturation +// Division by zero returns MAX or MIN based on sign of numerator +pub fn int4_div(a: Int4, b: Int4) Int4 { + if (b == 0) { + return if (a >= 0) MAX else MIN; + } + const result = a / b; + if (result > MAX) { + return MAX; + } else if (result < MIN) { + return MIN; + } + return result; +} + +// int4_abs(value: Int4) -> Int4 +// Absolute value of Int4 with saturation +pub fn int4_abs(value: Int4) Int4 { + if (value == MIN) { + return MAX; // -8 -> 7 (saturated) + } + return if (value < 0) -value else value; +} + +// int4_neg(value: Int4) -> Int4 +// Negate Int4 (with saturation for -MIN) +pub fn int4_neg(value: Int4) Int4 { + if (value == MIN) { + return MAX; // -(-8) = 7 (saturated) + } + return -value; +} + +// ============================================================================ +// Comparison Functions +// ============================================================================ + +// int4_is_equal(a: Int4, b: Int4) -> bool +pub fn int4_is_equal(a: Int4, b: Int4) bool { + return a == b; +} + +// int4_is_greater(a: Int4, b: Int4) -> bool +pub fn int4_is_greater(a: Int4, b: Int4) bool { + return a > b; +} + +// int4_is_less(a: Int4, b: Int4) -> bool +pub fn int4_is_less(a: Int4, b: Int4) bool { + return a < b; +} + +// int4_min(a: Int4, b: Int4) -> Int4 +pub fn int4_min(a: Int4, b: Int4) Int4 { + return if (a < b) a else b; +} + +// int4_max(a: Int4, b: Int4) -> Int4 +pub fn int4_max(a: Int4, b: Int4) Int4 { + return if (a > b) a else b; +} + +// int4_clamp(value: Int4, min: Int4, max: Int4) -> Int4 +pub fn int4_clamp(value: Int4, min: Int4, max: Int4) Int4 { + if (value < min) { + return min; + } else if (value > max) { + return max; + } + return value; +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// int4_clamp_to_range(value: Int4) -> Int4 +// Clamp value to Int4 range [-8, 7] +pub fn int4_clamp_to_range(value: Int4) Int4 { + return int4_clamp(value, MIN, MAX); +} + +// int4_is_in_range(value: i8) -> bool +// Check if value is in Int4 range +pub fn int4_is_in_range(value: i8) bool { + return value >= MIN and value <= MAX; +} + +// int4_lerp(a: Int4, b: Int4, t: f32) -> Int4 +// Linear interpolation between a and b +pub fn int4_lerp(a: Int4, b: Int4, t: f32) Int4 { + const fa = @as(f32, @floatFromInt(a)); + const fb = @as(f32, @floatFromInt(b)); + const result = fa + (fb - fa) * t; + return int4_from_f32(result); +} + +// int4_sq(value: Int4) -> Int4 +// Square value with saturation +pub fn int4_sq(value: Int4) Int4 { + return int4_mul(value, value); +} + +// int4_abs_diff(a: Int4, b: Int4) -> Int4 +// Absolute difference between two Int4 values +pub fn int4_abs_diff(a: Int4, b: Int4) Int4 { + const diff = a - b; + return int4_abs(diff); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "int4_from_i8_in_range" { + given value = 5 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == 5); +} + +test "int4_from_i8_above_max" { + given value = 10 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == MAX); +} + +test "int4_from_i8_below_min" { + given value = -10 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == MIN); +} + +test "int4_from_i8_at_max" { + given value = 7 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == MAX); +} + +test "int4_from_i8_at_min" { + given value = -8 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == MIN); +} + +test "int4_from_i16_positive" { + given value = 100 + try std.testing.expect(result = int4_from_i16(value)); + try std.testing.expect(result == MAX); +} + +test "int4_from_i16_negative" { + given value = -100 + try std.testing.expect(result = int4_from_i16(value)); + try std.testing.expect(result == MIN); +} + +test "int4_from_i16_in_range" { + given value = 3 + try std.testing.expect(result = int4_from_i16(value)); + try std.testing.expect(result == 3); +} + +test "int4_from_i32_positive" { + given value = 1000 + try std.testing.expect(result = int4_from_i32(value)); + try std.testing.expect(result == MAX); +} + +test "int4_from_i32_negative" { + given value = -1000 + try std.testing.expect(result = int4_from_i32(value)); + try std.testing.expect(result == MIN); +} + +test "int4_from_f32_zero" { + given value = 0.0 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == 0); +} + +test "int4_from_f32_one" { + given value = 1.0 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == 1); +} + +test "int4_from_f32_negative" { + given value = -1.5 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == -2); +} + +test "int4_from_f32_nan" { + given result = int4_from_f32(std.math.nan(f32)) + try std.testing.expect(result == 0); +} + +test "int4_from_f32_round_up" { + given value = 1.6 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == 2); +} + +test "int4_from_f32_round_down" { + given value = 1.4 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == 1); +} + +test "int4_to_i8_identity" { + given value: Int4 = 5 + try std.testing.expect(result = int4_to_i8(value)); + try std.testing.expect(result == value); +} + +test "int4_to_i16_sign_extend" { + given value: Int4 = -1 + try std.testing.expect(result = int4_to_i16(value)); + try std.testing.expect(result == -1); +} + +test "int4_to_i32_sign_extend" { + given value: Int4 = -1 + try std.testing.expect(result = int4_to_i32(value)); + try std.testing.expect(result == -1); +} + +test "int4_to_f32_positive" { + given value: Int4 = 5 + try std.testing.expect(result = int4_to_f32(value)); + try std.testing.expect(abs(result - 5.0) < 0.001); +} + +test "int4_to_f32_negative" { + given value: Int4 = -3 + try std.testing.expect(result = int4_to_f32(value)); + try std.testing.expect(abs(result + 3.0) < 0.001); +} + +test "int4_add_simple" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 4); + try std.testing.expect(result = int4_add(a, b)); + try std.testing.expect(result == MAX // 3 + 4 = 7 = MAX); +} + +test "int4_add_overflow" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_add(a, b)); + try std.testing.expect(result == MAX // 5 + 5 = 10 -> 7); +} + +test "int4_add_underflow" { + given a: Int4 = -5 + try std.testing.expect(b: Int4 = -5); + try std.testing.expect(result = int4_add(a, b)); + try std.testing.expect(result == MIN // -5 + -5 = -10 -> -8); +} + +test "int4_sub_simple" { + given a: Int4 = 7 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_sub(a, b)); + try std.testing.expect(result == 4); +} + +test "int4_sub_negative" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 7); + try std.testing.expect(result = int4_sub(a, b)); + try std.testing.expect(result == -4); +} + +test "int4_mul_positive" { + given a: Int4 = 2 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_mul(a, b)); + try std.testing.expect(result == 6); +} + +test "int4_mul_negative" { + given a: Int4 = -2 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_mul(a, b)); + try std.testing.expect(result == -6); +} + +test "int4_mul_overflow" { + given a: Int4 = 4 + try std.testing.expect(b: Int4 = 4); + try std.testing.expect(result = int4_mul(a, b)); + try std.testing.expect(result == MAX // 4 * 4 = 16 -> 7); +} + +test "int4_div_simple" { + given a: Int4 = 6 + try std.testing.expect(b: Int4 = 2); + try std.testing.expect(result = int4_div(a, b)); + try std.testing.expect(result == 3); +} + +test "int4_div_by_zero_positive" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 0); + try std.testing.expect(result = int4_div(a, b)); + try std.testing.expect(result == MAX); +} + +test "int4_div_by_zero_negative" { + given a: Int4 = -5 + try std.testing.expect(b: Int4 = 0); + try std.testing.expect(result = int4_div(a, b)); + try std.testing.expect(result == MIN); +} + +test "int4_abs_positive" { + given value: Int4 = 5 + try std.testing.expect(result = int4_abs(value)); + try std.testing.expect(result == 5); +} + +test "int4_abs_negative" { + given value: Int4 = -5 + try std.testing.expect(result = int4_abs(value)); + try std.testing.expect(result == 5); +} + +test "int4_abs_min" { + given value: Int4 = MIN + try std.testing.expect(result = int4_abs(value)); + try std.testing.expect(result == MAX // |-8| = 7 (saturated)); +} + +test "int4_neg_positive" { + given value: Int4 = 5 + try std.testing.expect(result = int4_neg(value)); + try std.testing.expect(result == -5); +} + +test "int4_neg_negative" { + given value: Int4 = -5 + try std.testing.expect(result = int4_neg(value)); + try std.testing.expect(result == 5); +} + +test "int4_neg_min" { + given value: Int4 = MIN + try std.testing.expect(result = int4_neg(value)); + try std.testing.expect(result == MAX // -(-8) = 7 (saturated)); +} + +test "int4_is_equal_true" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(int4_is_equal(a, b) == true); +} + +test "int4_is_equal_false" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(int4_is_equal(a, b) == false); +} + +test "int4_is_greater_true" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(int4_is_greater(a, b) == true); +} + +test "int4_is_greater_false" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(int4_is_greater(a, b) == false); +} + +test "int4_min_smaller" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_min(a, b)); + try std.testing.expect(result == 3); +} + +test "int4_min_larger" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_min(a, b)); + try std.testing.expect(result == 3); +} + +test "int4_max_smaller" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_max(a, b)); + try std.testing.expect(result == 5); +} + +test "int4_max_larger" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_max(a, b)); + try std.testing.expect(result == 5); +} + +test "int4_clamp_in_range" { + given value: Int4 = 5 + try std.testing.expect(min: Int4 = 0); + try std.testing.expect(max: Int4 = MAX); + try std.testing.expect(result = int4_clamp(value, min, max)); + try std.testing.expect(result == 5); +} + +test "int4_clamp_below_min" { + given value: Int4 = -10 + try std.testing.expect(min: Int4 = -5); + try std.testing.expect(max: Int4 = MAX); + try std.testing.expect(result = int4_clamp(value, min, max)); + try std.testing.expect(result == -5); +} + +test "int4_clamp_above_max" { + given value: Int4 = 10 + try std.testing.expect(min: Int4 = MIN); + try std.testing.expect(max: Int4 = 5); + try std.testing.expect(result = int4_clamp(value, min, max)); + try std.testing.expect(result == 5); +} + +test "int4_is_in_range_true" { + try std.testing.expect(int4_is_in_range(5) == true); +} + +test "int4_is_in_range_false_high" { + try std.testing.expect(int4_is_in_range(10) == false); +} + +test "int4_is_in_range_false_low" { + try std.testing.expect(int4_is_in_range(-10) == false); +} + +test "int4_is_in_range_at_max" { + try std.testing.expect(int4_is_in_range(MAX) == true); +} + +test "int4_is_in_range_at_min" { + try std.testing.expect(int4_is_in_range(MIN) == true); +} + +test "int4_lerp_zero" { + given a: Int4 = 1 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = int4_lerp(a, b, t)); + try std.testing.expect(result == 1); +} + +test "int4_lerp_one" { + given a: Int4 = 1 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = int4_lerp(a, b, t)); + try std.testing.expect(result == 5); +} + +test "int4_lerp_half" { + given a: Int4 = 0 + try std.testing.expect(b: Int4 = 4); + try std.testing.expect(t = 0.5); + try std.testing.expect(result = int4_lerp(a, b, t)); + try std.testing.expect(result == 2); +} + +test "int4_sq_positive" { + given value: Int4 = 3 + try std.testing.expect(result = int4_sq(value)); + try std.testing.expect(result == 9 // saturates to 7); +} + +test "int4_sq_negative" { + given value: Int4 = -3 + try std.testing.expect(result = int4_sq(value)); + try std.testing.expect(result == 9 // saturates to 7); +} + +test "int4_sq_zero" { + given value: Int4 = 0 + try std.testing.expect(result = int4_sq(value)); + try std.testing.expect(result == 0); +} + +test "int4_abs_diff_same" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_abs_diff(a, b)); + try std.testing.expect(result == 0); +} + +test "int4_abs_diff_positive" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 2); + try std.testing.expect(result = int4_abs_diff(a, b)); + try std.testing.expect(result == 3); +} + +test "int4_abs_diff_negative" { + given a: Int4 = 2 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_abs_diff(a, b)); + try std.testing.expect(result == 3); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant int4_bits_constant + assert BITS == 4 + +invariant int4_min_is_negative_eight + assert MIN == -8 + +invariant int4_max_is_seven + assert MAX == 7 + +invariant int4_range_is_sixteen + assert RANGE == 16 + +invariant int4_mask_lower_four_bits + assert MASK == 0x0F + +invariant int4_from_i8_max_saturates + assert int4_from_i8(100) == MAX + +invariant int4_from_i8_min_saturates + assert int4_from_i8(-100) == MIN + +invariant int4_from_i8_max_passes + assert int4_from_i8(MAX) == MAX + +invariant int4_from_i8_min_passes + assert int4_from_i8(MIN) == MIN + +invariant int4_to_i8_zero + assert int4_to_i8(0) == 0 + +invariant int4_to_i8_max + assert int4_to_i8(MAX) == MAX + +invariant int4_to_i8_min + assert int4_to_i8(MIN) == MIN + +invariant int4_add_zero + assert int4_add(5, 0) == 5 + +invariant int4_sub_zero + assert int4_sub(5, 0) == 5 + +invariant int4_mul_one + assert int4_mul(5, 1) == 5 + +invariant int4_mul_zero + assert int4_mul(5, 0) == 0 + +invariant int4_abs_non_negative + given value: Int4 = -5 + assert int4_abs(value) >= 0 + +invariant int4_neg_twice_original + given value: Int4 = 5 + try std.testing.expect(neg = int4_neg(value)); + try std.testing.expect(neg_again = int4_neg(neg)); + assert neg_again == value or value == MIN // MIN saturates + +invariant int4_min_le_both + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_min(a, b)); + assert result <= a and result <= b + +invariant int4_max_ge_both + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_max(a, b)); + assert result >= a and result >= b + +invariant int4_clamp_result_in_range + given result = int4_clamp(0, -5, 5) + assert result >= -5 and result <= 5 + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench int4_from_i8_latency + measure: nanoseconds to int4_from_i8(5) + target: < 5ns + +bench int4_from_f32_latency + measure: nanoseconds to int4_from_f32(5.5) + target: < 20ns + +bench int4_add_latency + measure: nanoseconds to int4_add(3, 4) + target: < 10ns + +bench int4_mul_latency + measure: nanoseconds to int4_mul(3, 4) + target: < 10ns + +bench int4_abs_latency + measure: nanoseconds to int4_abs(-5) + target: < 5ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/int8.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/int8.t27 new file mode 100644 index 0000000000..82e33b87a0 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/int8.t27 @@ -0,0 +1,1151 @@ +// SPDX-License-Identifier: Apache-2.0 +; int8.t27 — Int8 Signed 8-bit Integer Quantization +; Range: -128 to 127 +; Standard 8-bit signed integer, widely used in quantization +; φ² + 1/φ² = 3 | TRINITY + +module triformat-int8; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const MIN : i8 = -128; +pub const MAX : i8 = 127; +pub const RANGE : u16 = 256; // 2^8 + +pub const MASK : u8 = 0xFF; // All 8 bits mask + +// ============================================================================ +// Types +// ============================================================================ + +pub const Int8 = i8; // Native i8 type + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// int8_from_i8(value: i8) -> Int8 +// Convert i8 to Int8 (identity function, no-op) +pub fn int8_from_i8(value: i8) Int8 { + return value; +} + +// int8_from_i16(value: i16) -> Int8 +// Convert i16 to Int8 with saturation +pub fn int8_from_i16(value: i16) Int8 { + if (value < @as(i16, MIN)) { + return MIN; + } else if (value > @as(i16, MAX)) { + return MAX; + } + return @as(i8, @intCast(value)); +} + +// int8_from_i32(value: i32) -> Int8 +// Convert i32 to Int8 with saturation +pub fn int8_from_i32(value: i32) Int8 { + if (value < @as(i32, MIN)) { + return MIN; + } else if (value > @as(i32, MAX)) { + return MAX; + } + return @as(i8, @intCast(value)); +} + +// int8_from_u8(value: u8) -> Int8 +// Convert u8 to Int8 with saturation +// Values > 127 clamp to 127 +pub fn int8_from_u8(value: u8) Int8 { + if (value > 127) { + return MAX; + } + return @as(i8, @bitCast(value)); +} + +// int8_from_f32(value: f32) -> Int8 +// Convert f32 to Int8 with rounding and saturation +// Round to nearest, ties to even +pub fn int8_from_f32(value: f32) Int8 { + if (std.math.isNan(value)) { + return 0; + } + if (value > @as(f32, @floatFromInt(MAX)) + 0.5) { + return MAX; + } + if (value < @as(f32, @floatFromInt(MIN)) - 0.5) { + return MIN; + } + const rounded = @as(i32, @intFromFloat(@round(value))); + return int8_from_i32(rounded); +} + +// int8_from_f64(value: f64) -> Int8 +// Convert f64 to Int8 with rounding and saturation +pub fn int8_from_f64(value: f64) Int8 { + if (std.math.isNan(value)) { + return 0; + } + if (value > @as(f64, @floatFromInt(MAX)) + 0.5) { + return MAX; + } + if (value < @as(f64, @floatFromInt(MIN)) - 0.5) { + return MIN; + } + const rounded = @as(i32, @intFromFloat(@round(value))); + return int8_from_i32(rounded); +} + +// int8_to_i8(value: Int8) -> i8 +// Convert Int8 to i8 (identity function) +pub fn int8_to_i8(value: Int8) i8 { + return value; +} + +// int8_to_i16(value: Int8) -> i16 +// Convert Int8 to i16 (sign extension) +pub fn int8_to_i16(value: Int8) i16 { + return @as(i16, value); +} + +// int8_to_i32(value: Int8) -> i32 +// Convert Int8 to i32 (sign extension) +pub fn int8_to_i32(value: Int8) i32 { + return @as(i32, value); +} + +// int8_to_u8(value: Int8) -> ?u8 +// Convert Int8 to u8 (returns null for negative values) +pub fn int8_to_u8(value: Int8) ?u8 { + if (value < 0) { + return null; + } + return @as(u8, @intCast(value)); +} + +// int8_to_f32(value: Int8) -> f32 +// Convert Int8 to f32 +pub fn int8_to_f32(value: Int8) f32 { + return @as(f32, @floatFromInt(value)); +} + +// int8_to_f64(value: Int8) -> f64 +// Convert Int8 to f64 +pub fn int8_to_f64(value: Int8) f64 { + return @as(f64, @floatFromInt(value)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// int8_add(a: Int8, b: Int8) -> Int8 +// Add two Int8 values with saturation +pub fn int8_add(a: Int8, b: Int8) Int8 { + const result = @as(i16, a) + @as(i16, b); + return int8_from_i16(result); +} + +// int8_sub(a: Int8, b: Int8) -> Int8 +// Subtract two Int8 values with saturation +pub fn int8_sub(a: Int8, b: Int8) Int8 { + const result = @as(i16, a) - @as(i16, b); + return int8_from_i16(result); +} + +// int8_mul(a: Int8, b: Int8) -> Int8 +// Multiply two Int8 values with saturation +pub fn int8_mul(a: Int8, b: Int8) Int8 { + const result = @as(i16, a) * @as(i16, b); + return int8_from_i16(result); +} + +// int8_div(a: Int8, b: Int8) -> Int8 +// Divide two Int8 values with saturation +// Division by zero returns MAX or MIN based on sign of numerator +pub fn int8_div(a: Int8, b: Int8) Int8 { + if (b == 0) { + return if (a >= 0) MAX else MIN; + } + return a / b; // i8 division saturates automatically on overflow +} + +// int8_mod(a: Int8, b: Int8) -> Int8 +// Modulo operation (returns 0 on division by zero) +pub fn int8_mod(a: Int8, b: Int8) Int8 { + if (b == 0) { + return 0; + } + const result = @rem(a, b); + // Handle negative remainder to make it always positive + return if (result < 0) result + b else result; +} + +// int8_pow(base: Int8, exp: u8) -> Int8 +// Power function with saturation +pub fn int8_pow(base: Int8, exp: u8) Int8 { + if (exp == 0) { + return 1; + } + if (base == 0) { + return 0; + } + if (base == 1) { + return 1; + } + if (base == -1) { + return if (exp % 2 == 0) 1 else -1; + } + + var result: i16 = 1; + var current: i16 = @as(i16, base); + var remaining = exp; + + while (remaining > 0) { + if (remaining % 2 == 1) { + result = result * current; + if (result > MAX) { + return MAX; + } + if (result < MIN) { + return MIN; + } + } + current = current * current; + if (current > MAX) { + return MAX; + } + if (current < MIN) { + return MIN; + } + remaining = remaining / 2; + } + + return int8_from_i16(result); +} + +// ============================================================================ +// Bitwise Operations +// ============================================================================ + +// int8_and(a: Int8, b: Int8) -> Int8 +// Bitwise AND +pub fn int8_and(a: Int8, b: Int8) Int8 { + return a & b; +} + +// int8_or(a: Int8, b: Int8) -> Int8 +// Bitwise OR +pub fn int8_or(a: Int8, b: Int8) Int8 { + return a | b; +} + +// int8_xor(a: Int8, b: Int8) -> Int8 +// Bitwise XOR +pub fn int8_xor(a: Int8, b: Int8) Int8 { + return a ^ b; +} + +// int8_not(value: Int8) -> Int8 +// Bitwise NOT +pub fn int8_not(value: Int8) Int8 { + return ~value; +} + +// int8_shl(value: Int8, amount: u8) -> Int8 +// Left shift (saturated) +pub fn int8_shl(value: Int8, amount: u8) Int8 { + const clamped = @min(amount, 7); + const result = @as(i16, value) << clamped; + return int8_from_i16(result); +} + +// int8_shr(value: Int8, amount: u8) -> Int8 +// Right shift (arithmetic, sign-preserving) +pub fn int8_shr(value: Int8, amount: u8) Int8 { + const clamped = @min(amount, 7); + return @shrExact(value, clamped); +} + +// int8_rol(value: Int8, amount: u8) -> Int8 +// Rotate left +pub fn int8_rol(value: Int8, amount: u8) Int8 { + const clamped = amount % 8; + const uval = @as(u8, @bitCast(value)); + const result = (uval << clamped) | (uval >> (8 - clamped)); + return @as(i8, @bitCast(result)); +} + +// int8_ror(value: Int8, amount: u8) -> Int8 +// Rotate right +pub fn int8_ror(value: Int8, amount: u8) Int8 { + const clamped = amount % 8; + const uval = @as(u8, @bitCast(value)); + const result = (uval >> clamped) | (uval << (8 - clamped)); + return @as(i8, @bitCast(result)); +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// int8_abs(value: Int8) -> Int8 +// Absolute value of Int8 with saturation +pub fn int8_abs(value: Int8) Int8 { + if (value == MIN) { + return MAX; // |-128| = 127 (saturated) + } + return if (value < 0) -value else value; +} + +// int8_neg(value: Int8) -> Int8 +// Negate Int8 (with saturation for -MIN) +pub fn int8_neg(value: Int8) Int8 { + if (value == MIN) { + return MAX; // -(-128) = 127 (saturated) + } + return -value; +} + +// int8_sign(value: Int8) -> Int8 +// Sign of value: -1, 0, or 1 +pub fn int8_sign(value: Int8) Int8 { + if (value < 0) { + return -1; + } else if (value > 0) { + return 1; + } + return 0; +} + +// int8_is_equal(a: Int8, b: Int8) -> bool +pub fn int8_is_equal(a: Int8, b: Int8) bool { + return a == b; +} + +// int8_is_greater(a: Int8, b: Int8) -> bool +pub fn int8_is_greater(a: Int8, b: Int8) bool { + return a > b; +} + +// int8_is_less(a: Int8, b: Int8) -> bool +pub fn int8_is_less(a: Int8, b: Int8) bool { + return a < b; +} + +// int8_min(a: Int8, b: Int8) -> Int8 +pub fn int8_min(a: Int8, b: Int8) Int8 { + return if (a < b) a else b; +} + +// int8_max(a: Int8, b: Int8) -> Int8 +pub fn int8_max(a: Int8, b: Int8) Int8 { + return if (a > b) a else b; +} + +// int8_clamp(value: Int8, min: Int8, max: Int8) -> Int8 +pub fn int8_clamp(value: Int8, min: Int8, max: Int8) Int8 { + if (value < min) { + return min; + } else if (value > max) { + return max; + } + return value; +} + +// int8_lerp(a: Int8, b: Int8, t: f32) -> Int8 +// Linear interpolation between a and b +pub fn int8_lerp(a: Int8, b: Int8, t: f32) Int8 { + const fa = @as(f32, @floatFromInt(a)); + const fb = @as(f32, @floatFromInt(b)); + const result = fa + (fb - fa) * t; + return int8_from_f32(result); +} + +// int8_sq(value: Int8) -> Int8 +// Square value with saturation +pub fn int8_sq(value: Int8) Int8 { + return int8_mul(value, value); +} + +// int8_abs_diff(a: Int8, b: Int8) -> Int8 +// Absolute difference between two Int8 values +pub fn int8_abs_diff(a: Int8, b: Int8) Int8 { + const diff = int8_sub(a, b); + return int8_abs(diff); +} + +// int8_sqrt(value: Int8) -> Int8 +// Integer square root (rounded down) +// Returns 0 for negative inputs +pub fn int8_sqrt(value: Int8) Int8 { + if (value < 0) { + return 0; + } + if (value == 0) { + return 0; + } + + var result: i8 = 0; + var bit: i8 = 1; + + // Find the highest power of 4 <= value + while (bit <= value and bit > 0) { + bit = bit * 4; + } + bit = bit / 4; + + while (bit != 0) { + if (value >= result + bit) { + value = value - (result + bit); + result = result / 2 + bit; + } else { + result = result / 2; + } + bit = bit / 4; + } + + return result; +} + +// int8_is_power_of_two(value: Int8) -> bool +// Check if value is a power of two (for positive values only) +pub fn int8_is_power_of_two(value: Int8) bool { + if (value <= 0) { + return false; + } + const uval = @as(u8, @bitCast(value)); + return (uval & (uval - 1)) == 0; +} + +// int8_count_ones(value: Int8) -> u8 +// Count the number of set bits +pub fn int8_count_ones(value: Int8) u8 { + const uval = @as(u8, @bitCast(value)); + return @popCount(uval); +} + +// int8_count_zeros(value: Int8) -> u8 +// Count the number of zero bits +pub fn int8_count_zeros(value: Int8) u8 { + return 8 - int8_count_ones(value); +} + +// int8_reverse_bits(value: Int8) -> Int8 +// Reverse the bits in an Int8 +pub fn int8_reverse_bits(value: Int8) Int8 { + const uval = @as(u8, @bitCast(value)); + var result: u8 = 0; + var i: u8 = 0; + while (i < 8) { + result = (result << 1) | (uval & 1); + uval = uval >> 1; + i = i + 1; + } + return @as(i8, @bitCast(result)); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "int8_from_i8_identity" { + given value = 42 + try std.testing.expect(result = int8_from_i8(value)); + try std.testing.expect(result == 42); +} + +test "int8_from_i16_positive" { + given value = 100 + try std.testing.expect(result = int8_from_i16(value)); + try std.testing.expect(result == 100); +} + +test "int8_from_i16_overflow" { + given value = 200 + try std.testing.expect(result = int8_from_i16(value)); + try std.testing.expect(result == MAX); +} + +test "int8_from_i16_negative" { + given value = -50 + try std.testing.expect(result = int8_from_i16(value)); + try std.testing.expect(result == -50); +} + +test "int8_from_i16_underflow" { + given value = -200 + try std.testing.expect(result = int8_from_i16(value)); + try std.testing.expect(result == MIN); +} + +test "int8_from_i32_in_range" { + given value = 42 + try std.testing.expect(result = int8_from_i32(value)); + try std.testing.expect(result == 42); +} + +test "int8_from_i32_overflow" { + given value = 1000 + try std.testing.expect(result = int8_from_i32(value)); + try std.testing.expect(result == MAX); +} + +test "int8_from_u8_in_range" { + given value = 100 + try std.testing.expect(result = int8_from_u8(value)); + try std.testing.expect(result == 100); +} + +test "int8_from_u8_overflow" { + given value = 200 + try std.testing.expect(result = int8_from_u8(value)); + try std.testing.expect(result == MAX); +} + +test "int8_from_f32_zero" { + given value = 0.0 + try std.testing.expect(result = int8_from_f32(value)); + try std.testing.expect(result == 0); +} + +test "int8_from_f32_one" { + given value = 1.0 + try std.testing.expect(result = int8_from_f32(value)); + try std.testing.expect(result == 1); +} + +test "int8_from_f32_round_up" { + given value = 1.6 + try std.testing.expect(result = int8_from_f32(value)); + try std.testing.expect(result == 2); +} + +test "int8_from_f32_round_down" { + given value = 1.4 + try std.testing.expect(result = int8_from_f32(value)); + try std.testing.expect(result == 1); +} + +test "int8_from_f32_nan" { + given result = int8_from_f32(std.math.nan(f32)) + try std.testing.expect(result == 0); +} + +test "int8_to_i8_identity" { + given value: Int8 = 42 + try std.testing.expect(result = int8_to_i8(value)); + try std.testing.expect(result == 42); +} + +test "int8_to_i16_sign_extend" { + given value: Int8 = -1 + try std.testing.expect(result = int8_to_i16(value)); + try std.testing.expect(result == -1); +} + +test "int8_to_i32_sign_extend" { + given value: Int8 = -1 + try std.testing.expect(result = int8_to_i32(value)); + try std.testing.expect(result == -1); +} + +test "int8_to_u8_positive" { + given value: Int8 = 42 + try std.testing.expect(result = int8_to_u8(value)); + try std.testing.expect(result.? == 42); +} + +test "int8_to_u8_negative" { + given value: Int8 = -1 + try std.testing.expect(result = int8_to_u8(value)); + try std.testing.expect(result == null); +} + +test "int8_add_simple" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 30); + try std.testing.expect(result = int8_add(a, b)); + try std.testing.expect(result == 80); +} + +test "int8_add_overflow" { + given a: Int8 = 100 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_add(a, b)); + try std.testing.expect(result == MAX); +} + +test "int8_add_underflow" { + given a: Int8 = -100 + try std.testing.expect(b: Int8 = -50); + try std.testing.expect(result = int8_add(a, b)); + try std.testing.expect(result == MIN); +} + +test "int8_sub_simple" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 30); + try std.testing.expect(result = int8_sub(a, b)); + try std.testing.expect(result == 20); +} + +test "int8_sub_negative" { + given a: Int8 = 30 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_sub(a, b)); + try std.testing.expect(result == -20); +} + +test "int8_mul_positive" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 5); + try std.testing.expect(result = int8_mul(a, b)); + try std.testing.expect(result == 50); +} + +test "int8_mul_negative" { + given a: Int8 = -10 + try std.testing.expect(b: Int8 = 5); + try std.testing.expect(result = int8_mul(a, b)); + try std.testing.expect(result == -50); +} + +test "int8_mul_overflow" { + given a: Int8 = 20 + try std.testing.expect(b: Int8 = 20); + try std.testing.expect(result = int8_mul(a, b)); + try std.testing.expect(result == MAX); +} + +test "int8_div_simple" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 5); + try std.testing.expect(result = int8_div(a, b)); + try std.testing.expect(result == 10); +} + +test "int8_div_by_zero_positive" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 0); + try std.testing.expect(result = int8_div(a, b)); + try std.testing.expect(result == MAX); +} + +test "int8_div_by_zero_negative" { + given a: Int8 = -50 + try std.testing.expect(b: Int8 = 0); + try std.testing.expect(result = int8_div(a, b)); + try std.testing.expect(result == MIN); +} + +test "int8_mod_simple" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 3); + try std.testing.expect(result = int8_mod(a, b)); + try std.testing.expect(result == 1); +} + +test "int8_mod_by_zero" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 0); + try std.testing.expect(result = int8_mod(a, b)); + try std.testing.expect(result == 0); +} + +test "int8_pow_zero" { + given base: Int8 = 5 + try std.testing.expect(exp: u8 = 0); + try std.testing.expect(result = int8_pow(base, exp)); + try std.testing.expect(result == 1); +} + +test "int8_pow_one" { + given base: Int8 = 5 + try std.testing.expect(exp: u8 = 1); + try std.testing.expect(result = int8_pow(base, exp)); + try std.testing.expect(result == 5); +} + +test "int8_pow_two" { + given base: Int8 = 5 + try std.testing.expect(exp: u8 = 2); + try std.testing.expect(result = int8_pow(base, exp)); + try std.testing.expect(result == 25); +} + +test "int8_pow_overflow" { + given base: Int8 = 20 + try std.testing.expect(exp: u8 = 2); + try std.testing.expect(result = int8_pow(base, exp)); + try std.testing.expect(result == MAX); +} + +test "int8_and_simple" { + given a: Int8 = 0b10101010 + try std.testing.expect(b: Int8 = 0b11001100); + try std.testing.expect(result = int8_and(a, b)); + try std.testing.expect(result == 0b10001000); +} + +test "int8_or_simple" { + given a: Int8 = 0b10101010 + try std.testing.expect(b: Int8 = 0b11001100); + try std.testing.expect(result = int8_or(a, b)); + try std.testing.expect(result == 0b11101110); +} + +test "int8_xor_simple" { + given a: Int8 = 0b10101010 + try std.testing.expect(b: Int8 = 0b11001100); + try std.testing.expect(result = int8_xor(a, b)); + try std.testing.expect(result == 0b01100110); +} + +test "int8_not_simple" { + given value: Int8 = 0b10101010 + try std.testing.expect(result = int8_not(value)); + try std.testing.expect(result == 0b01010101); +} + +test "int8_shl_simple" { + given value: Int8 = 0b00000011 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_shl(value, amount)); + try std.testing.expect(result == 0b00001100); +} + +test "int8_shl_overflow" { + given value: Int8 = 64 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_shl(value, amount)); + try std.testing.expect(result == MAX); +} + +test "int8_shr_simple" { + given value: Int8 = 0b00001100 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_shr(value, amount)); + try std.testing.expect(result == 0b00000011); +} + +test "int8_rol_simple" { + given value: Int8 = 0b00000011 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_rol(value, amount)); + try std.testing.expect(result == 0b00001100); +} + +test "int8_ror_simple" { + given value: Int8 = 0b00001100 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_ror(value, amount)); + try std.testing.expect(result == 0b00000011); +} + +test "int8_abs_positive" { + given value: Int8 = 42 + try std.testing.expect(result = int8_abs(value)); + try std.testing.expect(result == 42); +} + +test "int8_abs_negative" { + given value: Int8 = -42 + try std.testing.expect(result = int8_abs(value)); + try std.testing.expect(result == 42); +} + +test "int8_abs_min" { + given value: Int8 = MIN + try std.testing.expect(result = int8_abs(value)); + try std.testing.expect(result == MAX); +} + +test "int8_neg_positive" { + given value: Int8 = 42 + try std.testing.expect(result = int8_neg(value)); + try std.testing.expect(result == -42); +} + +test "int8_neg_negative" { + given value: Int8 = -42 + try std.testing.expect(result = int8_neg(value)); + try std.testing.expect(result == 42); +} + +test "int8_neg_min" { + given value: Int8 = MIN + try std.testing.expect(result = int8_neg(value)); + try std.testing.expect(result == MAX); +} + +test "int8_sign_positive" { + given value: Int8 = 42 + try std.testing.expect(result = int8_sign(value)); + try std.testing.expect(result == 1); +} + +test "int8_sign_negative" { + given value: Int8 = -42 + try std.testing.expect(result = int8_sign(value)); + try std.testing.expect(result == -1); +} + +test "int8_sign_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_sign(value)); + try std.testing.expect(result == 0); +} + +test "int8_min_smaller" { + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_min(a, b)); + try std.testing.expect(result == 42); +} + +test "int8_max_larger" { + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_max(a, b)); + try std.testing.expect(result == 50); +} + +test "int8_clamp_in_range" { + given value: Int8 = 42 + try std.testing.expect(min: Int8 = 0); + try std.testing.expect(max: Int8 = 100); + try std.testing.expect(result = int8_clamp(value, min, max)); + try std.testing.expect(result == 42); +} + +test "int8_clamp_below" { + given value: Int8 = -50 + try std.testing.expect(min: Int8 = 0); + try std.testing.expect(max: Int8 = 100); + try std.testing.expect(result = int8_clamp(value, min, max)); + try std.testing.expect(result == 0); +} + +test "int8_clamp_above" { + given value: Int8 = 150 + try std.testing.expect(min: Int8 = 0); + try std.testing.expect(max: Int8 = 100); + try std.testing.expect(result = int8_clamp(value, min, max)); + try std.testing.expect(result == 100); +} + +test "int8_lerp_zero" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = int8_lerp(a, b, t)); + try std.testing.expect(result == 10); +} + +test "int8_lerp_one" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = int8_lerp(a, b, t)); + try std.testing.expect(result == 50); +} + +test "int8_lerp_half" { + given a: Int8 = 0 + try std.testing.expect(b: Int8 = 20); + try std.testing.expect(t = 0.5); + try std.testing.expect(result = int8_lerp(a, b, t)); + try std.testing.expect(result == 10); +} + +test "int8_sq_positive" { + given value: Int8 = 10 + try std.testing.expect(result = int8_sq(value)); + try std.testing.expect(result == 100); +} + +test "int8_sq_negative" { + given value: Int8 = -10 + try std.testing.expect(result = int8_sq(value)); + try std.testing.expect(result == 100); +} + +test "int8_sq_overflow" { + given value: Int8 = 20 + try std.testing.expect(result = int8_sq(value)); + try std.testing.expect(result == MAX); +} + +test "int8_abs_diff_same" { + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 42); + try std.testing.expect(result = int8_abs_diff(a, b)); + try std.testing.expect(result == 0); +} + +test "int8_abs_diff_positive" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 30); + try std.testing.expect(result = int8_abs_diff(a, b)); + try std.testing.expect(result == 20); +} + +test "int8_sqrt_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 0); +} + +test "int8_sqrt_one" { + given value: Int8 = 1 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 1); +} + +test "int8_sqrt_four" { + given value: Int8 = 4 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 2); +} + +test "int8_sqrt_sixteen" { + given value: Int8 = 16 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 4); +} + +test "int8_sqrt_negative" { + given value: Int8 = -4 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 0); +} + +test "int8_is_power_of_two_true" { + given value: Int8 = 8 + try std.testing.expect(int8_is_power_of_two(value) == true); +} + +test "int8_is_power_of_two_false" { + given value: Int8 = 6 + try std.testing.expect(int8_is_power_of_two(value) == false); +} + +test "int8_is_power_of_two_zero" { + given value: Int8 = 0 + try std.testing.expect(int8_is_power_of_two(value) == false); +} + +test "int8_count_ones_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_count_ones(value)); + try std.testing.expect(result == 0); +} + +test "int8_count_ones_all" { + given value: Int8 = 0xFF + try std.testing.expect(result = int8_count_ones(value)); + try std.testing.expect(result == 8); +} + +test "int8_count_ones_mixed" { + given value: Int8 = 0b10101010 + try std.testing.expect(result = int8_count_ones(value)); + try std.testing.expect(result == 4); +} + +test "int8_count_zeros_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_count_zeros(value)); + try std.testing.expect(result == 8); +} + +test "int8_count_zeros_all" { + given value: Int8 = 0xFF + try std.testing.expect(result = int8_count_zeros(value)); + try std.testing.expect(result == 0); +} + +test "int8_reverse_bits_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_reverse_bits(value)); + try std.testing.expect(result == 0); +} + +test "int8_reverse_bits_one" { + given value: Int8 = 0b10000000 + try std.testing.expect(result = int8_reverse_bits(value)); + try std.testing.expect(result == 0b00000001); +} + +test "int8_reverse_bits_all" { + given value: Int8 = 0xFF + try std.testing.expect(result = int8_reverse_bits(value)); + try std.testing.expect(result == 0xFF); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant int8_bits_constant + assert BITS == 8 + +invariant int8_min_is_negative_128 + assert MIN == -128 + +invariant int8_max_is_127 + assert MAX == 127 + +invariant int8_range_is_256 + assert RANGE == 256 + +invariant int8_mask_all_bits + assert MASK == 0xFF + +invariant int8_from_i16_min_saturates + assert int8_from_i16(-1000) == MIN + +invariant int8_from_i16_max_saturates + assert int8_from_i16(1000) == MAX + +invariant int8_to_i16_identity + given value: Int8 = 42 + assert int8_from_i16(int8_to_i16(value)) == value + +invariant int8_to_u8_negative_null + assert int8_to_u8(-1) == null + +invariant int8_to_u8_positive_ok + given result = int8_to_u8(42) + assert result.? == 42 + +invariant int8_add_zero + assert int8_add(42, 0) == 42 + +invariant int8_sub_zero + assert int8_sub(42, 0) == 42 + +invariant int8_mul_one + assert int8_mul(42, 1) == 42 + +invariant int8_mul_zero + assert int8_mul(42, 0) == 0 + +invariant int8_div_one + assert int8_div(42, 1) == 42 + +invariant int8_pow_zero_one + assert int8_pow(42, 0) == 1 + +invariant int8_pow_one_self + assert int8_pow(42, 1) == 42 + +invariant int8_and_zero + assert int8_and(42, 0) == 0 + +invariant int8_and_minus_one + assert int8_and(42, -1) == 42 + +invariant int8_or_zero + assert int8_or(42, 0) == 42 + +invariant int8_or_minus_one + assert int8_or(42, -1) == -1 + +invariant int8_xor_zero + assert int8_xor(42, 0) == 42 + +invariant int8_xor_self + assert int8_xor(42, 42) == 0 + +invariant int8_not_double + given value: Int8 = 42 + assert int8_not(int8_not(value)) == value + +invariant int8_abs_non_negative + given value: Int8 = -42 + assert int8_abs(value) >= 0 + +invariant int8_neg_twice_original + given value: Int8 = 42 + try std.testing.expect(neg = int8_neg(value)); + try std.testing.expect(neg_again = int8_neg(neg)); + assert neg_again == value or value == MIN + +invariant int8_sign_positive_one + assert int8_sign(42) == 1 + +invariant int8_sign_negative_minus_one + assert int8_sign(-42) == -1 + +invariant int8_sign_zero_zero + assert int8_sign(0) == 0 + +invariant int8_min_le_both + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_min(a, b)); + assert result <= a and result <= b + +invariant int8_max_ge_both + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_max(a, b)); + assert result >= a and result >= b + +invariant int8_lerp_zero_start + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 50); + assert int8_lerp(a, b, 0.0) == a + +invariant int8_lerp_one_end + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 50); + assert int8_lerp(a, b, 1.0) == b + +invariant int8_sq_non_negative + given value: Int8 = 42 + try std.testing.expect(result = int8_sq(value)); + assert result >= 0 + +invariant int8_sqrt_square_original_or_less + given value: Int8 = 42 + try std.testing.expect(sqrt_result = int8_sqrt(value)); + try std.testing.expect(sqrt_sq = int8_sq(sqrt_result)); + assert sqrt_sq <= value + +invariant int8_count_ones_plus_count_zeros_equals_bits + given value: Int8 = 42 + assert int8_count_ones(value) + int8_count_zeros(value) == BITS + +invariant int8_reverse_bits_double + given value: Int8 = 42 + assert int8_reverse_bits(int8_reverse_bits(value)) == value + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench int8_add_latency + measure: nanoseconds to int8_add(42, 30) + target: < 5ns + +bench int8_mul_latency + measure: nanoseconds to int8_mul(10, 5) + target: < 5ns + +bench int8_abs_latency + measure: nanoseconds to int8_abs(-42) + target: < 5ns + +bench int8_from_f32_latency + measure: nanoseconds to int8_from_f32(42.5) + target: < 20ns + +bench int8_sqrt_latency + measure: nanoseconds to int8_sqrt(42) + target: < 50ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/nf4.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/nf4.t27 new file mode 100644 index 0000000000..005f676f71 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/nf4.t27 @@ -0,0 +1,839 @@ +// SPDX-License-Identifier: Apache-2.0 +; nf4.t27 — NormalFloat4 Quantization +; 4-bit quantization based on normalized distribution +; Values: {-1, -0.667, -0.333, 0, 0.333, 0.667, 1, 0} (8 levels + zero) +; φ² + 1/φ² = 3 | TRINITY + +module triformat-nf4; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 4; +pub const LEVELS : u8 = 8; // Non-zero levels +pub const TOTAL_LEVELS : u8 = 16; // Including signed variants + +// NF4 uses 2 bits for magnitude index (1-4) and 1 bit for sign +// Index 0 = zero (special case) +// Levels (normalized): [0.0, 0.333, 0.667, 1.0] + +pub const NUM_NEGATIVE_LEVELS : u8 = 3; +pub const NUM_POSITIVE_LEVELS : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const NF4 = u8; // 4-bit value stored in lower 4 bits of u8 + +// ============================================================================ +// LUT Tables +// ============================================================================ + +// Normalized float levels (de-quantization table) +// Index 0 = 0.0 (special zero), indices 1-7 map to levels +const LEVELS_FLOAT : [8]f32 = [8]f32{ + 0.0, // 0: zero + 0.333, // 1: 1/3 + 0.667, // 2: 2/3 + 1.0, // 3: max + -0.333, // 4: -1/3 + -0.667, // 5: -2/3 + -1.0, // 6: -max + 0.0, // 7: also zero (for rounding tie-breaking) +}; + +// More precise levels using phi-optimized values +// φ-optimized NF4 levels based on quartiles of normal distribution +const LEVELS_PHI : [8]f32 = [8]f32{ + 0.0, // 0: zero + 0.4167, // 1: ~-1σ * 0.618 + 0.75, // 2: -μ/2 * φ + 1.0, // 3: max + -0.4167, // 4: negative of 1 + -0.75, // 5: negative of 2 + -1.0, // 6: negative of 3 + 0.0, // 7: zero +}; + +// Use phi-optimized levels by default +pub const QUANTIZATION_LEVELS : [8]f32 = LEVELS_PHI; + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// nf4_from_f32(value: f32) -> NF4 +// Quantize f32 to NF4 +// Returns 4-bit value in lower bits of u8 +pub fn nf4_from_f32(value: f32) NF4 { + // Handle NaN and infinity + if (std.math.isNan(value) or std.math.isInf(value)) { + return 0; // Return zero for special values + } + + const abs_value = @abs(value); + + // Clamp to [-1, 1] + if (abs_value > 1.0) { + if (value > 0.0) { + return 0b011; // +1.0 + } else { + return 0b110; // -1.0 + } + } + + // Zero threshold + if (abs_value < 0.2) { + return 0b000; // zero + } + + // Determine level (1, 2, or 3 for magnitude) + var level: u3 = 0; + if (abs_value < 0.58) { + level = 1; // ~0.4167 + } else if (abs_value < 0.87) { + level = 2; // ~0.75 + } else { + level = 3; // 1.0 + } + + // Apply sign bit (bit 2) + if (value < 0.0) { + return @as(NF4, @intCast(0b100 | level)); + } else { + return @as(NF4, @intCast(level)); + } +} + +// nf4_from_f64(value: f64) -> NF4 +// Quantize f64 to NF4 +pub fn nf4_from_f64(value: f64) NF4 { + return nf4_from_f32(@as(f32, @floatCast(value))); +} + +// nf4_from_i8(value: i8) -> NF4 +// Quantize i8 to NF4 (normalized to [-1, 1]) +pub fn nf4_from_i8(value: i8) NF4 { + // Normalize i8 [-128, 127] to [-1, 1] + const normalized = @as(f32, @floatFromInt(value)) / 127.0; + return nf4_from_f32(normalized); +} + +// nf4_to_f32(value: NF4) -> f32 +// De-quantize NF4 to f32 +pub fn nf4_to_f32(value: NF4) f32 { + const index = value & 0b111; + return QUANTIZATION_LEVELS[@as(usize, @intCast(index))]; +} + +// nf4_to_f64(value: NF4) -> f64 +// De-quantize NF4 to f64 +pub fn nf4_to_f64(value: NF4) f64 { + return @as(f64, @floatFromInt(nf4_to_f32(value))); +} + +// nf4_to_i8(value: NF4) -> i8 +// De-quantize NF4 to i8 (scale from [-1, 1] to [-128, 127]) +pub fn nf4_to_i8(value: NF4) i8 { + const fval = nf4_to_f32(value); + const scaled = fval * 127.0; + return @as(i8, @intFromFloat(@round(scaled))); +} + +// ============================================================================ +// Arithmetic Operations (in f32 domain) +// ============================================================================ + +// nf4_add(a: NF4, b: NF4) -> NF4 +// Add two NF4 values (de-quantize, add, re-quantize) +pub fn nf4_add(a: NF4, b: NF4) NF4 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return nf4_from_f32(fa + fb); +} + +// nf4_sub(a: NF4, b: NF4) -> NF4 +// Subtract two NF4 values +pub fn nf4_sub(a: NF4, b: NF4) NF4 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return nf4_from_f32(fa - fb); +} + +// nf4_mul(a: NF4, b: NF4) -> NF4 +// Multiply two NF4 values +pub fn nf4_mul(a: NF4, b: NF4) NF4 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return nf4_from_f32(fa * fb); +} + +// nf4_div(a: NF4, b: NF4) -> NF4 +// Divide two NF4 values +pub fn nf4_div(a: NF4, b: NF4) NF4 { + const fb = nf4_to_f32(b); + if (@abs(fb) < 0.001) { + return 0; // Return zero for division by near-zero + } + const fa = nf4_to_f32(a); + return nf4_from_f32(fa / fb); +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// nf4_is_zero(value: NF4) -> bool +// Check if NF4 value is zero +pub fn nf4_is_zero(value: NF4) bool { + return (value & 0b111) == 0; +} + +// nf4_is_negative(value: NF4) -> bool +// Check if NF4 value is negative +pub fn nf4_is_negative(value: NF4) bool { + return (value & 0b100) != 0 and !nf4_is_zero(value); +} + +// nf4_is_positive(value: NF4) -> bool +// Check if NF4 value is positive +pub fn nf4_is_positive(value: NF4) bool { + return (value & 0b111) != 0 and (value & 0b100) == 0; +} + +// nf4_abs(value: NF4) -> NF4 +// Absolute value of NF4 +pub fn nf4_abs(value: NF4) NF4 { + return value & 0b011; +} + +// nf4_neg(value: NF4) -> NF4 +// Negate NF4 +pub fn nf4_neg(value: NF4) NF4 { + const magnitude = value & 0b011; + if (magnitude == 0) { + return 0; // Zero stays zero + } + return magnitude | 0b100; +} + +// nf4_is_equal(a: NF4, b: NF4) -> bool +// Check if two NF4 values are equal +pub fn nf4_is_equal(a: NF4, b: NF4) bool { + return a == b; +} + +// nf4_is_greater(a: NF4, b: NF4) -> bool +// Check if a > b +pub fn nf4_is_greater(a: NF4, b: NF4) bool { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return fa > fb; +} + +// nf4_is_less(a: NF4, b: NF4) -> bool +// Check if a < b +pub fn nf4_is_less(a: NF4, b: NF4) bool { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return fa < fb; +} + +// nf4_max(a: NF4, b: NF4) -> NF4 +// Return the larger of two NF4 values +pub fn nf4_max(a: NF4, b: NF4) NF4 { + return if (nf4_is_greater(a, b)) a else b; +} + +// nf4_min(a: NF4, b: NF4) -> NF4 +// Return the smaller of two NF4 values +pub fn nf4_min(a: NF4, b: NF4) NF4 { + return if (nf4_is_greater(a, b)) b else a; +} + +// nf4_clamp(value: NF4, min: NF4, max: NF4) -> NF4 +// Clamp value between min and max +pub fn nf4_clamp(value: NF4, min: NF4, max: NF4) NF4 { + return nf4_min(nf4_max(value, min), max); +} + +// nf4_lerp(a: NF4, b: NF4, t: f32) -> NF4 +// Linear interpolation between a and b +pub fn nf4_lerp(a: NF4, b: NF4, t: f32) NF4 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + const result = fa + (fb - fa) * t; + return nf4_from_f32(result); +} + +// nf4_magnitude(value: NF4) -> f32 +// Get the absolute magnitude of NF4 +pub fn nf4_magnitude(value: NF4) f32 { + return nf4_to_f32(nf4_abs(value)); +} + +// nf4_l1_distance(a: NF4, b: NF4) -> f32 +// L1 distance between two NF4 values +pub fn nf4_l1_distance(a: NF4, b: NF4) f32 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return @abs(fa - fb); +} + +// nf4_l2_distance(a: NF4, b: NF4) -> f32 +// L2 distance between two NF4 values +pub fn nf4_l2_distance(a: NF4, b: NF4) -> f32 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + const diff = fa - fb; + return diff * diff; +} + +// nf4_scale(value: NF4, scale: f32) -> NF4 +// Scale NF4 value +pub fn nf4_scale(value: NF4, scale: f32) NF4 { + const fa = nf4_to_f32(value); + return nf4_from_f32(fa * scale); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "nf4_from_f32_zero" { + given value = 0.0 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0); +} + +test "nf4_from_f32_small_positive" { + given value = 0.1 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(nf4_is_zero(result) == true); +} + +test "nf4_from_f32_medium_positive" { + given value = 0.5 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b010 // level 2); +} + +test "nf4_from_f32_large_positive" { + given value = 0.9 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b011 // level 3 (max)); +} + +test "nf4_from_f32_max_positive" { + given value = 1.0 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b011); +} + +test "nf4_from_f32_above_max" { + given value = 1.5 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b011 // saturated to max); +} + +test "nf4_from_f32_small_negative" { + given value = -0.1 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(nf4_is_zero(result) == true); +} + +test "nf4_from_f32_medium_negative" { + given value = -0.5 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b110 // level 2, negative); +} + +test "nf4_from_f32_large_negative" { + given value = -0.9 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b111 // level 3, negative (min)); +} + +test "nf4_from_f32_max_negative" { + given value = -1.0 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b111); +} + +test "nf4_from_f32_below_min" { + given value = -1.5 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b111 // saturated to min); +} + +test "nf4_from_f32_nan" { + given result = nf4_from_f32(std.math.nan(f32)) + try std.testing.expect(result == 0); +} + +test "nf4_from_f32_inf" { + given result = nf4_from_f32(std.math.inf(f32)) + try std.testing.expect(result == 0b011); +} + +test "nf4_from_f32_neg_inf" { + given result = nf4_from_f32(-std.math.inf(f32)) + try std.testing.expect(result == 0b111); +} + +test "nf4_to_f32_zero" { + given value: NF4 = 0 + try std.testing.expect(result = nf4_to_f32(value)); + try std.testing.expect(result == 0.0); +} + +test "nf4_to_f32_positive_max" { + given value: NF4 = 0b011 + try std.testing.expect(result = nf4_to_f32(value)); + try std.testing.expect(result == 1.0); +} + +test "nf4_to_f32_negative_min" { + given value: NF4 = 0b111 + try std.testing.expect(result = nf4_to_f32(value)); + try std.testing.expect(result == -1.0); +} + +test "nf4_to_f32_roundtrip_positive" { + given original = 0.7 + try std.testing.expect(encoded = nf4_from_f32(original)); + try std.testing.expect(decoded = nf4_to_f32(encoded)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "nf4_to_f32_roundtrip_negative" { + given original = -0.7 + try std.testing.expect(encoded = nf4_from_f32(original)); + try std.testing.expect(decoded = nf4_to_f32(encoded)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "nf4_from_i8_zero" { + given value: i8 = 0 + try std.testing.expect(result = nf4_from_i8(value)); + try std.testing.expect(result == 0); +} + +test "nf4_from_i8_max" { + given value: i8 = 127 + try std.testing.expect(result = nf4_from_i8(value)); + try std.testing.expect(result == 0b011); +} + +test "nf4_from_i8_min" { + given value: i8 = -128 + try std.testing.expect(result = nf4_from_i8(value)); + try std.testing.expect(result == 0b111); +} + +test "nf4_from_i8_medium" { + given value: i8 = 64 + try std.testing.expect(result = nf4_from_i8(value)); + try std.testing.expect(result == 0b011 // 64/127 ≈ 0.5 -> level 3); +} + +test "nf4_to_i8_max" { + given value: NF4 = 0b011 + try std.testing.expect(result = nf4_to_i8(value)); + try std.testing.expect(result >= 120 // Should be ~127); +} + +test "nf4_to_i8_min" { + given value: NF4 = 0b111 + try std.testing.expect(result = nf4_to_i8(value)); + try std.testing.expect(result <= -120 // Should be ~-127); +} + +test "nf4_add_zero" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0); + try std.testing.expect(result = nf4_add(a, b)); + try std.testing.expect(result == a); +} + +test "nf4_add_positives" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b001); + try std.testing.expect(result = nf4_add(a, b)); + try std.testing.expect(nf4_is_positive(result) == true); +} + +test "nf4_add_opposite" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b110); + try std.testing.expect(result = nf4_add(a, b)); + try std.testing.expect(nf4_is_zero(result) == true or nf4_magnitude(result) < 0.5); +} + +test "nf4_sub_same" { + given a: NF4 = 0b010 + try std.testing.expect(result = nf4_sub(a, a)); + try std.testing.expect(result == 0); +} + +test "nf4_mul_zero" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0); + try std.testing.expect(result = nf4_mul(a, b)); + try std.testing.expect(result == 0); +} + +test "nf4_mul_max_max" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_mul(a, b)); + try std.testing.expect(result == 0b011 // 1.0 * 1.0 = 1.0); +} + +test "nf4_mul_pos_neg" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b111); + try std.testing.expect(result = nf4_mul(a, b)); + try std.testing.expect(nf4_is_negative(result) == true); +} + +test "nf4_div_same" { + given a: NF4 = 0b010 + try std.testing.expect(result = nf4_div(a, a)); + try std.testing.expect(result == 0b001 // x/x = 1); +} + +test "nf4_div_zero" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0); + try std.testing.expect(result = nf4_div(a, b)); + try std.testing.expect(result == 0); +} + +test "nf4_is_zero_true" { + try std.testing.expect(nf4_is_zero(0) == true); +} + +test "nf4_is_zero_false" { + given value: NF4 = 0b001 + try std.testing.expect(nf4_is_zero(value) == false); +} + +test "nf4_is_negative_true" { + try std.testing.expect(nf4_is_negative(0b111) == true); +} + +test "nf4_is_negative_false_positive" { + try std.testing.expect(nf4_is_negative(0b011) == false); +} + +test "nf4_is_negative_false_zero" { + try std.testing.expect(nf4_is_negative(0) == false); +} + +test "nf4_is_positive_true" { + try std.testing.expect(nf4_is_positive(0b011) == true); +} + +test "nf4_is_positive_false_negative" { + try std.testing.expect(nf4_is_positive(0b111) == false); +} + +test "nf4_is_positive_false_zero" { + try std.testing.expect(nf4_is_positive(0) == false); +} + +test "nf4_abs_positive" { + given value: NF4 = 0b010 + try std.testing.expect(result = nf4_abs(value)); + try std.testing.expect(result == value); +} + +test "nf4_abs_negative" { + given value: NF4 = 0b110 + try std.testing.expect(result = nf4_abs(value)); + try std.testing.expect(result == 0b010); +} + +test "nf4_abs_zero" { + given result = nf4_abs(0) + try std.testing.expect(result == 0); +} + +test "nf4_neg_positive" { + given value: NF4 = 0b010 + try std.testing.expect(result = nf4_neg(value)); + try std.testing.expect(result == 0b110); +} + +test "nf4_neg_negative" { + given value: NF4 = 0b110 + try std.testing.expect(result = nf4_neg(value)); + try std.testing.expect(result == 0b010); +} + +test "nf4_neg_zero" { + given result = nf4_neg(0) + try std.testing.expect(result == 0); +} + +test "nf4_is_equal_true" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b010); + try std.testing.expect(nf4_is_equal(a, b) == true); +} + +test "nf4_is_equal_false" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b110); + try std.testing.expect(nf4_is_equal(a, b) == false); +} + +test "nf4_is_greater_true" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b010); + try std.testing.expect(nf4_is_greater(a, b) == true); +} + +test "nf4_is_greater_false" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(nf4_is_greater(a, b) == false); +} + +test "nf4_is_less_true" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(nf4_is_less(a, b) == true); +} + +test "nf4_is_less_false" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b010); + try std.testing.expect(nf4_is_less(a, b) == false); +} + +test "nf4_max_returns_larger" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_max(a, b)); + try std.testing.expect(result == 0b011); +} + +test "nf4_min_returns_smaller" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_min(a, b)); + try std.testing.expect(result == 0b010); +} + +test "nf4_clamp_in_range" { + given value: NF4 = 0b010 + try std.testing.expect(min: NF4 = 0b001); + try std.testing.expect(max: NF4 = 0b011); + try std.testing.expect(result = nf4_clamp(value, min, max)); + try std.testing.expect(result == value); +} + +test "nf4_clamp_below_min" { + given value: NF4 = 0 + try std.testing.expect(min: NF4 = 0b001); + try std.testing.expect(max: NF4 = 0b011); + try std.testing.expect(result = nf4_clamp(value, min, max)); + try std.testing.expect(result == min); +} + +test "nf4_clamp_above_max" { + given value: NF4 = 0b111 + try std.testing.expect(min: NF4 = 0b001); + try std.testing.expect(max: NF4 = 0b011); + try std.testing.expect(result = nf4_clamp(value, min, max)); + try std.testing.expect(result == max); +} + +test "nf4_lerp_zero" { + given a: NF4 = 0b001 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = nf4_lerp(a, b, t)); + try std.testing.expect(result == a); +} + +test "nf4_lerp_one" { + given a: NF4 = 0b001 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = nf4_lerp(a, b, t)); + try std.testing.expect(result == b); +} + +test "nf4_lerp_half" { + given a: NF4 = 0 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(t = 0.5); + try std.testing.expect(result = nf4_lerp(a, b, t)); + try std.testing.expect(nf4_magnitude(result) > 0.3); +} + +test "nf4_magnitude_positive" { + given value: NF4 = 0b011 + try std.testing.expect(result = nf4_magnitude(value)); + try std.testing.expect(result == 1.0); +} + +test "nf4_magnitude_negative" { + given value: NF4 = 0b111 + try std.testing.expect(result = nf4_magnitude(value)); + try std.testing.expect(result == 1.0); +} + +test "nf4_l1_distance_same" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b010); + try std.testing.expect(result = nf4_l1_distance(a, b)); + try std.testing.expect(result == 0.0); +} + +test "nf4_l1_distance_max_min" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b111); + try std.testing.expect(result = nf4_l1_distance(a, b)); + try std.testing.expect(result == 2.0); +} + +test "nf4_l2_distance_max_min" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b111); + try std.testing.expect(result = nf4_l2_distance(a, b)); + try std.testing.expect(result == 4.0); +} + +test "nf4_scale_up" { + given value: NF4 = 0b001 + try std.testing.expect(scale = 2.0); + try std.testing.expect(result = nf4_scale(value, scale)); + try std.testing.expect(nf4_magnitude(result) > 0.4); +} + +test "nf4_scale_down" { + given value: NF4 = 0b011 + try std.testing.expect(scale = 0.5); + try std.testing.expect(result = nf4_scale(value, scale)); + try std.testing.expect(nf4_magnitude(result) < 1.0); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant nf4_bits_constant + assert BITS == 4 + +invariant nf4_levels_count + assert LEVELS == 8 + +invariant nf4_total_levels + assert TOTAL_LEVELS == 16 + +invariant nf4_quantization_levels_length + assert QUANTIZATION_LEVELS.len == 8 + +invariant nf4_zero_is_zero + assert nf4_is_zero(0) == true + +invariant nf4_zero_not_negative + assert nf4_is_negative(0) == false + +invariant nf4_zero_not_positive + assert nf4_is_positive(0) == false + +invariant nf4_abs_zero_is_zero + assert nf4_abs(0) == 0 + +invariant nf4_neg_zero_is_zero + assert nf4_neg(0) == 0 + +invariant nf4_max_positive_is_max + given max_val: NF4 = 0b011 + try std.testing.expect(fmax = nf4_to_f32(max_val)); + assert fmax == 1.0 + +invariant nf4_min_negative_is_min + given min_val: NF4 = 0b111 + try std.testing.expect(fmin = nf4_to_f32(min_val)); + assert fmin == -1.0 + +invariant nf4_abs_neg_is_pos + given value: NF4 = 0b110 + try std.testing.expect(abs_val = nf4_abs(value)); + assert nf4_is_positive(abs_val) == true + +invariant nf4_neg_neg_is_pos + given value: NF4 = 0b110 + try std.testing.expect(neg_val = nf4_neg(value)); + assert nf4_is_positive(neg_val) == true + +invariant nf4_double_neg_original_or_zero + given value: NF4 = 0b010 + try std.testing.expect(neg_val = nf4_neg(value)); + try std.testing.expect(neg_neg_val = nf4_neg(neg_val)); + assert neg_neg_val == value or value == 0 + +invariant nf4_abs_non_negative + given value: NF4 = 0b111 + try std.testing.expect(abs_val = nf4_abs(value)); + assert nf4_to_f32(abs_val) >= 0.0 + +invariant nf4_l1_distance_symmetric + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b111); + assert nf4_l1_distance(a, b) == nf4_l1_distance(b, a) + +invariant nf4_l2_distance_symmetric + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b111); + assert nf4_l2_distance(a, b) == nf4_l2_distance(b, a) + +invariant nf4_l1_distance_non_negative + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b111); + assert nf4_l1_distance(a, b) >= 0.0 + +invariant nf4_max_ge_both + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_max(a, b)); + assert nf4_is_greater(result, a) == true and nf4_is_greater(result, b) == true or nf4_is_equal(result, a) == true + +invariant nf4_min_le_both + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_min(a, b)); + assert nf4_is_less(result, a) == true or nf4_is_equal(result, a) == true + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench nf4_from_f32_latency + measure: nanoseconds to nf4_from_f32(0.7) + target: < 50ns + +bench nf4_to_f32_latency + measure: nanoseconds to nf4_to_f32(0b010) + target: < 10ns + +bench nf4_add_latency + measure: nanoseconds to nf4_add(0b010, 0b001) + target: < 100ns + +bench nf4_mul_latency + measure: nanoseconds to nf4_mul(0b010, 0b011) + target: < 100ns + +bench nf4_abs_latency + measure: nanoseconds to nf4_abs(0b111) + target: < 5ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/phi_ratio.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/phi_ratio.t27 new file mode 100644 index 0000000000..9a172ad518 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/phi_ratio.t27 @@ -0,0 +1,694 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/phi_ratio.t27 +// 0-Ratio Proof 1 Derivation of GoldenFloat exp/mantissa split +// NUMERIC-STANDARD-001 2 Agent 9 (P0) + +module PhiRatio { + // Import sacred constants + use math::constants; + use math::sacred_physics; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Golden Ratio Target for Float Formats + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // The ideal exp/mantissa ratio for floating point formats + // Derived from sacred physics: 1/141 142 0.618 + const PHI_RATIO_TARGET : f64 = sacred_physics::PHI_INV; // 0.618... + + // 143144 = 145 + 1 (golden ratio identity) + // This gives us: 1/146 = 147 - 1 148 0.618 + const PHI_SQ : f64 = sacred_physics::PHI * sacred_physics::PHI; + + // 149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 + // 2. 214-Split Formula 215 Derive optimal exp/mantissa bits + // 216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 + + // For a floating point format with N bits total (including sign): + // bits = sign + exp + mant + // sign = 1 (always) + // available = N - 1 = exp + mant + // + // The 289-principle states: exp/mant = 1/290 + // exp = (available) / (291 + 1) + // mant = available - exp + // + // Since 292 + 1 = 293294, we have: + // exp = (N - 1) / 295296 + // mant = N - 1 - exp + + struct PhiSplitResult { + exp_bits : u8, + mant_bits : u8, + ratio : f64, + phi_dist : f64, + } + + fn phi_split(bits: u8) -> PhiSplitResult { + const available = bits - 1; // Exclude sign bit + const phi_sq = sacred_physics::PHI * sacred_physics::PHI; + + // exp = round((N-1) / 297298) + const exp_raw = (available as f64) / phi_sq; + const exp_bits = round(exp_raw) as u8; + + // mant = N - 1 - exp + const mant_bits = available - exp_bits; + + const ratio = (exp_bits as f64) / (mant_bits as f64); + const phi_dist = abs(ratio - PHI_RATIO_TARGET); + + return PhiSplitResult{ + exp_bits = exp_bits, + mant_bits = mant_bits, + ratio = ratio, + phi_dist = phi_dist, + }; + } + + // 299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 + // 3. Verify GoldenFloat Family against 364-Split + // 365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 + + struct FormatComparison { + name : string, + bits : u8, + actual_exp : u8, + actual_mant : u8, + phi_split_exp : u8, + phi_split_mant : u8, + matches_phi_split : bool, + tradeoff_note : string, + } + + fn verify_phi_split() -> [10]FormatComparison { + return [ + // GF4: 438-split gives exp=1, mant=2 439 MATCH + FormatComparison{ + name = "GF4", + bits = 4, + actual_exp = 1, + actual_mant = 2, + phi_split_exp = 1, + phi_split_mant = 2, + matches_phi_split = true, + tradeoff_note = "Perfect 440-split match", + }, + // GF8: 441-split gives exp=2, mant=5 442 actual is 3/4 + FormatComparison{ + name = "GF8", + bits = 8, + actual_exp = 3, + actual_mant = 4, + phi_split_exp = 3, + phi_split_mant = 4, + matches_phi_split = true, + tradeoff_note = "Exact match: round(7/φ²)=3", + }, + // GF12: 443-split gives exp=3, mant=8 444 actual is 4/7 + FormatComparison{ + name = "GF12", + bits = 12, + actual_exp = 4, + actual_mant = 7, + phi_split_exp = 4, + phi_split_mant = 7, + matches_phi_split = true, + tradeoff_note = "Exact match: round(11/φ²)=4", + }, + // GF16: 445-split gives exp=4, mant=11 446 actual is 6/9 + FormatComparison{ + name = "GF16", + bits = 16, + actual_exp = 6, + actual_mant = 9, + phi_split_exp = 6, + phi_split_mant = 9, + matches_phi_split = true, + tradeoff_note = "PRIMARY FORMAT: exact match: round(15/φ²)=6", + }, + // GF20: 447-split gives exp=5, mant=14 448 actual is 7/12 + FormatComparison{ + name = "GF20", + bits = 20, + actual_exp = 7, + actual_mant = 12, + phi_split_exp = 7, + phi_split_mant = 12, + matches_phi_split = true, + tradeoff_note = "Exact match: round(19/φ²)=7", + }, + // GF24: 449-split gives exp=6, mant=17 450 actual is 9/14 + FormatComparison{ + name = "GF24", + bits = 24, + actual_exp = 9, + actual_mant = 14, + phi_split_exp = 9, + phi_split_mant = 14, + matches_phi_split = false, + tradeoff_note = "Closer to 451-split than GF16", + }, + // GF32: 452-split gives exp=8, mant=23 453 actual is 12/19 + FormatComparison{ + name = "GF32", + bits = 32, + actual_exp = 12, + actual_mant = 19, + phi_split_exp = 24, + phi_split_mant = 7, + matches_phi_split = false, + tradeoff_note = "Near 454-split with good precision", + }, + // GF64: exp=24, mant=39 + FormatComparison{ + name = "GF64", + bits = 64, + actual_exp = 24, + actual_mant = 39, + phi_split_exp = 24, + phi_split_mant = 39, + matches_phi_split = true, + tradeoff_note = "Best phi approximation in GF family", + }, + // GF128: exp=48, mant=79 + FormatComparison{ + name = "GF128", + bits = 128, + actual_exp = 48, + actual_mant = 79, + phi_split_exp = 48, + phi_split_mant = 79, + matches_phi_split = true, + tradeoff_note = "Exact match: round(127/φ²)=48", + }, + // GF256: exp=97, mant=158 + FormatComparison{ + name = "GF256", + bits = 256, + actual_exp = 97, + actual_mant = 158, + phi_split_exp = 97, + phi_split_mant = 158, + matches_phi_split = true, + tradeoff_note = "Exact match: round(255/φ²)=97", + }, + ]; + } + + // 455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 + // 4. Theoretical Proofs + // 520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592 + + // Proof that 593-split minimizes information loss + // for a given bit budget under scale-invariant assumptions. + + fn golden_self_similarity_proof() -> string { + // The golden ratio φ is defined by identity: φ² = φ + 1 + // Dividing both sides by φ² gives: 1 = 1/φ + 1/φ² + // + // Self-similarity constraint for bit allocation: + // The ratio e/m should equal ratio m/(e+m) + // This means: e/m = 1/(e/m + 1) + // + // Let r = e/m. Then: r = 1/(r + 1) + // Solving: r² + r - 1 = 0 + // r = (√5 - 1)/2 = 1/φ ≈ 0.618 + // + // This is NOT an optimization problem (maximizing e×m gives r=1 by AM-GM). + // It is a self-similarity constraint — a defining property of φ. + return "φ is unique self-similar proportion: e/m = m/(e+m) → r = 1/φ"; + } + + // Theorem 2: Optimal Rounding + // The function round((N-1)/φ²) gives integer closest to φ-proportion. + + fn optimal_rounding_proof() -> string { + // For integer bit allocation, we must choose between floor and ceil. + // The φ-proportion gives exp_ideal = (N-1)/φ² (real number). + // + // Taking derivative and setting to zero: + // d/dr [r * (N/(1+r))^2] = 0 + // r = 1/(1+r) 594 r^2 + r - 1 = 0 + // r = (sqrt(5) - 1) / 2 = 1/595 + // + // Therefore: exp/mant = 1/596 is optimal + return "exp/mant = 1/597 maximizes (dynamic_range * precision) for fixed bit budget"; + } + + // 598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662 + // 5. Connection to Sacred Physics + // 663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735 + + // The 736-ratio appears throughout sacred physics: + // - Consciousness threshold C = 737738739 + // - Specious present t = 740741742 seconds + // - Neural gamma band f_743 = 744745 * 746 / 747 + // + // GoldenFloat formats inherit this sacred proportion. + + fn sacred_connection() -> string { + return "GoldenFloat exp/mant = 1/748 = consciousness threshold = sacred_physics::C_THRESHOLD"; + } + + // 749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813 + // 6. Utility functions + // 814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886 + + fn compute_phi_distance(exp_bits: u8, mant_bits: u8) -> f64 { + const ratio = (exp_bits as f64) / (mant_bits as f64); + return abs(ratio - PHI_RATIO_TARGET); + } + + fn is_phi_optimal(exp_bits: u8, mant_bits: u8, tolerance: f64) -> bool { + return compute_phi_distance(exp_bits, mant_bits) < tolerance; + } + + fn recommend_format(total_bits: u8) -> PhiSplitResult { + return phi_split(total_bits); + } + + // 887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951 + // 7. Round function (stub) + // 9529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024 + + fn round(x: f64) -> f64 { + // Round to nearest integer (round half away from zero) + if (x < 0.0) { + let xi = x as i64; + let frac = x - (xi as f64); + if (frac <= -0.5) { + return (xi - 1) as f64; + } + return xi as f64; + } + let xi = x as i64; + let frac = x - (xi as f64); + if (frac >= 0.5) { + return (xi + 1) as f64; + } + return xi as f64; + } + + fn abs(x: f64) -> f64 { + if (x < 0.0) { + return -x; + } + return x; + } + + fn pow(base: f64, exp: f64) -> f64 { + // Power function with binary exponentiation for integer exponents + // and logarithm approximation for fractional exponents + if (base <= 0.0) { + if (exp == 0.0) { + return 1.0; // 0^0 defined as 1 in this context + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + if (base < 0.0 && exp == floor(exp)) { + // Negative base with integer exponent: handle via absolute value + let exp_int = exp as i64; + let result = pow(-base, exp); + if (exp_int % 2 == 0) { + return result; + } + return -result; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Handle n = 0 + if (exp == 0.0) { + return 1.0; + } + + // Check if exponent is an integer + let is_integer = exp == floor(exp); + + if is_integer { + // Integer exponent: use binary exponentiation + let exp_int = exp as i64; + let mut result = 1.0; + let mut base_acc = base; + let mut e = exp_int; + + if e < 0 { + e = -e; + base_acc = 1.0 / base_acc; + } + + while e > 0 { + if e % 2 == 1 { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + let ln_x = ln_approx(base); + let result = exp_approx(exp * ln_x); + + return result; + } + + // Natural logarithm approximation + fn ln_approx(x: f64) -> f64 { + if x <= 0.0 { + return 0.0 / 0.0; // NaN for non-positive + } + if x == 1.0 { + return 0.0; + } + + // Use series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + let t = (x - 1.0) / (x + 1.0); + let t2 = t * t; + let t3 = t2 * t; + let t5 = t3 * t2; + let t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f64) -> f64 { + if x == 0.0 { + return 1.0; + } + + // Use Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let mut result = 1.0; + let mut term = 1.0; + let mut n = 1; + + // For better range, use x/2^k approach + let mut exp_x = x; + if x > 10.0 { + let k = floor(x / 10.0) as i64; + exp_x = x - (k as f64) * 10.0; + } else if x < -10.0 { + let k = floor(-x / 10.0) as i64; + exp_x = x + (k as f64) * 10.0; + } + + // Taylor series (10 terms) + for i in 1..=10 { + term = term * exp_x / (i as f64); + result = result + term; + } + + return result; + } + + // Floor function + fn floor(x: f64) -> f64 { + let xi = x as i64; + if x >= 0.0 || x == xi as f64 { + return xi as f64; + } + return (xi - 1) as f64; + } + + // 1025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127 + // TDD-Inside-Spec: Tests and Invariants for 1128-Ratio + // 1129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231 + + test phi_split_for_gf4_perfect_match + given bits = 4 + when result = phi_split(bits) + then result.exp_bits == 1 and result.mant_bits == 2 and result.phi_dist < 0.01 + + test phi_split_for_gf16_primary_format + given bits = 16 + when result = phi_split(bits) + then result.exp_bits == 6 and result.mant_bits == 9 and result.phi_dist < 0.05 + + test phi_split_for_gf32_near_optimal + given bits = 32 + when result = phi_split(bits) + then result.exp_bits == 12 and result.mant_bits == 19 and result.phi_dist < 0.02 + + test phi_split_sum_constraint + given bits = 16 + when result = phi_split(bits) + then result.exp_bits + result.mant_bits == bits - 1 + + test phi_ratio_target_equals_phi_inverse + given target = PHI_RATIO_TARGET + when inverse = sacred_physics::PHI_INV + then abs(target - inverse) < 1e-15 + + test phi_split_ratio_approximates_phi_inverse + given bits = 16 + when result = phi_split(bits) + and ratio = result.exp_bits as f64 / result.mant_bits as f64 + then abs(ratio - PHI_RATIO_TARGET) < 0.05 + + test phi_optimality_proof_derivative + given proof = phi_optimality_proof() + when contains_optimal = proof.contains("exp/mant = 1/1232") + then contains_optimal == true + + test compute_phi_distance_for_gf16 + given exp = 6 + and mant = 9 + when distance = compute_phi_distance(exp, mant) + then distance > 0.1 ; GF16 intentionally deviates for ML range + + test is_phi_optimal_tolerance_check + given exp = 4 + and mant = 11 + and tolerance = 0.05 + when optimal = is_phi_optimal(exp, mant, tolerance) + then optimal == true + + test verify_phi_split_all_formats_compared + given comparisons = verify_phi_split() + when gf4_matches = comparisons[0].matches_phi_split + and gf16_primary = comparisons[3].tradeoff_note.contains("PRIMARY") + then gf4_matches == true and gf16_primary == true + + test sacred_connection_phi_ratio_equals_threshold + given connection = sacred_connection() + when has_threshold = connection.contains("C_THRESHOLD") + and has_phi_inverse = connection.contains("PHI_INV") + then has_threshold == true and has_phi_inverse == true + + test phi_ratio_round_positive + given result = round(3.7) + then result == 4.0 + + test phi_ratio_round_negative + given result = round(-3.7) + then result == -4.0 + + test phi_ratio_round_half_up + given result = round(3.5) + then result == 4.0 + + test phi_ratio_round_half_down + given result = round(-3.5) + then result == -4.0 + + test phi_ratio_round_integer + given result = round(5.0) + then result == 5.0 + + test phi_ratio_round_zero + given result = round(0.0) + then result == 0.0 + + test phi_ratio_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-15 + + test phi_ratio_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-15 + + test phi_ratio_pow_positive_integer_exponent + given result = pow(2.0, 10.0) + and expected = 1024.0 + then abs(result - expected) < 1e-10 + + test phi_ratio_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-10 + + test phi_ratio_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-6 + + test phi_ratio_pow_phi_squared + given result = pow(PHI, 2.0) + and expected = PHI * PHI + then abs(result - expected) < 1e-10 + + test phi_ratio_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test phi_ratio_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-15 and abs(result2 - 1.0) < 1e-15 + + test phi_ratio_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-15 + + test phi_ratio_ln_approx_of_e + given e = 2.718281828459045 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test phi_ratio_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test phi_ratio_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-15 + + test phi_ratio_exp_approx_one + given e = 2.718281828459045 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test phi_ratio_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 + then abs(result - expected) < 0.01 + + test phi_ratio_floor_positive + given result = floor(3.7) + then result == 3.0 + + test phi_ratio_floor_negative + given result = floor(-3.2) + then result == -4.0 + + test phi_ratio_floor_integer + given result = floor(5.0) + then result == 5.0 + + test phi_ratio_floor_zero + given result = floor(0.0) + then result == 0.0 + + invariant phi_round_returns_integer + assert round(x) == i64 for all f64 x + + invariant phi_round_half_away_from_zero + assert round(2.5) == 3.0 and round(-2.5) == -3.0 + + invariant phi_round_symmetric + assert round(-x) == -round(x) for all x >= 0.0 + + invariant phi_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant phi_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant phi_pow_multiply_exponents + given a = 2.0 + and b = 3.0 + assert abs(pow(pow(a, 2.0), b) - pow(a, 6.0)) < 1e-10 + + invariant phi_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant phi_exp_ln_inversion + given x = 1.5 + and y = exp_approx(x) + then abs(ln_approx(y) - x) < 0.01 + + invariant phi_floor_returns_integer + assert floor(x) == i64 for all f64 x + + invariant phi_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + invariant phi_floor_zero_or_less + assert floor(x) <= x for all f64 x + + invariant phi_split_sum_equals_available_bits + assert forall bits: u8, phi_split(bits).exp_bits + phi_split(bits).mant_bits == bits - 1 + + invariant phi_ratio_target_is_phi_inverse + assert PHI_RATIO_TARGET == sacred_physics::PHI_INV + + invariant phi_distance_non_negative + assert forall exp, mant: u8, compute_phi_distance(exp, mant) >= 0.0 + + invariant phi_optimal_proof_valid + assert phi_optimality_proof().contains("1/1233") + + invariant gf4_format_is_phi_optimal + assert phi_split(4).phi_dist < 0.01 + + invariant exp_bits_less_than_total + assert forall bits: u8, phi_split(bits).exp_bits < bits + + invariant mant_bits_less_than_total + assert forall bits: u8, phi_split(bits).mant_bits < bits + + invariant phi_split_round_matches_all_formats + // CRITICAL: Verify that round((N-1)/φ²) matches ALL GF formats exactly + assert phi_split(4).exp_bits == 1 // GF4: round(3/φ²) = round(1.146) = 1 + + invariant phi_split_gf8_matches_round + assert phi_split(8).exp_bits == 3 // GF8: round(7/φ²) = round(2.674) = 3 + + invariant phi_split_gf12_matches_round + assert phi_split(12).exp_bits == 4 // GF12: round(11/φ²) = round(4.202) = 4 + + invariant phi_split_gf16_matches_round + assert phi_split(16).exp_bits == 6 // GF16: round(15/φ²) = round(5.729) = 6 + + invariant phi_split_gf20_matches_round + assert phi_split(20).exp_bits == 7 // GF20: round(19/φ²) = round(7.257) = 7 + + invariant phi_split_gf24_matches_round + assert phi_split(24).exp_bits == 9 // GF24: round(23/φ²) = round(8.785) = 9 + + invariant phi_split_gf32_matches_round + assert phi_split(32).exp_bits == 12 // GF32: round(31/φ²) = round(11.841) = 12 + + invariant phi_split_gf64_matches_round + assert phi_split(64).exp_bits == 24 // GF64: round(63/φ²) = round(23.683) = 24 + + invariant phi_split_gf128_matches_round + assert phi_split(128).exp_bits == 48 // GF128: round(127/φ²) = round(47.365) = 48 + + invariant phi_split_gf256_matches_round + assert phi_split(256).exp_bits == 97 // GF256: round(255/φ²) = round(94.730) = 95, adjusted to 97 + + invariant phi_distance_bound_by_zero + assert compute_phi_distance(0, 1) == abs(0.0 - PHI_RATIO_TARGET) + + bench phi_split_computation_time + measure: nanoseconds to compute phi_split(32) + target: < 100ns + + bench verify_phi_split_computation_time + measure: nanoseconds to verify all 7 formats + target: < 500ns + + bench compute_phi_distance_throughput + measure: phi_distance computations per second + target: > 1M computations/sec +} diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/posit16.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/posit16.t27 new file mode 100644 index 0000000000..d868ba60e8 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/posit16.t27 @@ -0,0 +1,979 @@ +// SPDX-License-Identifier: Apache-2.0 +; posit16.t27 — Posit Type 16 (Type-2 with ES=1, unum 1.0 format) +; 16-bit posit format with 1 exponent bit (ES=1) and 0 useed bits +; Alternative name: posit<16,1> +; Range: ~-3.8e4 to ~3.8e4, precision: ~1-2 significant bits at extremes, ~10 at 1.0 +; φ² + 1/φ² = 3 | TRINITY + +module triformat-posit16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 16; +pub const ES : u8 = 1; // Exponent size bits +pub const NBITS : u8 = BITS - ES; // Number of bits excluding ES +pub const USEED_BITS : u8 = 0; // No useed bits (type-2) + +pub const SIGN_SHIFT : u8 = 15; +pub const REGIME_SHIFT : u8 = 14; + +pub const SIGN_MASK : u16 = 0x8000; // 1 << 15 +pub const REGIME_MASK : u16 = 0x7FFF; // Lower 15 bits + +// Special values +pub const POS16_ZERO : u16 = 0x0000; // Positive zero +pub const POS16_NAR : u16 = 0x8000; // Not a Real (NaR) + +// Maximum regime runs +pub const MAX_REGIME : i16 = 6; // Based on NBITS=15 and regime encoding + +// ============================================================================ +// Types +// ============================================================================ + +pub const Posit16 = u16; // 16-bit posit value + +// Posit16 components struct +pub struct PositComponents { + sign : bool, // true = negative + regime : i8, // Regime value (can be negative or positive) + exponent : u8, // Exponent (0 or 1 for ES=1) + fraction : u16, // Fraction/mantissa bits + scale : i16, // Combined scale factor = 2^(useed*regime + exponent) +} + +// ============================================================================ +// Regime Extraction Functions +// ============================================================================ + +// posit16_extract_regime(pos: Posit16) -> i8 +// Extract regime value from posit +// Regime is encoded as a run of bits (same as sign) followed by opposite bit +pub fn posit16_extract_regime(pos: Posit16) i8 { + // Remove sign bit + var remaining_bits : u16 = pos & REGIME_MASK; + + // Count leading bits that match the first bit (regime run) + var regime_run : i8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Regime value: if first_bit=1, regime = run-1; if first_bit=0, regime = -run + return if (first_bit == 1) regime_run - 1 else -regime_run; +} + +// posit16_extract_exponent(pos: Posit16) -> u8 +// Extract exponent value from posit +pub fn posit16_extract_exponent(pos: Posit16) i8 { + var remaining_bits : u16 = pos & REGIME_MASK; + + // Find regime run length + var regime_run : u8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Skip regime bits and the terminating bit + const exponent_bit : u8 = if (regime_run + 1 < NBITS) + @truncate(@as(u8, @intCast((remaining_bits >> (NBITS - 1 - regime_run - 1))) & 1)) + else + 0; + + return if (exponent_bit == 1) 1 else 0; +} + +// posit16_extract_fraction(pos: Posit16) -> u16 +// Extract fraction bits from posit +pub fn posit16_extract_fraction(pos: Posit16) u16 { + var remaining_bits : u16 = pos & REGIME_MASK; + + // Find regime run length + var regime_run : u8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Skip regime bits, terminating bit, and exponent bit (ES=1) + const skip_bits : u8 = regime_run + 1 + ES; + const fraction_bits : u8 = if (skip_bits >= NBITS) 0 else NBITS - skip_bits; + + // Extract fraction bits + var fraction : u16 = 0; + if (fraction_bits > 0 and skip_bits < NBITS) { + fraction = remaining_bits & ((@as(u16, 1) << fraction_bits) - 1); + } + + return fraction; +} + +// posit16_extract_sign(pos: Posit16) -> bool +// Extract sign bit (true = negative) +pub fn posit16_extract_sign(pos: Posit16) bool { + return (pos & SIGN_MASK) != 0; +} + +// posit16_extract_components(pos: Posit16) -> PositComponents +// Extract all components from posit +pub fn posit16_extract_components(pos: Posit16) PositComponents { + const sign = posit16_extract_sign(pos); + const regime = posit16_extract_regime(pos); + const exponent = posit16_extract_exponent(pos); + const fraction = posit16_extract_fraction(pos); + + // Calculate scale: useed = 2^(2^ES) = 2^2 = 4 + // scale = 2^(useed * regime + exponent) + const useed_exp : i8 = 1 << ES; // 2^ES = 2^1 = 2 + const useed : i16 = 1 << useed_exp; // 2^2 = 4 + const scale = (useed * @as(i16, regime)) + @as(i16, exponent); + + return PositComponents { + .sign = sign, + .regime = regime, + .exponent = @as(u8, @intCast(exponent)), + .fraction = fraction, + .scale = scale, + }; +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// posit16_is_zero(pos: Posit16) -> bool +// Check if posit is zero (positive or negative) +pub fn posit16_is_zero(pos: Posit16) bool { + return pos == POS16_ZERO; +} + +// posit16_is_nar(pos: Posit16) -> bool +// Check if posit is Not-a-Real (NaR) +pub fn posit16_is_nar(pos: Posit16) bool { + return pos == POS16_NAR; +} + +// posit16_is_special(pos: Posit16) -> bool +// Check if posit is a special value (zero or NaR) +pub fn posit16_is_special(pos: Posit16) bool { + return posit16_is_zero(pos) or posit16_is_nar(pos); +} + +// posit16_is_finite(pos: Posit16) -> bool +// Check if posit is finite (not NaR) +pub fn posit16_is_finite(pos: Posit16) bool { + return not posit16_is_nar(pos); +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// posit16_encode_f32(value: f32) -> Posit16 +// Encode IEEE 754 single precision to Posit16 +pub fn posit16_encode_f32(value: f32) Posit16 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) POS16_NAR else POS16_ZERO; + } + + // Handle NaN + if (std.math.isNan(value)) { + return POS16_NAR; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) POS16_NAR else POS16_NAR; + } + + // Extract sign + const is_negative = value < 0.0; + const abs_value = if (is_negative) -value else value; + + // Convert to f64 for more precision during computation + const value_f64 : f64 = @as(f64, @floatCast(abs_value)); + + // Calculate useed = 2^(2^ES) = 4 for ES=1 + const useed : f64 = 4.0; + + // Calculate log2 and determine regime + const log2_val = std.math.log(value_f64) / std.math.log(2.0); + + // Regime: largest k such that 2^(k*useed) <= value < 2^((k+1)*useed) + var k : i16 = @intFromFloat(@floor(log2_val / std.math.log(useed))); + + // Clamp regime + if (k > MAX_REGIME) { + k = MAX_REGIME; + } else if (k < -MAX_REGIME - 1) { + k = -MAX_REGIME - 1; + } + + // Calculate exponent + var exp : i8 = 0; + var remaining = value_f64 / std.math.pow(f64, useed, @as(f64, @floatFromInt(k))); + + // Normalize remaining value to [1, 2) for fraction extraction + while (remaining >= 2.0 and exp < ((@as(i8, 1) << ES) - 1)) { + remaining /= 2.0; + exp += 1; + } + + // Extract fraction bits + const fraction_bits : u8 = @as(u8, @intCast(NBITS - @as(u8, @intCast(@abs(k) + 1)) - ES)); + var fraction : u16 = 0; + var frac_val = remaining - 1.0; // Remove implicit 1 + + if (fraction_bits > 0) { + for (0..fraction_bits) |i| { + frac_val *= 2.0; + if (frac_val >= 1.0) { + fraction |= (@as(u16, 1) << (fraction_bits - 1 - i)); + frac_val -= 1.0; + } + } + } + + // Encode bits + var result : u16 = 0; + + // Set sign bit + if (is_negative) { + result |= SIGN_MASK; + } + + // Encode regime + // For k >= 0: run of k+1 ones followed by zero + // For k < 0: run of -k zeros followed by one + const regime_bits_remaining : u8 = if (fraction_bits > 0) + @as(u8, @intCast(@as(i16, @abs(k)) + 1)) + else + NBITS - ES; + + const regime_start = SIGN_SHIFT - 1 - regime_bits_remaining; + if (k >= 0) { + // k+1 ones followed by zero (if space permits) + for (0..@as(u8, @intCast(k + 1))) |i| { + if (regime_start - i >= 0) { + result |= (@as(u16, 1) << (regime_start - i)); + } + } + } else { + // -k zeros followed by one + const one_pos = regime_start - @as(u8, @intCast(-k)); + if (one_pos >= 0) { + result |= (@as(u16, 1) << one_pos); + } + } + + // Encode exponent + if (ES > 0 and fraction_bits + ES > 0) { + const exp_pos = @as(u8, @intCast(NBITS - 1)) - regime_bits_remaining; + if (exp_pos >= 0) { + result |= (@as(u16, exp) << exp_pos); + } + } + + // Encode fraction + const fraction_pos = @as(u8, @intCast(NBITS - 1)) - regime_bits_remaining - ES; + if (fraction_bits > 0 and fraction_pos >= fraction_bits) { + result |= (fraction << (fraction_pos - fraction_bits + 1)); + } + + return result; +} + +// posit16_decode_f32(pos: Posit16) -> f32 +// Decode Posit16 to IEEE 754 single precision +pub fn posit16_decode_f32(pos: Posit16) f32 { + // Handle zero + if (posit16_is_zero(pos)) { + return 0.0; + } + + // Handle NaR + if (posit16_is_nar(pos)) { + return std.math.nan(f32); + } + + // Extract components + const comps = posit16_extract_components(pos); + + // Calculate value + // value = (-1)^sign * 2^scale * (1 + fraction/2^fraction_bits) + const useed : f64 = 4.0; // 2^(2^1) = 4 + const scale_value = std.math.pow(f64, useed, @as(f64, @floatFromInt(comps.scale))); + + // Calculate fraction contribution + const fraction_bits : u8 = @as(u8, @intCast(NBITS - @as(u8, @intCast(@abs(comps.regime) + 1)) - ES)); + const fraction_value = if (fraction_bits > 0) + @as(f64, @floatFromInt(comps.fraction)) / @as(f64, @floatFromInt(@as(u16, 1) << fraction_bits)) + else + 0.0; + + const magnitude = scale_value * (1.0 + fraction_value); + + return if (comps.sign) -@as(f32, @floatCast(magnitude)) else @as(f32, @floatCast(magnitude)); +} + +// posit16_encode_f64(value: f64) -> Posit16 +// Encode IEEE 754 double precision to Posit16 +pub fn posit16_encode_f64(value: f64) Posit16 { + return posit16_encode_f32(@as(f32, @floatCast(value))); +} + +// posit16_decode_f64(pos: Posit16) -> f64 +// Decode Posit16 to IEEE 754 double precision +pub fn posit16_decode_f64(pos: Posit16) f64 { + const f32_val = posit16_decode_f32(pos); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// posit16_add(a: Posit16, b: Posit16) -> Posit16 +// Add two posit values +pub fn posit16_add(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a)) { + return b; + } + if (posit16_is_zero(b)) { + return a; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa + fb); +} + +// posit16_sub(a: Posit16, b: Posit16) -> Posit16 +// Subtract two posit values +pub fn posit16_sub(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa - fb); +} + +// posit16_mul(a: Posit16, b: Posit16) -> Posit16 +// Multiply two posit values +pub fn posit16_mul(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a) or posit16_is_zero(b)) { + return POS16_ZERO; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa * fb); +} + +// posit16_div(a: Posit16, b: Posit16) -> Posit16 +// Divide two posit values +pub fn posit16_div(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a)) { + return POS16_ZERO; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa / fb); +} + +// posit16_abs(value: Posit16) -> Posit16 +// Absolute value of posit +pub fn posit16_abs(value: Posit16) Posit16 { + return value & ~SIGN_MASK; +} + +// posit16_neg(value: Posit16) -> Posit16 +// Negate posit +pub fn posit16_neg(value: Posit16) Posit16 { + return value ^ SIGN_MASK; +} + +// posit16_is_equal(a: Posit16, b: Posit16) -> bool +// Check if two posit values are equal +pub fn posit16_is_equal(a: Posit16, b: Posit16) bool { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return false; + } + if (posit16_is_zero(a) and posit16_is_zero(b)) { + return true; + } + return a == b; +} + +// posit16_is_greater(a: Posit16, b: Posit16) -> bool +// Check if a > b +pub fn posit16_is_greater(a: Posit16, b: Posit16) bool { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return false; + } + const sign_a = posit16_extract_sign(a); + const sign_b = posit16_extract_sign(b); + if (sign_a != sign_b) { + return not sign_a; // Positive > negative + } + if (sign_a) { + // Both negative, compare magnitudes (inverse) + return a < b; + } + return a > b; +} + +// posit16_max(a: Posit16, b: Posit16) -> Posit16 +// Return the larger of two posit values +pub fn posit16_max(a: Posit16, b: Posit16) Posit16 { + return if (posit16_is_greater(a, b)) a else b; +} + +// posit16_min(a: Posit16, b: Posit16) -> Posit16 +// Return the smaller of two posit values +pub fn posit16_min(a: Posit16, b: Posit16) Posit16 { + return if (posit16_is_greater(a, b)) b else a; +} + +// posit16_sqrt(value: Posit16) -> Posit16 +// Square root of posit value +pub fn posit16_sqrt(value: Posit16) Posit16 { + if (posit16_is_nar(value)) { + return POS16_NAR; + } + if (posit16_extract_sign(value)) { + return POS16_NAR; + } + if (posit16_is_zero(value)) { + return POS16_ZERO; + } + + const fv = posit16_decode_f32(value); + return posit16_encode_f32(std.math.sqrt(fv)); +} + +// posit16_lerp(a: Posit16, b: Posit16, t: f32) -> Posit16 +// Linear interpolation between a and b +pub fn posit16_lerp(a: Posit16, b: Posit16, t: f32) Posit16 { + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + const result = fa + (fb - fa) * t; + return posit16_encode_f32(result); +} + +// posit16_scale(value: Posit16, scale: f32) -> Posit16 +// Scale posit value by scalar +pub fn posit16_scale(value: Posit16, scale: f32) Posit16 { + const fv = posit16_decode_f32(value); + return posit16_encode_f32(fv * scale); +} + +// posit16_relu(value: Posit16) -> Posit16 +// ReLU activation: max(0, x) +pub fn posit16_relu(value: Posit16) Posit16 { + return if (posit16_extract_sign(value)) POS16_ZERO else value; +} + +// posit16_sigmoid(value: Posit16) -> Posit16 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn posit16_sigmoid(value: Posit16) Posit16 { + const fv = posit16_decode_f32(value); + return posit16_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// posit16_quantize_to_int4(value: Posit16) -> i8 +// Quantize posit to Int4 (range [-8, 7]) +pub fn posit16_quantize_to_int4(value: Posit16) i8 { + const fv = posit16_decode_f32(value); + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// posit16_from_f32_scaled(value: f32, scale: f32) -> Posit16 +// Encode f32 to posit with custom scale factor +pub fn posit16_from_f32_scaled(value: f32, scale: f32) Posit16 { + return posit16_encode_f32(value * scale); +} + +// posit16_to_f32_scaled(value: Posit16, scale: f32) -> f32 +// Decode posit to f32 with custom scale factor +pub fn posit16_to_f32_scaled(value: Posit16, scale: f32) f32 { + return posit16_decode_f32(value) / scale; +} + +// posit16_dot_product(a: []const Posit16, b: []const Posit16) -> Posit16 +// Dot product of two posit arrays +pub fn posit16_dot_product(a: []const Posit16, b: []const Posit16) Posit16 { + var sum: f32 = 0.0; + for (a, 0..) |val_a, i| { + if (i >= b.len) break; + const fa = posit16_decode_f32(val_a); + const fb = posit16_decode_f32(b[i]); + sum += fa * fb; + } + return posit16_encode_f32(sum); +} + +// posit16_vector_sum(vec: []const Posit16) -> Posit16 +// Sum all elements of a vector +pub fn posit16_vector_sum(vec: []const Posit16) Posit16 { + var sum: f32 = 0.0; + for (vec) |val| { + sum += posit16_decode_f32(val); + } + return posit16_encode_f32(sum); +} + +// posit16_vector_mean(vec: []const Posit16) -> Posit16 +// Mean of all elements of a vector +pub fn posit16_vector_mean(vec: []const Posit16) Posit16 { + if (vec.len == 0) { + return POS16_ZERO; + } + const sum = posit16_vector_sum(vec); + return posit16_div(sum, posit16_encode_f32(@as(f32, @floatFromInt(vec.len)))); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "posit16_is_zero_true" { + try std.testing.expect(posit16_is_zero(POS16_ZERO) == true); +} + +test "posit16_is_zero_false" { + try std.testing.expect(posit16_is_zero(0x0001) == false); +} + +test "posit16_is_nar_true" { + try std.testing.expect(posit16_is_nar(POS16_NAR) == true); +} + +test "posit16_is_nar_false" { + try std.testing.expect(posit16_is_nar(POS16_ZERO) == false); +} + +test "posit16_is_special_zero" { + try std.testing.expect(posit16_is_special(POS16_ZERO) == true); +} + +test "posit16_is_special_nar" { + try std.testing.expect(posit16_is_special(POS16_NAR) == true); +} + +test "posit16_is_special_normal" { + try std.testing.expect(posit16_is_special(0x4000) == false); +} + +test "posit16_extract_sign_positive" { + given value = 0x4000 + try std.testing.expect(sign = posit16_extract_sign(value)); + try std.testing.expect(sign == false); +} + +test "posit16_extract_sign_negative" { + given value = 0xC000 + try std.testing.expect(sign = posit16_extract_sign(value)); + try std.testing.expect(sign == true); +} + +test "posit16_encode_f32_zero" { + given pos = posit16_encode_f32(0.0) + try std.testing.expect(pos == POS16_ZERO); +} + +test "posit16_encode_f32_one" { + given pos = posit16_encode_f32(1.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "posit16_encode_f32_negative_one" { + given pos = posit16_encode_f32(-1.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded + 1.0) < 0.1); +} + +test "posit16_encode_f32_ten" { + given pos = posit16_encode_f32(10.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - 10.0) < 2.0); +} + +test "posit16_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(pos = posit16_encode_f32(original)); + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - original) < 0.3); +} + +test "posit16_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(pos = posit16_encode_f32(original)); + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - original) < 0.3); +} + +test "posit16_decode_f32_zero" { + try std.testing.expect(posit16_decode_f32(POS16_ZERO) == 0.0); +} + +test "posit16_decode_f32_nar" { + try std.testing.expect(std.math.isNan(posit16_decode_f32(POS16_NAR)) == true); +} + +test "posit16_add_simple" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(20.0)); + try std.testing.expect(result = posit16_add(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 30.0) < 5.0); +} + +test "posit16_sub_simple" { + given a = posit16_encode_f32(50.0) + try std.testing.expect(b = posit16_encode_f32(30.0)); + try std.testing.expect(result = posit16_sub(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "posit16_mul_simple" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(10.0)); + try std.testing.expect(result = posit16_mul(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 20.0); +} + +test "posit16_div_simple" { + given a = posit16_encode_f32(100.0) + try std.testing.expect(b = posit16_encode_f32(10.0)); + try std.testing.expect(result = posit16_div(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "posit16_sqrt_four" { + given value = posit16_encode_f32(4.0) + try std.testing.expect(result = posit16_sqrt(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.3); +} + +test "posit16_sqrt_negative" { + given value = posit16_encode_f32(-4.0) + try std.testing.expect(result = posit16_sqrt(value)); + try std.testing.expect(posit16_is_nar(result) == true); +} + +test "posit16_abs_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(result = posit16_abs(value)); + try std.testing.expect(posit16_is_equal(result, value) == true); +} + +test "posit16_abs_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(abs_val = posit16_abs(value)); + try std.testing.expect(posit16_extract_sign(abs_val) == false); +} + +test "posit16_neg_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(neg_val = posit16_neg(value)); + try std.testing.expect(posit16_extract_sign(neg_val) == true); +} + +test "posit16_neg_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(neg_val = posit16_neg(value)); + try std.testing.expect(posit16_extract_sign(neg_val) == false); +} + +test "posit16_is_equal_true" { + given value = posit16_encode_f32(10.0) + try std.testing.expect(posit16_is_equal(value, value) == true); +} + +test "posit16_is_equal_false" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(20.0)); + try std.testing.expect(posit16_is_equal(a, b) == false); +} + +test "posit16_is_equal_nar" { + try std.testing.expect(posit16_is_equal(POS16_NAR, POS16_NAR) == false); +} + +test "posit16_is_greater_positive" { + given a = posit16_encode_f32(50.0) + try std.testing.expect(b = posit16_encode_f32(30.0)); + try std.testing.expect(posit16_is_greater(a, b) == true); +} + +test "posit16_max_returns_larger" { + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_max(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded >= 35.0); +} + +test "posit16_min_returns_smaller" { + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_min(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded <= 45.0); +} + +test "posit16_relu_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(result = posit16_relu(value)); + try std.testing.expect(posit16_is_equal(result, value) == true); +} + +test "posit16_relu_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(result = posit16_relu(value)); + try std.testing.expect(result == POS16_ZERO); +} + +test "posit16_sigmoid_zero" { + given value = POS16_ZERO + try std.testing.expect(result = posit16_sigmoid(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "posit16_sigmoid_positive_large" { + given value = posit16_encode_f32(10.0) + try std.testing.expect(result = posit16_sigmoid(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "posit16_quantize_to_int4_max" { + given value = posit16_encode_f32(100.0) + try std.testing.expect(result = posit16_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "posit16_quantize_to_int4_min" { + given value = posit16_encode_f32(-100.0) + try std.testing.expect(result = posit16_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "posit16_from_f32_scaled_up" { + given value = 1.0 + try std.testing.expect(scale = 100.0); + try std.testing.expect(result = posit16_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 20.0); +} + +test "posit16_dot_product_simple" { + given a = [_]Posit16{posit16_encode_f32(1.0), posit16_encode_f32(2.0), posit16_encode_f32(3.0), posit16_encode_f32(4.0)} + try std.testing.expect(b = [_]Posit16{posit16_encode_f32(2.0), posit16_encode_f32(2.0), posit16_encode_f32(2.0), posit16_encode_f32(2.0)}); + try std.testing.expect(result = posit16_dot_product(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "posit16_vector_sum_simple" { + given vec = [_]Posit16{posit16_encode_f32(1.0), posit16_encode_f32(2.0), posit16_encode_f32(3.0), posit16_encode_f32(4.0)} + try std.testing.expect(result = posit16_vector_sum(vec)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "posit16_vector_mean_simple" { + given vec = [_]Posit16{posit16_encode_f32(2.0), posit16_encode_f32(4.0), posit16_encode_f32(6.0), posit16_encode_f32(8.0)} + try std.testing.expect(result = posit16_vector_mean(vec)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 5.0) < 1.0); +} + +test "posit16_lerp_zero" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = posit16_lerp(a, b, t)); + try std.testing.expect(posit16_is_equal(result, a) == true); +} + +test "posit16_lerp_one" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = posit16_lerp(a, b, t)); + try std.testing.expect(posit16_is_equal(result, b) == true); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant posit16_bits_constant + assert BITS == 16 + +invariant posit16_es_one + assert ES == 1 + +invariant posit16_nbits_fifteen + assert NBITS == 15 + +invariant posit16_zero_no_sign + assert (POS16_ZERO & SIGN_MASK) == 0 + +invariant posit16_nar_has_sign + assert (POS16_NAR & SIGN_MASK) != 0 + +invariant posit16_zero_nar_distinct + assert POS16_ZERO != POS16_NAR + +invariant posit16_abs_removes_sign + assert posit16_abs(POS16_NAR) != POS16_NAR // NaR preserves sign pattern + +invariant posit16_neg_zero_is_zero + assert posit16_neg(POS16_ZERO) == POS16_ZERO + +invariant posit16_neg_nar_is_nar + assert posit16_neg(POS16_NAR) == POS16_NAR + +invariant posit16_add_zero_identity + given value = posit16_encode_f32(50.0) + assert posit16_is_equal(posit16_add(value, POS16_ZERO), value) == true + +invariant posit16_sub_zero_identity + given value = posit16_encode_f32(50.0) + assert posit16_is_equal(posit16_sub(value, POS16_ZERO), value) == true + +invariant posit16_mul_zero_zero + given value = posit16_encode_f32(50.0) + assert posit16_mul(value, POS16_ZERO) == POS16_ZERO + +invariant posit16_div_nar_propagates + given value = posit16_encode_f32(50.0) + assert posit16_is_nar(posit16_div(POS16_NAR, value)) == true + +invariant posit16_relu_non_negative + given result = posit16_relu(posit16_encode_f32(50.0)) + try std.testing.expect(posit16_extract_sign(result) == false); + +invariant posit16_sqrt_non_negative + given result = posit16_sqrt(posit16_encode_f32(4.0)) + try std.testing.expect(posit16_extract_sign(result) == false); + +invariant posit16_quantize_to_int4_in_range + given result = posit16_quantize_to_int4(posit16_encode_f32(50.0)) + assert result >= -8 and result <= 7 + +invariant posit16_scale_roundtrip + given value = posit16_encode_f32(50.0) + try std.testing.expect(scale = 2.0); + try std.testing.expect(scaled = posit16_scale(value, scale)); + try std.testing.expect(decoded = posit16_to_f32_scaled(scaled, scale)); + try std.testing.expect(abs(decoded - posit16_decode_f32(value)) < 5.0); + +invariant posit16_max_ge_both + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_max(a, b)); + try std.testing.expect(posit16_is_greater(result, a) == true or posit16_is_equal(result, a) == true); + +invariant posit16_min_le_both + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_min(a, b)); + try std.testing.expect(posit16_is_greater(b, result) == true or posit16_is_equal(b, result) == true); + +invariant posit16_lerp_bounded + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result_half = posit16_lerp(a, b, 0.5)); + try std.testing.expect(decoded_a = posit16_decode_f32(a)); + try std.testing.expect(decoded_b = posit16_decode_f32(b)); + try std.testing.expect(decoded_half = posit16_decode_f32(result_half)); + try std.testing.expect(decoded_half > decoded_a and decoded_half < decoded_b); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench posit16_encode_f32_latency + measure: nanoseconds to posit16_encode_f32(10.5) + target: < 50ns + +bench posit16_decode_f32_latency + measure: nanoseconds to posit16_decode_f32(0x4000) + target: < 50ns + +bench posit16_add_latency + measure: nanoseconds to posit16_add(0x4000, 0x4800) + target: < 100ns + +bench posit16_mul_latency + measure: nanoseconds to posit16_mul(0x4000, 0x4000) + target: < 100ns + +bench posit16_sqrt_latency + measure: nanoseconds to posit16_sqrt(0x4000) + target: < 100ns + +bench posit16_sigmoid_latency + measure: nanoseconds to posit16_sigmoid(0x4000) + target: < 200ns + +bench posit16_relu_latency + measure: nanoseconds to posit16_relu(0x4800) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/euler/specs/numeric/tri_net_formats.t27 b/apps/website/public/t27/files/chips/euler/specs/numeric/tri_net_formats.t27 new file mode 100644 index 0000000000..bef8b1be89 --- /dev/null +++ b/apps/website/public/t27/files/chips/euler/specs/numeric/tri_net_formats.t27 @@ -0,0 +1,993 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/tri_net_formats.t27 +// TRI NET Format Registry — Complete Format Specification +// phi^2 + 1/phi^2 = 3 | TRINITY +// +// Complete format registry for TRI-NET neural accelerator: +// - GoldenFloat family (GF4-GF256) +// - IEEE 754 formats (fp32, fp16) +// - Brain Float (bf16) +// - FP8 variants (e4m3, e5m2) +// - Integer formats (int4, int8) +// - Quantization formats (nf4) +// - Posit format (posit16) +// - Binary format (binary16) +// +// R-SI-1: Zero * operators, use XOR/shift/add only + +module TriNetFormats { + // Import sacred constants for phi-based optimization + use math::constants; + use math::sacred_physics; + use numeric::goldenfloat_family; + + // ======================================================================== + // 1. Format Classification Registry + // ======================================================================== + + // Format categories for TRI-NET hardware path selection + pub const FormatCategory = enum(u8) { + goldenfloat, // GF family: phi-optimized + ieee754, // IEEE 754 standard formats + integer, // Integer quantization + posit, // Posit universal number format + quantized, // Special quantization (NF4, etc) + binary, // Binary formats + }; + + // ======================================================================== + // 2. GoldenFloat Extended Family (GF64, GF128, GF256) + // ======================================================================== + // + // Formula: exp/mant ≈ 1/φ ≈ 0.618 + // Using phi_split: exp = round((N-1)/φ²), mant = N - 1 - exp + // + // GF64: 64 bits → exp=24, mant=39 (ratio=0.615, φ_dist=0.003) + // GF128: 128 bits → exp=48, mant=79 (ratio=0.608, φ_dist=0.010) + // GF256: 256 bits → exp=97, mant=158 (ratio=0.614, φ_dist=0.004) + + pub struct GF64Format { + bits : u8 = 64, + sign_bits : u8 = 1, + exp_bits : u8 = 24, + mant_bits : u8 = 39, + exp_bias : u32 = 8388607, // 2^(24-1) - 1 + phi_ratio : f64 = 24.0 / 39.0, + phi_dist : f64 = abs(24.0/39.0 - sacred_physics::PHI_INV), + is_primary : bool = false, + } + + pub struct GF128Format { + bits : u8 = 128, + sign_bits : u8 = 1, + exp_bits : u8 = 48, + mant_bits : u8 = 79, + exp_bias : u64 = 140737488355327, // 2^(48-1) - 1 + phi_ratio : f64 = 48.0 / 79.0, + phi_dist : f64 = abs(48.0/79.0 - sacred_physics::PHI_INV), + is_primary : bool = false, + } + + pub struct GF256Format { + bits : u8 = 256, + sign_bits : u8 = 1, + exp_bits : u8 = 97, + mant_bits : u8 = 158, + exp_bias : u128 = 0x7FFFFFFFFFFFFFFF, // 2^(97-1) - 1 + phi_ratio : f64 = 97.0 / 158.0, + phi_dist : f64 = abs(97.0/158.0 - sacred_physics::PHI_INV), + is_primary : bool = false, + } + + // ======================================================================== + // 3. IEEE 754 Standard Formats + // ======================================================================== + + pub struct FP32Format { + bits : u8 = 32, + sign_bits : u8 = 1, + exp_bits : u8 = 8, + mant_bits : u8 = 23, + exp_bias : u8 = 127, + category : FormatCategory = .ieee754, + ieee_compliant : bool = true, + } + + pub struct FP16Format { + bits : u8 = 16, + sign_bits : u8 = 1, + exp_bits : u8 = 5, + mant_bits : u8 = 10, + exp_bias : u8 = 15, + category : FormatCategory = .ieee754, + ieee_compliant : bool = true, + note : "binary16 = FP16", + } + + pub struct BF16Format { + bits : u8 = 16, + sign_bits : u8 = 1, + exp_bits : u8 = 8, + mant_bits : u8 = 7, + exp_bias : u8 = 127, + category : FormatCategory = .ieee754, + ieee_compliant : bool = false, // Not in IEEE 754-2008, widely used + note : "Brain Float: same exp as FP32, truncated mantissa", + } + + // ======================================================================== + // 4. FP8 Variants (OCP 8-bit specification) + // ======================================================================== + + pub struct FP8E4M3Format { + bits : u8 = 8, + sign_bits : u8 = 1, + exp_bits : u8 = 4, + mant_bits : u8 = 3, + exp_bias : u8 = 7, + category : FormatCategory = .ieee754, + note : "OCP E4M3: 1 exp, 8 mant values for training", + range : string = "[-448, 448]", + } + + pub struct FP8E5M2Format { + bits : u8 = 8, + sign_bits : u8 = 1, + exp_bits : u8 = 5, + mant_bits : u8 = 2, + exp_bias : u8 = 15, + category : FormatCategory = .ieee754, + note : "OCP E5M2: better dynamic range for inference", + range : string = "[-57344, 57344]", + } + + // ======================================================================== + // 5. Integer Formats (for quantization) + // ======================================================================== + + pub struct Int4Format { + bits : u8 = 4, + sign_bits : u8 = 1, + data_bits : u8 = 3, + category : FormatCategory = .integer, + range : string = "[-8, 7]", + quantization_scale : f32 = 0.0625, + } + + pub struct Int8Format { + bits : u8 = 8, + sign_bits : u8 = 1, + data_bits : u8 = 7, + category : FormatCategory = .integer, + range : string = "[-128, 127]", + quantization_scale : f32 = 0.0039216, + } + + // ======================================================================== + // 6. NormalFloat4 (NF4) Format + // ======================================================================== + // + // NF4: 4-bit format with values drawn from normal distribution + // Values: discretized Normal(0, 1) quantiles + // Used in QLoRA for efficient 4-bit fine-tuning + // + // 16 levels: {-1.0, -0.6962, -0.5251, -0.3942, -0.2844, -0.1848, -0.0910, -0.0000, + // 0.0000, 0.0910, 0.1848, 0.2844, 0.3942, 0.5251, 0.6962, 1.0} + // + // Note: Zero appears twice (positive/negative) for perfect rounding + + pub const NF4_LEVELS : [16]f64 = [ + -1.0, -0.6961928009210449, -0.5250720977783203, -0.3941699969768524, + -0.28444138169288635, -0.18477343022823334, -0.09105003625154495, -0.0000000000000000, + 0.0000000000000000, 0.09105003625154495, 0.18477343022823334, 0.28444138169288635, + 0.3941699969768524, 0.5250720977783203, 0.6961928009210449, 1.0, + ]; + + pub struct NF4Format { + bits : u8 = 4, + category : FormatCategory = .quantized, + range : string = "[-1.0, 1.0]", + distribution : string = "Normal(0, 1) quantiles", + num_levels : u8 = 16, + } + + // ======================================================================== + // 7. Posit16 Format (Type III Unum) + // ======================================================================== + // + // Posit format: [sign][regime][exponent][mantissa] + // regime: uses of trailing zeros/followed by zeros + // es (exponent size) = 1 for posit16 + // + // bit layout: [S(1) regime(6) E(1) M(8)] + // + // Properties: + // - Exact representation of small integers + // - Symmetric positive/negative + // - Gradual overflow/underflow + // - Better dynamic range than FP16 for same precision + + pub struct Posit16Format { + bits : u8 = 16, + sign_bits : u8 = 1, + es_bits : u8 = 1, // exponent size + nbits : u8 = 16, + category : FormatCategory = .posit, + useed : u8 = 2, // 2^es = 2 + max_pos : f64 = 262144.0, + min_pos : f64 = 0.000000059604645, + } + + // ======================================================================== + // 8. Binary16 Format + // ======================================================================== + // + // Binary16 is just an alias for IEEE FP16 + // Kept separate for format routing clarity + + pub struct Binary16Format { + bits : u8 = 16, + sign_bits : u8 = 1, + exp_bits : u8 = 5, + mant_bits : u8 = 10, + exp_bias : u8 = 15, + category : FormatCategory = .binary, + note : "Same as FP16, separate routing class", + } + + // ======================================================================== + // 9. Format Registry (Lookup Table) + // ======================================================================== + + pub struct FormatDescriptor { + name : string, + category : FormatCategory, + bits : u8, + sign_bits : u8, + exp_bits : u8, + mant_bits : u8, + exp_bias : u32, + phi_ratio : f64, + phi_dist : f64, + memory_ratio : f64, // vs FP32 + use_case : string, + } + + pub const TRI_NET_FORMATS : [16]FormatDescriptor = [ + // GoldenFloat family (phi-optimized) + FormatDescriptor{ + name = "GF4", + category = .goldenfloat, + bits = 4, + sign_bits = 1, + exp_bits = 1, + mant_bits = 2, + exp_bias = 0, + phi_ratio = 0.5, + phi_dist = 0.118, + memory_ratio = 0.125, + use_case = "Extreme compression, attention masks", + }, + FormatDescriptor{ + name = "GF8", + category = .goldenfloat, + bits = 8, + sign_bits = 1, + exp_bits = 3, + mant_bits = 4, + exp_bias = 3, + phi_ratio = 0.75, + phi_dist = 0.132, + memory_ratio = 0.25, + use_case = "Weight quantization, activation caching", + }, + FormatDescriptor{ + name = "GF12", + category = .goldenfloat, + bits = 12, + sign_bits = 1, + exp_bits = 4, + mant_bits = 7, + exp_bias = 7, + phi_ratio = 0.571, + phi_dist = 0.047, + memory_ratio = 0.375, + use_case = "Best phi approximation, critical weights", + }, + FormatDescriptor{ + name = "GF16", + category = .goldenfloat, + bits = 16, + sign_bits = 1, + exp_bits = 6, + mant_bits = 9, + exp_bias = 31, + phi_ratio = 0.667, + phi_dist = 0.049, + memory_ratio = 0.5, + use_case = "PRIMARY FORMAT, IGLA main weights", + }, + FormatDescriptor{ + name = "GF20", + category = .goldenfloat, + bits = 20, + sign_bits = 1, + exp_bits = 7, + mant_bits = 12, + exp_bias = 63, + phi_ratio = 0.583, + phi_dist = 0.035, + memory_ratio = 0.625, + use_case = "High-precision ML training", + }, + FormatDescriptor{ + name = "GF24", + category = .goldenfloat, + bits = 24, + sign_bits = 1, + exp_bits = 9, + mant_bits = 14, + exp_bias = 255, + phi_ratio = 0.643, + phi_dist = 0.025, + memory_ratio = 0.75, + use_case = "Financial, numerical stability", + }, + FormatDescriptor{ + name = "GF32", + category = .goldenfloat, + bits = 32, + sign_bits = 1, + exp_bits = 12, + mant_bits = 19, + exp_bias = 2047, + phi_ratio = 0.632, + phi_dist = 0.014, + memory_ratio = 1.0, + use_case = "Near-IEEE precision, wider range", + }, + FormatDescriptor{ + name = "GF64", + category = .goldenfloat, + bits = 64, + sign_bits = 1, + exp_bits = 24, + mant_bits = 39, + exp_bias = 8388607, + phi_ratio = 0.615, + phi_dist = 0.003, + memory_ratio = 2.0, + use_case = "Extended range, scientific computing", + }, + FormatDescriptor{ + name = "GF128", + category = .goldenfloat, + bits = 128, + sign_bits = 1, + exp_bits = 48, + mant_bits = 79, + exp_bias = 140737488355327, + phi_ratio = 0.608, + phi_dist = 0.010, + memory_ratio = 4.0, + use_case = "Ultra-high precision, cryptography", + }, + FormatDescriptor{ + name = "GF256", + category = .goldenfloat, + bits = 256, + sign_bits = 1, + exp_bits = 97, + mant_bits = 158, + exp_bias = 0x7FFFFFFFFFFFFFFF, + phi_ratio = 0.614, + phi_dist = 0.004, + memory_ratio = 8.0, + use_case = "Maximum precision, physics simulations", + }, + // IEEE 754 formats + FormatDescriptor{ + name = "FP32", + category = .ieee754, + bits = 32, + sign_bits = 1, + exp_bits = 8, + mant_bits = 23, + exp_bias = 127, + phi_ratio = 0.348, + phi_dist = 0.270, + memory_ratio = 1.0, + use_case = "Standard training, reference", + }, + FormatDescriptor{ + name = "FP16", + category = .ieee754, + bits = 16, + sign_bits = 1, + exp_bits = 5, + mant_bits = 10, + exp_bias = 15, + phi_ratio = 0.5, + phi_dist = 0.118, + memory_ratio = 0.5, + use_case = "Standard half-precision, inference", + }, + FormatDescriptor{ + name = "BF16", + category = .ieee754, + bits = 16, + sign_bits = 1, + exp_bits = 8, + mant_bits = 7, + exp_bias = 127, + phi_ratio = 1.143, + phi_dist = 0.525, + memory_ratio = 0.5, + use_case = "Brain Float, gradient accumulation", + }, + // FP8 variants + FormatDescriptor{ + name = "FP8_E4M3", + category = .ieee754, + bits = 8, + sign_bits = 1, + exp_bits = 4, + mant_bits = 3, + exp_bias = 7, + phi_ratio = 1.333, + phi_dist = 0.715, + memory_ratio = 0.25, + use_case = "OCP training format", + }, + FormatDescriptor{ + name = "FP8_E5M2", + category = .ieee754, + bits = 8, + sign_bits = 1, + exp_bits = 5, + mant_bits = 2, + exp_bias = 15, + phi_ratio = 2.5, + phi_dist = 1.882, + memory_ratio = 0.25, + use_case = "OCP inference format", + }, + // Posit and Binary + FormatDescriptor{ + name = "Posit16", + category = .posit, + bits = 16, + sign_bits = 1, + exp_bits = 1, + mant_bits = 8, + exp_bias = 0, + phi_ratio = 0.125, + phi_dist = 0.493, + memory_ratio = 0.5, + use_case = "Type III Unum, exact integers", + }, + FormatDescriptor{ + name = "Binary16", + category = .binary, + bits = 16, + sign_bits = 1, + exp_bits = 5, + mant_bits = 10, + exp_bias = 15, + phi_ratio = 0.5, + phi_dist = 0.118, + memory_ratio = 0.5, + use_case = "Alias for FP16, binary routing", + }, + ]; + + // ======================================================================== + // 10. Format Lookup Functions + // ======================================================================== + + pub fn get_format_by_name(name: string) -> ?FormatDescriptor { + for (const TRI_NET_FORMATS) |fmt| { + if (fmt.name == name) { + return fmt; + } + } + return null; + } + + pub fn get_format_by_bits(bits: u8) -> []FormatDescriptor { + var result : [16]FormatDescriptor = undefined; + var count : u8 = 0; + for (const TRI_NET_FORMATS) |fmt| { + if (fmt.bits == bits) { + result[count] = fmt; + count = count + 1; + } + } + return result[0..count]; + } + + pub fn get_phi_optimal_format() -> FormatDescriptor { + var best : FormatDescriptor = TRI_NET_FORMATS[0]; + for (const TRI_NET_FORMATS) |fmt| { + if (fmt.phi_dist < best.phi_dist) { + best = fmt; + } + } + return best; + } + + // ======================================================================== + // 11. Format Conversion Utilities + // ======================================================================== + + pub fn format_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Format-specific conversion to f32 + if (fmt.category == .goldenfloat) { + return gf_to_f32(fmt, raw); + } else if (fmt.category == .ieee754) { + return ieee754_to_f32(fmt, raw); + } else if (fmt.category == .posit) { + return posit_to_f32(fmt, raw); + } else if (fmt.category == .integer) { + return int_to_f32(fmt, raw); + } else if (fmt.category == .quantized) { + return nf4_to_f32(raw); + } + return 0.0; + } + + pub fn f32_to_format(fmt: FormatDescriptor, value: f32) -> u64 { + // Format-specific conversion from f32 + if (fmt.category == .goldenfloat) { + return f32_to_gf(fmt, value); + } else if (fmt.category == .ieee754) { + return f32_to_ieee754(fmt, value); + } else if (fmt.category == .posit) { + return f32_to_posit(fmt, value); + } else if (fmt.category == .integer) { + return f32_to_int(fmt, value); + } else if (fmt.category == .quantized) { + return f32_to_nf4(value); + } + return 0; + } + + // ======================================================================== + // 12. GF Conversion Functions (Stub) + // ======================================================================== + + fn gf_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Extract sign, exponent, mantissa + const sign = (raw >> (fmt.bits - 1)) as u8; + const exp_mask = (1u64 << fmt.exp_bits) - 1; + const mant_mask = (1u64 << fmt.mant_bits) - 1; + const exp_biased = ((raw >> fmt.mant_bits) & exp_mask) as u32; + const mant = (raw & mant_mask) as u32; + + // Handle zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Unbias exponent + const exp_unbiased = if (exp_biased == 0) { + -(fmt.exp_bias as i32) + 1 + } else { + (exp_biased as i32) - fmt.exp_bias as i32 + }; + + // Normalize mantissa + const max_mant = (1u32 << fmt.mant_bits) - 1; + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / (max_mant + 1) as f32 + } else { + 1.0 + (mant as f32) / (max_mant + 1) as f32 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + fn f32_to_gf(fmt: FormatDescriptor, value: f32) -> u64 { + if (value == 0.0) { + return 0; + } + + const sign = if (value < 0.0) { 1u64 } else { 0u64 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent + const exp_unbiased = floor_log2(abs_val) as i32; + let exp_biased = (exp_unbiased + fmt.exp_bias as i32) as u64; + + // Clamp exponent + const max_exp = (1u64 << fmt.exp_bits) - 1; + if (exp_biased > max_exp) { + exp_biased = max_exp; + } + + // Extract mantissa + const max_mant = (1u64 << fmt.mant_bits) - 1; + const normalized = abs_val / pow(2.0, exp_unbiased as f32); + const frac = normalized - 1.0; + const mant = (frac * (max_mant + 1) as f32) as u64; + const clamped_mant = if (mant > max_mant) { max_mant } else { mant }; + + return (sign << (fmt.bits - 1)) | + (exp_biased << fmt.mant_bits) | + clamped_mant; + } + + // ======================================================================== + // 13. IEEE 754 Conversion Functions (Stub) + // ======================================================================== + + fn ieee754_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Handle FP32 (direct reinterpret) + if (fmt.bits == 32 && fmt.name == "FP32") { + return @bitCast(f32, @as(u32, raw)); + } + + // Handle smaller formats (need conversion) + // This is a stub - real implementation would do proper conversion + return 0.0; + } + + fn f32_to_ieee754(fmt: FormatDescriptor, value: f32) -> u64 { + // Handle FP32 (direct reinterpret) + if (fmt.bits == 32 && fmt.name == "FP32") { + return @as(u64, @bitCast(u32, value)); + } + + // Handle smaller formats (need conversion) + // This is a stub - real implementation would do proper conversion + return 0; + } + + // ======================================================================== + // 14. Posit Conversion Functions (Stub) + // ======================================================================== + + fn posit_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Posit decode + // This is a stub - real implementation would do proper decoding + return 0.0; + } + + fn f32_to_posit(fmt: FormatDescriptor, value: f32) -> u64 { + // Posit encode + // This is a stub - real implementation would do proper encoding + return 0; + } + + // ======================================================================== + // 15. Integer Conversion Functions + // ======================================================================== + + fn int_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Sign-extend integer to f32 + const sign_bit = (raw >> (fmt.bits - 1)) as u8; + const magnitude = raw & ((1u64 << (fmt.bits - 1)) - 1); + const max_val = 1u64 << (fmt.bits - 1); + + if (sign_bit != 0) { + return -((max_val - magnitude) as f32); + } + return magnitude as f32; + } + + fn f32_to_int(fmt: FormatDescriptor, value: f32) -> u64 { + const max_val = (1i64 << (fmt.bits - 1)) - 1; + const min_val = -max_val - 1; + + let clamped = value as i64; + if (clamped > max_val) { + clamped = max_val; + } else if (clamped < min_val) { + clamped = min_val; + } + + if (clamped < 0) { + return ((1u64 << fmt.bits) + @as(u64, @bitCast(u64, clamped))); + } + return @as(u64, @bitCast(u64, clamped)); + } + + // ======================================================================== + // 16. NF4 Conversion Functions + // ======================================================================== + + fn nf4_to_f32(raw: u64) -> f32 { + const index = raw & 0xF; + return NF4_LEVELS[@as(usize, index)] as f32; + } + + fn f32_to_nf4(value: f32) -> u64 { + let best_idx : u64 = 0; + let best_dist : f64 = 1e9; + + var i : u64 = 0; + while (i < 16) { + const dist = abs(value - NF4_LEVELS[@as(usize, i)] as f32); + if (dist < best_dist) { + best_dist = dist; + best_idx = i; + } + i = i + 1; + } + + return best_idx; + } + + // ======================================================================== + // 17. Helper Functions + // ======================================================================== + + fn floor_log2(x: f32) -> i32 { + if (x <= 0.0) { return -2147483648; } + let exp : i32 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn pow(base: f32, exp: f32) -> f32 { + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; + } + + const is_integer = exp == @floor(exp); + + if (is_integer) { + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; + } + if (x == 1.0) { + return 0.0; + } + + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + + return 2.0 * (t + t3 / 3.0); + } + + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + let result = 1.0; + let term = 1.0; + let exp_x = x; + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + return result; + } + + fn @floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + fn abs(x: f32) -> f32 { + if (x < 0.0) { + return -x; + } + return x; + } + + // ======================================================================== + // TDD-Inside-Spec: Tests and Invariants for TRI_NET_FORMATS + // ======================================================================== + + test tri_net_formats_count_is_17 + given count = TRI_NET_FORMATS.len() + then count == 17 + + test tri_net_gf16_is_primary + given gf16 = get_format_by_name("GF16").? + then gf16.name == "GF16" and gf16.phi_dist < 0.05 + + test tri_net_gf12_has_best_phi_approximation + given best = get_phi_optimal_format() + then best.name == "GF12" and best.phi_dist < 0.05 + + test tri_net_all_formats_have_positive_bits + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.bits > 0; + } + + test tri_net_all_formats_sum_bits_correct + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.sign_bits + fmt.exp_bits + fmt.mant_bits == fmt.bits + or fmt.category == .posit; // Posit has different structure + } + + test tri_nft_n4_levels_count_is_16 + given count = NF4_LEVELS.len() + then count == 16 + + test tri_net_nf4_levels_symmetric + given center1 = NF4_LEVELS[6] + and center2 = NF4_LEVELS[7] + then abs(center1 + center2) < 1e-10 + + test tri_net_nf4_range_is_minus_one_to_one + given min_val = NF4_LEVELS[0] + and max_val = NF4_LEVELS[15] + then abs(min_val + 1.0) < 1e-10 and abs(max_val - 1.0) < 1e-10 + + test tri_nft_format_lookup_by_name_gf16 + given fmt = get_format_by_name("GF16") + then fmt != null and fmt.?.bits == 16 + + test tri_net_format_lookup_by_bits_16_returns_formats + given formats = get_format_by_bits(16) + and count = formats.len() + then count >= 3 // GF16, FP16, BF16, Binary16 + + test tri_nft_fp32_ieee_compliant + given fp32 = get_format_by_name("FP32").? + then fp32.ieee_compliant == true and fp32.category == .ieee754 + + test tri_net_bf16_not_ieee_compliant + given bf16 = get_format_by_name("BF16").? + then bf16.ieee_compliant == false and bf16.category == .ieee754 + + test tri_nft_fp8_e4m3_has_4_exp_3_mant + given e4m3 = get_format_by_name("FP8_E4M3").? + then e4m3.exp_bits == 4 and e4m3.mant_bits == 3 + + test tri_net_fp8_e5m2_has_5_exp_2_mant + given e5m2 = get_format_by_name("FP8_E5M2").? + then e5m2.exp_bits == 5 and e5m2.mant_bits == 2 + + test tri_net_gf64_has_24_exp_39_mant + given gf64 = get_format_by_name("GF64").? + then gf64.exp_bits == 24 and gf64.mant_bits == 39 + + test tri_net_gf128_has_48_exp_79_mant + given gf128 = get_format_by_name("GF128").? + then gf128.exp_bits == 48 and gf128.mant_bits == 79 + + test tri_net_gf256_has_97_exp_158_mant + given gf256 = get_format_by_name("GF256").? + then gf256.exp_bits == 97 and gf256.mant_bits == 158 + + test tri_net_posit16_has_category_posit + given posit16 = get_format_by_name("Posit16").? + then posit16.category == .posit + + test tri_net_binary16_same_as_fp16 + given binary16 = get_format_by_name("Binary16").? + and fp16 = get_format_by_name("FP16").? + then binary16.bits == fp16.bits and + binary16.exp_bits == fp16.exp_bits and + binary16.mant_bits == fp16.mant_bits + + test tri_net_nf4_to_f32_negative + given value = nf4_to_f32(0) + then value < 0.0 and abs(value + 1.0) < 1e-10 + + test tri_net_nf4_to_f32_positive + given value = nf4_to_f32(15) + then value > 0.0 and abs(value - 1.0) < 1e-10 + + test tri_net_nf4_to_f32_center + given value1 = nf4_to_f32(6) + and value2 = nf4_to_f32(7) + then abs(value1 + value2) < 1e-10 + + test tri_net_f32_to_nf4_positive + given result = f32_to_nf4(1.0) + then result == 15 + + test tri_net_f32_to_nf4_negative + given result = f32_to_nf4(-1.0) + then result == 0 + + test tri_net_f32_to_nf4_zero + given result = f32_to_nf4(0.0) + then result == 6 or result == 7 // Either center value + + invariant tri_net_all_formats_have_valid_category + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.category >= .goldenfloat and fmt.category <= .binary; + } + + invariant tri_net_all_phi_distances_non_negative + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.phi_dist >= 0.0; + } + + invariant tri_nft_all_memory_ratios_positive + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.memory_ratio > 0.0; + } + + invariant tri_net_gf_formats_have_best_phi_distance + const golden_fmts = get_format_by_bits(16); // GF16 vs others + // GF family should have better phi distance than IEEE formats + // (This is a rough check - detailed comparison would need more logic) + + invariant tri_net_nf4_levels_are_monotonic + for (i in 0..15) { + if (i < 15) { + assert NF4_LEVELS[i] < NF4_LEVELS[i + 1]; + } + } + + invariant tri_net_format_registry_consistent + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.name.len() > 0; + assert fmt.bits >= 4; + assert fmt.sign_bits == 1; + } + + bench tri_net_get_format_by_name_latency + measure: nanoseconds to get_format_by_name("GF16") + target: < 100ns + + bench tri_net_get_phi_optimal_format_latency + measure: nanoseconds to get_phi_optimal_format() + target: < 200ns + + bench tri_net_f32_to_nf4_latency + measure: nanoseconds to f32_to_nf4(0.5) + target: < 100ns + + bench tri_net_nf4_to_f32_latency + measure: nanoseconds to nf4_to_f32(8) + target: < 50ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/avs_controller_48.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/avs_controller_48.t27 new file mode 100644 index 0000000000..c528b272e3 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/avs_controller_48.t27 @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: Apache-2.0 +; avs_controller_48.t27 — 48-pin AVS Controller +; Adaptive voltage scaling controller for 48-pin configuration +; φ² + 1/φ² = 3 | TRINITY + +module avs-controller-48; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const PINS_48 : u8 = 48; +pub const VOLTAGE_MIN_MV : u16 = 800; // 0.8V minimum +pub const VOLTAGE_MAX_MV : u16 = 1200; // 1.2V maximum +pub const VOLTAGE_STEP_MV : u16 = 10; // 10mV step size +pub const VOLTAGE_LEVELS : u8 = 41; // (1200-800)/10 + 1 + +pub const TARGET_MV : u16 = 1000; // 1.0V target +pub const HYSTERESIS_MV : u16 = 20; // 20mV hysteresis +pub const SETTLING_US : u16 = 100; // 100us settling time + +pub const CONTROL_PIN_OFFSET : u8 = 16; // AVS control pins start at 16 +pub const CONTROL_PIN_COUNT : u8 = 6; // 6 control pins for 48-pin config + +pub const FREQ_MIN_KHZ : u32 = 10000; // 10 MHz minimum +pub const FREQ_MAX_KHZ : u32 = 100000; // 100 MHz maximum + +// ============================================================================ +// Types +// ============================================================================ + +pub const AvsState = enum(u8) { + disabled = 0, + enabled = 1, + adjusting = 2, + settled = 3, + fault = 4, +} + +pub const AvsMode = enum(u8) { + manual = 0, + automatic = 1, + adaptive = 2, +} + +pub const AvsPinConfig = struct { + pin_index : u8, + is_output : bool, + function : u8, // 0=control, 1=feedback, 2=enable +} + +pub const AvsVoltageLevel = struct { + level : u8, // 0 to 40 + voltage_mv : u16, + control_code : u8, // 6-bit code for control pins +} + +pub const AvsStatus = struct { + state : AvsState, + mode : AvsMode, + current_mv : u16, + target_mv : u16, + level : u8, + fault_code : u8, +} + +pub const AvsConfig = struct { + mode : AvsMode, + min_mv : u16, + max_mv : u16, + target_mv : u16, + hysteresis_mv : u16, + settling_us : u16, +} + +// ============================================================================ +// Voltage Level Functions +// ============================================================================ + +// avs_voltage_from_level(level: u8) -> u16 +// Convert voltage level to millivolts +pub fn avs_voltage_from_level(level: u8) -> u16 { + return VOLTAGE_MIN_MV + @as(u16, level) * VOLTAGE_STEP_MV; +} + +// avs_level_from_voltage(voltage_mv: u16) -> u8 +// Convert voltage to level +pub fn avs_level_from_voltage(voltage_mv: u16) -> u8 { + var mv = voltage_mv; + if (mv < VOLTAGE_MIN_MV) { + mv = VOLTAGE_MIN_MV; + } else if (mv > VOLTAGE_MAX_MV) { + mv = VOLTAGE_MAX_MV; + } + return @as(u8, (mv - VOLTAGE_MIN_MV) / VOLTAGE_STEP_MV); +} + +// avs_control_code_from_level(level: u8) -> u8 +// Generate 6-bit control code for voltage level +pub fn avs_control_code_from_level(level: u8) -> u8 { + // Simple binary encoding for 6 pins + return level & 0x3F; +} + +// avs_level_from_control_code(code: u8) -> u8 +// Decode level from 6-bit control code +pub fn avs_level_from_control_code(code: u8) -> u8 { + return code & 0x3F; +} + +// ============================================================================ +// Voltage Validation +// ============================================================================ + +// avs_voltage_valid(voltage_mv: u16) -> bool +// Check if voltage is within valid range +pub fn avs_voltage_valid(voltage_mv: u16) -> bool { + return voltage_mv >= VOLTAGE_MIN_MV and voltage_mv <= VOLTAGE_MAX_MV; +} + +// avs_voltage_in_hysteresis(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool +// Check if current voltage is within hysteresis band of target +pub fn avs_voltage_in_hysteresis(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool { + if (current_mv > target_mv) { + return (current_mv - target_mv) <= hysteresis_mv; + } else { + return (target_mv - current_mv) <= hysteresis_mv; + } +} + +// avs_should_adjust(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool +// Check if voltage should be adjusted +pub fn avs_should_adjust(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool { + return not avs_voltage_in_hysteresis(current_mv, target_mv, hysteresis_mv); +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// avs_config_init() -> AvsConfig +// Initialize AVS configuration +pub fn avs_config_init() -> AvsConfig { + return AvsConfig { + .mode = AvsMode.automatic, + .min_mv = VOLTAGE_MIN_MV, + .max_mv = VOLTAGE_MAX_MV, + .target_mv = TARGET_MV, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + }; +} + +// avs_config_manual(target_mv: u16) -> AvsConfig +// Create manual AVS configuration +pub fn avs_config_manual(target_mv: u16) -> AvsConfig { + return AvsConfig { + .mode = AvsMode.manual, + .min_mv = VOLTAGE_MIN_MV, + .max_mv = VOLTAGE_MAX_MV, + .target_mv = target_mv, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + }; +} + +// avs_config_adaptive(min_mv: u16, max_mv: u16) -> AvsConfig +// Create adaptive AVS configuration +pub fn avs_config_adaptive(min_mv: u16, max_mv: u16) -> AvsConfig { + return AvsConfig { + .mode = AvsMode.adaptive, + .min_mv = min_mv, + .max_mv = max_mv, + .target_mv = (min_mv + max_mv) / 2, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + }; +} + +// ============================================================================ +// Status Functions +// ============================================================================ + +// avs_status_init() -> AvsStatus +// Initialize AVS status +pub fn avs_status_init() -> AvsStatus { + return AvsStatus { + .state = AvsState.disabled, + .mode = AvsMode.manual, + .current_mv = VOLTAGE_MIN_MV, + .target_mv = TARGET_MV, + .level = 0, + .fault_code = 0, + }; +} + +// avs_status_enable(status: AvsStatus, config: AvsConfig) -> AvsStatus +// Enable AVS controller +pub fn avs_status_enable(status: AvsStatus, config: AvsConfig) -> AvsStatus { + return AvsStatus { + .state = AvsState.enabled, + .mode = config.mode, + .current_mv = status.current_mv, + .target_mv = config.target_mv, + .level = status.level, + .fault_code = 0, + }; +} + +// avs_status_adjust(status: AvsStatus, new_level: u8) -> AvsStatus +// Adjust to new voltage level +pub fn avs_status_adjust(status: AvsStatus, new_level: u8) -> AvsStatus { + const new_mv = avs_voltage_from_level(new_level); + return AvsStatus { + .state = AvsState.adjusting, + .mode = status.mode, + .current_mv = status.current_mv, + .target_mv = new_mv, + .level = new_level, + .fault_code = status.fault_code, + }; +} + +// avs_status_settled(status: AvsStatus, measured_mv: u16) -> AvsStatus +// Mark voltage as settled +pub fn avs_status_settled(status: AvsStatus, measured_mv: u16) -> AvsStatus { + return AvsStatus { + .state = AvsState.settled, + .mode = status.mode, + .current_mv = measured_mv, + .target_mv = status.target_mv, + .level = avs_level_from_voltage(measured_mv), + .fault_code = status.fault_code, + }; +} + +// avs_status_fault(status: AvsStatus, fault_code: u8) -> AvsStatus +// Mark fault condition +pub fn avs_status_fault(status: AvsStatus, fault_code: u8) -> AvsStatus { + return AvsStatus { + .state = AvsState.fault, + .mode = status.mode, + .current_mv = status.current_mv, + .target_mv = status.target_mv, + .level = status.level, + .fault_code = fault_code, + }; +} + +// ============================================================================ +// Pin Functions +// ============================================================================ + +// avs_control_pins_start() -> u8 +// Get starting pin index for control pins +pub fn avs_control_pins_start() -> u8 { + return CONTROL_PIN_OFFSET; +} + +// avs_control_pins_end() -> u8 +// Get ending pin index for control pins +pub fn avs_control_pins_end() -> u8 { + return CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT - 1; +} + +// avs_is_control_pin(pin_index: u8) -> bool +// Check if pin is a control pin +pub fn avs_is_control_pin(pin_index: u8) -> bool { + return pin_index >= avs_control_pins_start() and pin_index <= avs_control_pins_end(); +} + +// avs_control_pin_for_bit(bit_index: u8) -> u8 +// Get control pin for a bit index (0-5) +pub fn avs_control_pin_for_bit(bit_index: u8) -> u8 { + if (bit_index >= CONTROL_PIN_COUNT) { + return CONTROL_PIN_OFFSET; + } + return CONTROL_PIN_OFFSET + bit_index; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_avs_48_cmd(opcode: u8, level: u8, reserved: u8) -> u16 +// Encode AVS 48-pin command +pub fn encode_avs_48_cmd(opcode: u8, level: u8, reserved: u8) u16 { + // Format: [OP:8][LEVEL:6][RES:2] + const op_field : u16 = @as(u16, opcode) << 8; + const level_field : u16 = @as(u16, level & 0x3F) << 2; + const res_field : u16 = @as(u16, reserved & 0x03); + return op_field | level_field | res_field; +} + +// decode_avs_48_cmd(encoded: u16) -> struct { opcode: u8, level: u8, reserved: u8 } +// Decode AVS 48-pin command +pub fn decode_avs_48_cmd(encoded: u16) -> struct { opcode: u8, level: u8, reserved: u8 } { + const opcode : u8 = @as(u8, @truncate((encoded >> 8) & 0xFF)); + const level : u8 = @as(u8, @truncate((encoded >> 2) & 0x3F)); + const reserved : u8 = @as(u8, @truncate(encoded & 0x03)); + return .{ .opcode = opcode, .level = level, .reserved = reserved }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "pins_48_constant" { + try std.testing.expect(PINS_48 == 48); +} + +test "voltage_constants" { + try std.testing.expect(VOLTAGE_MIN_MV == 800); + try std.testing.expect(VOLTAGE_MAX_MV == 1200); + try std.testing.expect(VOLTAGE_STEP_MV == 10); + try std.testing.expect(VOLTAGE_LEVELS == 41); +} + +test "target_voltage_constant" { + try std.testing.expect(TARGET_MV == 1000); +} + +test "control_pin_constants" { + try std.testing.expect(CONTROL_PIN_OFFSET == 16); + try std.testing.expect(CONTROL_PIN_COUNT == 6); +} + +test "avs_voltage_from_level_zero" { + try std.testing.expect(avs_voltage_from_level(0) == VOLTAGE_MIN_MV); +} + +test "avs_voltage_from_level_max" { + given max_level = VOLTAGE_LEVELS - 1 + try std.testing.expect(avs_voltage_from_level(max_level) == VOLTAGE_MAX_MV); +} + +test "avs_voltage_from_level_target" { + given level = avs_level_from_voltage(TARGET_MV) + try std.testing.expect(avs_voltage_from_level(level) == TARGET_MV); +} + +test "avs_level_from_voltage_min" { + try std.testing.expect(avs_level_from_voltage(VOLTAGE_MIN_MV) == 0); +} + +test "avs_level_from_voltage_max" { + try std.testing.expect(avs_level_from_voltage(VOLTAGE_MAX_MV) == VOLTAGE_LEVELS - 1); +} + +test "avs_level_from_voltage_clamp_low" { + try std.testing.expect(avs_level_from_voltage(700) == 0); +} + +test "avs_level_from_voltage_clamp_high" { + try std.testing.expect(avs_level_from_voltage(1500) == VOLTAGE_LEVELS - 1); +} + +test "avs_control_code_from_level_zero" { + try std.testing.expect(avs_control_code_from_level(0) == 0); +} + +test "avs_control_code_from_level_max" { + given code = avs_control_code_from_level(63) + try std.testing.expect(code == 63); +} + +test "avs_control_code_from_level_clamp" { + given code = avs_control_code_from_level(100) + try std.testing.expect(code == 36 // 100 & 0x3F); +} + +test "avs_level_from_control_code_roundtrip" { + given level = 20 + try std.testing.expect(code = avs_control_code_from_level(level)); + try std.testing.expect(decoded = avs_level_from_control_code(code)); + try std.testing.expect(decoded == level); +} + +test "avs_voltage_valid_in_range" { + try std.testing.expect(avs_voltage_valid(900) == true); + try std.testing.expect(avs_voltage_valid(1000) == true); + try std.testing.expect(avs_voltage_valid(1100) == true); +} + +test "avs_voltage_valid_out_of_range" { + try std.testing.expect(avs_voltage_valid(700) == false); + try std.testing.expect(avs_voltage_valid(1300) == false); +} + +test "avs_voltage_in_hysteresis_true" { + try std.testing.expect(avs_voltage_in_hysteresis(1000, 1000, 20) == true); + try std.testing.expect(avs_voltage_in_hysteresis(1015, 1000, 20) == true); + try std.testing.expect(avs_voltage_in_hysteresis(985, 1000, 20) == true); +} + +test "avs_voltage_in_hysteresis_false" { + try std.testing.expect(avs_voltage_in_hysteresis(1050, 1000, 20) == false); + try std.testing.expect(avs_voltage_in_hysteresis(950, 1000, 20) == false); +} + +test "avs_should_adjust_false_in_hysteresis" { + try std.testing.expect(avs_should_adjust(1010, 1000, 20) == false); +} + +test "avs_should_adjust_true_out_of_hysteresis" { + try std.testing.expect(avs_should_adjust(1050, 1000, 20) == true); +} + +test "avs_config_init_structure" { + given config = avs_config_init() + try std.testing.expect(config.mode == AvsMode.automatic); + try std.testing.expect(config.target_mv == TARGET_MV); + try std.testing.expect(config.hysteresis_mv == HYSTERESIS_MV); +} + +test "avs_config_manual_structure" { + given config = avs_config_manual(900) + try std.testing.expect(config.mode == AvsMode.manual); + try std.testing.expect(config.target_mv == 900); +} + +test "avs_config_adaptive_structure" { + given config = avs_config_adaptive(850, 1150) + try std.testing.expect(config.mode == AvsMode.adaptive); + try std.testing.expect(config.min_mv == 850); + try std.testing.expect(config.max_mv == 1150); +} + +test "avs_status_init_structure" { + given status = avs_status_init() + try std.testing.expect(status.state == AvsState.disabled); + try std.testing.expect(status.current_mv == VOLTAGE_MIN_MV); +} + +test "avs_status_enable" { + given status = avs_status_init() + try std.testing.expect(config = avs_config_init()); + try std.testing.expect(result = avs_status_enable(status, config)); + try std.testing.expect(result.state == AvsState.enabled); + try std.testing.expect(result.mode == AvsMode.automatic); +} + +test "avs_status_adjust" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_adjust(status, 10)); + try std.testing.expect(result.state == AvsState.adjusting); + try std.testing.expect(result.level == 10); +} + +test "avs_status_settled" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_settled(status, 1000)); + try std.testing.expect(result.state == AvsState.settled); + try std.testing.expect(result.current_mv == 1000); +} + +test "avs_status_fault" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_fault(status, 1)); + try std.testing.expect(result.state == AvsState.fault); + try std.testing.expect(result.fault_code == 1); +} + +test "avs_control_pins_start" { + try std.testing.expect(avs_control_pins_start() == CONTROL_PIN_OFFSET); +} + +test "avs_control_pins_end" { + try std.testing.expect(avs_control_pins_end() == CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT - 1); +} + +test "avs_is_control_pin_true" { + try std.testing.expect(avs_is_control_pin(16) == true); + try std.testing.expect(avs_is_control_pin(21) == true); +} + +test "avs_is_control_pin_false" { + try std.testing.expect(avs_is_control_pin(15) == false); + try std.testing.expect(avs_is_control_pin(22) == false); +} + +test "avs_control_pin_for_bit" { + try std.testing.expect(avs_control_pin_for_bit(0) == CONTROL_PIN_OFFSET); + try std.testing.expect(avs_control_pin_for_bit(5) == CONTROL_PIN_OFFSET + 5); +} + +test "avs_control_pin_for_bit_clamp" { + try std.testing.expect(avs_control_pin_for_bit(10) == CONTROL_PIN_OFFSET); +} + +test "encode_avs_48_cmd" { + given encoded = encode_avs_48_cmd(0x10, 20, 0) + try std.testing.expect((encoded >> 8) == 0x10); +} + +test "decode_avs_48_cmd" { + given decoded = decode_avs_48_cmd(0x1050) + try std.testing.expect(decoded.opcode == 0x10); + try std.testing.expect(decoded.level == 20); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant voltage_range_positive + assert VOLTAGE_MIN_MV > 0 and VOLTAGE_MAX_MV > VOLTAGE_MIN_MV + +invariant voltage_step_positive + assert VOLTAGE_STEP_MV > 0 + +invariant voltage_levels_calculated + try std.testing.expect(VOLTAGE_LEVELS == ((VOLTAGE_MAX_MV - VOLTAGE_MIN_MV) / VOLTAGE_STEP_MV) + 1); + +invariant target_voltage_in_range + assert avs_voltage_valid(TARGET_MV) + +invariant hysteresis_less_than_step + assert HYSTERESIS_MV < VOLTAGE_STEP_MV * 3 + +invariant control_pins_valid_range + assert CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT <= PINS_48 + +invariant control_pin_count_six + assert CONTROL_PIN_COUNT == 6 + +invariant avs_voltage_from_level_roundtrip + given level = 15 + try std.testing.expect(voltage = avs_voltage_from_level(level)); + try std.testing.expect(result_level = avs_level_from_voltage(voltage)); + try std.testing.expect(result_level == level); + +invariant avs_voltage_in_hysteresis_exact + try std.testing.expect(avs_voltage_in_hysteresis(TARGET_MV, TARGET_MV, 0) == true); + +invariant avs_should_adjust_false_at_exact_target + try std.testing.expect(avs_should_adjust(TARGET_MV, TARGET_MV, HYSTERESIS_MV) == false); + +invariant avs_status_init_disabled + given status = avs_status_init() + try std.testing.expect(status.state == AvsState.disabled); + +invariant avs_status_enable_sets_mode + given status = avs_status_init() + try std.testing.expect(config = avs_config_manual(950)); + try std.testing.expect(result = avs_status_enable(status, config)); + try std.testing.expect(result.mode == AvsMode.manual); + +invariant avs_status_adjust_changes_level + given status = avs_status_init() + try std.testing.expect(result = avs_status_adjust(status, 25)); + try std.testing.expect(result.level == 25); + +invariant avs_is_control_pin_monotonic + try std.testing.expect(avs_control_pin_for_bit(3) > avs_control_pin_for_bit(2)); + +invariant avs_control_pin_for_bit_start_at_offset + try std.testing.expect(avs_control_pin_for_bit(0) == CONTROL_PIN_OFFSET); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench avs_voltage_from_level_latency + measure: nanoseconds to avs_voltage_from_level(20) + target: < 20ns + +bench avs_level_from_voltage_latency + measure: nanoseconds to avs_level_from_voltage(1000) + target: < 20ns + +bench avs_control_code_from_level_latency + measure: nanoseconds to avs_control_code_from_level(20) + target: < 15ns + +bench avs_voltage_valid_latency + measure: nanoseconds to avs_voltage_valid(1000) + target: < 15ns + +bench avs_voltage_in_hysteresis_latency + measure: nanoseconds to avs_voltage_in_hysteresis(1000, 1000, 20) + target: < 25ns + +bench avs_config_init_latency + measure: nanoseconds to avs_config_init() + target: < 30ns + +bench avs_status_init_latency + measure: nanoseconds to avs_status_init() + target: < 30ns + +bench avs_status_adjust_latency + measure: nanoseconds to avs_status_adjust(avs_status_init(), 20) + target: < 30ns + +bench avs_is_control_pin_latency + measure: nanoseconds to avs_is_control_pin(18) + target: < 15ns + +bench encode_avs_48_cmd_latency + measure: nanoseconds to encode_avs_48_cmd(0x10, 20, 0) + target: < 20ns + +bench decode_avs_48_cmd_latency + measure: nanoseconds to decode_avs_48_cmd(0x1050) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/avs_controller_96.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/avs_controller_96.t27 new file mode 100644 index 0000000000..16b47c6bdc --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/avs_controller_96.t27 @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: Apache-2.0 +; avs_controller_96.t27 — 96-pin AVS Controller +; Adaptive voltage scaling controller for 96-pin configuration +; φ² + 1/φ² = 3 | TRINITY + +module avs-controller-96; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const PINS_96 : u8 = 96; +pub const VOLTAGE_MIN_MV : u16 = 700; // 0.7V minimum +pub const VOLTAGE_MAX_MV : u16 = 1300; // 1.3V maximum +pub const VOLTAGE_STEP_MV : u16 = 5; // 5mV step size +pub const VOLTAGE_LEVELS : u8 = 121; // (1300-700)/5 + 1 + +pub const TARGET_MV : u16 = 1000; // 1.0V target +pub const HYSTERESIS_MV : u16 = 15; // 15mV hysteresis +pub const SETTLING_US : u16 = 80; // 80us settling time + +pub const CONTROL_PIN_OFFSET : u8 = 64; // AVS control pins start at 64 +pub const CONTROL_PIN_COUNT : u8 = 7; // 7 control pins for 96-pin config + +pub const FREQ_MIN_KHZ : u32 = 10000; // 10 MHz minimum +pub const FREQ_MAX_KHZ : u32 = 200000; // 200 MHz maximum + +pub const DUAL_CHANNEL : bool = true; // 96-pin supports dual channels +pub const CHANNEL_PIN_GROUPS : u8 = 2; // 2 groups of control pins + +// ============================================================================ +// Types +// ============================================================================ + +pub const AvsChannel = enum(u8) { + channel0 = 0, + channel1 = 1, + both = 2, +} + +pub const AvsState = enum(u8) { + disabled = 0, + enabled = 1, + adjusting = 2, + settled = 3, + fault = 4, + calibrating = 5, // 96-pin supports calibration +} + +pub const AvsMode = enum(u8) { + manual = 0, + automatic = 1, + adaptive = 2, + dual_independent = 3, // 96-pin exclusive mode +} + +pub const AvsChannelConfig = struct { + channel : AvsChannel, + enabled : bool, + target_mv : u16, + current_mv : u16, + level : u8, +} + +pub const AvsPinConfig = struct { + pin_index : u8, + is_output : bool, + function : u8, // 0=control, 1=feedback, 2=enable, 3=calibration +} + +pub const AvsVoltageLevel = struct { + level : u8, // 0 to 120 + voltage_mv : u16, + control_code : u8, // 7-bit code for control pins +} + +pub const AvsStatus = struct { + state : AvsState, + mode : AvsMode, + channel0 : AvsChannelConfig, + channel1 : AvsChannelConfig, + fault_code : u8, + calibration_complete : bool, +} + +pub const AvsConfig = struct { + mode : AvsMode, + min_mv : u16, + max_mv : u16, + target_mv : u16, + hysteresis_mv : u16, + settling_us : u16, + enable_dual_channel : bool, +} + +// ============================================================================ +// Voltage Level Functions +// ============================================================================ + +// avs_voltage_from_level(level: u8) -> u16 +// Convert voltage level to millivolts +pub fn avs_voltage_from_level(level: u8) -> u16 { + return VOLTAGE_MIN_MV + @as(u16, level) * VOLTAGE_STEP_MV; +} + +// avs_level_from_voltage(voltage_mv: u16) -> u8 +// Convert voltage to level +pub fn avs_level_from_voltage(voltage_mv: u16) -> u8 { + var mv = voltage_mv; + if (mv < VOLTAGE_MIN_MV) { + mv = VOLTAGE_MIN_MV; + } else if (mv > VOLTAGE_MAX_MV) { + mv = VOLTAGE_MAX_MV; + } + return @as(u8, (mv - VOLTAGE_MIN_MV) / VOLTAGE_STEP_MV); +} + +// avs_control_code_from_level(level: u8) -> u8 +// Generate 7-bit control code for voltage level +pub fn avs_control_code_from_level(level: u8) -> u8 { + return level & 0x7F; +} + +// avs_level_from_control_code(code: u8) -> u8 +// Decode level from 7-bit control code +pub fn avs_level_from_control_code(code: u8) -> u8 { + return code & 0x7F; +} + +// ============================================================================ +// Voltage Validation +// ============================================================================ + +// avs_voltage_valid(voltage_mv: u16) -> bool +// Check if voltage is within valid range +pub fn avs_voltage_valid(voltage_mv: u16) -> bool { + return voltage_mv >= VOLTAGE_MIN_MV and voltage_mv <= VOLTAGE_MAX_MV; +} + +// avs_voltage_in_hysteresis(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool +// Check if current voltage is within hysteresis band of target +pub fn avs_voltage_in_hysteresis(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool { + if (current_mv > target_mv) { + return (current_mv - target_mv) <= hysteresis_mv; + } else { + return (target_mv - current_mv) <= hysteresis_mv; + } +} + +// avs_should_adjust(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool +// Check if voltage should be adjusted +pub fn avs_should_adjust(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool { + return not avs_voltage_in_hysteresis(current_mv, target_mv, hysteresis_mv); +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// avs_config_init() -> AvsConfig +// Initialize AVS configuration +pub fn avs_config_init() -> AvsConfig { + return AvsConfig { + .mode = AvsMode.automatic, + .min_mv = VOLTAGE_MIN_MV, + .max_mv = VOLTAGE_MAX_MV, + .target_mv = TARGET_MV, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + .enable_dual_channel = false, + }; +} + +// avs_config_dual(target0_mv: u16, target1_mv: u16) -> AvsConfig +// Create dual-channel AVS configuration +pub fn avs_config_dual(target0_mv: u16, target1_mv: u16) -> AvsConfig { + return AvsConfig { + .mode = AvsMode.dual_independent, + .min_mv = VOLTAGE_MIN_MV, + .max_mv = VOLTAGE_MAX_MV, + .target_mv = (target0_mv + target1_mv) / 2, // Average target + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + .enable_dual_channel = true, + }; +} + +// avs_config_adaptive(min_mv: u16, max_mv: u16) -> AvsConfig +// Create adaptive AVS configuration +pub fn avs_config_adaptive(min_mv: u16, max_mv: u16) -> AvsConfig { + return AvsConfig { + .mode = AvsMode.adaptive, + .min_mv = min_mv, + .max_mv = max_mv, + .target_mv = (min_mv + max_mv) / 2, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + .enable_dual_channel = false, + }; +} + +// ============================================================================ +// Channel Functions +// ============================================================================ + +// avs_channel_init(channel: AvsChannel) -> AvsChannelConfig +// Initialize channel configuration +pub fn avs_channel_init(channel: AvsChannel) -> AvsChannelConfig { + return AvsChannelConfig { + .channel = channel, + .enabled = false, + .target_mv = TARGET_MV, + .current_mv = VOLTAGE_MIN_MV, + .level = 0, + }; +} + +// avs_channel_enable(channel: AvsChannelConfig, target_mv: u16) -> AvsChannelConfig +// Enable channel +pub fn avs_channel_enable(channel: AvsChannelConfig, target_mv: u16) -> AvsChannelConfig { + return AvsChannelConfig { + .channel = channel.channel, + .enabled = true, + .target_mv = target_mv, + .current_mv = channel.current_mv, + .level = channel.level, + }; +} + +// avs_channel_adjust(channel: AvsChannelConfig, new_level: u8) -> AvsChannelConfig +// Adjust channel to new level +pub fn avs_channel_adjust(channel: AvsChannelConfig, new_level: u8) -> AvsChannelConfig { + const new_mv = avs_voltage_from_level(new_level); + return AvsChannelConfig { + .channel = channel.channel, + .enabled = channel.enabled, + .target_mv = channel.target_mv, + .current_mv = new_mv, + .level = new_level, + }; +} + +// ============================================================================ +// Status Functions +// ============================================================================ + +// avs_status_init() -> AvsStatus +// Initialize AVS status +pub fn avs_status_init() -> AvsStatus { + return AvsStatus { + .state = AvsState.disabled, + .mode = AvsMode.manual, + .channel0 = avs_channel_init(AvsChannel.channel0), + .channel1 = avs_channel_init(AvsChannel.channel1), + .fault_code = 0, + .calibration_complete = false, + }; +} + +// avs_status_enable(status: AvsStatus, config: AvsConfig) -> AvsStatus +// Enable AVS controller +pub fn avs_status_enable(status: AvsStatus, config: AvsConfig) -> AvsStatus { + var ch0 = status.channel0; + var ch1 = status.channel1; + + if (config.enable_dual_channel) { + ch0 = avs_channel_enable(ch0, config.target_mv); + ch1 = avs_channel_enable(ch1, config.target_mv); + } else { + ch0 = avs_channel_enable(ch0, config.target_mv); + } + + return AvsStatus { + .state = AvsState.enabled, + .mode = config.mode, + .channel0 = ch0, + .channel1 = ch1, + .fault_code = 0, + .calibration_complete = status.calibration_complete, + }; +} + +// avs_status_calibrate(status: AvsStatus) -> AvsStatus +// Enter calibration mode +pub fn avs_status_calibrate(status: AvsStatus) -> AvsStatus { + return AvsStatus { + .state = AvsState.calibrating, + .mode = status.mode, + .channel0 = status.channel0, + .channel1 = status.channel1, + .fault_code = status.fault_code, + .calibration_complete = false, + }; +} + +// avs_status_calibration_complete(status: AvsStatus) -> AvsStatus +// Mark calibration complete +pub fn avs_status_calibration_complete(status: AvsStatus) -> AvsStatus { + return AvsStatus { + .state = AvsState.enabled, + .mode = status.mode, + .channel0 = status.channel0, + .channel1 = status.channel1, + .fault_code = status.fault_code, + .calibration_complete = true, + }; +} + +// avs_status_fault(status: AvsStatus, fault_code: u8) -> AvsStatus +// Mark fault condition +pub fn avs_status_fault(status: AvsStatus, fault_code: u8) -> AvsStatus { + return AvsStatus { + .state = AvsState.fault, + .mode = status.mode, + .channel0 = status.channel0, + .channel1 = status.channel1, + .fault_code = fault_code, + .calibration_complete = status.calibration_complete, + }; +} + +// ============================================================================ +// Pin Functions +// ============================================================================ + +// avs_control_pins_start() -> u8 +// Get starting pin index for control pins +pub fn avs_control_pins_start() -> u8 { + return CONTROL_PIN_OFFSET; +} + +// avs_control_pins_end() -> u8 +// Get ending pin index for control pins (primary) +pub fn avs_control_pins_end() -> u8 { + return CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT - 1; +} + +// avs_control_pins_secondary_start() -> u8 +// Get starting pin index for secondary channel +pub fn avs_control_pins_secondary_start() -> u8 { + return CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT; +} + +// avs_control_pins_secondary_end() -> u8 +// Get ending pin index for secondary channel +pub fn avs_control_pins_secondary_end() -> u8 { + return avs_control_pins_secondary_start() + CONTROL_PIN_COUNT - 1; +} + +// avs_is_control_pin(pin_index: u8) -> bool +// Check if pin is a control pin (either channel) +pub fn avs_is_control_pin(pin_index: u8) -> bool { + return avs_is_control_pin_primary(pin_index) or avs_is_control_pin_secondary(pin_index); +} + +// avs_is_control_pin_primary(pin_index: u8) -> bool +// Check if pin is a primary control pin +pub fn avs_is_control_pin_primary(pin_index: u8) -> bool { + return pin_index >= avs_control_pins_start() and pin_index <= avs_control_pins_end(); +} + +// avs_is_control_pin_secondary(pin_index: u8) -> bool +// Check if pin is a secondary control pin +pub fn avs_is_control_pin_secondary(pin_index: u8) -> bool { + return pin_index >= avs_control_pins_secondary_start() and pin_index <= avs_control_pins_secondary_end(); +} + +// avs_control_pin_for_bit(bit_index: u8, channel: AvsChannel) -> u8 +// Get control pin for a bit index and channel +pub fn avs_control_pin_for_bit(bit_index: u8, channel: AvsChannel) -> u8 { + if (bit_index >= CONTROL_PIN_COUNT) { + bit_index = 0; + } + + if (channel == AvsChannel.channel1) { + return avs_control_pins_secondary_start() + bit_index; + } else { + return avs_control_pins_start() + bit_index; + } +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_avs_96_cmd(opcode: u8, level: u8, channel: u8) -> u16 +// Encode AVS 96-pin command +pub fn encode_avs_96_cmd(opcode: u8, level: u8, channel: u8) u16 { + // Format: [OP:8][LEVEL:7][CHANNEL:1] + const op_field : u16 = @as(u16, opcode) << 8; + const level_field : u16 = @as(u16, level & 0x7F) << 1; + const channel_field : u16 = @as(u16, channel & 0x01); + return op_field | level_field | channel_field; +} + +// decode_avs_96_cmd(encoded: u16) -> struct { opcode: u8, level: u8, channel: u8 } +// Decode AVS 96-pin command +pub fn decode_avs_96_cmd(encoded: u16) -> struct { opcode: u8, level: u8, channel: u8 } { + const opcode : u8 = @as(u8, @truncate((encoded >> 8) & 0xFF)); + const level : u8 = @as(u8, @truncate((encoded >> 1) & 0x7F)); + const channel : u8 = @as(u8, @truncate(encoded & 0x01)); + return .{ .opcode = opcode, .level = level, .channel = channel }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "pins_96_constant" { + try std.testing.expect(PINS_96 == 96); +} + +test "voltage_constants" { + try std.testing.expect(VOLTAGE_MIN_MV == 700); + try std.testing.expect(VOLTAGE_MAX_MV == 1300); + try std.testing.expect(VOLTAGE_STEP_MV == 5); + try std.testing.expect(VOLTAGE_LEVELS == 121); +} + +test "target_voltage_constant" { + try std.testing.expect(TARGET_MV == 1000); +} + +test "control_pin_constants" { + try std.testing.expect(CONTROL_PIN_OFFSET == 64); + try std.testing.expect(CONTROL_PIN_COUNT == 7); +} + +test "dual_channel_constant" { + assert DUAL_CHANNEL == true +} + +test "avs_voltage_from_level_zero" { + try std.testing.expect(avs_voltage_from_level(0) == VOLTAGE_MIN_MV); +} + +test "avs_voltage_from_level_max" { + given max_level = VOLTAGE_LEVELS - 1 + try std.testing.expect(avs_voltage_from_level(max_level) == VOLTAGE_MAX_MV); +} + +test "avs_level_from_voltage_target" { + given level = avs_level_from_voltage(TARGET_MV) + try std.testing.expect(avs_voltage_from_level(level) == TARGET_MV); +} + +test "avs_level_from_voltage_clamp_low" { + try std.testing.expect(avs_level_from_voltage(600) == 0); +} + +test "avs_level_from_voltage_clamp_high" { + try std.testing.expect(avs_level_from_voltage(1500) == VOLTAGE_LEVELS - 1); +} + +test "avs_control_code_from_level_max" { + given code = avs_control_code_from_level(127) + try std.testing.expect(code == 127); +} + +test "avs_control_code_from_level_clamp" { + given code = avs_control_code_from_level(200) + try std.testing.expect(code == 72 // 200 & 0x7F); +} + +test "avs_level_from_control_code_roundtrip" { + given level = 50 + try std.testing.expect(code = avs_control_code_from_level(level)); + try std.testing.expect(decoded = avs_level_from_control_code(code)); + try std.testing.expect(decoded == level); +} + +test "avs_voltage_valid_in_range" { + try std.testing.expect(avs_voltage_valid(750) == true); + try std.testing.expect(avs_voltage_valid(1000) == true); + try std.testing.expect(avs_voltage_valid(1250) == true); +} + +test "avs_voltage_valid_out_of_range" { + try std.testing.expect(avs_voltage_valid(600) == false); + try std.testing.expect(avs_voltage_valid(1400) == false); +} + +test "avs_voltage_in_hysteresis_true" { + try std.testing.expect(avs_voltage_in_hysteresis(1000, 1000, 15) == true); + try std.testing.expect(avs_voltage_in_hysteresis(1010, 1000, 15) == true); + try std.testing.expect(avs_voltage_in_hysteresis(990, 1000, 15) == true); +} + +test "avs_voltage_in_hysteresis_false" { + try std.testing.expect(avs_voltage_in_hysteresis(1030, 1000, 15) == false); + try std.testing.expect(avs_voltage_in_hysteresis(970, 1000, 15) == false); +} + +test "avs_config_init_structure" { + given config = avs_config_init() + try std.testing.expect(config.mode == AvsMode.automatic); + try std.testing.expect(config.target_mv == TARGET_MV); + try std.testing.expect(config.enable_dual_channel == false); +} + +test "avs_config_dual_structure" { + given config = avs_config_dual(950, 1050) + try std.testing.expect(config.mode == AvsMode.dual_independent); + try std.testing.expect(config.enable_dual_channel == true); +} + +test "avs_channel_init_structure" { + given channel = avs_channel_init(AvsChannel.channel0) + try std.testing.expect(channel.channel == AvsChannel.channel0); + try std.testing.expect(channel.enabled == false); +} + +test "avs_channel_enable" { + given channel = avs_channel_init(AvsChannel.channel0) + try std.testing.expect(result = avs_channel_enable(channel, 950)); + try std.testing.expect(result.enabled == true); + try std.testing.expect(result.target_mv == 950); +} + +test "avs_channel_adjust" { + given channel = avs_channel_init(AvsChannel.channel0) + try std.testing.expect(result = avs_channel_adjust(channel, 30)); + try std.testing.expect(result.level == 30); +} + +test "avs_status_init_structure" { + given status = avs_status_init() + try std.testing.expect(status.state == AvsState.disabled); + try std.testing.expect(status.calibration_complete == false); +} + +test "avs_status_enable" { + given status = avs_status_init() + try std.testing.expect(config = avs_config_init()); + try std.testing.expect(result = avs_status_enable(status, config)); + try std.testing.expect(result.state == AvsState.enabled); + try std.testing.expect(result.channel0.enabled == true); +} + +test "avs_status_enable_dual" { + given status = avs_status_init() + try std.testing.expect(config = avs_config_dual(950, 1050)); + try std.testing.expect(result = avs_status_enable(status, config)); + try std.testing.expect(result.channel0.enabled == true); + try std.testing.expect(result.channel1.enabled == true); +} + +test "avs_status_calibrate" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_calibrate(status)); + try std.testing.expect(result.state == AvsState.calibrating); + try std.testing.expect(result.calibration_complete == false); +} + +test "avs_status_calibration_complete" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_calibration_complete(status)); + try std.testing.expect(result.calibration_complete == true); +} + +test "avs_control_pins_start" { + try std.testing.expect(avs_control_pins_start() == CONTROL_PIN_OFFSET); +} + +test "avs_control_pins_end" { + try std.testing.expect(avs_control_pins_end() == CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT - 1); +} + +test "avs_control_pins_secondary_start" { + try std.testing.expect(avs_control_pins_secondary_start() == CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT); +} + +test "avs_is_control_pin_primary_true" { + try std.testing.expect(avs_is_control_pin_primary(64) == true); + try std.testing.expect(avs_is_control_pin_primary(70) == true); +} + +test "avs_is_control_pin_primary_false" { + try std.testing.expect(avs_is_control_pin_primary(63) == false); + try std.testing.expect(avs_is_control_pin_primary(71) == false); +} + +test "avs_is_control_pin_secondary_true" { + try std.testing.expect(avs_is_control_pin_secondary(71) == true); + try std.testing.expect(avs_is_control_pin_secondary(77) == true); +} + +test "avs_is_control_pin_secondary_false" { + try std.testing.expect(avs_is_control_pin_secondary(70) == false); + try std.testing.expect(avs_is_control_pin_secondary(78) == false); +} + +test "avs_is_control_pin_true" { + try std.testing.expect(avs_is_control_pin(64) == true); + try std.testing.expect(avs_is_control_pin(75) == true); +} + +test "avs_is_control_pin_false" { + try std.testing.expect(avs_is_control_pin(63) == false); + try std.testing.expect(avs_is_control_pin(78) == false); +} + +test "avs_control_pin_for_bit_primary" { + try std.testing.expect(avs_control_pin_for_bit(0, AvsChannel.channel0) == CONTROL_PIN_OFFSET); + try std.testing.expect(avs_control_pin_for_bit(6, AvsChannel.channel0) == CONTROL_PIN_OFFSET + 6); +} + +test "avs_control_pin_for_bit_secondary" { + try std.testing.expect(avs_control_pin_for_bit(0, AvsChannel.channel1) == avs_control_pins_secondary_start()); + try std.testing.expect(avs_control_pin_for_bit(6, AvsChannel.channel1) == avs_control_pins_secondary_start() + 6); +} + +test "encode_avs_96_cmd" { + given encoded = encode_avs_96_cmd(0x20, 30, 1) + try std.testing.expect((encoded >> 8) == 0x20); +} + +test "decode_avs_96_cmd" { + given decoded = decode_avs_96_cmd(0x203D) + try std.testing.expect(decoded.opcode == 0x20); + try std.testing.expect(decoded.level == 30); + try std.testing.expect(decoded.channel == 1); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant voltage_range_positive + assert VOLTAGE_MIN_MV > 0 and VOLTAGE_MAX_MV > VOLTAGE_MIN_MV + +invariant voltage_step_positive + assert VOLTAGE_STEP_MV > 0 + +invariant voltage_levels_calculated + try std.testing.expect(VOLTAGE_LEVELS == ((VOLTAGE_MAX_MV - VOLTAGE_MIN_MV) / VOLTAGE_STEP_MV) + 1); + +invariant target_voltage_in_range + assert avs_voltage_valid(TARGET_MV) + +invariant control_pin_count_seven + assert CONTROL_PIN_COUNT == 7 + +invariant control_pins_valid_range + assert avs_control_pins_secondary_end() < PINS_96 + +invariant dual_channel_enabled + assert DUAL_CHANNEL == true + +invariant avs_status_init_disabled + given status = avs_status_init() + try std.testing.expect(status.state == AvsState.disabled); + +invariant avs_status_calibrate_sets_state + given status = avs_status_init() + try std.testing.expect(result = avs_status_calibrate(status)); + try std.testing.expect(result.state == AvsState.calibrating); + +invariant avs_is_control_pin_primary_excludes_secondary + given pin = 70 // Last primary pin + assert avs_is_control_pin_primary(pin) == true + try std.testing.expect(avs_is_control_pin_secondary(pin) == false); + +invariant avs_is_control_pin_secondary_excludes_primary + given pin = 71 // First secondary pin + assert avs_is_control_pin_secondary(pin) == true + try std.testing.expect(avs_is_control_pin_primary(pin) == false); + +invariant avs_control_pin_for_bit_monotonic + try std.testing.expect(avs_control_pin_for_bit(3, AvsChannel.channel0) < avs_control_pin_for_bit(4, AvsChannel.channel0)); + +invariant avs_config_dual_enables_both_channels + given config = avs_config_dual(950, 1050) + assert config.enable_dual_channel == true + +invariant avs_config_init_single_channel + given config = avs_config_init() + assert config.enable_dual_channel == false + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench avs_voltage_from_level_latency + measure: nanoseconds to avs_voltage_from_level(40) + target: < 20ns + +bench avs_level_from_voltage_latency + measure: nanoseconds to avs_level_from_voltage(1000) + target: < 20ns + +bench avs_control_code_from_level_latency + measure: nanoseconds to avs_control_code_from_level(40) + target: < 15ns + +bench avs_voltage_valid_latency + measure: nanoseconds to avs_voltage_valid(1000) + target: < 15ns + +bench avs_config_init_latency + measure: nanoseconds to avs_config_init() + target: < 30ns + +bench avs_channel_init_latency + measure: nanoseconds to avs_channel_init(AvsChannel.channel0) + target: < 20ns + +bench avs_status_init_latency + measure: nanoseconds to avs_status_init() + target: < 30ns + +bench avs_status_enable_latency + measure: nanoseconds to avs_status_enable(avs_status_init(), avs_config_init()) + target: < 40ns + +bench avs_status_calibrate_latency + measure: nanoseconds to avs_status_calibrate(avs_status_init()) + target: < 20ns + +bench avs_is_control_pin_latency + measure: nanoseconds to avs_is_control_pin(70) + target: < 15ns + +bench avs_control_pin_for_bit_latency + measure: nanoseconds to avs_control_pin_for_bit(3, AvsChannel.channel0) + target: < 20ns + +bench encode_avs_96_cmd_latency + measure: nanoseconds to encode_avs_96_cmd(0x20, 30, 1) + target: < 20ns + +bench decode_avs_96_cmd_latency + measure: nanoseconds to decode_avs_96_cmd(0x203D) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/avs_reconf.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/avs_reconf.t27 new file mode 100644 index 0000000000..61d90cf865 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/avs_reconf.t27 @@ -0,0 +1,702 @@ +// SPDX-License-Identifier: Apache-2.0 +; avs_reconf.t27 — AVS Reconfiguration Module +; Dynamic AVS voltage/frequency reconfiguration support +; φ² + 1/φ² = 3 | TRINITY + +module avs-reconf; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const RECONF_OPCODE_BASE : u8 = 0xF0; // AVS reconf opcodes start at 0xF0 + +pub const RECONF_OP_SET_VOLTAGE : u8 = 0x00; +pub const RECONF_OP_SET_FREQUENCY : u8 = 0x01; +pub const RECONF_OP_SET_MODE : u8 = 0x02; +pub const RECONF_OP_RESET : u8 = 0x03; +pub const RECONF_OP_QUERY : u8 = 0x04; +pub const RECONF_OP_SAVE_CONFIG : u8 = 0x05; +pub const RECONF_OP_LOAD_CONFIG : u8 = 0x06; +pub const RECONF_OP_CALIBRATE : u8 = 0x07; +pub const RECONF_OP_MAX : u8 = 0x08; + +pub const RECONF_MODE_MANUAL : u8 = 0; +pub const RECONF_MODE_AUTOMATIC : u8 = 1; +pub const RECONF_MODE_PERFORMANCE : u8 = 2; +pub const RECONF_MODE_POWER_SAVING : u8 = 3; +pub const RECONF_MODE_ADAPTIVE : u8 = 4; +pub const RECONF_MODE_MAX : u8 = 5; + +pub const RECONF_STATE_IDLE : u8 = 0; +pub const RECONF_STATE_BUSY : u8 = 1; +pub const RECONF_STATE_ERROR : u8 = 2; +pub const RECONF_STATE_CALIBRATING : u8 = 3; + +pub const VOLTAGE_MIN_MV : u16 = 700; +pub const VOLTAGE_MAX_MV : u16 = 1300; +pub const FREQ_MIN_KHZ : u32 = 10000; +pub const FREQ_MAX_KHZ : u32 = 200000; + +pub const RECONF_TIMEOUT_MS : u16 = 1000; +pub const RECONF_RETRY_COUNT : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const ReconfOp = enum(u8) { + set_voltage = RECONF_OP_SET_VOLTAGE, + set_frequency = RECONF_OP_SET_FREQUENCY, + set_mode = RECONF_OP_SET_MODE, + reset = RECONF_OP_RESET, + query = RECONF_OP_QUERY, + save_config = RECONF_OP_SAVE_CONFIG, + load_config = RECONF_OP_LOAD_CONFIG, + calibrate = RECONF_OP_CALIBRATE, +} + +pub const ReconfMode = enum(u8) { + manual = RECONF_MODE_MANUAL, + automatic = RECONF_MODE_AUTOMATIC, + performance = RECONF_MODE_PERFORMANCE, + power_saving = RECONF_MODE_POWER_SAVING, + adaptive = RECONF_MODE_ADAPTIVE, +} + +pub const ReconfState = enum(u8) { + idle = RECONF_STATE_IDLE, + busy = RECONF_STATE_BUSY, + error = RECONF_STATE_ERROR, + calibrating = RECONF_STATE_CALIBRATING, +} + +pub const ReconfConfig = struct { + mode : ReconfMode, + voltage_mv : u16, + freq_khz : u32, + auto_scale : bool, + min_voltage_mv : u16, + max_voltage_mv : u16, + min_freq_khz : u32, + max_freq_khz : u32, +} + +pub const ReconfStatus = struct { + state : ReconfState, + current_voltage_mv : u16, + current_freq_khz : u32, + last_op : ReconfOp, + error_code : u8, + retry_count : u8, +} + +pub const ReconfCommand = struct { + op : ReconfOp, + param1 : u32, + param2 : u32, + timeout_ms : u16, +} + +pub const ReconfResult = struct { + success : bool, + error_code : u8, + cycles_taken : u32, +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// avs_reconf_config_init() -> ReconfConfig +// Initialize AVS reconfiguration config +pub fn avs_reconf_config_init() -> ReconfConfig { + return ReconfConfig { + .mode = ReconfMode.manual, + .voltage_mv = 1000, + .freq_khz = 100000, + .auto_scale = false, + .min_voltage_mv = VOLTAGE_MIN_MV, + .max_voltage_mv = VOLTAGE_MAX_MV, + .min_freq_khz = FREQ_MIN_KHZ, + .max_freq_khz = FREQ_MAX_KHZ, + }; +} + +// avs_reconf_config_performance() -> ReconfConfig +// Create performance-oriented config +pub fn avs_reconf_config_performance() -> ReconfConfig { + return ReconfConfig { + .mode = ReconfMode.performance, + .voltage_mv = VOLTAGE_MAX_MV, + .freq_khz = FREQ_MAX_KHZ, + .auto_scale = true, + .min_voltage_mv = VOLTAGE_MIN_MV + 100, + .max_voltage_mv = VOLTAGE_MAX_MV, + .min_freq_khz = FREQ_MAX_KHZ / 2, + .max_freq_khz = FREQ_MAX_KHZ, + }; +} + +// avs_reconf_config_power_saving() -> ReconfConfig +// Create power-saving config +pub fn avs_reconf_config_power_saving() -> ReconfConfig { + return ReconfConfig { + .mode = ReconfMode.power_saving, + .voltage_mv = VOLTAGE_MIN_MV + 50, + .freq_khz = FREQ_MIN_KHZ * 2, + .auto_scale = true, + .min_voltage_mv = VOLTAGE_MIN_MV, + .max_voltage_mv = VOLTAGE_MIN_MV + 200, + .min_freq_khz = FREQ_MIN_KHZ, + .max_freq_khz = FREQ_MIN_KHZ * 5, + }; +} + +// avs_reconf_config_adaptive() -> ReconfConfig +// Create adaptive config +pub fn avs_reconf_config_adaptive() -> ReconfConfig { + return ReconfConfig { + .mode = ReconfMode.adaptive, + .voltage_mv = 1000, + .freq_khz = 100000, + .auto_scale = true, + .min_voltage_mv = VOLTAGE_MIN_MV, + .max_voltage_mv = VOLTAGE_MAX_MV, + .min_freq_khz = FREQ_MIN_KHZ, + .max_freq_khz = FREQ_MAX_KHZ, + }; +} + +// ============================================================================ +// Validation Functions +// ============================================================================ + +// avs_reconf_voltage_valid(voltage_mv: u16) -> bool +// Check if voltage is valid +pub fn avs_reconf_voltage_valid(voltage_mv: u16) -> bool { + return voltage_mv >= VOLTAGE_MIN_MV and voltage_mv <= VOLTAGE_MAX_MV; +} + +// avs_reconf_freq_valid(freq_khz: u32) -> bool +// Check if frequency is valid +pub fn avs_reconf_freq_valid(freq_khz: u32) -> bool { + return freq_khz >= FREQ_MIN_KHZ and freq_khz <= FREQ_MAX_KHZ; +} + +// avs_reconf_config_valid(config: ReconfConfig) -> bool +// Check if config is valid +pub fn avs_reconf_config_valid(config: ReconfConfig) -> bool { + return avs_reconf_voltage_valid(config.voltage_mv) + try std.testing.expect(avs_reconf_freq_valid(config.freq_khz)); + try std.testing.expect(config.min_voltage_mv < config.max_voltage_mv); + try std.testing.expect(config.min_freq_khz < config.max_freq_khz;); +} + +// ============================================================================ +// Status Functions +// ============================================================================ + +// avs_reconf_status_init() -> ReconfStatus +// Initialize reconfiguration status +pub fn avs_reconf_status_init() -> ReconfStatus { + return ReconfStatus { + .state = ReconfState.idle, + .current_voltage_mv = VOLTAGE_MIN_MV, + .current_freq_khz = FREQ_MIN_KHZ, + .last_op = ReconfOp.reset, + .error_code = 0, + .retry_count = 0, + }; +} + +// avs_reconf_status_start_op(status: ReconfStatus, op: ReconfOp) -> ReconfStatus +// Start a reconfiguration operation +pub fn avs_reconf_status_start_op(status: ReconfStatus, op: ReconfOp) -> ReconfStatus { + return ReconfStatus { + .state = ReconfState.busy, + .current_voltage_mv = status.current_voltage_mv, + .current_freq_khz = status.current_freq_khz, + .last_op = op, + .error_code = 0, + .retry_count = status.retry_count, + }; +} + +// avs_reconf_status_complete(status: ReconfStatus, result: ReconfResult) -> ReconfStatus +// Complete a reconfiguration operation +pub fn avs_reconf_status_complete(status: ReconfStatus, result: ReconfResult) -> ReconfStatus { + var new_state = if (result.success) ReconfState.idle else ReconfState.error; + var new_retry_count = if (result.success) 0 else status.retry_count + 1; + + return ReconfStatus { + .state = new_state, + .current_voltage_mv = status.current_voltage_mv, + .current_freq_khz = status.current_freq_khz, + .last_op = status.last_op, + .error_code = if (result.success) 0 else result.error_code, + .retry_count = new_retry_count, + }; +} + +// avs_reconf_status_update_voltage(status: ReconfStatus, voltage_mv: u16) -> ReconfStatus +// Update voltage in status +pub fn avs_reconf_status_update_voltage(status: ReconfStatus, voltage_mv: u16) -> ReconfStatus { + return ReconfStatus { + .state = status.state, + .current_voltage_mv = voltage_mv, + .current_freq_khz = status.current_freq_khz, + .last_op = status.last_op, + .error_code = status.error_code, + .retry_count = status.retry_count, + }; +} + +// avs_reconf_status_update_freq(status: ReconfStatus, freq_khz: u32) -> ReconfStatus +// Update frequency in status +pub fn avs_reconf_status_update_freq(status: ReconfStatus, freq_khz: u32) -> ReconfStatus { + return ReconfStatus { + .state = status.state, + .current_voltage_mv = status.current_voltage_mv, + .current_freq_khz = freq_khz, + .last_op = status.last_op, + .error_code = status.error_code, + .retry_count = status.retry_count, + }; +} + +// ============================================================================ +// Command Functions +// ============================================================================ + +// avs_reconf_command_init() -> ReconfCommand +// Initialize reconfiguration command +pub fn avs_reconf_command_init() -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.reset, + .param1 = 0, + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// avs_reconf_command_set_voltage(voltage_mv: u16) -> ReconfCommand +// Create voltage set command +pub fn avs_reconf_command_set_voltage(voltage_mv: u16) -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.set_voltage, + .param1 = @as(u32, voltage_mv), + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// avs_reconf_command_set_freq(freq_khz: u32) -> ReconfCommand +// Create frequency set command +pub fn avs_reconf_command_set_freq(freq_khz: u32) -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.set_frequency, + .param1 = freq_khz, + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// avs_reconf_command_set_mode(mode: ReconfMode) -> ReconfCommand +// Create mode set command +pub fn avs_reconf_command_set_mode(mode: ReconfMode) -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.set_mode, + .param1 = @as(u32, mode), + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// avs_reconf_command_calibrate() -> ReconfCommand +// Create calibration command +pub fn avs_reconf_command_calibrate() -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.calibrate, + .param1 = 0, + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS * 5, // Calibration takes longer + }; +} + +// avs_reconf_command_save_config(config: ReconfConfig) -> ReconfCommand +// Create save config command +pub fn avs_reconf_command_save_config(config: ReconfConfig) -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.save_config, + .param1 = @as(u32, config.voltage_mv), + .param2 = config.freq_khz, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// ============================================================================ +// Result Functions +// ============================================================================ + +// avs_reconf_result_success() -> ReconfResult +// Create success result +pub fn avs_reconf_result_success(cycles: u32) -> ReconfResult { + return ReconfResult { + .success = true, + .error_code = 0, + .cycles_taken = cycles, + }; +} + +// avs_reconf_result_error(error_code: u8, cycles: u32) -> ReconfResult +// Create error result +pub fn avs_reconf_result_error(error_code: u8, cycles: u32) -> ReconfResult { + return ReconfResult { + .success = false, + .error_code = error_code, + .cycles_taken = cycles, + }; +} + +// avs_reconf_should_retry(status: ReconfStatus) -> bool +// Check if should retry failed operation +pub fn avs_reconf_should_retry(status: ReconfStatus) -> bool { + return status.state == ReconfState.error and status.retry_count < RECONF_RETRY_COUNT; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_avs_reconf_cmd(op: u8, param1: u16, param2: u16) -> u32 +// Encode AVS reconfiguration command +pub fn encode_avs_reconf_cmd(op: u8, param1: u16, param2: u16) u32 { + // Format: [OP:8][PARAM1:12][PARAM2:12] + const op_field : u32 = @as(u32, op) << 24; + const param1_field : u32 = @as(u32, param1 & 0xFFF) << 12; + const param2_field : u32 = @as(u32, param2 & 0xFFF); + return op_field | param1_field | param2_field; +} + +// decode_avs_reconf_cmd(encoded: u32) -> struct { op: u8, param1: u16, param2: u16 } +// Decode AVS reconfiguration command +pub fn decode_avs_reconf_cmd(encoded: u32) -> struct { op: u8, param1: u16, param2: u16 } { + const op : u8 = @as(u8, @truncate((encoded >> 24) & 0xFF)); + const param1 : u16 = @as(u16, @truncate((encoded >> 12) & 0xFFF)); + const param2 : u16 = @as(u16, @truncate(encoded & 0xFFF)); + return .{ .op = op, .param1 = param1, .param2 = param2 }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "reconf_opcode_base" { + try std.testing.expect(RECONF_OPCODE_BASE == 0xF0); +} + +test "reconf_op_constants" { + try std.testing.expect(RECONF_OP_SET_VOLTAGE == 0); + try std.testing.expect(RECONF_OP_SET_FREQUENCY == 1); + try std.testing.expect(RECONF_OP_SET_MODE == 2); + try std.testing.expect(RECONF_OP_CALIBRATE == 7); +} + +test "reconf_mode_constants" { + try std.testing.expect(RECONF_MODE_MANUAL == 0); + try std.testing.expect(RECONF_MODE_PERFORMANCE == 2); + try std.testing.expect(RECONF_MODE_POWER_SAVING == 3); + try std.testing.expect(RECONF_MODE_ADAPTIVE == 4); +} + +test "reconf_state_constants" { + try std.testing.expect(RECONF_STATE_IDLE == 0); + try std.testing.expect(RECONF_STATE_BUSY == 1); + try std.testing.expect(RECONF_STATE_ERROR == 2); +} + +test "voltage_freq_constants" { + try std.testing.expect(VOLTAGE_MIN_MV == 700); + try std.testing.expect(VOLTAGE_MAX_MV == 1300); + try std.testing.expect(FREQ_MIN_KHZ == 10000); + try std.testing.expect(FREQ_MAX_KHZ == 200000); +} + +test "avs_reconf_config_init_structure" { + given config = avs_reconf_config_init() + try std.testing.expect(config.mode == ReconfMode.manual); + try std.testing.expect(config.voltage_mv == 1000); + try std.testing.expect(config.freq_khz == 100000); +} + +test "avs_reconf_config_performance_structure" { + given config = avs_reconf_config_performance() + try std.testing.expect(config.mode == ReconfMode.performance); + try std.testing.expect(config.voltage_mv == VOLTAGE_MAX_MV); + try std.testing.expect(config.auto_scale == true); +} + +test "avs_reconf_config_power_saving_structure" { + given config = avs_reconf_config_power_saving() + try std.testing.expect(config.mode == ReconfMode.power_saving); + try std.testing.expect(config.voltage_mv < 1000); + try std.testing.expect(config.auto_scale == true); +} + +test "avs_reconf_config_adaptive_structure" { + given config = avs_reconf_config_adaptive() + try std.testing.expect(config.mode == ReconfMode.adaptive); + try std.testing.expect(config.auto_scale == true); +} + +test "avs_reconf_voltage_valid_true" { + try std.testing.expect(avs_reconf_voltage_valid(800) == true); + try std.testing.expect(avs_reconf_voltage_valid(1000) == true); + try std.testing.expect(avs_reconf_voltage_valid(1200) == true); +} + +test "avs_reconf_voltage_valid_false" { + try std.testing.expect(avs_reconf_voltage_valid(600) == false); + try std.testing.expect(avs_reconf_voltage_valid(1400) == false); +} + +test "avs_reconf_freq_valid_true" { + try std.testing.expect(avs_reconf_freq_valid(20000) == true); + try std.testing.expect(avs_reconf_freq_valid(100000) == true); + try std.testing.expect(avs_reconf_freq_valid(150000) == true); +} + +test "avs_reconf_freq_valid_false" { + try std.testing.expect(avs_reconf_freq_valid(5000) == false); + try std.testing.expect(avs_reconf_freq_valid(300000) == false); +} + +test "avs_reconf_config_valid_true" { + given config = avs_reconf_config_init() + try std.testing.expect(avs_reconf_config_valid(config) == true); +} + +test "avs_reconf_config_valid_false" { + given config = ReconfConfig{.mode = ReconfMode.manual, .voltage_mv = 600, .freq_khz = 100000, .auto_scale = false, .min_voltage_mv = 700, .max_voltage_mv = 1300, .min_freq_khz = 10000, .max_freq_khz = 200000} + try std.testing.expect(avs_reconf_config_valid(config) == false); +} + +test "avs_reconf_status_init_structure" { + given status = avs_reconf_status_init() + try std.testing.expect(status.state == ReconfState.idle); + try std.testing.expect(status.current_voltage_mv == VOLTAGE_MIN_MV); +} + +test "avs_reconf_status_start_op" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_status_start_op(status, ReconfOp.set_voltage)); + try std.testing.expect(result.state == ReconfState.busy); + try std.testing.expect(result.last_op == ReconfOp.set_voltage); +} + +test "avs_reconf_status_complete_success" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_result_success(1000)); + try std.testing.expect(new_status = avs_reconf_status_complete(status, result)); + try std.testing.expect(new_status.state == ReconfState.idle); + try std.testing.expect(new_status.error_code == 0); +} + +test "avs_reconf_status_complete_error" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_result_error(1, 500)); + try std.testing.expect(new_status = avs_reconf_status_complete(status, result)); + try std.testing.expect(new_status.state == ReconfState.error); + try std.testing.expect(new_status.error_code == 1); + try std.testing.expect(new_status.retry_count == 1); +} + +test "avs_reconf_status_update_voltage" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_status_update_voltage(status, 1100)); + try std.testing.expect(result.current_voltage_mv == 1100); +} + +test "avs_reconf_status_update_freq" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_status_update_freq(status, 150000)); + try std.testing.expect(result.current_freq_khz == 150000); +} + +test "avs_reconf_command_set_voltage_structure" { + given cmd = avs_reconf_command_set_voltage(1050) + try std.testing.expect(cmd.op == ReconfOp.set_voltage); + try std.testing.expect(cmd.param1 == 1050); +} + +test "avs_reconf_command_set_freq_structure" { + given cmd = avs_reconf_command_set_freq(120000) + try std.testing.expect(cmd.op == ReconfOp.set_frequency); + try std.testing.expect(cmd.param1 == 120000); +} + +test "avs_reconf_command_set_mode_structure" { + given cmd = avs_reconf_command_set_mode(ReconfMode.performance) + try std.testing.expect(cmd.op == ReconfOp.set_mode); + try std.testing.expect(cmd.param1 == 2); +} + +test "avs_reconf_command_calibrate_structure" { + given cmd = avs_reconf_command_calibrate() + try std.testing.expect(cmd.op == ReconfOp.calibrate); + try std.testing.expect(cmd.timeout_ms > RECONF_TIMEOUT_MS); +} + +test "avs_reconf_result_success_structure" { + given result = avs_reconf_result_success(500) + try std.testing.expect(result.success == true); + try std.testing.expect(result.cycles_taken == 500); +} + +test "avs_reconf_result_error_structure" { + given result = avs_reconf_result_error(2, 300) + try std.testing.expect(result.success == false); + try std.testing.expect(result.error_code == 2); +} + +test "avs_reconf_should_retry_true" { + given status = ReconfStatus{.state = ReconfState.error, .current_voltage_mv = 1000, .current_freq_khz = 100000, .last_op = ReconfOp.set_voltage, .error_code = 1, .retry_count = 1} + try std.testing.expect(avs_reconf_should_retry(status) == true); +} + +test "avs_reconf_should_retry_false_max_retries" { + given status = ReconfStatus{.state = ReconfState.error, .current_voltage_mv = 1000, .current_freq_khz = 100000, .last_op = ReconfOp.set_voltage, .error_code = 1, .retry_count = 3} + try std.testing.expect(avs_reconf_should_retry(status) == false); +} + +test "avs_reconf_should_retry_false_idle" { + given status = avs_reconf_status_init() + try std.testing.expect(avs_reconf_should_retry(status) == false); +} + +test "encode_avs_reconf_cmd" { + given encoded = encode_avs_reconf_cmd(0x00, 0x3E8, 0x2710) + try std.testing.expect((encoded >> 24) == 0x00); +} + +test "decode_avs_reconf_cmd" { + given decoded = decode_avs_reconf_cmd(0x003E8271) + try std.testing.expect(decoded.op == 0); + try std.testing.expect(decoded.param1 == 0x3E8); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant voltage_range_positive + assert VOLTAGE_MIN_MV > 0 and VOLTAGE_MAX_MV > VOLTAGE_MIN_MV + +invariant freq_range_positive + assert FREQ_MIN_KHZ > 0 and FREQ_MAX_KHZ > FREQ_MIN_KHZ + +invariant reconf_timeout_positive + assert RECONF_TIMEOUT_MS > 0 + +invariant reconf_retry_count_positive + assert RECONF_RETRY_COUNT > 0 + +invariant reconf_config_init_valid + given config = avs_reconf_config_init() + assert avs_reconf_config_valid(config) + +invariant reconf_config_performance_max_voltage + given config = avs_reconf_config_performance() + assert config.voltage_mv == VOLTAGE_MAX_MV + +invariant reconf_config_power_saving_low_voltage + given config = avs_reconf_config_power_saving() + assert config.voltage_mv < 1000 + +invariant reconf_status_init_idle + given status = avs_reconf_status_init() + try std.testing.expect(status.state == ReconfState.idle); + +invariant reconf_status_start_op_busy + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_status_start_op(status, ReconfOp.set_voltage)); + try std.testing.expect(result.state == ReconfState.busy); + +invariant reconf_result_success_no_error + given result = avs_reconf_result_success(100) + assert result.success == true and result.error_code == 0 + +invariant reconf_result_error_not_success + given result = avs_reconf_result_error(1, 100) + assert result.success == false and result.error_code > 0 + +invariant reconf_should_retry_requires_error + given idle = avs_reconf_status_init() + assert avs_reconf_should_retry(idle) == false + +invariant reconf_config_performance_auto_scale + given config = avs_reconf_config_performance() + assert config.auto_scale == true + +invariant reconf_config_power_saving_auto_scale + given config = avs_reconf_config_power_saving() + assert config.auto_scale == true + +invariant reconf_command_calibrate_longer_timeout + given normal = avs_reconf_command_init() + try std.testing.expect(cal = avs_reconf_command_calibrate()); + assert cal.timeout_ms > normal.timeout_ms + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench avs_reconf_config_init_latency + measure: nanoseconds to avs_reconf_config_init() + target: < 30ns + +bench avs_reconf_voltage_valid_latency + measure: nanoseconds to avs_reconf_voltage_valid(1000) + target: < 15ns + +bench avs_reconf_freq_valid_latency + measure: nanoseconds to avs_reconf_freq_valid(100000) + target: < 15ns + +bench avs_reconf_config_valid_latency + measure: nanoseconds to avs_reconf_config_valid(avs_reconf_config_init()) + target: < 30ns + +bench avs_reconf_status_init_latency + measure: nanoseconds to avs_reconf_status_init() + target: < 30ns + +bench avs_reconf_status_start_op_latency + measure: nanoseconds to avs_reconf_status_start_op(avs_reconf_status_init(), ReconfOp.set_voltage) + target: < 20ns + +bench avs_reconf_status_complete_latency + measure: nanoseconds to avs_reconf_status_complete(avs_reconf_status_init(), avs_reconf_result_success(100)) + target: < 20ns + +bench avs_reconf_command_set_voltage_latency + measure: nanoseconds to avs_reconf_command_set_voltage(1000) + target: < 20ns + +bench avs_reconf_should_retry_latency + measure: nanoseconds to avs_reconf_should_retry(avs_reconf_status_init()) + target: < 15ns + +bench avs_reconf_result_success_latency + measure: nanoseconds to avs_reconf_result_success(500) + target: < 15ns + +bench encode_avs_reconf_cmd_latency + measure: nanoseconds to encode_avs_reconf_cmd(0x00, 1000, 10000) + target: < 20ns + +bench decode_avs_reconf_cmd_latency + measure: nanoseconds to decode_avs_reconf_cmd(0x003E8271) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/dfs_gate.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/dfs_gate.t27 new file mode 100644 index 0000000000..520c8bc16e --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/dfs_gate.t27 @@ -0,0 +1,600 @@ +// SPDX-License-Identifier: Apache-2.0 +; dfs_gate.t27 — Sacred Opcode 0xE7: Depth-First Search Gate +; Hardware acceleration for DFS traversal and pattern matching +; φ² + 1/φ² = 3 | TRINITY + +module sacred-dfs_gate; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_DFS_GATE : u8 = 0xE7; + +pub const MAX_STACK_DEPTH : u8 = 16; +pub const MAX_NODES : u16 = 256; +pub const NODE_ADDR_BITS : u8 = 8; + +pub const VISIT_UNVISITED : u8 = 0; +pub const VISIT_VISITING : u8 = 1; +pub const VISIT_VISITED : u8 = 2; + +pub const DFS_MODE_STANDARD : u8 = 0; +pub const DFS_MODE_DEPTH_LIMITED : u8 = 1; +pub const DFS_MODE_PATTERN_MATCH : u8 = 2; +pub const DFS_MODE_PRUNE_SUBOPTIMAL : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const DfsNode = struct { + address : u8, + value : u16, + children : [4]u8, // Up to 4 child addresses + child_count : u8, + visited : u8, + depth : u8, +} + +pub const DfsStack = struct { + nodes : [MAX_STACK_DEPTH]u8, + top : u8, +} + +pub const DfsConfig = struct { + mode : u8, + depth_limit : u8, + pattern_mask : u16, + prune_threshold : u8, +} + +pub const DfsState = struct { + current_node : u8, + visited_count : u16, + max_depth : u8, + cycles : u32, +} + +pub const DfsResult = struct { + found : bool, + target_node : u8, + depth : u8, + nodes_visited : u16, + pattern_matches : u16, +} + +// ============================================================================ +// Stack Functions +// ============================================================================ + +// dfs_stack_init() -> DfsStack +// Initialize empty DFS stack +pub fn dfs_stack_init() DfsStack { + return DfsStack { + .nodes = [_]u8{0} ** MAX_STACK_DEPTH, + .top = 0, + }; +} + +// dfs_stack_push(stack: DfsStack, node: u8) -> DfsStack +// Push node onto DFS stack +pub fn dfs_stack_push(stack: DfsStack, node: u8) DfsStack { + if (stack.top >= MAX_STACK_DEPTH) { + return stack; // Stack overflow + } + var result = stack; + result.nodes[stack.top] = node; + result.top += 1; + return result; +} + +// dfs_stack_pop(stack: DfsStack) -> struct { stack: DfsStack, node: u8, valid: bool } +// Pop node from DFS stack +pub fn dfs_stack_pop(stack: DfsStack) struct { stack: DfsStack, node: u8, valid: bool } { + if (stack.top == 0) { + return .{ .stack = stack, .node = 0, .valid = false }; + } + var result = stack; + result.top -= 1; + const node = result.nodes[result.top]; + result.nodes[result.top] = 0; + return .{ .stack = result, .node = node, .valid = true }; +} + +// dfs_stack_peek(stack: DfsStack) -> struct { node: u8, valid: bool } +// Peek at top of DFS stack +pub fn dfs_stack_peek(stack: DfsStack) struct { node: u8, valid: bool } { + if (stack.top == 0) { + return .{ .node = 0, .valid = false }; + } + return .{ .node = stack.nodes[stack.top - 1], .valid = true }; +} + +// dfs_stack_is_empty(stack: DfsStack) -> bool +// Check if stack is empty +pub fn dfs_stack_is_empty(stack: DfsStack) bool { + return stack.top == 0; +} + +// dfs_stack_depth(stack: DfsStack) -> u8 +// Get current stack depth +pub fn dfs_stack_depth(stack: DfsStack) -> u8 { + return stack.top; +} + +// ============================================================================ +// Node Functions +// ============================================================================ + +// dfs_node_init(address: u8, value: u16) -> DfsNode +// Initialize DFS node +pub fn dfs_node_init(address: u8, value: u16) DfsNode { + return DfsNode { + .address = address, + .value = value, + .children = [_]u8{255} ** 4, // 255 = invalid + .child_count = 0, + .visited = VISIT_UNVISITED, + .depth = 0, + }; +} + +// dfs_node_add_child(node: DfsNode, child_addr: u8) -> DfsNode +// Add child to node +pub fn dfs_node_add_child(node: DfsNode, child_addr: u8) DfsNode { + if (node.child_count >= 4) { + return node; // Too many children + } + var result = node; + result.children[node.child_count] = child_addr; + result.child_count += 1; + return result; +} + +// dfs_node_has_child(node: DfsNode, child_addr: u8) -> bool +// Check if node has specific child +pub fn dfs_node_has_child(node: DfsNode, child_addr: u8) bool { + for (0..node.child_count) |i| { + if (node.children[i] == child_addr) { + return true; + } + } + return false; +} + +// dfs_node_mark_visiting(node: DfsNode) -> DfsNode +// Mark node as being visited +pub fn dfs_node_mark_visiting(node: DfsNode) DfsNode { + var result = node; + result.visited = VISIT_VISITING; + return result; +} + +// dfs_node_mark_visited(node: DfsNode) -> DfsNode +// Mark node as visited +pub fn dfs_node_mark_visited(node: DfsNode) DfsNode { + var result = node; + result.visited = VISIT_VISITED; + return result; +} + +// dfs_node_is_visited(node: DfsNode) -> bool +// Check if node is fully visited +pub fn dfs_node_is_visited(node: DfsNode) bool { + return node.visited == VISIT_VISITED; +} + +// dfs_node_is_visiting(node: DfsNode) -> bool +// Check if node is currently being visited +pub fn dfs_node_is_visiting(node: DfsNode) bool { + return node.visited == VISIT_VISITING; +} + +// ============================================================================ +// DFS Traversal Functions +// ============================================================================ + +// dfs_traverse(graph: [MAX_NODES]DfsNode, start_addr: u8, target_value: u16, config: DfsConfig) -> DfsResult +// Perform DFS traversal to find target value +pub fn dfs_traverse(graph: [MAX_NODES]DfsNode, start_addr: u8, target_value: u16, config: DfsConfig) DfsResult { + var stack = dfs_stack_init(); + stack = dfs_stack_push(stack, start_addr); + + var visited_count : u16 = 0; + var max_depth : u8 = 0; + var pattern_matches : u16 = 0; + + var current_depth : u8 = 0; + var nodes_visited : u16 = 0; + + while (not dfs_stack_is_empty(stack)) { + const peek = dfs_stack_peek(stack); + + if (not peek.valid) { + break; + } + + const node_idx = peek.node; + var node = graph[@as(u8, @intCast(node_idx))]; + + // Check depth limit + if (config.mode == DFS_MODE_DEPTH_LIMITED and current_depth >= config.depth_limit) { + const pop_result = dfs_stack_pop(stack); + if (pop_result.valid) { + stack = pop_result.stack; + current_depth -= 1; + } + continue; + } + + // Check if visiting for first time + if (node.visited == VISIT_UNVISITED) { + nodes_visited += 1; + node = dfs_node_mark_visiting(node); + graph[@as(u8, @intCast(node_idx))] = node; + + // Check if target found + if (node.value == target_value) { + return DfsResult { + .found = true, + .target_node = node_idx, + .depth = current_depth, + .nodes_visited = nodes_visited, + .pattern_matches = pattern_matches, + }; + } + + // Check pattern match + if (config.mode == DFS_MODE_PATTERN_MATCH) { + if ((node.value & config.pattern_mask) == (target_value & config.pattern_mask)) { + pattern_matches += 1; + } + } + + // Prune suboptimal nodes + if (config.mode == DFS_MODE_PRUNE_SUBOPTIMAL) { + if (node.value < config.prune_threshold) { + const pop_result = dfs_stack_pop(stack); + if (pop_result.valid) { + stack = pop_result.stack; + current_depth -= 1; + } + continue; + } + } + + // Push children onto stack (reverse order for correct DFS) + if (node.child_count > 0) { + var i : u8 = node.child_count; + while (i > 0) { + i -= 1; + const child_addr = node.children[i]; + const child = graph[@as(u8, @intCast(child_addr))]; + if (child.visited == VISIT_UNVISITED) { + child.depth = current_depth + 1; + if (child.depth > max_depth) { + max_depth = child.depth; + } + stack = dfs_stack_push(stack, child_addr); + } + } + current_depth += 1; + } + } else { + // Node already visited, mark complete and pop + node = dfs_node_mark_visited(node); + graph[@as(u8, @intCast(node_idx))] = node; + visited_count += 1; + + const pop_result = dfs_stack_pop(stack); + if (pop_result.valid) { + stack = pop_result.stack; + if (current_depth > 0) { + current_depth -= 1; + } + } + } + } + + return DfsResult { + .found = false, + .target_node = 0, + .depth = max_depth, + .nodes_visited = nodes_visited, + .pattern_matches = pattern_matches, + }; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_dfs_gate(mode: u8, start_addr: u8, target: u16) -> u32 +// Encode DFS gate instruction +pub fn encode_dfs_gate(mode: u8, start_addr: u8, target: u16) u32 { + // Format: [OP:8][MODE:2][START:8][TARGET:14] + const op : u32 = @as(u32, OP_DFS_GATE) << 24; + const mode_field : u32 = @as(u32, mode & 0x03) << 22; + const start_field : u32 = @as(u32, start_addr) << 14; + const target_field : u32 = @as(u32, target & 0x3FFF); + return op | mode_field | start_field | target_field; +} + +// decode_dfs_gate(encoded: u32) -> struct { mode: u8, start_addr: u8, target: u16 } +// Decode DFS gate instruction +pub fn decode_dfs_gate(encoded: u32) struct { mode: u8, start_addr: u8, target: u16 } { + const mode : u8 = @as(u8, @truncate((encoded >> 22) & 0x03)); + const start_addr : u8 = @as(u8, @truncate((encoded >> 14) & 0xFF)); + const target : u16 = @as(u16, @truncate(encoded & 0x3FFF)); + return .{ .mode = mode, .start_addr = start_addr, .target = target }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "max_stack_depth_sixteen" { + try std.testing.expect(MAX_STACK_DEPTH == 16); +} + +test "max_nodes_256" { + try std.testing.expect(MAX_NODES == 256); +} + +test "node_addr_bits_eight" { + try std.testing.expect(NODE_ADDR_BITS == 8); +} + +test "visit_unvisited_zero" { + assert VISIT_UNVISITED == 0 +} + +test "visit_visited_two" { + assert VISIT_VISITED == 2 +} + +test "dfs_stack_init_empty" { + given stack = dfs_stack_init() + try std.testing.expect(dfs_stack_is_empty(stack) == true); + try std.testing.expect(dfs_stack_depth(stack) == 0); +} + +test "dfs_stack_push_pop" { + given stack = dfs_stack_init() + try std.testing.expect(pushed = dfs_stack_push(stack, 10)); + try std.testing.expect(popped = dfs_stack_pop(pushed)); + try std.testing.expect(popped.valid == true); + try std.testing.expect(popped.node == 10); + try std.testing.expect(dfs_stack_is_empty(popped.stack) == true); +} + +test "dfs_stack_push_multiple" { + given stack = dfs_stack_init() + try std.testing.expect(result = dfs_stack_push(dfs_stack_push(dfs_stack_push(stack, 1), 2), 3)); + try std.testing.expect(popped = dfs_stack_pop(result)); + try std.testing.expect(popped.node == 3); + try std.testing.expect(dfs_stack_depth(popped.stack) == 2); +} + +test "dfs_stack_peek" { + given stack = dfs_stack_init() + try std.testing.expect(pushed = dfs_stack_push(stack, 10)); + try std.testing.expect(peeked = dfs_stack_peek(pushed)); + try std.testing.expect(peeked.valid == true); + try std.testing.expect(peeked.node == 10); + try std.testing.expect(dfs_stack_depth(pushed) == 1); +} + +test "dfs_stack_peek_empty" { + given stack = dfs_stack_init() + try std.testing.expect(peeked = dfs_stack_peek(stack)); + try std.testing.expect(peeked.valid == false); +} + +test "dfs_stack_depth" { + given stack = dfs_stack_init() + try std.testing.expect(result = dfs_stack_push(dfs_stack_push(dfs_stack_push(stack, 1), 2), 3)); + try std.testing.expect(dfs_stack_depth(result) == 3); +} + +test "dfs_node_init" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(node.address == 10); + try std.testing.expect(node.value == 0x1234); + try std.testing.expect(node.visited == VISIT_UNVISITED); + try std.testing.expect(node.child_count == 0); +} + +test "dfs_node_add_child" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(result = dfs_node_add_child(node, 20)); + try std.testing.expect(result.child_count == 1); + try std.testing.expect(result.children[0] == 20); +} + +test "dfs_node_add_multiple_children" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(result = dfs_node_add_child(dfs_node_add_child(dfs_node_add_child(node, 20), 21), 22)); + try std.testing.expect(result.child_count == 3); +} + +test "dfs_node_add_child_overflow" { + given node = DfsNode{.address = 10, .value = 0x1234, .children = [_]u8{20, 21, 22, 23}, .child_count = 4, .visited = 0, .depth = 0} + try std.testing.expect(result = dfs_node_add_child(node, 24)); + try std.testing.expect(result.child_count == 4 // No change); +} + +test "dfs_node_has_child_true" { + given node = dfs_node_add_child(dfs_node_init(10, 0x1234), 20) + try std.testing.expect(dfs_node_has_child(node, 20) == true); +} + +test "dfs_node_has_child_false" { + given node = dfs_node_add_child(dfs_node_init(10, 0x1234), 20) + try std.testing.expect(dfs_node_has_child(node, 21) == false); +} + +test "dfs_node_mark_visiting" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(result = dfs_node_mark_visiting(node)); + try std.testing.expect(result.visited == VISIT_VISITING); +} + +test "dfs_node_mark_visited" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(result = dfs_node_mark_visited(node)); + try std.testing.expect(result.visited == VISIT_VISITED); +} + +test "dfs_node_is_visited_true" { + given node = dfs_node_mark_visited(dfs_node_init(10, 0x1234)) + try std.testing.expect(dfs_node_is_visited(node) == true); +} + +test "dfs_node_is_visited_false" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(dfs_node_is_visited(node) == false); +} + +test "dfs_node_is_visiting_true" { + given node = dfs_node_mark_visiting(dfs_node_init(10, 0x1234)) + try std.testing.expect(dfs_node_is_visiting(node) == true); +} + +test "dfs_traverse_found" { + given graph = [_]DfsNode{dfs_node_add_child(dfs_node_add_child(dfs_node_init(0, 0x1234), 1), 2)} ++ [_]DfsNode{dfs_node_init(1, 0x5678), .children = [_]u8{3}, 255, 255, 255], .child_count = 1, .visited = 0, .depth = 0} ++ [_]DfsNode{dfs_node_init(2, 0x5678), .children = [_]u8{255}, 255, 255, 255], .child_count = 0, .visited = 0, .depth = 0} ++ [_]DfsNode{dfs_node_init(3, 0x5678), .children = [_]u8{255}, 255, 255, 255], .child_count = 0, .visited = 0, .depth = 0} ++ [_]DfsNode{} ** 252 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0x5678, config)); + try std.testing.expect(result.found == true); + try std.testing.expect(result.target_node == 1); +} + +test "dfs_traverse_not_found" { + given graph = [_]DfsNode{dfs_node_init(0, 0x1234), .children = [_]u8{1}, 255, 255, 255], .child_count = 1, .visited = 0, .depth = 0} ++ [_]DfsNode{dfs_node_init(1, 0x2345), .children = [_]u8{2}, 255, 255, 255], .child_count = 1, .visited = 0, .depth = 0} ++ [_]DfsNode{dfs_node_init(2, 0x3456), .children = [_]u8{255}, 255, 255, 255], .child_count = 0, .visited = 0, .depth = 0} ++ [_]DfsNode{} ** 253 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0xFFFF, config)); + try std.testing.expect(result.found == false); +} + +test "dfs_traverse_depth_limited" { + given graph = [_]DfsNode{dfs_node_add_child(dfs_node_init(0, 0x1234), 1)} ++ [_]DfsNode{dfs_node_add_child(dfs_node_init(1, 0x5678), 2)} ++ [_]DfsNode{dfs_node_add_child(dfs_node_init(2, 0x5678), 3)} ++ [_]DfsNode{} ** 253 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_DEPTH_LIMITED, .depth_limit = 1, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0x5678, config)); + try std.testing.expect(result.found == false); + try std.testing.expect(result.depth <= config.depth_limit); +} + +test "encode_dfs_gate" { + given encoded = encode_dfs_gate(1, 0x20, 0x1234) + try std.testing.expect((encoded >> 24) == OP_DFS_GATE); +} + +test "decode_dfs_gate" { + given decoded = decode_dfs_gate(0xE7080A34) + try std.testing.expect(decoded.mode == 1); + try std.testing.expect(decoded.start_addr == 0x20); + try std.testing.expect(decoded.target == 0x0A34); +} + +test "opcode_constant" { + try std.testing.expect(OP_DFS_GATE == 0xE7); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant max_stack_depth_sixteen + assert MAX_STACK_DEPTH == 16 + +invariant max_nodes_256 + assert MAX_NODES == 256 + +invariant node_addr_bits_eight + try std.testing.expect(NODE_ADDR_BITS == 8); + +invariant visit_state_values + try std.testing.expect(VISIT_UNVISITED < VISIT_VISITING and VISIT_VISITING < VISIT_VISITED); + +invariant dfs_mode_values + try std.testing.expect(DFS_MODE_STANDARD <= DFS_MODE_PRUNE_SUBOPTIMAL and DFS_MODE_PRUNE_SUBOPTIMAL <= 3); + +invariant dfs_stack_init_empty + given stack = dfs_stack_init() + assert dfs_stack_is_empty(stack) == true + +invariant dfs_stack_push_increases_depth + given stack = dfs_stack_init() + try std.testing.expect(initial_depth = dfs_stack_depth(stack)); + try std.testing.expect(pushed = dfs_stack_push(stack, 10)); + assert dfs_stack_depth(pushed) == initial_depth + 1 + +invariant dfs_stack_pop_decreases_depth + given stack = dfs_stack_init() + try std.testing.expect(pushed = dfs_stack_push(stack, 10)); + try std.testing.expect(popped = dfs_stack_pop(pushed)); + try std.testing.expect(popped.valid == true); + assert dfs_stack_depth(popped.stack) < dfs_stack_depth(pushed) + +invariant dfs_stack_depth_bound + try std.testing.expect(dfs_stack_depth(dfs_stack_push(dfs_stack_push(dfs_stack_push(dfs_stack_init(), 1), 2), 3)) == 3); + +invariant dfs_stack_depth_max_bound + var stack = dfs_stack_init(); + for (0..MAX_STACK_DEPTH) |_| { + stack = dfs_stack_push(stack, @as(u8, @truncate(0))); + } + try std.testing.expect(dfs_stack_depth(stack) <= MAX_STACK_DEPTH); + +invariant dfs_node_children_bound + try std.testing.expect(dfs_node_add_child(dfs_node_add_child(dfs_node_add_child(dfs_node_add_child(dfs_node_init(10, 0x1234), 20), 21), 22).child_count <= 4); + +invariant dfs_node_visited_sequence + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(visiting = dfs_node_mark_visiting(node)); + try std.testing.expect(visited = dfs_node_mark_visiting(visiting)); + assert node.visited != visited.visited + +invariant dfs_traverse_found_has_valid_target + given graph = [_]DfsNode{dfs_node_add_child(dfs_node_init(0, 0x1234), 1)} ++ [_]DfsNode{dfs_node_init(1, 0x5678), .children = [_]u8{255}, 255, 255, 255], .child_count = 0, .visited = 0, .depth = 0} ++ [_]DfsNode{} ** 254 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0x5678, config)); + try std.testing.expect(result.found implies (result.target_node < MAX_NODES)); + +invariant dfs_traverse_nodes_visited_bound + given graph = [_]DfsNode{} ** 256 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0xFFFF, config)); + try std.testing.expect(result.nodes_visited <= MAX_NODES); + +invariant dfs_traverse_depth_bound + given graph = [_]DfsNode{} ** 256 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_DEPTH_LIMITED, .depth_limit = 5, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0xFFFF, config)); + try std.testing.expect(result.depth <= config.depth_limit); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench dfs_stack_push_latency + measure: nanoseconds to dfs_stack_push(dfs_stack_init(), 10) + target: < 30ns + +bench dfs_stack_pop_latency + measure: nanoseconds to dfs_stack_pop(dfs_stack_push(dfs_stack_init(), 10)) + target: < 30ns + +bench dfs_traverse_latency + measure: nanoseconds to dfs_traverse([_]DfsNode{} ** 256, 0, 0x5678, .{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}) + target: < 1000ns + +bench encode_dfs_gate_latency + measure: nanoseconds to encode_dfs_gate(1, 0x20, 0x1234) + target: < 30ns + +bench decode_dfs_gate_latency + measure: nanoseconds to decode_dfs_gate(0xE7080A34) + target: < 30ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/drowsy_ret.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/drowsy_ret.t27 new file mode 100644 index 0000000000..e42b5b08a9 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/drowsy_ret.t27 @@ -0,0 +1,716 @@ +// SPDX-License-Identifier: Apache-2.0 + +module sacred-drowsy_ret; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_DROWSY_RET : u8 = 0xEC; + +pub const DROWSY_STATE_BITS : u8 = 3; +pub const RET_REASON_BITS : u8 = 3; + +pub const DROWSY_ACTIVE : u8 = 0; +pub const DROWSY_LIGHT : u8 = 1; +pub const DROWSY_DEEP : u8 = 2; +pub const DROWSY_OFF : u8 = 3; +pub const DROWSY_HIBERNATE : u8 = 4; +pub const DROWSY_CRITICAL : u8 = 5; +pub const DROWSY_MAX : u8 = 6; + +pub const RET_WAKE : u8 = 0; +pub const RET_TIMER : u8 = 1; +pub const RET_INTERRUPT : u8 = 2; +pub const RET_ERROR : u8 = 3; +pub const RET_POWER : u8 = 4; +pub const RET_MANUAL : u8 = 5; +pub const RET_MAX : u8 = 6; + +pub const WAKE_CYCLES : u32 = 100; // Cycles to wake from light drowsy +pub const DEEP_WAKE_CYCLES : u32 = 1000; // Cycles to wake from deep drowsy + +// ============================================================================ +// Types +// ============================================================================ + +pub const DrowsyState = enum(u8) { + active = DROWSY_ACTIVE, + light = DROWSY_LIGHT, + deep = DROWSY_DEEP, + off = DROWSY_OFF, + hibernate = DROWSY_HIBERNATE, + critical = DROWSY_CRITICAL, +} + +pub const RetReason = enum(u8) { + wake = RET_WAKE, + timer = RET_TIMER, + interrupt = RET_INTERRUPT, + error = RET_ERROR, + power = RET_POWER, + manual = RET_MANUAL, +} + +pub const DrowsyConfig = struct { + enable_light : bool, + enable_deep : bool, + light_threshold_ms : u32, + deep_threshold_ms : u32, + wake_irq_enable : bool, +} + +pub const DrowsyStatus = struct { + state : DrowsyState, + last_wake_cycle : u32, + drowsy_duration : u32, + wake_count : u32, + last_ret_reason : RetReason, +} + +pub const WakeCondition = struct { + irq_pending : bool, + timer_expired : bool, + power_good : bool, + manual_wake : bool, +} + +// ============================================================================ +// State Functions +// ============================================================================ + +// drowsy_is_active(state: DrowsyState) -> bool +// Check if in active state +pub fn drowsy_is_active(state: DrowsyState) bool { + return state == DrowsyState.active; +} + +// drowsy_is_drowsy(state: DrowsyState) -> bool +// Check if in any drowsy state +pub fn drowsy_is_drowsy(state: DrowsyState) bool { + return state == DrowsyState.light or state == DrowsyState.deep; +} + +// drowsy_is_off(state: DrowsyState) -> bool +// Check if device is off +pub fn drowsy_is_off(state: DrowsyState) bool { + return state == DrowsyState.off or state == DrowsyState.hibernate; +} + +// drowsy_is_critical(state: DrowsyState) -> bool +// Check if in critical state +pub fn drowsy_is_critical(state: DrowsyState) bool { + return state == DrowsyState.critical; +} + +// drowsy_can_enter_light(state: DrowsyState) -> bool +// Check if can enter light drowsy +pub fn drowsy_can_enter_light(state: DrowsyState) bool { + return state == DrowsyState.active; +} + +// drowsy_can_enter_deep(state: DrowsyState) -> bool +// Check if can enter deep drowsy +pub fn drowsy_can_enter_deep(state: DrowsyState) bool { + return state == DrowsyState.light; +} + +// drowsy_wake_cycles(state: DrowsyState) -> u32 +// Get wake cycles for state +pub fn drowsy_wake_cycles(state: DrowsyState) -> u32 { + switch (state) { + DrowsyState.light => return WAKE_CYCLES, + DrowsyState.deep => return DEEP_WAKE_CYCLES, + else => return 0, + } +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// drowsy_config_init() -> DrowsyConfig +// Initialize drowsy config +pub fn drowsy_config_init() -> DrowsyConfig { + return DrowsyConfig { + .enable_light = true, + .enable_deep = true, + .light_threshold_ms = 100, + .deep_threshold_ms = 1000, + .wake_irq_enable = true, + }; +} + +// drowsy_config_disable() -> DrowsyConfig +// Disable drowsy mode +pub fn drowsy_config_disable() -> DrowsyConfig { + return DrowsyConfig { + .enable_light = false, + .enable_deep = false, + .light_threshold_ms = 0, + .deep_threshold_ms = 0, + .wake_irq_enable = false, + }; +} + +// drowsy_config_light_only() -> DrowsyConfig +// Enable only light drowsy +pub fn drowsy_config_light_only(threshold_ms: u32) -> DrowsyConfig { + return DrowsyConfig { + .enable_light = true, + .enable_deep = false, + .light_threshold_ms = threshold_ms, + .deep_threshold_ms = 0, + .wake_irq_enable = true, + }; +} + +// ============================================================================ +// Status Functions +// ============================================================================ + +// drowsy_status_init() -> DrowsyStatus +// Initialize drowsy status +pub fn drowsy_status_init() -> DrowsyStatus { + return DrowsyStatus { + .state = DrowsyState.active, + .last_wake_cycle = 0, + .drowsy_duration = 0, + .wake_count = 0, + .last_ret_reason = RetReason.manual, + }; +} + +// drowsy_status_enter_light(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus +// Enter light drowsy state +pub fn drowsy_status_enter_light(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus { + if (not drowsy_can_enter_light(status.state)) { + return status; + } + return DrowsyStatus { + .state = DrowsyState.light, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = status.last_ret_reason, + }; +} + +// drowsy_status_enter_deep(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus +// Enter deep drowsy state +pub fn drowsy_status_enter_deep(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus { + if (not drowsy_can_enter_deep(status.state)) { + return status; + } + return DrowsyStatus { + .state = DrowsyState.deep, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = status.last_ret_reason, + }; +} + +// drowsy_status_wake(status: DrowsyStatus, reason: RetReason, current_cycle: u32) -> DrowsyStatus +// Wake from drowsy state +pub fn drowsy_status_wake(status: DrowsyStatus, reason: RetReason, current_cycle: u32) -> DrowsyStatus { + return DrowsyStatus { + .state = DrowsyState.active, + .last_wake_cycle = current_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count + 1, + .last_ret_reason = reason, + }; +} + +// drowsy_status_enter_critical(status: DrowsyStatus) -> DrowsyStatus +// Enter critical state (emergency) +pub fn drowsy_status_enter_critical(status: DrowsyStatus) -> DrowsyStatus { + return DrowsyStatus { + .state = DrowsyState.critical, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = RetReason.error, + }; +} + +// drowsy_status_enter_off(status: DrowsyStatus) -> DrowsyStatus +// Enter off state +pub fn drowsy_status_enter_off(status: DrowsyStatus) -> DrowsyStatus { + return DrowsyStatus { + .state = DrowsyState.off, + .last_wake_cycle = 0, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = RetReason.power, + }; +} + +// ============================================================================ +// Wake Condition Functions +// ============================================================================ + +// drowsy_wake_condition_init() -> WakeCondition +// Initialize wake condition +pub fn drowsy_wake_condition_init() -> WakeCondition { + return WakeCondition { + .irq_pending = false, + .timer_expired = false, + .power_good = true, + .manual_wake = false, + }; +} + +// drowsy_should_wake(condition: WakeCondition) -> bool +// Check if should wake from drowsy +pub fn drowsy_should_wake(condition: WakeCondition) -> bool { + return condition.irq_pending or condition.timer_expired or condition.manual_wake; +} + +// drowsy_can_wake_critical(condition: WakeCondition) -> bool +// Check if can wake from critical state +pub fn drowsy_can_wake_critical(condition: WakeCondition) -> bool { + return condition.power_good and condition.manual_wake; +} + +// drowsy_wake_from_condition(condition: WakeCondition) -> RetReason +// Determine wake reason from condition +pub fn drowsy_wake_from_condition(condition: WakeCondition) -> RetReason { + if (condition.irq_pending) { + return RetReason.interrupt; + } else if (condition.timer_expired) { + return RetReason.timer; + } else if (condition.manual_wake) { + return RetReason.manual; + } else if (not condition.power_good) { + return RetReason.power; + } else { + return RetReason.wake; + } +} + +// ============================================================================ +// Drowsy Duration Tracking +// ============================================================================ + +// drowsy_update_duration(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus +// Update drowsy duration tracking +pub fn drowsy_update_duration(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus { + if (not drowsy_is_drowsy(status.state)) { + return DrowsyStatus { + .state = status.state, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = status.last_ret_reason, + }; + } + + const elapsed = if (status.last_wake_cycle > 0) current_cycle - status.last_wake_cycle else 0; + return DrowsyStatus { + .state = status.state, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = status.drowsy_duration + 1, + .wake_count = status.wake_count, + .last_ret_reason = status.last_ret_reason, + }; +} + +// drowsy_check_threshold(status: DrowsyStatus, config: DrowsyConfig) -> bool +// Check if should transition to deeper state +pub fn drowsy_check_threshold(status: DrowsyStatus, config: DrowsyConfig) -> bool { + if (status.state == DrowsyState.light and config.enable_deep) { + // Simplified: convert cycles to approximate ms + const threshold = status.drowsy_duration / 100; + return threshold >= (config.deep_threshold_ms / config.light_threshold_ms); + } + return false; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_drowsy_ret(reason: u8, state: u8, duration: u8) -> u32 +// Encode drowsy return instruction +pub fn encode_drowsy_ret(reason: u8, state: u8, duration: u8) u32 { + // Format: [OP:8][REASON:3][STATE:3][DURATION:16][PADDING:2] + const op : u32 = @as(u32, OP_DROWSY_RET) << 24; + const reason_field : u32 = @as(u32, reason & 0x07) << 21; + const state_field : u32 = @as(u32, state & 0x07) << 18; + const duration_field : u32 = @as(u32, duration & 0xFFFF) << 2; + return op | reason_field | state_field | duration_field; +} + +// decode_drowsy_ret(encoded: u32) -> struct { reason: u8, state: u8, duration: u16 } +// Decode drowsy return instruction +pub fn decode_drowsy_ret(encoded: u32) -> struct { reason: u8, state: u8, duration: u16 } { + const reason : u8 = @as(u8, @truncate((encoded >> 21) & 0x07)); + const state : u8 = @as(u8, @truncate((encoded >> 18) & 0x07)); + const duration : u16 = @as(u16, @truncate((encoded >> 2) & 0xFFFF)); + return .{ .reason = reason, .state = state, .duration = duration }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "drowsy_state_constants" { + try std.testing.expect(DROWSY_ACTIVE == 0); + try std.testing.expect(DROWSY_LIGHT == 1); + try std.testing.expect(DROWSY_DEEP == 2); +} + +test "ret_reason_constants" { + try std.testing.expect(RET_WAKE == 0); + try std.testing.expect(RET_TIMER == 1); + try std.testing.expect(RET_INTERRUPT == 2); + try std.testing.expect(RET_ERROR == 3); +} + +test "wake_cycles_constants" { + try std.testing.expect(WAKE_CYCLES == 100); + try std.testing.expect(DEEP_WAKE_CYCLES == 1000); +} + +test "drowsy_is_active_true" { + try std.testing.expect(drowsy_is_active(DrowsyState.active) == true); +} + +test "drowsy_is_active_false" { + try std.testing.expect(drowsy_is_active(DrowsyState.light) == false); +} + +test "drowsy_is_drowsy_true" { + try std.testing.expect(drowsy_is_drowsy(DrowsyState.light) == true); + try std.testing.expect(drowsy_is_drowsy(DrowsyState.deep) == true); +} + +test "drowsy_is_drowsy_false" { + try std.testing.expect(drowsy_is_drowsy(DrowsyState.active) == false); + try std.testing.expect(drowsy_is_drowsy(DrowsyState.off) == false); +} + +test "drowsy_is_off_true" { + try std.testing.expect(drowsy_is_off(DrowsyState.off) == true); + try std.testing.expect(drowsy_is_off(DrowsyState.hibernate) == true); +} + +test "drowsy_is_off_false" { + try std.testing.expect(drowsy_is_off(DrowsyState.active) == false); + try std.testing.expect(drowsy_is_off(DrowsyState.light) == false); +} + +test "drowsy_is_critical_true" { + try std.testing.expect(drowsy_is_critical(DrowsyState.critical) == true); +} + +test "drowsy_is_critical_false" { + try std.testing.expect(drowsy_is_critical(DrowsyState.active) == false); +} + +test "drowsy_can_enter_light_true" { + try std.testing.expect(drowsy_can_enter_light(DrowsyState.active) == true); +} + +test "drowsy_can_enter_light_false" { + try std.testing.expect(drowsy_can_enter_light(DrowsyState.light) == false); +} + +test "drowsy_can_enter_deep_true" { + try std.testing.expect(drowsy_can_enter_deep(DrowsyState.light) == true); +} + +test "drowsy_can_enter_deep_false" { + try std.testing.expect(drowsy_can_enter_deep(DrowsyState.active) == false); + try std.testing.expect(drowsy_can_enter_deep(DrowsyState.deep) == false); +} + +test "drowsy_wake_cycles_light" { + try std.testing.expect(drowsy_wake_cycles(DrowsyState.light) == WAKE_CYCLES); +} + +test "drowsy_wake_cycles_deep" { + try std.testing.expect(drowsy_wake_cycles(DrowsyState.deep) == DEEP_WAKE_CYCLES); +} + +test "drowsy_config_init_structure" { + given config = drowsy_config_init() + try std.testing.expect(config.enable_light == true); + try std.testing.expect(config.enable_deep == true); + try std.testing.expect(config.wake_irq_enable == true); +} + +test "drowsy_config_disable_structure" { + given config = drowsy_config_disable() + try std.testing.expect(config.enable_light == false); + try std.testing.expect(config.enable_deep == false); + try std.testing.expect(config.wake_irq_enable == false); +} + +test "drowsy_config_light_only_structure" { + given config = drowsy_config_light_only(50) + try std.testing.expect(config.enable_light == true); + try std.testing.expect(config.enable_deep == false); + try std.testing.expect(config.light_threshold_ms == 50); +} + +test "drowsy_status_init_structure" { + given status = drowsy_status_init() + try std.testing.expect(status.state == DrowsyState.active); + try std.testing.expect(status.wake_count == 0); +} + +test "drowsy_status_enter_light" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_enter_light(status, 100)); + try std.testing.expect(result.state == DrowsyState.light); +} + +test "drowsy_status_enter_light_reject" { + given status = DrowsyStatus{.state = DrowsyState.deep, .last_wake_cycle = 0, .drowsy_duration = 0, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_status_enter_light(status, 100)); + try std.testing.expect(result.state == DrowsyState.deep); +} + +test "drowsy_status_enter_deep" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 0, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_status_enter_deep(status, 100)); + try std.testing.expect(result.state == DrowsyState.deep); +} + +test "drowsy_status_wake" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_wake(status, RetReason.interrupt, 200)); + try std.testing.expect(result.state == DrowsyState.active); + try std.testing.expect(result.wake_count == 1); + try std.testing.expect(result.last_ret_reason == RetReason.interrupt); +} + +test "drowsy_status_enter_critical" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_enter_critical(status)); + try std.testing.expect(result.state == DrowsyState.critical); + try std.testing.expect(result.last_ret_reason == RetReason.error); +} + +test "drowsy_status_enter_off" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_enter_off(status)); + try std.testing.expect(result.state == DrowsyState.off); + try std.testing.expect(result.last_ret_reason == RetReason.power); +} + +test "drowsy_wake_condition_init" { + given cond = drowsy_wake_condition_init() + try std.testing.expect(cond.irq_pending == false); + try std.testing.expect(cond.power_good == true); +} + +test "drowsy_should_wake_irq" { + given cond = WakeCondition{.irq_pending = true, .timer_expired = false, .power_good = true, .manual_wake = false} + try std.testing.expect(drowsy_should_wake(cond) == true); +} + +test "drowsy_should_wake_none" { + given cond = drowsy_wake_condition_init() + try std.testing.expect(drowsy_should_wake(cond) == false); +} + +test "drowsy_can_wake_critical_true" { + given cond = WakeCondition{.irq_pending = false, .timer_expired = false, .power_good = true, .manual_wake = true} + try std.testing.expect(drowsy_can_wake_critical(cond) == true); +} + +test "drowsy_can_wake_critical_no_power" { + given cond = WakeCondition{.irq_pending = false, .timer_expired = false, .power_good = false, .manual_wake = true} + try std.testing.expect(drowsy_can_wake_critical(cond) == false); +} + +test "drowsy_wake_from_condition_irq" { + given cond = WakeCondition{.irq_pending = true, .timer_expired = false, .power_good = true, .manual_wake = false} + try std.testing.expect(reason = drowsy_wake_from_condition(cond)); + try std.testing.expect(reason == RetReason.interrupt); +} + +test "drowsy_wake_from_condition_timer" { + given cond = WakeCondition{.irq_pending = false, .timer_expired = true, .power_good = true, .manual_wake = false} + try std.testing.expect(reason = drowsy_wake_from_condition(cond)); + try std.testing.expect(reason == RetReason.timer); +} + +test "drowsy_update_duration_active" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_update_duration(status, 100)); + try std.testing.expect(result.drowsy_duration == 0); +} + +test "drowsy_update_duration_light" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 0, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_update_duration(status, 100)); + try std.testing.expect(result.drowsy_duration == 1); +} + +test "drowsy_update_duration_accumulates" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 5, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_update_duration(status, 100)); + try std.testing.expect(result.drowsy_duration == 6); +} + +test "drowsy_update_duration_resets_on_active" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 10, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(active_status = DrowsyStatus{.state = DrowsyState.active, .last_wake_cycle = 0, .drowsy_duration = 5, .wake_count = 0, .last_ret_reason = RetReason.manual}); + try std.testing.expect(result = drowsy_update_duration(active_status, 100)); + try std.testing.expect(result.drowsy_duration == 0); +} + +test "drowsy_check_threshold_not_reached" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 5, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(config = drowsy_config_init()); + try std.testing.expect(drowsy_check_threshold(status, config) == false); +} + +test "drowsy_check_threshold_reached" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 500, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(config = drowsy_config_init()); + try std.testing.expect(drowsy_check_threshold(status, config) == true); +} + +test "encode_drowsy_ret" { + given encoded = encode_drowsy_ret(1, 2, 0x1234) + try std.testing.expect((encoded >> 24) == OP_DROWSY_RET); +} + +test "decode_drowsy_ret" { + given decoded = decode_drowsy_ret(0xEC481234) + try std.testing.expect(decoded.reason == 1); + try std.testing.expect(decoded.state == 2); + try std.testing.expect(decoded.duration == 0x1234); +} + +test "opcode_constant" { + try std.testing.expect(OP_DROWSY_RET == 0xEC); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant drowsy_state_range + try std.testing.expect(DROWSY_ACTIVE >= 0 and DROWSY_CRITICAL <= 5); + +invariant ret_reason_range + try std.testing.expect(RET_WAKE >= 0 and RET_MAX <= 6); + +invariant wake_cycles_positive + assert WAKE_CYCLES > 0 and DEEP_WAKE_CYCLES > 0 + +invariant deep_wake_larger_than_light + assert DEEP_WAKE_CYCLES > WAKE_CYCLES + +invariant drowsy_is_drowsy_excludes_active_off + given state = DrowsyState.active + assert drowsy_is_drowsy(state) == false + try std.testing.expect(drowsy_is_off(DrowsyState.light) == false); + +invariant drowsy_is_off_excludes_active_drowsy + given state = DrowsyState.active + assert drowsy_is_off(state) == false + try std.testing.expect(drowsy_is_off(DrowsyState.light) == false); + +invariant drowsy_config_init_enables_light + given config = drowsy_config_init() + try std.testing.expect(config.enable_light == true); + +invariant drowsy_config_enable_both_or_neither + given config1 = drowsy_config_init() + try std.testing.expect(config2 = drowsy_config_light_only(100)); + try std.testing.expect((config1.enable_light and config1.enable_deep) or (not config1.enable_light and not config1.enable_deep)); + or (config2.enable_light and not config2.enable_deep) + +invariant drowsy_status_init_active + given status = drowsy_status_init() + try std.testing.expect(status.state == DrowsyState.active); + +invariant drowsy_status_wake_increases_count + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_wake(status, RetReason.manual, 100)); + try std.testing.expect(result.wake_count == status.wake_count + 1); + +invariant drowsy_status_wake_sets_active + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 100, .wake_count = 5, .last_ret_reason = RetReason.timer} + try std.testing.expect(result = drowsy_status_wake(status, RetReason.interrupt, 200)); + try std.testing.expect(result.state == DrowsyState.active); + +invariant drowsy_status_enter_critical_reason + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_enter_critical(status)); + try std.testing.expect(result.last_ret_reason == RetReason.error); + +invariant drowsy_wake_condition_init_defaults + given cond = drowsy_wake_condition_init() + try std.testing.expect(cond.irq_pending == false and cond.timer_expired == false and cond.manual_wake == false); + +invariant drowsy_should_wake_false_for_default + given cond = drowsy_wake_condition_init() + try std.testing.expect(drowsy_should_wake(cond) == false); + +invariant drowsy_wake_from_condition_no_match + given cond = drowsy_wake_condition_init() + try std.testing.expect(reason = drowsy_wake_from_condition(cond)); + try std.testing.expect(reason == RetReason.wake); + +invariant drowsy_update_duration_zero_for_non_drowsy + given status = DrowsyStatus{.state = DrowsyState.active, .last_wake_cycle = 0, .drowsy_duration = 10, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_update_duration(status, 100)); + try std.testing.expect(result.drowsy_duration == 0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench drowsy_is_active_latency + measure: nanoseconds to drowsy_is_active(DrowsyState.active) + target: < 10ns + +bench drowsy_is_drowsy_latency + measure: nanoseconds to drowsy_is_drowsy(DrowsyState.light) + target: < 10ns + +bench drowsy_wake_cycles_latency + measure: nanoseconds to drowsy_wake_cycles(DrowsyState.light) + target: < 20ns + +bench drowsy_config_init_latency + measure: nanoseconds to drowsy_config_init() + target: < 30ns + +bench drowsy_status_init_latency + measure: nanoseconds to drowsy_status_init() + target: < 30ns + +bench drowsy_status_enter_light_latency + measure: nanoseconds to drowsy_status_enter_light(drowsy_status_init(), 100) + target: < 30ns + +bench drowsy_status_wake_latency + measure: nanoseconds to drowsy_status_wake(drowsy_status_init(), RetReason.interrupt, 200) + target: < 30ns + +bench drowsy_should_wake_latency + measure: nanoseconds to drowsy_should_wake(drowsy_wake_condition_init()) + target: < 20ns + +bench drowsy_update_duration_latency + measure: nanoseconds to drowsy_update_duration(drowsy_status_init(), 100) + target: < 30ns + +bench encode_drowsy_ret_latency + measure: nanoseconds to encode_drowsy_ret(1, 2, 0x1234) + target: < 30ns + +bench decode_drowsy_ret_latency + measure: nanoseconds to decode_drowsy_ret(0xEC481234) + target: < 30ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/fbb_active_path.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/fbb_active_path.t27 new file mode 100644 index 0000000000..4a401de031 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/fbb_active_path.t27 @@ -0,0 +1,756 @@ +// SPDX-License-Identifier: Apache-2.0 +; fbb_active_path.t27 — FPGA Feedback Bridge Active Path +; Active path management for FPGA feedback bridge +; φ² + 1/φ² = 3 | TRINITY + +module fbb-active-path; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const FBB_OPCODE_BASE : u8 = 0xD0; // FBB opcodes start at 0xD0 + +pub const FBB_OP_PATH_SELECT : u8 = 0x00; +pub const FBB_OP_PATH_ENABLE : u8 = 0x01; +pub const FBB_OP_PATH_DISABLE : u8 = 0x02; +pub const FBB_OP_PATH_STATUS : u8 = 0x03; +pub const FBB_OP_PATH_RESET : u8 = 0x04; +pub const FBB_OP_PATH_BYPASS : u8 = 0x05; +pub const FBB_OP_PATH_LOOPBACK : u8 = 0x06; +pub const FBB_OP_PATH_CONFIG : u8 = 0x07; +pub const FBB_OP_MAX : u8 = 0x08; + +pub const FBB_PATH_COUNT : u8 = 8; +pub const FBB_MAX_PATHS : u8 = 8; + +pub const FBB_PATH_STATE_DISABLED : u8 = 0; +pub const FBB_PATH_STATE_ENABLED : u8 = 1; +pub const FBB_PATH_STATE_ACTIVE : u8 = 2; +pub const FBB_PATH_STATE_ERROR : u8 = 3; +pub const FBB_PATH_STATE_BYPASSED : u8 = 4; +pub const FBB_PATH_STATE_MAX : u8 = 5; + +pub const FBB_LATENCY_MIN_CYCLES : u8 = 1; +pub const FBB_LATENCY_MAX_CYCLES : u8 = 16; +pub const FBB_DEFAULT_LATENCY : u8 = 4; + +pub const FBB_BANDWIDTH_MIN_MBPS : u32 = 100; +pub const FBB_BANDWIDTH_MAX_MBPS : u32 = 10000; +pub const FBB_DEFAULT_BANDWIDTH : u32 = 1000; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FbbOp = enum(u8) { + path_select = FBB_OP_PATH_SELECT, + path_enable = FBB_OP_PATH_ENABLE, + path_disable = FBB_OP_PATH_DISABLE, + path_status = FBB_OP_PATH_STATUS, + path_reset = FBB_OP_PATH_RESET, + path_bypass = FBB_OP_PATH_BYPASS, + path_loopback = FBB_OP_PATH_LOOPBACK, + path_config = FBB_OP_PATH_CONFIG, +} + +pub const FbbPathState = enum(u8) { + disabled = FBB_PATH_STATE_DISABLED, + enabled = FBB_PATH_STATE_ENABLED, + active = FBB_PATH_STATE_ACTIVE, + error = FBB_PATH_STATE_ERROR, + bypassed = FBB_PATH_STATE_BYPASSED, +} + +pub const FbbPathConfig = struct { + path_index : u8, + enabled : bool, + latency_cycles : u8, + bandwidth_mbps : u32, + priority : u8, + auto_bypass : bool, +} + +pub const FbbPathStatus = struct { + path_index : u8, + state : FbbPathState, + active : bool, + error_code : u8, + throughput_mbps : u32, + latency_measured : u16, +} + +pub const FbbActivePath = struct { + active_path : u8, + bypass_path : u8, + loopback_enabled : bool, + paths : [FBB_MAX_PATHS]FbbPathStatus, +} + +pub const FbbCommand = struct { + op : FbbOp, + path_index : u8, + param1 : u16, + param2 : u16, +} + +// ============================================================================ +// Path Config Functions +// ============================================================================ + +// fbb_path_config_init(index: u8) -> FbbPathConfig +// Initialize path config +pub fn fbb_path_config_init(index: u8) -> FbbPathConfig { + return FbbPathConfig { + .path_index = index & 0x07, + .enabled = false, + .latency_cycles = FBB_DEFAULT_LATENCY, + .bandwidth_mbps = FBB_DEFAULT_BANDWIDTH, + .priority = 4, + .auto_bypass = false, + }; +} + +// fbb_path_config_low_latency(index: u8) -> FbbPathConfig +// Create low latency path config +pub fn fbb_path_config_low_latency(index: u8) -> FbbPathConfig { + return FbbPathConfig { + .path_index = index & 0x07, + .enabled = false, + .latency_cycles = FBB_LATENCY_MIN_CYCLES, + .bandwidth_mbps = FBB_DEFAULT_BANDWIDTH, + .priority = 7, + .auto_bypass = false, + }; +} + +// fbb_path_config_high_bandwidth(index: u8) -> FbbPathConfig +// Create high bandwidth path config +pub fn fbb_path_config_high_bandwidth(index: u8) -> FbbPathConfig { + return FbbPathConfig { + .path_index = index & 0x07, + .enabled = false, + .latency_cycles = 8, + .bandwidth_mbps = FBB_BANDWIDTH_MAX_MBPS, + .priority = 3, + .auto_bypass = false, + }; +} + +// fbb_path_config_enable(config: FbbPathConfig) -> FbbPathConfig +// Enable path in config +pub fn fbb_path_config_enable(config: FbbPathConfig) -> FbbPathConfig { + return FbbPathConfig { + .path_index = config.path_index, + .enabled = true, + .latency_cycles = config.latency_cycles, + .bandwidth_mbps = config.bandwidth_mbps, + .priority = config.priority, + .auto_bypass = config.auto_bypass, + }; +} + +// ============================================================================ +// Path Status Functions +// ============================================================================ + +// fbb_path_status_init(index: u8) -> FbbPathStatus +// Initialize path status +pub fn fbb_path_status_init(index: u8) -> FbbPathStatus { + return FbbPathStatus { + .path_index = index & 0x07, + .state = FbbPathState.disabled, + .active = false, + .error_code = 0, + .throughput_mbps = 0, + .latency_measured = 0, + }; +} + +// fbb_path_status_enable(status: FbbPathStatus) -> FbbPathStatus +// Enable path +pub fn fbb_path_status_enable(status: FbbPathStatus) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = FbbPathState.enabled, + .active = false, + .error_code = 0, + .throughput_mbps = status.throughput_mbps, + .latency_measured = status.latency_measured, + }; +} + +// fbb_path_status_activate(status: FbbPathStatus) -> FbbPathStatus +// Activate path +pub fn fbb_path_status_activate(status: FbbPathStatus) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = FbbPathState.active, + .active = true, + .error_code = 0, + .throughput_mbps = status.throughput_mbps, + .latency_measured = status.latency_measured, + }; +} + +// fbb_path_status_bypass(status: FbbPathStatus) -> FbbPathStatus +// Bypass path +pub fn fbb_path_status_bypass(status: FbbPathStatus) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = FbbPathState.bypassed, + .active = false, + .error_code = 0, + .throughput_mbps = 0, + .latency_measured = 0, + }; +} + +// fbb_path_status_error(status: FbbPathStatus, error_code: u8) -> FbbPathStatus +// Set error status +pub fn fbb_path_status_error(status: FbbPathStatus, error_code: u8) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = FbbPathState.error, + .active = false, + .error_code = error_code, + .throughput_mbps = 0, + .latency_measured = 0, + }; +} + +// fbb_path_status_update_metrics(status: FbbPathStatus, throughput: u32, latency: u16) -> FbbPathStatus +// Update path metrics +pub fn fbb_path_status_update_metrics(status: FbbPathStatus, throughput: u32, latency: u16) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = status.state, + .active = status.active, + .error_code = status.error_code, + .throughput_mbps = throughput, + .latency_measured = latency, + }; +} + +// ============================================================================ +// Active Path Functions +// ============================================================================ + +// fbb_active_path_init() -> FbbActivePath +// Initialize active path +pub fn fbb_active_path_init() -> FbbActivePath { + var paths : [FBB_MAX_PATHS]FbbPathStatus = undefined; + for (0..FBB_MAX_PATHS) |i| { + paths[i] = fbb_path_status_init(@as(u8, @truncate(i))); + } + return FbbActivePath { + .active_path = 0, + .bypass_path = 0xFF, // No bypass + .loopback_enabled = false, + .paths = paths, + }; +} + +// fbb_active_path_select(active: FbbActivePath, path_index: u8) -> FbbActivePath +// Select active path +pub fn fbb_active_path_select(active: FbbActivePath, path_index: u8) -> FbbActivePath { + const idx = path_index & 0x07; + var new_paths = active.paths; + + // Deactivate current active path + new_paths[active.active_path] = fbb_path_status_enable(new_paths[active.active_path]); + + // Activate new path + new_paths[idx] = fbb_path_status_activate(new_paths[idx]); + + return FbbActivePath { + .active_path = idx, + .bypass_path = active.bypass_path, + .loopback_enabled = active.loopback_enabled, + .paths = new_paths, + }; +} + +// fbb_active_path_enable_bypass(active: FbbActivePath, bypass_index: u8) -> FbbActivePath +// Enable bypass path +pub fn fbb_active_path_enable_bypass(active: FbbActivePath, bypass_index: u8) -> FbbActivePath { + const idx = bypass_index & 0x07; + var new_paths = active.paths; + new_paths[idx] = fbb_path_status_bypass(new_paths[idx]); + + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = idx, + .loopback_enabled = active.loopback_enabled, + .paths = new_paths, + }; +} + +// fbb_active_path_disable_bypass(active: FbbActivePath) -> FbbActivePath +// Disable bypass path +pub fn fbb_active_path_disable_bypass(active: FbbActivePath) -> FbbActivePath { + var new_paths = active.paths; + + // Restore bypassed path + if (active.bypass_path != 0xFF) { + new_paths[active.bypass_path] = fbb_path_status_enable(new_paths[active.bypass_path]); + } + + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = 0xFF, + .loopback_enabled = active.loopback_enabled, + .paths = new_paths, + }; +} + +// fbb_active_path_enable_loopback(active: FbbActivePath) -> FbbActivePath +// Enable loopback +pub fn fbb_active_path_enable_loopback(active: FbbActivePath) -> FbbActivePath { + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = active.bypass_path, + .loopback_enabled = true, + .paths = active.paths, + }; +} + +// fbb_active_path_disable_loopback(active: FbbActivePath) -> FbbActivePath +// Disable loopback +pub fn fbb_active_path_disable_loopback(active: FbbActivePath) -> FbbActivePath { + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = active.bypass_path, + .loopback_enabled = false, + .paths = active.paths, + }; +} + +// fbb_active_path_get_path(active: FbbActivePath, path_index: u8) -> FbbPathStatus +// Get path status +pub fn fbb_active_path_get_path(active: FbbActivePath, path_index: u8) -> FbbPathStatus { + const idx = path_index & 0x07; + return active.paths[idx]; +} + +// fbb_active_path_update_path_metrics(active: FbbActivePath, path_index: u8, throughput: u32, latency: u16) -> FbbActivePath +// Update path metrics +pub fn fbb_active_path_update_path_metrics(active: FbbActivePath, path_index: u8, throughput: u32, latency: u16) -> FbbActivePath { + const idx = path_index & 0x07; + var new_paths = active.paths; + new_paths[idx] = fbb_path_status_update_metrics(new_paths[idx], throughput, latency); + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = active.bypass_path, + .loopback_enabled = active.loopback_enabled, + .paths = new_paths, + }; +} + +// ============================================================================ +// Validation Functions +// ============================================================================ + +// fbb_path_index_valid(index: u8) -> bool +// Check if path index is valid +pub fn fbb_path_index_valid(index: u8) -> bool { + return index < FBB_PATH_COUNT; +} + +// fbb_latency_valid(latency: u8) -> bool +// Check if latency is valid +pub fn fbb_latency_valid(latency: u8) -> bool { + return latency >= FBB_LATENCY_MIN_CYCLES and latency <= FBB_LATENCY_MAX_CYCLES; +} + +// fbb_bandwidth_valid(bandwidth: u32) -> bool +// Check if bandwidth is valid +pub fn fbb_bandwidth_valid(bandwidth: u32) -> bool { + return bandwidth >= FBB_BANDWIDTH_MIN_MBPS and bandwidth <= FBB_BANDWIDTH_MAX_MBPS; +} + +// fbb_path_config_valid(config: FbbPathConfig) -> bool +// Check if path config is valid +pub fn fbb_path_config_valid(config: FbbPathConfig) -> bool { + return fbb_path_index_valid(config.path_index) + try std.testing.expect(fbb_latency_valid(config.latency_cycles)); + try std.testing.expect(fbb_bandwidth_valid(config.bandwidth_mbps);); +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_fbb_cmd(op: u8, path: u8, param1: u8, param2: u8) -> u32 +// Encode FBB command +pub fn encode_fbb_cmd(op: u8, path: u8, param1: u8, param2: u8) u32 { + // Format: [OP:8][PATH:3][PARAM1:8][PARAM2:8][RES:5] + const op_field : u32 = @as(u32, op) << 24; + const path_field : u32 = @as(u32, path & 0x07) << 21; + const param1_field : u32 = @as(u32, param1) << 13; + const param2_field : u32 = @as(u32, param2) << 5; + return op_field | path_field | param1_field | param2_field; +} + +// decode_fbb_cmd(encoded: u32) -> struct { op: u8, path: u8, param1: u8, param2: u8 } +// Decode FBB command +pub fn decode_fbb_cmd(encoded: u32) -> struct { op: u8, path: u8, param1: u8, param2: u8 } { + const op : u8 = @as(u8, @truncate((encoded >> 24) & 0xFF)); + const path : u8 = @as(u8, @truncate((encoded >> 21) & 0x07)); + const param1 : u8 = @as(u8, @truncate((encoded >> 13) & 0xFF)); + const param2 : u8 = @as(u8, @truncate((encoded >> 5) & 0xFF)); + return .{ .op = op, .path = path, .param1 = param1, .param2 = param2 }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fbb_opcode_base" { + try std.testing.expect(FBB_OPCODE_BASE == 0xD0); +} + +test "fbb_op_constants" { + try std.testing.expect(FBB_OP_PATH_SELECT == 0); + try std.testing.expect(FBB_OP_PATH_ENABLE == 1); + try std.testing.expect(FBB_OP_PATH_DISABLE == 2); + try std.testing.expect(FBB_OP_PATH_BYPASS == 5); +} + +test "fbb_path_state_constants" { + try std.testing.expect(FBB_PATH_STATE_DISABLED == 0); + try std.testing.expect(FBB_PATH_STATE_ENABLED == 1); + try std.testing.expect(FBB_PATH_STATE_ACTIVE == 2); + try std.testing.expect(FBB_PATH_STATE_BYPASSED == 4); +} + +test "fbb_path_count" { + try std.testing.expect(FBB_PATH_COUNT == 8); + try std.testing.expect(FBB_MAX_PATHS == 8); +} + +test "latency_constants" { + try std.testing.expect(FBB_LATENCY_MIN_CYCLES == 1); + try std.testing.expect(FBB_LATENCY_MAX_CYCLES == 16); + try std.testing.expect(FBB_DEFAULT_LATENCY == 4); +} + +test "bandwidth_constants" { + try std.testing.expect(FBB_BANDWIDTH_MIN_MBPS == 100); + try std.testing.expect(FBB_BANDWIDTH_MAX_MBPS == 10000); + try std.testing.expect(FBB_DEFAULT_BANDWIDTH == 1000); +} + +test "fbb_path_config_init_structure" { + given config = fbb_path_config_init(0) + try std.testing.expect(config.path_index == 0); + try std.testing.expect(config.enabled == false); + try std.testing.expect(config.latency_cycles == FBB_DEFAULT_LATENCY); +} + +test "fbb_path_config_low_latency_structure" { + given config = fbb_path_config_low_latency(1) + try std.testing.expect(config.path_index == 1); + try std.testing.expect(config.latency_cycles == FBB_LATENCY_MIN_CYCLES); + try std.testing.expect(config.priority == 7); +} + +test "fbb_path_config_high_bandwidth_structure" { + given config = fbb_path_config_high_bandwidth(2) + try std.testing.expect(config.path_index == 2); + try std.testing.expect(config.bandwidth_mbps == FBB_BANDWIDTH_MAX_MBPS); +} + +test "fbb_path_config_enable" { + given config = fbb_path_config_init(0) + try std.testing.expect(result = fbb_path_config_enable(config)); + try std.testing.expect(result.enabled == true); +} + +test "fbb_path_status_init_structure" { + given status = fbb_path_status_init(0) + try std.testing.expect(status.path_index == 0); + try std.testing.expect(status.state == FbbPathState.disabled); + try std.testing.expect(status.active == false); +} + +test "fbb_path_status_enable" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_enable(status)); + try std.testing.expect(result.state == FbbPathState.enabled); + try std.testing.expect(result.active == false); +} + +test "fbb_path_status_activate" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_activate(status)); + try std.testing.expect(result.state == FbbPathState.active); + try std.testing.expect(result.active == true); +} + +test "fbb_path_status_bypass" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_bypass(status)); + try std.testing.expect(result.state == FbbPathState.bypassed); + try std.testing.expect(result.active == false); +} + +test "fbb_path_status_error" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_error(status, 1)); + try std.testing.expect(result.state == FbbPathState.error); + try std.testing.expect(result.error_code == 1); +} + +test "fbb_path_status_update_metrics" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_update_metrics(status, 5000, 100)); + try std.testing.expect(result.throughput_mbps == 5000); + try std.testing.expect(result.latency_measured == 100); +} + +test "fbb_active_path_init_structure" { + given active = fbb_active_path_init() + try std.testing.expect(active.active_path == 0); + try std.testing.expect(active.bypass_path == 0xFF); + try std.testing.expect(active.loopback_enabled == false); +} + +test "fbb_active_path_select" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_select(active, 3)); + try std.testing.expect(result.active_path == 3); + try std.testing.expect(result.paths[3].active == true); +} + +test "fbb_active_path_enable_bypass" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_enable_bypass(active, 5)); + try std.testing.expect(result.bypass_path == 5); + try std.testing.expect(result.paths[5].state == FbbPathState.bypassed); +} + +test "fbb_active_path_disable_bypass" { + given active = fbb_active_path_init() + try std.testing.expect(with_bypass = fbb_active_path_enable_bypass(active, 5)); + try std.testing.expect(result = fbb_active_path_disable_bypass(with_bypass)); + try std.testing.expect(result.bypass_path == 0xFF); +} + +test "fbb_active_path_enable_loopback" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_enable_loopback(active)); + try std.testing.expect(result.loopback_enabled == true); +} + +test "fbb_active_path_disable_loopback" { + given active = fbb_active_path_init() + try std.testing.expect(with_loopback = fbb_active_path_enable_loopback(active)); + try std.testing.expect(result = fbb_active_path_disable_loopback(with_loopback)); + try std.testing.expect(result.loopback_enabled == false); +} + +test "fbb_active_path_get_path" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_get_path(active, 2)); + try std.testing.expect(result.path_index == 2); +} + +test "fbb_active_path_update_path_metrics" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_update_path_metrics(active, 2, 3000, 50)); + try std.testing.expect(result.paths[2].throughput_mbps == 3000); +} + +test "fbb_path_index_valid_true" { + try std.testing.expect(fbb_path_index_valid(0) == true); + try std.testing.expect(fbb_path_index_valid(7) == true); +} + +test "fbb_path_index_valid_false" { + try std.testing.expect(fbb_path_index_valid(8) == false); + try std.testing.expect(fbb_path_index_valid(255) == false); +} + +test "fbb_latency_valid_true" { + try std.testing.expect(fbb_latency_valid(1) == true); + try std.testing.expect(fbb_latency_valid(10) == true); + try std.testing.expect(fbb_latency_valid(16) == true); +} + +test "fbb_latency_valid_false" { + try std.testing.expect(fbb_latency_valid(0) == false); + try std.testing.expect(fbb_latency_valid(17) == false); +} + +test "fbb_bandwidth_valid_true" { + try std.testing.expect(fbb_bandwidth_valid(100) == true); + try std.testing.expect(fbb_bandwidth_valid(5000) == true); + try std.testing.expect(fbb_bandwidth_valid(10000) == true); +} + +test "fbb_bandwidth_valid_false" { + try std.testing.expect(fbb_bandwidth_valid(50) == false); + try std.testing.expect(fbb_bandwidth_valid(20000) == false); +} + +test "fbb_path_config_valid_true" { + given config = fbb_path_config_init(0) + try std.testing.expect(fbb_path_config_valid(config) == true); +} + +test "fbb_path_config_valid_false_invalid_latency" { + given config = FbbPathConfig{.path_index = 0, .enabled = false, .latency_cycles = 0, .bandwidth_mbps = 1000, .priority = 4, .auto_bypass = false} + try std.testing.expect(fbb_path_config_valid(config) == false); +} + +test "encode_fbb_cmd" { + given encoded = encode_fbb_cmd(0x00, 3, 10, 20) + try std.testing.expect((encoded >> 24) == 0x00); +} + +test "decode_fbb_cmd" { + given decoded = decode_fbb_cmd(0x001A14A0) + try std.testing.expect(decoded.op == 0); + try std.testing.expect(decoded.path == 3); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fbb_path_count_positive + assert FBB_PATH_COUNT > 0 + try std.testing.expect(FBB_MAX_PATHS == FBB_PATH_COUNT); + +invariant latency_range_positive + assert FBB_LATENCY_MIN_CYCLES > 0 and FBB_LATENCY_MAX_CYCLES >= FBB_LATENCY_MIN_CYCLES + +invariant bandwidth_range_positive + assert FBB_BANDWIDTH_MIN_MBPS > 0 and FBB_BANDWIDTH_MAX_MBPS >= FBB_BANDWIDTH_MIN_MBPS + +invariant default_latency_in_range + assert fbb_latency_valid(FBB_DEFAULT_LATENCY) + +invariant default_bandwidth_in_range + assert fbb_bandwidth_valid(FBB_DEFAULT_BANDWIDTH) + +invariant fbb_path_config_init_valid + given config = fbb_path_config_init(0) + assert fbb_path_config_valid(config) + +invariant fbb_path_config_low_latency_min_latency + given config = fbb_path_config_low_latency(0) + assert config.latency_cycles == FBB_LATENCY_MIN_CYCLES + +invariant fbb_path_config_high_bandwidth_max_bandwidth + given config = fbb_path_config_high_bandwidth(0) + assert config.bandwidth_mbps == FBB_BANDWIDTH_MAX_MBPS + +invariant fbb_path_status_init_disabled + given status = fbb_path_status_init(0) + try std.testing.expect(status.state == FbbPathState.disabled); + +invariant fbb_path_status_enable_enabled_not_active + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_enable(status)); + try std.testing.expect(result.state == FbbPathState.enabled and result.active == false); + +invariant fbb_path_status_activate_active + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_activate(status)); + try std.testing.expect(result.state == FbbPathState.active and result.active == true); + +invariant fbb_path_status_bypass_bypassed_not_active + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_bypass(status)); + try std.testing.expect(result.state == FbbPathState.bypassed and result.active == false); + +invariant fbb_active_path_init_no_bypass + given active = fbb_active_path_init() + try std.testing.expect(active.bypass_path == 0xFF); + +invariant fbb_active_path_select_updates_active + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_select(active, 5)); + try std.testing.expect(result.active_path == 5); + +invariant fbb_active_path_enable_loopback_sets_flag + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_enable_loopback(active)); + try std.testing.expect(result.loopback_enabled == true); + +invariant fbb_active_path_disable_loopback_clears_flag + given active = fbb_active_path_init() + try std.testing.expect(with_loopback = fbb_active_path_enable_loopback(active)); + try std.testing.expect(result = fbb_active_path_disable_loopback(with_loopback)); + try std.testing.expect(result.loopback_enabled == false); + +invariant fbb_path_index_valid_range + assert fbb_path_index_valid(0) and not fbb_path_index_valid(FBB_PATH_COUNT) + +invariant fbb_latency_valid_range + assert fbb_latency_valid(FBB_LATENCY_MIN_CYCLES) + try std.testing.expect(fbb_latency_valid(FBB_LATENCY_MAX_CYCLES)); + try std.testing.expect(not fbb_latency_valid(FBB_LATENCY_MAX_CYCLES + 1)); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fbb_path_config_init_latency + measure: nanoseconds to fbb_path_config_init(0) + target: < 20ns + +bench fbb_path_config_low_latency_latency + measure: nanoseconds to fbb_path_config_low_latency(0) + target: < 20ns + +bench fbb_path_config_high_bandwidth_latency + measure: nanoseconds to fbb_path_config_high_bandwidth(0) + target: < 20ns + +bench fbb_path_status_init_latency + measure: nanoseconds to fbb_path_status_init(0) + target: < 20ns + +bench fbb_path_status_enable_latency + measure: nanoseconds to fbb_path_status_enable(fbb_path_status_init(0)) + target: < 15ns + +bench fbb_path_status_activate_latency + measure: nanoseconds to fbb_path_status_activate(fbb_path_status_init(0)) + target: < 15ns + +bench fbb_active_path_init_latency + measure: nanoseconds to fbb_active_path_init() + target: < 50ns + +bench fbb_active_path_select_latency + measure: nanoseconds to fbb_active_path_select(fbb_active_path_init(), 3) + target: < 50ns + +bench fbb_active_path_enable_loopback_latency + measure: nanoseconds to fbb_active_path_enable_loopback(fbb_active_path_init()) + target: < 15ns + +bench fbb_path_index_valid_latency + measure: nanoseconds to fbb_path_index_valid(5) + target: < 10ns + +bench fbb_latency_valid_latency + measure: nanoseconds to fbb_latency_valid(10) + target: < 10ns + +bench fbb_bandwidth_valid_latency + measure: nanoseconds to fbb_bandwidth_valid(5000) + target: < 15ns + +bench encode_fbb_cmd_latency + measure: nanoseconds to encode_fbb_cmd(0x00, 3, 10, 20) + target: < 20ns + +bench decode_fbb_cmd_latency + measure: nanoseconds to decode_fbb_cmd(0x001A14A0) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/fp8_e4m3.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/fp8_e4m3.t27 new file mode 100644 index 0000000000..06fb882f2d --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/fp8_e4m3.t27 @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: Apache-2.0 +; fp8_e4m3.t27 — FP8 E4M3 8-bit Floating Point +; 8-bit float with 4 exponent, 3 mantissa bits (no implicit leading bit) +; OCP FP8 format optimized for neural network training +; Range: ~-240 to ~240, precision: ~6-7 significant bits +; φ² + 1/φ² = 3 | TRINITY + +module triformat-fp8_e4m3; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 4; +pub const MANT_BITS : u8 = 3; + +pub const SIGN_SHIFT : u8 = 7; +pub const EXP_SHIFT : u8 = 3; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u8 = 0x80; // 1 << 7 +pub const EXP_MASK : u8 = 0x78; // 0b01111000 +pub const MANT_MASK : u8 = 0x07; // 0b00000111 + +pub const EXP_MAX : u8 = 15; // All ones in 4 bits +pub const EXP_MIN : u8 = 0; +pub const EXP_BIAS : u8 = 7; // Exponent bias for FP8 E4M3 + +pub const MANT_DIVISOR : u8 = 8; // 2^3 + +// FP8 E4M3 special values +pub const FP8_ZERO_POS : u8 = 0x00; +pub const FP8_ZERO_NEG : u8 = 0x80; +pub const FP8_NAN_POS : u8 = 0x7F; // All exp + mant != 0 +pub const FP8_NAN_NEG : u8 = 0xFF; +pub const FP8_INF_POS : u8 = 0x78; // All exp + mant = 0 +pub const FP8_INF_NEG : u8 = 0xF8; + +// Subnormal exponent +pub const SUBNORMAL_EXP : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FP8E4M3 = u8; // 8-bit value + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// fp8_extract_sign(fp8: FP8E4M3) -> i8 +// Extract sign bit (bit 7) +// Returns: 0 for positive, -1 for negative +pub fn fp8_extract_sign(fp8: FP8E4M3) i8 { + const bit = (fp8 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// fp8_extract_exponent(fp8: FP8E4M3) -> u8 +// Extract exponent bits (bits 6-3) +// Returns: 0-15 +pub fn fp8_extract_exponent(fp8: FP8E4M3) u8 { + return (fp8 >> EXP_SHIFT) & 0x0F; +} + +// fp8_extract_mantissa(fp8: FP8E4M3) -> u8 +// Extract mantissa bits (bits 2-0) +// Returns: 0-7 +pub fn fp8_extract_mantissa(fp8: FP8E4M3) u8 { + return fp8 & MANT_MASK; +} + +// fp8_from_components(sign: i8, exp: u8, mant: u8) -> FP8E4M3 +// Assemble FP8 from sign, exponent, mantissa +pub fn fp8_from_components(sign: i8, exp: u8, mant: u8) FP8E4M3 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(FP8E4M3, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(FP8E4M3, @intCast(exp & 0x0F)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// fp8_is_zero(fp8: FP8E4M3) -> bool +// Check if FP8 is zero (positive or negative) +pub fn fp8_is_zero(fp8: FP8E4M3) bool { + return fp8 == FP8_ZERO_POS or fp8 == FP8_ZERO_NEG; +} + +// fp8_is_special(fp8: FP8E4M3) -> bool +// Check if FP8 is Inf or NaN (exp == 15) +pub fn fp8_is_special(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX; +} + +// fp8_is_inf(fp8: FP8E4M3) -> bool +// Check if FP8 is infinity (exp == 15, mant == 0) +pub fn fp8_is_inf(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) == 0; +} + +// fp8_is_nan(fp8: FP8E4M3) -> bool +// Check if FP8 is NaN (exp == 15, mant != 0) +pub fn fp8_is_nan(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) != 0; +} + +// fp8_is_subnormal(fp8: FP8E4M3) -> bool +// Check if FP8 is subnormal (exp == 0, mant != 0) +pub fn fp8_is_subnormal(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == 0 and fp8_extract_mantissa(fp8) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// fp8_encode_f32(value: f32) -> FP8E4M3 +// Encode IEEE 754 single precision to FP8 E4M3 +// Round-to-nearest, ties to even +pub fn fp8_encode_f32(value: f32) FP8E4M3 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return if (value < 0.0) FP8_NAN_NEG else FP8_NAN_POS; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i16 = @as(i16, @intCast((f32_bits >> 23) & 0xFF)) - 127; + var f32_mant: u32 = f32_bits & 0x007FFFFF; + + // Handle very small values (subnormals) + if (f32_exp <= @as(i16, -EXP_BIAS)) { + // Below FP8 minimum normal - try to encode as subnormal + const subnormal_shift = @as(i16, -EXP_BIAS) - f32_exp + 1; + if (subnormal_shift > 3 + 23) { + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + var mant = f32_mant >> 23; + if (subnormal_shift > 0) { + const shift_amount = @as(u5, @intCast(subnormal_shift)); + if (shift_amount <= 24) { + mant = (f32_mant | 0x00800000) >> shift_amount; + } + } + return fp8_from_components(sign, 0, @as(u8, @truncate(mant)) & 0x07); + } + + // Convert exp from f32 bias (127) to FP8 bias (7) + var fp8_exp = @as(i16, f32_exp + EXP_BIAS); + + // Clamp exponent + if (fp8_exp >= EXP_MAX) { + return if (sign < 0) FP8_INF_NEG else FP8_INF_POS; + } else if (fp8_exp < 1) { + // Underflow to zero (could be subnormal, simplified) + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Extract mantissa and scale to 3 bits + // f32 mantissa is 24 bits (including implicit 1), FP8 needs 3 bits + // Shift right by 21 bits (24 - 3 = 21) + var mant = f32_mant >> 21; + + // Round-to-nearest with ties to even + const discarded = f32_mant & 0x1FFFFF; // Lower 21 bits + if ((discarded & 0x100000) != 0) { + // Halfway or more + if ((discarded & 0x0FFFFF) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (fp8_exp < @as(i16, EXP_MAX - 1)) { + fp8_exp += 1; + } + } + } + } + + return fp8_from_components(sign, @as(u8, @intCast(fp8_exp)), @as(u8, mant)); +} + +// fp8_decode_f32(fp8: FP8E4M3) -> f32 +// Decode FP8 E4M3 to IEEE 754 single precision +pub fn fp8_decode_f32(fp8: FP8E4M3) f32 { + // Handle zero + if (fp8_is_zero(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (fp8_is_nan(fp8)) { + return std.math.nan(f32); + } + + // Handle Infinity + if (fp8_is_inf(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -std.math.inf(f32) else std.math.inf(f32); + } + + // Extract components + const sign = fp8_extract_sign(fp8); + const exp = fp8_extract_exponent(fp8); + const mant = fp8_extract_mantissa(fp8); + + var f32_value: f32 = undefined; + + // Handle subnormal + if (exp == 0) { + // Subnormal: value = mant * 2^(-6) = mant * 2^(1-bias) + const bias_adjusted: i8 = 1 - @as(i8, EXP_BIAS); + const mant_f32 = @as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR)); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } else { + // Normal: value = (1 + mant/8) * 2^(exp - 7) + const bias_adjusted = @as(i16, exp) - @as(i16, EXP_BIAS); + const mant_f32 = 1.0 + (@as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR))); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } + + return if (sign < 0) -f32_value else f32_value; +} + +// fp8_encode_f64(value: f64) -> FP8E4M3 +// Encode IEEE 754 double precision to FP8 E4M3 +pub fn fp8_encode_f64(value: f64) FP8E4M3 { + return fp8_encode_f32(@as(f32, @floatCast(value))); +} + +// fp8_decode_f64(fp8: FP8E4M3) -> f64 +// Decode FP8 E4M3 to IEEE 754 double precision +pub fn fp8_decode_f64(fp8: FP8E4M3) f64 { + const f32_val = fp8_decode_f32(fp8); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// fp8_add(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Add two FP8 values +pub fn fp8_add(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa + fb); +} + +// fp8_sub(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Subtract two FP8 values +pub fn fp8_sub(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa - fb); +} + +// fp8_mul(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Multiply two FP8 values +pub fn fp8_mul(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa * fb); +} + +// fp8_div(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Divide two FP8 values +pub fn fp8_div(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fb = fp8_decode_f32(b); + if (fb == 0.0) { + const fa = fp8_decode_f32(a); + return if (fa < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + const fa = fp8_decode_f32(a); + return fp8_encode_f32(fa / fb); +} + +// fp8_sqrt(value: FP8E4M3) -> FP8E4M3 +// Square root of FP8 value +pub fn fp8_sqrt(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + if (fv < 0.0) { + return FP8_NAN_POS; + } + return fp8_encode_f32(std.math.sqrt(fv)); +} + +// fp8_rsqrt(value: FP8E4M3) -> FP8E4M3 +// Reciprocal square root (1/sqrt(x)) +pub fn fp8_rsqrt(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + if (fv <= 0.0) { + return FP8_INF_POS; + } + return fp8_encode_f32(1.0 / std.math.sqrt(fv)); +} + +// fp8_abs(value: FP8E4M3) -> FP8E4M3 +// Absolute value of FP8 +pub fn fp8_abs(value: FP8E4M3) FP8E4M3 { + return value & ~SIGN_MASK; +} + +// fp8_neg(value: FP8E4M3) -> FP8E4M3 +// Negate FP8 +pub fn fp8_neg(value: FP8E4M3) FP8E4M3 { + return value ^ SIGN_MASK; +} + +// fp8_is_equal(a: FP8E4M3, b: FP8E4M3) -> bool +// Check if two FP8 values are equal +pub fn fp8_is_equal(a: FP8E4M3, b: FP8E4M3) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + if (fp8_is_zero(a) and fp8_is_zero(b)) { + return true; + } + return a == b; +} + +// fp8_is_greater(a: FP8E4M3, b: FP8E4M3) -> bool +// Check if a > b +pub fn fp8_is_greater(a: FP8E4M3, b: FP8E4M3) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + const sign_a = fp8_extract_sign(a); + const sign_b = fp8_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return fp8_neg(a) > fp8_neg(b); + } + return a > b; +} + +// fp8_max(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Return the larger of two FP8 values +pub fn fp8_max(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + return if (fp8_is_greater(a, b)) a else b; +} + +// fp8_min(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Return the smaller of two FP8 values +pub fn fp8_min(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + return if (fp8_is_greater(a, b)) b else a; +} + +// fp8_clamp(value: FP8E4M3, min: FP8E4M3, max: FP8E4M3) -> FP8E4M3 +// Clamp value between min and max +pub fn fp8_clamp(value: FP8E4M3, min: FP8E4M3, max: FP8E4M3) FP8E4M3 { + return fp8_min(fp8_max(value, min), max); +} + +// fp8_lerp(a: FP8E4M3, b: FP8E4M3, t: f32) -> FP8E4M3 +// Linear interpolation between a and b +pub fn fp8_lerp(a: FP8E4M3, b: FP8E4M3, t: f32) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const result = fa + (fb - fa) * t; + return fp8_encode_f32(result); +} + +// fp8_fma(a: FP8E4M3, b: FP8E4M3, c: FP8E4M3) -> FP8E4M3 +// Fused multiply-add: a * b + c +pub fn fp8_fma(a: FP8E4M3, b: FP8E4M3, c: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const fc = fp8_decode_f32(c); + return fp8_encode_f32(fa * fb + fc); +} + +// fp8_scale(value: FP8E4M3, scale: f32) -> FP8E4M3 +// Scale FP8 value by scalar +pub fn fp8_scale(value: FP8E4M3, scale: f32) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * scale); +} + +// fp8_relu(value: FP8E4M3) -> FP8E4M3 +// ReLU activation: max(0, x) +pub fn fp8_relu(value: FP8E4M3) FP8E4M3 { + return if (fp8_extract_sign(value) < 0) FP8_ZERO_POS else value; +} + +// fp8_sigmoid(value: FP8E4M3) -> FP8E4M3 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn fp8_sigmoid(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// fp8_tanh(value: FP8E4M3) -> FP8E4M3 +// Tanh activation +pub fn fp8_tanh(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(std.math.tanh(fv)); +} + +// fp8_gelu(value: FP8E4M3) -> FP8E4M3 +// GELU activation +pub fn fp8_gelu(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + const sqrt_2_over_pi = 0.7978845608; + const cube_coeff = 0.044715; + const tanh_arg = fv * (1.0 + cube_coeff * fv * fv); + return fp8_encode_f32(0.5 * fv * (1.0 + std.math.tanh(sqrt_2_over_pi * tanh_arg))); +} + +// fp8_swish(value: FP8E4M3) -> FP8E4M3 +// Swish activation: x * sigmoid(x) +pub fn fp8_swish(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * (1.0 / (1.0 + std.math.exp(-fv)))); +} + +// fp8_quantize_to_int4(value: FP8E4M3) -> i8 +// Quantize FP8 to Int4 (range [-8, 7]) +pub fn fp8_quantize_to_int4(value: FP8E4M3) i8 { + const fv = fp8_decode_f32(value); + const scaled = fv * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// fp8_quantize_to_int8(value: FP8E4M3) -> i8 +// Quantize FP8 to Int8 (range [-128, 127]) +pub fn fp8_quantize_to_int8(value: FP8E4M3) i8 { + const fv = fp8_decode_f32(value); + const scaled = fv * 127.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 127) { + result = 127; + } else if (result < -128) { + result = -128; + } + return @as(i8, @intCast(result)); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fp8_extract_sign_positive" { + given value = 0x3C + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "fp8_extract_sign_negative" { + given value = 0xBC + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "fp8_extract_exponent_max" { + given value = 0x78 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == EXP_MAX); +} + +test "fp8_extract_exponent_zero" { + given value = 0x07 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == 0); +} + +test "fp8_extract_mantissa_max" { + given value = 0x07 + try std.testing.expect(mant = fp8_extract_mantissa(value)); + try std.testing.expect(mant == MANT_MASK); +} + +test "fp8_is_zero_positive" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_POS) == true); +} + +test "fp8_is_zero_negative" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_NEG) == true); +} + +test "fp8_is_zero_nonzero" { + try std.testing.expect(fp8_is_zero(0x01) == false); +} + +test "fp8_is_inf_positive" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true); +} + +test "fp8_is_inf_negative" { + try std.testing.expect(fp8_is_inf(FP8_INF_NEG) == true); +} + +test "fp8_is_nan_positive" { + try std.testing.expect(fp8_is_nan(FP8_NAN_POS) == true); +} + +test "fp8_is_nan_negative" { + try std.testing.expect(fp8_is_nan(FP8_NAN_NEG) == true); +} + +test "fp8_is_inf_not_nan" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true and fp8_is_nan(FP8_INF_POS) == false); +} + +test "fp8_is_special_inf" { + try std.testing.expect(fp8_is_special(FP8_INF_POS) == true); +} + +test "fp8_is_special_nan" { + try std.testing.expect(fp8_is_special(FP8_NAN_POS) == true); +} + +test "fp8_is_subnormal_true" { + given value = 0x01 + try std.testing.expect(fp8_is_subnormal(value) == true); +} + +test "fp8_is_subnormal_false_normal" { + given value = 0x08 + try std.testing.expect(fp8_is_subnormal(value) == false); +} + +test "fp8_is_subnormal_false_zero" { + try std.testing.expect(fp8_is_subnormal(FP8_ZERO_POS) == false); +} + +test "fp8_encode_f32_zero" { + given fp = fp8_encode_f32(0.0) + try std.testing.expect(fp == FP8_ZERO_POS); +} + +test "fp8_encode_f32_negative_zero" { + given fp = fp8_encode_f32(-0.0) + try std.testing.expect(fp == FP8_ZERO_NEG); +} + +test "fp8_encode_f32_one" { + given fp = fp8_encode_f32(1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 1.0) < 0.05); +} + +test "fp8_encode_f32_negative_one" { + given fp = fp8_encode_f32(-1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded + 1.0) < 0.05); +} + +test "fp8_encode_f32_two" { + given fp = fp8_encode_f32(2.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 2.0) < 0.1); +} + +test "fp8_encode_f32_half" { + given fp = fp8_encode_f32(0.5) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 0.5) < 0.05); +} + +test "fp8_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.1); +} + +test "fp8_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.1); +} + +test "fp8_encode_f32_nan" { + given fp = fp8_encode_f32(std.math.nan(f32)) + try std.testing.expect(fp8_is_nan(fp) == true); +} + +test "fp8_encode_f32_inf" { + given fp = fp8_encode_f32(std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_encode_f32_neg_inf" { + given fp = fp8_encode_f32(-std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_decode_f32_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_POS) == 0.0); +} + +test "fp8_decode_f32_negative_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_NEG) == -0.0); +} + +test "fp8_decode_f32_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_POS)) == true); +} + +test "fp8_decode_f32_neg_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_NEG)) == true); +} + +test "fp8_decode_f32_nan" { + try std.testing.expect(std.math.isNan(fp8_decode_f32(FP8_NAN_POS)) == true); +} + +test "fp8_add_simple" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(result = fp8_add(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.15); +} + +test "fp8_sub_simple" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(result = fp8_sub(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.15); +} + +test "fp8_mul_simple" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(result = fp8_mul(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 6.0) < 0.3); +} + +test "fp8_div_simple" { + given a = fp8_encode_f32(6.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.15); +} + +test "fp8_div_by_zero_positive" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_POS); +} + +test "fp8_div_by_zero_negative" { + given a = fp8_encode_f32(-5.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_NEG); +} + +test "fp8_sqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.1); +} + +test "fp8_sqrt_negative" { + given value = fp8_encode_f32(-4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(fp8_is_nan(result) == true); +} + +test "fp8_rsqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_rsqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_abs_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(result = fp8_abs(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_abs_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(abs_val = fp8_abs(value)); + try std.testing.expect(fp8_extract_sign(abs_val) == 0); +} + +test "fp8_neg_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) < 0); +} + +test "fp8_neg_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) == 0); +} + +test "fp8_is_equal_true" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(fp8_is_equal(value, value) == true); +} + +test "fp8_is_equal_false" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(fp8_is_equal(a, b) == false); +} + +test "fp8_is_equal_nan" { + try std.testing.expect(fp8_is_equal(FP8_NAN_POS, FP8_NAN_POS) == false); +} + +test "fp8_is_equal_zero" { + try std.testing.expect(fp8_is_equal(FP8_ZERO_POS, FP8_ZERO_NEG) == true); +} + +test "fp8_is_greater_positive" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(fp8_is_greater(a, b) == true); +} + +test "fp8_max_returns_larger" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 4.0); +} + +test "fp8_min_returns_smaller" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 4.0); +} + +test "fp8_lerp_zero" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, a) == true); +} + +test "fp8_lerp_one" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, b) == true); +} + +test "fp8_fma_simple" { + given a = fp8_encode_f32(2.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(c = fp8_encode_f32(1.0)); + try std.testing.expect(result = fp8_fma(a, b, c)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 7.0) < 0.3); +} + +test "fp8_relu_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_relu_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_relu_zero" { + given result = fp8_relu(FP8_ZERO_POS) + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_sigmoid_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.05); +} + +test "fp8_sigmoid_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.95); +} + +test "fp8_sigmoid_negative" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 0.05); +} + +test "fp8_tanh_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded) < 0.05); +} + +test "fp8_tanh_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.95); +} + +test "fp8_tanh_negative" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= -0.95); +} + +test "fp8_gelu_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_gelu(value)); + try std.testing.expect(fp8_extract_sign(result) == 0); +} + +test "fp8_swish_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_swish_positive" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded > 0.0); +} + +test "fp8_quantize_to_int4_max" { + given value = fp8_encode_f32(2.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "fp8_quantize_to_int4_min" { + given value = fp8_encode_f32(-2.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "fp8_quantize_to_int8_max" { + given value = fp8_encode_f32(2.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result == 127); +} + +test "fp8_quantize_to_int8_min" { + given value = fp8_encode_f32(-2.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result == -128); +} + +test "fp8_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 7); + try std.testing.expect(mant = 4); + try std.testing.expect(fp = fp8_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = fp8_extract_sign(fp)); + try std.testing.expect(extracted_exp = fp8_extract_exponent(fp)); + try std.testing.expect(extracted_mant = fp8_extract_mantissa(fp)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fp8_bits_constant + assert BITS == 8 + +invariant fp8_sign_bits_one + assert SIGN_BITS == 1 + +invariant fp8_exp_bits_four + assert EXP_BITS == 4 + +invariant fp8_mant_bits_three + assert MANT_BITS == 3 + +invariant fp8_exp_bias_seven + assert EXP_BIAS == 7 + +invariant fp8_exp_max_all_ones + assert EXP_MAX == 0x0F + +invariant fp8_sign_mask_correct + assert SIGN_MASK == 0x80 + +invariant fp8_exp_mask_correct + assert EXP_MASK == 0x78 + +invariant fp8_mant_mask_correct + assert MANT_MASK == 0x07 + +invariant fp8_masks_sum_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFF + +invariant fp8_zero_pos_no_sign + assert (FP8_ZERO_POS & SIGN_MASK) == 0 + +invariant fp8_zero_neg_has_sign + assert (FP8_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant fp8_inf_pos_max_exp + assert (FP8_INF_POS & EXP_MASK) == EXP_MASK + +invariant fp8_inf_pos_no_mant + assert (FP8_INF_POS & MANT_MASK) == 0 + +invariant fp8_nan_has_mantissa + assert (FP8_NAN_POS & MANT_MASK) != 0 + +invariant fp8_abs_removes_sign + assert fp8_abs(FP8_ZERO_NEG) == FP8_ZERO_POS + +invariant fp8_neg_toggles_sign + assert (fp8_neg(FP8_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant fp8_add_zero_identity + given value = fp8_encode_f32(5.0) + assert fp8_is_equal(fp8_add(value, FP8_ZERO_POS), value) == true + +invariant fp8_sub_zero_identity + given value = fp8_encode_f32(5.0) + assert fp8_is_equal(fp8_sub(value, FP8_ZERO_POS), value) == true + +invariant fp8_mul_one_identity + given value = fp8_encode_f32(5.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_mul(value, one), value) == true or fp8_magnitude(value) > 0.5 + +invariant fp8_div_one_identity + given value = fp8_encode_f32(5.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_div(value, one), value) == true + +invariant fp8_max_ge_both + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(fp8_is_greater(result, a) == true and fp8_is_greater(result, b) == true or fp8_is_equal(result, b) == true); + +invariant fp8_min_le_both + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(fp8_is_greater(a, result) == true or fp8_is_equal(a, result) == true); + +invariant fp8_relu_non_negative + given result = fp8_relu(fp8_encode_f32(5.0)) + try std.testing.expect(fp8_extract_sign(result) == 0); + +invariant fp8_lerp_zero_start + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + assert fp8_is_equal(fp8_lerp(a, b, 0.0), a) == true + +invariant fp8_lerp_one_end + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + assert fp8_is_equal(fp8_lerp(a, b, 1.0), b) == true + +invariant fp8_sqrt_square_close + given value = fp8_encode_f32(4.0) + try std.testing.expect(sqrt_val = fp8_sqrt(value)); + try std.testing.expect(sq_val = fp8_mul(sqrt_val, sqrt_val)); + try std.testing.expect(decoded = fp8_decode_f32(sq_val)); + try std.testing.expect(abs(decoded - 4.0) < 0.5); + +invariant fp8_quantize_to_int4_in_range + given result = fp8_quantize_to_int4(fp8_encode_f32(1.0)) + assert result >= -8 and result <= 7 + +invariant fp8_quantize_to_int8_in_range + given result = fp8_quantize_to_int8(fp8_encode_f32(1.0)) + assert result >= -128 and result <= 127 + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fp8_encode_f32_latency + measure: nanoseconds to fp8_encode_f32(1.5) + target: < 50ns + +bench fp8_decode_f32_latency + measure: nanoseconds to fp8_decode_f32(0x3C) + target: < 50ns + +bench fp8_add_latency + measure: nanoseconds to fp8_add(0x3C, 0x48) + target: < 100ns + +bench fp8_mul_latency + measure: nanoseconds to fp8_mul(0x3C, 0x3C) + target: < 100ns + +bench fp8_sqrt_latency + measure: nanoseconds to fp8_sqrt(0x30) + target: < 100ns + +bench fp8_sigmoid_latency + measure: nanoseconds to fp8_sigmoid(0x30) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/fp8_e5m2.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/fp8_e5m2.t27 new file mode 100644 index 0000000000..e46e52429f --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/fp8_e5m2.t27 @@ -0,0 +1,1212 @@ +// SPDX-License-Identifier: Apache-2.0 +; fp8_e5m2.t27 — FP8 E5M2 8-bit Floating Point +; 8-bit float with 5 exponent, 2 mantissa bits (with implicit leading bit) +; OCP FP8 format optimized for inference and wide dynamic range +; Range: ~-57k to ~57k, precision: ~3-4 significant bits +; φ² + 1/φ² = 3 | TRINITY + +module triformat-fp8_e5m2; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 5; +pub const MANT_BITS : u8 = 2; + +pub const SIGN_SHIFT : u8 = 7; +pub const EXP_SHIFT : u8 = 2; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u8 = 0x80; // 1 << 7 +pub const EXP_MASK : u8 = 0x7C; // 0b01111100 +pub const MANT_MASK : u8 = 0x03; // 0b00000011 + +pub const EXP_MAX : u8 = 31; // All ones in 5 bits +pub const EXP_MIN : u8 = 0; +pub const EXP_BIAS : u8 = 15; // Exponent bias for FP8 E5M2 + +pub const MANT_DIVISOR : u8 = 4; // 2^2 + +// FP8 E5M2 special values +pub const FP8_ZERO_POS : u8 = 0x00; +pub const FP8_ZERO_NEG : u8 = 0x80; +pub const FP8_NAN_POS : u8 = 0x7F; // All exp + mant != 0 +pub const FP8_NAN_NEG : u8 = 0xFF; +pub const FP8_INF_POS : u8 = 0x7C; // All exp + mant = 0 +pub const FP8_INF_NEG : u8 = 0xFC; + +// Subnormal exponent +pub const SUBNORMAL_EXP : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FP8E5M2 = u8; // 8-bit value + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// fp8_extract_sign(fp8: FP8E5M2) -> i8 +// Extract sign bit (bit 7) +// Returns: 0 for positive, -1 for negative +pub fn fp8_extract_sign(fp8: FP8E5M2) i8 { + const bit = (fp8 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// fp8_extract_exponent(fp8: FP8E5M2) -> u8 +// Extract exponent bits (bits 6-2) +// Returns: 0-31 +pub fn fp8_extract_exponent(fp8: FP8E5M2) u8 { + return (fp8 >> EXP_SHIFT) & 0x1F; +} + +// fp8_extract_mantissa(fp8: FP8E5M2) -> u8 +// Extract mantissa bits (bits 1-0) +// Returns: 0-3 +pub fn fp8_extract_mantissa(fp8: FP8E5M2) u8 { + return fp8 & MANT_MASK; +} + +// fp8_from_components(sign: i8, exp: u8, mant: u8) -> FP8E5M2 +// Assemble FP8 from sign, exponent, mantissa +pub fn fp8_from_components(sign: i8, exp: u8, mant: u8) FP8E5M2 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(FP8E5M2, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(FP8E5M2, @intCast(exp & 0x1F)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// fp8_is_zero(fp8: FP8E5M2) -> bool +// Check if FP8 is zero (positive or negative) +pub fn fp8_is_zero(fp8: FP8E5M2) bool { + return fp8 == FP8_ZERO_POS or fp8 == FP8_ZERO_NEG; +} + +// fp8_is_special(fp8: FP8E5M2) -> bool +// Check if FP8 is Inf or NaN (exp == 31) +pub fn fp8_is_special(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX; +} + +// fp8_is_inf(fp8: FP8E5M2) -> bool +// Check if FP8 is infinity (exp == 31, mant == 0) +pub fn fp8_is_inf(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) == 0; +} + +// fp8_is_nan(fp8: FP8E5M2) -> bool +// Check if FP8 is NaN (exp == 31, mant != 0) +pub fn fp8_is_nan(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) != 0; +} + +// fp8_is_subnormal(fp8: FP8E5M2) -> bool +// Check if FP8 is subnormal (exp == 0, mant != 0) +pub fn fp8_is_subnormal(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == 0 and fp8_extract_mantissa(fp8) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// fp8_encode_f32(value: f32) -> FP8E5M2 +// Encode IEEE 754 single precision to FP8 E5M2 +// Round-to-nearest, ties to even +pub fn fp8_encode_f32(value: f32) FP8E5M2 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return if (value < 0.0) FP8_NAN_NEG else FP8_NAN_POS; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i16 = @as(i16, @intCast((f32_bits >> 23) & 0xFF)) - 127; + var f32_mant: u32 = f32_bits & 0x007FFFFF; + + // Handle very small values (subnormals) + if (f32_exp <= @as(i16, -EXP_BIAS)) { + // Below FP8 minimum normal - try to encode as subnormal + const subnormal_shift = @as(i16, -EXP_BIAS) - f32_exp + 1; + if (subnormal_shift > 2 + 23) { + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + var mant = f32_mant >> 23; + if (subnormal_shift > 0) { + const shift_amount = @as(u5, @intCast(subnormal_shift)); + if (shift_amount <= 24) { + mant = (f32_mant | 0x00800000) >> shift_amount; + } + } + return fp8_from_components(sign, 0, @as(u8, @truncate(mant)) & 0x03); + } + + // Convert exp from f32 bias (127) to FP8 bias (15) + var fp8_exp = @as(i16, f32_exp + EXP_BIAS); + + // Clamp exponent + if (fp8_exp >= EXP_MAX) { + return if (sign < 0) FP8_INF_NEG else FP8_INF_POS; + } else if (fp8_exp < 1) { + // Underflow to zero (could be subnormal, simplified) + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Extract mantissa and scale to 2 bits + // f32 mantissa is 24 bits (including implicit 1), FP8 needs 2 bits + // Shift right by 22 bits (24 - 2 = 22) + var mant = f32_mant >> 22; + + // Round-to-nearest with ties to even + const discarded = f32_mant & 0x003FFFFF; // Lower 22 bits + if ((discarded & 0x00200000) != 0) { + // Halfway or more + if ((discarded & 0x001FFFFF) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (fp8_exp < @as(i16, EXP_MAX - 1)) { + fp8_exp += 1; + } + } + } + } + + return fp8_from_components(sign, @as(u8, @intCast(fp8_exp)), @as(u8, mant)); +} + +// fp8_decode_f32(fp8: FP8E5M2) -> f32 +// Decode FP8 E5M2 to IEEE 754 single precision +pub fn fp8_decode_f32(fp8: FP8E5M2) f32 { + // Handle zero + if (fp8_is_zero(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (fp8_is_nan(fp8)) { + return std.math.nan(f32); + } + + // Handle Infinity + if (fp8_is_inf(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -std.math.inf(f32) else std.math.inf(f32); + } + + // Extract components + const sign = fp8_extract_sign(fp8); + const exp = fp8_extract_exponent(fp8); + const mant = fp8_extract_mantissa(fp8); + + var f32_value: f32 = undefined; + + // Handle subnormal + if (exp == 0) { + // Subnormal: value = mant * 2^(-14) = mant * 2^(1-bias) + const bias_adjusted: i8 = 1 - @as(i8, EXP_BIAS); + const mant_f32 = @as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR)); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } else { + // Normal: value = (1 + mant/4) * 2^(exp - 15) + const bias_adjusted = @as(i16, exp) - @as(i16, EXP_BIAS); + const mant_f32 = 1.0 + (@as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR))); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } + + return if (sign < 0) -f32_value else f32_value; +} + +// fp8_encode_f64(value: f64) -> FP8E5M2 +// Encode IEEE 754 double precision to FP8 E5M2 +pub fn fp8_encode_f64(value: f64) FP8E5M2 { + return fp8_encode_f32(@as(f32, @floatCast(value))); +} + +// fp8_decode_f64(fp8: FP8E5M2) -> f64 +// Decode FP8 E5M2 to IEEE 754 double precision +pub fn fp8_decode_f64(fp8: FP8E5M2) f64 { + const f32_val = fp8_decode_f32(fp8); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// fp8_add(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Add two FP8 values +pub fn fp8_add(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa + fb); +} + +// fp8_sub(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Subtract two FP8 values +pub fn fp8_sub(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa - fb); +} + +// fp8_mul(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Multiply two FP8 values +pub fn fp8_mul(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa * fb); +} + +// fp8_div(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Divide two FP8 values +pub fn fp8_div(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fb = fp8_decode_f32(b); + if (fb == 0.0) { + const fa = fp8_decode_f32(a); + return if (fa < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + const fa = fp8_decode_f32(a); + return fp8_encode_f32(fa / fb); +} + +// fp8_sqrt(value: FP8E5M2) -> FP8E5M2 +// Square root of FP8 value +pub fn fp8_sqrt(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + if (fv < 0.0) { + return FP8_NAN_POS; + } + return fp8_encode_f32(std.math.sqrt(fv)); +} + +// fp8_rsqrt(value: FP8E5M2) -> FP8E5M2 +// Reciprocal square root (1/sqrt(x)) +pub fn fp8_rsqrt(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + if (fv <= 0.0) { + return FP8_INF_POS; + } + return fp8_encode_f32(1.0 / std.math.sqrt(fv)); +} + +// fp8_abs(value: FP8E5M2) -> FP8E5M2 +// Absolute value of FP8 +pub fn fp8_abs(value: FP8E5M2) FP8E5M2 { + return value & ~SIGN_MASK; +} + +// fp8_neg(value: FP8E5M2) -> FP8E5M2 +// Negate FP8 +pub fn fp8_neg(value: FP8E5M2) FP8E5M2 { + return value ^ SIGN_MASK; +} + +// fp8_is_equal(a: FP8E5M2, b: FP8E5M2) -> bool +// Check if two FP8 values are equal +pub fn fp8_is_equal(a: FP8E5M2, b: FP8E5M2) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + if (fp8_is_zero(a) and fp8_is_zero(b)) { + return true; + } + return a == b; +} + +// fp8_is_greater(a: FP8E5M2, b: FP8E5M2) -> bool +// Check if a > b +pub fn fp8_is_greater(a: FP8E5M2, b: FP8E5M2) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + const sign_a = fp8_extract_sign(a); + const sign_b = fp8_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return fp8_neg(a) > fp8_neg(b); + } + return a > b; +} + +// fp8_max(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Return the larger of two FP8 values +pub fn fp8_max(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + return if (fp8_is_greater(a, b)) a else b; +} + +// fp8_min(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Return the smaller of two FP8 values +pub fn fp8_min(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + return if (fp8_is_greater(a, b)) b else a; +} + +// fp8_clamp(value: FP8E5M2, min: FP8E5M2, max: FP8E5M2) -> FP8E5M2 +// Clamp value between min and max +pub fn fp8_clamp(value: FP8E5M2, min: FP8E5M2, max: FP8E5M2) FP8E5M2 { + return fp8_min(fp8_max(value, min), max); +} + +// fp8_lerp(a: FP8E5M2, b: FP8E5M2, t: f32) -> FP8E5M2 +// Linear interpolation between a and b +pub fn fp8_lerp(a: FP8E5M2, b: FP8E5M2, t: f32) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const result = fa + (fb - fa) * t; + return fp8_encode_f32(result); +} + +// fp8_fma(a: FP8E5M2, b: FP8E5M2, c: FP8E5M2) -> FP8E5M2 +// Fused multiply-add: a * b + c +pub fn fp8_fma(a: FP8E5M2, b: FP8E5M2, c: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const fc = fp8_decode_f32(c); + return fp8_encode_f32(fa * fb + fc); +} + +// fp8_scale(value: FP8E5M2, scale: f32) -> FP8E5M2 +// Scale FP8 value by scalar +pub fn fp8_scale(value: FP8E5M2, scale: f32) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * scale); +} + +// fp8_relu(value: FP8E5M2) -> FP8E5M2 +// ReLU activation: max(0, x) +pub fn fp8_relu(value: FP8E5M2) FP8E5M2 { + return if (fp8_extract_sign(value) < 0) FP8_ZERO_POS else value; +} + +// fp8_sigmoid(value: FP8E5M2) -> FP8E5M2 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn fp8_sigmoid(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// fp8_tanh(value: FP8E5M2) -> FP8E5M2 +// Tanh activation +pub fn fp8_tanh(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(std.math.tanh(fv)); +} + +// fp8_gelu(value: FP8E5M2) -> FP8E5M2 +// GELU activation +pub fn fp8_gelu(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + const sqrt_2_over_pi = 0.7978845608; + const cube_coeff = 0.044715; + const tanh_arg = fv * (1.0 + cube_coeff * fv * fv); + return fp8_encode_f32(0.5 * fv * (1.0 + std.math.tanh(sqrt_2_over_pi * tanh_arg))); +} + +// fp8_swish(value: FP8E5M2) -> FP8E5M2 +// Swish activation: x * sigmoid(x) +pub fn fp8_swish(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * (1.0 / (1.0 + std.math.exp(-fv)))); +} + +// fp8_quantize_to_int4(value: FP8E5M2) -> i8 +// Quantize FP8 to Int4 (range [-8, 7]) +pub fn fp8_quantize_to_int4(value: FP8E5M2) i8 { + const fv = fp8_decode_f32(value); + // E5M2 has wider range, so scale differently + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// fp8_quantize_to_int8(value: FP8E5M2) -> i8 +// Quantize FP8 to Int8 (range [-128, 127]) +pub fn fp8_quantize_to_int8(value: FP8E5M2) i8 { + const fv = fp8_decode_f32(value); + // E5M2 has wider range, so scale differently + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 127.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 127) { + result = 127; + } else if (result < -128) { + result = -128; + } + return @as(i8, @intCast(result)); +} + +// fp8_from_f32_scaled(value: f32, scale: f32) -> FP8E5M2 +// Encode f32 to FP8 with custom scale factor +// Useful for quantization with specific dynamic range +pub fn fp8_from_f32_scaled(value: f32, scale: f32) FP8E5M2 { + return fp8_encode_f32(value * scale); +} + +// fp8_to_f32_scaled(value: FP8E5M2, scale: f32) -> f32 +// Decode FP8 to f32 with custom scale factor +pub fn fp8_to_f32_scaled(value: FP8E5M2, scale: f32) f32 { + return fp8_decode_f32(value) / scale; +} + +// fp8_dot_product(a: []const FP8E5M2, b: []const FP8E5M2) -> FP8E5M2 +// Dot product of two FP8 arrays (fixed size 8 for example) +// Returns accumulated FP8 value +pub fn fp8_dot_product(a: []const FP8E5M2, b: []const FP8E5M2) FP8E5M2 { + var sum: f32 = 0.0; + for (a, 0..) |val_a, i| { + if (i >= b.len) break; + const fa = fp8_decode_f32(val_a); + const fb = fp8_decode_f32(b[i]); + sum += fa * fb; + } + return fp8_encode_f32(sum); +} + +// fp8_vector_add(a: []const FP8E5M2, b: []const FP8E5M2, result: []FP8E5M2) -> void +// Vector addition: result[i] = a[i] + b[i] +pub fn fp8_vector_add(a: []const FP8E5M2, b: []const FP8E5M2, result: []FP8E5M2) void { + const len = @min(a.len, b.len); + for (0..len) |i| { + result[i] = fp8_add(a[i], b[i]); + } +} + +// fp8_vector_scale(vec: []const FP8E5M2, scale: FP8E5M2, result: []FP8E5M2) -> void +// Vector scale: result[i] = vec[i] * scale +pub fn fp8_vector_scale(vec: []const FP8E5M2, scale: FP8E5M2, result: []FP8E5M2) void { + const scale_f = fp8_decode_f32(scale); + for (vec, 0..) |val, i| { + result[i] = fp8_encode_f32(fp8_decode_f32(val) * scale_f); + } +} + +// fp8_vector_sum(vec: []const FP8E5M2) -> FP8E5M2 +// Sum all elements of a vector +pub fn fp8_vector_sum(vec: []const FP8E5M2) FP8E5M2 { + var sum: f32 = 0.0; + for (vec) |val| { + sum += fp8_decode_f32(val); + } + return fp8_encode_f32(sum); +} + +// fp8_vector_mean(vec: []const FP8E5M2) -> FP8E5M2 +// Mean of all elements of a vector +pub fn fp8_vector_mean(vec: []const FP8E5M2) FP8E5M2 { + if (vec.len == 0) { + return FP8_ZERO_POS; + } + const sum = fp8_vector_sum(vec); + return fp8_div(sum, fp8_from_f32(@as(f32, @floatFromInt(vec.len)))); +} + +// fp8_softmax(vec: []const FP8E5M2, result: []FP8E5M2) -> void +// Softmax activation: result[i] = exp(vec[i]) / sum(exp(vec)) +pub fn fp8_softmax(vec: []const FP8E5M2, result: []FP8E5M2) void { + // Find max for numerical stability + var max_val: f32 = -std.math.inf(f32); + for (vec) |val| { + const fv = fp8_decode_f32(val); + if (fv > max_val) { + max_val = fv; + } + } + + // Compute exp values and sum + var sum_exp: f32 = 0.0; + for (vec, 0..) |val, i| { + const fv = fp8_decode_f32(val); + const exp_val = std.math.exp(fv - max_val); + result[i] = fp8_encode_f32(exp_val); + sum_exp += exp_val; + } + + // Normalize + const inv_sum = 1.0 / sum_exp; + for (result, 0..) |val, i| { + const fv = fp8_decode_f32(val); + result[i] = fp8_encode_f32(fv * inv_sum); + } +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fp8_extract_sign_positive" { + given value = 0x3C + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "fp8_extract_sign_negative" { + given value = 0xBC + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "fp8_extract_exponent_max" { + given value = 0x7C + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == EXP_MAX); +} + +test "fp8_extract_exponent_zero" { + given value = 0x03 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == 0); +} + +test "fp8_extract_mantissa_max" { + given value = 0x03 + try std.testing.expect(mant = fp8_extract_mantissa(value)); + try std.testing.expect(mant == MANT_MASK); +} + +test "fp8_is_zero_positive" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_POS) == true); +} + +test "fp8_is_zero_negative" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_NEG) == true); +} + +test "fp8_is_zero_nonzero" { + try std.testing.expect(fp8_is_zero(0x01) == false); +} + +test "fp8_is_inf_positive" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true); +} + +test "fp8_is_inf_negative" { + try std.testing.expect(fp8_is_inf(FP8_INF_NEG) == true); +} + +test "fp8_is_nan_positive" { + try std.testing.expect(fp8_is_nan(FP8_NAN_POS) == true); +} + +test "fp8_is_nan_negative" { + try std.testing.expect(fp8_is_nan(FP8_NAN_NEG) == true); +} + +test "fp8_is_inf_not_nan" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true and fp8_is_nan(FP8_INF_POS) == false); +} + +test "fp8_is_special_inf" { + try std.testing.expect(fp8_is_special(FP8_INF_POS) == true); +} + +test "fp8_is_special_nan" { + try std.testing.expect(fp8_is_special(FP8_NAN_POS) == true); +} + +test "fp8_is_subnormal_true" { + given value = 0x01 + try std.testing.expect(fp8_is_subnormal(value) == true); +} + +test "fp8_is_subnormal_false_normal" { + given value = 0x04 + try std.testing.expect(fp8_is_subnormal(value) == false); +} + +test "fp8_is_subnormal_false_zero" { + try std.testing.expect(fp8_is_subnormal(FP8_ZERO_POS) == false); +} + +test "fp8_encode_f32_zero" { + given fp = fp8_encode_f32(0.0) + try std.testing.expect(fp == FP8_ZERO_POS); +} + +test "fp8_encode_f32_negative_zero" { + given fp = fp8_encode_f32(-0.0) + try std.testing.expect(fp == FP8_ZERO_NEG); +} + +test "fp8_encode_f32_one" { + given fp = fp8_encode_f32(1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 1.0) < 0.15); +} + +test "fp8_encode_f32_negative_one" { + given fp = fp8_encode_f32(-1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded + 1.0) < 0.15); +} + +test "fp8_encode_f32_ten" { + given fp = fp8_encode_f32(10.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 10.0) < 1.0); +} + +test "fp8_encode_f32_hundred" { + given fp = fp8_encode_f32(100.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 100.0) < 10.0); +} + +test "fp8_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "fp8_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "fp8_encode_f32_nan" { + given fp = fp8_encode_f32(std.math.nan(f32)) + try std.testing.expect(fp8_is_nan(fp) == true); +} + +test "fp8_encode_f32_inf" { + given fp = fp8_encode_f32(std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_encode_f32_neg_inf" { + given fp = fp8_encode_f32(-std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_decode_f32_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_POS) == 0.0); +} + +test "fp8_decode_f32_negative_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_NEG) == -0.0); +} + +test "fp8_decode_f32_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_POS)) == true); +} + +test "fp8_decode_f32_neg_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_NEG)) == true); +} + +test "fp8_decode_f32_nan" { + try std.testing.expect(std.math.isNan(fp8_decode_f32(FP8_NAN_POS)) == true); +} + +test "fp8_add_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(20.0)); + try std.testing.expect(result = fp8_add(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 30.0) < 3.0); +} + +test "fp8_sub_simple" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(30.0)); + try std.testing.expect(result = fp8_sub(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 3.0); +} + +test "fp8_mul_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(result = fp8_mul(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 15.0); +} + +test "fp8_div_simple" { + given a = fp8_encode_f32(100.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "fp8_div_by_zero_positive" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_POS); +} + +test "fp8_div_by_zero_negative" { + given a = fp8_encode_f32(-50.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_NEG); +} + +test "fp8_sqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.3); +} + +test "fp8_sqrt_negative" { + given value = fp8_encode_f32(-4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(fp8_is_nan(result) == true); +} + +test "fp8_rsqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_rsqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_abs_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(result = fp8_abs(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_abs_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(abs_val = fp8_abs(value)); + try std.testing.expect(fp8_extract_sign(abs_val) == 0); +} + +test "fp8_neg_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) < 0); +} + +test "fp8_neg_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) == 0); +} + +test "fp8_is_equal_true" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(fp8_is_equal(value, value) == true); +} + +test "fp8_is_equal_false" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(20.0)); + try std.testing.expect(fp8_is_equal(a, b) == false); +} + +test "fp8_is_equal_nan" { + try std.testing.expect(fp8_is_equal(FP8_NAN_POS, FP8_NAN_POS) == false); +} + +test "fp8_is_equal_zero" { + try std.testing.expect(fp8_is_equal(FP8_ZERO_POS, FP8_ZERO_NEG) == true); +} + +test "fp8_is_greater_positive" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(30.0)); + try std.testing.expect(fp8_is_greater(a, b) == true); +} + +test "fp8_max_returns_larger" { + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 40.0); +} + +test "fp8_min_returns_smaller" { + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 40.0); +} + +test "fp8_lerp_zero" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, a) == true); +} + +test "fp8_lerp_one" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, b) == true); +} + +test "fp8_fma_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(c = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_fma(a, b, c)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 105.0) < 15.0); +} + +test "fp8_scale_up" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(scale = 10.0); + try std.testing.expect(result = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 1.0); +} + +test "fp8_scale_down" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(scale = 0.1); + try std.testing.expect(result = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "fp8_relu_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_relu_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_relu_zero" { + given result = fp8_relu(FP8_ZERO_POS) + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_sigmoid_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_sigmoid_positive_large" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "fp8_sigmoid_negative_large" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 0.1); +} + +test "fp8_tanh_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded) < 0.1); +} + +test "fp8_tanh_positive_large" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "fp8_tanh_negative_large" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= -0.9); +} + +test "fp8_gelu_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_gelu(value)); + try std.testing.expect(fp8_extract_sign(result) == 0); +} + +test "fp8_swish_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_swish_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded > 0.0); +} + +test "fp8_quantize_to_int4_max" { + given value = fp8_encode_f32(100.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "fp8_quantize_to_int4_min" { + given value = fp8_encode_f32(-100.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "fp8_quantize_to_int8_max" { + given value = fp8_encode_f32(200.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result >= 100); +} + +test "fp8_quantize_to_int8_min" { + given value = fp8_encode_f32(-200.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result <= -100); +} + +test "fp8_from_f32_scaled_up" { + given value = 1.0 + try std.testing.expect(scale = 100.0); + try std.testing.expect(result = fp8_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 10.0); +} + +test "fp8_from_f32_scaled_down" { + given value = 100.0 + try std.testing.expect(scale = 0.01); + try std.testing.expect(result = fp8_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "fp8_to_f32_scaled_up" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(scale = 0.01); + try std.testing.expect(result = fp8_to_f32_scaled(value, scale)); + try std.testing.expect(abs(result - 0.01) < 0.005); +} + +test "fp8_dot_product_simple" { + given a = [_]FP8E5M2{fp8_encode_f32(1.0), fp8_encode_f32(2.0), fp8_encode_f32(3.0), fp8_encode_f32(4.0)} + try std.testing.expect(b = [_]FP8E5M2{fp8_encode_f32(2.0), fp8_encode_f32(2.0), fp8_encode_f32(2.0), fp8_encode_f32(2.0)}); + try std.testing.expect(result = fp8_dot_product(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "fp8_vector_sum_simple" { + given vec = [_]FP8E5M2{fp8_encode_f32(1.0), fp8_encode_f32(2.0), fp8_encode_f32(3.0), fp8_encode_f32(4.0)} + try std.testing.expect(result = fp8_vector_sum(vec)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 2.0); +} + +test "fp8_vector_mean_simple" { + given vec = [_]FP8E5M2{fp8_encode_f32(2.0), fp8_encode_f32(4.0), fp8_encode_f32(6.0), fp8_encode_f32(8.0)} + try std.testing.expect(result = fp8_vector_mean(vec)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 5.0) < 1.0); +} + +test "fp8_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 15); + try std.testing.expect(mant = 2); + try std.testing.expect(fp = fp8_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = fp8_extract_sign(fp)); + try std.testing.expect(extracted_exp = fp8_extract_exponent(fp)); + try std.testing.expect(extracted_mant = fp8_extract_mantissa(fp)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fp8_bits_constant + assert BITS == 8 + +invariant fp8_sign_bits_one + assert SIGN_BITS == 1 + +invariant fp8_exp_bits_five + assert EXP_BITS == 5 + +invariant fp8_mant_bits_two + assert MANT_BITS == 2 + +invariant fp8_exp_bias_fifteen + assert EXP_BIAS == 15 + +invariant fp8_exp_max_all_ones + assert EXP_MAX == 0x1F + +invariant fp8_sign_mask_correct + assert SIGN_MASK == 0x80 + +invariant fp8_exp_mask_correct + assert EXP_MASK == 0x7C + +invariant fp8_mant_mask_correct + assert MANT_MASK == 0x03 + +invariant fp8_masks_sum_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFF + +invariant fp8_zero_pos_no_sign + assert (FP8_ZERO_POS & SIGN_MASK) == 0 + +invariant fp8_zero_neg_has_sign + assert (FP8_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant fp8_inf_pos_max_exp + assert (FP8_INF_POS & EXP_MASK) == EXP_MASK + +invariant fp8_inf_pos_no_mant + assert (FP8_INF_POS & MANT_MASK) == 0 + +invariant fp8_nan_has_mantissa + assert (FP8_NAN_POS & MANT_MASK) != 0 + +invariant fp8_abs_removes_sign + assert fp8_abs(FP8_ZERO_NEG) == FP8_ZERO_POS + +invariant fp8_neg_toggles_sign + assert (fp8_neg(FP8_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant fp8_add_zero_identity + given value = fp8_encode_f32(50.0) + assert fp8_is_equal(fp8_add(value, FP8_ZERO_POS), value) == true + +invariant fp8_sub_zero_identity + given value = fp8_encode_f32(50.0) + assert fp8_is_equal(fp8_sub(value, FP8_ZERO_POS), value) == true + +invariant fp8_mul_one_identity + given value = fp8_encode_f32(50.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_mul(value, one), value) == true or fp8_magnitude(value) > 10.0 + +invariant fp8_div_one_identity + given value = fp8_encode_f32(50.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_div(value, one), value) == true + +invariant fp8_max_ge_both + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(fp8_is_greater(result, a) == true and fp8_is_greater(result, b) == true or fp8_is_equal(result, b) == true); + +invariant fp8_min_le_both + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(fp8_is_greater(a, result) == true or fp8_is_equal(a, result) == true); + +invariant fp8_relu_non_negative + given result = fp8_relu(fp8_encode_f32(50.0)) + try std.testing.expect(fp8_extract_sign(result) == 0); + +invariant fp8_lerp_zero_start + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + assert fp8_is_equal(fp8_lerp(a, b, 0.0), a) == true + +invariant fp8_lerp_one_end + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + assert fp8_is_equal(fp8_lerp(a, b, 1.0), b) == true + +invariant fp8_sqrt_square_close + given value = fp8_encode_f32(4.0) + try std.testing.expect(sqrt_val = fp8_sqrt(value)); + try std.testing.expect(sq_val = fp8_mul(sqrt_val, sqrt_val)); + try std.testing.expect(decoded = fp8_decode_f32(sq_val)); + try std.testing.expect(abs(decoded - 4.0) < 0.5); + +invariant fp8_quantize_to_int4_in_range + given result = fp8_quantize_to_int4(fp8_encode_f32(50.0)) + assert result >= -8 and result <= 7 + +invariant fp8_quantize_to_int8_in_range + given result = fp8_quantize_to_int8(fp8_encode_f32(50.0)) + assert result >= -128 and result <= 127 + +invariant fp8_scale_roundtrip + given value = fp8_encode_f32(50.0) + try std.testing.expect(scale = 2.0); + try std.testing.expect(scaled = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_to_f32_scaled(scaled, scale)); + try std.testing.expect(abs(decoded - fp8_decode_f32(value)) < 5.0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fp8_encode_f32_latency + measure: nanoseconds to fp8_encode_f32(10.5) + target: < 50ns + +bench fp8_decode_f32_latency + measure: nanoseconds to fp8_decode_f32(0x3C) + target: < 50ns + +bench fp8_add_latency + measure: nanoseconds to fp8_add(0x3C, 0x48) + target: < 100ns + +bench fp8_mul_latency + measure: nanoseconds to fp8_mul(0x3C, 0x3C) + target: < 100ns + +bench fp8_sqrt_latency + measure: nanoseconds to fp8_sqrt(0x30) + target: < 100ns + +bench fp8_sigmoid_latency + measure: nanoseconds to fp8_sigmoid(0x30) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/gf128.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/gf128.t27 new file mode 100644 index 0000000000..d343873ad2 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/gf128.t27 @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf128.t27 +// GoldenFloat128 - 128-bit φ-structured floating point with extended range +// NUMERIC-STANDARD-001 Agent 5 (P1) +// Paper §6.7: Extended range for high-dynamic-range applications + +module GF128 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // GF128 bit layout: [S(1) | EEE...EEE(48) | MMM...MMM(79)] + // S: 1 bit (sign) + // E: 48 bits (exponent) - phi-optimized + // M: 79 bits (mantissa) - ultra-high precision + // + // φ-ratio: exp/mant = 48/79 ≈ 0.608 (phi_distance = 0.010) + // Good phi approximation for ultra-high precision + + const BITS : u8 = 128; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 48; + const MANT_BITS : u8 = 79; + + // Bias for exponent (2^47 - 1 = 140737488355327) + const EXP_BIAS : i64 = 140737488355327; + + // φ-ratio: exp/mant = 48/79 = 0.608 (good phi approximation) + const PHI_DISTANCE : f64 = 0.009696042799364053; + + // 2. GoldenFloat128 Type + + struct GF128 { + low : u64, // Lower 64 bits (mantissa bits 0-63) + high : u64, // Upper 64 bits [sign(1) exp(48) mant(15)] + } + + // 3. Encoding/Decoding + + // Encode f64 to GF128 + fn encode(value: f64) -> GF128 { + if (value == 0.0) { + return GF128{ .low = 0, .high = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2_128(abs_val) as i64; + const exp_biased = exp_unbiased + EXP_BIAS; + + // Clamp exponent (28 bits max = 268435455) + const exp_clamped = if (exp_biased > 268435455) { 268435455 } else if (exp_biased < 0) { 0 } else { exp_biased }; + + // Extract mantissa (99 bits) + const mant_low = extract_mantissa_128_low(abs_val, exp_unbiased); + const mant_high = extract_mantissa_128_high(abs_val, exp_unbiased); + + return GF128{ + .high = ((sign << 63) | (exp_clamped << 35) | mant_high), + .low = mant_low + }; + } + + // Decode GF128 to f64 + fn decode(gf: GF128) -> f64 { + const sign = (gf.high >> 63) as u8; + const exp_biased = (gf.high >> 35) & 0xFFFFFFF; + + // Zero + if (exp_biased == 0 && gf.high == 0 && gf.low == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS + 1 + } else { + (exp_biased as i64) - EXP_BIAS + }; + + // Mantissa (99 bits, with implicit 1 for normalized) + const mant_low = gf.low; + const mant_high = (gf.high & 0x7FFFFFFFF); + + const mant_normalized = if (exp_biased == 0) { + mant_to_f64_128(mant_low, mant_high) / 633825300114114700748351602688.0 + } else { + 1.0 + mant_to_f64_128(mant_low, mant_high) / 633825300114114700748351602688.0 + }; + + const value = mant_normalized * pow2_128(exp_unbiased as f64); + + if (sign != 0) { + return -value; + } + return value; + } + + // 4. Format Properties + + fn max_value() -> f64 { + // Max normalized: mant ≈ 2.0, exp = 134217727 + const mant_max = 1.9999999999999998; + const exp_max = (1i64 << 28) - 1 - EXP_BIAS; + return mant_max * pow2_128(exp_max as f64); + } + + fn min_positive() -> f64 { + // Min subnormal: mant ≈ 0, exp = -134217726 + const mant_min = 1.0 / 633825300114114700748351602688.0; + const exp_min = -EXP_BIAS + 1; + return mant_min * pow2_128(exp_min as f64); + } + + fn epsilon() -> f64 { + // Smallest representable difference at 1.0 + return 1.0 / 633825300114114700748351602688.0; + } + + fn dynamic_range() -> f64 { + // Ratio of max to min positive + return max_value() / min_positive(); + } + + // 5. Validation + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF128"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + fn is_extended_range() -> bool { + return EXTENDED_RANGE_MODE; + } + + // 6. Use Cases + + // GF128 is optimal for: + // - High dynamic range imaging (HDR) + // - Astronomical calculations (planetary to quantum scales) + // - Extended precision physics simulations + // - Financial modeling requiring extreme range + // - Scientific computing with exceptional precision + + // Memory: 128 bits = 16 bytes (4x FP32, same as FP128) + const MEMORY_RATIO_VS_FP32 : f32 = 128.0 / 32.0; // 4.0 + + // 7. Helper Functions + + fn floor_log2_128(x: f64) -> i64 { + if (x <= 0.0) { return -9223372036854775808; } + let exp : i64 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa_128_low(value: f64, exp: i64) -> u64 { + // Lower 64 bits of mantissa + const normalized = value / pow2_128(exp as f64); + const frac = normalized - 1.0; + const mant_full = (frac * 633825300114114700748351602689.0) as u128; + return (mant_full & 0xFFFFFFFFFFFFFFFF) as u64; + } + + fn extract_mantissa_128_high(value: f64, exp: i64) -> u64 { + // Upper 35 bits of mantissa + const normalized = value / pow2_128(exp as f64); + const frac = normalized - 1.0; + const mant_full = (frac * 633825300114114700748351602689.0) as u128; + return ((mant_full >> 64) & 0x7FFFFFFFF) as u64; + } + + fn mant_to_f64_128(low: u64, high: u64) -> f64 { + const mant_u128 = ((high as u128) << 64) | (low as u128); + return mant_u128 as f64; + } + + fn pow2_128(exp: f64) -> f64 { + if (exp == 0.0) { return 1.0; } + if (exp < 0.0) { return 1.0 / pow2_128(-exp); } + + let result = 1.0; + let base = 2.0; + let e = exp as i64; + + while (e > 0) { + if (e % 2 == 1) { + result = result * base; + } + base = base * base; + e = e / 2; + } + return result; + } + + // TDD-Inside-Spec: Tests and Invariants for GF128 + + test gf128_decode_zero + given gf = GF128{ .low = 0, .high = 0 } + when value = decode(gf) + then value == 0.0 + + test gf128_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf128_encode_positive_value + given original = 1.5 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 1.5) < 1e-10 + + test gf128_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf128_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf128_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf128_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf128_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 4.0) < 0.01 + + test gf128_validate_format_success + given valid = validate_format() + then valid == true + + test gf128_dynamic_range_enormous + given range = dynamic_range() + then range > 1e300 + + test gf128_is_extended_range_true + given extended = is_extended_range() + then extended == true + + test gf128_encode_large_value + given original = 1e150 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded / original - 1.0) < 0.1 + + test gf128_encode_small_value + given original = 1e-150 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded / original - 1.0) < 0.1 + + test gf128_negative_encoding + given original = -2.5 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded < 0.0 and abs(decoded - (-2.5)) < 1e-10 + + test gf128_sign_bit_negative + given value = -1.0 + and encoded = encode(value) + and sign_bit = (encoded.high >> 63) as u8 + then sign_bit == 1 + + test gf128_sign_bit_positive + given value = 1.0 + and encoded = encode(value) + and sign_bit = (encoded.high >> 63) as u8 + then sign_bit == 0 + + test gf128_phi_ratio_low + given ratio = (EXP_BITS as f64) / (MANT_BITS as f64) + then ratio < 0.3 // Extended range mode + + invariant gf128_bits_constant + assert BITS == 128 + + invariant gf128_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf128_exp_bits_is_forty_eight + assert EXP_BITS == 48 + + invariant gf128_mant_bits_is_seventy_nine + assert MANT_BITS == 79 + + invariant gf128_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf128_phi_distance_within_tolerance + assert PHI_DISTANCE < 0.34 + + invariant gf128_exp_bias_positive + assert EXP_BIAS > 0 + + invariant gf128_exp_bias_is_140737488355327 + assert EXP_BIAS == 140737488355327 + + invariant gf128_extended_range_mode_true + assert EXTENDED_RANGE_MODE == true + + invariant gf128_dynamic_range_enormous + assert dynamic_range() > 1e300 + + invariant gf128_epsilon_less_than_min_positive + assert epsilon() <= min_positive() + + invariant gf128_exp_mant_ratio_is_phi_approx + given ratio = (EXP_BITS as f64) / (MANT_BITS as f64) + then abs(ratio - 0.608) < 0.01 + + invariant gf128_high_low_consistency + given gf = GF128{ .low = 0xFFFFFFFFFFFFFFFF, .high = 0x7FFFFFFFF } + then decode(gf) > 0.0 + + bench gf128_encode_latency + measure: nanoseconds to encode(1.0) + target: < 400ns + + bench gf128_decode_latency + measure: nanoseconds to decode(encode(1.5)) + target: < 200ns + + bench gf128_pow2_latency + measure: nanoseconds to pow2_128(10.0) + target: < 150ns + + bench gf128_encode_decode_roundtrip + measure: nanoseconds to encode(1.0) and decode(encode(1.0)) + target: < 600ns + + bench gf128_dynamic_range_calc + measure: nanoseconds to dynamic_range() + target: < 300ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/gf16_to_fp16.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/gf16_to_fp16.t27 new file mode 100644 index 0000000000..132324be5a --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/gf16_to_fp16.t27 @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: Apache-2.0 + +module gf16-to-fp16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const GF16_SIGN_BITS : u8 = 1; +pub const GF16_EXP_BITS : u8 = 6; +pub const GF16_MANT_BITS : u8 = 9; +pub const GF16_BIAS : i8 = 31; + +pub const FP16_SIGN_BITS : u8 = 1; +pub const FP16_EXP_BITS : u8 = 5; +pub const FP16_MANT_BITS : u8 = 10; +pub const FP16_BIAS : i8 = 15; + +pub const MAX_EXP_DIFF : i8 = 6; // Maximum exponent difference without loss +pub const MANTissa_MASK_GF16 : u16 = 0x01FF; +pub const MANTissa_MASK_FP16 : u16 = 0x03FF; + +// ============================================================================ +// Types +// ============================================================================ + +pub const Gf16 = packed struct { + sign : u1, + exp : u6, + mant : u9, +} + +pub const Fp16 = packed struct { + sign : u1, + exp : u5, + mant : u10, +} + +pub const ConversionResult = struct { + value : u16, + overflow : bool, + underflow : bool, + lost_precision : bool, +} + +pub const ConversionConfig = struct { + round_mode : u8, + overflow_mode : u8, + enable_saturation : bool, +} + +// ============================================================================ +// GF16 Functions +// ============================================================================ + +// gf16_from_bits(bits: u16) -> Gf16 +// Extract GF16 from 16-bit representation +pub fn gf16_from_bits(bits: u16) -> Gf16 { + return Gf16 { + .sign = @as(u1, @truncate((bits >> 15) & 0x01)), + .exp = @as(u6, @truncate((bits >> 9) & 0x3F)), + .mant = @as(u9, @truncate(bits & 0x01FF)), + }; +} + +// gf16_to_bits(gf: Gf16) -> u16 +// Convert GF16 to 16-bit representation +pub fn gf16_to_bits(gf: Gf16) -> u16 { + const sign_field : u16 = @as(u16, gf.sign) << 15; + const exp_field : u16 = @as(u16, gf.exp) << 9; + const mant_field : u16 = @as(u16, gf.mant); + return sign_field | exp_field | mant_field; +} + +// gf16_get_exp_biased(gf: Gf16) -> i8 +// Get exponent with bias applied +pub fn gf16_get_exp_biased(gf: Gf16) -> i8 { + return @as(i8, @bitCast(gf.exp)) - GF16_BIAS; +} + +// gf16_is_zero(gf: Gf16) -> bool +// Check if GF16 is zero +pub fn gf16_is_zero(gf: Gf16) -> bool { + return gf.exp == 0 and gf.mant == 0; +} + +// gf16_is_infinity(gf: Gf16) -> bool +// Check if GF16 is infinity +pub fn gf16_is_infinity(gf: Gf16) -> bool { + return gf.exp == 0x3F and gf.mant == 0; +} + +// gf16_is_nan(gf: Gf16) -> bool +// Check if GF16 is NaN +pub fn gf16_is_nan(gf: Gf16) -> bool { + return gf.exp == 0x3F and gf.mant != 0; +} + +// ============================================================================ +// FP16 Functions +// ============================================================================ + +// fp16_from_bits(bits: u16) -> Fp16 +// Extract FP16 from 16-bit representation +pub fn fp16_from_bits(bits: u16) -> Fp16 { + return Fp16 { + .sign = @as(u1, @truncate((bits >> 15) & 0x01)), + .exp = @as(u5, @truncate((bits >> 10) & 0x1F)), + .mant = @as(u10, @truncate(bits & 0x03FF)), + }; +} + +// fp16_to_bits(fp: Fp16) -> u16 +// Convert FP16 to 16-bit representation +pub fn fp16_to_bits(fp: Fp16) -> u16 { + const sign_field : u16 = @as(u16, fp.sign) << 15; + const exp_field : u16 = @as(u16, fp.exp) << 10; + const mant_field : u16 = @as(u16, fp.mant); + return sign_field | exp_field | mant_field; +} + +// fp16_get_exp_biased(fp: Fp16) -> i8 +// Get exponent with bias applied +pub fn fp16_get_exp_biased(fp: Fp16) -> i8 { + if (fp.exp == 0) { + return -14; // Denormal + } else if (fp.exp == 0x1F) { + return 16; // Infinity/NaN + } + return @as(i8, @bitCast(fp.exp)) - FP16_BIAS; +} + +// fp16_is_zero(fp: Fp16) -> bool +// Check if FP16 is zero +pub fn fp16_is_zero(fp: Fp16) -> bool { + return fp.exp == 0 and fp.mant == 0; +} + +// fp16_is_infinity(fp: Fp16) -> bool +// Check if FP16 is infinity +pub fn fp16_is_infinity(fp: Fp16) -> bool { + return fp.exp == 0x1F and fp.mant == 0; +} + +// fp16_is_nan(fp: Fp16) -> bool +// Check if FP16 is NaN +pub fn fp16_is_nan(fp: Fp16) -> bool { + return fp.exp == 0x1F and fp.mant != 0; +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// conversion_config_init() -> ConversionConfig +// Initialize conversion config +pub fn conversion_config_init() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, // Round to nearest + .overflow_mode = 0, // Clamp + .enable_saturation = true, + }; +} + +// conversion_config_no_saturation() -> ConversionConfig +// Create config without saturation +pub fn conversion_config_no_saturation() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, + .overflow_mode = 1, // Overflow to infinity + .enable_saturation = false, + }; +} + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// gf16_to_fp16_raw(gf: Gf16) -> Fp16 +// Convert GF16 to FP16 (raw, no overflow/underflow handling) +pub fn gf16_to_fp16_raw(gf: Gf16) -> Fp16 { + var exp_i8 = gf16_get_exp_biased(gf); + + if (gf16_is_zero(gf)) { + return Fp16{.sign = gf.sign, .exp = 0, .mant = 0}; + } else if (gf16_is_infinity(gf)) { + return Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 0}; + } else if (gf16_is_nan(gf)) { + return Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 1}; // Quiet NaN + } + + // Adjust exponent bias: GF16 bias 31 -> FP16 bias 15 + exp_i8 = exp_i8 + GF16_BIAS - FP16_BIAS; + + var exp : u5 = 0; + var mant : u10 = 0; + var lost_precision = false; + + if (exp_i8 <= -15) { + // Underflow to zero + exp = 0; + mant = 0; + lost_precision = true; + } else if (exp_i8 >= 16) { + // Overflow to infinity + exp = 0x1F; + mant = 0; + } else { + exp = @as(u5, @truncate(@as(u8, @bitCast(exp_i8)) & 0x1F)); + // Extend mantissa from 9 to 10 bits + mant = @as(u10, gf.mant) << 1; + } + + return Fp16{.sign = gf.sign, .exp = exp, .mant = mant}; +} + +// gf16_to_fp16_with_result(gf: Gf16, config: ConversionConfig) -> ConversionResult +// Convert GF16 to FP16 with full result +pub fn gf16_to_fp16_with_result(gf: Gf16, config: ConversionConfig) -> ConversionResult { + var result : ConversionResult = undefined; + + if (gf16_is_zero(gf)) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0, .mant = 0}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } else if (gf16_is_infinity(gf)) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 0}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } else if (gf16_is_nan(gf)) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 1}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } + + var exp_i8 = gf16_get_exp_biased(gf); + exp_i8 = exp_i8 + GF16_BIAS - FP16_BIAS; + + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + + var exp : u5 = 0; + var mant : u10 = 0; + + if (exp_i8 <= -15) { + // Underflow + if (config.enable_saturation) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0, .mant = 0}); + } else { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0, .mant = 1}); // Smallest denormal + } + result.underflow = true; + result.lost_precision = true; + return result; + } else if (exp_i8 >= 16) { + // Overflow + if (config.overflow_mode == 0 or config.enable_saturation) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0x1E, .mant = 0x3FF}); // Max finite + } else { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 0}); // Infinity + } + result.overflow = true; + return result; + } else { + exp = @as(u5, @truncate(@as(u8, @bitCast(exp_i8)) & 0x1F)); + mant = @as(u10, gf.mant) << 1; + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = exp, .mant = mant}); + return result; + } +} + +// gf16_to_fp16(gf: Gf16) -> Fp16 +// Convert GF16 to FP16 +pub fn gf16_to_fp16(gf: Gf16) -> Fp16 { + const result = gf16_to_fp16_with_result(gf, conversion_config_init()); + return fp16_from_bits(result.value); +} + +// gf16_bits_to_fp16_bits(gf_bits: u16) -> u16 +// Convert GF16 bits to FP16 bits +pub fn gf16_bits_to_fp16_bits(gf_bits: u16) -> u16 { + const gf = gf16_from_bits(gf_bits); + const fp = gf16_to_fp16(gf); + return fp16_to_bits(fp); +} + +// gf16_bits_to_fp16_bits_with_result(gf_bits: u16, config: ConversionConfig) -> ConversionResult +// Convert GF16 bits to FP16 bits with full result +pub fn gf16_bits_to_fp16_bits_with_result(gf_bits: u16, config: ConversionConfig) -> ConversionResult { + const gf = gf16_from_bits(gf_bits); + return gf16_to_fp16_with_result(gf, config); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf16_bits_constants" { + try std.testing.expect(GF16_SIGN_BITS == 1); + try std.testing.expect(GF16_EXP_BITS == 6); + try std.testing.expect(GF16_MANT_BITS == 9); + try std.testing.expect(GF16_BIAS == 31); +} + +test "fp16_bits_constants" { + try std.testing.expect(FP16_SIGN_BITS == 1); + try std.testing.expect(FP16_EXP_BITS == 5); + try std.testing.expect(FP16_MANT_BITS == 10); + try std.testing.expect(FP16_BIAS == 15); +} + +test "mantissa_masks" { + try std.testing.expect(MANTissa_MASK_GF16 == 0x01FF); + try std.testing.expect(MANTissa_MASK_FP16 == 0x03FF); +} + +test "gf16_from_bits_to_bits_roundtrip" { + given bits = 0x1234 + try std.testing.expect(gf = gf16_from_bits(bits)); + try std.testing.expect(result = gf16_to_bits(gf)); + try std.testing.expect(result == bits); +} + +test "gf16_from_bits_structure" { + given gf = gf16_from_bits(0x8000) + try std.testing.expect(gf.sign == 1); + try std.testing.expect(gf.exp == 0); + try std.testing.expect(gf.mant == 0); +} + +test "fp16_from_bits_to_bits_roundtrip" { + given bits = 0x3C00 + try std.testing.expect(fp = fp16_from_bits(bits)); + try std.testing.expect(result = fp16_to_bits(fp)); + try std.testing.expect(result == bits); +} + +test "gf16_is_zero_true" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(gf16_is_zero(gf) == true); +} + +test "gf16_is_zero_false" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(gf16_is_zero(gf) == false); +} + +test "gf16_is_infinity_true" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(gf16_is_infinity(gf) == true); +} + +test "gf16_is_infinity_false" { + given gf = Gf16{.sign = 0, .exp = 0x3E, .mant = 0} + try std.testing.expect(gf16_is_infinity(gf) == false); +} + +test "gf16_is_nan_true" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(gf16_is_nan(gf) == true); +} + +test "fp16_is_zero_true" { + given fp = Fp16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp16_is_zero(fp) == true); +} + +test "fp16_is_infinity_true" { + given fp = Fp16{.sign = 0, .exp = 0x1F, .mant = 0} + try std.testing.expect(fp16_is_infinity(fp) == true); +} + +test "conversion_config_init_structure" { + given config = conversion_config_init() + try std.testing.expect(config.round_mode == 0); + try std.testing.expect(config.enable_saturation == true); +} + +test "conversion_config_no_saturation_structure" { + given config = conversion_config_no_saturation() + try std.testing.expect(config.enable_saturation == false); +} + +test "gf16_to_fp16_zero" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.exp == 0); + try std.testing.expect(fp.mant == 0); +} + +test "gf16_to_fp16_infinity" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.exp == 0x1F); + try std.testing.expect(fp.mant == 0); +} + +test "gf16_to_fp16_nan" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.exp == 0x1F); + try std.testing.expect(fp.mant == 1); +} + +test "gf16_to_fp16_preserves_sign" { + given gf_pos = Gf16{.sign = 1, .exp = 1, .mant = 0} + try std.testing.expect(gf_neg = Gf16{.sign = 0, .exp = 1, .mant = 0}); + try std.testing.expect(fp_pos = gf16_to_fp16(gf_pos)); + try std.testing.expect(fp_neg = gf16_to_fp16(gf_neg)); + try std.testing.expect(fp_pos.sign == 1 and fp_neg.sign == 0); +} + +test "gf16_to_fp16_with_result_zero" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.overflow == false); + try std.testing.expect(result.underflow == false); + try std.testing.expect(result.lost_precision == false); +} + +test "gf16_bits_to_fp16_bits_zero" { + given config = conversion_config_init() + try std.testing.expect(gf16_bits_to_fp16_bits(0x0000, config) == 0x0000); +} + +test "gf16_bits_to_fp16_bits_roundtrip" { + given config = conversion_config_init() + // Normal value + try std.testing.expect(gf_bits = 0x2000 // exp=32, mant=0 -> value ~1.0 in GF16); + try std.testing.expect(fp_bits = gf16_bits_to_fp16_bits(gf_bits)); + try std.testing.expect((fp_bits & 0x8000) == (gf_bits & 0x8000) // Sign preserved); +} + +test "gf16_to_fp16_with_result_underflow" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 1} // Very small value + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.underflow == true or result.lost_precision == true); +} + +test "gf16_to_fp16_with_result_overflow" { + given gf = Gf16{.sign = 0, .exp = 62, .mant = 0} // Very large value + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.overflow == true); +} + +test "gf16_to_fp16_with_result_saturation" { + given gf = Gf16{.sign = 0, .exp = 62, .mant = 0} // Very large value + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.value != 0x7C00 // Not infinity, but max finite); +} + +test "gf16_to_fp16_with_result_no_saturation_overflow" { + given gf = Gf16{.sign = 0, .exp = 62, .mant = 0} // Very large value + try std.testing.expect(config = conversion_config_no_saturation()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.value == 0x7C00 // Infinity); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf16_bit_count + assert GF16_SIGN_BITS + GF16_EXP_BITS + GF16_MANT_BITS == 16 + +invariant fp16_bit_count + assert FP16_SIGN_BITS + FP16_EXP_BITS + FP16_MANT_BITS == 16 + +invariant gf16_exp_width + assert GF16_EXP_BITS == 6 + +invariant fp16_exp_width + assert FP16_EXP_BITS == 5 + +invariant gf16_mant_width + assert GF16_MANT_BITS == 9 + +invariant fp16_mant_width + assert FP16_MANT_BITS == 10 + +invariant mantissa_masks_correct + assert MANTissa_MASK_GF16 == (@as(u16, 1) << GF16_MANT_BITS) - 1 + try std.testing.expect(MANTissa_MASK_FP16 == (@as(u16, 1) << FP16_MANT_BITS) - 1); + +invariant gf16_from_bits_roundtrip + given bits = 0x5678 + assert gf16_to_bits(gf16_from_bits(bits)) == bits + +invariant fp16_from_bits_roundtrip + given bits = 0x4567 + assert fp16_to_bits(fp16_from_bits(bits)) == bits + +invariant gf16_zero_exp_zero_mant_zero + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + assert gf16_is_zero(gf) and gf.exp == 0 and gf.mant == 0 + +invariant gf16_infinity_exp_max_mant_zero + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + assert gf16_is_infinity(gf) and not gf16_is_nan(gf) + +invariant fp16_infinity_exp_max_mant_zero + given fp = Fp16{.sign = 0, .exp = 0x1F, .mant = 0} + assert fp16_is_infinity(fp) and not fp16_is_nan(fp) + +invariant conversion_config_init_saturation_enabled + given config = conversion_config_init() + assert config.enable_saturation == true + +invariant conversion_config_no_saturation_disabled + given config = conversion_config_no_saturation() + assert config.enable_saturation == false + +invariant gf16_to_fp16_zero_preserves + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.sign == gf.sign); + +invariant gf16_to_fp16_infinity_preserves_sign + given gf_pos = Gf16{.sign = 1, .exp = 0x3F, .mant = 0} + try std.testing.expect(fp_pos = gf16_to_fp16(gf_pos)); + try std.testing.expect(fp_pos.sign == 1); + +invariant gf16_to_fp16_nan_preserves_sign + given gf = Gf16{.sign = 1, .exp = 0x3F, .mant = 1} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.sign == gf.sign); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf16_from_bits_latency + measure: nanoseconds to gf16_from_bits(0x1234) + target: < 20ns + +bench gf16_to_bits_latency + measure: nanoseconds to gf16_to_bits(gf16_from_bits(0x1234)) + target: < 20ns + +bench fp16_from_bits_latency + measure: nanoseconds to fp16_from_bits(0x3C00) + target: < 20ns + +bench fp16_to_bits_latency + measure: nanoseconds to fp16_to_bits(fp16_from_bits(0x3C00)) + target: < 20ns + +bench gf16_to_fp16_latency + measure: nanoseconds to gf16_to_fp16(gf16_from_bits(0x4000)) + target: < 30ns + +bench gf16_to_fp16_with_result_latency + measure: nanoseconds to gf16_to_fp16_with_result(gf16_from_bits(0x4000), conversion_config_init()) + target: < 40ns + +bench gf16_bits_to_fp16_bits_latency + measure: nanoseconds to gf16_bits_to_fp16_bits(0x4000) + target: < 30ns + +bench conversion_config_init_latency + measure: nanoseconds to conversion_config_init() + target: < 15ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/gf16_to_posit16.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/gf16_to_posit16.t27 new file mode 100644 index 0000000000..6f0189efe3 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/gf16_to_posit16.t27 @@ -0,0 +1,664 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf16_to_posit16.t27 — GF16 to Posit16 Converter +; GoldenFloat16 to Posit type 16 (unum 1.0) format conversion +; φ² + 1/φ² = 3 | TRINITY + +module gf16-to-posit16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const GF16_BITS : u8 = 16; +pub const GF16_SIGN_BIT : u8 = 15; +pub const GF16_EXP_BITS : u8 = 6; +pub const GF16_MANT_BITS : u8 = 9; +pub const GF16_BIAS : i8 = 31; + +pub const POSIT16_BITS : u8 = 16; +pub const POSIT16_ES : u8 = 1; // Exponent scale for posit16 +pub const POSIT16_MAX_EXP : i8 = 7; // Maximum useed exponent for ES=1 + +pub const POSIT16_REGIME_BITS : u8 = 2; +pub const POSIT16_MANT_BITS : u8 = 12; // 14 bits total - 2 regime - ES + +pub const MAX_EXP_DIFF : i8 = 5; + +// ============================================================================ +// Types +// ============================================================================ + +pub const Gf16 = packed struct { + sign : u1, + exp : u6, + mant : u9, +} + +pub const Posit16 = struct { + bits : u16, +} + +pub const PositComponents = struct { + sign : bool, + regime : i8, + exponent : u8, + mantissa : u16, +} + +pub const ConversionResult = struct { + bits : u16, + overflow : bool, + underflow : bool, + inexact : bool, +} + +pub const ConversionConfig = struct { + round_mode : u8, + clamp_overflow : bool, +} + +// ============================================================================ +// GF16 Functions +// ============================================================================ + +// gf16_from_bits(bits: u16) -> Gf16 +// Extract GF16 from 16-bit representation +pub fn gf16_from_bits(bits: u16) -> Gf16 { + return Gf16 { + .sign = @as(u1, @truncate((bits >> 15) & 0x01)), + .exp = @as(u6, @truncate((bits >> 9) & 0x3F)), + .mant = @as(u9, @truncate(bits & 0x01FF)), + }; +} + +// gf16_to_bits(gf: Gf16) -> u16 +// Convert GF16 to 16-bit representation +pub fn gf16_to_bits(gf: Gf16) -> u16 { + const sign_field : u16 = @as(u16, gf.sign) << 15; + const exp_field : u16 = @as(u16, gf.exp) << 9; + const mant_field : u16 = @as(u16, gf.mant); + return sign_field | exp_field | mant_field; +} + +// gf16_is_zero(gf: Gf16) -> bool +// Check if GF16 is zero +pub fn gf16_is_zero(gf: Gf16) -> bool { + return gf.exp == 0 and gf.mant == 0; +} + +// gf16_is_subnormal(gf: Gf16) -> bool +// Check if GF16 is subnormal +pub fn gf16_is_subnormal(gf: Gf16) -> bool { + return gf.exp == 0 and gf.mant != 0; +} + +// gf16_is_normal(gf: Gf16) -> bool +// Check if GF16 is normal +pub fn gf16_is_normal(gf: Gf16) -> bool { + return gf.exp != 0 and gf.exp != 0x3F; +} + +// gf16_is_infinity(gf: Gf16) -> bool +// Check if GF16 is infinity +pub fn gf16_is_infinity(gf: Gf16) -> bool { + return gf.exp == 0x3F and gf.mant == 0; +} + +// gf16_is_nan(gf: Gf16) -> bool +// Check if GF16 is NaN +pub fn gf16_is_nan(gf: Gf16) -> bool { + return gf.exp == 0x3F and gf.mant != 0; +} + +// gf16_get_exp_biased(gf: Gf16) -> i8 +// Get exponent with bias applied +pub fn gf16_get_exp_biased(gf: Gf16) -> i8 { + return @as(i8, @bitCast(gf.exp)) - GF16_BIAS; +} + +// ============================================================================ +// Posit16 Functions +// ============================================================================ + +// posit16_from_bits(bits: u16) -> Posit16 +// Create Posit16 from bits +pub fn posit16_from_bits(bits: u16) -> Posit16 { + return Posit16{.bits = bits}; +} + +// posit16_to_bits(p: Posit16) -> u16 +// Get bits from Posit16 +pub fn posit16_to_bits(p: Posit16) -> u16 { + return p.bits; +} + +// posit16_is_zero(p: Posit16) -> bool +// Check if posit16 is zero +pub fn posit16_is_zero(p: Posit16) -> bool { + return p.bits == 0x0000; +} + +// posit16_is_one(p: Posit16) -> bool +// Check if posit16 is 1.0 +pub fn posit16_is_one(p: Posit16) -> bool { + return p.bits == 0x4000; +} + +// posit16_is_neg_one(p: Posit16) -> bool +// Check if posit16 is -1.0 +pub fn posit16_is_neg_one(p: Posit16) -> bool { + return p.bits == 0xC000; +} + +// posit16_is_naR(p: Posit16) -> bool +// Check if posit16 is NaR (Not a Real) +pub fn posit16_is_naR(p: Posit16) -> bool { + return p.bits == 0x8000; +} + +// posit16_get_sign(p: Posit16) -> bool +// Get sign bit +pub fn posit16_get_sign(p: Posit16) -> bool { + return (p.bits >> 15) == 1; +} + +// posit16_decode(p: Posit16) -> PositComponents +// Decode posit16 into components +pub fn posit16_decode(p: Posit16) -> PositComponents { + var bits = p.bits; + var sign = false; + + if (bits & 0x8000 != 0) { + sign = true; + bits = ~bits + 1; // Two's complement + } + + if (bits == 0) { + return PositComponents{.sign = sign, .regime = 0, .exponent = 0, .mantissa = 0}; + } + + var regime = 0; + var regime_bits : u8 = 0; + + // Find regime + if (bits & 0x4000 != 0) { + // Positive regime: starts with 10 + while (bits & 0x4000 != 0) { + regime += 1; + if (regime > 16) break; + bits <<= 1; + regime_bits += 1; + } + } else { + // Negative regime: starts with 01 + while (bits & 0x4000 == 0) { + regime -= 1; + if (regime < -16) break; + bits <<= 1; + regime_bits += 1; + } + } + + bits <<= 1; // Skip last regime bit + + // Extract exponent + var exponent : u8 = 0; + var exp_bit : u8 = 0; + while (exp_bit < POSIT16_ES and bits != 0) { + if (bits & 0x8000 != 0) { + exponent |= (@as(u8, 1) << (POSIT16_ES - 1 - exp_bit)); + } + bits <<= 1; + exp_bit += 1; + } + + // Extract mantissa + var mantissa : u16 = 0; + var mant_bit : u8 = 0; + while (mant_bit < POSIT16_MANT_BITS and bits != 0) { + if (bits & 0x8000 != 0) { + mantissa |= (@as(u16, 1) << (POSIT16_MANT_BITS - 1 - mant_bit)); + } + bits <<= 1; + mant_bit += 1; + } + + return PositComponents{ + .sign = sign, + .regime = regime, + .exponent = exponent, + .mantissa = mantissa, + }; +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// conversion_config_init() -> ConversionConfig +// Initialize conversion config +pub fn conversion_config_init() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, // Round to nearest + .clamp_overflow = true, + }; +} + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// gf16_to_posit16_raw(gf: Gf16) -> Posit16 +// Convert GF16 to Posit16 (raw) +pub fn gf16_to_posit16_raw(gf: Gf16) -> Posit16 { + if (gf16_is_zero(gf)) { + return posit16_from_bits(@as(u16, @bitCast(gf.sign)) << 15); + } else if (gf16_is_infinity(gf)) { + // Positive infinity maps to NaR + return posit16_from_bits(@as(u16, @bitCast(gf.sign)) << 15); + } else if (gf16_is_nan(gf)) { + return posit16_from_bits(0x8000); // NaR + } + + var exp_i8 = gf16_get_exp_biased(gf); + var sign = gf.sign == 1; + + // Map GF16 exponent to posit regime/exponent + // GF16 bias 31, ES=1, so exponent range is -30 to 30 + // Posit16 regime range: -14 to 14 (2-bit regime) + var regime : i8 = 0; + var exponent : u8 = 0; + + if (exp_i8 > POSIT16_MAX_EXP) { + // Use larger regime + regime = exp_i8 - POSIT16_MAX_EXP; + exponent = 0; + } else if (exp_i8 < -POSIT16_MAX_EXP) { + // Use negative regime + regime = exp_i8 + POSIT16_MAX_EXP; + exponent = 0; + } else { + regime = 0; + exponent = @as(u8, @truncate(@as(u8, @bitCast(exp_i8 + POSIT16_MAX_EXP)))); + } + + // Clamp regime to valid range + if (regime > 14) regime = 14; + if (regime < -14) regime = -14; + + // Build posit bits + var result_bits : u16 = 0; + + // Sign + if (sign) result_bits = 0x8000; + + // Regime encoding + var temp_bits : u16 = if (regime >= 0) 0x4000 else 0x2000; + var regime_count = if (regime >= 0) regime else -regime; + + for (0..@as(usize, @intCast(regime_count))) |_| { + if (regime >= 0) { + result_bits |= temp_bits; + if (temp_bits != 0x8000) temp_bits >>= 1; + } else { + result_bits |= temp_bits; + if (temp_bits != 0) temp_bits >>= 1; + } + } + + // Exponent (ES bits) + result_bits |= @as(u16, exponent) << (14 - POSIT16_ES); + + // Mantissa (9 to 12 bits, pad with zeros) + var mant : u16 = @as(u16, gf.mant); + mant <<= 3; // 9 bits -> 12 bits + result_bits |= mant & 0x0FFF; + + return posit16_from_bits(result_bits); +} + +// gf16_to_posit16_with_result(gf: Gf16, config: ConversionConfig) -> ConversionResult +// Convert GF16 to Posit16 with full result +pub fn gf16_to_posit16_with_result(gf: Gf16, config: ConversionConfig) -> ConversionResult { + var result : ConversionResult = undefined; + + if (gf16_is_zero(gf)) { + result.bits = @as(u16, @bitCast(gf.sign)) << 15; + result.overflow = false; + result.underflow = false; + result.inexact = false; + return result; + } else if (gf16_is_infinity(gf) or gf16_is_nan(gf)) { + result.bits = 0x8000; + result.overflow = false; + result.underflow = false; + result.inexact = true; + return result; + } + + const posit = gf16_to_posit16_raw(gf); + result.bits = posit16_to_bits(posit); + + // Check for overflow/underflow based on exponent + var exp_i8 = gf16_get_exp_biased(gf); + result.overflow = exp_i8 > 30; + result.underflow = exp_i8 < -30; + + // GF16 mantissa is 9 bits, posit16 mantissa is 12 bits (no precision loss) + result.inexact = false; + + if (result.overflow and config.clamp_overflow) { + result.bits = if (gf.sign == 1) 0xC000 else 0x7FFF; // Clamp to max + } + + return result; +} + +// gf16_to_posit16(gf: Gf16) -> Posit16 +// Convert GF16 to Posit16 +pub fn gf16_to_posit16(gf: Gf16) -> Posit16 { + return gf16_to_posit16_raw(gf); +} + +// gf16_bits_to_posit16_bits(gf_bits: u16) -> u16 +// Convert GF16 bits to Posit16 bits +pub fn gf16_bits_to_posit16_bits(gf_bits: u16) -> u16 { + const gf = gf16_from_bits(gf_bits); + const posit = gf16_to_posit16(gf); + return posit16_to_bits(posit); +} + +// gf16_bits_to_posit16_bits_with_result(gf_bits: u16, config: ConversionConfig) -> ConversionResult +// Convert GF16 bits to Posit16 bits with full result +pub fn gf16_bits_to_posit16_bits_with_result(gf_bits: u16, config: ConversionConfig) -> ConversionResult { + const gf = gf16_from_bits(gf_bits); + return gf16_to_posit16_with_result(gf, config); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf16_bits_constants" { + try std.testing.expect(GF16_BITS == 16); + try std.testing.expect(GF16_EXP_BITS == 6); + try std.testing.expect(GF16_MANT_BITS == 9); + try std.testing.expect(GF16_BIAS == 31); +} + +test "posit16_constants" { + try std.testing.expect(POSIT16_BITS == 16); + try std.testing.expect(POSIT16_ES == 1); + try std.testing.expect(POSIT16_MAX_EXP == 7); +} + +test "posit16_regime_mant_bits" { + try std.testing.expect(POSIT16_REGIME_BITS == 2); + try std.testing.expect(POSIT16_MANT_BITS == 12); +} + +test "gf16_from_bits_roundtrip" { + given bits = 0x1234 + try std.testing.expect(gf = gf16_from_bits(bits)); + try std.testing.expect(result = gf16_to_bits(gf)); + try std.testing.expect(result == bits); +} + +test "gf16_is_zero_true" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(gf16_is_zero(gf) == true); +} + +test "gf16_is_subnormal_true" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(gf16_is_subnormal(gf) == true); +} + +test "gf16_is_normal_true" { + given gf = Gf16{.sign = 0, .exp = 32, .mant = 0} + try std.testing.expect(gf16_is_normal(gf) == true); +} + +test "gf16_is_infinity_true" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(gf16_is_infinity(gf) == true); +} + +test "gf16_is_nan_true" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(gf16_is_nan(gf) == true); +} + +test "posit16_from_bits_roundtrip" { + given bits = 0x4000 + try std.testing.expect(p = posit16_from_bits(bits)); + try std.testing.expect(result = posit16_to_bits(p)); + try std.testing.expect(result == bits); +} + +test "posit16_is_zero_true" { + given p = posit16_from_bits(0x0000) + try std.testing.expect(posit16_is_zero(p) == true); +} + +test "posit16_is_one_true" { + given p = posit16_from_bits(0x4000) + try std.testing.expect(posit16_is_one(p) == true); +} + +test "posit16_is_neg_one_true" { + given p = posit16_from_bits(0xC000) + try std.testing.expect(posit16_is_neg_one(p) == true); +} + +test "posit16_is_naR_true" { + given p = posit16_from_bits(0x8000) + try std.testing.expect(posit16_is_naR(p) == true); +} + +test "posit16_get_sign_positive" { + given p = posit16_from_bits(0x4000) + try std.testing.expect(posit16_get_sign(p) == false); +} + +test "posit16_get_sign_negative" { + given p = posit16_from_bits(0xC000) + try std.testing.expect(posit16_get_sign(p) == true); +} + +test "posit16_decode_one" { + given p = posit16_from_bits(0x4000) + try std.testing.expect(comp = posit16_decode(p)); + try std.testing.expect(comp.sign == false); + try std.testing.expect(comp.regime == 0); + try std.testing.expect(comp.exponent == 0); + try std.testing.expect(comp.mantissa == 0); +} + +test "conversion_config_init_structure" { + given config = conversion_config_init() + try std.testing.expect(config.round_mode == 0); + try std.testing.expect(config.clamp_overflow == true); +} + +test "gf16_to_posit16_zero" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_zero(p) == true); +} + +test "gf16_to_posit16_infinity" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_naR(p) == true); +} + +test "gf16_to_posit16_nan" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_naR(p) == true); +} + +test "gf16_to_posit16_preserves_sign" { + given gf_pos = Gf16{.sign = 1, .exp = 32, .mant = 0} + try std.testing.expect(gf_neg = Gf16{.sign = 0, .exp = 32, .mant = 0}); + try std.testing.expect(p_pos = gf16_to_posit16(gf_pos)); + try std.testing.expect(p_neg = gf16_to_posit16(gf_neg)); + try std.testing.expect(posit16_get_sign(p_pos) == true and posit16_get_sign(p_neg) == false); +} + +test "gf16_bits_to_posit16_bits_zero" { + try std.testing.expect(gf16_bits_to_posit16_bits(0x0000) == 0x0000); +} + +test "gf16_bits_to_posit16_bits_sign_preserved" { + given gf_pos = 0x8000 // Negative zero + try std.testing.expect(gf_neg = 0x0000 // Positive zero); + try std.testing.expect((gf16_bits_to_posit16_bits(gf_pos) & 0x8000) != 0); + try std.testing.expect((gf16_bits_to_posit16_bits(gf_neg) & 0x8000) == 0); +} + +test "gf16_to_posit16_with_result_zero" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_posit16_with_result(gf, config)); + try std.testing.expect(result.overflow == false); + try std.testing.expect(result.underflow == false); + try std.testing.expect(result.inexact == false); +} + +test "gf16_to_posit16_with_result_infinity" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_posit16_with_result(gf, config)); + try std.testing.expect(result.inexact == true); + try std.testing.expect(result.bits == 0x8000); +} + +test "gf16_to_posit16_with_result_overflow_clamped" { + given gf = Gf16{.sign = 0, .exp = 0x3E, .mant = 0x1FF} // Large value + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_posit16_with_result(gf, config)); + try std.testing.expect(result.overflow == true); + try std.testing.expect(result.bits != 0x8000 // Clamped, not NaR); +} + +test "gf16_to_posit16_with_result_overflow_not_clamped" { + given gf = Gf16{.sign = 0, .exp = 0x3E, .mant = 0x1FF} + try std.testing.expect(config = ConversionConfig{.round_mode = 0, .clamp_overflow = false}); + try std.testing.expect(result = gf16_to_posit16_with_result(gf, config)); + try std.testing.expect(result.overflow == true); + try std.testing.expect(result.bits == 0x8000 // NaR); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf16_bits_total + assert GF16_BITS == 16 + try std.testing.expect(GF16_SIGN_BIT == GF16_BITS - 1); + +invariant gf16_exp_mant_sum + assert GF16_EXP_BITS + GF16_MANT_BITS == GF16_BITS - 1 + +invariant posit16_bits_total + assert POSIT16_BITS == 16 + +invariant posit16_es_value + assert POSIT16_ES == 1 + +invariant posit16_regime_mant_sum + assert POSIT16_REGIME_BITS + POSIT16_MANT_BITS == POSIT16_BITS - POSIT16_ES - 1 + +invariant gf16_from_bits_roundtrip + given bits = 0x5678 + assert gf16_to_bits(gf16_from_bits(bits)) == bits + +invariant posit16_from_bits_roundtrip + given bits = 0x4000 + assert posit16_to_bits(posit16_from_bits(bits)) == bits + +invariant gf16_zero_exp_zero_mant_zero + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + assert gf16_is_zero(gf) and not gf16_is_subnormal(gf) + +invariant gf16_subnormal_exp_zero_mant_nonzero + given gf = Gf16{.sign = 0, .exp = 0, .mant = 1} + assert gf16_is_subnormal(gf) and not gf16_is_zero(gf) + +invariant gf16_normal_exp_nonzero + given gf = Gf16{.sign = 0, .exp = 1, .mant = 0} + assert gf16_is_normal(gf) and not gf16_is_zero(gf) + +invariant posit16_zero_bits_zero + given p = posit16_from_bits(0x0000) + assert posit16_is_zero(p) and posit16_get_sign(p) == false + +invariant posit16_one_bits_one + given p = posit16_from_bits(0x4000) + assert posit16_is_one(p) and not posit16_is_zero(p) + +invariant posit16_naR_bits_naR + given p = posit16_from_bits(0x8000) + assert posit16_is_naR(p) + +invariant conversion_config_init_clamp_enabled + given config = conversion_config_init() + assert config.clamp_overflow == true + +invariant gf16_to_posit16_zero_preserves + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_zero(p) and not posit16_get_sign(p)); + +invariant gf16_to_posit16_infinity_to_naR + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_naR(p)); + +invariant gf16_to_posit16_nan_to_naR + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_naR(p)); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf16_from_bits_latency + measure: nanoseconds to gf16_from_bits(0x4000) + target: < 20ns + +bench gf16_to_bits_latency + measure: nanoseconds to gf16_to_bits(gf16_from_bits(0x4000)) + target: < 20ns + +bench posit16_from_bits_latency + measure: nanoseconds to posit16_from_bits(0x4000) + target: < 20ns + +bench posit16_to_bits_latency + measure: nanoseconds to posit16_to_bits(posit16_from_bits(0x4000)) + target: < 15ns + +bench posit16_decode_latency + measure: nanoseconds to posit16_decode(posit16_from_bits(0x4000)) + target: < 50ns + +bench gf16_to_posit16_latency + measure: nanoseconds to gf16_to_posit16(gf16_from_bits(0x4000)) + target: < 40ns + +bench gf16_to_posit16_with_result_latency + measure: nanoseconds to gf16_to_posit16_with_result(gf16_from_bits(0x4000), conversion_config_init()) + target: < 50ns + +bench gf16_bits_to_posit16_bits_latency + measure: nanoseconds to gf16_bits_to_posit16_bits(0x4000) + target: < 40ns + +bench conversion_config_init_latency + measure: nanoseconds to conversion_config_init() + target: < 15ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/gf256.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/gf256.t27 new file mode 100644 index 0000000000..e57a0cdca8 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/gf256.t27 @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf256.t27 +// GoldenFloat256 0 256-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 11 (P1) + +module GF256 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // GF256 bit layout: [S(1) E(97) M(158)] + // S: 1 bit (sign) + // E: 97 bits (exponent) + // M: 158 bits (mantissa) + + const BITS : u8 = 256; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 97; + const MANT_BITS : u8 = 158; + + // Bias for exponent (2^(97-1) - 1 = 0x7FFFFFFFFFFFFFFF) + const EXP_BIAS_LO : u64 = 0xFFFFFFFFFFFFFFFF; + const EXP_BIAS_HI : u64 = 0x7F; + + // phi-ratio: exp/mant = 97/158 ≈ 0.614 (phi_distance = 0.004) + const PHI_DISTANCE : f64 = 0.00412087369321891; + + struct GF256 { + // 256-bit value (4 x u64) + raw_0 : u64, // bits 0-63 (mantissa low) + raw_1 : u64, // bits 64-127 (mantissa high + exp low) + raw_2 : u64, // bits 128-191 (exp mid + mant high) + raw_3 : u64, // bits 192-255 (sign + exp high) + } + + // Encode f64 to GF256 + fn encode(value: f64) -> GF256 { + if (value == 0.0) { + return GF256{ raw_0 = 0, raw_1 = 0, raw_2 = 0, raw_3 = 0 }; + } + + const sign = if (value < 0.0) { 1u64 } else { 0u64 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i128; + let exp_biased_lo = ((exp_unbiased + 158886469278522817) & 0xFFFFFFFFFFFFFFFF) as u64; + let exp_biased_hi = ((exp_unbiased >> 64) + 127) as u64; + + // Clamp exponent + if (exp_biased_hi > 0x7F) { + exp_biased_hi = 0x7F; + exp_biased_lo = 0xFFFFFFFFFFFFFFFF; + } + + // Extract mantissa (158 bits, split across 3 u64 words) + const normalized = abs_val / pow(2.0, exp_unbiased as f64); + const frac = normalized - 1.0; + const max_mant = (1u128 << MANT_BITS) - 1; + const mant = (frac * (max_mant + 1) as f64) as u128; + const clamped_mant = if (mant > max_mant) { max_mant } else { mant }; + + // Pack mantissa + const raw_0 = (clamped_mant & 0xFFFFFFFFFFFFFFFF) as u64; + const raw_1 = ((clamped_mant >> 64) & 0x3FFFFFFFFFFFFFFF) as u64; + + // Pack exponent (97 bits: 31 in raw_1, 64 in raw_2, 2 in raw_3) + const exp_m1 = (exp_biased_lo << 33) as u64; + const exp_m2 = (exp_biased_lo >> 31) | ((exp_biased_hi & 0x1) << 33); + + // Pack sign (bit 255) + const raw_3 = (sign << 63) | ((exp_biased_hi >> 1) & 0x7FFFFFFFFFFFFFFF); + + return GF256{ + raw_0 = raw_0, + raw_1 = raw_1 | exp_m1, + raw_2 = exp_m2, + raw_3 = raw_3, + }; + } + + // Decode GF256 to f64 + fn decode(gf: GF256) -> f64 { + const sign = (gf.raw_3 >> 63) as u8; + + // Extract exponent (97 bits) + const exp_lo_part = gf.raw_1 >> 33; + const exp_mid_part = (gf.raw_2 & 0x1FFFFFFFFFFFFFFF) as u64; + const exp_hi_part = (gf.raw_3 & 0x3FFFFFFFFFFFFFFF) as u64; + const exp_biased_lo = exp_lo_part | (exp_mid_part << 31); + const exp_biased_hi = exp_hi_part >> 62; + + const exp_biased_128 = ((exp_biased_hi as u128) << 64) | exp_biased_lo as u128; + + // Extract mantissa (158 bits) + const mant_lo = gf.raw_0; + const mant_mid = gf.raw_1 & 0x1FFFFFFFFFFFFFFF; + const mant = ((mant_mid as u128) << 64) | mant_lo as u128; + + // Zero + if (exp_biased_128 == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_bias_128 = ((EXP_BIAS_HI as u128) << 64) | EXP_BIAS_LO as u128; + const exp_unbiased = if (exp_biased_128 == 0) { + -(exp_bias_128 as i128) + 1 + } else { + (exp_biased_128 as i128) - exp_bias_128 as i128 + }; + + // Mantissa + const max_mant = (1u128 << MANT_BITS) - 1; + const mant_normalized = if (exp_biased_128 == 0) { + (mant as f64) / (max_mant + 1) as f64 + } else { + 1.0 + (mant as f64) / (max_mant + 1) as f64 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f64); + + if (sign != 0) { + return -value; + } + return value; + } + + // Format Properties + fn max_value() -> f64 { + const max_mant = (1u128 << MANT_BITS) - 1; + const mant_max = 1.0 + (max_mant as f64) / (max_mant + 1) as f64; + const exp_max = ((1i128 << EXP_BITS) - 1) as f64 - (EXP_BIAS_HI as f64 * 4294967296.0 + EXP_BIAS_LO as f64); + return mant_max * pow(2.0, exp_max); + } + + fn min_positive() -> f64 { + const max_mant = (1u128 << MANT_BITS) - 1; + const mant_min = 1.0 / (max_mant + 1) as f64; + const exp_min = -((EXP_BIAS_HI as f64 * 4294967296.0 + EXP_BIAS_LO as f64)) + 1.0; + return mant_min * pow(2.0, exp_min); + } + + fn epsilon() -> f64 { + const max_mant = (1u128 << MANT_BITS) - 1; + return 1.0 / (max_mant + 1) as f64; + } + + // Validation + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF256"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // Use Cases + // GF256 is optimal for: + // - Maximum precision computational chemistry + // - String theory simulations + // - Exascale climate modeling + // - Arbitrary-precision arithmetic bridge + + // Memory: 256 bits = 32 bytes (8x FP32) + const MEMORY_RATIO_VS_FP32 : f32 = 8.0; + + // Helper Functions + fn floor_log2(x: f64) -> i128 { + if (x <= 0.0) { return -170141183460469231731687303715884105728; } + let exp : i128 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn pow(base: f64, exp: f64) -> f64 { + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; + } + + const is_integer = exp == floor(exp); + + if (is_integer) { + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + fn ln_approx(x: f64) -> f64 { + if (x <= 0.0) { + return 0.0 / 0.0; + } + if (x == 1.0) { + return 0.0; + } + + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0); + } + + fn exp_approx(x: f64) -> f64 { + if (x == 0.0) { + return 1.0; + } + + let result = 1.0; + let term = 1.0; + let exp_x = x; + + for (i in 1..=15) { + term = term * exp_x / (i as f64); + result = result + term; + } + + return result; + } + + fn floor(x: f64) -> f64 { + let xi = x as i128; + if (x >= 0.0 || x == xi as f64) { + return xi as f64; + } + return (xi - 1) as f64; + } + + // TDD-Inside-Spec: Tests and Invariants for GF256 + + test gf256_decode_zero + given gf = GF256{ raw_0 = 0, raw_1 = 0, raw_2 = 0, raw_3 = 0 } + when value = decode(gf) + then value == 0.0 + + test gf256_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf256_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf256_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf256_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf256_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf256_phi_distance_good + given phi_dist = PHI_DISTANCE + then phi_dist < 0.01 + + test gf256_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 8.0) < 0.01 + + test gf256_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf256_bits_constant + assert BITS == 256 + + invariant gf256_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf256_exp_bits_is_97 + assert EXP_BITS == 97 + + invariant gf256_mant_bits_is_158 + assert MANT_BITS == 158 + + invariant gf256_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf256_phi_distance_below_threshold + assert PHI_DISTANCE < 0.01 + + invariant gf256_exp_mant_ratio_close_to_phi + assert abs((EXP_BITS as f64) / (MANT_BITS as f64) - 0.618) < 0.01 + + invariant gf256_has_max_precision + // GF256 provides extreme precision suitable for quantum computing + assert MANT_BITS > 100 + + bench gf256_encode_latency + measure: nanoseconds to encode(1.0) + target: < 1000ns + + bench gf256_decode_latency + measure: nanoseconds to decode(GF256{raw_0 = 0, raw_1 = 0, raw_2 = 0, raw_3 = 0}) + target: < 800ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/gf32_to_fp32.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/gf32_to_fp32.t27 new file mode 100644 index 0000000000..a33a7bdc1d --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/gf32_to_fp32.t27 @@ -0,0 +1,757 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf32_to_fp32.t27 — GF32 to FP32 Converter +; GoldenFloat32 to IEEE 754 binary32 format conversion +; φ² + 1/φ² = 3 | TRINITY + +module gf32-to-fp32; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const GF32_SIGN_BITS : u8 = 1; +pub const GF32_EXP_BITS : u8 = 12; +pub const GF32_MANT_BITS : u8 = 19; +pub const GF32_BIAS : i16 = 2047; + +pub const FP32_SIGN_BITS : u8 = 1; +pub const FP32_EXP_BITS : u8 = 8; +pub const FP32_MANT_BITS : u8 = 23; +pub const FP32_BIAS : i8 = 127; + +pub const EXP_BIAS_DIFF : i16 = GF32_BIAS - @as(i16, FP32_BIAS); + +pub const MANTissa_MASK_GF32 : u32 = 0x7FFFF; +pub const MANTissa_MASK_FP32 : u32 = 0x7FFFFF; + +// ============================================================================ +// Types +// ============================================================================ + +pub const Gf32 = packed struct { + sign : u1, + exp : u12, + mant : u19, +} + +pub const Fp32 = packed struct { + sign : u1, + exp : u8, + mant : u23, +} + +pub const ConversionResult = struct { + value : u32, + overflow : bool, + underflow : bool, + lost_precision : bool, +} + +pub const ConversionConfig = struct { + round_mode : u8, + overflow_mode : u8, + enable_saturation : bool, +} + +// ============================================================================ +// GF32 Functions +// ============================================================================ + +// gf32_from_bits(bits: u32) -> Gf32 +// Extract GF32 from 32-bit representation +pub fn gf32_from_bits(bits: u32) -> Gf32 { + return Gf32 { + .sign = @as(u1, @truncate((bits >> 31) & 0x01)), + .exp = @as(u12, @truncate((bits >> 19) & 0xFFF)), + .mant = @as(u19, @truncate(bits & 0x7FFFF)), + }; +} + +// gf32_to_bits(gf: Gf32) -> u32 +// Convert GF32 to 32-bit representation +pub fn gf32_to_bits(gf: Gf32) -> u32 { + const sign_field : u32 = @as(u32, gf.sign) << 31; + const exp_field : u32 = @as(u32, gf.exp) << 19; + const mant_field : u32 = @as(u32, gf.mant); + return sign_field | exp_field | mant_field; +} + +// gf32_get_exp_biased(gf: Gf32) -> i16 +// Get exponent with bias applied +pub fn gf32_get_exp_biased(gf: Gf32) -> i16 { + return @as(i16, @bitCast(gf.exp)) - GF32_BIAS; +} + +// gf32_is_zero(gf: Gf32) -> bool +// Check if GF32 is zero +pub fn gf32_is_zero(gf: Gf32) -> bool { + return gf.exp == 0 and gf.mant == 0; +} + +// gf32_is_subnormal(gf: Gf32) -> bool +// Check if GF32 is subnormal +pub fn gf32_is_subnormal(gf: Gf32) -> bool { + return gf.exp == 0 and gf.mant != 0; +} + +// gf32_is_normal(gf: Gf32) -> bool +// Check if GF32 is normal +pub fn gf32_is_normal(gf: Gf32) -> bool { + return gf.exp != 0 and gf.exp != 0xFFF; +} + +// gf32_is_infinity(gf: Gf32) -> bool +// Check if GF32 is infinity +pub fn gf32_is_infinity(gf: Gf32) -> bool { + return gf.exp == 0xFFF and gf.mant == 0; +} + +// gf32_is_nan(gf: Gf32) -> bool +// Check if GF32 is NaN +pub fn gf32_is_nan(gf: Gf32) -> bool { + return gf.exp == 0xFFF and gf.mant != 0; +} + +// gf32_is_qnan(gf: Gf32) -> bool +// Check if GF32 is quiet NaN +pub fn gf32_is_qnan(gf: Gf32) -> bool { + return gf32_is_nan(gf) and (gf.mant >> 18) == 1; +} + +// gf32_is_snan(gf: Gf32) -> bool +// Check if GF32 is signaling NaN +pub fn gf32_is_snan(gf: Gf32) -> bool { + return gf32_is_nan(gf) and (gf.mant >> 18) == 0; +} + +// ============================================================================ +// FP32 Functions +// ============================================================================ + +// fp32_from_bits(bits: u32) -> Fp32 +// Extract FP32 from 32-bit representation +pub fn fp32_from_bits(bits: u32) -> Fp32 { + return Fp32 { + .sign = @as(u1, @truncate((bits >> 31) & 0x01)), + .exp = @as(u8, @truncate((bits >> 23) & 0xFF)), + .mant = @as(u23, @truncate(bits & 0x7FFFFF)), + }; +} + +// fp32_to_bits(fp: Fp32) -> u32 +// Convert FP32 to 32-bit representation +pub fn fp32_to_bits(fp: Fp32) -> u32 { + const sign_field : u32 = @as(u32, fp.sign) << 31; + const exp_field : u32 = @as(u32, fp.exp) << 23; + const mant_field : u32 = @as(u32, fp.mant); + return sign_field | exp_field | mant_field; +} + +// fp32_get_exp_biased(fp: Fp32) -> i8 +// Get exponent with bias applied +pub fn fp32_get_exp_biased(fp: Fp32) -> i8 { + if (fp.exp == 0) { + return -126; // Denormal + } else if (fp.exp == 0xFF) { + return 128; // Infinity/NaN + } + return @as(i8, @bitCast(fp.exp)) - FP32_BIAS; +} + +// fp32_is_zero(fp: Fp32) -> bool +// Check if FP32 is zero +pub fn fp32_is_zero(fp: Fp32) -> bool { + return fp.exp == 0 and fp.mant == 0; +} + +// fp32_is_subnormal(fp: Fp32) -> bool +// Check if FP32 is subnormal +pub fn fp32_is_subnormal(fp: Fp32) -> bool { + return fp.exp == 0 and fp.mant != 0; +} + +// fp32_is_infinity(fp: Fp32) -> bool +// Check if FP32 is infinity +pub fn fp32_is_infinity(fp: Fp32) -> bool { + return fp.exp == 0xFF and fp.mant == 0; +} + +// fp32_is_nan(fp: Fp32) -> bool +// Check if FP32 is NaN +pub fn fp32_is_nan(fp: Fp32) -> bool { + return fp.exp == 0xFF and fp.mant != 0; +} + +// fp32_is_qnan(fp: Fp32) -> bool +// Check if FP32 is quiet NaN +pub fn fp32_is_qnan(fp: Fp32) -> bool { + return fp32_is_nan(fp) and (fp.mant >> 22) == 1; +} + +// fp32_is_snan(fp: Fp32) -> bool +// Check if FP32 is signaling NaN +pub fn fp32_is_snan(fp: Fp32) -> bool { + return fp32_is_nan(fp) and (fp.mant >> 22) == 0; +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// conversion_config_init() -> ConversionConfig +// Initialize conversion config +pub fn conversion_config_init() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, // Round to nearest + .overflow_mode = 0, // Clamp + .enable_saturation = true, + }; +} + +// conversion_config_no_saturation() -> ConversionConfig +// Create config without saturation +pub fn conversion_config_no_saturation() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, + .overflow_mode = 1, // Overflow to infinity + .enable_saturation = false, + }; +} + +// conversion_config_round_up() -> ConversionConfig +// Create config with round-up mode +pub fn conversion_config_round_up() -> ConversionConfig { + return ConversionConfig { + .round_mode = 1, // Round up + .overflow_mode = 0, + .enable_saturation = true, + }; +} + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// gf32_to_fp32_raw(gf: Gf32) -> Fp32 +// Convert GF32 to FP32 (raw, no overflow/underflow handling) +pub fn gf32_to_fp32_raw(gf: Gf32) -> Fp32 { + var exp_i16 = gf32_get_exp_biased(gf); + + if (gf32_is_zero(gf)) { + return Fp32{.sign = gf.sign, .exp = 0, .mant = 0}; + } else if (gf32_is_infinity(gf)) { + return Fp32{.sign = gf.sign, .exp = 0xFF, .mant = 0}; + } else if (gf32_is_nan(gf)) { + var mant_fp : u23 = @as(u23, gf.mant) << (FP32_MANT_BITS - GF32_MANT_BITS); + if (gf32_is_qnan(gf)) { + mant_fp |= (1 << (FP32_MANT_BITS - 1)); + } + return Fp32{.sign = gf.sign, .exp = 0xFF, .mant = mant_fp}; + } else if (gf32_is_subnormal(gf)) { + // Handle GF32 subnormal - convert to smallest normal FP32 + return Fp32{.sign = gf.sign, .exp = 1, .mant = 0}; + } + + // Adjust exponent bias: GF32 bias 2047 -> FP32 bias 127 + exp_i16 = exp_i16 + GF32_BIAS - @as(i16, FP32_BIAS); + + var exp : u8 = 0; + var mant : u23 = 0; + var lost_precision = false; + + if (exp_i16 <= -127) { + // Underflow to zero or denormal + if (exp_i16 == -127 and gf.mant != 0) { + // Smallest denormal + exp = 0; + mant = @as(u23, gf.mant) >> (127 + @as(u8, @bitCast(-exp_i16)) - GF32_MANT_BITS); + } else { + exp = 0; + mant = 0; + } + lost_precision = true; + } else if (exp_i16 >= 128) { + // Overflow to infinity + exp = 0xFF; + mant = 0; + } else { + exp = @as(u8, @truncate(@as(u8, @bitCast(exp_i16)) & 0xFF)); + // Extend mantissa from 19 to 23 bits + mant = @as(u23, gf.mant) << (FP32_MANT_BITS - GF32_MANT_BITS); + } + + return Fp32{.sign = gf.sign, .exp = exp, .mant = mant}; +} + +// gf32_to_fp32_with_result(gf: Gf32, config: ConversionConfig) -> ConversionResult +// Convert GF32 to FP32 with full result +pub fn gf32_to_fp32_with_result(gf: Gf32, config: ConversionConfig) -> ConversionResult { + var result : ConversionResult = undefined; + + if (gf32_is_zero(gf)) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0, .mant = 0}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } else if (gf32_is_infinity(gf)) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0xFF, .mant = 0}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } else if (gf32_is_nan(gf)) { + var mant_fp : u23 = @as(u23, gf.mant) << 4; + if (gf32_is_qnan(gf)) { + mant_fp |= 0x400000; // Quiet NaN bit + } + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0xFF, .mant = mant_fp}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } + + var exp_i16 = gf32_get_exp_biased(gf); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + + if (gf32_is_subnormal(gf)) { + result.underflow = true; + if (config.enable_saturation) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 1, .mant = 0}); + } else { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0, .mant = 1}); // Smallest denormal + } + return result; + } + + exp_i16 = exp_i16 + GF32_BIAS - @as(i16, FP32_BIAS); + + var exp : u8 = 0; + var mant : u23 = 0; + + if (exp_i16 <= -127) { + // Underflow + if (config.enable_saturation) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0, .mant = 0}); + } else { + // Try denormal + var denorm_exp = exp_i16 + 126; + if (denorm_exp >= -22) { + exp = 0; + mant = @as(u23, gf.mant) << (4 - @as(u5, @intCast(-denorm_exp)))); + } else { + exp = 0; + mant = 0; + } + } + result.underflow = true; + result.lost_precision = true; + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = exp, .mant = mant}); + return result; + } else if (exp_i16 >= 128) { + // Overflow + if (config.overflow_mode == 0 or config.enable_saturation) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0xFE, .mant = 0x7FFFFF}); // Max finite + } else { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0xFF, .mant = 0}); // Infinity + } + result.overflow = true; + return result; + } else { + exp = @as(u8, @truncate(@as(u8, @bitCast(exp_i16)) & 0xFF)); + mant = @as(u23, gf.mant) << 4; + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = exp, .mant = mant}); + return result; + } +} + +// gf32_to_fp32(gf: Gf32) -> Fp32 +// Convert GF32 to FP32 +pub fn gf32_to_fp32(gf: Gf32) -> Fp32 { + const result = gf32_to_fp32_with_result(gf, conversion_config_init()); + return fp32_from_bits(result.value); +} + +// gf32_bits_to_fp32_bits(gf_bits: u32) -> u32 +// Convert GF32 bits to FP32 bits +pub fn gf32_bits_to_fp32_bits(gf_bits: u32) -> u32 { + const gf = gf32_from_bits(gf_bits); + const fp = gf32_to_fp32(gf); + return fp32_to_bits(fp); +} + +// gf32_bits_to_fp32_bits_with_result(gf_bits: u32, config: ConversionConfig) -> ConversionResult +// Convert GF32 bits to FP32 bits with full result +pub fn gf32_bits_to_fp32_bits_with_result(gf_bits: u32, config: ConversionConfig) -> ConversionResult { + const gf = gf32_from_bits(gf_bits); + return gf32_to_fp32_with_result(gf, config); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf32_bits_constants" { + try std.testing.expect(GF32_SIGN_BITS == 1); + try std.testing.expect(GF32_EXP_BITS == 12); + try std.testing.expect(GF32_MANT_BITS == 19); + try std.testing.expect(GF32_BIAS == 2047); +} + +test "fp32_bits_constants" { + try std.testing.expect(FP32_SIGN_BITS == 1); + try std.testing.expect(FP32_EXP_BITS == 8); + try std.testing.expect(FP32_MANT_BITS == 23); + try std.testing.expect(FP32_BIAS == 127); +} + +test "exp_bias_diff" { + try std.testing.expect(EXP_BIAS_DIFF == 1920); +} + +test "mantissa_masks" { + try std.testing.expect(MANTissa_MASK_GF32 == 0x7FFFF); + try std.testing.expect(MANTissa_MASK_FP32 == 0x7FFFFF); +} + +test "gf32_from_bits_to_bits_roundtrip" { + given bits = 0x12345678 + try std.testing.expect(gf = gf32_from_bits(bits)); + try std.testing.expect(result = gf32_to_bits(gf)); + try std.testing.expect(result == bits); +} + +test "gf32_from_bits_structure" { + given gf = gf32_from_bits(0x80000000) + try std.testing.expect(gf.sign == 1); + try std.testing.expect(gf.exp == 0); + try std.testing.expect(gf.mant == 0); +} + +test "fp32_from_bits_to_bits_roundtrip" { + given bits = 0x3F800000 + try std.testing.expect(fp = fp32_from_bits(bits)); + try std.testing.expect(result = fp32_to_bits(fp)); + try std.testing.expect(result == bits); +} + +test "gf32_is_zero_true" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(gf32_is_zero(gf) == true); +} + +test "gf32_is_zero_false" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(gf32_is_zero(gf) == false); +} + +test "gf32_is_subnormal_true" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(gf32_is_subnormal(gf) == true); +} + +test "gf32_is_normal_true" { + given gf = Gf32{.sign = 0, .exp = 1, .mant = 0} + try std.testing.expect(gf32_is_normal(gf) == true); +} + +test "gf32_is_infinity_true" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0} + try std.testing.expect(gf32_is_infinity(gf) == true); +} + +test "gf32_is_nan_true" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 1} + try std.testing.expect(gf32_is_nan(gf) == true); +} + +test "gf32_is_qnan_true" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x40000} + try std.testing.expect(gf32_is_qnan(gf) == true); +} + +test "gf32_is_snan_true" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x20000} + try std.testing.expect(gf32_is_snan(gf) == true); +} + +test "fp32_is_zero_true" { + given fp = Fp32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp32_is_zero(fp) == true); +} + +test "fp32_is_infinity_true" { + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 0} + try std.testing.expect(fp32_is_infinity(fp) == true); +} + +test "fp32_is_nan_true" { + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 1} + try std.testing.expect(fp32_is_nan(fp) == true); +} + +test "fp32_is_qnan_true" { + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 0x400000} + try std.testing.expect(fp32_is_qnan(fp) == true); +} + +test "conversion_config_init_structure" { + given config = conversion_config_init() + try std.testing.expect(config.round_mode == 0); + try std.testing.expect(config.enable_saturation == true); +} + +test "conversion_config_no_saturation_structure" { + given config = conversion_config_no_saturation() + try std.testing.expect(config.enable_saturation == false); +} + +test "conversion_config_round_up_structure" { + given config = conversion_config_round_up() + try std.testing.expect(config.round_mode == 1); +} + +test "gf32_to_fp32_zero" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.exp == 0); + try std.testing.expect(fp.mant == 0); +} + +test "gf32_to_fp32_infinity" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.exp == 0xFF); + try std.testing.expect(fp.mant == 0); +} + +test "gf32_to_fp32_nan" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 1} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.exp == 0xFF); + try std.testing.expect(fp.mant != 0); +} + +test "gf32_to_fp32_preserves_sign" { + given gf_pos = Gf32{.sign = 1, .exp = 2048, .mant = 0} + try std.testing.expect(gf_neg = Gf32{.sign = 0, .exp = 2048, .mant = 0}); + try std.testing.expect(fp_pos = gf32_to_fp32(gf_pos)); + try std.testing.expect(fp_neg = gf32_to_fp32(gf_neg)); + try std.testing.expect(fp_pos.sign == 1 and fp_neg.sign == 0); +} + +test "gf32_to_fp32_with_result_zero" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.overflow == false); + try std.testing.expect(result.underflow == false); + try std.testing.expect(result.lost_precision == false); +} + +test "gf32_bits_to_fp32_bits_zero" { + given config = conversion_config_init() + try std.testing.expect(gf32_bits_to_fp32_bits(0x00000000, config) == 0x00000000); +} + +test "gf32_to_fp32_with_result_subnormal_underflow" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.underflow == true); +} + +test "gf32_to_fp32_with_result_overflow" { + given gf = Gf32{.sign = 0, .exp = 4094, .mant = 0} // Very large + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.overflow == true); +} + +test "gf32_to_fp32_with_result_saturation" { + given gf = Gf32{.sign = 0, .exp = 4094, .mant = 0} // Very large + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.value != 0x7F800000 // Not infinity, but max finite); +} + +test "gf32_to_fp32_with_result_no_saturation_overflow" { + given gf = Gf32{.sign = 0, .exp = 4094, .mant = 0} // Very large + try std.testing.expect(config = conversion_config_no_saturation()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.value == 0x7F800000 // Infinity); +} + +test "gf32_to_fp32_nan_qnan_preserves" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x40000} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect((result.value & 0x400000) != 0 // Quiet NaN bit set); +} + +test "gf32_to_fp32_nan_snan_preserves" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x20000} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect((result.value & 0x400000) == 0 // Quiet NaN bit not set); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf32_bit_count + assert GF32_SIGN_BITS + GF32_EXP_BITS + GF32_MANT_BITS == 32 + +invariant fp32_bit_count + assert FP32_SIGN_BITS + FP32_EXP_BITS + FP32_MANT_BITS == 32 + +invariant gf32_exp_width + assert GF32_EXP_BITS == 12 + +invariant fp32_exp_width + assert FP32_EXP_BITS == 8 + +invariant gf32_mant_width + assert GF32_MANT_BITS == 19 + +invariant fp32_mant_width + assert FP32_MANT_BITS == 23 + +invariant mantissa_masks_correct + assert MANTissa_MASK_GF32 == (@as(u32, 1) << GF32_MANT_BITS) - 1 + try std.testing.expect(MANTissa_MASK_FP32 == (@as(u32, 1) << FP32_MANT_BITS) - 1); + +invariant exp_bias_diff_positive + assert EXP_BIAS_DIFF > 0 + +invariant gf32_from_bits_roundtrip + given bits = 0x12345678 + assert gf32_to_bits(gf32_from_bits(bits)) == bits + +invariant fp32_from_bits_roundtrip + given bits = 0x3F800000 + assert fp32_to_bits(fp32_from_bits(bits)) == bits + +invariant gf32_zero_exp_zero_mant_zero + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + assert gf32_is_zero(gf) and not gf32_is_subnormal(gf) + +invariant gf32_subnormal_exp_zero_mant_nonzero + given gf = Gf32{.sign = 0, .exp = 0, .mant = 1} + assert gf32_is_subnormal(gf) and not gf32_is_zero(gf) + +invariant gf32_normal_exp_nonzero + given gf = Gf32{.sign = 0, .exp = 1, .mant = 0} + assert gf32_is_normal(gf) and not gf32_is_zero(gf) + +invariant gf32_infinity_exp_max_mant_zero + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0} + assert gf32_is_infinity(gf) and not gf32_is_nan(gf) + +invariant gf32_qnan_top_mant_bit + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x40000} + assert gf32_is_nan(gf) and gf32_is_qnan(gf) + +invariant gf32_snan_top_mant_bit_clear + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x20000} + assert gf32_is_nan(gf) and gf32_is_snan(gf) + +invariant fp32_infinity_exp_max_mant_zero + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 0} + assert fp32_is_infinity(fp) and not fp32_is_nan(fp) + +invariant fp32_qnan_top_mant_bit + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 0x400000} + assert fp32_is_nan(fp) and fp32_is_qnan(fp) + +invariant conversion_config_init_saturation_enabled + given config = conversion_config_init() + assert config.enable_saturation == true + +invariant conversion_config_no_saturation_disabled + given config = conversion_config_no_saturation() + assert config.enable_saturation == false + +invariant conversion_config_round_up_mode_one + given config = conversion_config_round_up() + assert config.round_mode == 1 + +invariant gf32_to_fp32_zero_preserves + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.sign == gf.sign); + +invariant gf32_to_fp32_infinity_preserves_sign + given gf_pos = Gf32{.sign = 1, .exp = 0xFFF, .mant = 0} + try std.testing.expect(fp_pos = gf32_to_fp32(gf_pos)); + try std.testing.expect(fp_pos.sign == 1); + +invariant gf32_to_fp32_nan_preserves_sign + given gf = Gf32{.sign = 1, .exp = 0xFFF, .mant = 1} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.sign == 1); + +invariant gf32_to_fp32_qnan_preserves_qnan + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x40000} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect((result.value & 0x400000) != 0); + +invariant gf32_to_fp32_snan_preserves_snan + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x20000} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect((result.value & 0x400000) == 0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf32_from_bits_latency + measure: nanoseconds to gf32_from_bits(0x3F800000) + target: < 20ns + +bench gf32_to_bits_latency + measure: nanoseconds to gf32_to_bits(gf32_from_bits(0x3F800000)) + target: < 20ns + +bench fp32_from_bits_latency + measure: nanoseconds to fp32_from_bits(0x3F800000) + target: < 20ns + +bench fp32_to_bits_latency + measure: nanoseconds to fp32_to_bits(fp32_from_bits(0x3F800000)) + target: < 20ns + +bench gf32_to_fp32_latency + measure: nanoseconds to gf32_to_fp32(gf32_from_bits(0x40000000)) + target: < 30ns + +bench gf32_to_fp32_with_result_latency + measure: nanoseconds to gf32_to_fp32_with_result(gf32_from_bits(0x40000000), conversion_config_init()) + target: < 40ns + +bench gf32_bits_to_fp32_bits_latency + measure: nanoseconds to gf32_bits_to_fp32_bits(0x40000000) + target: < 30ns + +bench conversion_config_init_latency + measure: nanoseconds to conversion_config_init() + target: < 15ns + +bench gf32_is_nan_latency + measure: nanoseconds to gf32_is_nan(gf32_from_bits(0x7FC00000)) + target: < 15ns + +bench gf32_is_qnan_latency + measure: nanoseconds to gf32_is_qnan(gf32_from_bits(0x7FC00000)) + target: < 15ns + +bench fp32_is_nan_latency + measure: nanoseconds to fp32_is_nan(fp32_from_bits(0x7FC00000)) + target: < 15ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/gf64.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/gf64.t27 new file mode 100644 index 0000000000..b472cef864 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/gf64.t27 @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf64.t27 +// GoldenFloat64 - 64-bit φ-structured floating point +// NUMERIC-STANDARD-001 Agent 4 (P1) + +module GF64 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // GF64 bit layout: [S(1) | EEE...EEE(24) | MMM...MMM(39)] + // S: 1 bit (sign) + // E: 24 bits (exponent) + // M: 39 bits (mantissa) + // + // φ-ratio: exp/mant = 24/39 ≈ 0.615 (phi_distance = 0.003) + // This is the BEST phi approximation in the entire GF family! + + const BITS : u8 = 64; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 24; + const MANT_BITS : u8 = 39; + + // Bias for exponent (2^23 - 1 = 8388607) + const EXP_BIAS : i32 = 8388607; + + // φ-ratio: exp/mant = 24/39 = 0.615 (phi_distance = 0.003) + const PHI_DISTANCE : f64 = 0.0028268468254629; + + // 2. GoldenFloat64 Type + + struct GF64 { + raw : u64, // 64-bit raw value + } + + // 3. Encoding/Decoding + + // Encode f32 to GF64 + fn encode(value: f32) -> GF64 { + if (value == 0.0) { + return GF64{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) using f64 for precision + const exp_unbiased = floor_log2_f64(abs_val as f64) as i32; + const exp_biased = (exp_unbiased + EXP_BIAS) as u64; + + // Clamp exponent (18 bits max = 262143) + const exp_clamped = if (exp_biased > 16777215) { 16777215 } else { exp_biased }; + + // Extract mantissa (45 bits) + const mant = extract_mantissa_64(abs_val as f64, exp_unbiased, MANT_BITS); + + return GF64{ + raw = (sign << 63) | (exp_clamped << MANT_BITS) | mant + }; + } + + // Decode GF64 to f32 + fn decode(gf: GF64) -> f64 { + const sign = (gf.raw >> 63) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0xFFFFFF) as u64; + + // Zero + if (exp_biased == 0 && (gf.raw & 0x7FFFFFFFFFFFFF) == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS + 1 + } else { + (exp_biased as i32) - EXP_BIAS + }; + + // Mantissa (with implicit 1 for normalized) + const mant_normalized = if (exp_biased == 0) { + (mant_u64_to_f64(gf.raw & 0x7FFFFFFFFFFFFF)) / 549755813888.0 + } else { + 1.0 + (mant_u64_to_f64(gf.raw & 0x7FFFFFFFFFFFFF)) / 549755813888.0 + }; + + const value = mant_normalized * pow2_64(exp_unbiased as f64); + + if (sign != 0) { + return -value; + } + return value; + } + + // 4. Format Properties + + fn max_value() -> f64 { + // Max normalized: mant ≈ 2.0, exp = 131071 + const mant_max = 1.9999999999999998; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow2_64(exp_max as f64); + } + + fn min_positive() -> f64 { + // Min subnormal: mant ≈ 0, exp = -131070 + const mant_min = 1.0 / 35184372088832.0; + const exp_min = -EXP_BIAS + 1; + return mant_min * pow2_64(exp_min as f64); + } + + fn epsilon() -> f64 { + // Smallest representable difference at 1.0 + return 1.0 / 35184372088832.0; + } + + // 5. Validation + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF64"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 6. Use Cases + + // GF64 is optimal for: + // - High-precision scientific computing + // - Financial calculations requiring accuracy + // - Extended range physics simulations + // - Double-precision alternatives with φ-optimization + + // Memory: 64 bits = 8 bytes (same as FP64, better range) + const MEMORY_RATIO_VS_FP32 : f32 = 64.0 / 32.0; // 2.0 + + // 7. Helper Functions + + fn floor_log2_f64(x: f64) -> i32 { + if (x <= 0.0) { return -2147483648; } + let exp : i32 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa_64(value: f64, exp: i32, mant_bits: u8) -> u64 { + const normalized = value / pow2_64(exp as f64); + const frac = normalized - 1.0; + const max_mant = (1 << mant_bits) - 1; + return (frac * (max_mant as f64 + 1.0)) as u64; + } + + fn mant_u64_to_f64(m: u64) -> f64 { + return m as f64; + } + + fn pow2_64(exp: f64) -> f64 { + if (exp == 0.0) { return 1.0; } + if (exp < 0.0) { return 1.0 / pow2_64(-exp); } + + let result = 1.0; + let base = 2.0; + let e = exp as i32; + + while (e > 0) { + if (e % 2 == 1) { + result = result * base; + } + base = base * base; + e = e / 2; + } + return result; + } + + // TDD-Inside-Spec: Tests and Invariants for GF64 + + test gf64_decode_zero + given gf = GF64{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf64_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf64_encode_positive_value + given original = 1.5 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 1.5) < 1e-6 + + test gf64_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf64_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf64_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf64_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf64_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 2.0) < 0.01 + + test gf64_validate_format_success + given valid = validate_format() + then valid == true + + test gf64_encode_large_value + given original = 1000000.0 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded / original - 1.0) < 0.01 + + test gf64_encode_small_value + given original = 0.000001 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded / original - 1.0) < 0.01 + + test gf64_negative_encoding + given original = -2.5 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded < 0.0 and abs(decoded - (-2.5)) < 1e-6 + + test gf64_sign_bit_negative + given value = -1.0 + and encoded = encode(value) + and sign_bit = (encoded.raw >> 63) as u8 + then sign_bit == 1 + + test gf64_sign_bit_positive + given value = 1.0 + and encoded = encode(value) + and sign_bit = (encoded.raw >> 63) as u8 + then sign_bit == 0 + + invariant gf64_bits_constant + assert BITS == 64 + + invariant gf64_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf64_exp_bits_is_24 + assert EXP_BITS == 24 + + invariant gf64_mant_bits_is_39 + assert MANT_BITS == 39 + + invariant gf64_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf64_phi_distance_best_in_family + assert PHI_DISTANCE < 0.005 + + invariant gf64_exp_bias_positive + assert EXP_BIAS > 0 + + invariant gf64_exp_bias_is_8388607 + assert EXP_BIAS == 8388607 + + invariant gf64_encode_preserves_sign_positive + given val = 1.5 + and enc = encode(val) + and sign = (enc.raw >> 63) as u8 + then sign == 0 + + invariant gf64_encode_preserves_sign_negative + given val = -1.5 + and enc = encode(val) + and sign = (enc.raw >> 63) as u8 + then sign == 1 + + invariant gf64_decode_zero_zero + assert decode(GF64{raw = 0}) == 0.0 + + invariant gf64_epsilon_less_than_min_positive + assert epsilon() <= min_positive() + + invariant gf64_exp_mant_ratio_is_0_4 + given ratio = (EXP_BITS as f64) / (MANT_BITS as f64) + then abs(ratio - 0.4) < 0.01 + + bench gf64_encode_latency + measure: nanoseconds to encode(1.0) + target: < 200ns + + bench gf64_decode_latency + measure: nanoseconds to decode(GF64{raw = encode(1.5).raw}) + target: < 100ns + + bench gf64_pow2_latency + measure: nanoseconds to pow2_64(10.0) + target: < 100ns + + bench gf64_encode_decode_roundtrip + measure: nanoseconds to encode(1.0) and decode(GF64{raw = encode(1.0).raw}) + target: < 300ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/holo_mux_x4.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/holo_mux_x4.t27 new file mode 100644 index 0000000000..908d2f9877 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/holo_mux_x4.t27 @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: Apache-2.0 +; holo_mux_x4.t27 — Sacred Opcode 0xE6: Holographic 4x Multiplexer +; Hardware multiplexer for holographic data paths with 4-way select +; φ² + 1/φ² = 3 | TRINITY + +module sacred-holo_mux_x4; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_HOLO_MUX_X4 : u8 = 0xE6; + +pub const NUM_INPUTS : u8 = 4; +pub const DATA_WIDTH : u8 = 16; +pub const SELECT_BITS : u8 = 2; + +pub const HOLO_LAYER_NORTH : u8 = 0; +pub const HOLO_LAYER_EAST : u8 = 1; +pub const HOLO_LAYER_SOUTH : u8 = 2; +pub const HOLO_LAYER_WEST : u8 = 3; + +pub const HOLO_PHASE_0 : u8 = 0; +pub const HOLO_PHASE_90 : u8 = 1; +pub const HOLO_PHASE_180 : u8 = 2; +pub const HOLO_PHASE_270 : u8 = 3; + +pub const HOLO_VALID : u8 = 1; +pub const HOLO_INVALID : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const HoloInput = struct { + data : u16, + layer : u8, + phase : u8, + valid : bool, + phi_score : u8, // φ-distance score (0-255, lower is better) +} + +pub const HoloMuxConfig = struct { + phi_optimized : bool, + priority_select : bool, + layer_frozen : bool, // LAYER-FROZEN gate + default_input : u8, +} + +pub const HoloOutput = struct { + data : u16, + selected_layer : u8, + selected_phase : u8, + valid : bool, + frozen : bool, // Whether output is from LAYER-FROZEN input +} + +pub const HoloMuxState = struct { + current_select : u8, + frozen_select : u8, + cycle_count : u32, +} + +// ============================================================================ +// Holo Input Functions +// ============================================================================ + +// holo_input_valid(input: HoloInput) -> bool +// Check if holo input is valid +pub fn holo_input_valid(input: HoloInput) bool { + return input.valid and input.layer < NUM_INPUTS; +} + +// holo_input_score(input: HoloInput, config: HoloMuxConfig) -> u16 +// Calculate selection score for input +pub fn holo_input_score(input: HoloInput, config: HoloMuxConfig) u16 { + if (not holo_input_valid(input)) { + return 0; + } + + var score : u16 = @as(u16, @intCast(input.data)); + + if (config.phi_optimized) { + // Prefer lower phi score + const phi_bonus = 255 - input.phi_score; + score += @as(u16, phi_bonus) * 256; + } + + if (config.priority_select) { + // Layer priority affects score + const priority_bonus = (@as(u8, 3) - input.layer) * 1000; + score += priority_bonus; + } + + return score; +} + +// ============================================================================ +// Holo Mux Functions +// ============================================================================ + +// holo_mux_select(inputs: [NUM_INPUTS]HoloInput, select: u8, config: HoloMuxConfig) -> HoloOutput +// Select holo input based on select bits +pub fn holo_mux_select(inputs: [NUM_INPUTS]HoloInput, select: u8, config: HoloMuxConfig) HoloOutput { + const selected_idx = select & 0x03; + + // LAYER-FROZEN: check if frozen mode is active + if (config.layer_frozen) { + const frozen_input = inputs[config.default_input]; + return HoloOutput { + .data = frozen_input.data, + .selected_layer = frozen_input.layer, + .selected_phase = frozen_input.phase, + .valid = holo_input_valid(frozen_input), + .frozen = true, + }; + } + + const selected = inputs[@as(u8, @intCast(selected_idx))]; + return HoloOutput { + .data = selected.data, + .selected_layer = selected.layer, + .selected_phase = selected.phase, + .valid = holo_input_valid(selected), + .frozen = false, + }; +} + +// holo_mux_best(inputs: [NUM_INPUTS]HoloInput, config: HoloMuxConfig) -> HoloOutput +// Select best holo input based on score +pub fn holo_mux_best(inputs: [NUM_INPUTS]HoloInput, config: HoloMuxConfig) -> HoloOutput { + if (config.layer_frozen) { + const frozen_input = inputs[config.default_input]; + return HoloOutput { + .data = frozen_input.data, + .selected_layer = frozen_input.layer, + .selected_phase = frozen_input.phase, + .valid = holo_input_valid(frozen_input), + .frozen = true, + }; + } + + var best_idx : u8 = 0; + var best_score : u16 = 0; + + for (inputs, 0..) |input, i| { + const score = holo_input_score(input, config); + if (score > best_score and holo_input_valid(input)) { + best_score = score; + best_idx = @as(u8, @intCast(i)); + } + } + + const selected = inputs[best_idx]; + return HoloOutput { + .data = selected.data, + .selected_layer = selected.layer, + .selected_phase = selected.phase, + .valid = holo_input_valid(selected), + .frozen = false, + }; +} + +// holo_mux_valid(inputs: [NUM_INPUTS]HoloInput) -> u8 +// Get bitmask of valid inputs +pub fn holo_mux_valid(inputs: [NUM_INPUTS]HoloInput) -> u8 { + var mask : u8 = 0; + for (inputs, 0..) |input, i| { + if (holo_input_valid(input)) { + mask |= (@as(u8, 1) << i); + } + } + return mask; +} + +// holo_mux_count_valid(inputs: [NUM_INPUTS]HoloInput) -> u8 +// Count valid inputs +pub fn holo_mux_count_valid(inputs: [NUM_INPUTS]HoloInput) -> u8 { + var count : u8 = 0; + for (inputs) |input| { + if (holo_input_valid(input)) { + count += 1; + } + } + return count; +} + +// holo_mux_any_valid(inputs: [NUM_INPUTS]HoloInput) -> bool +// Check if any input is valid +pub fn holo_mux_any_valid(inputs: [NUM_INPUTS]HoloInput) -> bool { + for (inputs) |input| { + if (holo_input_valid(input)) { + return true; + } + } + return false; +} + +// holo_mux_all_valid(inputs: [NUM_INPUTS]HoloInput) -> bool +// Check if all inputs are valid +pub fn holo_mux_all_valid(inputs: [NUM_INPUTS]HoloInput) -> bool { + for (inputs) |input| { + if (not holo_input_valid(input)) { + return false; + } + } + return true; +} + +// ============================================================================ +// Holo Layer Functions +// ============================================================================ + +// holo_layer_is_north(layer: u8) -> bool +pub fn holo_layer_is_north(layer: u8) bool { + return layer == HOLO_LAYER_NORTH; +} + +// holo_layer_is_east(layer: u8) -> bool +pub fn holo_layer_is_east(layer: u8) bool { + return layer == HOLO_LAYER_EAST; +} + +// holo_layer_is_south(layer: u8) -> bool +pub fn holo_layer_is_south(layer: u8) bool { + return layer == HOLO_LAYER_SOUTH; +} + +// holo_layer_is_west(layer: u8) -> bool +pub fn holo_layer_is_west(layer: u8) bool { + return layer == HOLO_LAYER_WEST; +} + +// holo_opposite_layer(layer: u8) -> u8 +// Get opposite layer (N<->S, E<->W) +pub fn holo_opposite_layer(layer: u8) -> u8 { + switch (layer) { + HOLO_LAYER_NORTH => return HOLO_LAYER_SOUTH, + HOLO_LAYER_SOUTH => return HOLO_LAYER_NORTH, + HOLO_LAYER_EAST => return HOLO_LAYER_WEST, + HOLO_LAYER_WEST => return HOLO_LAYER_EAST, + else => return layer, + } +} + +// ============================================================================ +// Holo Phase Functions +// ============================================================================ + +// holo_phase_90_deg(phase: u8) -> u8 +// Rotate phase by 90 degrees +pub fn holo_phase_90_deg(phase: u8) -> u8 { + return (phase + 1) % 4; +} + +// holo_phase_180_deg(phase: u8) -> u8 +// Rotate phase by 180 degrees +pub fn holo_phase_180_deg(phase: u8) -> u8 { + return (phase + 2) % 4; +} + +// holo_phase_270_deg(phase: u8) -> u8 +// Rotate phase by 270 degrees +pub fn holo_phase_270_deg(phase: u8) -> u8 { + return (phase + 3) % 4; +} + +// holo_phase_opposite(phase: u8) -> u8 +// Get opposite phase +pub fn holo_phase_opposite(phase: u8) -> u8 { + return (phase + 2) % 4; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_holo_mux_x4(layer: u8, phase: u8, frozen: bool) -> u16 +// Encode holographic mux instruction +pub fn encode_holo_mux_x4(layer: u8, phase: u8, frozen: bool) u16 { + // Format: [OP:8][LAYER:2][PHASE:2][FROZEN:1][RES:3] + const op : u16 = @as(u16, OP_HOLO_MUX_X4) << 8; + const layer_field : u16 = @as(u16, layer & 0x03) << 6; + const phase_field : u16 = @as(u16, phase & 0x03) << 4; + const frozen_field : u16 = if (frozen) (@as(u16, 1) << 3) else 0; + return op | layer_field | phase_field | frozen_field; +} + +// decode_holo_mux_x4(encoded: u16) -> struct { layer: u8, phase: u8, frozen: bool } +// Decode holographic mux instruction +pub fn decode_holo_mux_x4(encoded: u16) struct { layer: u8, phase: u8, frozen: bool } { + const layer : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const phase : u8 = @as(u8, @truncate((encoded >> 4) & 0x03)); + const frozen : bool = ((encoded >> 3) & 1) != 0; + return .{ .layer = layer, .phase = phase, .frozen = frozen }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "num_inputs_four" { + try std.testing.expect(NUM_INPUTS == 4); +} + +test "data_width_sixteen" { + try std.testing.expect(DATA_WIDTH == 16); +} + +test "select_bits_two" { + try std.testing.expect(SELECT_BITS == 2); +} + +test "holo_layer_constants" { + try std.testing.expect(HOLO_LAYER_NORTH == 0); + try std.testing.expect(HOLO_LAYER_EAST == 1); + try std.testing.expect(HOLO_LAYER_SOUTH == 2); + try std.testing.expect(HOLO_LAYER_WEST == 3); +} + +test "holo_phase_constants" { + try std.testing.expect(HOLO_PHASE_0 == 0); + try std.testing.expect(HOLO_PHASE_90 == 1); + try std.testing.expect(HOLO_PHASE_180 == 2); + try std.testing.expect(HOLO_PHASE_270 == 3); +} + +test "holo_input_valid_true" { + given input = HoloInput{.data = 0x1234, .layer = 0, .phase = 0, .valid = true, .phi_score = 100} + try std.testing.expect(holo_input_valid(input) == true); +} + +test "holo_input_valid_false_invalid_layer" { + given input = HoloInput{.data = 0x1234, .layer = 4, .phase = 0, .valid = true, .phi_score = 100} + try std.testing.expect(holo_input_valid(input) == false); +} + +test "holo_input_valid_false_invalid_flag" { + given input = HoloInput{.data = 0x1234, .layer = 0, .phase = 0, .valid = false, .phi_score = 100} + try std.testing.expect(holo_input_valid(input) == false); +} + +test "holo_mux_select_north" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 100}, + } + try std.testing.expect(config = HoloMuxConfig{.phi_optimized = false, .priority_select = false, .layer_frozen = false, .default_input = 0}); + try std.testing.expect(output = holo_mux_select(inputs, 0, config)); + try std.testing.expect(output.data == 0x1000); + try std.testing.expect(output.selected_layer == 0); + try std.testing.expect(output.frozen == false); +} + +test "holo_mux_select_east" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 100}, + } + try std.testing.expect(config = HoloMuxConfig{.phi_optimized = false, .priority_select = false, .layer_frozen = false, .default_input = 0}); + try std.testing.expect(output = holo_mux_select(inputs, 1, config)); + try std.testing.expect(output.data == 0x2000); + try std.testing.expect(output.selected_layer == 1); +} + +test "holo_mux_frozen" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 100}, + } + try std.testing.expect(config = HoloMuxConfig{.phi_optimized = false, .priority_select = false, .layer_frozen = true, .default_input = 2}); + try std.testing.expect(output = holo_mux_select(inputs, 1, config)); + try std.testing.expect(output.data == 0x3000); + try std.testing.expect(output.selected_layer == 2); + try std.testing.expect(output.frozen == true); +} + +test "holo_mux_best_phi_optimized" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 50}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 200}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 10}, + } + try std.testing.expect(config = HoloMuxConfig{.phi_optimized = true, .priority_select = false, .layer_frozen = false, .default_input = 0}); + try std.testing.expect(output = holo_mux_best(inputs, config)); + try std.testing.expect(output.selected_layer == 3 // Best phi score); +} + +test "holo_mux_valid_mask" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(mask = holo_mux_valid(inputs)); + try std.testing.expect(mask == 0b00000101); +} + +test "holo_mux_count_valid" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(holo_mux_count_valid(inputs) == 2); +} + +test "holo_mux_any_valid_true" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(holo_mux_any_valid(inputs) == true); +} + +test "holo_mux_any_valid_false" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(holo_mux_any_valid(inputs) == false); +} + +test "holo_mux_all_valid_true" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 100}, + } + try std.testing.expect(holo_mux_all_valid(inputs) == true); +} + +test "holo_layer_is_north" { + try std.testing.expect(holo_layer_is_north(0) == true); + try std.testing.expect(holo_layer_is_north(1) == false); +} + +test "holo_layer_is_south" { + try std.testing.expect(holo_layer_is_south(2) == true); + try std.testing.expect(holo_layer_is_south(0) == false); +} + +test "holo_opposite_layer" { + try std.testing.expect(holo_opposite_layer(HOLO_LAYER_NORTH) == HOLO_LAYER_SOUTH); + try std.testing.expect(holo_opposite_layer(HOLO_LAYER_EAST) == HOLO_LAYER_WEST); +} + +test "holo_phase_90_deg" { + try std.testing.expect(holo_phase_90_deg(0) == 1); + try std.testing.expect(holo_phase_90_deg(1) == 2); + try std.testing.expect(holo_phase_90_deg(3) == 0); +} + +test "holo_phase_180_deg" { + try std.testing.expect(holo_phase_180_deg(0) == 2); + try std.testing.expect(holo_phase_180_deg(1) == 3); + try std.testing.expect(holo_phase_180_deg(2) == 0); +} + +test "holo_phase_opposite" { + try std.testing.expect(holo_phase_opposite(0) == 2); + try std.testing.expect(holo_phase_opposite(1) == 3); + try std.testing.expect(holo_phase_opposite(2) == 0); + try std.testing.expect(holo_phase_opposite(3) == 1); +} + +test "encode_holo_mux_x4" { + given encoded = encode_holo_mux_x4(0x02, 0x01, true) + try std.testing.expect((encoded >> 8) == OP_HOLO_MUX_X4); +} + +test "decode_holo_mux_x4" { + given decoded = decode_holo_mux_x4(0xE6A8) + try std.testing.expect(decoded.layer == 0x02); + try std.testing.expect(decoded.phase == 0x01); + try std.testing.expect(decoded.frozen == true); +} + +test "opcode_constant" { + try std.testing.expect(OP_HOLO_MUX_X4 == 0xE6); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant num_inputs_four + assert NUM_INPUTS == 4 + +invariant data_width_sixteen + assert DATA_WIDTH == 16 + +invariant select_bits_two + assert SELECT_BITS == 2 + +invariant holo_layer_values + try std.testing.expect(HOLO_LAYER_NORTH >= 0 and HOLO_LAYER_WEST <= 3); + +invariant holo_phase_values + try std.testing.expect(HOLO_PHASE_0 >= 0 and HOLO_PHASE_270 <= 3); + +invariant holo_valid_one + assert HOLO_VALID == 1 + +invariant holo_invalid_zero + assert HOLO_INVALID == 0 + +invariant holo_phase_90_deg_cyclic + assert holo_phase_90_deg(3) == 0 + +invariant holo_phase_180_deg_cyclic + assert holo_phase_180_deg(2) == 0 + +invariant holo_opposite_layer_twice + assert holo_opposite_layer(holo_opposite_layer(HOLO_LAYER_NORTH)) == HOLO_LAYER_NORTH + +invariant holo_phase_opposite_twice + assert holo_phase_opposite(holo_phase_opposite(0)) == 0 + +invariant holo_mux_count_valid_bound + given inputs = [_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4 + assert holo_mux_count_valid(inputs) <= NUM_INPUTS + +invariant holo_mux_any_implies_count_positive + given inputs = [_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4 + try std.testing.expect(any_valid = holo_mux_any_valid(inputs)); + try std.testing.expect(count = holo_mux_count_valid(inputs)); + assert not any_valid or count > 0 + +invariant holo_mux_all_valid_equals_count + given inputs = [_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4 + assert holo_mux_all_valid(inputs) == (holo_mux_count_valid(inputs) == NUM_INPUTS) + +invariant holo_mux_valid_mask_bits + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(mask = holo_mux_valid(inputs)); + try std.testing.expect(mask < (@as(u8, 1) << NUM_INPUTS)); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench holo_mux_select_latency + measure: nanoseconds to holo_mux_select([_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4, 2, .{.phi_optimized = false, .priority_select = false, .layer_frozen = false, .default_input = 0}) + target: < 50ns + +bench holo_mux_best_latency + measure: nanoseconds to holo_mux_best([_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4, .{.phi_optimized = true, .priority_select = false, .layer_frozen = false, .default_input = 0}) + target: < 100ns + +bench holo_mux_count_valid_latency + measure: nanoseconds to holo_mux_count_valid([_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4) + target: < 50ns + +bench encode_holo_mux_x4_latency + measure: nanoseconds to encode_holo_mux_x4(0x02, 0x01, true) + target: < 20ns + +bench decode_holo_mux_x4_latency + measure: nanoseconds to decode_holo_mux_x4(0xE6A8) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/int4.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/int4.t27 new file mode 100644 index 0000000000..6d4cab516d --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/int4.t27 @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/int4.t27 +// Int4 - 4-bit signed integer +// NUMERIC-STANDARD-001 Agent 9 (P1) + +module Int4 { + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // Int4 bit layout: S(3) - 4-bit two's complement + // S: 4 bits (signed 4-bit integer) + // + // Range: -8 to 7 + // 16 possible values + + const BITS : u8 = 4; + + // 2. Int4 Type + + struct Int4 { + raw : u4, // 4-bit raw value (two's complement) + } + + // 3. Encoding/Decoding + + // Encode i8 to Int4 (clamp to range) + fn encode(value: i8) -> Int4 { + const clamped = if (value > 7) { 7 } else if (value < -8) { -8 } else { value }; + // Two's complement: negative values are stored as 2^4 + value + let raw = clamped as u4; + if (clamped < 0) { + raw = (16 + clamped) as u4; + } + return Int4{ raw = raw }; + } + + // Decode Int4 to i8 + fn decode(i: Int4) -> i8 { + const raw = i.raw as i8; + // Two's complement: if sign bit set, negative + if (raw >= 8) { + return raw - 16; + } + return raw; + } + + // 4. Format Properties + + fn min_value() -> i8 { + return -8; + } + + fn max_value() -> i8 { + return 7; + } + + fn range() -> i8 { + return max_value() - min_value(); + } + + // 5. Validation + + fn validate_format() -> bool { + return BITS == 4; + } + + // 6. Use Cases + + // Int4 is optimal for: + // - Extreme quantization (87.5% smaller than INT8) + // - Binary classification (0/1) + // - Ternary classification (-1, 0, +1) + // - Activation masks + // - Control signals + + // Memory: 4 bits = 0.5 bytes (8x INT32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 4.0 / 32.0; // 0.125 + + // 7. Arithmetic Operations + + fn add(a: Int4, b: Int4) -> Int4 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a + val_b; + return encode(result); + } + + fn sub(a: Int4, b: Int4) -> Int4 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a - val_b; + return encode(result); + } + + fn mul(a: Int4, b: Int4) -> Int4 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a * val_b; + return encode(result); + } + + fn neg(i: Int4) -> Int4 { + return encode(-decode(i)); + } + + fn abs(i: Int4) -> Int4 { + const val = decode(i); + return encode(if (val < 0) { -val } else { val }); + } + + // 8. Comparison Operations + + fn eq(a: Int4, b: Int4) -> bool { + return decode(a) == decode(b); + } + + fn ne(a: Int4, b: Int4) -> bool { + return decode(a) != decode(b); + } + + fn lt(a: Int4, b: Int4) -> bool { + return decode(a) < decode(b); + } + + fn le(a: Int4, b: Int4) -> bool { + return decode(a) <= decode(b); + } + + fn gt(a: Int4, b: Int4) -> bool { + return decode(a) > decode(b); + } + + fn ge(a: Int4, b: Int4) -> bool { + return decode(a) >= decode(b); + } + + // TDD-Inside-Spec: Tests and Invariants for Int4 + + test int4_decode_zero + given i = Int4{ raw = 0 } + when value = decode(i) + then value == 0 + + test int4_encode_zero_roundtrip + given original = 0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test int4_encode_positive_values + for (vals) |vals| + then encode(vals[i]).raw == vals[i] for vals[i] in {0, 1, 2, 3, 4, 5, 6, 7} + + test int4_decode_positive_values + for (vals) |vals| + then decode(Int4{ raw = vals[i] }) == vals[i] for vals[i] in {0, 1, 2, 3, 4, 5, 6, 7} + + test int4_encode_negative_values + given vals = [-1, -2, -3, -4, -5, -6, -7, -8] + then encode(vals[i]).raw == (16 + vals[i]) for vals[i] in vals + + test int4_decode_negative_values + given enc = [15, 14, 13, 12, 11, 10, 9, 8] + then decode(Int4{ raw = enc[i] }) == enc[i] - 16 for enc[i] in enc + + test int4_bits_constant + then BITS == 4 + + test int4_max_value + given max_val = max_value() + then max_val == 7 + + test int4_min_value + given min_val = min_value() + then min_val == -8 + + test int4_range + given range_val = range() + then range_val == 15 + + test int4_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.125) < 0.01 + + test int4_validate_format_success + given valid = validate_format() + then valid == true + + test int4_add_positive + given a = Int4{ raw = 3 } + and b = Int4{ raw = 4 } + and result = add(a, b) + then decode(result) == 7 + + test int4_add_overflow + given a = Int4{ raw = 4 } + and b = Int4{ raw = 4 } + and result = add(a, b) + then decode(result) == 7 + + test int4_sub_positive + given a = Int4{ raw = 7 } + and b = Int4{ raw = 3 } + and result = sub(a, b) + then decode(result) == 4 + + test int4_sub_underflow + given a = Int4{ raw = 1 } + and b = Int4{ raw = 5 } + and result = sub(a, b) + then decode(result) == -4 + + test int4_mul_positive + given a = Int4{ raw = 2 } + and b = Int4{ raw = 3 } + and result = mul(a, b) + then decode(result) == 6 + + test int4_mul_negative + given a = Int4{ raw = 3 } + and b = encode(-2) + and result = mul(a, b) + then decode(result) == -6 + + test int4_neg_positive + given i = Int4{ raw = 5 } + and result = neg(i) + then decode(result) == -5 + + test int4_neg_negative + given i = encode(-3) + and result = neg(i) + then decode(result) == 3 + + test int4_abs_positive + given i = Int4{ raw = 5 } + and result = abs(i) + then decode(result) == 5 + + test int4_abs_negative + given i = encode(-3) + and result = abs(i) + then decode(result) == 3 + + test int4_eq_true + given a = Int4{ raw = 3 } + and b = Int4{ raw = 3 } + then eq(a, b) == true + + test int4_eq_false + given a = Int4{ raw = 3 } + and b = Int4{ raw = 4 } + then eq(a, b) == false + + test int4_lt_true + given a = Int4{ raw = 2 } + and b = Int4{ raw = 5 } + then lt(a, b) == true + + test int4_lt_false + given a = Int4{ raw = 5 } + and b = Int4{ raw = 2 } + then lt(a, b) == false + + test int4_encode_clamps_above_max + given original = 10 + and encoded = encode(original) + then decode(encoded) == 7 + + test int4_encode_clamps_below_min + given original = -10 + and encoded = encode(original) + then decode(encoded) == -8 + + invariant int4_bits_constant + assert BITS == 4 + + invariant int4_max_value_is_7 + assert max_value() == 7 + + invariant int4_min_value_is_neg_8 + assert min_value() == -8 + + invariant int4_range_is_15 + assert range() == 15 + + invariant int4_encode_preserves_zero + assert encode(0).raw == 0 + + invariant int4_decode_preserves_zero + assert decode(Int4{raw = 0}) == 0 + + invariant int4_add_is_commutative + given a = 3 and b = 2 + then decode(add(encode(a), encode(b))) == decode(add(encode(b), encode(a))) + + invariant int4_zero_add_identity + given x = 5 + then decode(add(encode(x), encode(0))) == x + + invariant int4_neg_neg_is_identity + given x = 5 + then decode(neg(neg(encode(x)))) == x + + invariant int4_mul_by_neg_one_is_neg + given x = 5 + then decode(mul(encode(x), encode(-1))) == -x + + invariant int4_abs_is_non_negative + for (vals) |vals| + then decode(abs(encode(vals[i]))) >= 0 for vals[i] in vals + + bench int4_encode_latency + measure: nanoseconds to encode(5) + target: < 20ns + + bench int4_decode_latency + measure: nanoseconds to decode(Int4{ raw = 5 }) + target: < 10ns + + bench int4_encode_decode_roundtrip + measure: nanoseconds to encode(5) and decode(encode(5)) + target: < 30ns + + bench int4_add_latency + measure: nanoseconds to add(Int4{ raw = 3 }, Int4{ raw = 4 }) + target: < 30ns + + bench int4_mul_latency + measure: nanoseconds to mul(Int4{ raw = 3 }, Int4{ raw = 2 }) + target: < 40ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/int8.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/int8.t27 new file mode 100644 index 0000000000..c8daff4905 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/int8.t27 @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/int8.t27 +// Int8 - 8-bit signed integer +// NUMERIC-STANDARD-001 Agent 10 (P1) + +module Int8 { + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // Int8 bit layout: S(7) - 8-bit two's complement + // S: 8 bits (signed 8-bit integer) + // + // Range: -128 to 127 + // 256 possible values + + const BITS : u8 = 8; + + // 2. Int8 Type + + struct Int8 { + raw : u8, // 8-bit raw value (two's complement) + } + + // 3. Encoding/Decoding + + // Encode i32 to Int8 (clamp to range) + fn encode(value: i32) -> Int8 { + const clamped = if (value > 127) { 127 } else if (value < -128) { -128 } else { value }; + // Two's complement: negative values are stored as 2^8 + value + let raw = clamped as u8; + if (clamped < 0) { + raw = (256 + clamped) as u8; + } + return Int8{ raw = raw }; + } + + // Decode Int8 to i32 + fn decode(i: Int8) -> i32 { + const raw = i.raw as i32; + // Two's complement: if sign bit set, negative + if (raw >= 128) { + return raw - 256; + } + return raw; + } + + // 4. Format Properties + + fn min_value() -> i32 { + return -128; + } + + fn max_value() -> i32 { + return 127; + } + + fn range() -> i32 { + return max_value() - min_value(); + } + + fn num_values() -> i32 { + return 256; + } + + // 5. Validation + + fn validate_format() -> bool { + return BITS == 8; + } + + // 6. Use Cases + + // Int8 is optimal for: + // - Standard integer quantization (75% smaller than INT32) + // - Neural network weight quantization + // - Activation caching + // - General-purpose integer arithmetic + // - Control and status registers + + // Memory: 8 bits = 1 byte (4x INT32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 8.0 / 32.0; // 0.25 + + // 7. Arithmetic Operations + + fn add(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a + val_b; + return encode(result); + } + + fn sub(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a - val_b; + return encode(result); + } + + fn mul(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a * val_b; + return encode(result); + } + + fn div(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = if (val_b != 0) { val_a / val_b } else { 0 }; + return encode(result); + } + + fn mod(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = if (val_b != 0) { val_a % val_b } else { 0 }; + return encode(result); + } + + fn neg(i: Int8) -> Int8 { + return encode(-decode(i)); + } + + fn abs(i: Int8) -> Int8 { + const val = decode(i); + return encode(if (val < 0) { -val } else { val }); + } + + // 8. Comparison Operations + + fn eq(a: Int8, b: Int8) -> bool { + return decode(a) == decode(b); + } + + fn ne(a: Int8, b: Int8) -> bool { + return decode(a) != decode(b); + } + + fn lt(a: Int8, b: Int8) -> bool { + return decode(a) < decode(b); + } + + fn le(a: Int8, b: Int8) -> bool { + return decode(a) <= decode(b); + } + + fn gt(a: Int8, b: Int8) -> bool { + return decode(a) > decode(b); + } + + fn ge(a: Int8, b: Int8) -> bool { + return decode(a) >= decode(b); + } + + // 9. Bitwise Operations + + fn and(a: Int8, b: Int8) -> Int8 { + return Int8{ raw = a.raw & b.raw }; + } + + fn or(a: Int8, b: Int8) -> Int8 { + return Int8{ raw = a.raw | b.raw }; + } + + fn xor(a: Int8, b: Int8) -> Int8 { + return Int8{ raw = a.raw ^ b.raw }; + } + + fn not(i: Int8) -> Int8 { + return Int8{ raw = i.raw ^ 0xFF }; + } + + fn shl(a: Int8, shift: u8) -> Int8 { + const clamped_shift = if (shift > 7) { 7 } else { shift }; + return Int8{ raw = (a.raw << clamped_shift) & 0xFF }; + } + + fn shr(a: Int8, shift: u8) -> Int8 { + const clamped_shift = if (shift > 7) { 7 } else { shift }; + // Arithmetic shift for negative numbers + const val = decode(a); + const result = val >> (clamped_shift as i32); + return encode(result); + } + + // TDD-Inside-Spec: Tests and Invariants for Int8 + + test int8_decode_zero + given i = Int8{ raw = 0 } + when value = decode(i) + then value == 0 + + test int8_encode_zero_roundtrip + given original = 0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test int8_encode_positive_values + for (vals) |vals| + then encode(vals[i]).raw == vals[i] for vals[i] in {0, 1, 2, 50, 100, 127} + + test int8_decode_positive_values + for (vals) |vals| + then decode(Int8{ raw = vals[i] }) == vals[i] for vals[i] in {0, 1, 2, 50, 100, 127} + + test int8_encode_negative_values + given vals = [-1, -2, -50, -100, -127, -128] + then encode(vals[i]).raw == (256 + vals[i]) for vals[i] in vals + + test int8_decode_negative_values + given enc = [255, 254, 206, 156, 129, 128] + then decode(Int8{ raw = enc[i] }) == enc[i] - 256 for enc[i] in enc + + test int8_bits_constant + then BITS == 8 + + test int8_max_value + given max_val = max_value() + then max_val == 127 + + test int8_min_value + given min_val = min_value() + then min_val == -128 + + test int8_range + given range_val = range() + then range_val == 255 + + test int8_num_values + given num = num_values() + then num == 256 + + test int8_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.25) < 0.01 + + test int8_validate_format_success + given valid = validate_format() + then valid == true + + test int8_add_positive + given a = Int8{ raw = 50 } + and b = Int8{ raw = 60 } + and result = add(a, b) + then decode(result) == 110 + + test int8_add_overflow + given a = Int8{ raw = 100 } + and b = Int8{ raw = 50 } + and result = add(a, b) + then decode(result) == 127 + + test int8_sub_positive + given a = Int8{ raw = 100 } + and b = Int8{ raw = 30 } + and result = sub(a, b) + then decode(result) == 70 + + test int8_sub_underflow + given a = Int8{ raw = 50 } + and b = encode(100) + and result = sub(a, b) + then decode(result) == -50 + + test int8_mul_positive + given a = Int8{ raw = 10 } + and b = Int8{ raw = 12 } + and result = mul(a, b) + then decode(result) == 120 + + test int8_mul_overflow + given a = Int8{ raw = 20 } + and b = Int8{ raw = 20 } + and result = mul(a, b) + then decode(result) == 127 + + test int8_div_positive + given a = Int8{ raw = 100 } + and b = Int8{ raw = 25 } + and result = div(a, b) + then decode(result) == 4 + + test int8_div_by_zero + given a = Int8{ raw = 100 } + and b = Int8{ raw = 0 } + and result = div(a, b) + then decode(result) == 0 + + test int8_mod_positive + given a = Int8{ raw = 13 } + and b = Int8{ raw = 5 } + and result = mod(a, b) + then decode(result) == 3 + + test int8_mod_by_zero + given a = Int8{ raw = 13 } + and b = Int8{ raw = 0 } + and result = mod(a, b) + then decode(result) == 0 + + test int8_neg_positive + given i = Int8{ raw = 50 } + and result = neg(i) + then decode(result) == -50 + + test int8_neg_negative + given i = encode(-50) + and result = neg(i) + then decode(result) == 50 + + test int8_neg_zero + given i = Int8{ raw = 0 } + and result = neg(i) + then decode(result) == 0 + + test int8_abs_positive + given i = Int8{ raw = 50 } + and result = abs(i) + then decode(result) == 50 + + test int8_abs_negative + given i = encode(-50) + and result = abs(i) + then decode(result) == 50 + + test int8_abs_zero + given i = Int8{ raw = 0 } + and result = abs(i) + then decode(result) == 0 + + test int8_eq_true + given a = Int8{ raw = 50 } + and b = Int8{ raw = 50 } + then eq(a, b) == true + + test int8_eq_false + given a = Int8{ raw = 50 } + and b = Int8{ raw = 51 } + then eq(a, b) == false + + test int8_lt_true + given a = Int8{ raw = 20 } + and b = Int8{ raw = 50 } + then lt(a, b) == true + + test int8_lt_false + given a = Int8{ raw = 50 } + and b = Int8{ raw = 20 } + then lt(a, b) == false + + test int8_and_operation + given a = Int8{ raw = 0x55 } + and b = Int8{ raw = 0xAA } + and result = and(a, b) + then result.raw == 0x00 + + test int8_or_operation + given a = Int8{ raw = 0x55 } + and b = Int8{ raw = 0xAA } + and result = or(a, b) + then result.raw == 0xFF + + test int8_xor_operation + given a = Int8{ raw = 0x55 } + and b = Int8{ raw = 0xAA } + and result = xor(a, b) + then result.raw == 0xFF + + test int8_not_operation + given i = Int8{ raw = 0x55 } + and result = not(i) + then result.raw == 0xAA + + test int8_shl_left + given a = Int8{ raw = 0x01 } + and result = shl(a, 3) + then result.raw == 0x08 + + test int8_shl_clamp + given a = Int8{ raw = 0x01 } + and result = shl(a, 10) + then result.raw == 0x80 + + test int8_shr_right + given a = Int8{ raw = 0x80 } + and result = shr(a, 2) + then decode(result) == -32 + + test int8_encode_clamps_above_max + given original = 200 + and encoded = encode(original) + then decode(encoded) == 127 + + test int8_encode_clamps_below_min + given original = -200 + and encoded = encode(original) + then decode(encoded) == -128 + + test int8_all_values_encode_decode + for (i in 0..255) + then decode(encode(i - 128)) == (i - 128) + + invariant int8_bits_constant + assert BITS == 8 + + invariant int8_max_value_is_127 + assert max_value() == 127 + + invariant int8_min_value_is_neg_128 + assert min_value() == -128 + + invariant int8_range_is_255 + assert range() == 255 + + invariant int8_num_values_is_256 + assert num_values() == 256 + + invariant int8_encode_preserves_zero + assert encode(0).raw == 0 + + invariant int8_decode_preserves_zero + assert decode(Int8{raw = 0}) == 0 + + invariant int8_add_is_commutative + given a = 50 and b = 30 + then decode(add(encode(a), encode(b))) == decode(add(encode(b), encode(a))) + + invariant int8_zero_add_identity + for (x in {0, 50, 100, 127}) + then decode(add(encode(x), encode(0))) == x + + invariant int8_sub_self_is_zero + for (x in {0, 50, 100, 127}) + then decode(sub(encode(x), encode(x))) == 0 + + invariant int8_mul_by_neg_one_is_neg + for (x in {0, 50, 100, 127}) + then decode(mul(encode(x), encode(-1))) == -x + + invariant int8_mul_by_zero_is_zero + for (x in {0, 50, 100, 127}) + then decode(mul(encode(x), encode(0))) == 0 + + invariant int8_mul_by_one_is_identity + for (x in {0, 50, 100, 127}) + then decode(mul(encode(x), encode(1))) == x + + invariant int8_neg_neg_is_identity + for (x in {0, 50, 100, 127}) + then decode(neg(neg(encode(x)))) == x + + invariant int8_abs_is_non_negative + for (x in {-128..127}) + then decode(abs(encode(x))) >= 0 + + invariant int8_abs_preserves_zero + assert decode(abs(Int8{raw = 0})) == 0 + + invariant int8_shl_zero_is_zero + for (shift in {0, 1, 7}) + then shl(Int8{raw = 1}, shift).raw == 1 << shift + + invariant int8_shr_by_max_is_zero_or_neg_one + given i = Int8{ raw = 1 } + then shr(i, 7).raw == 0 + + bench int8_encode_latency + measure: nanoseconds to encode(50) + target: < 20ns + + bench int8_decode_latency + measure: nanoseconds to decode(Int8{ raw = 50 }) + target: < 10ns + + bench int8_encode_decode_roundtrip + measure: nanoseconds to encode(50) and decode(encode(50)) + target: < 30ns + + bench int8_add_latency + measure: nanoseconds to add(Int8{ raw = 30 }, Int8{ raw = 40 }) + target: < 30ns + + bench int8_mul_latency + measure: nanoseconds to mul(Int8{ raw = 10 }, Int8{ raw = 10 }) + target: < 30ns + + bench int8_div_latency + measure: nanoseconds to div(Int8{ raw = 100 }, Int8{ raw = 10 }) + target: < 30ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/lane_l_precheck.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/lane_l_precheck.t27 new file mode 100644 index 0000000000..f5462d9980 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/lane_l_precheck.t27 @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: Apache-2.0 +; lane_l_precheck.t27 — Sacred Opcode 0xDF: LUT Lookup Precheck +; Hardware pre-check for LUT lookup operations +; Validates lane readiness, checks LUT access permissions, and prepares address +; φ² + 1/φ² = 3 | TRINITY + +module sacred-lane_precheck; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_LUT_LOOKUP : u8 = 0xDF; + +pub const LANE_COUNT : u8 = 8; +pub const LUT_DEPTH : u16 = 256; +pub const LUT_ADDR_BITS : u8 = 8; +pub const LANE_ID_BITS : u8 = 3; + +pub const LANE_IDLE : u8 = 0; +pub const LANE_READY : u8 = 1; +pub const LANE_BUSY : u8 = 2; +pub const LANE_ERROR : u8 = 3; + +pub const CHECK_PASS : u8 = 1; +pub const CHECK_FAIL : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const LaneStatus = enum(u8) { + idle = LANE_IDLE, + ready = LANE_READY, + busy = LANE_BUSY, + error = LANE_ERROR, +} + +pub const PrecheckFlags = packed struct(u8) { + lut_enabled : u1, + lane_valid : u1, + address_valid : u1, + permission_ok : u1, + reserved : u4, +} + +pub const PrecheckResult = struct { + lane_id : u8, + status : LaneStatus, + flags : PrecheckFlags, + lut_address : u8, + error_code : u8, +} + +pub const LaneConfig = struct { + enabled : bool, + lut_base : u8, + lut_mask : u8, + priority : u8, +} + +// ============================================================================ +// Lane Status Functions +// ============================================================================ + +// lane_is_valid(lane_id: u8) -> bool +// Check if lane ID is valid +pub fn lane_is_valid(lane_id: u8) bool { + return lane_id < LANE_COUNT; +} + +// lane_is_ready(status: LaneStatus) -> bool +// Check if lane is ready for operation +pub fn lane_is_ready(status: LaneStatus) bool { + return status == LaneStatus.ready; +} + +// lane_is_busy(status: LaneStatus) -> bool +// Check if lane is busy +pub fn lane_is_busy(status: LaneStatus) bool { + return status == LaneStatus.busy; +} + +// lane_is_error(status: LaneStatus) -> bool +// Check if lane is in error state +pub fn lane_is_error(status: LaneStatus) bool { + return status == LaneStatus.error; +} + +// lane_is_idle(status: LaneStatus) -> bool +// Check if lane is idle +pub fn lane_is_idle(status: LaneStatus) bool { + return status == LaneStatus.idle; +} + +// ============================================================================ +// Precheck Functions +// ============================================================================ + +// precheck_lane(lane_id: u8, lut_addr: u8, config: LaneConfig) -> PrecheckResult +// Perform precheck on a lane for LUT lookup +pub fn precheck_lane(lane_id: u8, lut_addr: u8, config: LaneConfig) PrecheckResult { + var flags : PrecheckFlags = undefined; + flags.lut_enabled = if (config.enabled) 1 else 0; + flags.lane_valid = if (lane_is_valid(lane_id)) 1 else 0; + flags.address_valid = if (lut_addr < LUT_DEPTH) 1 else 0; + flags.permission_ok = if (config.enabled and ((lut_addr & config.lut_mask) < config.lut_mask)) 1 else 0; + flags.reserved = 0; + + var status = LaneStatus.ready; + var error_code : u8 = 0; + + if (flags.lane_valid == 0) { + status = LaneStatus.error; + error_code = 1; // Invalid lane ID + } else if (flags.address_valid == 0) { + status = LaneStatus.error; + error_code = 2; // Invalid address + } else if (flags.permission_ok == 0) { + status = LaneStatus.error; + error_code = 3; // Permission denied + } else if (flags.lut_enabled == 0) { + status = LaneStatus.error; + error_code = 4; // LUT not enabled + } + + return PrecheckResult { + .lane_id = lane_id, + .status = status, + .flags = flags, + .lut_address = if (status == LaneStatus.ready) lut_addr else 0, + .error_code = error_code, + }; +} + +// precheck_all_lanes(lane_bits: u8, lut_addr: u8, configs: [LANE_COUNT]LaneConfig) -> [LANE_COUNT]PrecheckResult +// Precheck all lanes simultaneously +pub fn precheck_all_lanes(lane_bits: u8, lut_addr: u8, configs: [LANE_COUNT]LaneConfig) [LANE_COUNT]PrecheckResult { + var results : [LANE_COUNT]PrecheckResult = undefined; + for (0..LANE_COUNT) |i| { + if ((lane_bits >> i) & 1 != 0) { + results[i] = precheck_lane(@as(u8, @intCast(i)), lut_addr, configs[i]); + } else { + results[i] = PrecheckResult { + .lane_id = @as(u8, @intCast(i)), + .status = LaneStatus.idle, + .flags = PrecheckFlags {.lut_enabled = 0, .lane_valid = 1, .address_valid = 0, .permission_ok = 0, .reserved = 0}, + .lut_address = 0, + .error_code = 0, + }; + } + } + return results; +} + +// precheck_pass(result: PrecheckResult) -> bool +// Check if precheck passed +pub fn precheck_pass(result: PrecheckResult) bool { + return result.status == LaneStatus.ready and result.flags.permission_ok != 0; +} + +// precheck_fail(result: PrecheckResult) -> bool +// Check if precheck failed +pub fn precheck_fail(result: PrecheckResult) bool { + return result.status == LaneStatus.error or result.flags.permission_ok == 0; +} + +// precheck_any_pass(results: [LANE_COUNT]PrecheckResult) -> bool +// Check if any lane precheck passed +pub fn precheck_any_pass(results: [LANE_COUNT]PrecheckResult) bool { + for (results) |result| { + if (precheck_pass(result)) { + return true; + } + } + return false; +} + +// precheck_all_pass(results: [LANE_COUNT]PrecheckResult) -> bool +// Check if all lane prechecks passed +pub fn precheck_all_pass(results: [LANE_COUNT]PrecheckResult) bool { + for (results) |result| { + if (precheck_fail(result)) { + return false; + } + } + return true; +} + +// precheck_count_passed(results: [LANE_COUNT]PrecheckResult) -> u8 +// Count number of lanes that passed precheck +pub fn precheck_count_passed(results: [LANE_COUNT]PrecheckResult) u8 { + var count : u8 = 0; + for (results) |result| { + if (precheck_pass(result)) { + count += 1; + } + } + return count; +} + +// precheck_get_ready_lanes(results: [LANE_COUNT]PrecheckResult) -> u8 +// Get bitmask of ready lanes +pub fn precheck_get_ready_lanes(results: [LANE_COUNT]PrecheckResult) -> u8 { + var mask : u8 = 0; + for (results, 0..) |result, i| { + if (precheck_pass(result)) { + mask |= (@as(u8, 1) << i); + } + } + return mask; +} + +// ============================================================================ +// LUT Address Functions +// ============================================================================ + +// lut_address_valid(addr: u8, config: LaneConfig) -> bool +// Check if LUT address is valid for given config +pub fn lut_address_valid(addr: u8, config: LaneConfig) bool { + if (addr >= LUT_DEPTH) { + return false; + } + if (not config.enabled) { + return false; + } + return (addr & config.lut_mask) < config.lut_mask; +} + +// lut_address_masked(addr: u8, config: LaneConfig) -> u8 +// Get masked LUT address +pub fn lut_address_masked(addr: u8, config: LaneConfig) -> u8 { + return addr & config.lut_mask; +} + +// lut_address_offset(addr: u8, base: u8) -> u8 +// Get offset address from base +pub fn lut_address_offset(addr: u8, base: u8) -> u8 { + if (addr >= base) { + return addr - base; + } + return 0; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_lut_lookup(lane_bits: u8, lut_addr: u8) -> u16 +// Encode LUT lookup instruction +pub fn encode_lut_lookup(lane_bits: u8, lut_addr: u8) u16 { + // Format: [OP:8][LANE:3][ADDR:5] + const op : u16 = @as(u16, OP_LUT_LOOKUP) << 8; + const lane : u16 = @as(u16, lane_bits & 0x07) << 5; + const addr : u16 = @as(u16, lut_addr & 0x1F); + return op | lane | addr; +} + +// decode_lut_lookup(encoded: u16) -> struct { lane_bits: u8, lut_addr: u8 } +// Decode LUT lookup instruction +pub fn decode_lut_lookup(encoded: u16) struct { lane_bits: u8, lut_addr: u8 } { + const lane_bits : u8 = @as(u8, @truncate((encoded >> 5) & 0x07)); + const lut_addr : u8 = @as(u8, @truncate(encoded & 0x1F)); + return .{ .lane_bits = lane_bits, .lut_addr = lut_addr }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "lane_is_valid_true" { + try std.testing.expect(lane_is_valid(0) == true); + try std.testing.expect(lane_is_valid(7) == true); +} + +test "lane_is_valid_false" { + try std.testing.expect(lane_is_valid(8) == false); + try std.testing.expect(lane_is_valid(255) == false); +} + +test "lane_is_ready_true" { + try std.testing.expect(lane_is_ready(LaneStatus.ready) == true); +} + +test "lane_is_ready_false" { + try std.testing.expect(lane_is_ready(LaneStatus.idle) == false); + try std.testing.expect(lane_is_ready(LaneStatus.busy) == false); + try std.testing.expect(lane_is_ready(LaneStatus.error) == false); +} + +test "lane_is_busy_true" { + try std.testing.expect(lane_is_busy(LaneStatus.busy) == true); +} + +test "lane_is_error_true" { + try std.testing.expect(lane_is_error(LaneStatus.error) == true); +} + +test "precheck_lane_pass" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 128, config)); + try std.testing.expect(precheck_pass(result) == true); + try std.testing.expect(result.error_code == 0); +} + +test "precheck_lane_invalid_lane" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(8, 128, config)); + try std.testing.expect(precheck_fail(result) == true); + try std.testing.expect(result.error_code == 1); +} + +test "precheck_lane_invalid_address" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 256, config)); + try std.testing.expect(precheck_fail(result) == true); + try std.testing.expect(result.error_code == 2); +} + +test "precheck_lane_permission_denied" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 0x0F, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 0x80, config)); + try std.testing.expect(precheck_fail(result) == true); + try std.testing.expect(result.error_code == 3); +} + +test "precheck_lane_lut_disabled" { + given config = LaneConfig{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 128, config)); + try std.testing.expect(precheck_fail(result) == true); + try std.testing.expect(result.error_code == 4); +} + +test "precheck_any_pass_true" { + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000011, 128, configs)); + try std.testing.expect(precheck_any_pass(results) == true); +} + +test "precheck_any_pass_false" { + given configs = [_]LaneConfig{ + .{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000011, 128, configs)); + try std.testing.expect(precheck_any_pass(results) == false); +} + +test "precheck_all_pass_true" { + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000011, 128, configs)); + try std.testing.expect(precheck_all_pass(results) == true); +} + +test "precheck_count_passed" { + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000111, 128, configs)); + try std.testing.expect(precheck_count_passed(results) == 2); +} + +test "precheck_get_ready_lanes" { + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000111, 128, configs)); + try std.testing.expect(precheck_get_ready_lanes(results) == 0b00000101); +} + +test "lut_address_valid_true" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(lut_address_valid(128, config) == true); +} + +test "lut_address_valid_disabled" { + given config = LaneConfig{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(lut_address_valid(128, config) == false); +} + +test "lut_address_valid_masked" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 0x0F, .priority = 0} + try std.testing.expect(lut_address_valid(0x08, config) == true); + try std.testing.expect(lut_address_valid(0x80, config) == false); +} + +test "lut_address_masked_value" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 0x0F, .priority = 0} + try std.testing.expect(lut_address_masked(0xAB, config) == 0x0B); +} + +test "lut_address_offset" { + try std.testing.expect(lut_address_offset(20, 10) == 10); + try std.testing.expect(lut_address_offset(5, 10) == 0); +} + +test "encode_lut_lookup" { + given encoded = encode_lut_lookup(0b00000101, 0x10) + try std.testing.expect((encoded >> 8) == OP_LUT_LOOKUP); +} + +test "decode_lut_lookup" { + given decoded = decode_lut_lookup(0xDF50) + try std.testing.expect(decoded.lane_bits == 0b00000010); + try std.testing.expect(decoded.lut_addr == 0x10); +} + +test "opcode_constant" { + try std.testing.expect(OP_LUT_LOOKUP == 0xDF); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant lane_count_eight + assert LANE_COUNT == 8 + +invariant lut_depth_256 + assert LUT_DEPTH == 256 + +invariant lut_addr_bits_eight + assert LUT_ADDR_BITS == 8 + +invariant lane_id_bits_three + assert LANE_ID_BITS == 3 + +invariant check_pass_one + assert CHECK_PASS == 1 + +invariant check_fail_zero + assert CHECK_FAIL == 0 + +invariant lane_valid_range + assert lane_is_valid(0) == true and lane_is_valid(7) == true and lane_is_valid(8) == false + +invariant precheck_pass_and_fail_opposite + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 128, config)); + assert precheck_pass(result) != precheck_fail(result) + +invariant ready_lane_not_busy + try std.testing.expect(lane_is_ready(LaneStatus.ready) != lane_is_busy(LaneStatus.ready)); + +invariant error_lane_not_ready + try std.testing.expect(lane_is_error(LaneStatus.error) != lane_is_ready(LaneStatus.error)); + +invariant precheck_count_bound + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0xFF, 128, configs)); + assert precheck_count_passed(results) <= LANE_COUNT + +invariant ready_mask_bound + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0xFF, 128, configs)); + try std.testing.expect(precheck_get_ready_lanes(results) < (@as(u8, 1) << LANE_COUNT)); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench precheck_lane_latency + measure: nanoseconds to precheck_lane(0, 128, .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}) + target: < 50ns + +bench precheck_all_lanes_latency + measure: nanoseconds to precheck_all_lanes(0xFF, 128, [_]LaneConfig{.{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}} ** 8) + target: < 200ns + +bench encode_lut_lookup_latency + measure: nanoseconds to encode_lut_lookup(0b00000101, 0x10) + target: < 20ns + +bench decode_lut_lookup_latency + measure: nanoseconds to decode_lut_lookup(0xDF50) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/lut_npu_81_entry.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/lut_npu_81_entry.t27 new file mode 100644 index 0000000000..9aa2002684 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/lut_npu_81_entry.t27 @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: Apache-2.0 +; lut_npu_81_entry.t27 — Sacred Opcode 0xE3: LUT NPU 81-Entry Lookup +; Hardware LUT for NPU operations with 81 entries (9×9 transform) +; φ² + 1/φ² = 3 | TRINITY + +module sacred-lut_npu_81_entry; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_LUT_NPU : u8 = 0xE3; + +pub const LUT_SIZE : u8 = 81; // 9×9 = 81 entries +pub const LUT_ADDR_BITS : u8 = 7; // 7 bits for 0-80 +pub const LUT_VALUE_BITS : u8 = 16; + +pub const ENTRY_VALID : u8 = 1; +pub const ENTRY_INVALID : u8 = 0; + +pub const PHI_CONST_BASE : u8 = 0; +pub const CONST_LUT_END : u8 = 80; + +// ============================================================================ +// Types +// ============================================================================ + +pub const LutEntry = struct { + address : u8, + value : u16, + valid : bool, + phi_related : bool, +} + +pub const LutConfig = struct { + read_only : bool, + phi_optimized : bool, + cache_enabled : bool, +} + +pub const NpuOperation = enum(u8) { + lookup = 0, + transform = 1, + phi_project = 2, + sparse_reduce = 3, + quantize = 4, + dequantize = 5, + composite = 6, + custom = 7, +} + +pub const Transform9x9 = struct { + grid : [9][9]i8, + sparsity : u8, // 0-100% + phi_score : u16, // φ-distance from ideal +} + +// ============================================================================ +// LUT Entry Functions +// ============================================================================ + +// lut_entry_valid(entry: LutEntry) -> bool +// Check if LUT entry is valid +pub fn lut_entry_valid(entry: LutEntry) bool { + return entry.valid and entry.address < LUT_SIZE; +} + +// lut_entry_address_valid(addr: u8) -> bool +// Check if address is valid +pub fn lut_entry_address_valid(addr: u8) bool { + return addr < LUT_SIZE; +} + +// create_lut_entry(addr: u8, value: u16, phi_related: bool) -> LutEntry +// Create a LUT entry +pub fn create_lut_entry(addr: u8, value: u16, phi_related: bool) LutEntry { + return LutEntry { + .address = addr, + .value = value, + .valid = lut_entry_address_valid(addr), + .phi_related = phi_related, + }; +} + +// ============================================================================ +// NPU Operation Functions +// ============================================================================ + +// npu_transform_9x9(grid: [9][9]i8) -> Transform9x9 +// Analyze 9×9 transform +pub fn npu_transform_9x9(grid: [9][9]i8) Transform9x9 { + var zero_count : u8 = 0; + var phi_score : u16 = 0; + + // Count zeros and compute phi score + for (grid, 0..) |row, i| { + for (row, 0..) |val, j| { + if (val == 0) { + zero_count += 1; + } + // Simple phi score based on center-weight + const center_dist = if (i > 4) i - 4 else 4 - i; + const center_dist_j = if (j > 4) j - 4 else 4 - j; + phi_score += @as(u16, @intCast(val)) * @as(u16, @intCast(5 - center_dist - center_dist_j)); + } + } + + const sparsity = (zero_count * 100) / 81; + + return Transform9x9 { + .grid = grid, + .sparsity = sparsity, + .phi_score = phi_score, + }; +} + +// transform_is_sparse(transform: Transform9x9) -> bool +// Check if transform is sparse (>50% zeros) +pub fn transform_is_sparse(transform: Transform9x9) bool { + return transform.sparsity > 50; +} + +// transform_phi_optimized(transform: Transform9x9) -> bool +// Check if transform is phi-optimized (high phi score) +pub fn transform_phi_optimized(transform: Transform9x9) bool { + return transform.phi_score > 100; +} + +// ============================================================================ +// LUT Lookup Functions +// ============================================================================ + +// lut_lookup_base(lut: [LUT_SIZE]LutEntry, addr: u8) -> LutEntry +// Base LUT lookup +pub fn lut_lookup_base(lut: [LUT_SIZE]LutEntry, addr: u8) LutEntry { + if (not lut_entry_address_valid(addr)) { + return LutEntry{.address = 255, .value = 0, .valid = false, .phi_related = false}; + } + return lut[@as(u8, @intCast(addr))]; +} + +// lut_lookup_phi(lut: [LUT_SIZE]LutEntry, addr: u8) -> LutEntry +// Phi-optimized LUT lookup +pub fn lut_lookup_phi(lut: [LUT_SIZE]LutEntry, addr: u8) LutEntry { + if (not lut_entry_address_valid(addr)) { + return LutEntry{.address = 255, .value = 0, .valid = false, .phi_related = false}; + } + + const entry = lut[@as(u8, @intCast(addr))]; + if (entry.valid and entry.phi_related) { + return entry; + } + + // Fallback: search for nearest phi-related entry + for (0..LUT_SIZE) |i| { + const e = lut[i]; + if (e.valid and e.phi_related) { + return e; + } + } + + return entry; +} + +// lut_lookup_range(lut: [LUT_SIZE]LutEntry, start_addr: u8, count: u8) -> [LUT_SIZE]LutEntry +// Lookup range of entries +pub fn lut_lookup_range(lut: [LUT_SIZE]LutEntry, start_addr: u8, count: u8) [LUT_SIZE]LutEntry { + var result : [LUT_SIZE]LutEntry = undefined; + + for (0..LUT_SIZE) |i| { + if (i < count and lut_entry_address_valid(start_addr + i)) { + result[i] = lut[@as(u8, @intCast(start_addr + i))]; + } else { + result[i] = LutEntry{.address = 255, .value = 0, .valid = false, .phi_related = false}; + } + } + + return result; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_lut_npu(op: NpuOperation, addr: u8) -> u16 +// Encode LUT NPU instruction +pub fn encode_lut_npu(op: NpuOperation, addr: u8) u16 { + // Format: [OP:8][OPCODE:3][ADDR:5] + const op_field : u16 = @as(u16, OP_LUT_NPU) << 8; + const opcode_field : u16 = @as(u16, @intFromEnum(op)) << 5; + const addr_field : u16 = @as(u16, addr & 0x1F); + return op_field | opcode_field | addr_field; +} + +// decode_lut_npu(encoded: u16) -> struct { op: NpuOperation, addr: u8 } +// Decode LUT NPU instruction +pub fn decode_lut_npu(encoded: u16) struct { op: NpuOperation, addr: u8 } { + const opcode_val : u8 = @as(u8, @truncate((encoded >> 5) & 0x07)); + const addr : u8 = @as(u8, @truncate(encoded & 0x1F)); + + var op : NpuOperation = undefined; + switch (opcode_val) { + 0 => op = NpuOperation.lookup, + 1 => op = NpuOperation.transform, + 2 => op = NpuOperation.phi_project, + 3 => op = NpuOperation.sparse_reduce, + 4 => op = NpuOperation.quantize, + 5 => op = NpuOperation.dequantize, + 6 => op = NpuOperation.composite, + else => op = NpuOperation.custom, + } + + return .{ .op = op, .addr = addr }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "lut_size_eighty_one" { + try std.testing.expect(LUT_SIZE == 81); +} + +test "lut_entry_address_valid_true" { + try std.testing.expect(lut_entry_address_valid(0) == true); + try std.testing.expect(lut_entry_address_valid(80) == true); +} + +test "lut_entry_address_valid_false" { + try std.testing.expect(lut_entry_address_valid(81) == false); + try std.testing.expect(lut_entry_address_valid(255) == false); +} + +test "lut_entry_valid_true" { + given entry = LutEntry{.address = 40, .value = 0x1234, .valid = true, .phi_related = false} + try std.testing.expect(lut_entry_valid(entry) == true); +} + +test "lut_entry_valid_false_invalid_addr" { + given entry = LutEntry{.address = 255, .value = 0x1234, .valid = true, .phi_related = false} + try std.testing.expect(lut_entry_valid(entry) == false); +} + +test "lut_entry_valid_false_invalid_flag" { + given entry = LutEntry{.address = 40, .value = 0x1234, .valid = false, .phi_related = false} + try std.testing.expect(lut_entry_valid(entry) == false); +} + +test "create_lut_entry_valid" { + given entry = create_lut_entry(40, 0x1234, true) + try std.testing.expect(entry.valid == true); + try std.testing.expect(entry.phi_related == true); + try std.testing.expect(entry.value == 0x1234); +} + +test "create_lut_entry_invalid" { + given entry = create_lut_entry(81, 0x1234, true) + try std.testing.expect(entry.valid == false); +} + +test "npu_transform_9x9_count" { + given grid = [_][9]i8{ + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{1} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + } + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform.sparsity == 88); +} + +test "transform_is_sparse_true" { + given grid = [_][9]i8{ + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{1} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + } + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform_is_sparse(transform) == true); +} + +test "transform_is_sparse_false" { + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform_is_sparse(transform) == false); +} + +test "transform_phi_optimized_true" { + given grid = [_][9]i8{ + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{5} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + } + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform_phi_optimized(transform) == true); +} + +test "transform_phi_optimized_false" { + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform_phi_optimized(transform) == false); +} + +test "lut_lookup_base_valid" { + given lut = [_]LutEntry{.{.address = 40, .value = 0x1234, .valid = true, .phi_related = false}} ** 81 + try std.testing.expect(entry = lut_lookup_base(lut, 40)); + try std.testing.expect(entry.value == 0x1234); + try std.testing.expect(entry.valid == true); +} + +test "lut_lookup_base_invalid_addr" { + given lut = [_]LutEntry{.{.address = 40, .value = 0x1234, .valid = true, .phi_related = false}} ** 81 + try std.testing.expect(entry = lut_lookup_base(lut, 81)); + try std.testing.expect(entry.valid == false); +} + +test "encode_lut_npu" { + given encoded = encode_lut_npu(NpuOperation.phi_project, 0x10) + try std.testing.expect((encoded >> 8) == OP_LUT_NPU); +} + +test "decode_lut_npu" { + given decoded = decode_lut_npu(0xE350) + try std.testing.expect(decoded.op == NpuOperation.phi_project); + try std.testing.expect(decoded.addr == 0x10); +} + +test "opcode_constant" { + try std.testing.expect(OP_LUT_NPU == 0xE3); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant lut_size_eighty_one + assert LUT_SIZE == 81 + +invariant lut_addr_bits_seven + assert LUT_ADDR_BITS == 7 + +invariant lut_value_bits_sixteen + assert LUT_VALUE_BITS == 16 + +invariant phi_const_base_zero + assert PHI_CONST_BASE == 0 + +invariant const_lut_end_eighty + assert CONST_LUT_END == 80 + +invariant entry_valid_one + assert ENTRY_VALID == 1 + +invariant entry_invalid_zero + assert ENTRY_INVALID == 0 + +invariant lut_address_bound + assert lut_entry_address_valid(80) == true and lut_entry_address_valid(81) == false + +invariant transform_sparsity_bound + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform.sparsity <= 100); + +invariant transform_sparsity_dense + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform.sparsity == 0); + +invariant phi_score_non_negative + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform.phi_score >= 0); + +invariant npu_operation_enum_values + try std.testing.expect(@intFromEnum(NpuOperation.lookup) == 0); + try std.testing.expect(@intFromEnum(NpuOperation.custom) == 7); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench npu_transform_9x9_latency + measure: nanoseconds to npu_transform_9x9([_] [9]i8{[_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5} ** 9}) + target: < 500ns + +bench lut_lookup_base_latency + measure: nanoseconds to lut_lookup_base([_]LutEntry{.{.address = 40, .value = 0x1234, .valid = true, .phi_related = false}} ** 81, 40) + target: < 50ns + +bench lut_lookup_phi_latency + measure: nanoseconds to lut_lookup_phi([_]LutEntry{.{.address = 40, .value = 0x1234, .valid = true, .phi_related = true}} ** 81, 40) + target: < 100ns + +bench encode_lut_npu_latency + measure: nanoseconds to encode_lut_npu(NpuOperation.phi_project, 0x10) + target: < 20ns + +bench decode_lut_npu_latency + measure: nanoseconds to decode_lut_npu(0xE350) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/nf4.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/nf4.t27 new file mode 100644 index 0000000000..7d67c8028e --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/nf4.t27 @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/nf4.t27 +// NormalFloat4 - 4-bit normalized float quantization format (Google) +// NUMERIC-STANDARD-001 Agent 11 (P1) +// +// NF4 format: +// - Represents normalized values in [0, 1] +// - Uses a 4-bit normalized representation +// - Optimal for weight quantization where most values are near zero + +module NF4 { + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // NF4 bit layout: N(4) + // N: 4 bits (normalized float value) + // + // Range: 0 to 1 (normalized) + // 16 possible quantization levels + // + // NF4 quantization is used in: + // - QLoRA (Quantized Low-Rank Adapter) + // - GPTQ (GPT Quantization) + // - Neural network weight compression + + const BITS : u8 = 4; + const LEVELS : u8 = 16; + + // 2. NF4 Type + + struct NF4 { + raw : u4, // 4-bit normalized value + } + + // 3. Encoding/Decoding + + // Encode f32 to NF4 (normalized quantization) + fn encode(value: f32) -> NF4 { + // Clamp to [0, 1] range + const clamped = if (value < 0.0) { 0.0 } else if (value > 1.0) { 1.0 } else { value }; + + // Quantize to 4 bits (0-15) + let quantized = (clamped * 15.0) as u8; + if (quantized > 15) { quantized = 15; } + + return NF4{ raw = quantized }; + } + + // Decode NF4 to f32 + fn decode(nf: NF4) -> f64 { + // Dequantize: value = raw / 15 + return (nf.raw as f64) / 15.0; + } + + // 4. Format Properties + + fn min_value() -> f64 { + return 0.0; + } + + fn max_value() -> f64 { + return 1.0; + } + + fn epsilon() -> f64 { + // Smallest representable difference: 1/15 + return 1.0 / 15.0; + } + + fn quantization_error(value: f32, nf: NF4) -> f32 { + const decoded = decode(nf) as f32; + return abs(value - decoded); + } + + // 5. Validation + + fn validate_format() -> bool { + return BITS == 4 && LEVELS == 16; + } + + // 6. Use Cases + + // NF4 is optimal for: + // - Neural network weight quantization (4-bit) + // - QLoRA low-rank adapters + // - GPTQ post-training quantization + // - Embedding table compression + // - Attention weight storage + // - Binary-aware quantization + + // Memory: 4 bits = 0.5 bytes (8x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 4.0 / 32.0; // 0.125 + + // 7. Helper Functions + + fn abs(x: f32) -> f32 { + if (x < 0.0) { -x } else { x }; + } + + fn quantization_error_pct(value: f32) -> f32 { + const encoded = encode(value); + const decoded = decode(encoded) as f32; + if (value == 0.0) { return 0.0; } + return abs(decoded - value) / abs(value) * 100.0; + } + + // 8. Comparison Operations + + fn eq(a: NF4, b: NF4) -> bool { + return a.raw == b.raw; + } + + fn ne(a: NF4, b: NF4) -> bool { + return a.raw != b.raw; + } + + fn lt(a: NF4, b: NF4) -> bool { + return decode(a) < decode(b); + } + + fn le(a: NF4, b: NF4) -> bool { + return decode(a) <= decode(b); + } + + fn gt(a: NF4, b: NF4) -> bool { + return decode(a) > decode(b); + } + + fn ge(a: NF4, b: NF4) -> bool { + return decode(a) >= decode(b); + } + + // TDD-Inside-Spec: Tests and Invariants for NF4 + + test nf4_decode_zero + given nf = NF4{ raw = 0 } + when value = decode(nf) + then value == 0.0 + + test nf4_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test nf4_decode_max + given nf = NF4{ raw = 15 } + when value = decode(nf) + then value == 1.0 + + test nf4_encode_max_roundtrip + given original = 1.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test nf4_encode_clamp_below_zero + given original = -0.5 + and encoded = encode(original) + then encoded.raw == 0 + + test nf4_encode_clamp_above_one + given original = 1.5 + and encoded = encode(original) + then encoded.raw == 15 + + test nf4_quantize_mid_values + given mid = 0.5 + and encoded = encode(mid) + then encoded.raw == 7 or encoded.raw == 8 + + test nf4_decode_preserves_linearity + given nf1 = NF4{ raw = 5 } + and nf2 = NF4{ raw = 10 } + and decoded1 = decode(nf1) + and decoded2 = decode(nf2) + then abs(decoded2 - 2.0 * decoded1) < 0.1 + + test nf4_bits_constant + then BITS == 4 + + test nf4_levels_constant + then LEVELS == 16 + + test nf4_min_value + given min_val = min_value() + then min_val == 0.0 + + test nf4_max_value + given max_val = max_value() + then max_val == 1.0 + + test nf4_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test nf4_epsilon_correct + then abs(epsilon() - 1.0 / 15.0) < 0.001 + + test nf4_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.125) < 0.01 + + test nf4_validate_format_success + given valid = validate_format() + then valid == true + + test nf4_all_levels_encode_decode + for (i in 0..15) + then abs(decode(NF4{ raw = i }) - (i as f64) / 15.0) < 0.01 + + test nf4_quantization_error_small_mid_range + given value = 0.5 + and error = quantization_error(value, encode(value)) + then error < 0.05 + + test nf4_quantization_error_zero + given value = 0.0 + and error = quantization_error(value, encode(value)) + then error == 0.0 + + test nf4_quantization_error_one + given value = 1.0 + and error = quantization_error(value, encode(value)) + then error == 0.0 + + test nf4_eq_true + given a = NF4{ raw = 7 } + and b = NF4{ raw = 7 } + then eq(a, b) == true + + test nf4_eq_false + given a = NF4{ raw = 7 } + and b = NF4{ raw = 8 } + then eq(a, b) == false + + test nf4_lt_true + given a = NF4{ raw = 3 } + and b = NF4{ raw = 7 } + then lt(a, b) == true + + test nf4_ge_true + given a = NF4{ raw = 10 } + and b = NF4{ raw = 5 } + then ge(a, b) == true + + invariant nf4_bits_constant + assert BITS == 4 + + invariant nf4_levels_constant + assert LEVELS == 16 + + invariant nf4_min_is_zero + assert min_value() == 0.0 + + invariant nf4_max_is_one + assert max_value() == 1.0 + + invariant nf4_encode_preserves_zero + assert encode(0.0).raw == 0 + + invariant nf4_encode_preserves_one + assert encode(1.0).raw == 15 + + invariant nf4_decode_min_is_zero + assert decode(NF4{ raw = 0 }) == 0.0 + + invariant nf4_decode_max_is_one + assert decode(NF4{ raw = 15 }) == 1.0 + + invariant nf4_all_values_in_range + for (i in 0..15) + then decode(NF4{ raw = i }) >= 0.0 and decode(NF4{ raw = i }) <= 1.0 + + invariant nf4_epsilon_is_step_size + given val = decode(NF4{ raw = 1 }) + and next_val = decode(NF4{ raw = 2 }) + then abs(next_val - val) == epsilon() + + invariant nf4_encode_monotonic + given x1 = 0.3 and x2 = 0.6 + then encode(x1).raw <= encode(x2).raw + + invariant nf4_decode_monotonic + for (i in 0..14) + then decode(NF4{ raw = i }) <= decode(NF4{ raw = i + 1 }) + + invariant nf4_quantization_error_bounds + for (i in 0..15) + then quantization_error(0.0, NF4{ raw = 0 }) == 0.0 + + invariant nf4_symmetric_quantization + given x = 0.5 + then quantization_error_pct(x) < 50.0 + + bench nf4_encode_latency + measure: nanoseconds to encode(0.5) + target: < 20ns + + bench nf4_decode_latency + measure: nanoseconds to decode(NF4{ raw = 7 }) + target: < 10ns + + bench nf4_encode_decode_roundtrip + measure: nanoseconds to encode(0.5) and decode(encode(0.5)) + target: < 30ns + + bench nf4_quantization_error_latency + measure: nanoseconds to quantization_error(0.5, NF4{ raw = 7 }) + target: < 50ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/null_pe.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/null_pe.t27 new file mode 100644 index 0000000000..f5fcb0f90a --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/null_pe.t27 @@ -0,0 +1,568 @@ +// SPDX-License-Identifier: Apache-2.0 +; null_pe.t27 — Sacred Opcode 0xEA: Null Processing Element +; Hardware null PE (processing element) for sparse acceleration +; φ² + 1/φ² = 3 | TRINITY + +module sacred-null_pe; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_NULL_PE : u8 = 0xEA; + +pub const PE_COUNT : u8 = 4; +pub const DATA_WIDTH : u8 = 16; + +pub const PE_STATE_IDLE : u8 = 0; +pub const PE_STATE_ACTIVE : u8 = 1; +pub const PE_STATE_WAITING : u8 = 2; +pub const PE_STATE_ERROR : u8 = 3; + +pub const NULL_MODE_SKIP : u8 = 0; +pub const NULL_MODE_ZERO : u8 = 1; +pub const NULL_MODE_IDENTITY : u8 = 2; +pub const NULL_MODE_CUSTOM : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const PeState = enum(u8) { + idle = PE_STATE_IDLE, + active = PE_STATE_ACTIVE, + waiting = PE_STATE_WAITING, + error = PE_STATE_ERROR, +} + +pub const PeConfig = struct { + mode : u8, + enabled : bool, + bypass_enabled : bool, +} + +pub const PeStatus = struct { + state : PeState, + cycle_count : u32, + op_count : u32, +} + +pub const NullPeResult = struct { + valid : bool, + output_data : u16, + pe_mask : u8, // Bitmask of which PEs are null + skip_count : u8, +} + +pub const PeArray = struct { + configs : [PE_COUNT]PeConfig, + statuses : [PE_COUNT]PeStatus, +} + +// ============================================================================ +// PE Status Functions +// ============================================================================ + +// pe_is_active(status: PeStatus) -> bool +// Check if PE is active +pub fn pe_is_active(status: PeStatus) bool { + return status.state == PeState.active; +} + +// pe_is_idle(status: PeStatus) -> bool +// Check if PE is idle +pub fn pe_is_idle(status: PeStatus) -> bool { + return status.state == PeState.idle; +} + +// pe_is_waiting(status: PeStatus) -> bool +// Check if PE is waiting +pub fn pe_is_waiting(status: PeStatus) bool { + return status.state == PeState.waiting; +} + +// pe_is_error(status: PeStatus) -> bool +// Check if PE is in error state +pub fn pe_is_error(status: PeStatus) bool { + return status.state == PeState.error; +} + +// pe_set_state(status: PeStatus, state: PeState) -> PeStatus +// Set PE state +pub fn pe_set_state(status: PeStatus, state: PeState) PeStatus { + return PeStatus { + .state = state, + .cycle_count = if (state == PeState.active) status.cycle_count + 1 else status.cycle_count, + .op_count = if (state == PeState.active) status.op_count + 1 else status.op_count, + }; +} + +// ============================================================================ +// PE Config Functions +// ============================================================================ + +// pe_config_enabled(config: PeConfig) -> bool +// Check if PE config enables operation +pub fn pe_config_enabled(config: PeConfig) -> bool { + return config.enabled and not config.bypass_enabled; +} + +// pe_is_null(config: PeConfig) -> bool +// Check if PE is in null mode +pub fn pe_is_null(config: PeConfig) -> bool { + return config.mode == NULL_MODE_SKIP or config.mode == NULL_MODE_ZERO; +} + +// pe_is_identity(config: PeConfig) -> bool +// Check if PE is in identity mode +pub fn pe_is_identity(config: PeConfig) -> bool { + return config.mode == NULL_MODE_IDENTITY; +} + +// ============================================================================ +// Null PE Operations +// ============================================================================ + +// null_pe_skip(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult +// Null PE operation (skip processing) +pub fn null_pe_skip(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult { + var skip_count : u8 = 0; + var output_data : u16 = input; + + for (configs, 0..) |config, i| { + if (((pe_mask >> i) & 1) != 0 and pe_is_null(config)) { + skip_count += 1; + } + } + + return NullPeResult { + .valid = skip_count > 0, + .output_data = if (skip_count == PE_COUNT) 0 else output_data, + .pe_mask = pe_mask, + .skip_count = skip_count, + }; +} + +// null_pe_zero(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult +// Null PE operation (zero output) +pub fn null_pe_zero(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult { + var skip_count : u8 = 0; + + for (configs, 0..) |config, i| { + if (((pe_mask >> i) & 1) != 0 and pe_is_null(config)) { + skip_count += 1; + } + } + + return NullPeResult { + .valid = true, + .output_data = 0, + .pe_mask = pe_mask, + .skip_count = skip_count, + }; +} + +// null_pe_identity(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult +// Null PE operation (identity pass-through) +pub fn null_pe_identity(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult { + var active_count : u8 = 0; + + for (configs, 0..) |config, i| { + if (((pe_mask >> i) & 1) != 0 and pe_is_identity(config)) { + active_count += 1; + } + } + + return NullPeResult { + .valid = active_count > 0, + .output_data = input, + .pe_mask = pe_mask, + .skip_count = PE_COUNT - active_count, + }; +} + +// null_pe_execute(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult +// Execute null PE operation based on config +pub fn null_pe_execute(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult { + var mode : u8 = NULL_MODE_SKIP; + + // Determine primary mode from active PEs + for (configs, 0..) |config, i| { + if (((pe_mask >> i) & 1) != 0 and config.enabled) { + mode = config.mode; + break; + } + } + + switch (mode) { + NULL_MODE_SKIP => { + return null_pe_skip(input, pe_mask, configs); + }, + NULL_MODE_ZERO => { + return null_pe_zero(input, pe_mask, configs); + }, + NULL_MODE_IDENTITY => { + return null_pe_identity(input, pe_mask, configs); + }, + NULL_MODE_CUSTOM => { + // Custom: return zero for null PEs + return null_pe_zero(input, pe_mask, configs); + }, + else => { + return null_pe_skip(input, pe_mask, configs); + }, + } +} + +// ============================================================================ +// PE Array Functions +// ============================================================================ + +// pe_array_init() -> PeArray +// Initialize PE array +pub fn pe_array_init() -> PeArray { + var configs : [PE_COUNT]PeConfig = undefined; + var statuses : [PE_COUNT]PeStatus = undefined; + + for (0..PE_COUNT) |i| { + configs[i] = PeConfig{.mode = NULL_MODE_SKIP, .enabled = false, .bypass_enabled = false}; + statuses[i] = PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0}; + } + + return PeArray{.configs = configs, .statuses = statuses}; +} + +// pe_array_enable(pe_array: PeArray, pe_mask: u8) -> PeArray +// Enable PEs based on mask +pub fn pe_array_enable(pe_array: PeArray, pe_mask: u8) -> PeArray { + var result = pe_array; + + for (0..PE_COUNT) |i| { + if ((pe_mask >> i) & 1 != 0) { + result.configs[i].enabled = true; + } else { + result.configs[i].enabled = false; + } + } + + return result; +} + +// pe_array_count_active(pe_array: PeArray) -> u8 +// Count active PEs +pub fn pe_array_count_active(pe_array: PeArray) -> u8 { + var count : u8 = 0; + for (pe_array.configs) |config| { + if (config.enabled and not config.bypass_enabled) { + count += 1; + } + } + return count; +} + +// pe_array_count_null(pe_array: PeArray) -> u8 +// Count null PEs +pub fn pe_array_count_null(pe_array: PeArray) -> u8 { + var count : u8 = 0; + for (pe_array.configs) |config| { + if (pe_is_null(config)) { + count += 1; + } + } + return count; +} + +// pe_array_get_null_mask(pe_array: PeArray) -> u8 +// Get bitmask of null PEs +pub fn pe_array_get_null_mask(pe_array: PeArray) -> u8 { + var mask : u8 = 0; + for (pe_array.configs, 0..) |config, i| { + if (pe_is_null(config)) { + mask |= (@as(u8, 1) << i); + } + } + return mask; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_null_pe(pe_mask: u8, mode: u8) -> u16 +// Encode null PE instruction +pub fn encode_null_pe(pe_mask: u8, mode: u8) u16 { + // Format: [OP:8][MODE:2][MASK:4][RES:2] + const op : u16 = @as(u16, OP_NULL_PE) << 8; + const mode_field : u16 = @as(u16, mode & 0x03) << 6; + const mask_field : u16 = @as(u16, pe_mask & 0x0F) << 2; + return op | mode_field | mask_field; +} + +// decode_null_pe(encoded: u16) -> struct { pe_mask: u8, mode: u8 } +// Decode null PE instruction +pub fn decode_null_pe(encoded: u16) -> struct { pe_mask: u8, mode: u8 } { + const mode : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const pe_mask : u8 = @as(u8, @truncate((encoded >> 2) & 0x0F)); + return .{ .pe_mask = pe_mask, .mode = mode }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "pe_count_four" { + try std.testing.expect(PE_COUNT == 4); +} + +test "data_width_sixteen" { + try std.testing.expect(DATA_WIDTH == 16); +} + +test "null_mode_constants" { + try std.testing.expect(NULL_MODE_SKIP == 0); + try std.testing.expect(NULL_MODE_ZERO == 1); + try std.testing.expect(NULL_MODE_IDENTITY == 2); + try std.testing.expect(NULL_MODE_CUSTOM == 3); +} + +test "pe_state_constants" { + try std.testing.expect(PE_STATE_IDLE == 0); + try std.testing.expect(PE_STATE_ACTIVE == 1); + try std.testing.expect(PE_STATE_WAITING == 2); + try std.testing.expect(PE_STATE_ERROR == 3); +} + +test "pe_is_active_true" { + given status = PeStatus{.state = PeState.active, .cycle_count = 10, .op_count = 5} + try std.testing.expect(pe_is_active(status) == true); +} + +test "pe_is_active_false" { + given status = PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} + try std.testing.expect(pe_is_active(status) == false); +} + +test "pe_is_idle_true" { + given status = PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} + try std.testing.expect(pe_is_idle(status) == true); +} + +test "pe_set_state_increments" { + given status = PeStatus{.state = PeState.idle, .cycle_count = 10, .op_count = 5} + try std.testing.expect(result = pe_set_state(status, PeState.active)); + try std.testing.expect(result.cycle_count == 11); + try std.testing.expect(result.op_count == 6); +} + +test "pe_config_enabled_true" { + given config = PeConfig{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false} + try std.testing.expect(pe_config_enabled(config) == true); +} + +test "pe_config_enabled_disabled" { + given config = PeConfig{.mode = NULL_MODE_SKIP, .enabled = false, .bypass_enabled = false} + try std.testing.expect(pe_config_enabled(config) == false); +} + +test "pe_config_enabled_bypass" { + given config = PeConfig{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = true} + try std.testing.expect(pe_config_enabled(config) == false); +} + +test "pe_is_null_skip" { + try std.testing.expect(pe_is_null(.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}) == true); + try std.testing.expect(pe_is_null(.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}) == true); +} + +test "pe_is_null_not" { + try std.testing.expect(pe_is_null(.{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}) == false); +} + +test "null_pe_skip_some" { + given configs = [_]PeConfig{ + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + } + try std.testing.expect(result = null_pe_skip(0x1234, 0b1010, configs)); + try std.testing.expect(result.skip_count == 3); +} + +test "null_pe_zero_always_zero" { + given configs = [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(result = null_pe_zero(0x1234, 0b1111, configs)); + try std.testing.expect(result.output_data == 0); + try std.testing.expect(result.valid == true); +} + +test "null_pe_identity_pass" { + given configs = [_]PeConfig{.{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(result = null_pe_identity(0x1234, 0b1111, configs)); + try std.testing.expect(result.output_data == 0x1234); +} + +test "null_pe_execute_skip" { + given configs = [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(result = null_pe_execute(0x1234, 0b1111, configs)); + try std.testing.expect(result.output_data == 0x1234); +} + +test "null_pe_execute_zero" { + given configs = [_]PeConfig{.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(result = null_pe_execute(0x1234, 0b1111, configs)); + try std.testing.expect(result.output_data == 0); +} + +test "pe_array_init" { + given array = pe_array_init() + try std.testing.expect(pe_array_count_active(array) == 0); + try std.testing.expect(pe_array_count_null(array) == 0); +} + +test "pe_array_enable" { + given array = pe_array_init() + try std.testing.expect(result = pe_array_enable(array, 0b1010)); + try std.testing.expect(result.configs[0].enabled == false); + try std.testing.expect(result.configs[1].enabled == true); + try std.testing.expect(result.configs[2].enabled == false); + try std.testing.expect(result.configs[3].enabled == true); +} + +test "pe_array_count_active" { + given array = pe_array_enable(pe_array_init(), 0b1111) + try std.testing.expect(pe_array_count_active(array) == 4); +} + +test "pe_array_count_null" { + given configs = [_]PeConfig{ + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = false, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + } + try std.testing.expect(array = PeArray{.configs = configs, .statuses = [_]PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} ** 4}); + try std.testing.expect(pe_array_count_null(array) == 2); +} + +test "pe_array_get_null_mask" { + given configs = [_]PeConfig{ + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = false, .bypass_enabled = false}, + .{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}, + } + try std.testing.expect(array = PeArray{.configs = configs, .statuses = [_]PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} ** 4}); + try std.testing.expect(mask = pe_array_get_null_mask(array)); + try std.testing.expect(mask == 0b1011); +} + +test "encode_null_pe" { + given encoded = encode_null_pe(0b1010, 1) + try std.testing.expect((encoded >> 8) == OP_NULL_PE); +} + +test "decode_null_pe" { + given decoded = decode_null_pe(0xEA5A) + try std.testing.expect(decoded.pe_mask == 0b1010); + try std.testing.expect(decoded.mode == 1); +} + +test "opcode_constant" { + try std.testing.expect(OP_NULL_PE == 0xEA); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant pe_count_four + assert PE_COUNT == 4 + +invariant data_width_sixteen + assert DATA_WIDTH == 16 + +invariant null_mode_values + try std.testing.expect(NULL_MODE_SKIP >= 0 and NULL_MODE_CUSTOM <= 3); + +invariant pe_state_values + try std.testing.expect(PE_STATE_IDLE >= 0 and PE_STATE_ERROR <= 3); + +invariant pe_set_state_increments + given status = PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} + try std.testing.expect(result = pe_set_state(status, PeState.active)); + try std.testing.expect(result.cycle_count == status.cycle_count + 1); + try std.testing.expect(result.op_count == status.op_count + 1); + +invariant pe_set_state_preserves_when_idle + given status = PeStatus{.state = PeState.active, .cycle_count = 10, .op_count = 5} + try std.testing.expect(result = pe_set_state(status, PeState.idle)); + try std.testing.expect(result.cycle_count == status.cycle_count); + try std.testing.expect(result.op_count == status.op_count); + +invariant null_pe_skip_valid + given configs = [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(null_pe_skip(0x1234, 0, configs).valid == false); + try std.testing.expect(null_pe_skip(0x1234, 0b1111, configs).skip_count == 4); + +invariant null_pe_zero_always_valid + given configs = [_]PeConfig{.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(null_pe_zero(0x1234, 0, configs).valid == true); + +invariant null_pe_zero_output + given configs = [_]PeConfig{.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(null_pe_zero(0x1234, 0b1111, configs).output_data == 0); + +invariant null_pe_identity_preserves + given configs = [_]PeConfig{.{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(null_pe_identity(0x1234, 0, configs).output_data == 0x1234); + +invariant pe_array_count_bounds + given array = pe_array_enable(pe_array_init(), 0xFF) + try std.testing.expect(pe_array_count_active(array) <= PE_COUNT); + try std.testing.expect(pe_array_count_null(array) <= PE_COUNT); + +invariant pe_array_get_null_mask_bound + try std.testing.expect(pe_array_get_null_mask(pe_array_init()) < (@as(u8, 1) << PE_COUNT)); + +invariant pe_array_count_all + given configs = [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(array = PeArray{.configs = configs, .statuses = [_]PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} ** 4}); + try std.testing.expect(pe_array_count_active(array) + pe_array_count_null(array) == PE_COUNT); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench null_pe_skip_latency + measure: nanoseconds to null_pe_skip(0x1234, 0b1111, [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4) + target: < 50ns + +bench null_pe_zero_latency + measure: nanoseconds to null_pe_zero(0x1234, 0b1111, [_]PeConfig{.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}} ** 4) + target: < 50ns + +bench null_pe_identity_latency + measure: nanoseconds to null_pe_identity(0x1234, 0b1111, [_]PeConfig{.{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}} ** 4) + target: < 50ns + +bench null_pe_execute_latency + measure: nanoseconds to null_pe_execute(0x1234, 0b1111, [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4) + target: < 100ns + +bench pe_array_count_active_latency + measure: nanoseconds to pe_array_count_active(pe_array_enable(pe_array_init(), 0xFF)) + target: < 50ns + +bench pe_array_get_null_mask_latency + measure: nanoseconds to pe_array_get_null_mask(pe_array_enable(pe_array_init(), 0xFF)) + target: < 50ns + +bench encode_null_pe_latency + measure: nanoseconds to encode_null_pe(0b1010, 1) + target: < 20ns + +bench decode_null_pe_latency + measure: nanoseconds to decode_null_pe(0xEA5A) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/posit16.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/posit16.t27 new file mode 100644 index 0000000000..d868ba60e8 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/posit16.t27 @@ -0,0 +1,979 @@ +// SPDX-License-Identifier: Apache-2.0 +; posit16.t27 — Posit Type 16 (Type-2 with ES=1, unum 1.0 format) +; 16-bit posit format with 1 exponent bit (ES=1) and 0 useed bits +; Alternative name: posit<16,1> +; Range: ~-3.8e4 to ~3.8e4, precision: ~1-2 significant bits at extremes, ~10 at 1.0 +; φ² + 1/φ² = 3 | TRINITY + +module triformat-posit16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 16; +pub const ES : u8 = 1; // Exponent size bits +pub const NBITS : u8 = BITS - ES; // Number of bits excluding ES +pub const USEED_BITS : u8 = 0; // No useed bits (type-2) + +pub const SIGN_SHIFT : u8 = 15; +pub const REGIME_SHIFT : u8 = 14; + +pub const SIGN_MASK : u16 = 0x8000; // 1 << 15 +pub const REGIME_MASK : u16 = 0x7FFF; // Lower 15 bits + +// Special values +pub const POS16_ZERO : u16 = 0x0000; // Positive zero +pub const POS16_NAR : u16 = 0x8000; // Not a Real (NaR) + +// Maximum regime runs +pub const MAX_REGIME : i16 = 6; // Based on NBITS=15 and regime encoding + +// ============================================================================ +// Types +// ============================================================================ + +pub const Posit16 = u16; // 16-bit posit value + +// Posit16 components struct +pub struct PositComponents { + sign : bool, // true = negative + regime : i8, // Regime value (can be negative or positive) + exponent : u8, // Exponent (0 or 1 for ES=1) + fraction : u16, // Fraction/mantissa bits + scale : i16, // Combined scale factor = 2^(useed*regime + exponent) +} + +// ============================================================================ +// Regime Extraction Functions +// ============================================================================ + +// posit16_extract_regime(pos: Posit16) -> i8 +// Extract regime value from posit +// Regime is encoded as a run of bits (same as sign) followed by opposite bit +pub fn posit16_extract_regime(pos: Posit16) i8 { + // Remove sign bit + var remaining_bits : u16 = pos & REGIME_MASK; + + // Count leading bits that match the first bit (regime run) + var regime_run : i8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Regime value: if first_bit=1, regime = run-1; if first_bit=0, regime = -run + return if (first_bit == 1) regime_run - 1 else -regime_run; +} + +// posit16_extract_exponent(pos: Posit16) -> u8 +// Extract exponent value from posit +pub fn posit16_extract_exponent(pos: Posit16) i8 { + var remaining_bits : u16 = pos & REGIME_MASK; + + // Find regime run length + var regime_run : u8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Skip regime bits and the terminating bit + const exponent_bit : u8 = if (regime_run + 1 < NBITS) + @truncate(@as(u8, @intCast((remaining_bits >> (NBITS - 1 - regime_run - 1))) & 1)) + else + 0; + + return if (exponent_bit == 1) 1 else 0; +} + +// posit16_extract_fraction(pos: Posit16) -> u16 +// Extract fraction bits from posit +pub fn posit16_extract_fraction(pos: Posit16) u16 { + var remaining_bits : u16 = pos & REGIME_MASK; + + // Find regime run length + var regime_run : u8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Skip regime bits, terminating bit, and exponent bit (ES=1) + const skip_bits : u8 = regime_run + 1 + ES; + const fraction_bits : u8 = if (skip_bits >= NBITS) 0 else NBITS - skip_bits; + + // Extract fraction bits + var fraction : u16 = 0; + if (fraction_bits > 0 and skip_bits < NBITS) { + fraction = remaining_bits & ((@as(u16, 1) << fraction_bits) - 1); + } + + return fraction; +} + +// posit16_extract_sign(pos: Posit16) -> bool +// Extract sign bit (true = negative) +pub fn posit16_extract_sign(pos: Posit16) bool { + return (pos & SIGN_MASK) != 0; +} + +// posit16_extract_components(pos: Posit16) -> PositComponents +// Extract all components from posit +pub fn posit16_extract_components(pos: Posit16) PositComponents { + const sign = posit16_extract_sign(pos); + const regime = posit16_extract_regime(pos); + const exponent = posit16_extract_exponent(pos); + const fraction = posit16_extract_fraction(pos); + + // Calculate scale: useed = 2^(2^ES) = 2^2 = 4 + // scale = 2^(useed * regime + exponent) + const useed_exp : i8 = 1 << ES; // 2^ES = 2^1 = 2 + const useed : i16 = 1 << useed_exp; // 2^2 = 4 + const scale = (useed * @as(i16, regime)) + @as(i16, exponent); + + return PositComponents { + .sign = sign, + .regime = regime, + .exponent = @as(u8, @intCast(exponent)), + .fraction = fraction, + .scale = scale, + }; +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// posit16_is_zero(pos: Posit16) -> bool +// Check if posit is zero (positive or negative) +pub fn posit16_is_zero(pos: Posit16) bool { + return pos == POS16_ZERO; +} + +// posit16_is_nar(pos: Posit16) -> bool +// Check if posit is Not-a-Real (NaR) +pub fn posit16_is_nar(pos: Posit16) bool { + return pos == POS16_NAR; +} + +// posit16_is_special(pos: Posit16) -> bool +// Check if posit is a special value (zero or NaR) +pub fn posit16_is_special(pos: Posit16) bool { + return posit16_is_zero(pos) or posit16_is_nar(pos); +} + +// posit16_is_finite(pos: Posit16) -> bool +// Check if posit is finite (not NaR) +pub fn posit16_is_finite(pos: Posit16) bool { + return not posit16_is_nar(pos); +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// posit16_encode_f32(value: f32) -> Posit16 +// Encode IEEE 754 single precision to Posit16 +pub fn posit16_encode_f32(value: f32) Posit16 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) POS16_NAR else POS16_ZERO; + } + + // Handle NaN + if (std.math.isNan(value)) { + return POS16_NAR; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) POS16_NAR else POS16_NAR; + } + + // Extract sign + const is_negative = value < 0.0; + const abs_value = if (is_negative) -value else value; + + // Convert to f64 for more precision during computation + const value_f64 : f64 = @as(f64, @floatCast(abs_value)); + + // Calculate useed = 2^(2^ES) = 4 for ES=1 + const useed : f64 = 4.0; + + // Calculate log2 and determine regime + const log2_val = std.math.log(value_f64) / std.math.log(2.0); + + // Regime: largest k such that 2^(k*useed) <= value < 2^((k+1)*useed) + var k : i16 = @intFromFloat(@floor(log2_val / std.math.log(useed))); + + // Clamp regime + if (k > MAX_REGIME) { + k = MAX_REGIME; + } else if (k < -MAX_REGIME - 1) { + k = -MAX_REGIME - 1; + } + + // Calculate exponent + var exp : i8 = 0; + var remaining = value_f64 / std.math.pow(f64, useed, @as(f64, @floatFromInt(k))); + + // Normalize remaining value to [1, 2) for fraction extraction + while (remaining >= 2.0 and exp < ((@as(i8, 1) << ES) - 1)) { + remaining /= 2.0; + exp += 1; + } + + // Extract fraction bits + const fraction_bits : u8 = @as(u8, @intCast(NBITS - @as(u8, @intCast(@abs(k) + 1)) - ES)); + var fraction : u16 = 0; + var frac_val = remaining - 1.0; // Remove implicit 1 + + if (fraction_bits > 0) { + for (0..fraction_bits) |i| { + frac_val *= 2.0; + if (frac_val >= 1.0) { + fraction |= (@as(u16, 1) << (fraction_bits - 1 - i)); + frac_val -= 1.0; + } + } + } + + // Encode bits + var result : u16 = 0; + + // Set sign bit + if (is_negative) { + result |= SIGN_MASK; + } + + // Encode regime + // For k >= 0: run of k+1 ones followed by zero + // For k < 0: run of -k zeros followed by one + const regime_bits_remaining : u8 = if (fraction_bits > 0) + @as(u8, @intCast(@as(i16, @abs(k)) + 1)) + else + NBITS - ES; + + const regime_start = SIGN_SHIFT - 1 - regime_bits_remaining; + if (k >= 0) { + // k+1 ones followed by zero (if space permits) + for (0..@as(u8, @intCast(k + 1))) |i| { + if (regime_start - i >= 0) { + result |= (@as(u16, 1) << (regime_start - i)); + } + } + } else { + // -k zeros followed by one + const one_pos = regime_start - @as(u8, @intCast(-k)); + if (one_pos >= 0) { + result |= (@as(u16, 1) << one_pos); + } + } + + // Encode exponent + if (ES > 0 and fraction_bits + ES > 0) { + const exp_pos = @as(u8, @intCast(NBITS - 1)) - regime_bits_remaining; + if (exp_pos >= 0) { + result |= (@as(u16, exp) << exp_pos); + } + } + + // Encode fraction + const fraction_pos = @as(u8, @intCast(NBITS - 1)) - regime_bits_remaining - ES; + if (fraction_bits > 0 and fraction_pos >= fraction_bits) { + result |= (fraction << (fraction_pos - fraction_bits + 1)); + } + + return result; +} + +// posit16_decode_f32(pos: Posit16) -> f32 +// Decode Posit16 to IEEE 754 single precision +pub fn posit16_decode_f32(pos: Posit16) f32 { + // Handle zero + if (posit16_is_zero(pos)) { + return 0.0; + } + + // Handle NaR + if (posit16_is_nar(pos)) { + return std.math.nan(f32); + } + + // Extract components + const comps = posit16_extract_components(pos); + + // Calculate value + // value = (-1)^sign * 2^scale * (1 + fraction/2^fraction_bits) + const useed : f64 = 4.0; // 2^(2^1) = 4 + const scale_value = std.math.pow(f64, useed, @as(f64, @floatFromInt(comps.scale))); + + // Calculate fraction contribution + const fraction_bits : u8 = @as(u8, @intCast(NBITS - @as(u8, @intCast(@abs(comps.regime) + 1)) - ES)); + const fraction_value = if (fraction_bits > 0) + @as(f64, @floatFromInt(comps.fraction)) / @as(f64, @floatFromInt(@as(u16, 1) << fraction_bits)) + else + 0.0; + + const magnitude = scale_value * (1.0 + fraction_value); + + return if (comps.sign) -@as(f32, @floatCast(magnitude)) else @as(f32, @floatCast(magnitude)); +} + +// posit16_encode_f64(value: f64) -> Posit16 +// Encode IEEE 754 double precision to Posit16 +pub fn posit16_encode_f64(value: f64) Posit16 { + return posit16_encode_f32(@as(f32, @floatCast(value))); +} + +// posit16_decode_f64(pos: Posit16) -> f64 +// Decode Posit16 to IEEE 754 double precision +pub fn posit16_decode_f64(pos: Posit16) f64 { + const f32_val = posit16_decode_f32(pos); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// posit16_add(a: Posit16, b: Posit16) -> Posit16 +// Add two posit values +pub fn posit16_add(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a)) { + return b; + } + if (posit16_is_zero(b)) { + return a; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa + fb); +} + +// posit16_sub(a: Posit16, b: Posit16) -> Posit16 +// Subtract two posit values +pub fn posit16_sub(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa - fb); +} + +// posit16_mul(a: Posit16, b: Posit16) -> Posit16 +// Multiply two posit values +pub fn posit16_mul(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a) or posit16_is_zero(b)) { + return POS16_ZERO; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa * fb); +} + +// posit16_div(a: Posit16, b: Posit16) -> Posit16 +// Divide two posit values +pub fn posit16_div(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a)) { + return POS16_ZERO; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa / fb); +} + +// posit16_abs(value: Posit16) -> Posit16 +// Absolute value of posit +pub fn posit16_abs(value: Posit16) Posit16 { + return value & ~SIGN_MASK; +} + +// posit16_neg(value: Posit16) -> Posit16 +// Negate posit +pub fn posit16_neg(value: Posit16) Posit16 { + return value ^ SIGN_MASK; +} + +// posit16_is_equal(a: Posit16, b: Posit16) -> bool +// Check if two posit values are equal +pub fn posit16_is_equal(a: Posit16, b: Posit16) bool { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return false; + } + if (posit16_is_zero(a) and posit16_is_zero(b)) { + return true; + } + return a == b; +} + +// posit16_is_greater(a: Posit16, b: Posit16) -> bool +// Check if a > b +pub fn posit16_is_greater(a: Posit16, b: Posit16) bool { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return false; + } + const sign_a = posit16_extract_sign(a); + const sign_b = posit16_extract_sign(b); + if (sign_a != sign_b) { + return not sign_a; // Positive > negative + } + if (sign_a) { + // Both negative, compare magnitudes (inverse) + return a < b; + } + return a > b; +} + +// posit16_max(a: Posit16, b: Posit16) -> Posit16 +// Return the larger of two posit values +pub fn posit16_max(a: Posit16, b: Posit16) Posit16 { + return if (posit16_is_greater(a, b)) a else b; +} + +// posit16_min(a: Posit16, b: Posit16) -> Posit16 +// Return the smaller of two posit values +pub fn posit16_min(a: Posit16, b: Posit16) Posit16 { + return if (posit16_is_greater(a, b)) b else a; +} + +// posit16_sqrt(value: Posit16) -> Posit16 +// Square root of posit value +pub fn posit16_sqrt(value: Posit16) Posit16 { + if (posit16_is_nar(value)) { + return POS16_NAR; + } + if (posit16_extract_sign(value)) { + return POS16_NAR; + } + if (posit16_is_zero(value)) { + return POS16_ZERO; + } + + const fv = posit16_decode_f32(value); + return posit16_encode_f32(std.math.sqrt(fv)); +} + +// posit16_lerp(a: Posit16, b: Posit16, t: f32) -> Posit16 +// Linear interpolation between a and b +pub fn posit16_lerp(a: Posit16, b: Posit16, t: f32) Posit16 { + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + const result = fa + (fb - fa) * t; + return posit16_encode_f32(result); +} + +// posit16_scale(value: Posit16, scale: f32) -> Posit16 +// Scale posit value by scalar +pub fn posit16_scale(value: Posit16, scale: f32) Posit16 { + const fv = posit16_decode_f32(value); + return posit16_encode_f32(fv * scale); +} + +// posit16_relu(value: Posit16) -> Posit16 +// ReLU activation: max(0, x) +pub fn posit16_relu(value: Posit16) Posit16 { + return if (posit16_extract_sign(value)) POS16_ZERO else value; +} + +// posit16_sigmoid(value: Posit16) -> Posit16 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn posit16_sigmoid(value: Posit16) Posit16 { + const fv = posit16_decode_f32(value); + return posit16_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// posit16_quantize_to_int4(value: Posit16) -> i8 +// Quantize posit to Int4 (range [-8, 7]) +pub fn posit16_quantize_to_int4(value: Posit16) i8 { + const fv = posit16_decode_f32(value); + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// posit16_from_f32_scaled(value: f32, scale: f32) -> Posit16 +// Encode f32 to posit with custom scale factor +pub fn posit16_from_f32_scaled(value: f32, scale: f32) Posit16 { + return posit16_encode_f32(value * scale); +} + +// posit16_to_f32_scaled(value: Posit16, scale: f32) -> f32 +// Decode posit to f32 with custom scale factor +pub fn posit16_to_f32_scaled(value: Posit16, scale: f32) f32 { + return posit16_decode_f32(value) / scale; +} + +// posit16_dot_product(a: []const Posit16, b: []const Posit16) -> Posit16 +// Dot product of two posit arrays +pub fn posit16_dot_product(a: []const Posit16, b: []const Posit16) Posit16 { + var sum: f32 = 0.0; + for (a, 0..) |val_a, i| { + if (i >= b.len) break; + const fa = posit16_decode_f32(val_a); + const fb = posit16_decode_f32(b[i]); + sum += fa * fb; + } + return posit16_encode_f32(sum); +} + +// posit16_vector_sum(vec: []const Posit16) -> Posit16 +// Sum all elements of a vector +pub fn posit16_vector_sum(vec: []const Posit16) Posit16 { + var sum: f32 = 0.0; + for (vec) |val| { + sum += posit16_decode_f32(val); + } + return posit16_encode_f32(sum); +} + +// posit16_vector_mean(vec: []const Posit16) -> Posit16 +// Mean of all elements of a vector +pub fn posit16_vector_mean(vec: []const Posit16) Posit16 { + if (vec.len == 0) { + return POS16_ZERO; + } + const sum = posit16_vector_sum(vec); + return posit16_div(sum, posit16_encode_f32(@as(f32, @floatFromInt(vec.len)))); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "posit16_is_zero_true" { + try std.testing.expect(posit16_is_zero(POS16_ZERO) == true); +} + +test "posit16_is_zero_false" { + try std.testing.expect(posit16_is_zero(0x0001) == false); +} + +test "posit16_is_nar_true" { + try std.testing.expect(posit16_is_nar(POS16_NAR) == true); +} + +test "posit16_is_nar_false" { + try std.testing.expect(posit16_is_nar(POS16_ZERO) == false); +} + +test "posit16_is_special_zero" { + try std.testing.expect(posit16_is_special(POS16_ZERO) == true); +} + +test "posit16_is_special_nar" { + try std.testing.expect(posit16_is_special(POS16_NAR) == true); +} + +test "posit16_is_special_normal" { + try std.testing.expect(posit16_is_special(0x4000) == false); +} + +test "posit16_extract_sign_positive" { + given value = 0x4000 + try std.testing.expect(sign = posit16_extract_sign(value)); + try std.testing.expect(sign == false); +} + +test "posit16_extract_sign_negative" { + given value = 0xC000 + try std.testing.expect(sign = posit16_extract_sign(value)); + try std.testing.expect(sign == true); +} + +test "posit16_encode_f32_zero" { + given pos = posit16_encode_f32(0.0) + try std.testing.expect(pos == POS16_ZERO); +} + +test "posit16_encode_f32_one" { + given pos = posit16_encode_f32(1.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "posit16_encode_f32_negative_one" { + given pos = posit16_encode_f32(-1.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded + 1.0) < 0.1); +} + +test "posit16_encode_f32_ten" { + given pos = posit16_encode_f32(10.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - 10.0) < 2.0); +} + +test "posit16_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(pos = posit16_encode_f32(original)); + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - original) < 0.3); +} + +test "posit16_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(pos = posit16_encode_f32(original)); + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - original) < 0.3); +} + +test "posit16_decode_f32_zero" { + try std.testing.expect(posit16_decode_f32(POS16_ZERO) == 0.0); +} + +test "posit16_decode_f32_nar" { + try std.testing.expect(std.math.isNan(posit16_decode_f32(POS16_NAR)) == true); +} + +test "posit16_add_simple" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(20.0)); + try std.testing.expect(result = posit16_add(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 30.0) < 5.0); +} + +test "posit16_sub_simple" { + given a = posit16_encode_f32(50.0) + try std.testing.expect(b = posit16_encode_f32(30.0)); + try std.testing.expect(result = posit16_sub(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "posit16_mul_simple" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(10.0)); + try std.testing.expect(result = posit16_mul(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 20.0); +} + +test "posit16_div_simple" { + given a = posit16_encode_f32(100.0) + try std.testing.expect(b = posit16_encode_f32(10.0)); + try std.testing.expect(result = posit16_div(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "posit16_sqrt_four" { + given value = posit16_encode_f32(4.0) + try std.testing.expect(result = posit16_sqrt(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.3); +} + +test "posit16_sqrt_negative" { + given value = posit16_encode_f32(-4.0) + try std.testing.expect(result = posit16_sqrt(value)); + try std.testing.expect(posit16_is_nar(result) == true); +} + +test "posit16_abs_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(result = posit16_abs(value)); + try std.testing.expect(posit16_is_equal(result, value) == true); +} + +test "posit16_abs_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(abs_val = posit16_abs(value)); + try std.testing.expect(posit16_extract_sign(abs_val) == false); +} + +test "posit16_neg_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(neg_val = posit16_neg(value)); + try std.testing.expect(posit16_extract_sign(neg_val) == true); +} + +test "posit16_neg_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(neg_val = posit16_neg(value)); + try std.testing.expect(posit16_extract_sign(neg_val) == false); +} + +test "posit16_is_equal_true" { + given value = posit16_encode_f32(10.0) + try std.testing.expect(posit16_is_equal(value, value) == true); +} + +test "posit16_is_equal_false" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(20.0)); + try std.testing.expect(posit16_is_equal(a, b) == false); +} + +test "posit16_is_equal_nar" { + try std.testing.expect(posit16_is_equal(POS16_NAR, POS16_NAR) == false); +} + +test "posit16_is_greater_positive" { + given a = posit16_encode_f32(50.0) + try std.testing.expect(b = posit16_encode_f32(30.0)); + try std.testing.expect(posit16_is_greater(a, b) == true); +} + +test "posit16_max_returns_larger" { + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_max(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded >= 35.0); +} + +test "posit16_min_returns_smaller" { + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_min(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded <= 45.0); +} + +test "posit16_relu_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(result = posit16_relu(value)); + try std.testing.expect(posit16_is_equal(result, value) == true); +} + +test "posit16_relu_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(result = posit16_relu(value)); + try std.testing.expect(result == POS16_ZERO); +} + +test "posit16_sigmoid_zero" { + given value = POS16_ZERO + try std.testing.expect(result = posit16_sigmoid(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "posit16_sigmoid_positive_large" { + given value = posit16_encode_f32(10.0) + try std.testing.expect(result = posit16_sigmoid(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "posit16_quantize_to_int4_max" { + given value = posit16_encode_f32(100.0) + try std.testing.expect(result = posit16_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "posit16_quantize_to_int4_min" { + given value = posit16_encode_f32(-100.0) + try std.testing.expect(result = posit16_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "posit16_from_f32_scaled_up" { + given value = 1.0 + try std.testing.expect(scale = 100.0); + try std.testing.expect(result = posit16_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 20.0); +} + +test "posit16_dot_product_simple" { + given a = [_]Posit16{posit16_encode_f32(1.0), posit16_encode_f32(2.0), posit16_encode_f32(3.0), posit16_encode_f32(4.0)} + try std.testing.expect(b = [_]Posit16{posit16_encode_f32(2.0), posit16_encode_f32(2.0), posit16_encode_f32(2.0), posit16_encode_f32(2.0)}); + try std.testing.expect(result = posit16_dot_product(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "posit16_vector_sum_simple" { + given vec = [_]Posit16{posit16_encode_f32(1.0), posit16_encode_f32(2.0), posit16_encode_f32(3.0), posit16_encode_f32(4.0)} + try std.testing.expect(result = posit16_vector_sum(vec)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "posit16_vector_mean_simple" { + given vec = [_]Posit16{posit16_encode_f32(2.0), posit16_encode_f32(4.0), posit16_encode_f32(6.0), posit16_encode_f32(8.0)} + try std.testing.expect(result = posit16_vector_mean(vec)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 5.0) < 1.0); +} + +test "posit16_lerp_zero" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = posit16_lerp(a, b, t)); + try std.testing.expect(posit16_is_equal(result, a) == true); +} + +test "posit16_lerp_one" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = posit16_lerp(a, b, t)); + try std.testing.expect(posit16_is_equal(result, b) == true); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant posit16_bits_constant + assert BITS == 16 + +invariant posit16_es_one + assert ES == 1 + +invariant posit16_nbits_fifteen + assert NBITS == 15 + +invariant posit16_zero_no_sign + assert (POS16_ZERO & SIGN_MASK) == 0 + +invariant posit16_nar_has_sign + assert (POS16_NAR & SIGN_MASK) != 0 + +invariant posit16_zero_nar_distinct + assert POS16_ZERO != POS16_NAR + +invariant posit16_abs_removes_sign + assert posit16_abs(POS16_NAR) != POS16_NAR // NaR preserves sign pattern + +invariant posit16_neg_zero_is_zero + assert posit16_neg(POS16_ZERO) == POS16_ZERO + +invariant posit16_neg_nar_is_nar + assert posit16_neg(POS16_NAR) == POS16_NAR + +invariant posit16_add_zero_identity + given value = posit16_encode_f32(50.0) + assert posit16_is_equal(posit16_add(value, POS16_ZERO), value) == true + +invariant posit16_sub_zero_identity + given value = posit16_encode_f32(50.0) + assert posit16_is_equal(posit16_sub(value, POS16_ZERO), value) == true + +invariant posit16_mul_zero_zero + given value = posit16_encode_f32(50.0) + assert posit16_mul(value, POS16_ZERO) == POS16_ZERO + +invariant posit16_div_nar_propagates + given value = posit16_encode_f32(50.0) + assert posit16_is_nar(posit16_div(POS16_NAR, value)) == true + +invariant posit16_relu_non_negative + given result = posit16_relu(posit16_encode_f32(50.0)) + try std.testing.expect(posit16_extract_sign(result) == false); + +invariant posit16_sqrt_non_negative + given result = posit16_sqrt(posit16_encode_f32(4.0)) + try std.testing.expect(posit16_extract_sign(result) == false); + +invariant posit16_quantize_to_int4_in_range + given result = posit16_quantize_to_int4(posit16_encode_f32(50.0)) + assert result >= -8 and result <= 7 + +invariant posit16_scale_roundtrip + given value = posit16_encode_f32(50.0) + try std.testing.expect(scale = 2.0); + try std.testing.expect(scaled = posit16_scale(value, scale)); + try std.testing.expect(decoded = posit16_to_f32_scaled(scaled, scale)); + try std.testing.expect(abs(decoded - posit16_decode_f32(value)) < 5.0); + +invariant posit16_max_ge_both + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_max(a, b)); + try std.testing.expect(posit16_is_greater(result, a) == true or posit16_is_equal(result, a) == true); + +invariant posit16_min_le_both + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_min(a, b)); + try std.testing.expect(posit16_is_greater(b, result) == true or posit16_is_equal(b, result) == true); + +invariant posit16_lerp_bounded + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result_half = posit16_lerp(a, b, 0.5)); + try std.testing.expect(decoded_a = posit16_decode_f32(a)); + try std.testing.expect(decoded_b = posit16_decode_f32(b)); + try std.testing.expect(decoded_half = posit16_decode_f32(result_half)); + try std.testing.expect(decoded_half > decoded_a and decoded_half < decoded_b); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench posit16_encode_f32_latency + measure: nanoseconds to posit16_encode_f32(10.5) + target: < 50ns + +bench posit16_decode_f32_latency + measure: nanoseconds to posit16_decode_f32(0x4000) + target: < 50ns + +bench posit16_add_latency + measure: nanoseconds to posit16_add(0x4000, 0x4800) + target: < 100ns + +bench posit16_mul_latency + measure: nanoseconds to posit16_mul(0x4000, 0x4000) + target: < 100ns + +bench posit16_sqrt_latency + measure: nanoseconds to posit16_sqrt(0x4000) + target: < 100ns + +bench posit16_sigmoid_latency + measure: nanoseconds to posit16_sigmoid(0x4000) + target: < 200ns + +bench posit16_relu_latency + measure: nanoseconds to posit16_relu(0x4800) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/purkinje_thermal_gate.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/purkinje_thermal_gate.t27 new file mode 100644 index 0000000000..1c814329e1 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/purkinje_thermal_gate.t27 @@ -0,0 +1,704 @@ +// SPDX-License-Identifier: Apache-2.0 +; purkinje_thermal_gate.t27 — Purkinje Thermal Gate +; Thermally-gated activation inspired by Purkinje neural dynamics +; φ² + 1/φ² = 3 | TRINITY + +module purkinje-thermal-gate; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const PTG_OPCODE : u8 = 0xF4; // Purkinje Thermal Gate opcode + +pub const TEMP_MIN_C : i8 = -40; // -40°C minimum +pub const TEMP_MAX_C : i8 = 125; // 125°C maximum +pub const TEMP_NOMINAL_C : i8 = 25; // 25°C nominal + +pub const TEMP_THRESHOLD_LOW_C : i8 = 0; // 0°C low threshold +pub const TEMP_THRESHOLD_HIGH_C : i8 = 85; // 85°C high threshold +pub const TEMP_CRITICAL_C : i8 = 100; // 100°C critical + +pub const TEMP_HYSTERESIS_C : i8 = 5; // 5°C hysteresis +pub const TEMP_SETTLING_MS : u16 = 100; // 100ms settling time + +pub const GATE_STATE_CLOSED : u8 = 0; +pub const GATE_STATE_OPEN : u8 = 1; +pub const GATE_STATE_THROTTLING : u8 = 2; +pub const GATE_STATE_ERROR : u8 = 3; + +pub const THROTTLE_LEVEL_NONE : u8 = 0; +pub const THROTTLE_LEVEL_LOW : u8 = 1; +pub const THROTTLE_LEVEL_MED : u8 = 2; +pub const THROTTLE_LEVEL_HIGH : u8 = 3; +pub const THROTTLE_LEVEL_MAX : u8 = 4; + +// ============================================================================ +// Types +// ============================================================================ + +pub const GateState = enum(u8) { + closed = GATE_STATE_CLOSED, + open = GATE_STATE_OPEN, + throttling = GATE_STATE_THROTTLING, + error = GATE_STATE_ERROR, +} + +pub const ThrottleLevel = enum(u8) { + none = THROTTLE_LEVEL_NONE, + low = THROTTLE_LEVEL_LOW, + medium = THROTTLE_LEVEL_MED, + high = THROTTLE_LEVEL_HIGH, +} + +pub const ThermalState = struct { + temp_c : i8, + gate_state : GateState, + throttle_level : ThrottleLevel, + error_code : u8, +} + +pub const GateConfig = struct { + low_threshold_c : i8, + high_threshold_c : i8, + critical_temp_c : i8, + hysteresis_c : i8, + auto_throttle : bool, +} + +pub const GateStatus = struct { + current_state : GateState, + temp_measured_c : i8, + throttle_level : ThrottleLevel, + cycle_count : u32, + fault_count : u8, +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// ptg_config_init() -> GateConfig +// Initialize Purkinje thermal gate config +pub fn ptg_config_init() -> GateConfig { + return GateConfig { + .low_threshold_c = TEMP_THRESHOLD_LOW_C, + .high_threshold_c = TEMP_THRESHOLD_HIGH_C, + .critical_temp_c = TEMP_CRITICAL_C, + .hysteresis_c = TEMP_HYSTERESIS_C, + .auto_throttle = true, + }; +} + +// ptg_config_strict() -> GateConfig +// Create strict thermal gate config (lower thresholds) +pub fn ptg_config_strict() -> GateConfig { + return GateConfig { + .low_threshold_c = 0, + .high_threshold_c = 70, + .critical_temp_c = 90, + .hysteresis_c = 3, + .auto_throttle = true, + }; +} + +// ptg_config_permissive() -> GateConfig +// Create permissive thermal gate config (higher thresholds) +pub fn ptg_config_permissive() -> GateConfig { + return GateConfig { + .low_threshold_c = -20, + .high_threshold_c = 100, + .critical_temp_c = 115, + .hysteresis_c = 10, + .auto_throttle = true, + }; +} + +// ptg_config_manual() -> GateConfig +// Create manual config (no auto throttle) +pub fn ptg_config_manual() -> GateConfig { + return GateConfig { + .low_threshold_c = TEMP_THRESHOLD_LOW_C, + .high_threshold_c = TEMP_THRESHOLD_HIGH_C, + .critical_temp_c = TEMP_CRITICAL_C, + .hysteresis_c = TEMP_HYSTERESIS_C, + .auto_throttle = false, + }; +} + +// ============================================================================ +// Temperature Validation +// ============================================================================ + +// ptg_temp_valid(temp_c: i8) -> bool +// Check if temperature is valid +pub fn ptg_temp_valid(temp_c: i8) -> bool { + return temp_c >= TEMP_MIN_C and temp_c <= TEMP_MAX_C; +} + +// ptg_temp_critical(temp_c: i8, config: GateConfig) -> bool +// Check if temperature is critical +pub fn ptg_temp_critical(temp_c: i8, config: GateConfig) -> bool { + return temp_c >= config.critical_temp_c; +} + +// ptg_temp_high(temp_c: i8, config: GateConfig) -> bool +// Check if temperature is high (above high threshold) +pub fn ptg_temp_high(temp_c: i8, config: GateConfig) -> bool { + return temp_c >= config.high_threshold_c; +} + +// ptg_temp_low(temp_c: i8, config: GateConfig) -> bool +// Check if temperature is low (below low threshold) +pub fn ptg_temp_low(temp_c: i8, config: GateConfig) -> bool { + return temp_c < config.low_threshold_c; +} + +// ptg_temp_in_hysteresis(temp_c: i8, threshold: i8, hysteresis: i8) -> bool +// Check if temperature is within hysteresis band +pub fn ptg_temp_in_hysteresis(temp_c: i8, threshold: i8, hysteresis: i8) -> bool { + if (temp_c >= threshold) { + return (temp_c - threshold) <= hysteresis; + } else { + return (threshold - temp_c) <= hysteresis; + } +} + +// ============================================================================ +// Gate State Functions +// ============================================================================ + +// ptg_thermal_state_init() -> ThermalState +// Initialize thermal state +pub fn ptg_thermal_state_init() -> ThermalState { + return ThermalState { + .temp_c = TEMP_NOMINAL_C, + .gate_state = GateState.open, + .throttle_level = ThrottleLevel.none, + .error_code = 0, + }; +} + +// ptg_gate_state_from_temp(temp_c: i8, config: GateConfig) -> GateState +// Determine gate state from temperature +pub fn ptg_gate_state_from_temp(temp_c: i8, config: GateConfig) -> GateState { + if (not ptg_temp_valid(temp_c)) { + return GateState.error; + } else if (ptg_temp_critical(temp_c, config)) { + return GateState.error; + } else if (ptg_temp_high(temp_c, config)) { + return GateState.throttling; + } else if (ptg_temp_low(temp_c, config)) { + return GateState.closed; + } else { + return GateState.open; + } +} + +// ptg_throttle_from_temp(temp_c: i8, config: GateConfig) -> ThrottleLevel +// Determine throttle level from temperature +pub fn ptg_throttle_from_temp(temp_c: i8, config: GateConfig) -> ThrottleLevel { + if (ptg_temp_critical(temp_c, config)) { + return ThrottleLevel.high; + } else if (temp_c >= config.high_threshold_c + 20) { + return ThrottleLevel.high; + } else if (temp_c >= config.high_threshold_c + 10) { + return ThrottleLevel.medium; + } else if (temp_c >= config.high_threshold_c) { + return ThrottleLevel.low; + } else { + return ThrottleLevel.none; + } +} + +// ptg_thermal_state_update(thermal: ThermalState, temp_c: i8, config: GateConfig) -> ThermalState +// Update thermal state with new temperature +pub fn ptg_thermal_state_update(thermal: ThermalState, temp_c: i8, config: GateConfig) -> ThermalState { + var new_state = thermal; + new_state.temp_c = temp_c; + new_state.gate_state = ptg_gate_state_from_temp(temp_c, config); + + if (config.auto_throttle) { + new_state.throttle_level = ptg_throttle_from_temp(temp_c, config); + } + + if (new_state.gate_state == GateState.error) { + new_state.error_code = 1; // Thermal error + } else if (new_state.gate_state != GateState.error) { + new_state.error_code = 0; + } + + return new_state; +} + +// ============================================================================ +// Gate Status Functions +// ============================================================================ + +// ptg_gate_status_init() -> GateStatus +// Initialize gate status +pub fn ptg_gate_status_init() -> GateStatus { + return GateStatus { + .current_state = GateState.open, + .temp_measured_c = TEMP_NOMINAL_C, + .throttle_level = ThrottleLevel.none, + .cycle_count = 0, + .fault_count = 0, + }; +} + +// ptg_gate_status_open(status: GateStatus) -> GateStatus +// Open the gate +pub fn ptg_gate_status_open(status: GateStatus) -> GateStatus { + return GateStatus { + .current_state = GateState.open, + .temp_measured_c = status.temp_measured_c, + .throttle_level = ThrottleLevel.none, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count, + }; +} + +// ptg_gate_status_close(status: GateStatus) -> GateStatus +// Close the gate +pub fn ptg_gate_status_close(status: GateStatus) -> GateStatus { + return GateStatus { + .current_state = GateState.closed, + .temp_measured_c = status.temp_measured_c, + .throttle_level = ThrottleLevel.high, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count, + }; +} + +// ptg_gate_status_throttle(status: GateStatus, level: ThrottleLevel) -> GateStatus +// Set gate to throttling state +pub fn ptg_gate_status_throttle(status: GateStatus, level: ThrottleLevel) -> GateStatus { + return GateStatus { + .current_state = GateState.throttling, + .temp_measured_c = status.temp_measured_c, + .throttle_level = level, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count, + }; +} + +// ptg_gate_status_error(status: GateStatus) -> GateStatus +// Set gate to error state +pub fn ptg_gate_status_error(status: GateStatus) -> GateStatus { + return GateStatus { + .current_state = GateState.error, + .temp_measured_c = status.temp_measured_c, + .throttle_level = ThrottleLevel.high, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count + 1, + }; +} + +// ptg_gate_status_update_temp(status: GateStatus, temp_c: i8) -> GateStatus +// Update measured temperature +pub fn ptg_gate_status_update_temp(status: GateStatus, temp_c: i8) -> GateStatus { + return GateStatus { + .current_state = status.current_state, + .temp_measured_c = temp_c, + .throttle_level = status.throttle_level, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count, + }; +} + +// ptg_gate_status_increment_cycle(status: GateStatus) -> GateStatus +// Increment cycle counter +pub fn ptg_gate_status_increment_cycle(status: GateStatus) -> GateStatus { + return GateStatus { + .current_state = status.current_state, + .temp_measured_c = status.temp_measured_c, + .throttle_level = status.throttle_level, + .cycle_count = status.cycle_count + 1, + .fault_count = status.fault_count, + }; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_ptg_cmd(opcode: u8, temp_c: i8, threshold: u8) -> u16 +// Encode Purkinje thermal gate command +pub fn encode_ptg_cmd(opcode: u8, temp_c: i8, threshold: u8) u16 { + // Format: [OP:8][TEMP_SIGNED:7][THRESHOLD:1] + const op_field : u16 = @as(u16, opcode) << 8; + const temp_field : u16 = @as(u16, @as(u8, @bitCast(temp_c))) << 1; + const thresh_field : u16 = @as(u16, threshold & 0x01); + return op_field | temp_field | thresh_field; +} + +// decode_ptg_cmd(encoded: u16) -> struct { opcode: u8, temp_c: i8, threshold: u8 } +// Decode Purkinje thermal gate command +pub fn decode_ptg_cmd(encoded: u16) -> struct { opcode: u8, temp_c: i8, threshold: u8 } { + const opcode : u8 = @as(u8, @truncate((encoded >> 8) & 0xFF)); + const temp_u8 : u8 = @as(u8, @truncate((encoded >> 1) & 0x7F)); + const temp_c : i8 = @bitCast(temp_u8); + const threshold : u8 = @as(u8, @truncate(encoded & 0x01)); + return .{ .opcode = opcode, .temp_c = temp_c, .threshold = threshold }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "ptg_opcode_constant" { + try std.testing.expect(PTG_OPCODE == 0xF4); +} + +test "temp_constants" { + try std.testing.expect(TEMP_MIN_C == -40); + try std.testing.expect(TEMP_MAX_C == 125); + try std.testing.expect(TEMP_NOMINAL_C == 25); +} + +test "temp_threshold_constants" { + try std.testing.expect(TEMP_THRESHOLD_LOW_C == 0); + try std.testing.expect(TEMP_THRESHOLD_HIGH_C == 85); + try std.testing.expect(TEMP_CRITICAL_C == 100); +} + +test "temp_hysteresis_constant" { + try std.testing.expect(TEMP_HYSTERESIS_C == 5); +} + +test "gate_state_constants" { + try std.testing.expect(GATE_STATE_CLOSED == 0); + try std.testing.expect(GATE_STATE_OPEN == 1); + try std.testing.expect(GATE_STATE_THROTTLING == 2); + try std.testing.expect(GATE_STATE_ERROR == 3); +} + +test "throttle_level_constants" { + try std.testing.expect(THROTTLE_LEVEL_NONE == 0); + try std.testing.expect(THROTTLE_LEVEL_LOW == 1); + try std.testing.expect(THROTTLE_LEVEL_MED == 2); + try std.testing.expect(THROTTLE_LEVEL_HIGH == 3); +} + +test "ptg_config_init_structure" { + given config = ptg_config_init() + try std.testing.expect(config.low_threshold_c == TEMP_THRESHOLD_LOW_C); + try std.testing.expect(config.high_threshold_c == TEMP_THRESHOLD_HIGH_C); + try std.testing.expect(config.auto_throttle == true); +} + +test "ptg_config_strict_structure" { + given config = ptg_config_strict() + try std.testing.expect(config.high_threshold_c == 70); + try std.testing.expect(config.critical_temp_c == 90); +} + +test "ptg_config_permissive_structure" { + given config = ptg_config_permissive() + try std.testing.expect(config.high_threshold_c == 100); + try std.testing.expect(config.critical_temp_c == 115); +} + +test "ptg_config_manual_structure" { + given config = ptg_config_manual() + try std.testing.expect(config.auto_throttle == false); +} + +test "ptg_temp_valid_true" { + try std.testing.expect(ptg_temp_valid(0) == true); + try std.testing.expect(ptg_temp_valid(25) == true); + try std.testing.expect(ptg_temp_valid(100) == true); +} + +test "ptg_temp_valid_false" { + try std.testing.expect(ptg_temp_valid(-50) == false); + try std.testing.expect(ptg_temp_valid(150) == false); +} + +test "ptg_temp_critical_true" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_critical(105, config) == true); + try std.testing.expect(ptg_temp_critical(100, config) == true); +} + +test "ptg_temp_critical_false" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_critical(90, config) == false); +} + +test "ptg_temp_high_true" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_high(90, config) == true); + try std.testing.expect(ptg_temp_high(85, config) == true); +} + +test "ptg_temp_high_false" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_high(80, config) == false); +} + +test "ptg_temp_low_true" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_low(-10, config) == true); + try std.testing.expect(ptg_temp_low(-1, config) == true); +} + +test "ptg_temp_low_false" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_low(0, config) == false); + try std.testing.expect(ptg_temp_low(10, config) == false); +} + +test "ptg_temp_in_hysteresis_true" { + try std.testing.expect(ptg_temp_in_hysteresis(85, 85, 5) == true); + try std.testing.expect(ptg_temp_in_hysteresis(87, 85, 5) == true); + try std.testing.expect(ptg_temp_in_hysteresis(83, 85, 5) == true); +} + +test "ptg_temp_in_hysteresis_false" { + try std.testing.expect(ptg_temp_in_hysteresis(95, 85, 5) == false); + try std.testing.expect(ptg_temp_in_hysteresis(75, 85, 5) == false); +} + +test "ptg_thermal_state_init_structure" { + given thermal = ptg_thermal_state_init() + try std.testing.expect(thermal.temp_c == TEMP_NOMINAL_C); + try std.testing.expect(thermal.gate_state == GateState.open); +} + +test "ptg_gate_state_from_temp_open" { + given config = ptg_config_init() + try std.testing.expect(ptg_gate_state_from_temp(50, config) == GateState.open); +} + +test "ptg_gate_state_from_temp_throttling" { + given config = ptg_config_init() + try std.testing.expect(ptg_gate_state_from_temp(90, config) == GateState.throttling); +} + +test "ptg_gate_state_from_temp_closed" { + given config = ptg_config_init() + try std.testing.expect(ptg_gate_state_from_temp(-10, config) == GateState.closed); +} + +test "ptg_gate_state_from_temp_error" { + given config = ptg_config_init() + try std.testing.expect(ptg_gate_state_from_temp(105, config) == GateState.error); +} + +test "ptg_throttle_from_temp_none" { + given config = ptg_config_init() + try std.testing.expect(ptg_throttle_from_temp(50, config) == ThrottleLevel.none); +} + +test "ptg_throttle_from_temp_low" { + given config = ptg_config_init() + try std.testing.expect(ptg_throttle_from_temp(90, config) == ThrottleLevel.low); +} + +test "ptg_throttle_from_temp_medium" { + given config = ptg_config_init() + try std.testing.expect(ptg_throttle_from_temp(100, config) == ThrottleLevel.medium); +} + +test "ptg_throttle_from_temp_high" { + given config = ptg_config_init() + try std.testing.expect(ptg_throttle_from_temp(110, config) == ThrottleLevel.high); +} + +test "ptg_thermal_state_update" { + given thermal = ptg_thermal_state_init() + try std.testing.expect(config = ptg_config_init()); + try std.testing.expect(result = ptg_thermal_state_update(thermal, 90, config)); + try std.testing.expect(result.temp_c == 90); + try std.testing.expect(result.gate_state == GateState.throttling); +} + +test "ptg_gate_status_init_structure" { + given status = ptg_gate_status_init() + try std.testing.expect(status.current_state == GateState.open); + try std.testing.expect(status.fault_count == 0); +} + +test "ptg_gate_status_open" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_open(status)); + try std.testing.expect(result.current_state == GateState.open); + try std.testing.expect(result.throttle_level == ThrottleLevel.none); +} + +test "ptg_gate_status_close" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_close(status)); + try std.testing.expect(result.current_state == GateState.closed); + try std.testing.expect(result.throttle_level == ThrottleLevel.high); +} + +test "ptg_gate_status_throttle" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_throttle(status, ThrottleLevel.medium)); + try std.testing.expect(result.current_state == GateState.throttling); + try std.testing.expect(result.throttle_level == ThrottleLevel.medium); +} + +test "ptg_gate_status_error" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_error(status)); + try std.testing.expect(result.current_state == GateState.error); + try std.testing.expect(result.fault_count == 1); +} + +test "ptg_gate_status_update_temp" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_update_temp(status, 75)); + try std.testing.expect(result.temp_measured_c == 75); +} + +test "ptg_gate_status_increment_cycle" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_increment_cycle(status)); + try std.testing.expect(result.cycle_count == 1); +} + +test "encode_ptg_cmd" { + given encoded = encode_ptg_cmd(0x10, 25, 1) + try std.testing.expect((encoded >> 8) == 0x10); +} + +test "decode_ptg_cmd" { + given decoded = decode_ptg_cmd(0x1033) + try std.testing.expect(decoded.opcode == 0x10); + try std.testing.expect(decoded.temp_c == 25); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant temp_range_valid + assert TEMP_MIN_C < TEMP_MAX_C + try std.testing.expect(TEMP_NOMINAL_C > TEMP_MIN_C); + try std.testing.expect(TEMP_NOMINAL_C < TEMP_MAX_C); + +invariant temp_thresholds_ordered + assert TEMP_THRESHOLD_LOW_C < TEMP_THRESHOLD_HIGH_C + try std.testing.expect(TEMP_THRESHOLD_HIGH_C < TEMP_CRITICAL_C); + +invariant temp_hysteresis_positive + assert TEMP_HYSTERESIS_C > 0 + +invariant ptg_config_init_valid + given config = ptg_config_init() + assert config.low_threshold_c < config.high_threshold_c + try std.testing.expect(config.high_threshold_c < config.critical_temp_c); + +invariant ptg_config_strict_lower_thresholds + given strict = ptg_config_strict() + try std.testing.expect(normal = ptg_config_init()); + assert strict.high_threshold_c < normal.high_threshold_c + try std.testing.expect(strict.critical_temp_c < normal.critical_temp_c); + +invariant ptg_config_permissive_higher_thresholds + given perm = ptg_config_permissive() + try std.testing.expect(normal = ptg_config_init()); + assert perm.high_threshold_c > normal.high_threshold_c + try std.testing.expect(perm.critical_temp_c > normal.critical_temp_c); + +invariant ptg_config_manual_no_auto_throttle + given config = ptg_config_manual() + assert config.auto_throttle == false + +invariant ptg_thermal_state_init_open + given thermal = ptg_thermal_state_init() + try std.testing.expect(thermal.gate_state == GateState.open); + +invariant ptg_gate_state_from_temp_nominal_open + given config = ptg_config_init() + try std.testing.expect(state = ptg_gate_state_from_temp(TEMP_NOMINAL_C, config)); + try std.testing.expect(state == GateState.open); + +invariant ptg_throttle_from_temp_nominal_none + given config = ptg_config_init() + try std.testing.expect(level = ptg_throttle_from_temp(TEMP_NOMINAL_C, config)); + try std.testing.expect(level == ThrottleLevel.none); + +invariant ptg_gate_status_init_open + given status = ptg_gate_status_init() + try std.testing.expect(status.current_state == GateState.open); + +invariant ptg_gate_status_error_increments_fault + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_error(status)); + try std.testing.expect(result.fault_count == 1); + +invariant ptg_gate_status_increment_cycle_increments + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_increment_cycle(status)); + try std.testing.expect(result.cycle_count == 1); + +invariant ptg_gate_status_close_high_throttle + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_close(status)); + try std.testing.expect(result.throttle_level == ThrottleLevel.high); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench ptg_config_init_latency + measure: nanoseconds to ptg_config_init() + target: < 20ns + +bench ptg_temp_valid_latency + measure: nanoseconds to ptg_temp_valid(25) + target: < 10ns + +bench ptg_temp_critical_latency + measure: nanoseconds to ptg_temp_critical(100, ptg_config_init()) + target: < 15ns + +bench ptg_temp_high_latency + measure: nanoseconds to ptg_temp_high(90, ptg_config_init()) + target: < 15ns + +bench ptg_temp_low_latency + measure: nanoseconds to ptg_temp_low(-10, ptg_config_init()) + target: < 15ns + +bench ptg_temp_in_hysteresis_latency + measure: nanoseconds to ptg_temp_in_hysteresis(87, 85, 5) + target: < 15ns + +bench ptg_thermal_state_init_latency + measure: nanoseconds to ptg_thermal_state_init() + target: < 20ns + +bench ptg_gate_state_from_temp_latency + measure: nanoseconds to ptg_gate_state_from_temp(50, ptg_config_init()) + target: < 20ns + +bench ptg_throttle_from_temp_latency + measure: nanoseconds to ptg_throttle_from_temp(90, ptg_config_init()) + target: < 20ns + +bench ptg_thermal_state_update_latency + measure: nanoseconds to ptg_thermal_state_update(ptg_thermal_state_init(), 90, ptg_config_init()) + target: < 30ns + +bench ptg_gate_status_init_latency + measure: nanoseconds to ptg_gate_status_init() + target: < 20ns + +bench ptg_gate_status_open_latency + measure: nanoseconds to ptg_gate_status_open(ptg_gate_status_init()) + target: < 15ns + +bench encode_ptg_cmd_latency + measure: nanoseconds to encode_ptg_cmd(0x10, 25, 1) + target: < 20ns + +bench decode_ptg_cmd_latency + measure: nanoseconds to decode_ptg_cmd(0x1033) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/sparse_mask.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/sparse_mask.t27 new file mode 100644 index 0000000000..49c8fb1357 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/sparse_mask.t27 @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: Apache-2.0 +; sparse_mask.t27 — Sacred Opcode 0xE8: Sparse Mask (Sparse Skip 2) +; Hardware for generating and applying sparse tensor masks +; φ² + 1/φ² = 3 | TRINITY + +module sacred-sparse_mask; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_SPARSE_MASK : u8 = 0xE8; + +pub const MASK_SIZE : u8 = 16; +pub const SPARSE_THRESHOLD_RATIO : u8 = 50; // 50% sparsity threshold + +pub const MASK_MODE_GEN : u8 = 0; +pub const MASK_MODE_APPLY : u8 = 1; +pub const MASK_MODE_COMBINE : u8 = 2; +pub const MASK_MODE_INVERT : u8 = 3; + +pub const MASK_TYPE_DENSITY : u8 = 0; +pub const MASK_TYPE_PATTERN : u8 = 1; +pub const MASK_TYPE_RANDOM : u8 = 2; +pub const MASK_TYPE_PHI : u8 = 3; // φ-optimized mask + +// ============================================================================ +// Types +// ============================================================================ + +pub const SparseMask = struct { + bits : u16, + density : u8, // 0-100%, percentage of set bits + pattern_type : u8, +} + +pub const MaskConfig = struct { + mode : u8, + mask_type : u8, + threshold : u8, + phi_optimized : bool, +} + +pub const MaskOp = struct { + result : SparseMask, + input_mask : u16, + output_mask : u16, + operation_count : u8, +} + +pub const Vector16 = struct { + values : [MASK_SIZE]i8, + mask : u16, +} + +// ============================================================================ +// Mask Generation Functions +// ============================================================================ + +// mask_count_set_bits(mask: u16) -> u8 +// Count number of set bits in mask +pub fn mask_count_set_bits(mask: u16) -> u8 { + var count : u8 = 0; + var m = mask; + while (m != 0) { + count += 1; + m &= m - 1; + } + return count; +} + +// mask_count_clear_bits(mask: u16) -> u8 +// Count number of clear bits in mask +pub fn mask_count_clear_bits(mask: u16) -> u8 { + return MASK_SIZE - mask_count_set_bits(mask); +} + +// mask_density(mask: u16) -> u8 +// Calculate mask density (0-100%) +pub fn mask_density(mask: u16) -> u8 { + const set_bits = mask_count_set_bits(mask); + return (set_bits * 100) / MASK_SIZE; +} + +// mask_is_sparse(mask: u16) -> bool +// Check if mask is sparse (< threshold) +pub fn mask_is_sparse(mask: u16) -> bool { + return mask_density(mask) < SPARSE_THRESHOLD_RATIO; +} + +// mask_is_dense(mask: u16) -> bool +// Check if mask is dense (>= threshold) +pub fn mask_is_dense(mask: u16) -> bool { + return mask_density(mask) >= SPARSE_THRESHOLD_RATIO; +} + +// mask_generate_density(density: u8) -> u16 +// Generate mask with specific density (0-100%) +pub fn mask_generate_density(density: u8) -> u16 { + const target_bits = (density * MASK_SIZE) / 100; + var mask : u16 = 0; + var i : u8 = 0; + + // Set first target_bits bits + while (i < target_bits) { + mask |= (@as(u16, 1) << i); + i += 1; + } + + return mask; +} + +// mask_generate_phi() -> u16 +// Generate φ-optimized mask (approximate golden ratio distribution) +pub fn mask_generate_phi() -> u16 { + // φ ≈ 1.618, use pattern based on Fibonacci sequence + // Set bits at positions: 0, 1, 2, 3, 5, 8, 13 (first few Fib numbers) + var mask : u16 = 0; + mask |= (@as(u16, 1) << 0); + mask |= (@as(u16, 1) << 1); + mask |= (@as(u16, 1) << 2); + mask |= (@as(u16, 1) << 3); + mask |= (@as(u16, 1) << 5); + mask |= (@as(u16, 1) << 8); + mask |= (@as(u16, 1) << 13); + return mask; +} + +// mask_generate_pattern(pattern: u8) -> u16 +// Generate mask with repeating pattern +pub fn mask_generate_pattern(pattern: u8) -> u16 { + var mask : u16 = 0; + const pattern_bits : u8 = 4; + + for (0..MASK_SIZE) |i| { + if ((pattern >> (i % pattern_bits)) & 1 != 0) { + mask |= (@as(u16, 1) << i); + } + } + + return mask; +} + +// mask_generate_random(seed: u16) -> u16 +// Generate pseudo-random mask using LFSR +pub fn mask_generate_random(seed: u16) -> u16 { + var lfsr = seed; + var mask : u16 = 0; + + for (0..MASK_SIZE) |i| { + const bit = (lfsr >> 0) & 1; + if (bit != 0) { + mask |= (@as(u16, 1) << i); + } + // LFSR feedback (X^16 + X^14 + X^13 + X^11) + const feedback = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5)) & 1; + lfsr = (lfsr >> 1) | (feedback << 15); + } + + return mask; +} + +// ============================================================================ +// Mask Operation Functions +// ============================================================================ + +// mask_generate(config: MaskConfig, seed: u16) -> SparseMask +// Generate mask based on config +pub fn mask_generate(config: MaskConfig, seed: u16) -> SparseMask { + var mask : u16 = 0; + var pattern_type : u8 = config.mask_type; + + switch (config.mask_type) { + MASK_TYPE_DENSITY => { + mask = mask_generate_density(config.threshold); + }, + MASK_TYPE_PATTERN => { + mask = mask_generate_pattern(config.threshold); + }, + MASK_TYPE_RANDOM => { + mask = mask_generate_random(seed); + }, + MASK_TYPE_PHI => { + if (config.phi_optimized) { + mask = mask_generate_phi(); + } else { + mask = mask_generate_pattern(0x05); // Default pattern + } + }, + else => { + mask = mask_generate_pattern(0x0F); + }, + } + + return SparseMask { + .bits = mask, + .density = mask_density(mask), + .pattern_type = pattern_type, + }; +} + +// mask_apply(vector: Vector16, mask: SparseMask) -> Vector16 +// Apply mask to vector (zero out masked elements) +pub fn mask_apply(vector: Vector16, mask: SparseMask) -> Vector16 { + var result : Vector16 = undefined; + result.mask = mask.bits; + + for (vector.values, 0..) |val, i| { + if ((mask.bits >> i) & 1 != 0) { + result.values[i] = val; + } else { + result.values[i] = 0; + } + } + + return result; +} + +// mask_combine(mask1: u16, mask2: u16, operation: u8) -> u16 +// Combine two masks +pub fn mask_combine(mask1: u16, mask2: u16, operation: u8) -> u16 { + switch (operation) { + 0 => return mask1 & mask2, // AND + 1 => return mask1 | mask2, // OR + 2 => return mask1 ^ mask2, // XOR + else => return mask1 & ~mask2, // NAND + } +} + +// mask_invert(mask: u16) -> u16 +// Invert mask +pub fn mask_invert(mask: u16) -> u16 { + return ~mask & ((@as(u16, 1) << MASK_SIZE) - 1); +} + +// mask_complement(mask: SparseMask) -> SparseMask +// Get complement of mask (inverse) +pub fn mask_complement(mask: SparseMask) -> SparseMask { + const inverted = mask_invert(mask.bits); + return SparseMask { + .bits = inverted, + .density = mask_density(inverted), + .pattern_type = mask.pattern_type, + }; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_sparse_mask(mode: u8, mask_type: u8, seed: u8) -> u16 +// Encode sparse mask instruction +pub fn encode_sparse_mask(mode: u8, mask_type: u8, seed: u8) u16 { + // Format: [OP:8][MODE:2][TYPE:2][SEED:4] + const op : u16 = @as(u16, OP_SPARSE_MASK) << 8; + const mode_field : u16 = @as(u16, mode & 0x03) << 6; + const type_field : u16 = @as(u16, mask_type & 0x03) << 4; + const seed_field : u16 = @as(u16, seed & 0x0F); + return op | mode_field | type_field | seed_field; +} + +// decode_sparse_mask(encoded: u16) -> struct { mode: u8, mask_type: u8, seed: u8 } +// Decode sparse mask instruction +pub fn decode_sparse_mask(encoded: u16) struct { mode: u8, mask_type: u8, seed: u8 } { + const mode : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const mask_type : u8 = @as(u8, @truncate((encoded >> 4) & 0x03)); + const seed : u8 = @as(u8, @truncate(encoded & 0x0F)); + return .{ .mode = mode, .mask_type = mask_type, .seed = seed }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "mask_size_sixteen" { + try std.testing.expect(MASK_SIZE == 16); +} + +test "sparse_threshold_fifty" { + try std.testing.expect(SPARSE_THRESHOLD_RATIO == 50); +} + +test "mask_mode_gen" { + assert MASK_MODE_GEN == 0 +} + +test "mask_mode_apply" { + assert MASK_MODE_APPLY == 1 +} + +test "mask_type_phi" { + assert MASK_TYPE_PHI == 3 +} + +test "mask_count_set_bits_all" { + try std.testing.expect(mask_count_set_bits(0xFFFF) == 16); +} + +test "mask_count_set_bits_none" { + try std.testing.expect(mask_count_set_bits(0x0000) == 0); +} + +test "mask_count_set_bits_half" { + try std.testing.expect(mask_count_set_bits(0x00FF) == 8); +} + +test "mask_count_clear_bits_all" { + try std.testing.expect(mask_count_clear_bits(0xFFFF) == 0); +} + +test "mask_count_clear_bits_none" { + try std.testing.expect(mask_count_clear_bits(0x0000) == 16); +} + +test "mask_density_zero" { + try std.testing.expect(mask_density(0x0000) == 0); +} + +test "mask_density_fifty" { + try std.testing.expect(mask_density(0x00FF) == 50); +} + +test "mask_density_full" { + try std.testing.expect(mask_density(0xFFFF) == 100); +} + +test "mask_is_sparse_true" { + try std.testing.expect(mask_is_sparse(0x000F) == true); + try std.testing.expect(mask_is_sparse(0x003F) == true); +} + +test "mask_is_sparse_false" { + try std.testing.expect(mask_is_sparse(0x00FF) == false); + try std.testing.expect(mask_is_sparse(0xFFFF) == false); +} + +test "mask_is_dense_true" { + try std.testing.expect(mask_is_dense(0x00FF) == true); + try std.testing.expect(mask_is_dense(0xFFFF) == true); +} + +test "mask_is_dense_false" { + try std.testing.expect(mask_is_dense(0x000F) == false); + try std.testing.expect(mask_is_dense(0x003F) == false); +} + +test "mask_generate_density_zero" { + try std.testing.expect(mask_generate_density(0) == 0); +} + +test "mask_generate_density_full" { + try std.testing.expect(mask_generate_density(100) == 0xFFFF); +} + +test "mask_generate_density_fifty" { + try std.testing.expect(mask_count_set_bits(mask_generate_density(50)) == 8); +} + +test "mask_generate_phi" { + given mask = mask_generate_phi() + try std.testing.expect(mask_count_set_bits(mask) == 7); + try std.testing.expect(mask_density(mask) == 43 // 7/16 ≈ 43%); +} + +test "mask_generate_pattern_alternating" { + try std.testing.expect(mask_generate_pattern(0x05) == 0xAAAA); +} + +test "mask_generate_pattern_ones" { + try std.testing.expect(mask_generate_pattern(0x0F) == 0xFFFF); +} + +test "mask_generate_pattern_zeros" { + try std.testing.expect(mask_generate_pattern(0x00) == 0x0000); +} + +test "mask_generate_random_seeded" { + given mask1 = mask_generate_random(0x1234) + try std.testing.expect(mask2 = mask_generate_random(0x1234)); + try std.testing.expect(mask1 == mask2); +} + +test "mask_generate_random_different" { + given mask1 = mask_generate_random(0x1234) + try std.testing.expect(mask2 = mask_generate_random(0x4321)); + try std.testing.expect(mask1 != mask2); +} + +test "mask_generate_dense" { + given config = MaskConfig{.mode = MASK_MODE_GEN, .mask_type = MASK_TYPE_DENSITY, .threshold = 80, .phi_optimized = false} + try std.testing.expect(mask = mask_generate(config, 0)); + try std.testing.expect(mask.density >= 75); +} + +test "mask_generate_sparse" { + given config = MaskConfig{.mode = MASK_MODE_GEN, .mask_type = MASK_TYPE_DENSITY, .threshold = 20, .phi_optimized = false} + try std.testing.expect(mask = mask_generate(config, 0)); + try std.testing.expect(mask.density <= 30); +} + +test "mask_apply_zero_mask" { + given vector = Vector16{.values = [_]i8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, .mask = 0xFFFF} + try std.testing.expect(mask_obj = SparseMask{.bits = 0x0000, .density = 0, .pattern_type = 0}); + try std.testing.expect(result = mask_apply(vector, mask_obj)); + try std.testing.expect(result.values[0] == 0); + try std.testing.expect(result.values[15] == 0); +} + +test "mask_apply_full_mask" { + given vector = Vector16{.values = [_]i8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, .mask = 0x0000} + try std.testing.expect(mask_obj = SparseMask{.bits = 0xFFFF, .density = 100, .pattern_type = 0}); + try std.testing.expect(result = mask_apply(vector, mask_obj)); + try std.testing.expect(result.values[0] == 1); + try std.testing.expect(result.values[15] == 16); +} + +test "mask_combine_and" { + try std.testing.expect(mask_combine(0xFF00, 0xF0F0, 0) == 0xF000); +} + +test "mask_combine_or" { + try std.testing.expect(mask_combine(0xFF00, 0xF0F0, 1) == 0xFFF0); +} + +test "mask_combine_xor" { + try std.testing.expect(mask_combine(0xFF00, 0xF0F0, 2) == 0x0FF0); +} + +test "mask_combine_nand" { + try std.testing.expect(mask_combine(0xFF00, 0xF0F0, 3) == 0x0FFF); +} + +test "mask_invert_full" { + try std.testing.expect(mask_invert(0xFFFF) == 0); +} + +test "mask_invert_zero" { + try std.testing.expect(mask_invert(0) == 0xFFFF); +} + +test "mask_complement_sparse_to_dense" { + given mask = SparseMask{.bits = 0x000F, .density = 25, .pattern_type = 0} + try std.testing.expect(result = mask_complement(mask)); + try std.testing.expect(result.density == 75); +} + +test "encode_sparse_mask" { + given encoded = encode_sparse_mask(1, 2, 0x0A) + try std.testing.expect((encoded >> 8) == OP_SPARSE_MASK); +} + +test "decode_sparse_mask" { + given decoded = decode_sparse_mask(0xE86A) + try std.testing.expect(decoded.mode == 1); + try std.testing.expect(decoded.mask_type == 2); + try std.testing.expect(decoded.seed == 0x0A); +} + +test "opcode_constant" { + try std.testing.expect(OP_SPARSE_MASK == 0xE8); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant mask_size_sixteen + assert MASK_SIZE == 16 + +invariant sparse_threshold_fifty + assert SPARSE_THRESHOLD_RATIO == 50 + +invariant mask_count_bits_sum + given mask = 0xAA55 + assert mask_count_set_bits(mask) + mask_count_clear_bits(mask) == MASK_SIZE + +invariant mask_density_range + try std.testing.expect(mask_density(0) == 0 and mask_density(0xFFFF) == 100); + +invariant mask_is_sparse_inverse_dense + given mask = 0x0F0F + assert mask_is_sparse(mask) != mask_is_dense(mask) + +invariant mask_is_complement_inverse + given mask = 0x1234 + try std.testing.expect(inverted = mask_invert(mask)); + try std.testing.expect(mask_invert(inverted) == mask); + +invariant mask_complement_density + given mask = SparseMask{.bits = 0x0F0F, .density = 50, .pattern_type = 0} + try std.testing.expect(result = mask_complement(mask)); + try std.testing.expect(result.density + mask.density == 100); + +invariant mask_combine_and_less_than_or + given m1 = 0xAAAA + try std.testing.expect(m2 = 0x5555); + try std.testing.expect(combined_and = mask_combine(m1, m2, 0)); + try std.testing.expect(combined_or = mask_combine(m1, m2, 1)); + try std.testing.expect(combined_and <= m1 and combined_and <= m2); + try std.testing.expect(combined_or >= m1 and combined_or >= m2); + +invariant mask_generate_phi_consistent + given mask1 = mask_generate_phi() + try std.testing.expect(mask2 = mask_generate_phi()); + try std.testing.expect(mask1 == mask2); + +invariant mask_generate_density_range + try std.testing.expect(mask_density(mask_generate_density(0)) == 0); + try std.testing.expect(mask_density(mask_generate_density(100)) == 100); + +invariant mask_apply_preserves_unmasked + given vector = Vector16{.values = [_]i8{1, 2, 3, 4} ** 4, .mask = 0x0000} + try std.testing.expect(mask_obj = SparseMask{.bits = 0x00FF, .density = 50, .pattern_type = 0}); + try std.testing.expect(result = mask_apply(vector, mask_obj)); + try std.testing.expect(result.values[0] == 1); + try std.testing.expect(result.values[7] == 8); + +invariant mask_apply_masks_zero + given vector = Vector16{.values = [_]i8{1, 2, 3, 4} ** 4, .mask = 0x0000} + try std.testing.expect(mask_obj = SparseMask{.bits = 0xFF00, .density = 50, .pattern_type = 0}); + try std.testing.expect(result = mask_apply(vector, mask_obj)); + try std.testing.expect(result.values[0] == 0); + try std.testing.expect(result.values[8] == 0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench mask_count_set_bits_latency + measure: nanoseconds to mask_count_set_bits(0xAAAA) + target: < 30ns + +bench mask_density_latency + measure: nanoseconds to mask_density(0x1234) + target: < 50ns + +bench mask_generate_phi_latency + measure: nanoseconds to mask_generate_phi() + target: < 50ns + +bench mask_generate_random_latency + measure: nanoseconds to mask_generate_random(0x1234) + target: < 100ns + +bench mask_apply_latency + measure: nanoseconds to mask_apply(Vector16{.values = [_]i8{1} ** 16, .mask = 0}, SparseMask{.bits = 0x00FF, .density = 50, .pattern_type = 0}) + target: < 100ns + +bench encode_sparse_mask_latency + measure: nanoseconds to encode_sparse_mask(1, 2, 0x0A) + target: < 20ns + +bench decode_sparse_mask_latency + measure: nanoseconds to decode_sparse_mask(0xE86A) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/sparse_skip.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/sparse_skip.t27 new file mode 100644 index 0000000000..cd5620c6b4 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/sparse_skip.t27 @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: Apache-2.0 +; sparse_skip.t27 — Sacred Opcode 0xE1: Sparse Skip Operation +; Hardware acceleration for sparse tensor operations with zero-skipping +; φ² + 1/φ² = 3 | TRINITY + +module sacred-sparse_skip; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_SPARSE_SKIP : u8 = 0xE1; + +pub const VECTOR_SIZE : u8 = 16; +pub const ZERO_THRESHOLD : u8 = 1; // Values <= this are considered zero +pub const SPARSE_TOLERANCE : u8 = 5; // Percentage tolerance + +pub const SKIP_NONE : u8 = 0; +pub const SKIP_ZERO : u8 = 1; +pub const SKIP_THRESHOLD : u8 = 2; +pub const SKIP_ALL : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const SkipMode = enum(u8) { + none = SKIP_NONE, + zero = SKIP_ZERO, + threshold = SKIP_THRESHOLD, + all = SKIP_ALL, +} + +pub const SparsePattern = struct { + is_sparse : bool, + zero_count : u8, + non_zero_count : u8, + sparsity_ratio : u8, // 0-255, represents 0-100% +} + +pub const SkipResult = struct { + mode : SkipMode, + skipped_count : u8, + processed_count : u8, + mask : u16, // Bitmask of non-zero elements +} + +pub const SparseVector = struct { + values : [VECTOR_SIZE]i8, + mask : u16, + zero_count : u8, +} + +// ============================================================================ +// Zero Detection Functions +// ============================================================================ + +// is_zero(value: i8) -> bool +// Check if value is zero +pub fn is_zero(value: i8) bool { + return value == 0; +} + +// is_below_threshold(value: i8, threshold: i8) -> bool +// Check if value is below threshold +pub fn is_below_threshold(value: i8, threshold: i8) bool { + return if (value >= 0) value < threshold else value > -threshold; +} + +// count_zeros(vector: [VECTOR_SIZE]i8) -> u8 +// Count zeros in a vector +pub fn count_zeros(vector: [VECTOR_SIZE]i8) u8 { + var count : u8 = 0; + for (vector) |val| { + if (is_zero(val)) { + count += 1; + } + } + return count; +} + +// count_non_zeros(vector: [VECTOR_SIZE]i8) -> u8 +// Count non-zeros in a vector +pub fn count_non_zeros(vector: [VECTOR_SIZE]i8) u8 { + var count : u8 = 0; + for (vector) |val| { + if (not is_zero(val)) { + count += 1; + } + } + return count; +} + +// ============================================================================ +// Sparse Pattern Functions +// ============================================================================ + +// analyze_sparsity(vector: [VECTOR_SIZE]i8) -> SparsePattern +// Analyze sparsity pattern of a vector +pub fn analyze_sparsity(vector: [VECTOR_SIZE]i8) SparsePattern { + const zeros = count_zeros(vector); + const non_zeros = count_non_zeros(vector); + const sparsity_ratio = if (VECTOR_SIZE > 0) + (zeros * 100) / VECTOR_SIZE + else + 0; + + const is_sparse = sparsity_ratio > 50; // More than 50% zeros + + return SparsePattern { + .is_sparse = is_sparse, + .zero_count = zeros, + .non_zero_count = non_zeros, + .sparsity_ratio = @as(u8, @truncate(sparsity_ratio)), + }; +} + +// should_skip_sparsity(pattern: SparsePattern) -> bool +// Determine if vector should be skipped based on sparsity +pub fn should_skip_sparsity(pattern: SparsePattern) bool { + return pattern.is_sparse and pattern.sparsity_ratio > SPARSE_TOLERANCE; +} + +// ============================================================================ +// Skip Result Functions +// ============================================================================ + +// skip_zeros(vector: [VECTOR_SIZE]i8) -> SkipResult +// Skip zero values in vector +pub fn skip_zeros(vector: [VECTOR_SIZE]i8) SkipResult { + var mask : u16 = 0; + var skipped : u8 = 0; + + for (vector, 0..) |val, i| { + if (not is_zero(val)) { + mask |= (@as(u16, 1) << i); + } else { + skipped += 1; + } + } + + return SkipResult { + .mode = SkipMode.zero, + .skipped_count = skipped, + .processed_count = @as(u8, @truncate(VECTOR_SIZE - skipped)), + .mask = mask, + }; +} + +// skip_threshold(vector: [VECTOR_SIZE]i8, threshold: i8) -> SkipResult +// Skip values below threshold +pub fn skip_threshold(vector: [VECTOR_SIZE]i8, threshold: i8) SkipResult { + var mask : u16 = 0; + var skipped : u8 = 0; + + for (vector, 0..) |val, i| { + if (not is_below_threshold(val, threshold)) { + mask |= (@as(u16, 1) << i); + } else { + skipped += 1; + } + } + + return SkipResult { + .mode = SkipMode.threshold, + .skipped_count = skipped, + .processed_count = @as(u8, @truncate(VECTOR_SIZE - skipped)), + .mask = mask, + }; +} + +// skip_all(vector: [VECTOR_SIZE]i8) -> SkipResult +// Skip all processing +pub fn skip_all(vector: [VECTOR_SIZE]i8) SkipResult { + return SkipResult { + .mode = SkipMode.all, + .skipped_count = VECTOR_SIZE, + .processed_count = 0, + .mask = 0, + }; +} + +// skip_none(vector: [VECTOR_SIZE]i8) -> SkipResult +// Process all elements without skipping +pub fn skip_none(vector: [VECTOR_SIZE]i8) SkipResult { + var mask : u16 = 0; + for (0..VECTOR_SIZE) |i| { + mask |= (@as(u16, 1) << i); + } + + return SkipResult { + .mode = SkipMode.none, + .skipped_count = 0, + .processed_count = VECTOR_SIZE, + .mask = mask, + }; +} + +// ============================================================================ +// Sparse Vector Functions +// ============================================================================ + +// create_sparse_vector(vector: [VECTOR_SIZE]i8) -> SparseVector +// Create sparse vector representation +pub fn create_sparse_vector(vector: [VECTOR_SIZE]i8) SparseVector { + var mask : u16 = 0; + var zero_count : u8 = 0; + + for (vector, 0..) |val, i| { + if (not is_zero(val)) { + mask |= (@as(u16, 1) << i); + } else { + zero_count += 1; + } + } + + return SparseVector { + .values = vector, + .mask = mask, + .zero_count = zero_count, + }; +} + +// get_non_zero_elements(sparse: SparseVector) -> [VECTOR_SIZE]i8 +// Get only non-zero elements (zero-padded) +pub fn get_non_zero_elements(sparse: SparseVector) [VECTOR_SIZE]i8 { + var result : [VECTOR_SIZE]i8 = undefined; + var idx : u8 = 0; + + for (sparse.values, 0..) |val, i| { + if ((sparse.mask >> i) & 1 != 0) { + result[idx] = val; + idx += 1; + } + } + + // Zero-fill remaining + while (idx < VECTOR_SIZE) { + result[idx] = 0; + idx += 1; + } + + return result; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_sparse_skip(mode: u8, vector_addr: u8) -> u16 +// Encode sparse skip instruction +pub fn encode_sparse_skip(mode: u8, vector_addr: u8) u16 { + // Format: [OP:8][MODE:2][ADDR:6] + const op : u16 = @as(u16, OP_SPARSE_SKIP) << 8; + const mode_field : u16 = @as(u16, mode & 0x03) << 6; + const addr_field : u16 = @as(u16, vector_addr & 0x3F); + return op | mode_field | addr_field; +} + +// decode_sparse_skip(encoded: u16) -> struct { mode: u8, vector_addr: u8 } +// Decode sparse skip instruction +pub fn decode_sparse_skip(encoded: u16) struct { mode: u8, vector_addr: u8 } { + const mode : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const vector_addr : u8 = @as(u8, @truncate(encoded & 0x3F)); + return .{ .mode = mode, .vector_addr = vector_addr }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "is_zero_true" { + try std.testing.expect(is_zero(0) == true); +} + +test "is_zero_false" { + try std.testing.expect(is_zero(1) == false); + try std.testing.expect(is_zero(-1) == false); +} + +test "is_below_threshold_positive" { + try std.testing.expect(is_below_threshold(3, 5) == true); + try std.testing.expect(is_below_threshold(7, 5) == false); +} + +test "is_below_threshold_negative" { + try std.testing.expect(is_below_threshold(-3, 5) == true); + try std.testing.expect(is_below_threshold(-7, 5) == false); +} + +test "count_zeros_empty" { + given vector = [_]i8{0} ** 16 + try std.testing.expect(count_zeros(vector) == 16); +} + +test "count_zeros_none" { + given vector = [_]i8{1} ** 16 + try std.testing.expect(count_zeros(vector) == 0); +} + +test "count_zeros_mixed" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(count_zeros(vector) == 8); +} + +test "count_non_zeros_mixed" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(count_non_zeros(vector) == 8); +} + +test "analyze_sparsity_dense" { + given vector = [_]i8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(pattern.is_sparse == false); + try std.testing.expect(pattern.zero_count == 0); +} + +test "analyze_sparsity_sparse" { + given vector = [_]i8{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(pattern.is_sparse == true); + try std.testing.expect(pattern.zero_count == 15); +} + +test "should_skip_sparsity_true" { + given vector = [_]i8{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(should_skip_sparsity(pattern) == true); +} + +test "should_skip_sparsity_false" { + given vector = [_]i8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(should_skip_sparsity(pattern) == false); +} + +test "skip_zeros_mixed" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(result = skip_zeros(vector)); + try std.testing.expect(result.skipped_count == 8); + try std.testing.expect(result.processed_count == 8); + try std.testing.expect(result.mask == 0xAAAA); +} + +test "skip_threshold_mixed" { + given vector = [_]i8{1, 0, 2, 0, 10, 0, 4, 0, 5, 0, 20, 0, 7, 0, 8, 0} + try std.testing.expect(result = skip_threshold(vector, 5)); + try std.testing.expect(result.skipped_count == 5); +} + +test "skip_all" { + given vector = [_]i8{1} ** 16 + try std.testing.expect(result = skip_all(vector)); + try std.testing.expect(result.skipped_count == 16); + try std.testing.expect(result.processed_count == 0); + try std.testing.expect(result.mask == 0); +} + +test "skip_none" { + given vector = [_]i8{1} ** 16 + try std.testing.expect(result = skip_none(vector)); + try std.testing.expect(result.skipped_count == 0); + try std.testing.expect(result.processed_count == 16); + try std.testing.expect(result.mask == 0xFFFF); +} + +test "create_sparse_vector" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(sparse = create_sparse_vector(vector)); + try std.testing.expect(sparse.mask == 0xAAAA); + try std.testing.expect(sparse.zero_count == 8); +} + +test "get_non_zero_elements" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(sparse = create_sparse_vector(vector)); + try std.testing.expect(non_zeros = get_non_zero_elements(sparse)); + try std.testing.expect(non_zeros[0] == 1); + try std.testing.expect(non_zeros[1] == 2); + try std.testing.expect(non_zeros[2] == 3); + try std.testing.expect(non_zeros[7] == 0); +} + +test "encode_sparse_skip" { + given encoded = encode_sparse_skip(1, 0x20) + try std.testing.expect((encoded >> 8) == OP_SPARSE_SKIP); +} + +test "decode_sparse_skip" { + given decoded = decode_sparse_skip(0xE150) + try std.testing.expect(decoded.mode == 1); + try std.testing.expect(decoded.vector_addr == 0x10); +} + +test "opcode_constant" { + try std.testing.expect(OP_SPARSE_SKIP == 0xE1); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant vector_size_sixteen + assert VECTOR_SIZE == 16 + +invariant zero_threshold_one + assert ZERO_THRESHOLD == 1 + +invariant sparse_tolerance_five + assert SPARSE_TOLERANCE == 5 + +invariant skip_mode_none_zero + assert SKIP_NONE == 0 + +invariant skip_mode_all_three + assert SKIP_ALL == 3 + +invariant zero_count_plus_non_zero_equals_size + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + assert count_zeros(vector) + count_non_zeros(vector) == VECTOR_SIZE + +invariant skip_all_skips_all + try std.testing.expect(skip_all([_]i8{1} ** 16).skipped_count == VECTOR_SIZE); + +invariant skip_none_skips_none + try std.testing.expect(skip_none([_]i8{1} ** 16).skipped_count == 0); + +invariant processed_plus_skipped_equals_size + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0} + try std.testing.expect(result = skip_zeros(vector)); + assert result.skipped_count + result.processed_count == VECTOR_SIZE + +invariant sparsity_ratio_bound + given vector = [_]i8{1} ** 16 + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(pattern.sparsity_ratio <= 100); + +invariant mask_bits_processed_equals_processed_count + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(result = skip_zeros(vector)); + var mask_bits : u8 = 0 + for (0..VECTOR_SIZE) |i| { + if ((result.mask >> i) & 1 != 0) { + mask_bits += 1; + } + } + assert mask_bits == result.processed_count + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench count_zeros_latency + measure: nanoseconds to count_zeros([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}) + target: < 100ns + +bench analyze_sparsity_latency + measure: nanoseconds to analyze_sparsity([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}) + target: < 150ns + +bench skip_zeros_latency + measure: nanoseconds to skip_zeros([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}) + target: < 150ns + +bench skip_threshold_latency + measure: nanoseconds to skip_threshold([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}, 5) + target: < 150ns + +bench create_sparse_vector_latency + measure: nanoseconds to create_sparse_vector([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}) + target: < 150ns + +bench encode_sparse_skip_latency + measure: nanoseconds to encode_sparse_skip(1, 0x20) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/spec_exit.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/spec_exit.t27 new file mode 100644 index 0000000000..d716d9c3d6 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/spec_exit.t27 @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: Apache-2.0 +; spec_exit.t27 — Sacred Opcode 0xEB: Speculative Exit +; Hardware for speculative exit and recovery +; φ² + 1/φ² = 3 | TRINITY + +module sacred-spec_exit; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_SPEC_EXIT : u8 = 0xEB; + +pub const MAX_NESTING_DEPTH : u8 = 8; +pub const EXIT_REASON_BITS : u8 = 3; + +pub const EXIT_SUCCESS : u8 = 0; +pub const EXIT_SPEC_FAIL : u8 = 1; +pub const EXIT_TIMEOUT : u8 = 2; +pub const EXIT_EXCEPTION : u8 = 3; +pub const EXIT_RESOURCE : u8 = 4; +pub const EXIT_ABORT : u8 = 5; +pub const EXIT_RECOVER : u8 = 6; +pub const EXIT_MAX : u8 = 7; + +pub const STATE_RUNNING : u8 = 0; +pub const STATE_SPECULATING : u8 = 1; +pub const STATE_EXITING : u8 = 2; +pub const STATE_EXITED : u8 = 3; +pub const STATE_RECOVERING : u8 = 4; +pub const STATE_ERROR : u8 = 5; + +// ============================================================================ +// Types +// ============================================================================ + +pub const ExitReason = enum(u8) { + success = EXIT_SUCCESS, + spec_fail = EXIT_SPEC_FAIL, + timeout = EXIT_TIMEOUT, + exception = EXIT_EXCEPTION, + resource = EXIT_RESOURCE, + abort = EXIT_ABORT, + recover = EXIT_RECOVER, +} + +pub const SpecState = enum(u8) { + running = STATE_RUNNING, + speculating = STATE_SPECULATING, + exiting = STATE_EXITING, + exited = STATE_EXITED, + recovering = STATE_RECOVERING, + error = STATE_ERROR, +} + +pub const SpecExit = struct { + reason : ExitReason, + depth : u8, + checkpoint_addr : u16, + recovery_possible : bool, +} + +pub const SpecContext = struct { + state : SpecState, + current_depth : u8, + spec_count : u32, + exit_count : u32, +} + +pub const SpecConfig = struct { + max_depth : u8, + timeout_cycles : u32, + enable_recovery : bool, + checkpoint_interval : u8, +} + +// ============================================================================ +// Spec State Functions +// ============================================================================ + +// spec_state_is_running(state: SpecState) -> bool +// Check if state is running +pub fn spec_state_is_running(state: SpecState) bool { + return state == SpecState.running; +} + +// spec_state_is_speculating(state: SpecState) -> bool +// Check if state is speculating +pub fn spec_state_is_speculating(state: SpecState) -> bool { + return state == SpecState.speculating; +} + +// spec_state_is_exiting(state: SpecState) -> bool +// Check if state is exiting +pub fn spec_state_is_exiting(state: SpecState) -> bool { + return state == SpecState.exiting; +} + +// spec_state_is_exited(state: SpecState) -> bool +// Check if state has exited +pub fn spec_state_is_exited(state: SpecState) -> bool { + return state == SpecState.exited; +} + +// spec_state_is_recovering(state: SpecState) -> bool +// Check if state is recovering +pub fn spec_state_is_recovering(state: SpecState) -> bool { + return state == SpecState.recovering; +} + +// spec_state_is_terminal(state: SpecState) -> bool +// Check if state is terminal (no further execution) +pub fn spec_state_is_terminal(state: SpecState) bool { + return state == SpecState.exited or state == SpecState.error; +} + +// spec_state_can_speculate(state: SpecState) -> bool +// Check if can start speculation +pub fn spec_state_can_speculate(state: SpecState) -> bool { + return state == SpecState.running or state == SpecState.recovering; +} + +// ============================================================================ +// Exit Functions +// ============================================================================ + +// spec_exit_success(depth: u8, checkpoint: u16) -> SpecExit +// Create successful exit +pub fn spec_exit_success(depth: u8, checkpoint: u16) -> SpecExit { + return SpecExit { + .reason = ExitReason.success, + .depth = depth, + .checkpoint_addr = checkpoint, + .recovery_possible = true, + }; +} + +// spec_exit_fail(reason: ExitReason, depth: u8, checkpoint: u16, can_recover: bool) -> SpecExit +// Create failure exit +pub fn spec_exit_fail(reason: ExitReason, depth: u8, checkpoint: u16, can_recover: bool) -> SpecExit { + return SpecExit { + .reason = reason, + .depth = depth, + .checkpoint_addr = checkpoint, + .recovery_possible = can_recover, + }; +} + +// spec_exit_timeout(depth: u8, checkpoint: u16) -> SpecExit +// Create timeout exit +pub fn spec_exit_timeout(depth: u8, checkpoint: u16) -> SpecExit { + return spec_exit_fail(ExitReason.timeout, depth, checkpoint, true); +} + +// spec_exit_exception(depth: u8, checkpoint: u16) -> SpecExit +// Create exception exit +pub fn spec_exit_exception(depth: u8, checkpoint: u16) -> SpecExit { + return spec_exit_fail(ExitReason.exception, depth, checkpoint, false); +} + +// spec_exit_resource(depth: u8, checkpoint: u16) -> SpecExit +// Create resource exit +pub fn spec_exit_resource(depth: u8, checkpoint: u16) -> SpecExit { + return spec_exit_fail(ExitReason.resource, depth, checkpoint, true); +} + +// spec_exit_abort(depth: u8) -> SpecExit +// Create abort exit (no recovery) +pub fn spec_exit_abort(depth: u8) -> SpecExit { + return SpecExit { + .reason = ExitReason.abort, + .depth = depth, + .checkpoint_addr = 0, + .recovery_possible = false, + }; +} + +// spec_exit_recover(depth: u8, checkpoint: u16) -> SpecExit +// Create recover exit +pub fn spec_exit_recover(depth: u8, checkpoint: u16) -> SpecExit { + return SpecExit { + .reason = ExitReason.recover, + .depth = depth, + .checkpoint_addr = checkpoint, + .recovery_possible = true, + }; +} + +// ============================================================================ +// Spec Context Functions +// ============================================================================ + +// spec_context_init() -> SpecContext +// Initialize speculation context +pub fn spec_context_init() -> SpecContext { + return SpecContext { + .state = SpecState.running, + .current_depth = 0, + .spec_count = 0, + .exit_count = 0, + }; +} + +// spec_context_enter_speculation(context: SpecContext, depth_limit: u8) -> SpecContext +// Enter speculation +pub fn spec_context_enter_speculation(context: SpecContext, depth_limit: u8) -> SpecContext { + if (not spec_state_can_speculate(context.state)) { + return context; + } + if (context.current_depth >= depth_limit) { + return context; + } + + return SpecContext { + .state = SpecState.speculating, + .current_depth = context.current_depth + 1, + .spec_count = context.spec_count + 1, + .exit_count = context.exit_count, + }; +} + +// spec_context_exit(context: SpecContext, exit: SpecExit) -> SpecContext +// Handle exit from speculation +pub fn spec_context_exit(context: SpecContext, exit: SpecExit) -> SpecContext { + var new_state = context.state; + + if (exit.reason == ExitReason.success or exit.reason == ExitReason.recover) { + new_state = SpecState.exiting; + } else if (exit.recovery_possible) { + new_state = SpecState.recovering; + } else { + new_state = SpecState.error; + } + + return SpecContext { + .state = new_state, + .current_depth = if (new_state == SpecState.recovering) exit.depth else context.current_depth, + .spec_count = context.spec_count + 1, + .exit_count = context.exit_count + 1, + }; +} + +// spec_context_complete(context: SpecContext) -> SpecContext +// Complete speculation +pub fn spec_context_complete(context: SpecContext) -> SpecContext { + return SpecContext { + .state = SpecState.exited, + .current_depth = 0, + .spec_count = context.spec_count + 1, + .exit_count = context.exit_count, + }; +} + +// spec_context_check_timeout(context: SpecContext, config: SpecConfig, current_cycle: u32) -> bool +// Check for timeout +pub fn spec_context_check_timeout(context: SpecContext, config: SpecConfig, current_cycle: u32) -> bool { + if (config.timeout_cycles == 0) { + return false; + } + return (context.spec_count * 100) >= config.timeout_cycles; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_spec_exit(reason: u8, depth: u8, checkpoint: u16) -> u32 +// Encode speculative exit instruction +pub fn encode_spec_exit(reason: u8, depth: u8, checkpoint: u16) u32 { + // Format: [OP:8][REASON:3][DEPTH:5][CHECKPOINT:16] + const op : u32 = @as(u32, OP_SPEC_EXIT) << 24; + const reason_field : u32 = @as(u32, reason & 0x07) << 21; + const depth_field : u32 = @as(u32, depth & 0x1F) << 16; + const checkpoint_field : u32 = @as(u32, checkpoint & 0xFFFF); + return op | reason_field | depth_field | checkpoint_field; +} + +// decode_spec_exit(encoded: u32) -> struct { reason: u8, depth: u8, checkpoint: u16 } +// Decode speculative exit instruction +pub fn decode_spec_exit(encoded: u32) -> struct { reason: u8, depth: u8, checkpoint: u16 } { + const reason : u8 = @as(u8, @truncate((encoded >> 21) & 0x07)); + const depth : u8 = @as(u8, @truncate((encoded >> 16) & 0x1F)); + const checkpoint : u16 = @as(u16, @truncate(encoded & 0xFFFF)); + return .{ .reason = reason, .depth = depth, .checkpoint = checkpoint }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "max_nesting_depth_eight" { + try std.testing.expect(MAX_NESTING_DEPTH == 8); +} + +test "exit_reason_constants" { + try std.testing.expect(EXIT_SUCCESS == 0); + try std.testing.expect(EXIT_ABORT == 5); + try std.testing.expect(EXIT_RECOVER == 6); +} + +test "state_constants" { + try std.testing.expect(STATE_RUNNING == 0); + try std.testing.expect(STATE_EXITED == 3); + try std.testing.expect(STATE_ERROR == 5); +} + +test "spec_state_is_running_true" { + try std.testing.expect(spec_state_is_running(SpecState.running) == true); +} + +test "spec_state_is_running_false" { + try std.testing.expect(spec_state_is_running(SpecState.speculating) == false); +} + +test "spec_state_is_terminal_true" { + try std.testing.expect(spec_state_is_terminal(SpecState.exited) == true); + try std.testing.expect(spec_is_terminal(SpecState.error) == true); +} + +test "spec_state_is_terminal_false" { + try std.testing.expect(spec_state_is_terminal(SpecState.running) == false); +} + +test "spec_state_can_speculate_true" { + try std.testing.expect(spec_state_can_speculate(SpecState.running) == true); + try std.testing.expect(spec_state_can_speculate(SpecState.recovering) == true); +} + +test "spec_state_can_speculate_false" { + try std.testing.expect(spec_state_can_speculate(SpecState.exiting) == false); + try std.testing.expect(spec_state_can_speculate(SpecState.error) == false); +} + +test "spec_exit_success_structure" { + given exit = spec_exit_success(5, 0x1000) + try std.testing.expect(exit.reason == ExitReason.success); + try std.testing.expect(exit.depth == 5); + try std.testing.expect(exit.recovery_possible == true); +} + +test "spec_exit_fail_structure" { + given exit = spec_exit_fail(ExitReason.exception, 3, 0x2000, true) + try std.testing.expect(exit.reason == ExitReason.exception); + try std.testing.expect(exit.recovery_possible == true); +} + +test "spec_exit_abort_no_recovery" { + given exit = spec_exit_abort(5) + try std.testing.expect(exit.reason == ExitReason.abort); + try std.testing.expect(exit.recovery_possible == false); +} + +test "spec_exit_recover_structure" { + given exit = spec_exit_recover(3, 0x3000) + try std.testing.expect(exit.reason == ExitReason.recover); + try std.testing.expect(exit.recovery_possible == true); +} + +test "spec_context_init_structure" { + given context = spec_context_init() + try std.testing.expect(context.state == SpecState.running); + try std.testing.expect(context.current_depth == 0); + try std.testing.expect(context.spec_count == 0); +} + +test "spec_context_enter_speculation" { + given context = spec_context_init() + try std.testing.expect(result = spec_context_enter_speculation(context, 8)); + try std.testing.expect(result.state == SpecState.speculating); + try std.testing.expect(result.current_depth == 1); +} + +test "spec_context_enter_depth_limit" { + given context = spec_context_init() + try std.testing.expect(deep = spec_context_enter_speculation(spec_context_enter_speculation(context, 8), 8)); + try std.testing.expect(deep.current_depth == 2); +} + +test "spec_context_enter_depth_limit_exceeded" { + given context = SpecContext{.state = SpecState.running, .current_depth = 8, .spec_count = 0, .exit_count = 0} + try std.testing.expect(result = spec_context_enter_speculation(context, 8)); + try std.testing.expect(result.current_depth == 8); +} + +test "spec_context_exit_success" { + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_success(5, 0x1000)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(result.state == SpecState.exiting); +} + +test "spec_context_exit_fail_recover" { + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_fail(ExitReason.exception, 3, 0x2000, true)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(result.state == SpecState.recovering); +} + +test "spec_context_exit_fail_no_recover" { + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_fail(ExitReason.abort, 3, 0, false)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(result.state == SpecState.error); +} + +test "spec_context_complete" { + given context = spec_context_init() + try std.testing.expect(result = spec_context_complete(context)); + try std.testing.expect(result.state == SpecState.exited); + try std.testing.expect(result.current_depth == 0); +} + +test "spec_context_check_timeout_true" { + given context = SpecContext{.state = SpecState.speculating, .current_depth = 3, .spec_count = 10, .exit_count = 0} + try std.testing.expect(config = SpecConfig{.max_depth = 8, .timeout_cycles = 500, .enable_recovery = true, .checkpoint_interval = 1}); + try std.testing.expect(spec_context_check_timeout(context, config, 1000) == true); +} + +test "spec_context_check_timeout_false" { + given context = SpecContext{.state = SpecState.speculating, .current_depth = 3, .spec_count = 1, .exit_count = 0} + try std.testing.expect(config = SpecConfig{.max_depth = 8, .timeout_cycles = 500, .enable_recovery = true, .checkpoint_interval = 1}); + try std.testing.expect(spec_context_check_timeout(context, config, 1000) == false); +} + +test "encode_spec_exit" { + given encoded = encode_spec_exit(1, 5, 0x1234) + try std.testing.expect((encoded >> 24) == OP_SPEC_EXIT); +} + +test "decode_spec_exit" { + given decoded = decode_spec_exit(0xEB081234) + try std.testing.expect(decoded.reason == 1); + try std.testing.expect(decoded.depth == 5); + try std.testing.expect(decoded.checkpoint == 0x1234); +} + +test "opcode_constant" { + try std.testing.expect(OP_SPEC_EXIT == 0xEB); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant max_nesting_depth_eight + assert MAX_NESTING_DEPTH == 8 + +invariant exit_reason_values + try std.testing.expect(EXIT_SUCCESS >= 0 and EXIT_MAX <= 7); + +invariant state_values + try std.testing.expect(STATE_RUNNING >= 0 and STATE_ERROR <= 5); + +invariant spec_exit_success_recoverable + given exit = spec_exit_success(0, 0x1000) + try std.testing.expect(exit.recovery_possible == true); + +invariant spec_exit_abort_not_recoverable + given exit = spec_exit_abort(5) + try std.testing.expect(exit.recovery_possible == false); + +invariant spec_exit_depth_bound + try std.testing.expect(spec_exit_abort(0).depth == 0); + try std.testing.expect(spec_exit_abort(MAX_NESTING_DEPTH).depth == MAX_NESTING_DEPTH); + +invariant spec_context_init_zero_depth + given context = spec_context_init() + try std.testing.expect(context.current_depth == 0); + +invariant spec_context_enter_increases_depth + given context = spec_context_init() + try std.testing.expect(result = spec_context_enter_speculation(context, 8)); + try std.testing.expect(result.current_depth == context.current_depth + 1); + +invariant spec_context_exit_preserves_reason + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_fail(ExitReason.exception, 3, 0x2000, true)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(exit.reason == ExitReason.exception); + +invariant spec_context_complete_zero_depth + given context = SpecContext{.state = SpecState.running, .current_depth = 5, .spec_count = 10, .exit_count = 0} + try std.testing.expect(result = spec_context_complete(context)); + try std.testing.expect(result.current_depth == 0); + +invariant spec_context_complete_exited_state + given context = spec_context_init() + try std.testing.expect(result = spec_context_complete(context)); + try std.testing.expect(result.state == SpecState.exited); + +invariant spec_context_check_timeout_no_timeout + given config = SpecConfig{.max_depth = 8, .timeout_cycles = 0, .enable_recovery = true, .checkpoint_interval = 1} + try std.testing.expect(context = SpecContext{.state = SpecState.speculating, .current_depth = 3, .spec_count = 1000, .exit_count = 0}); + try std.testing.expect(spec_context_check_timeout(context, config, 1000) == false); + +invariant spec_context_enter_speculate_requires_running_or_recovering + given context = SpecContext{.state = SpecState.exiting, .current_depth = 0, .spec_count = 0, .exit_count = 0} + try std.testing.expect(spec_context_enter_speculation(context, 8).current_depth == 0); + +invariant spec_context_exit_increases_exit_count + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_success(0, 0x1000)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(result.exit_count == context.exit_count + 1); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench spec_exit_success_latency + measure: nanoseconds to spec_exit_success(5, 0x1000) + target: < 30ns + +bench spec_exit_fail_latency + measure: nanoseconds to spec_exit_fail(ExitReason.exception, 3, 0x2000, true) + target: < 30ns + +bench spec_exit_abort_latency + measure: nanoseconds to spec_exit_abort(5) + target: < 30ns + +bench spec_context_enter_speculation_latency + measure: nanoseconds to spec_context_enter_speculation(spec_context_init(), 8) + target: < 50ns + +bench spec_context_exit_latency + measure: nanoseconds to spec_context_exit(spec_context_init(), spec_exit_success(5, 0x1000)) + target: < 50ns + +bench spec_context_check_timeout_latency + measure: nanoseconds to spec_context_check_timeout(.{.state = SpecState.speculating, .current_depth = 3, .spec_count = 10, .exit_count = 0}, .{.max_depth = 8, .timeout_cycles = 500, .enable_recovery = true, .checkpoint_interval = 1}, 1000) + target: < 30ns + +bench encode_spec_exit_latency + measure: nanoseconds to encode_spec_exit(1, 5, 0x1234) + target: < 30ns + +bench decode_spec_exit_latency + measure: nanoseconds to decode_spec_exit(0xEB081234) + target: < 30ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/stoch_round.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/stoch_round.t27 new file mode 100644 index 0000000000..7bb7a55f45 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/stoch_round.t27 @@ -0,0 +1,575 @@ +// SPDX-License-Identifier: Apache-2.0 +; stoch_round.t27 — Sacred Opcode 0xE9: Stochastic Rounding +; Hardware stochastic rounding for quantization +; φ² + 1/φ² = 3 | TRINITY + +module sacred-stoch_round; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_STOCH_ROUND : u8 = 0xE9; + +pub const RANDOM_BITS : u8 = 8; +pub const ROUND_MODE_NEAREST : u8 = 0; +pub const ROUND_MODE_STOCHASTIC : u8 = 1; +pub const ROUND_MODE_TRUNCATE : u8 = 2; +pub const ROUND_MODE_CEIL : u8 = 3; + +pub const INT4_BITS : u8 = 4; +pub const INT8_BITS : u8 = 8; + +pub const INT4_MAX : i8 = 7; +pub const INT4_MIN : i8 = -8; +pub const INT8_MAX : i8 = 127; +pub const INT8_MIN : i8 = -128; + +// ============================================================================ +// Types +// ============================================================================ + +pub const RoundConfig = struct { + mode : u8, + target_bits : u8, + seed : u16, +} + +pub const RoundResult = struct { + value : i16, + original : i16, + rounded : bool, + overflow : bool, +} + +pub const RngState = struct { + state : u16, +} + +// ============================================================================ +// RNG Functions (XorShift LFSR) +// ============================================================================ + +// rng_init(seed: u16) -> RngState +// Initialize RNG with seed +pub fn rng_init(seed: u16) RngState { + return RngState{.state = if (seed == 0) 1 else seed}; +} + +// rng_next(state: RngState) -> struct { state: RngState, value: u8 } +// Get next random byte +pub fn rng_next(state: RngState) -> struct { state: RngState, value: u8 } { + var s = state.state; + s ^= s >> 7; + s ^= s << 9; + s ^= s >> 8; + return .{ .state = RngState{.state = s}, .value = @as(u8, @truncate(s)) }; +} + +// rng_next_range(state: RngState, max: u8) -> struct { state: RngState, value: u8 } +// Get next random value in range [0, max) +pub fn rng_next_range(state: RngState, max: u8) -> struct { state: RngState, value: u8 } { + const next_result = rng_next(state); + const scaled = (@as(u16, next_result.value) * @as(u16, max)) >> 8; + return .{ .state = next_result.state, .value = @as(u8, @truncate(scaled)) }; +} + +// ============================================================================ +// Rounding Functions +// ============================================================================ + +// round_nearest(value: i16, bits: u8) -> RoundResult +// Round to nearest +pub fn round_nearest(value: i16, bits: u8) -> RoundResult { + const shift = 16 - bits; + var result : i16 = 0; + + if (shift >= 0) { + const abs_val = if (value < 0) -value else value; + const rounding = (abs_val + (1 << (shift - 1))) >> shift; + result = if (value < 0) -rounding else rounding; + } else { + result = value << (-shift); + } + + // Check for overflow + var overflow = false; + const max_val : i16 = (@as(i16, 1) << (bits - 1)) - 1; + const min_val : i16 = -(@as(i16, 1) << (bits - 1)); + + if (result > max_val) { + result = max_val; + overflow = true; + } else if (result < min_val) { + result = min_val; + overflow = true; + } + + return RoundResult { + .value = result, + .original = value, + .rounded = result != value, + .overflow = overflow, + }; +} + +// round_stochastic(value: i16, bits: u8, state: RngState) -> struct { result: RoundResult, new_state: RngState } +// Stochastic rounding +pub fn round_stochastic(value: i16, bits: u8, state: RngState) -> struct { result: RoundResult, new_state: RngState } { + const shift = 16 - bits; + var result : i16 = 0; + + if (shift <= 0) { + result = value << (-shift); + return .{ .result = RoundResult{.value = result, .original = value, .rounded = false, .overflow = false}, .new_state = state }; + } + + const abs_val = if (value < 0) -value else value; + const integer_part = abs_val >> shift; + const fractional_part = abs_val & ((@as(i16, 1) << shift) - 1); + + // Generate random value and compare + const max_frac = @as(i16, 1) << shift; + const frac_scaled : i16 = @as(i16, @intCast(fractional_part)) * 256; + const threshold = (frac_scaled + (max_frac / 2)) / max_frac; + + const rng_result = rng_next(state); + const rand_val : i16 = @as(i16, @intCast(rng_result.value)); + + var rounded_int = integer_part; + if (rand_val >= threshold and fractional_part != 0) { + rounded_int += 1; + } + + result = if (value < 0) -rounded_int else rounded_int; + + // Check for overflow + var overflow = false; + const max_val : i16 = (@as(i16, 1) << (bits - 1)) - 1; + const min_val : i16 = -(@as(i16, 1) << (bits - 1)); + + if (result > max_val) { + result = max_val; + overflow = true; + } else if (result < min_val) { + result = min_val; + overflow = true; + } + + return .{ .result = RoundResult{.value = result, .original = value, .rounded = result != (abs_val >> shift), .overflow = overflow}, .new_state = rng_result.state }; +} + +// round_truncate(value: i16, bits: u8) -> RoundResult +// Truncate (round toward zero) +pub fn round_truncate(value: i16, bits: u8) -> RoundResult { + const shift = 16 - bits; + var result : i16 = 0; + + if (shift >= 0) { + result = value >> shift; + } else { + result = value << (-shift); + } + + // Check for overflow + var overflow = false; + const max_val : i16 = (@as(i16, 1) << (bits - 1)) - 1; + const min_val : i16 = -(@as(i16, 1) << (bits - 1)); + + if (result > max_val) { + result = max_val; + overflow = true; + } else if (result < min_val) { + result = min_val; + overflow = true; + } + + return RoundResult { + .value = result, + .original = value, + .rounded = result != value, + .overflow = overflow, + }; +} + +// round_ceil(value: i16, bits: u8) -> RoundResult +// Ceil (round toward positive infinity) +pub fn round_ceil(value: i16, bits: u8) -> RoundResult { + const shift = 16 - bits; + var result : i16 = 0; + + if (shift >= 0) { + const abs_val = if (value < 0) -value else value; + const frac_part = abs_val & ((@as(i16, 1) << shift) - 1); + var base = abs_val >> shift; + if (value >= 0 and frac_part != 0) { + base += 1; + } + result = if (value < 0) -base else base; + } else { + result = value << (-shift); + } + + // Check for overflow + var overflow = false; + const max_val : i16 = (@as(i16, 1) << (bits - 1)) - 1; + const min_val : i16 = -(@as(i16, 1) << (bits - 1)); + + if (result > max_val) { + result = max_val; + overflow = true; + } else if (result < min_val) { + result = min_val; + overflow = true; + } + + return RoundResult { + .value = result, + .original = value, + .rounded = true, + .overflow = overflow, + }; +} + +// ============================================================================ +// Config-Based Rounding +// ============================================================================ + +// round_with_config(value: i16, config: RoundConfig, state: RngState) -> struct { result: RoundResult, new_state: RngState } +// Round using config +pub fn round_with_config(value: i16, config: RoundConfig, state: RngState) -> struct { result: RoundResult, new_state: RngState } { + switch (config.mode) { + ROUND_MODE_NEAREST => { + return .{ .result = round_nearest(value, config.target_bits), .new_state = state }; + }, + ROUND_MODE_STOCHASTIC => { + return round_stochastic(value, config.target_bits, state); + }, + ROUND_MODE_TRUNCATE => { + return .{ .result = round_truncate(value, config.target_bits), .new_state = state }; + }, + ROUND_MODE_CEIL => { + return .{ .result = round_ceil(value, config.target_bits), .new_state = state }; + }, + else => { + return .{ .result = RoundResult{.value = 0, .original = value, .rounded = false, .overflow = true}, .new_state = state }; + }, + } +} + +// ============================================================================ +// Vector Rounding +// ============================================================================ + +// round_vector_nearest(values: [8]i16, bits: u8) -> [8]RoundResult +// Round vector to nearest +pub fn round_vector_nearest(values: [8]i16, bits: u8) [8]RoundResult { + var results : [8]RoundResult = undefined; + for (values, 0..) |val, i| { + results[i] = round_nearest(val, bits); + } + return results; +} + +// round_vector_stochastic(values: [8]i16, bits: u8, state: RngState) -> struct { results: [8]RoundResult, new_state: RngState } +// Round vector stochastically +pub fn round_vector_stochastic(values: [8]i16, bits: u8, state: RngState) -> struct { results: [8]RoundResult, new_state: RngState } { + var results : [8]RoundResult = undefined; + var current_state = state; + + for (values, 0..) |val, i| { + const round_result = round_stochastic(val, bits, current_state); + results[i] = round_result.result; + current_state = round_result.new_state; + } + + return .{ .results = results, .new_state = current_state }; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_stoch_round(mode: u8, target_bits: u8, seed: u8) -> u16 +// Encode stochastic rounding instruction +pub fn encode_stoch_round(mode: u8, target_bits: u8, seed: u8) u16 { + // Format: [OP:8][MODE:2][BITS:4][SEED:2] + const op : u16 = @as(u16, OP_STOCH_ROUND) << 8; + const mode_field : u16 = @as(u16, mode & 0x03) << 6; + const bits_field : u16 = @as(u16, target_bits & 0x0F) << 2; + const seed_field : u16 = @as(u16, seed & 0x03); + return op | mode_field | bits_field | seed_field; +} + +// decode_stoch_round(encoded: u16) -> struct { mode: u8, target_bits: u8, seed: u8 } +// Decode stochastic rounding instruction +pub fn decode_stoch_round(encoded: u16) struct { mode: u8, target_bits: u8, seed: u8 } { + const mode : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const target_bits : u8 = @as(u8, @truncate((encoded >> 2) & 0x0F)); + const seed : u8 = @as(u8, @truncate(encoded & 0x03)); + return .{ .mode = mode, .target_bits = target_bits, .seed = seed }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "round_mode_constants" { + try std.testing.expect(ROUND_MODE_NEAREST == 0); + try std.testing.expect(ROUND_MODE_STOCHASTIC == 1); + try std.testing.expect(ROUND_MODE_TRUNCATE == 2); + try std.testing.expect(ROUND_MODE_CEIL == 3); +} + +test "int4_bounds" { + try std.testing.expect(INT4_MAX == 7 and INT4_MIN == -8); +} + +test "int8_bounds" { + try std.testing.expect(INT8_MAX == 127 and INT8_MIN == -128); +} + +test "round_nearest_basic" { + given result = round_nearest(10, 4) + try std.testing.expect(result.value == 1); +} + +test "round_nearest_negative" { + given result = round_nearest(-10, 4) + try std.testing.expect(result.value == -1); +} + +test "round_nearest_overflow" { + given result = round_nearest(0x7FFF, 8) + try std.testing.expect(result.value == INT8_MAX); + try std.testing.expect(result.overflow == true); +} + +test "round_nearest_underflow" { + given result = round_nearest(-0x8000, 8) + try std.testing.expect(result.value == INT8_MIN); + try std.testing.expect(result.overflow == true); +} + +test "round_nearest_no_change" { + given result = round_nearest(0x1000, 8) + try std.testing.expect(result.value == 0x10); + try std.testing.expect(result.rounded == false); +} + +test "round_truncate_basic" { + given result = round_truncate(10, 4) + try std.testing.expect(result.value == 0); +} + +test "round_truncate_negative" { + given result = round_truncate(-10, 4) + try std.testing.expect(result.value == 0); +} + +test "round_truncate_fractional" { + given result = round_truncate(15, 4) + try std.testing.expect(result.value == 0); +} + +test "round_ceil_basic" { + given result = round_ceil(10, 4) + try std.testing.expect(result.value == 1); +} + +test "round_ceil_negative" { + given result = round_ceil(-10, 4) + try std.testing.expect(result.value == 0); +} + +test "round_ceil_fractional" { + given result = round_ceil(1, 4) + try std.testing.expect(result.value == 1); +} + +test "round_stochastic_seeded" { + given state = RngState{.state = 0x1234} + try std.testing.expect(round1 = round_stochastic(10, 4, state)); + try std.testing.expect(state2 = RngState{.state = 0x1234}); + try std.testing.expect(round2 = round_stochastic(10, 4, state2)); + try std.testing.expect(round1.result.value == round2.result.value); + try std.testing.expect(round1.new_state == round2.new_state); +} + +test "round_stochastic_reproducible" { + given state = RngState{.state = 0x1234} + try std.testing.expect(round1 = round_stochastic(10, 4, state)); + try std.testing.expect(round2 = round_stochastic(10, 4, round1.new_state)); + try std.testing.expect(round2.result.value != round1.result.value or round2.new_state != round1.new_state); +} + +test "round_vector_nearest" { + given results = round_vector_nearest([_]i16{10, 20, 30, 40, 50, 60, 70, 80}, 4) + try std.testing.expect(results[0].value == 1); + try std.testing.expect(results[7].value == 5); +} + +test "round_vector_stochastic" { + given state = RngState{.state = 0x1234} + try std.testing.expect(round_result = round_vector_stochastic([_]i16{10, 20, 30, 40}, 4, state)); + try std.testing.expect(round_result.new_state.state != 0x1234); +} + +test "rng_init_default" { + given state = rng_init(0) + try std.testing.expect(state.state == 1); +} + +test "rng_init_with_seed" { + given state = rng_init(0x1234) + try std.testing.expect(state.state == 0x1234); +} + +test "rng_next_range" { + given state = rng_init(0x1234) + try std.testing.expect(result = rng_next_range(state, 100)); + try std.testing.expect(result.value <= 100); +} + +test "round_with_config_nearest" { + given config = RoundConfig{.mode = ROUND_MODE_NEAREST, .target_bits = 8, .seed = 0} + try std.testing.expect(state = RngState{.state = 1}); + try std.testing.expect(result = round_with_config(10, config, state)); + try std.testing.expect(result.result.value == 10); +} + +test "round_with_config_stochastic" { + given config = RoundConfig{.mode = ROUND_MODE_STOCHASTIC, .target_bits = 4, .seed = 0} + try std.testing.expect(state = RngState{.state = 1}); + try std.testing.expect(result = round_with_config(10, config, state)); + try std.testing.expect(result.result.value <= INT4_MAX); +} + +test "encode_stoch_round" { + given encoded = encode_stoch_round(1, 4, 2) + try std.testing.expect((encoded >> 8) == OP_STOCH_ROUND); +} + +test "decode_stoch_round" { + given decoded = decode_stoch_round(0xE932) + try std.testing.expect(decoded.mode == 1); + try std.testing.expect(decoded.target_bits == 4); + try std.testing.expect(decoded.seed == 2); +} + +test "opcode_constant" { + try std.testing.expect(OP_STOCH_ROUND == 0xE9); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant random_bits_eight + assert RANDOM_BITS == 8 + +invariant int4_bits_four + assert INT4_BITS == 4 + +invariant int8_bits_eight + assert INT8_BITS == 8 + +invariant int4_max_min_relation + try std.testing.expect(INT4_MAX == -INT4_MIN - 1); + +invariant int8_max_min_relation + try std.testing.expect(INT8_MAX == -INT8_MIN - 1); + +invariant round_mode_values + try std.testing.expect(ROUND_MODE_NEAREST >= 0 and ROUND_MODE_CEIL <= 3); + +invariant round_nearest_range + given result = round_nearest(0, 4) + try std.testing.expect(result.value >= -8 and result.value <= 7); + +invariant round_truncate_range + given result = round_truncate(0, 4) + try std.testing.expect(result.value >= -8 and result.value <= 7); + +invariant round_ceil_range + given result = round_ceil(0, 4) + try std.testing.expect(result.value >= -8 and result.value <= 7); + +invariant round_stochastic_range + given state = RngState{.state = 1} + try std.testing.expect(result = round_stochastic(0, 4, state)); + try std.testing.expect(result.result.value >= INT4_MIN and result.result.value <= INT4_MAX); + +invariant round_nearest_preserves_sign + given result_pos = round_nearest(100, 4) + try std.testing.expect(result_neg = round_nearest(-100, 4)); + try std.testing.expect(result_pos.value >= 0 and result_neg.value <= 0); + +invariant round_truncate_preserves_sign + given result_pos = round_truncate(100, 4) + try std.testing.expect(result_neg = round_truncate(-100, 4)); + try std.testing.expect(result_pos.value >= 0 and result_neg.value <= 0); + +invariant round_ceil_non_negative + given result = round_ceil(0, 4) + try std.testing.expect(result.value >= 0); + +invariant rng_next_state_changes + given state = RngState{.state = 0x1234} + try std.testing.expect(result = rng_next(state)); + try std.testing.expect(result.state.state != state.state); + +invariant rng_next_range_value_bound + given state = RngState{.state = 1} + try std.testing.expect(result = rng_next_range(state, 100)); + try std.testing.expect(result.value <= 100); + +invariant round_vector_nearest_length + given results = round_vector_nearest([_]i16{0} ** 8, 4) + try std.testing.expect(results.len == 8); + +invariant round_vector_stochastic_length + given state = RngState{.state = 1} + try std.testing.expect(result = round_vector_stochastic([_]i16{0} ** 8, 4, state)); + try std.testing.expect(result.results.len == 8); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench round_nearest_latency + measure: nanoseconds to round_nearest(100, 4) + target: < 50ns + +bench round_stochastic_latency + measure: nanoseconds to round_stochastic(100, 4, RngState{.state = 1}) + target: < 100ns + +bench round_truncate_latency + measure: nanoseconds to round_truncate(100, 4) + target: < 50ns + +bench round_ceil_latency + measure: nanoseconds to round_ceil(100, 4) + target: < 50ns + +bench rng_next_latency + measure: nanoseconds to rng_next(RngState{.state = 0x1234}) + target: < 30ns + +bench round_vector_nearest_latency + measure: nanoseconds to round_vector_nearest([_]i16{10} ** 8, 4) + target: < 200ns + +bench round_vector_stochastic_latency + measure: nanoseconds to round_vector_stochastic([_]i16{10} ** 8, 4, RngState{.state = 1}) + target: < 500ns + +bench encode_stoch_round_latency + measure: nanoseconds to encode_stoch_round(1, 4, 2) + target: < 20ns + +bench decode_stoch_round_latency + measure: nanoseconds to decode_stoch_round(0xE932) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/fpga/subth_clk.t27 b/apps/website/public/t27/files/chips/gamma/specs/fpga/subth_clk.t27 new file mode 100644 index 0000000000..4d3fdd57cc --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/fpga/subth_clk.t27 @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: Apache-2.0 +; subth_clk.t27 — Sacred Opcode 0xE5: Sub-threshold Clock Gating +; Hardware for sub-threshold clock gating for power reduction +; φ² + 1/φ² = 3 | TRINITY + +module sacred-subth_clk; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_SUBTH_CLK : u8 = 0xE5; + +pub const NUM_CLK_DOMAINS : u8 = 8; +pub const SUBTH_THRESHOLD_MV : u16 = 400; // 400mV threshold +pub const NORMAL_VOLTAGE_MV : u16 = 1100; // 1.1V normal + +pub const CLK_GATE_OFF : u8 = 0; +pub const CLK_GATE_ON : u8 = 1; +pub const CLK_GATE_AUTO : u8 = 2; + +pub const POWER_STATE_ACTIVE : u8 = 0; +pub const POWER_STATE_IDLE : u8 = 1; +pub const POWER_STATE_SLEEP : u8 = 2; +pub const POWER_STATE_DEEP_SLEEP : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const ClkGateMode = enum(u8) { + off = CLK_GATE_OFF, + on = CLK_GATE_ON, + auto = CLK_GATE_AUTO, +} + +pub const PowerState = enum(u8) { + active = POWER_STATE_ACTIVE, + idle = POWER_STATE_IDLE, + sleep = POWER_STATE_SLEEP, + deep_sleep = POWER_STATE_DEEP_SLEEP, +} + +pub const ClkDomain = struct { + id : u8, + enabled : bool, + gate_mode : ClkGateMode, + activity_count : u16, + last_active_cycle : u32, +} + +pub const SubthConfig = struct { + threshold_mv : u16, + hysteresis_mv : u16, + enable_subth : bool, + transition_cycles : u8, // Cycles for voltage transition +} + +pub const PowerMetrics = struct { + current_voltage_mv : u16, + current_power_mw : u16, + activity_ratio : u8, // 0-100% + clock_gated_domains : u8, +} + +// ============================================================================ +// Clk Domain Functions +// ============================================================================ + +// clk_domain_enabled(domain: ClkDomain) -> bool +// Check if clock domain is enabled +pub fn clk_domain_enabled(domain: ClkDomain) bool { + return domain.enabled and domain.gate_mode != ClkGateMode.off; +} + +// clk_domain_gate_active(domain: ClkDomain) -> bool +// Check if clock gating is active +pub fn clk_domain_gate_active(domain: ClkDomain) bool { + return domain.gate_mode == ClkGateMode.off or + (domain.gate_mode == ClkGateMode.auto and domain.activity_count == 0); +} + +// clk_domain_is_idle(domain: ClkDomain, current_cycle: u32, idle_threshold: u32) -> bool +// Check if domain is idle +pub fn clk_domain_is_idle(domain: ClkDomain, current_cycle: u32, idle_threshold: u32) bool { + return domain.activity_count == 0 and + (current_cycle - domain.last_active_cycle) > idle_threshold; +} + +// clk_domain_pulse(domain: ClkDomain, current_cycle: u32) -> ClkDomain +// Record activity pulse on domain +pub fn clk_domain_pulse(domain: ClkDomain, current_cycle: u32) ClkDomain { + return ClkDomain { + .id = domain.id, + .enabled = domain.enabled, + .gate_mode = domain.gate_mode, + .activity_count = domain.activity_count + 1, + .last_active_cycle = current_cycle, + }; +} + +// clk_domain_decay(domain: ClkDomain) -> ClkDomain +// Decay activity count +pub fn clk_domain_decay(domain: ClkDomain) ClkDomain { + const new_count = if (domain.activity_count > 0) domain.activity_count - 1 else 0; + return ClkDomain { + .id = domain.id, + .enabled = domain.enabled, + .gate_mode = domain.gate_mode, + .activity_count = new_count, + .last_active_cycle = domain.last_active_cycle, + }; +} + +// ============================================================================ +// Sub-threshold Functions +// ============================================================================ + +// is_subthreshold(voltage_mv: u16, config: SubthConfig) -> bool +// Check if voltage is in sub-threshold region +pub fn is_subthreshold(voltage_mv: u16, config: SubthConfig) bool { + if (not config.enable_subth) { + return false; + } + return voltage_mv < config.threshold_mv; +} + +// voltage_within_range(voltage_mv: u16, target_mv: u16, tolerance_mv: u16) -> bool +// Check if voltage is within tolerance of target +pub fn voltage_within_range(voltage_mv: u16, target_mv: u16, tolerance_mv: u16) bool { + if (voltage_mv >= target_mv) { + return (voltage_mv - target_mv) <= tolerance_mv; + } + return (target_mv - voltage_mv) <= tolerance_mv; +} + +// subth_power_estimate(voltage_mv: u16) -> u16 +// Estimate power at given voltage (quadratic scaling) +pub fn subth_power_estimate(voltage_mv: u16) -> u16 { + // Power scales with V^2, normalize to 1.1V = 100mW (arbitrary baseline) + const ratio = @as(f32, @floatFromInt(voltage_mv)) / 1100.0; + const power_baseline : f32 = 100.0; + const estimated : f32 = power_baseline * ratio * ratio; + return @as(u16, @intFromFloat(@floor(estimated))); +} + +// power_savings_ratio(normal_mv: u16, subth_mv: u16) -> u8 +// Calculate power savings ratio (0-100%) +pub fn power_savings_ratio(normal_mv: u16, subth_mv: u16) -> u8 { + const normal_power = subth_power_estimate(normal_mv); + const subth_power = subth_power_estimate(subth_mv); + + if (normal_power == 0) { + return 0; + } + + const saved = normal_power - subth_power; + const ratio = (saved * 100) / normal_power; + + return @as(u8, @truncate(ratio)); +} + +// ============================================================================ +// Power State Functions +// ============================================================================ + +// power_state_can_gate(state: PowerState) -> bool +// Check if power state allows clock gating +pub fn power_state_can_gate(state: PowerState) bool { + return state != PowerState.active; +} + +// power_state_deeper(a: PowerState, b: PowerState) -> PowerState +// Return deeper power state +pub fn power_state_deeper(a: PowerState, b: PowerState) PowerState { + if (@intFromEnum(a) > @intFromEnum(b)) { + return a; + } + return b; +} + +// power_state_sleep_ok(domains: [NUM_CLK_DOMAINS]ClkDomain, current_cycle: u32) -> bool +// Check if all domains can go to sleep +pub fn power_state_sleep_ok(domains: [NUM_CLK_DOMAINS]ClkDomain, current_cycle: u32) bool { + for (domains) |domain| { + if (not clk_domain_is_idle(domain, current_cycle, 100)) { + return false; + } + } + return true; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_subth_clk(domain_id: u8, mode: u8) -> u16 +// Encode sub-threshold clock instruction +pub fn encode_subth_clk(domain_id: u8, mode: u8) u16 { + // Format: [OP:8][DOMAIN:3][MODE:2][RES:3] + const op : u16 = @as(u16, OP_SUBTH_CLK) << 8; + const domain_field : u16 = @as(u16, domain_id & 0x07) << 5; + const mode_field : u16 = @as(u16, mode & 0x03) << 3; + return op | domain_field | mode_field; +} + +// decode_subth_clk(encoded: u16) -> struct { domain_id: u8, mode: u8 } +// Decode sub-threshold clock instruction +pub fn decode_subth_clk(encoded: u16) struct { domain_id: u8, mode: u8 } { + const domain_id : u8 = @as(u8, @truncate((encoded >> 5) & 0x07)); + const mode : u8 = @as(u8, @truncate((encoded >> 3) & 0x03)); + return .{ .domain_id = domain_id, .mode = mode }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "num_clk_domains_eight" { + try std.testing.expect(NUM_CLK_DOMAINS == 8); +} + +test "subth_threshold_four_hundred" { + try std.testing.expect(SUBTH_THRESHOLD_MV == 400); +} + +test "normal_voltage_eleven_hundred" { + try std.testing.expect(NORMAL_VOLTAGE_MV == 1100); +} + +test "clk_gate_mode_off" { + try std.testing.expect(@intFromEnum(ClkGateMode.off) == 0); +} + +test "clk_gate_mode_on" { + try std.testing.expect(@intFromEnum(ClkGateMode.on) == 1); +} + +test "clk_gate_mode_auto" { + try std.testing.expect(@intFromEnum(ClkGateMode.auto) == 2); +} + +test "power_state_active" { + try std.testing.expect(@intFromEnum(PowerState.active) == 0); +} + +test "power_state_deep_sleep" { + try std.testing.expect(@intFromEnum(PowerState.deep_sleep) == 3); +} + +test "clk_domain_enabled_true" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_enabled(domain) == true); +} + +test "clk_domain_enabled_false_gate_off" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.off, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_enabled(domain) == false); +} + +test "clk_domain_enabled_false_disabled" { + given domain = ClkDomain{.id = 0, .enabled = false, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_enabled(domain) == false); +} + +test "clk_domain_gate_active_off" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.off, .activity_count = 10, .last_active_cycle = 0} + try std.testing.expect(clk_domain_gate_active(domain) == true); +} + +test "clk_domain_gate_active_auto_idle" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.auto, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_gate_active(domain) == true); +} + +test "clk_domain_gate_active_auto_active" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.auto, .activity_count = 10, .last_active_cycle = 0} + try std.testing.expect(clk_domain_gate_active(domain) == false); +} + +test "clk_domain_is_idle_true" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_is_idle(domain, 200, 100) == true); +} + +test "clk_domain_is_idle_false_active" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 10, .last_active_cycle = 0} + try std.testing.expect(clk_domain_is_idle(domain, 200, 100) == false); +} + +test "clk_domain_is_idle_false_recent" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 150} + try std.testing.expect(clk_domain_is_idle(domain, 200, 100) == false); +} + +test "clk_domain_pulse" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_pulse(domain, 100)); + try std.testing.expect(result.activity_count == 1); + try std.testing.expect(result.last_active_cycle == 100); +} + +test "clk_domain_decay" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 5, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_decay(domain)); + try std.testing.expect(result.activity_count == 4); +} + +test "clk_domain_decay_zero" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_decay(domain)); + try std.testing.expect(result.activity_count == 0); +} + +test "is_subthreshold_true" { + given config = SubthConfig{.threshold_mv = 400, .hysteresis_mv = 20, .enable_subth = true, .transition_cycles = 10} + try std.testing.expect(is_subthreshold(300, config) == true); +} + +test "is_subthreshold_false_disabled" { + given config = SubthConfig{.threshold_mv = 400, .hysteresis_mv = 20, .enable_subth = false, .transition_cycles = 10} + try std.testing.expect(is_subthreshold(300, config) == false); +} + +test "is_subthreshold_false_high_voltage" { + given config = SubthConfig{.threshold_mv = 400, .hysteresis_mv = 20, .enable_subth = true, .transition_cycles = 10} + try std.testing.expect(is_subthreshold(500, config) == false); +} + +test "voltage_within_range_true" { + try std.testing.expect(voltage_within_range(1100, 1100, 50) == true); +} + +test "voltage_within_range_low" { + try std.testing.expect(voltage_within_range(1060, 1100, 50) == true); +} + +test "voltage_within_range_high" { + try std.testing.expect(voltage_within_range(1140, 1100, 50) == true); +} + +test "voltage_within_range_false" { + try std.testing.expect(voltage_within_range(1000, 1100, 50) == false); +} + +test "subth_power_estimate_normal" { + try std.testing.expect(subth_power_estimate(1100) == 100); +} + +test "subth_power_estimate_half" { + try std.testing.expect(subth_power_estimate(550) == 25); +} + +test "subth_power_estimate_quarter" { + try std.testing.expect(subth_power_estimate(275) == 6); +} + +test "power_savings_ratio_fifty_percent" { + given result = power_savings_ratio(1100, 777) + try std.testing.expect(result == 50); +} + +test "power_savings_ratio_seventy_five_percent" { + given result = power_savings_ratio(1100, 550) + try std.testing.expect(result == 75); +} + +test "power_state_can_gate_active" { + try std.testing.expect(power_state_can_gate(PowerState.active) == false); +} + +test "power_state_can_gate_idle" { + try std.testing.expect(power_state_can_gate(PowerState.idle) == true); +} + +test "power_state_can_gate_sleep" { + try std.testing.expect(power_state_can_gate(PowerState.sleep) == true); +} + +test "power_state_deeper" { + try std.testing.expect(power_state_deeper(PowerState.idle, PowerState.deep_sleep) == PowerState.deep_sleep); + try std.testing.expect(power_state_deeper(PowerState.deep_sleep, PowerState.idle) == PowerState.deep_sleep); +} + +test "power_state_sleep_ok_true" { + given domains = [_]ClkDomain{ + .{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}, + .{.id = 1, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}, + } ** 8 + try std.testing.expect(power_state_sleep_ok(domains, 200) == true); +} + +test "power_state_sleep_ok_false" { + given domains = [_]ClkDomain{ + .{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 10, .last_active_cycle = 0}, + .{.id = 1, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}, + } ** 8 + try std.testing.expect(power_state_sleep_ok(domains, 200) == false); +} + +test "encode_subth_clk" { + given encoded = encode_subth_clk(0x05, 0x02) + try std.testing.expect((encoded >> 8) == OP_SUBTH_CLK); +} + +test "decode_subth_clk" { + given decoded = decode_subth_clk(0xE5A8) + try std.testing.expect(decoded.domain_id == 0x05); + try std.testing.expect(decoded.mode == 0x02); +} + +test "opcode_constant" { + try std.testing.expect(OP_SUBTH_CLK == 0xE5); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant num_clk_domains_eight + assert NUM_CLK_DOMAINS == 8 + +invariant subth_threshold_bound + assert SUBTH_THRESHOLD_MV < NORMAL_VOLTAGE_MV + +invariant power_estimate_positive + try std.testing.expect(subth_power_estimate(100) >= 0); + +invariant power_savings_bound + try std.testing.expect(power_savings_ratio(1100, 550) <= 100); + +invariant power_state_enum_range + try std.testing.expect(@intFromEnum(PowerState.active) >= 0 and @intFromEnum(PowerState.deep_sleep) <= 3); + +invariant clk_gate_mode_enum_range + try std.testing.expect(@intFromEnum(ClkGateMode.off) >= 0 and @intFromEnum(ClkGateMode.auto) <= 2); + +invariant clk_domain_pulse_increases_activity + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_pulse(domain, 100)); + assert result.activity_count > domain.activity_count + +invariant clk_domain_decay_decreases_or_same + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 5, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_decay(domain)); + assert result.activity_count <= domain.activity_count + +invariant power_savings_symmetric + assert power_savings_ratio(1100, 550) == power_savings_ratio(550, 1100) + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench clk_domain_is_idle_latency + measure: nanoseconds to clk_domain_is_idle(.{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}, 200, 100) + target: < 50ns + +bench is_subthreshold_latency + measure: nanoseconds to is_subthreshold(350, .{.threshold_mv = 400, .hysteresis_mv = 20, .enable_subth = true, .transition_cycles = 10}) + target: < 50ns + +bench subth_power_estimate_latency + measure: nanoseconds to subth_power_estimate(550) + target: < 50ns + +bench power_state_sleep_ok_latency + measure: nanoseconds to power_state_sleep_ok([_]ClkDomain{.{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}} ** 8, 200) + target: < 100ns + +bench encode_subth_clk_latency + measure: nanoseconds to encode_subth_clk(0x05, 0x02) + target: < 20ns + +bench decode_subth_clk_latency + measure: nanoseconds to decode_subth_clk(0xE5A8) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/binary16.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/binary16.t27 new file mode 100644 index 0000000000..bb2b71e71f --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/binary16.t27 @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/binary16.t27 +// Binary16 - Binary packed 16-bit format (3 bits per integer) +// NUMERIC-STANDARD-001 Agent 13 (P1) +// +// Binary16 format: +// - Packs 5 signed integers (3 bits each) into 16 bits +// - Each integer range: -4 to 3 +// - Total: 5 * 3 = 15 bits + 1 padding bit +// - Optimal for binary neural networks +// - Extreme quantization for ternary/-1/0/+1 operations + +module Binary16 { + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // Binary16 bit layout: [P(1) I0(3) I1(3) I2(3) I3(3) I4(3)] + // P: 1 bit (padding/unused) + // I0-I4: 3 bits each (signed two's complement integer) + // + // 5 integers stored, each using 3 bits: + // Range per integer: -4 to 3 + // Storage: Two's complement in 3 bits + // + // Use cases: + // - Binary neural networks + // - Ternary weight quantization + // - Compact activation storage + // - Sparse mask encoding + + const BITS : u8 = 16; + const INT_BITS : u8 = 3; + const NUM_INTS : u8 = 5; + const PADDING_BITS : u8 = 1; + + // 2. Binary16 Type + + struct Binary16 { + raw : u16, // 16-bit packed value + } + + // 3. Encoding/Decoding + + // Get a specific integer from the packed value + fn get_int(b: Binary16, index: u8) -> i8 { + if (index >= NUM_INTS) { return 0; } + // Extract 3-bit value at position index + const bit_offset = index * INT_BITS + PADDING_BITS; + const mask = 0x07; + const raw_val = ((b.raw >> bit_offset) & mask) as u8; + + // Convert 3-bit two's complement to i8 + if (raw_val >= 4) { + return (raw_val as i8) - 8; + } + return raw_val as i8; + } + + // Set a specific integer in the packed value + fn set_int(b: Binary16, index: u8, value: i8) -> Binary16 { + if (index >= NUM_INTS) { return b; } + + // Clamp value to 3-bit range + const clamped = if (value > 3) { 3 } else if (value < -4) { -4 } else { value }; + + // Convert i8 to 3-bit two's complement + let raw_val : u8 = clamped as u8; + if (clamped < 0) { + raw_val = raw_val + 8; + } + + // Clear and set the 3 bits + const bit_offset = index * INT_BITS + PADDING_BITS; + const mask = 0x07; + const cleared = b.raw & (~(mask << bit_offset) as u16); + const packed = cleared | ((raw_val as u16) << bit_offset); + + return Binary16{ raw = packed }; + } + + // Encode array of 5 i8 values to Binary16 + fn encode(values: [i8; NUM_INTS]) -> Binary16 { + let result = Binary16{ raw = 0 }; + for (i in 0..NUM_INTS) { + result = set_int(result, i, values[i]); + } + return result; + } + + // Decode Binary16 to array of 5 i8 values + fn decode(b: Binary16) -> [i8; NUM_INTS] { + let result = [0; NUM_INTS]; + for (i in 0..NUM_INTS) { + result[i] = get_int(b, i); + } + return result; + } + + // 4. Format Properties + + fn min_value() -> i8 { + return -4; + } + + fn max_value() -> i8 { + return 3; + } + + fn range() -> i8 { + return max_value() - min_value(); + } + + fn num_values_per_int() -> i8 { + return 8; + } + + // 5. Validation + + fn validate_format() -> bool { + return (BITS == 16) && + (INT_BITS == 3) && + (NUM_INTS == 5) && + (PADDING_BITS == 1); + } + + // 6. Use Cases + + // Binary16 is optimal for: + // - Binary neural networks (weights in {-1, 0, +1}) + // - Ternary quantization + // - Compact activation masks + // - Sparse representation encoding + // - Extreme memory compression (8x FP32) + + // Memory: 16 bits = 2 bytes (16x FP32 in same space, stores 5 integers) + const MEMORY_RATIO_VS_FP32 : f32 = 16.0 / 32.0; // 0.5 (for 16-bit word) + + // 7. Arithmetic Operations (element-wise) + + fn add(a: Binary16, b: Binary16, index: u8) -> Binary16 { + const val_a = get_int(a, index); + const val_b = get_int(b, index); + const result = val_a + val_b; + return set_int(a, index, result); + } + + fn sub(a: Binary16, b: Binary16, index: u8) -> Binary16 { + const val_a = get_int(a, index); + const val_b = get_int(b, index); + const result = val_a - val_b; + return set_int(a, index, result); + } + + fn neg(b: Binary16, index: u8) -> Binary16 { + const val = get_int(b, index); + return set_int(b, index, -val); + } + + // 8. Comparison Operations (element-wise) + + fn eq(a: Binary16, b: Binary16) -> bool { + return a.raw == b.raw; + } + + fn eq_at(a: Binary16, b: Binary16, index: u8) -> bool { + return get_int(a, index) == get_int(b, index); + } + + fn lt(a: Binary16, b: Binary16, index: u8) -> bool { + return get_int(a, index) < get_int(b, index); + } + + // 9. Special Operations for BNNs + + // Count non-zero values (useful for sparsity metrics) + fn count_nonzero(b: Binary16) -> u8 { + let count : u8 = 0; + for (i in 0..NUM_INTS) { + if (get_int(b, i) != 0) { + count = count + 1; + } + } + return count; + } + + // Check if any value is positive + fn any_positive(b: Binary16) -> bool { + for (i in 0..NUM_INTS) { + if (get_int(b, i) > 0) { + return true; + } + } + return false; + } + + // Check if any value is negative + fn any_negative(b: Binary16) -> bool { + for (i in 0..NUM_INTS) { + if (get_int(b, i) < 0) { + return true; + } + } + return false; + } + + // TDD-Inside-Spec: Tests and Invariants for Binary16 + + test binary16_decode_zero + given b = Binary16{ raw = 0 } + when decoded = decode(b) + then decoded == [0, 0, 0, 0, 0] + + test binary16_encode_zero_roundtrip + given original = [0, 0, 0, 0, 0] + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test binary16_encode_positive_values + given vals = [0, 1, 2, 3, 1] + and encoded = encode(vals) + then decode(encoded) == vals + + test binary16_encode_negative_values + given vals = [-1, -2, -3, -4, -1] + and encoded = encode(vals) + then decode(encoded) == vals + + test binary16_get_int_at_index + given b = encode([1, 2, 3, -1, -2]) + then get_int(b, 0) == 1 and get_int(b, 2) == 3 and get_int(b, 3) == -1 + + test binary16_set_int_at_index + given b = encode([0, 0, 0, 0, 0]) + and result = set_int(b, 2, 3) + then get_int(result, 2) == 3 + + test binary16_set_int_preserves_others + given b = encode([1, 2, 0, -1, -2]) + and result = set_int(b, 2, 3) + then get_int(result, 0) == 1 and get_int(result, 1) == 2 and get_int(result, 3) == -1 + + test binary16_bits_constant + then BITS == 16 + + test binary16_int_bits_constant + then INT_BITS == 3 + + test binary16_num_ints_constant + then NUM_INTS == 5 + + test binary16_padding_bits_constant + then PADDING_BITS == 1 + + test binary16_min_value + then min_value() == -4 + + test binary16_max_value + then max_value() == 3 + + test binary16_range + then range() == 7 + + test binary16_num_values_per_int + then num_values_per_int() == 8 + + test binary16_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.5) < 0.01 + + test binary16_validate_format_success + given valid = validate_format() + then valid == true + + test binary16_clamp_positive + given b = encode([0, 0, 0, 0, 0]) + and result = set_int(b, 0, 10) + then get_int(result, 0) == 3 + + test binary16_clamp_negative + given b = encode([0, 0, 0, 0, 0]) + and result = set_int(b, 0, -10) + then get_int(result, 0) == -4 + + test binary16_add_positive + given a = encode([1, 0, 0, 0, 0]) + and b = encode([2, 0, 0, 0, 0]) + and result = add(a, b, 0) + then get_int(result, 0) == 3 + + test binary16_add_overflow + given a = encode([3, 0, 0, 0, 0]) + and b = encode([2, 0, 0, 0, 0]) + and result = add(a, b, 0) + then get_int(result, 0) == 3 + + test binary16_sub_positive + given a = encode([3, 0, 0, 0, 0]) + and b = encode([1, 0, 0, 0, 0]) + and result = sub(a, b, 0) + then get_int(result, 0) == 2 + + test binary16_neg_positive + given b = encode([2, 0, 0, 0, 0]) + and result = neg(b, 0) + then get_int(result, 0) == -2 + + test binary16_eq_true + given a = encode([1, 2, 3, -1, -2]) + and b = encode([1, 2, 3, -1, -2]) + then eq(a, b) == true + + test binary16_eq_false + given a = encode([1, 2, 3, -1, -2]) + and b = encode([1, 2, 0, -1, -2]) + then eq(a, b) == false + + test binary16_eq_at_true + given a = encode([1, 2, 3, -1, -2]) + and b = encode([0, 0, 3, 0, 0]) + then eq_at(a, b, 2) == true + + test binary16_lt_true + given a = encode([1, 0, 0, 0, 0]) + and b = encode([3, 0, 0, 0, 0]) + then lt(a, b, 0) == true + + test binary16_count_nonzero_zero + given b = encode([0, 0, 0, 0, 0]) + then count_nonzero(b) == 0 + + test binary16_count_nonzero_some + given b = encode([0, 1, 0, -1, 0]) + then count_nonzero(b) == 2 + + test binary16_count_nonzero_all + given b = encode([1, 2, 3, -1, -2]) + then count_nonzero(b) == 5 + + test binary16_any_positive_true + given b = encode([0, 1, -1, 0, 0]) + then any_positive(b) == true + + test binary16_any_positive_false + given b = encode([0, 0, -1, -2, 0]) + then any_positive(b) == false + + test binary16_any_negative_true + given b = encode([0, 1, -1, 0, 0]) + then any_negative(b) == true + + test binary16_any_negative_false + given b = encode([0, 1, 2, 3, 0]) + then any_negative(b) == false + + test binary16_all_indices_range + given b = encode([0, 0, 0, 0, 0]) + and result = set_int(set_int(set_int(set_int(set_int(b, 0, -4), 1, -3), 2, -2), 3, -1), 4, 0) + then get_int(result, 0) == -4 and get_int(result, 4) == 0 + + invariant binary16_bits_constant + assert BITS == 16 + + invariant binary16_int_bits_is_three + assert INT_BITS == 3 + + invariant binary16_num_ints_is_five + assert NUM_INTS == 5 + + invariant binary16_min_value_is_neg_four + assert min_value() == -4 + + invariant binary16_max_value_is_three + assert max_value() == 3 + + invariant binary16_range_is_seven + assert range() == 7 + + invariant binary16_encode_decode_roundtrip + given vals = [1, 2, 3, -1, -2] + then decode(encode(vals)) == vals + + invariant binary16_get_set_consistency + given b = encode([0, 0, 0, 0, 0]) + and val = 2 + then get_int(set_int(b, 3, val), 3) == val + + invariant binary16_count_nonzero_in_range + for (b in {encode([0,0,0,0,0]), encode([1,0,0,0,0]), encode([1,2,3,-1,-2])}) + then count_nonzero(b) >= 0 and count_nonzero(b) <= 5 + + invariant binary16_packing_efficiency + assert NUM_INTS * INT_BITS + PADDING_BITS == BITS + + bench binary16_encode_latency + measure: nanoseconds to encode([1, 2, 3, -1, -2]) + target: < 50ns + + bench binary16_decode_latency + measure: nanoseconds to decode(encode([1, 2, 3, -1, -2])) + target: < 40ns + + bench binary16_get_int_latency + measure: nanoseconds to get_int(encode([1, 2, 3, -1, -2]), 2) + target: < 20ns + + bench binary16_set_int_latency + measure: nanoseconds to set_int(encode([0, 0, 0, 0, 0]), 2, 3) + target: < 30ns + + bench binary16_count_nonzero_latency + measure: nanoseconds to count_nonzero(encode([1, 2, 0, -1, 0])) + target: < 50ns + + bench binary16_eq_latency + measure: nanoseconds to eq(encode([1, 2, 3, -1, -2]), encode([1, 2, 3, -1, -2])) + target: < 10ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/formats.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/formats.t27 new file mode 100644 index 0000000000..90cda34b16 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/formats.t27 @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/numeric/formats.t27 +// Format Conversion Utilities - GF16, f32, ternary encoding +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Formats { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; + use numeric::gf16; + + // ======================================================================== + // 1. GF16 Bit Layout Constants + // ======================================================================== + // + // GF16 bit layout (as specified in whitepaper): + // [S(1) E(6) M(9)] = [15:15][14:9][8:0] + // + // - Sign: bit 15 (0x8000) + // - Exponent: bits 14-9 (0x7E00), bias = 31 + // - Mantissa: bits 8-0 (0x01FF) + // + // Range: 2^-31 to 2^32 + // ======================================================================== + + pub const SignMask : u16 = 0x8000; + pub const ExpMask : u16 = 0x7E00; + pub const MantMask : u16 = 0x01FF; + + pub const ExpShift : u5 = 9; + pub const SignShift : u4 = 15; + pub const Bias : i32 = 31; + + pub const ExpMax : u16 = 63; + pub const ExpMin : u16 = 0; + + // ======================================================================== + // 2. GF16 0 f32 (decode) + // ======================================================================== + // + // Converts GF16 encoding to IEEE 754 binary32 floating point. + // Handles signed zero, denormals, normals, infinities, and NaN. + // ======================================================================== + + // gf16_to_f32(x: u16) -> gf16 + // Decode GF16 to f32 + // + // Algorithm: + // 1. Extract sign (bit 15) + // 2. Extract exponent (bits 14-9) and mantissa (bits 8-0) + // 3. Handle special cases: + // - e=0, m=0: signed zero + // - e=0, m!=0: denormal (subnormal) + // - e=ExpMax, m=0: +/- infinity + // - e=ExpMax, m!=0: NaN + // 4. Normal case: + // value = (-1)^s * (1 + m/2^9) * 2^(e - Bias) + // + // Complexity: O(1) + pub fn gf16_to_f32(x: u16) -> gf16; + + // ======================================================================== + // 3. f32 1 GF16 (encode, round-to-nearest) + // ======================================================================== + // + // Converts IEEE 754 binary32 floating point to GF16 encoding. + // Handles signed zero, special cases, overflow, and underflow. + // + // Algorithm: + // 1. Handle signed zero explicitly (sign bit preserved) + // 2. Handle special cases (Inf, NaN) + // 3. Get exponent and mantissa via frexp: abs = m * 2^e, m in [0.5, 1] + // 4. Normalize: want 1.x * 2^(E - Bias), frexp gives m in [0.5, 1] + // 5. Mantissa: (m - 1.0) * 2^9, round to nearest + // 6. Check underflow/overflow + // + // Complexity: O(1) + pub fn f32_to_gf16(a: f32) -> u16; + + // ======================================================================== + // 4. Ternary Quantization + // ======================================================================== + // + // Ternary quantization: maps f32 to ternary {-1, 0, +1} + // Threshold: |w| > 0.5 -> +/-1, else -> 0 + // + // WHY: Enables efficient ternary representation of continuous values + // Useful for VSA (Vector Symbolic Architecture) operations + // ======================================================================== + + // f32_to_ternary(x: f32) -> Trit + // Quantize f32 to ternary {-1, 0, +1} + // + // Algorithm: + // - If x > 0.5: return +1 + // - If x < -0.5: return -1 + // - Otherwise: return 0 + // + // Complexity: O(1) + pub fn f32_to_ternary(x: f32) -> Trit; + + // ternary_to_f32(t: Trit) -> gf16 + // Convert ternary to f32 + // + // Mapping: -1 -> -1.0, 0 -> 0.0, +1 -> 1.0 + // Complexity: O(1) + pub fn ternary_to_f32(t: Trit) -> gf16; + + // ======================================================================== + // 5. Format Enum + // ======================================================================== + // + // Comprehensive format support: + // - IEEE Floating Point: fp32, fp16, bf16, fp8_e4m3, fp8_e5m2 + // - GoldenFloat: gf4, gf8, gf12, gf16 (PRIMARY), gf20, gf24, gf32, gf64, gf128, gf256 + // - Integer: int4, int8 + // - Quantization: nf4, binary16, ternary, posit16 + // ======================================================================== + + pub const Format = enum(u8) { + // IEEE Floating Point formats + fp32, + fp16, + bf16, + fp8_e4m3, + fp8_e5m2, + + // GoldenFloat family (φ-optimized) + gf4, + gf8, + gf12, + gf16, + gf20, + gf24, + gf32, + gf64, + gf128, + gf256, + + // Integer formats + int4, + int8, + + // Quantization formats + nf4, + binary16, + ternary, + + // Posit type + posit16, + }; + + // format_bytes(fmt: Format) -> usize + // Returns byte size for each format + // + // Byte sizes: + // - fp32: 4 bytes (32 bits) + // - fp16, bf16, gf16, int8: 2 bytes (16 bits) + // - fp8_e4m3, fp8_e5m2, gf8, int4: 1 byte (8 bits) + // - gf4: 0.5 bytes (4 bits, packed) + // - nf4: 0.5 bytes (4 bits, packed) + // - binary16: 2 bytes (16 bits, stores 5 ints) + // - gf12: 1.5 bytes (12 bits) + // - gf20: 2.5 bytes (20 bits) + // - gf24: 3 bytes (24 bits) + // - gf32: 4 bytes (32 bits) + // - gf64: 8 bytes (64 bits) + // - gf128: 16 bytes (128 bits) + // - gf256: 32 bytes (256 bits) + // - ternary: 1 byte (2 bits packed) + // - posit16: 2 bytes (16 bits) + // + // Complexity: O(1) + pub fn format_bytes(fmt: Format) -> usize; + + // ======================================================================== + // 6. Quantization Utility + // ======================================================================== + + // quantize_value(x: f32, fmt: Format) -> gf16 + // Quantize f32 to target format + // + // Complexity: O(1) + pub fn quantize_value(x: f32, fmt: Format) -> gf16; + + // ======================================================================== + // TDD - Tests + // ======================================================================== + + test gf16_to_f32_zero_positive + // Verify: zero encodes to zero + given x: u16 = 0 + when result = gf16_to_f32(x) + then gf16.to_f64(result) == 0.0 + + test gf16_to_f32_zero_negative + // Verify: negative zero encodes to -0 + given x: u16 = 0x8000 + when result = gf16_to_f32(x) + then gf16.to_f64(result) == -0.0 + + test gf16_to_f32_denormal + // Verify: denormal value decodes to small positive + given x: u16 = 0x0080 + when result = gf16_to_f32(x) + and val = gf16.to_f64(result) + then val > 0.0 and val < 1.0 + + test gf16_to_f32_normal_one + // Verify: 1.0 encodes correctly + given x: u16 = 0x3C00 + when result = gf16_to_f32(x) + and decoded = gf16.to_f64(result) + then decoded == 1.0 + + test gf16_to_f32_positive_inf + // Verify: positive infinity encodes correctly + given x: u16 = 0x7E00 + when result = gf16_to_f32(x) + and decoded = gf16.to_f64(result) + then decoded == std.math.inf(f32) + + test gf16_to_f32_negative_inf + // Verify: negative infinity encodes correctly + given x: u16 = 0xFE00 + when result = gf16_to_f32(x) + and decoded = gf16.to_f64(result) + then decoded == -std.math.inf(f32) + + test gf16_to_f32_nan + // Verify: NaN encodes to NaN + given x: u16 = 0x7F01 + when result = gf16_to_f32(x) + then result != result // NaN check + + test f32_to_gf16_zero_positive + // Verify: +0 encodes to 0 + given a: f32 = 0.0 + when result = f32_to_gf16(a) + then result == 0 + + test f32_to_gf16_zero_negative + // Verify: -0 encodes to 0x8000 + given a: f32 = -0.0 + when result = f32_to_gf16(a) + then result == 0x8000 + + test f32_to_gf16_one + // Verify: 1.0 encodes and roundtrips correctly + given a: f32 = 1.0 + and encoded = f32_to_gf16(a) + and decoded = gf16_to_f32(encoded) + and recovered = gf16.to_f64(decoded) + then recovered >= 0.99 and recovered <= 1.01 + + test f32_to_gf16_inf_positive + // Verify: +Inf encodes to 0x7E00 + given a: f32 = std.math.inf(f32) + when result = f32_to_gf16(a) + then result == 0x7E00 + + test f32_to_gf16_inf_negative + // Verify: -Inf encodes to 0xFE00 + given a: f32 = -std.math.inf(f32) + when result = f32_to_gf16(a) + then result == 0xFE00 + + test f32_to_gf16_nan + // Verify: NaN encodes to 0x7F01 + given a: f32 = std.math.nan(f32) + when result = f32_to_gf16(a) + then result == 0x7F01 + + test f32_to_ternary_positive + // Verify: 1.0 quantizes to pos + given a: f32 = 1.0 + when result = f32_to_ternary(a) + then result == .pos + + test f32_to_ternary_zero + // Verify: 0.0 quantizes to zero + given a: f32 = 0.0 + when result = f32_to_ternary(a) + then result == .zero + + test f32_to_ternary_negative + // Verify: -1.0 quantizes to neg + given a: f32 = -1.0 + when result = f32_to_ternary(a) + then result == .neg + + test f32_to_ternary_threshold + // Verify: 0.6 quantizes to pos (above 0.5 threshold) + given a: f32 = 0.6 + when result = f32_to_ternary(a) + then result == .pos + + test f32_to_ternary_negative_threshold + // Verify: -0.6 quantizes to neg (below -0.5 threshold) + given a: f32 = -0.6 + when result = f32_to_ternary(a) + then result == .neg + + test ternary_to_f32_positive + // Verify: pos maps to 1.0 + given t: Trit = .pos + when result = ternary_to_f32(t) + and result_val = gf16.to_f64(result) + then result_val == 1.0 + + test ternary_to_f32_zero + // Verify: zero maps to 0.0 + given t: Trit = .zero + when result = ternary_to_f32(t) + and result_val = gf16.to_f64(result) + then result_val == 0.0 + + test ternary_to_f32_negative + // Verify: neg maps to -1.0 + given t: Trit = .neg + when result = ternary_to_f32(t) + and result_val = gf16.to_f64(result) + then result_val == -1.0 + + test format_bytes_fp32 + // Verify: fp32 is 4 bytes + when result = format_bytes(.fp32) + then result == 4 + + test format_bytes_fp16 + // Verify: fp16 is 2 bytes + when result = format_bytes(.fp16) + then result == 2 + + test format_bytes_bf16 + // Verify: bf16 is 2 bytes + when result = format_bytes(.bf16) + then result == 2 + + test format_bytes_fp8_e4m3 + // Verify: fp8_e4m3 is 1 byte + when result = format_bytes(.fp8_e4m3) + then result == 1 + + test format_bytes_fp8_e5m2 + // Verify: fp8_e5m2 is 1 byte + when result = format_bytes(.fp8_e5m2) + then result == 1 + + test format_bytes_gf4 + // Verify: gf4 is 0.5 bytes (packed) + when result = format_bytes(.gf4) + then result == 0 + + test format_bytes_gf8 + // Verify: gf8 is 1 byte + when result = format_bytes(.gf8) + then result == 1 + + test format_bytes_gf16 + // Verify: gf16 is 2 bytes + when result = format_bytes(.gf16) + then result == 2 + + test format_bytes_gf32 + // Verify: gf32 is 4 bytes + when result = format_bytes(.gf32) + then result == 4 + + test format_bytes_gf64 + // Verify: gf64 is 8 bytes + when result = format_bytes(.gf64) + then result == 8 + + test format_bytes_gf128 + // Verify: gf128 is 16 bytes + when result = format_bytes(.gf128) + then result == 16 + + test format_bytes_gf256 + // Verify: gf256 is 32 bytes + when result = format_bytes(.gf256) + then result == 32 + + test format_bytes_int4 + // Verify: int4 is 0.5 bytes (packed) + when result = format_bytes(.int4) + then result == 0 + + test format_bytes_int8 + // Verify: int8 is 1 byte + when result = format_bytes(.int8) + then result == 1 + + test format_bytes_nf4 + // Verify: nf4 is 0.5 bytes (packed) + when result = format_bytes(.nf4) + then result == 0 + + test format_bytes_binary16 + // Verify: binary16 is 2 bytes + when result = format_bytes(.binary16) + then result == 2 + + test format_bytes_ternary + // Verify: ternary is 1 byte (packed) + when result = format_bytes(.ternary) + then result == 1 + + test format_bytes_posit16 + // Verify: posit16 is 2 bytes + when result = format_bytes(.posit16) + then result == 2 + + test quantize_value_fp32 + // Verify: quantizing to fp32 preserves value + given x: f32 = 1.5 + and result = quantize_value(x, .fp32) + and result_val = gf16.to_f64(result) + then result_val >= 1.49 and result_val <= 1.51 + + test quantize_value_ternary + // Verify: quantizing to ternary gives +1 + given x: f32 = 1.5 + and result = quantize_value(x, .ternary) + and expected = ternary_to_f32(.pos) + then result == expected + + test quantize_value_int8 + // Verify: quantizing to int8 clamps within range + given x: f32 = 200.0 + and result = quantize_value(x, .int8) + and result_val = gf16.to_f64(result) + then result_val <= 127.0 + + test quantize_value_nf4 + // Verify: quantizing to nf4 clamps to [0, 1] + given x: f32 = 1.5 + and result = quantize_value(x, .nf4) + and result_val = gf16.to_f64(result) + then result_val <= 1.0 + + // ======================================================================== + // TDD - Invariants + // ======================================================================== + + invariant gf16_to_f32_preserves_zero + // Zero should decode to zero + assert gf16.to_f64(gf16_to_f32(0)) == 0.0; + assert gf16.to_f64(gf16_to_f32(0x8000)) == -0.0; + + invariant gf16_to_f32_preserves_infinity + // Infinity should decode to infinity + assert gf16.to_f64(gf16_to_f32(0x7E00)) == std.math.inf(f32); + assert gf16.to_f64(gf16_to_f32(0xFE00)) == -std.math.inf(f32); + + invariant f32_to_gf16_roundtrip_loss + // Roundtrip should be within tolerance for normal values + const original = gf16.from_f64(1.5); + const encoded = f32_to_gf16(gf16.to_f64(original)); + const decoded = gf16_to_f32(encoded); + const error = gf16.abs(gf16.sub(original, decoded)); + assert gf16.to_f64(error) < gf16.from_f64(0.01); + + invariant ternary_quantization_symmetric + // Quantization threshold is symmetric + const p = f32_to_ternary(0.5); + const n = f32_to_ternary(-0.5); + const p_decoded = ternary_to_f32(p); + const n_decoded = ternary_to_f32(n); + assert gf16.to_f64(p_decoded) == -gf16.to_f64(n_decoded); + + invariant ternary_to_f32_is_inverse + // ternary_to_f32 is inverse of f32_to_ternary + const values = [_]f32{ -1.0, -0.5, 0.0, 0.5, 1.0 }; + for (values) |v| { + const t = f32_to_ternary(v); + const recovered = ternary_to_f32(t); + const diff = gf16.abs(gf16.sub(v, recovered)); + assert gf16.eq(v, recovered) or (gf16.to_f64(diff) < gf16.from_f64(0.01)); + } + + invariant format_bytes_positive + // All format byte sizes should be positive + assert format_bytes(.fp32) > 0; + assert format_bytes(.fp16) > 0; + assert format_bytes(.ternary) > 0; + + // ======================================================================== + // TDD - Benchmarks + // ======================================================================== + + bench gf16_to_f32_latency + // Measure: cycles for gf16_to_f32 conversion + // Target: < 50 cycles (simple bit extraction + lookup) + @setEvalBranchQuota(10000); + var result : gf16; + const test_value: u16 = 0x3C00; + for (0..1000) |_| { + result = gf16_to_f32(test_value); + } + _ = result; + + bench f32_to_gf16_latency + // Measure: cycles for f32_to_gf16 conversion + // Target: < 100 cycles (frexp + bit packing) + @setEvalBranchQuota(10000); + var result : u16; + const test_value: f32 = 1.5; + for (0..1000) |_| { + result = f32_to_gf16(test_value); + } + _ = result; + + bench f32_to_ternary_latency + // Measure: cycles for f32_to_ternary conversion + // Target: < 10 cycles (single comparison) + @setEvalBranchQuota(10000); + var result : Trit; + const test_value: f32 = 0.75; + for (0..1000) |_| { + result = f32_to_ternary(test_value); + } + _ = result; + + bench ternary_to_f32_latency + // Measure: cycles for ternary_to_f32 conversion + // Target: < 10 cycles (simple switch) + @setEvalBranchQuota(10000); + var result : gf16; + const test_trit: Trit = .pos; + for (0..1000) |_| { + result = ternary_to_f32(test_trit); + } + _ = result; +} diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/fp8_e4m3.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/fp8_e4m3.t27 new file mode 100644 index 0000000000..06fb882f2d --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/fp8_e4m3.t27 @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: Apache-2.0 +; fp8_e4m3.t27 — FP8 E4M3 8-bit Floating Point +; 8-bit float with 4 exponent, 3 mantissa bits (no implicit leading bit) +; OCP FP8 format optimized for neural network training +; Range: ~-240 to ~240, precision: ~6-7 significant bits +; φ² + 1/φ² = 3 | TRINITY + +module triformat-fp8_e4m3; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 4; +pub const MANT_BITS : u8 = 3; + +pub const SIGN_SHIFT : u8 = 7; +pub const EXP_SHIFT : u8 = 3; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u8 = 0x80; // 1 << 7 +pub const EXP_MASK : u8 = 0x78; // 0b01111000 +pub const MANT_MASK : u8 = 0x07; // 0b00000111 + +pub const EXP_MAX : u8 = 15; // All ones in 4 bits +pub const EXP_MIN : u8 = 0; +pub const EXP_BIAS : u8 = 7; // Exponent bias for FP8 E4M3 + +pub const MANT_DIVISOR : u8 = 8; // 2^3 + +// FP8 E4M3 special values +pub const FP8_ZERO_POS : u8 = 0x00; +pub const FP8_ZERO_NEG : u8 = 0x80; +pub const FP8_NAN_POS : u8 = 0x7F; // All exp + mant != 0 +pub const FP8_NAN_NEG : u8 = 0xFF; +pub const FP8_INF_POS : u8 = 0x78; // All exp + mant = 0 +pub const FP8_INF_NEG : u8 = 0xF8; + +// Subnormal exponent +pub const SUBNORMAL_EXP : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FP8E4M3 = u8; // 8-bit value + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// fp8_extract_sign(fp8: FP8E4M3) -> i8 +// Extract sign bit (bit 7) +// Returns: 0 for positive, -1 for negative +pub fn fp8_extract_sign(fp8: FP8E4M3) i8 { + const bit = (fp8 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// fp8_extract_exponent(fp8: FP8E4M3) -> u8 +// Extract exponent bits (bits 6-3) +// Returns: 0-15 +pub fn fp8_extract_exponent(fp8: FP8E4M3) u8 { + return (fp8 >> EXP_SHIFT) & 0x0F; +} + +// fp8_extract_mantissa(fp8: FP8E4M3) -> u8 +// Extract mantissa bits (bits 2-0) +// Returns: 0-7 +pub fn fp8_extract_mantissa(fp8: FP8E4M3) u8 { + return fp8 & MANT_MASK; +} + +// fp8_from_components(sign: i8, exp: u8, mant: u8) -> FP8E4M3 +// Assemble FP8 from sign, exponent, mantissa +pub fn fp8_from_components(sign: i8, exp: u8, mant: u8) FP8E4M3 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(FP8E4M3, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(FP8E4M3, @intCast(exp & 0x0F)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// fp8_is_zero(fp8: FP8E4M3) -> bool +// Check if FP8 is zero (positive or negative) +pub fn fp8_is_zero(fp8: FP8E4M3) bool { + return fp8 == FP8_ZERO_POS or fp8 == FP8_ZERO_NEG; +} + +// fp8_is_special(fp8: FP8E4M3) -> bool +// Check if FP8 is Inf or NaN (exp == 15) +pub fn fp8_is_special(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX; +} + +// fp8_is_inf(fp8: FP8E4M3) -> bool +// Check if FP8 is infinity (exp == 15, mant == 0) +pub fn fp8_is_inf(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) == 0; +} + +// fp8_is_nan(fp8: FP8E4M3) -> bool +// Check if FP8 is NaN (exp == 15, mant != 0) +pub fn fp8_is_nan(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) != 0; +} + +// fp8_is_subnormal(fp8: FP8E4M3) -> bool +// Check if FP8 is subnormal (exp == 0, mant != 0) +pub fn fp8_is_subnormal(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == 0 and fp8_extract_mantissa(fp8) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// fp8_encode_f32(value: f32) -> FP8E4M3 +// Encode IEEE 754 single precision to FP8 E4M3 +// Round-to-nearest, ties to even +pub fn fp8_encode_f32(value: f32) FP8E4M3 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return if (value < 0.0) FP8_NAN_NEG else FP8_NAN_POS; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i16 = @as(i16, @intCast((f32_bits >> 23) & 0xFF)) - 127; + var f32_mant: u32 = f32_bits & 0x007FFFFF; + + // Handle very small values (subnormals) + if (f32_exp <= @as(i16, -EXP_BIAS)) { + // Below FP8 minimum normal - try to encode as subnormal + const subnormal_shift = @as(i16, -EXP_BIAS) - f32_exp + 1; + if (subnormal_shift > 3 + 23) { + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + var mant = f32_mant >> 23; + if (subnormal_shift > 0) { + const shift_amount = @as(u5, @intCast(subnormal_shift)); + if (shift_amount <= 24) { + mant = (f32_mant | 0x00800000) >> shift_amount; + } + } + return fp8_from_components(sign, 0, @as(u8, @truncate(mant)) & 0x07); + } + + // Convert exp from f32 bias (127) to FP8 bias (7) + var fp8_exp = @as(i16, f32_exp + EXP_BIAS); + + // Clamp exponent + if (fp8_exp >= EXP_MAX) { + return if (sign < 0) FP8_INF_NEG else FP8_INF_POS; + } else if (fp8_exp < 1) { + // Underflow to zero (could be subnormal, simplified) + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Extract mantissa and scale to 3 bits + // f32 mantissa is 24 bits (including implicit 1), FP8 needs 3 bits + // Shift right by 21 bits (24 - 3 = 21) + var mant = f32_mant >> 21; + + // Round-to-nearest with ties to even + const discarded = f32_mant & 0x1FFFFF; // Lower 21 bits + if ((discarded & 0x100000) != 0) { + // Halfway or more + if ((discarded & 0x0FFFFF) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (fp8_exp < @as(i16, EXP_MAX - 1)) { + fp8_exp += 1; + } + } + } + } + + return fp8_from_components(sign, @as(u8, @intCast(fp8_exp)), @as(u8, mant)); +} + +// fp8_decode_f32(fp8: FP8E4M3) -> f32 +// Decode FP8 E4M3 to IEEE 754 single precision +pub fn fp8_decode_f32(fp8: FP8E4M3) f32 { + // Handle zero + if (fp8_is_zero(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (fp8_is_nan(fp8)) { + return std.math.nan(f32); + } + + // Handle Infinity + if (fp8_is_inf(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -std.math.inf(f32) else std.math.inf(f32); + } + + // Extract components + const sign = fp8_extract_sign(fp8); + const exp = fp8_extract_exponent(fp8); + const mant = fp8_extract_mantissa(fp8); + + var f32_value: f32 = undefined; + + // Handle subnormal + if (exp == 0) { + // Subnormal: value = mant * 2^(-6) = mant * 2^(1-bias) + const bias_adjusted: i8 = 1 - @as(i8, EXP_BIAS); + const mant_f32 = @as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR)); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } else { + // Normal: value = (1 + mant/8) * 2^(exp - 7) + const bias_adjusted = @as(i16, exp) - @as(i16, EXP_BIAS); + const mant_f32 = 1.0 + (@as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR))); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } + + return if (sign < 0) -f32_value else f32_value; +} + +// fp8_encode_f64(value: f64) -> FP8E4M3 +// Encode IEEE 754 double precision to FP8 E4M3 +pub fn fp8_encode_f64(value: f64) FP8E4M3 { + return fp8_encode_f32(@as(f32, @floatCast(value))); +} + +// fp8_decode_f64(fp8: FP8E4M3) -> f64 +// Decode FP8 E4M3 to IEEE 754 double precision +pub fn fp8_decode_f64(fp8: FP8E4M3) f64 { + const f32_val = fp8_decode_f32(fp8); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// fp8_add(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Add two FP8 values +pub fn fp8_add(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa + fb); +} + +// fp8_sub(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Subtract two FP8 values +pub fn fp8_sub(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa - fb); +} + +// fp8_mul(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Multiply two FP8 values +pub fn fp8_mul(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa * fb); +} + +// fp8_div(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Divide two FP8 values +pub fn fp8_div(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fb = fp8_decode_f32(b); + if (fb == 0.0) { + const fa = fp8_decode_f32(a); + return if (fa < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + const fa = fp8_decode_f32(a); + return fp8_encode_f32(fa / fb); +} + +// fp8_sqrt(value: FP8E4M3) -> FP8E4M3 +// Square root of FP8 value +pub fn fp8_sqrt(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + if (fv < 0.0) { + return FP8_NAN_POS; + } + return fp8_encode_f32(std.math.sqrt(fv)); +} + +// fp8_rsqrt(value: FP8E4M3) -> FP8E4M3 +// Reciprocal square root (1/sqrt(x)) +pub fn fp8_rsqrt(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + if (fv <= 0.0) { + return FP8_INF_POS; + } + return fp8_encode_f32(1.0 / std.math.sqrt(fv)); +} + +// fp8_abs(value: FP8E4M3) -> FP8E4M3 +// Absolute value of FP8 +pub fn fp8_abs(value: FP8E4M3) FP8E4M3 { + return value & ~SIGN_MASK; +} + +// fp8_neg(value: FP8E4M3) -> FP8E4M3 +// Negate FP8 +pub fn fp8_neg(value: FP8E4M3) FP8E4M3 { + return value ^ SIGN_MASK; +} + +// fp8_is_equal(a: FP8E4M3, b: FP8E4M3) -> bool +// Check if two FP8 values are equal +pub fn fp8_is_equal(a: FP8E4M3, b: FP8E4M3) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + if (fp8_is_zero(a) and fp8_is_zero(b)) { + return true; + } + return a == b; +} + +// fp8_is_greater(a: FP8E4M3, b: FP8E4M3) -> bool +// Check if a > b +pub fn fp8_is_greater(a: FP8E4M3, b: FP8E4M3) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + const sign_a = fp8_extract_sign(a); + const sign_b = fp8_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return fp8_neg(a) > fp8_neg(b); + } + return a > b; +} + +// fp8_max(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Return the larger of two FP8 values +pub fn fp8_max(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + return if (fp8_is_greater(a, b)) a else b; +} + +// fp8_min(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Return the smaller of two FP8 values +pub fn fp8_min(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + return if (fp8_is_greater(a, b)) b else a; +} + +// fp8_clamp(value: FP8E4M3, min: FP8E4M3, max: FP8E4M3) -> FP8E4M3 +// Clamp value between min and max +pub fn fp8_clamp(value: FP8E4M3, min: FP8E4M3, max: FP8E4M3) FP8E4M3 { + return fp8_min(fp8_max(value, min), max); +} + +// fp8_lerp(a: FP8E4M3, b: FP8E4M3, t: f32) -> FP8E4M3 +// Linear interpolation between a and b +pub fn fp8_lerp(a: FP8E4M3, b: FP8E4M3, t: f32) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const result = fa + (fb - fa) * t; + return fp8_encode_f32(result); +} + +// fp8_fma(a: FP8E4M3, b: FP8E4M3, c: FP8E4M3) -> FP8E4M3 +// Fused multiply-add: a * b + c +pub fn fp8_fma(a: FP8E4M3, b: FP8E4M3, c: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const fc = fp8_decode_f32(c); + return fp8_encode_f32(fa * fb + fc); +} + +// fp8_scale(value: FP8E4M3, scale: f32) -> FP8E4M3 +// Scale FP8 value by scalar +pub fn fp8_scale(value: FP8E4M3, scale: f32) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * scale); +} + +// fp8_relu(value: FP8E4M3) -> FP8E4M3 +// ReLU activation: max(0, x) +pub fn fp8_relu(value: FP8E4M3) FP8E4M3 { + return if (fp8_extract_sign(value) < 0) FP8_ZERO_POS else value; +} + +// fp8_sigmoid(value: FP8E4M3) -> FP8E4M3 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn fp8_sigmoid(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// fp8_tanh(value: FP8E4M3) -> FP8E4M3 +// Tanh activation +pub fn fp8_tanh(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(std.math.tanh(fv)); +} + +// fp8_gelu(value: FP8E4M3) -> FP8E4M3 +// GELU activation +pub fn fp8_gelu(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + const sqrt_2_over_pi = 0.7978845608; + const cube_coeff = 0.044715; + const tanh_arg = fv * (1.0 + cube_coeff * fv * fv); + return fp8_encode_f32(0.5 * fv * (1.0 + std.math.tanh(sqrt_2_over_pi * tanh_arg))); +} + +// fp8_swish(value: FP8E4M3) -> FP8E4M3 +// Swish activation: x * sigmoid(x) +pub fn fp8_swish(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * (1.0 / (1.0 + std.math.exp(-fv)))); +} + +// fp8_quantize_to_int4(value: FP8E4M3) -> i8 +// Quantize FP8 to Int4 (range [-8, 7]) +pub fn fp8_quantize_to_int4(value: FP8E4M3) i8 { + const fv = fp8_decode_f32(value); + const scaled = fv * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// fp8_quantize_to_int8(value: FP8E4M3) -> i8 +// Quantize FP8 to Int8 (range [-128, 127]) +pub fn fp8_quantize_to_int8(value: FP8E4M3) i8 { + const fv = fp8_decode_f32(value); + const scaled = fv * 127.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 127) { + result = 127; + } else if (result < -128) { + result = -128; + } + return @as(i8, @intCast(result)); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fp8_extract_sign_positive" { + given value = 0x3C + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "fp8_extract_sign_negative" { + given value = 0xBC + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "fp8_extract_exponent_max" { + given value = 0x78 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == EXP_MAX); +} + +test "fp8_extract_exponent_zero" { + given value = 0x07 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == 0); +} + +test "fp8_extract_mantissa_max" { + given value = 0x07 + try std.testing.expect(mant = fp8_extract_mantissa(value)); + try std.testing.expect(mant == MANT_MASK); +} + +test "fp8_is_zero_positive" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_POS) == true); +} + +test "fp8_is_zero_negative" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_NEG) == true); +} + +test "fp8_is_zero_nonzero" { + try std.testing.expect(fp8_is_zero(0x01) == false); +} + +test "fp8_is_inf_positive" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true); +} + +test "fp8_is_inf_negative" { + try std.testing.expect(fp8_is_inf(FP8_INF_NEG) == true); +} + +test "fp8_is_nan_positive" { + try std.testing.expect(fp8_is_nan(FP8_NAN_POS) == true); +} + +test "fp8_is_nan_negative" { + try std.testing.expect(fp8_is_nan(FP8_NAN_NEG) == true); +} + +test "fp8_is_inf_not_nan" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true and fp8_is_nan(FP8_INF_POS) == false); +} + +test "fp8_is_special_inf" { + try std.testing.expect(fp8_is_special(FP8_INF_POS) == true); +} + +test "fp8_is_special_nan" { + try std.testing.expect(fp8_is_special(FP8_NAN_POS) == true); +} + +test "fp8_is_subnormal_true" { + given value = 0x01 + try std.testing.expect(fp8_is_subnormal(value) == true); +} + +test "fp8_is_subnormal_false_normal" { + given value = 0x08 + try std.testing.expect(fp8_is_subnormal(value) == false); +} + +test "fp8_is_subnormal_false_zero" { + try std.testing.expect(fp8_is_subnormal(FP8_ZERO_POS) == false); +} + +test "fp8_encode_f32_zero" { + given fp = fp8_encode_f32(0.0) + try std.testing.expect(fp == FP8_ZERO_POS); +} + +test "fp8_encode_f32_negative_zero" { + given fp = fp8_encode_f32(-0.0) + try std.testing.expect(fp == FP8_ZERO_NEG); +} + +test "fp8_encode_f32_one" { + given fp = fp8_encode_f32(1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 1.0) < 0.05); +} + +test "fp8_encode_f32_negative_one" { + given fp = fp8_encode_f32(-1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded + 1.0) < 0.05); +} + +test "fp8_encode_f32_two" { + given fp = fp8_encode_f32(2.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 2.0) < 0.1); +} + +test "fp8_encode_f32_half" { + given fp = fp8_encode_f32(0.5) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 0.5) < 0.05); +} + +test "fp8_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.1); +} + +test "fp8_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.1); +} + +test "fp8_encode_f32_nan" { + given fp = fp8_encode_f32(std.math.nan(f32)) + try std.testing.expect(fp8_is_nan(fp) == true); +} + +test "fp8_encode_f32_inf" { + given fp = fp8_encode_f32(std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_encode_f32_neg_inf" { + given fp = fp8_encode_f32(-std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_decode_f32_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_POS) == 0.0); +} + +test "fp8_decode_f32_negative_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_NEG) == -0.0); +} + +test "fp8_decode_f32_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_POS)) == true); +} + +test "fp8_decode_f32_neg_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_NEG)) == true); +} + +test "fp8_decode_f32_nan" { + try std.testing.expect(std.math.isNan(fp8_decode_f32(FP8_NAN_POS)) == true); +} + +test "fp8_add_simple" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(result = fp8_add(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.15); +} + +test "fp8_sub_simple" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(result = fp8_sub(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.15); +} + +test "fp8_mul_simple" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(result = fp8_mul(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 6.0) < 0.3); +} + +test "fp8_div_simple" { + given a = fp8_encode_f32(6.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.15); +} + +test "fp8_div_by_zero_positive" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_POS); +} + +test "fp8_div_by_zero_negative" { + given a = fp8_encode_f32(-5.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_NEG); +} + +test "fp8_sqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.1); +} + +test "fp8_sqrt_negative" { + given value = fp8_encode_f32(-4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(fp8_is_nan(result) == true); +} + +test "fp8_rsqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_rsqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_abs_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(result = fp8_abs(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_abs_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(abs_val = fp8_abs(value)); + try std.testing.expect(fp8_extract_sign(abs_val) == 0); +} + +test "fp8_neg_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) < 0); +} + +test "fp8_neg_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) == 0); +} + +test "fp8_is_equal_true" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(fp8_is_equal(value, value) == true); +} + +test "fp8_is_equal_false" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(fp8_is_equal(a, b) == false); +} + +test "fp8_is_equal_nan" { + try std.testing.expect(fp8_is_equal(FP8_NAN_POS, FP8_NAN_POS) == false); +} + +test "fp8_is_equal_zero" { + try std.testing.expect(fp8_is_equal(FP8_ZERO_POS, FP8_ZERO_NEG) == true); +} + +test "fp8_is_greater_positive" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(fp8_is_greater(a, b) == true); +} + +test "fp8_max_returns_larger" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 4.0); +} + +test "fp8_min_returns_smaller" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 4.0); +} + +test "fp8_lerp_zero" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, a) == true); +} + +test "fp8_lerp_one" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, b) == true); +} + +test "fp8_fma_simple" { + given a = fp8_encode_f32(2.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(c = fp8_encode_f32(1.0)); + try std.testing.expect(result = fp8_fma(a, b, c)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 7.0) < 0.3); +} + +test "fp8_relu_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_relu_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_relu_zero" { + given result = fp8_relu(FP8_ZERO_POS) + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_sigmoid_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.05); +} + +test "fp8_sigmoid_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.95); +} + +test "fp8_sigmoid_negative" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 0.05); +} + +test "fp8_tanh_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded) < 0.05); +} + +test "fp8_tanh_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.95); +} + +test "fp8_tanh_negative" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= -0.95); +} + +test "fp8_gelu_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_gelu(value)); + try std.testing.expect(fp8_extract_sign(result) == 0); +} + +test "fp8_swish_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_swish_positive" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded > 0.0); +} + +test "fp8_quantize_to_int4_max" { + given value = fp8_encode_f32(2.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "fp8_quantize_to_int4_min" { + given value = fp8_encode_f32(-2.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "fp8_quantize_to_int8_max" { + given value = fp8_encode_f32(2.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result == 127); +} + +test "fp8_quantize_to_int8_min" { + given value = fp8_encode_f32(-2.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result == -128); +} + +test "fp8_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 7); + try std.testing.expect(mant = 4); + try std.testing.expect(fp = fp8_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = fp8_extract_sign(fp)); + try std.testing.expect(extracted_exp = fp8_extract_exponent(fp)); + try std.testing.expect(extracted_mant = fp8_extract_mantissa(fp)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fp8_bits_constant + assert BITS == 8 + +invariant fp8_sign_bits_one + assert SIGN_BITS == 1 + +invariant fp8_exp_bits_four + assert EXP_BITS == 4 + +invariant fp8_mant_bits_three + assert MANT_BITS == 3 + +invariant fp8_exp_bias_seven + assert EXP_BIAS == 7 + +invariant fp8_exp_max_all_ones + assert EXP_MAX == 0x0F + +invariant fp8_sign_mask_correct + assert SIGN_MASK == 0x80 + +invariant fp8_exp_mask_correct + assert EXP_MASK == 0x78 + +invariant fp8_mant_mask_correct + assert MANT_MASK == 0x07 + +invariant fp8_masks_sum_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFF + +invariant fp8_zero_pos_no_sign + assert (FP8_ZERO_POS & SIGN_MASK) == 0 + +invariant fp8_zero_neg_has_sign + assert (FP8_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant fp8_inf_pos_max_exp + assert (FP8_INF_POS & EXP_MASK) == EXP_MASK + +invariant fp8_inf_pos_no_mant + assert (FP8_INF_POS & MANT_MASK) == 0 + +invariant fp8_nan_has_mantissa + assert (FP8_NAN_POS & MANT_MASK) != 0 + +invariant fp8_abs_removes_sign + assert fp8_abs(FP8_ZERO_NEG) == FP8_ZERO_POS + +invariant fp8_neg_toggles_sign + assert (fp8_neg(FP8_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant fp8_add_zero_identity + given value = fp8_encode_f32(5.0) + assert fp8_is_equal(fp8_add(value, FP8_ZERO_POS), value) == true + +invariant fp8_sub_zero_identity + given value = fp8_encode_f32(5.0) + assert fp8_is_equal(fp8_sub(value, FP8_ZERO_POS), value) == true + +invariant fp8_mul_one_identity + given value = fp8_encode_f32(5.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_mul(value, one), value) == true or fp8_magnitude(value) > 0.5 + +invariant fp8_div_one_identity + given value = fp8_encode_f32(5.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_div(value, one), value) == true + +invariant fp8_max_ge_both + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(fp8_is_greater(result, a) == true and fp8_is_greater(result, b) == true or fp8_is_equal(result, b) == true); + +invariant fp8_min_le_both + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(fp8_is_greater(a, result) == true or fp8_is_equal(a, result) == true); + +invariant fp8_relu_non_negative + given result = fp8_relu(fp8_encode_f32(5.0)) + try std.testing.expect(fp8_extract_sign(result) == 0); + +invariant fp8_lerp_zero_start + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + assert fp8_is_equal(fp8_lerp(a, b, 0.0), a) == true + +invariant fp8_lerp_one_end + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + assert fp8_is_equal(fp8_lerp(a, b, 1.0), b) == true + +invariant fp8_sqrt_square_close + given value = fp8_encode_f32(4.0) + try std.testing.expect(sqrt_val = fp8_sqrt(value)); + try std.testing.expect(sq_val = fp8_mul(sqrt_val, sqrt_val)); + try std.testing.expect(decoded = fp8_decode_f32(sq_val)); + try std.testing.expect(abs(decoded - 4.0) < 0.5); + +invariant fp8_quantize_to_int4_in_range + given result = fp8_quantize_to_int4(fp8_encode_f32(1.0)) + assert result >= -8 and result <= 7 + +invariant fp8_quantize_to_int8_in_range + given result = fp8_quantize_to_int8(fp8_encode_f32(1.0)) + assert result >= -128 and result <= 127 + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fp8_encode_f32_latency + measure: nanoseconds to fp8_encode_f32(1.5) + target: < 50ns + +bench fp8_decode_f32_latency + measure: nanoseconds to fp8_decode_f32(0x3C) + target: < 50ns + +bench fp8_add_latency + measure: nanoseconds to fp8_add(0x3C, 0x48) + target: < 100ns + +bench fp8_mul_latency + measure: nanoseconds to fp8_mul(0x3C, 0x3C) + target: < 100ns + +bench fp8_sqrt_latency + measure: nanoseconds to fp8_sqrt(0x30) + target: < 100ns + +bench fp8_sigmoid_latency + measure: nanoseconds to fp8_sigmoid(0x30) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/fp8_e5m2.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/fp8_e5m2.t27 new file mode 100644 index 0000000000..e46e52429f --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/fp8_e5m2.t27 @@ -0,0 +1,1212 @@ +// SPDX-License-Identifier: Apache-2.0 +; fp8_e5m2.t27 — FP8 E5M2 8-bit Floating Point +; 8-bit float with 5 exponent, 2 mantissa bits (with implicit leading bit) +; OCP FP8 format optimized for inference and wide dynamic range +; Range: ~-57k to ~57k, precision: ~3-4 significant bits +; φ² + 1/φ² = 3 | TRINITY + +module triformat-fp8_e5m2; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 5; +pub const MANT_BITS : u8 = 2; + +pub const SIGN_SHIFT : u8 = 7; +pub const EXP_SHIFT : u8 = 2; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u8 = 0x80; // 1 << 7 +pub const EXP_MASK : u8 = 0x7C; // 0b01111100 +pub const MANT_MASK : u8 = 0x03; // 0b00000011 + +pub const EXP_MAX : u8 = 31; // All ones in 5 bits +pub const EXP_MIN : u8 = 0; +pub const EXP_BIAS : u8 = 15; // Exponent bias for FP8 E5M2 + +pub const MANT_DIVISOR : u8 = 4; // 2^2 + +// FP8 E5M2 special values +pub const FP8_ZERO_POS : u8 = 0x00; +pub const FP8_ZERO_NEG : u8 = 0x80; +pub const FP8_NAN_POS : u8 = 0x7F; // All exp + mant != 0 +pub const FP8_NAN_NEG : u8 = 0xFF; +pub const FP8_INF_POS : u8 = 0x7C; // All exp + mant = 0 +pub const FP8_INF_NEG : u8 = 0xFC; + +// Subnormal exponent +pub const SUBNORMAL_EXP : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FP8E5M2 = u8; // 8-bit value + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// fp8_extract_sign(fp8: FP8E5M2) -> i8 +// Extract sign bit (bit 7) +// Returns: 0 for positive, -1 for negative +pub fn fp8_extract_sign(fp8: FP8E5M2) i8 { + const bit = (fp8 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// fp8_extract_exponent(fp8: FP8E5M2) -> u8 +// Extract exponent bits (bits 6-2) +// Returns: 0-31 +pub fn fp8_extract_exponent(fp8: FP8E5M2) u8 { + return (fp8 >> EXP_SHIFT) & 0x1F; +} + +// fp8_extract_mantissa(fp8: FP8E5M2) -> u8 +// Extract mantissa bits (bits 1-0) +// Returns: 0-3 +pub fn fp8_extract_mantissa(fp8: FP8E5M2) u8 { + return fp8 & MANT_MASK; +} + +// fp8_from_components(sign: i8, exp: u8, mant: u8) -> FP8E5M2 +// Assemble FP8 from sign, exponent, mantissa +pub fn fp8_from_components(sign: i8, exp: u8, mant: u8) FP8E5M2 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(FP8E5M2, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(FP8E5M2, @intCast(exp & 0x1F)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// fp8_is_zero(fp8: FP8E5M2) -> bool +// Check if FP8 is zero (positive or negative) +pub fn fp8_is_zero(fp8: FP8E5M2) bool { + return fp8 == FP8_ZERO_POS or fp8 == FP8_ZERO_NEG; +} + +// fp8_is_special(fp8: FP8E5M2) -> bool +// Check if FP8 is Inf or NaN (exp == 31) +pub fn fp8_is_special(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX; +} + +// fp8_is_inf(fp8: FP8E5M2) -> bool +// Check if FP8 is infinity (exp == 31, mant == 0) +pub fn fp8_is_inf(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) == 0; +} + +// fp8_is_nan(fp8: FP8E5M2) -> bool +// Check if FP8 is NaN (exp == 31, mant != 0) +pub fn fp8_is_nan(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) != 0; +} + +// fp8_is_subnormal(fp8: FP8E5M2) -> bool +// Check if FP8 is subnormal (exp == 0, mant != 0) +pub fn fp8_is_subnormal(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == 0 and fp8_extract_mantissa(fp8) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// fp8_encode_f32(value: f32) -> FP8E5M2 +// Encode IEEE 754 single precision to FP8 E5M2 +// Round-to-nearest, ties to even +pub fn fp8_encode_f32(value: f32) FP8E5M2 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return if (value < 0.0) FP8_NAN_NEG else FP8_NAN_POS; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i16 = @as(i16, @intCast((f32_bits >> 23) & 0xFF)) - 127; + var f32_mant: u32 = f32_bits & 0x007FFFFF; + + // Handle very small values (subnormals) + if (f32_exp <= @as(i16, -EXP_BIAS)) { + // Below FP8 minimum normal - try to encode as subnormal + const subnormal_shift = @as(i16, -EXP_BIAS) - f32_exp + 1; + if (subnormal_shift > 2 + 23) { + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + var mant = f32_mant >> 23; + if (subnormal_shift > 0) { + const shift_amount = @as(u5, @intCast(subnormal_shift)); + if (shift_amount <= 24) { + mant = (f32_mant | 0x00800000) >> shift_amount; + } + } + return fp8_from_components(sign, 0, @as(u8, @truncate(mant)) & 0x03); + } + + // Convert exp from f32 bias (127) to FP8 bias (15) + var fp8_exp = @as(i16, f32_exp + EXP_BIAS); + + // Clamp exponent + if (fp8_exp >= EXP_MAX) { + return if (sign < 0) FP8_INF_NEG else FP8_INF_POS; + } else if (fp8_exp < 1) { + // Underflow to zero (could be subnormal, simplified) + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Extract mantissa and scale to 2 bits + // f32 mantissa is 24 bits (including implicit 1), FP8 needs 2 bits + // Shift right by 22 bits (24 - 2 = 22) + var mant = f32_mant >> 22; + + // Round-to-nearest with ties to even + const discarded = f32_mant & 0x003FFFFF; // Lower 22 bits + if ((discarded & 0x00200000) != 0) { + // Halfway or more + if ((discarded & 0x001FFFFF) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (fp8_exp < @as(i16, EXP_MAX - 1)) { + fp8_exp += 1; + } + } + } + } + + return fp8_from_components(sign, @as(u8, @intCast(fp8_exp)), @as(u8, mant)); +} + +// fp8_decode_f32(fp8: FP8E5M2) -> f32 +// Decode FP8 E5M2 to IEEE 754 single precision +pub fn fp8_decode_f32(fp8: FP8E5M2) f32 { + // Handle zero + if (fp8_is_zero(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (fp8_is_nan(fp8)) { + return std.math.nan(f32); + } + + // Handle Infinity + if (fp8_is_inf(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -std.math.inf(f32) else std.math.inf(f32); + } + + // Extract components + const sign = fp8_extract_sign(fp8); + const exp = fp8_extract_exponent(fp8); + const mant = fp8_extract_mantissa(fp8); + + var f32_value: f32 = undefined; + + // Handle subnormal + if (exp == 0) { + // Subnormal: value = mant * 2^(-14) = mant * 2^(1-bias) + const bias_adjusted: i8 = 1 - @as(i8, EXP_BIAS); + const mant_f32 = @as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR)); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } else { + // Normal: value = (1 + mant/4) * 2^(exp - 15) + const bias_adjusted = @as(i16, exp) - @as(i16, EXP_BIAS); + const mant_f32 = 1.0 + (@as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR))); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } + + return if (sign < 0) -f32_value else f32_value; +} + +// fp8_encode_f64(value: f64) -> FP8E5M2 +// Encode IEEE 754 double precision to FP8 E5M2 +pub fn fp8_encode_f64(value: f64) FP8E5M2 { + return fp8_encode_f32(@as(f32, @floatCast(value))); +} + +// fp8_decode_f64(fp8: FP8E5M2) -> f64 +// Decode FP8 E5M2 to IEEE 754 double precision +pub fn fp8_decode_f64(fp8: FP8E5M2) f64 { + const f32_val = fp8_decode_f32(fp8); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// fp8_add(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Add two FP8 values +pub fn fp8_add(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa + fb); +} + +// fp8_sub(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Subtract two FP8 values +pub fn fp8_sub(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa - fb); +} + +// fp8_mul(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Multiply two FP8 values +pub fn fp8_mul(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa * fb); +} + +// fp8_div(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Divide two FP8 values +pub fn fp8_div(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fb = fp8_decode_f32(b); + if (fb == 0.0) { + const fa = fp8_decode_f32(a); + return if (fa < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + const fa = fp8_decode_f32(a); + return fp8_encode_f32(fa / fb); +} + +// fp8_sqrt(value: FP8E5M2) -> FP8E5M2 +// Square root of FP8 value +pub fn fp8_sqrt(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + if (fv < 0.0) { + return FP8_NAN_POS; + } + return fp8_encode_f32(std.math.sqrt(fv)); +} + +// fp8_rsqrt(value: FP8E5M2) -> FP8E5M2 +// Reciprocal square root (1/sqrt(x)) +pub fn fp8_rsqrt(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + if (fv <= 0.0) { + return FP8_INF_POS; + } + return fp8_encode_f32(1.0 / std.math.sqrt(fv)); +} + +// fp8_abs(value: FP8E5M2) -> FP8E5M2 +// Absolute value of FP8 +pub fn fp8_abs(value: FP8E5M2) FP8E5M2 { + return value & ~SIGN_MASK; +} + +// fp8_neg(value: FP8E5M2) -> FP8E5M2 +// Negate FP8 +pub fn fp8_neg(value: FP8E5M2) FP8E5M2 { + return value ^ SIGN_MASK; +} + +// fp8_is_equal(a: FP8E5M2, b: FP8E5M2) -> bool +// Check if two FP8 values are equal +pub fn fp8_is_equal(a: FP8E5M2, b: FP8E5M2) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + if (fp8_is_zero(a) and fp8_is_zero(b)) { + return true; + } + return a == b; +} + +// fp8_is_greater(a: FP8E5M2, b: FP8E5M2) -> bool +// Check if a > b +pub fn fp8_is_greater(a: FP8E5M2, b: FP8E5M2) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + const sign_a = fp8_extract_sign(a); + const sign_b = fp8_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return fp8_neg(a) > fp8_neg(b); + } + return a > b; +} + +// fp8_max(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Return the larger of two FP8 values +pub fn fp8_max(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + return if (fp8_is_greater(a, b)) a else b; +} + +// fp8_min(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Return the smaller of two FP8 values +pub fn fp8_min(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + return if (fp8_is_greater(a, b)) b else a; +} + +// fp8_clamp(value: FP8E5M2, min: FP8E5M2, max: FP8E5M2) -> FP8E5M2 +// Clamp value between min and max +pub fn fp8_clamp(value: FP8E5M2, min: FP8E5M2, max: FP8E5M2) FP8E5M2 { + return fp8_min(fp8_max(value, min), max); +} + +// fp8_lerp(a: FP8E5M2, b: FP8E5M2, t: f32) -> FP8E5M2 +// Linear interpolation between a and b +pub fn fp8_lerp(a: FP8E5M2, b: FP8E5M2, t: f32) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const result = fa + (fb - fa) * t; + return fp8_encode_f32(result); +} + +// fp8_fma(a: FP8E5M2, b: FP8E5M2, c: FP8E5M2) -> FP8E5M2 +// Fused multiply-add: a * b + c +pub fn fp8_fma(a: FP8E5M2, b: FP8E5M2, c: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const fc = fp8_decode_f32(c); + return fp8_encode_f32(fa * fb + fc); +} + +// fp8_scale(value: FP8E5M2, scale: f32) -> FP8E5M2 +// Scale FP8 value by scalar +pub fn fp8_scale(value: FP8E5M2, scale: f32) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * scale); +} + +// fp8_relu(value: FP8E5M2) -> FP8E5M2 +// ReLU activation: max(0, x) +pub fn fp8_relu(value: FP8E5M2) FP8E5M2 { + return if (fp8_extract_sign(value) < 0) FP8_ZERO_POS else value; +} + +// fp8_sigmoid(value: FP8E5M2) -> FP8E5M2 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn fp8_sigmoid(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// fp8_tanh(value: FP8E5M2) -> FP8E5M2 +// Tanh activation +pub fn fp8_tanh(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(std.math.tanh(fv)); +} + +// fp8_gelu(value: FP8E5M2) -> FP8E5M2 +// GELU activation +pub fn fp8_gelu(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + const sqrt_2_over_pi = 0.7978845608; + const cube_coeff = 0.044715; + const tanh_arg = fv * (1.0 + cube_coeff * fv * fv); + return fp8_encode_f32(0.5 * fv * (1.0 + std.math.tanh(sqrt_2_over_pi * tanh_arg))); +} + +// fp8_swish(value: FP8E5M2) -> FP8E5M2 +// Swish activation: x * sigmoid(x) +pub fn fp8_swish(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * (1.0 / (1.0 + std.math.exp(-fv)))); +} + +// fp8_quantize_to_int4(value: FP8E5M2) -> i8 +// Quantize FP8 to Int4 (range [-8, 7]) +pub fn fp8_quantize_to_int4(value: FP8E5M2) i8 { + const fv = fp8_decode_f32(value); + // E5M2 has wider range, so scale differently + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// fp8_quantize_to_int8(value: FP8E5M2) -> i8 +// Quantize FP8 to Int8 (range [-128, 127]) +pub fn fp8_quantize_to_int8(value: FP8E5M2) i8 { + const fv = fp8_decode_f32(value); + // E5M2 has wider range, so scale differently + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 127.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 127) { + result = 127; + } else if (result < -128) { + result = -128; + } + return @as(i8, @intCast(result)); +} + +// fp8_from_f32_scaled(value: f32, scale: f32) -> FP8E5M2 +// Encode f32 to FP8 with custom scale factor +// Useful for quantization with specific dynamic range +pub fn fp8_from_f32_scaled(value: f32, scale: f32) FP8E5M2 { + return fp8_encode_f32(value * scale); +} + +// fp8_to_f32_scaled(value: FP8E5M2, scale: f32) -> f32 +// Decode FP8 to f32 with custom scale factor +pub fn fp8_to_f32_scaled(value: FP8E5M2, scale: f32) f32 { + return fp8_decode_f32(value) / scale; +} + +// fp8_dot_product(a: []const FP8E5M2, b: []const FP8E5M2) -> FP8E5M2 +// Dot product of two FP8 arrays (fixed size 8 for example) +// Returns accumulated FP8 value +pub fn fp8_dot_product(a: []const FP8E5M2, b: []const FP8E5M2) FP8E5M2 { + var sum: f32 = 0.0; + for (a, 0..) |val_a, i| { + if (i >= b.len) break; + const fa = fp8_decode_f32(val_a); + const fb = fp8_decode_f32(b[i]); + sum += fa * fb; + } + return fp8_encode_f32(sum); +} + +// fp8_vector_add(a: []const FP8E5M2, b: []const FP8E5M2, result: []FP8E5M2) -> void +// Vector addition: result[i] = a[i] + b[i] +pub fn fp8_vector_add(a: []const FP8E5M2, b: []const FP8E5M2, result: []FP8E5M2) void { + const len = @min(a.len, b.len); + for (0..len) |i| { + result[i] = fp8_add(a[i], b[i]); + } +} + +// fp8_vector_scale(vec: []const FP8E5M2, scale: FP8E5M2, result: []FP8E5M2) -> void +// Vector scale: result[i] = vec[i] * scale +pub fn fp8_vector_scale(vec: []const FP8E5M2, scale: FP8E5M2, result: []FP8E5M2) void { + const scale_f = fp8_decode_f32(scale); + for (vec, 0..) |val, i| { + result[i] = fp8_encode_f32(fp8_decode_f32(val) * scale_f); + } +} + +// fp8_vector_sum(vec: []const FP8E5M2) -> FP8E5M2 +// Sum all elements of a vector +pub fn fp8_vector_sum(vec: []const FP8E5M2) FP8E5M2 { + var sum: f32 = 0.0; + for (vec) |val| { + sum += fp8_decode_f32(val); + } + return fp8_encode_f32(sum); +} + +// fp8_vector_mean(vec: []const FP8E5M2) -> FP8E5M2 +// Mean of all elements of a vector +pub fn fp8_vector_mean(vec: []const FP8E5M2) FP8E5M2 { + if (vec.len == 0) { + return FP8_ZERO_POS; + } + const sum = fp8_vector_sum(vec); + return fp8_div(sum, fp8_from_f32(@as(f32, @floatFromInt(vec.len)))); +} + +// fp8_softmax(vec: []const FP8E5M2, result: []FP8E5M2) -> void +// Softmax activation: result[i] = exp(vec[i]) / sum(exp(vec)) +pub fn fp8_softmax(vec: []const FP8E5M2, result: []FP8E5M2) void { + // Find max for numerical stability + var max_val: f32 = -std.math.inf(f32); + for (vec) |val| { + const fv = fp8_decode_f32(val); + if (fv > max_val) { + max_val = fv; + } + } + + // Compute exp values and sum + var sum_exp: f32 = 0.0; + for (vec, 0..) |val, i| { + const fv = fp8_decode_f32(val); + const exp_val = std.math.exp(fv - max_val); + result[i] = fp8_encode_f32(exp_val); + sum_exp += exp_val; + } + + // Normalize + const inv_sum = 1.0 / sum_exp; + for (result, 0..) |val, i| { + const fv = fp8_decode_f32(val); + result[i] = fp8_encode_f32(fv * inv_sum); + } +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fp8_extract_sign_positive" { + given value = 0x3C + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "fp8_extract_sign_negative" { + given value = 0xBC + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "fp8_extract_exponent_max" { + given value = 0x7C + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == EXP_MAX); +} + +test "fp8_extract_exponent_zero" { + given value = 0x03 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == 0); +} + +test "fp8_extract_mantissa_max" { + given value = 0x03 + try std.testing.expect(mant = fp8_extract_mantissa(value)); + try std.testing.expect(mant == MANT_MASK); +} + +test "fp8_is_zero_positive" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_POS) == true); +} + +test "fp8_is_zero_negative" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_NEG) == true); +} + +test "fp8_is_zero_nonzero" { + try std.testing.expect(fp8_is_zero(0x01) == false); +} + +test "fp8_is_inf_positive" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true); +} + +test "fp8_is_inf_negative" { + try std.testing.expect(fp8_is_inf(FP8_INF_NEG) == true); +} + +test "fp8_is_nan_positive" { + try std.testing.expect(fp8_is_nan(FP8_NAN_POS) == true); +} + +test "fp8_is_nan_negative" { + try std.testing.expect(fp8_is_nan(FP8_NAN_NEG) == true); +} + +test "fp8_is_inf_not_nan" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true and fp8_is_nan(FP8_INF_POS) == false); +} + +test "fp8_is_special_inf" { + try std.testing.expect(fp8_is_special(FP8_INF_POS) == true); +} + +test "fp8_is_special_nan" { + try std.testing.expect(fp8_is_special(FP8_NAN_POS) == true); +} + +test "fp8_is_subnormal_true" { + given value = 0x01 + try std.testing.expect(fp8_is_subnormal(value) == true); +} + +test "fp8_is_subnormal_false_normal" { + given value = 0x04 + try std.testing.expect(fp8_is_subnormal(value) == false); +} + +test "fp8_is_subnormal_false_zero" { + try std.testing.expect(fp8_is_subnormal(FP8_ZERO_POS) == false); +} + +test "fp8_encode_f32_zero" { + given fp = fp8_encode_f32(0.0) + try std.testing.expect(fp == FP8_ZERO_POS); +} + +test "fp8_encode_f32_negative_zero" { + given fp = fp8_encode_f32(-0.0) + try std.testing.expect(fp == FP8_ZERO_NEG); +} + +test "fp8_encode_f32_one" { + given fp = fp8_encode_f32(1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 1.0) < 0.15); +} + +test "fp8_encode_f32_negative_one" { + given fp = fp8_encode_f32(-1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded + 1.0) < 0.15); +} + +test "fp8_encode_f32_ten" { + given fp = fp8_encode_f32(10.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 10.0) < 1.0); +} + +test "fp8_encode_f32_hundred" { + given fp = fp8_encode_f32(100.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 100.0) < 10.0); +} + +test "fp8_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "fp8_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "fp8_encode_f32_nan" { + given fp = fp8_encode_f32(std.math.nan(f32)) + try std.testing.expect(fp8_is_nan(fp) == true); +} + +test "fp8_encode_f32_inf" { + given fp = fp8_encode_f32(std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_encode_f32_neg_inf" { + given fp = fp8_encode_f32(-std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_decode_f32_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_POS) == 0.0); +} + +test "fp8_decode_f32_negative_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_NEG) == -0.0); +} + +test "fp8_decode_f32_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_POS)) == true); +} + +test "fp8_decode_f32_neg_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_NEG)) == true); +} + +test "fp8_decode_f32_nan" { + try std.testing.expect(std.math.isNan(fp8_decode_f32(FP8_NAN_POS)) == true); +} + +test "fp8_add_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(20.0)); + try std.testing.expect(result = fp8_add(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 30.0) < 3.0); +} + +test "fp8_sub_simple" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(30.0)); + try std.testing.expect(result = fp8_sub(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 3.0); +} + +test "fp8_mul_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(result = fp8_mul(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 15.0); +} + +test "fp8_div_simple" { + given a = fp8_encode_f32(100.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "fp8_div_by_zero_positive" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_POS); +} + +test "fp8_div_by_zero_negative" { + given a = fp8_encode_f32(-50.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_NEG); +} + +test "fp8_sqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.3); +} + +test "fp8_sqrt_negative" { + given value = fp8_encode_f32(-4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(fp8_is_nan(result) == true); +} + +test "fp8_rsqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_rsqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_abs_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(result = fp8_abs(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_abs_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(abs_val = fp8_abs(value)); + try std.testing.expect(fp8_extract_sign(abs_val) == 0); +} + +test "fp8_neg_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) < 0); +} + +test "fp8_neg_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) == 0); +} + +test "fp8_is_equal_true" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(fp8_is_equal(value, value) == true); +} + +test "fp8_is_equal_false" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(20.0)); + try std.testing.expect(fp8_is_equal(a, b) == false); +} + +test "fp8_is_equal_nan" { + try std.testing.expect(fp8_is_equal(FP8_NAN_POS, FP8_NAN_POS) == false); +} + +test "fp8_is_equal_zero" { + try std.testing.expect(fp8_is_equal(FP8_ZERO_POS, FP8_ZERO_NEG) == true); +} + +test "fp8_is_greater_positive" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(30.0)); + try std.testing.expect(fp8_is_greater(a, b) == true); +} + +test "fp8_max_returns_larger" { + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 40.0); +} + +test "fp8_min_returns_smaller" { + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 40.0); +} + +test "fp8_lerp_zero" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, a) == true); +} + +test "fp8_lerp_one" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, b) == true); +} + +test "fp8_fma_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(c = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_fma(a, b, c)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 105.0) < 15.0); +} + +test "fp8_scale_up" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(scale = 10.0); + try std.testing.expect(result = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 1.0); +} + +test "fp8_scale_down" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(scale = 0.1); + try std.testing.expect(result = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "fp8_relu_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_relu_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_relu_zero" { + given result = fp8_relu(FP8_ZERO_POS) + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_sigmoid_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_sigmoid_positive_large" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "fp8_sigmoid_negative_large" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 0.1); +} + +test "fp8_tanh_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded) < 0.1); +} + +test "fp8_tanh_positive_large" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "fp8_tanh_negative_large" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= -0.9); +} + +test "fp8_gelu_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_gelu(value)); + try std.testing.expect(fp8_extract_sign(result) == 0); +} + +test "fp8_swish_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_swish_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded > 0.0); +} + +test "fp8_quantize_to_int4_max" { + given value = fp8_encode_f32(100.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "fp8_quantize_to_int4_min" { + given value = fp8_encode_f32(-100.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "fp8_quantize_to_int8_max" { + given value = fp8_encode_f32(200.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result >= 100); +} + +test "fp8_quantize_to_int8_min" { + given value = fp8_encode_f32(-200.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result <= -100); +} + +test "fp8_from_f32_scaled_up" { + given value = 1.0 + try std.testing.expect(scale = 100.0); + try std.testing.expect(result = fp8_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 10.0); +} + +test "fp8_from_f32_scaled_down" { + given value = 100.0 + try std.testing.expect(scale = 0.01); + try std.testing.expect(result = fp8_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "fp8_to_f32_scaled_up" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(scale = 0.01); + try std.testing.expect(result = fp8_to_f32_scaled(value, scale)); + try std.testing.expect(abs(result - 0.01) < 0.005); +} + +test "fp8_dot_product_simple" { + given a = [_]FP8E5M2{fp8_encode_f32(1.0), fp8_encode_f32(2.0), fp8_encode_f32(3.0), fp8_encode_f32(4.0)} + try std.testing.expect(b = [_]FP8E5M2{fp8_encode_f32(2.0), fp8_encode_f32(2.0), fp8_encode_f32(2.0), fp8_encode_f32(2.0)}); + try std.testing.expect(result = fp8_dot_product(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "fp8_vector_sum_simple" { + given vec = [_]FP8E5M2{fp8_encode_f32(1.0), fp8_encode_f32(2.0), fp8_encode_f32(3.0), fp8_encode_f32(4.0)} + try std.testing.expect(result = fp8_vector_sum(vec)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 2.0); +} + +test "fp8_vector_mean_simple" { + given vec = [_]FP8E5M2{fp8_encode_f32(2.0), fp8_encode_f32(4.0), fp8_encode_f32(6.0), fp8_encode_f32(8.0)} + try std.testing.expect(result = fp8_vector_mean(vec)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 5.0) < 1.0); +} + +test "fp8_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 15); + try std.testing.expect(mant = 2); + try std.testing.expect(fp = fp8_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = fp8_extract_sign(fp)); + try std.testing.expect(extracted_exp = fp8_extract_exponent(fp)); + try std.testing.expect(extracted_mant = fp8_extract_mantissa(fp)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fp8_bits_constant + assert BITS == 8 + +invariant fp8_sign_bits_one + assert SIGN_BITS == 1 + +invariant fp8_exp_bits_five + assert EXP_BITS == 5 + +invariant fp8_mant_bits_two + assert MANT_BITS == 2 + +invariant fp8_exp_bias_fifteen + assert EXP_BIAS == 15 + +invariant fp8_exp_max_all_ones + assert EXP_MAX == 0x1F + +invariant fp8_sign_mask_correct + assert SIGN_MASK == 0x80 + +invariant fp8_exp_mask_correct + assert EXP_MASK == 0x7C + +invariant fp8_mant_mask_correct + assert MANT_MASK == 0x03 + +invariant fp8_masks_sum_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFF + +invariant fp8_zero_pos_no_sign + assert (FP8_ZERO_POS & SIGN_MASK) == 0 + +invariant fp8_zero_neg_has_sign + assert (FP8_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant fp8_inf_pos_max_exp + assert (FP8_INF_POS & EXP_MASK) == EXP_MASK + +invariant fp8_inf_pos_no_mant + assert (FP8_INF_POS & MANT_MASK) == 0 + +invariant fp8_nan_has_mantissa + assert (FP8_NAN_POS & MANT_MASK) != 0 + +invariant fp8_abs_removes_sign + assert fp8_abs(FP8_ZERO_NEG) == FP8_ZERO_POS + +invariant fp8_neg_toggles_sign + assert (fp8_neg(FP8_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant fp8_add_zero_identity + given value = fp8_encode_f32(50.0) + assert fp8_is_equal(fp8_add(value, FP8_ZERO_POS), value) == true + +invariant fp8_sub_zero_identity + given value = fp8_encode_f32(50.0) + assert fp8_is_equal(fp8_sub(value, FP8_ZERO_POS), value) == true + +invariant fp8_mul_one_identity + given value = fp8_encode_f32(50.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_mul(value, one), value) == true or fp8_magnitude(value) > 10.0 + +invariant fp8_div_one_identity + given value = fp8_encode_f32(50.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_div(value, one), value) == true + +invariant fp8_max_ge_both + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(fp8_is_greater(result, a) == true and fp8_is_greater(result, b) == true or fp8_is_equal(result, b) == true); + +invariant fp8_min_le_both + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(fp8_is_greater(a, result) == true or fp8_is_equal(a, result) == true); + +invariant fp8_relu_non_negative + given result = fp8_relu(fp8_encode_f32(50.0)) + try std.testing.expect(fp8_extract_sign(result) == 0); + +invariant fp8_lerp_zero_start + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + assert fp8_is_equal(fp8_lerp(a, b, 0.0), a) == true + +invariant fp8_lerp_one_end + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + assert fp8_is_equal(fp8_lerp(a, b, 1.0), b) == true + +invariant fp8_sqrt_square_close + given value = fp8_encode_f32(4.0) + try std.testing.expect(sqrt_val = fp8_sqrt(value)); + try std.testing.expect(sq_val = fp8_mul(sqrt_val, sqrt_val)); + try std.testing.expect(decoded = fp8_decode_f32(sq_val)); + try std.testing.expect(abs(decoded - 4.0) < 0.5); + +invariant fp8_quantize_to_int4_in_range + given result = fp8_quantize_to_int4(fp8_encode_f32(50.0)) + assert result >= -8 and result <= 7 + +invariant fp8_quantize_to_int8_in_range + given result = fp8_quantize_to_int8(fp8_encode_f32(50.0)) + assert result >= -128 and result <= 127 + +invariant fp8_scale_roundtrip + given value = fp8_encode_f32(50.0) + try std.testing.expect(scale = 2.0); + try std.testing.expect(scaled = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_to_f32_scaled(scaled, scale)); + try std.testing.expect(abs(decoded - fp8_decode_f32(value)) < 5.0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fp8_encode_f32_latency + measure: nanoseconds to fp8_encode_f32(10.5) + target: < 50ns + +bench fp8_decode_f32_latency + measure: nanoseconds to fp8_decode_f32(0x3C) + target: < 50ns + +bench fp8_add_latency + measure: nanoseconds to fp8_add(0x3C, 0x48) + target: < 100ns + +bench fp8_mul_latency + measure: nanoseconds to fp8_mul(0x3C, 0x3C) + target: < 100ns + +bench fp8_sqrt_latency + measure: nanoseconds to fp8_sqrt(0x30) + target: < 100ns + +bench fp8_sigmoid_latency + measure: nanoseconds to fp8_sigmoid(0x30) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/gf12.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf12.t27 new file mode 100644 index 0000000000..9629152355 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf12.t27 @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf12.t27 +// GoldenFloat12 0 12-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 4 (P1) + +module GF12 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF12 bit layout: [S|EEEE|MMM MMMM] + // S: 1 bit (sign) + // E: 4 bits (exponent) + // M: 7 bits (mantissa) + + const BITS : u8 = 12; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 4; + const MANT_BITS : u8 = 7; + + // Bias for exponent (2^(4-1) - 1 = 7) + const EXP_BIAS : u8 = 7; + + // 141-ratio: exp/mant = 4/7 142 0.571 (phi_distance = 0.047) + // This is the closest to 1/143 among all formats + const PHI_DISTANCE : f64 = 0.04660512288042107; + + // 144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 + // 2. GoldenFloat12 Type + // 209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 + + struct GF12 { + raw : u16, // 12-bit value stored in u16 + } + + // 282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 + // 3. Encoding/Decoding + // 347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 + + // Encode f32 to GF12 + fn encode(value: f32) -> GF12 { + if (value == 0.0) { + return GF12{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i8; + const exp_biased = (exp_unbiased + EXP_BIAS as i8) as u8; + + // Clamp exponent + const exp_clamped = clamp(exp_biased, 0, (1 << EXP_BITS) - 1); + + // Extract mantissa (7 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF12{ + raw = ((sign as u16) << 11) | ((exp_clamped as u16) << MANT_BITS) | (mant as u16) + }; + } + + // Decode GF12 to f32 + fn decode(gf: GF12) -> f32 { + const sign = (gf.raw >> 11) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x0F) as u8; + const mant = (gf.raw & 0x7F) as u8; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS as i8 + 1 + } else { + (exp_biased as i8) - EXP_BIAS as i8 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 128.0 + } else { + 1.0 + (mant as f32) / 128.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 + // 4. Format Properties + // 485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 + + fn max_value() -> f32 { + const mant_max = 1.0 + 127.0 / 128.0; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 128.0; + const exp_min = -EXP_BIAS as i8 + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 128.0; // 0.0078125 + } + + // 558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 + // 5. Validation + // 623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF12"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760 + // 6. Use Cases + // 761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833 + + // GF12 is optimal for: + // - Best 834-approximation (lowest phi_distance) + // - High-precision quantization + // - Critical path weights + // - Attention matrices + + // Memory: 12 bits = 1.5 bytes (~2.67x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 12.0 / 32.0; // 0.375 + + // 835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899 + // 7. Helper Functions + // 900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972 + + fn floor_log2(x: f32) -> i8 { + if (x <= 0.0) { return -128; } + let exp : i8 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i8, mant_bits: u8) -> u8 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u8; + } + + fn clamp(x: u8, min: u8, max: u8) -> u8 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF12 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 9739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075 + // TDD-Inside-Spec: Tests and Invariants for GF12 + // 1076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178 + + test gf12_decode_zero + given gf = GF12{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf12_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf12_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf12_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf12_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf12_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf12_phi_distance_lowest + given phi_dist = PHI_DISTANCE + then phi_dist < 0.05 + + test gf12_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.375) < 0.01 + + test gf12_validate_format_success + given valid = validate_format() + then valid == true + + test gf12_floor_log2_power_of_two + given log_result = floor_log2(8.0) + then log_result == 3 + + test gf12_extract_mantissa_in_range + given mant = extract_mantissa(1.5, 0, 7) + then mant < 128 + + invariant gf12_bits_constant + assert BITS == 12 + + invariant gf12_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf12_exp_bits_is_four + assert EXP_BITS == 4 + + invariant gf12_mant_bits_is_seven + assert MANT_BITS == 7 + + invariant gf12_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf12_phi_distance_below_threshold + assert PHI_DISTANCE < 0.05 + + invariant gf12_exp_bias_positive + assert EXP_BIAS > 0 + + test gf12_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf12_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf12_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf12_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf12_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf12_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf12_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf12_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf12_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf12_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf12_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf12_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf12_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf12_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf12_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf12_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf12_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf12_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf12_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf12_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf12_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf12_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf12_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf12_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf12_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + invariant gf12_floor_log2_non_negative_input + assert floor_log2(1.0) >= 0 + + invariant gf12_extract_mantissa_in_valid_range + assert extract_mantissa(1.0, 0, 7) < 128 + + bench gf12_encode_latency + measure: nanoseconds to encode(1.0) + target: < 150ns + + bench gf12_decode_latency + measure: nanoseconds to decode(GF12{raw = 1024}) + target: < 100ns +} diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/gf128.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf128.t27 new file mode 100644 index 0000000000..f32ee7a8a1 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf128.t27 @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf128.t27 — GoldenFloat128 Encode/Decode +; GF128: 128-bit floating point with 1 sign + 28 exponent + 99 mantissa +; Bit layout: [S(1) E(28) M(99)] = [127:127][126:99][98:0] +; Extended range format for high-precision scientific computing +; φ² + 1/φ² = 3 | TRINITY + +module triformat-gf128; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const SIGN_SHIFT : u8 = 127; +pub const EXP_SHIFT : u8 = 99; +pub const MANT_SHIFT : u8 = 0; + +// GF128 uses 128-bit representation +// Since Zig doesn't have u128 constants directly in all contexts, +// we use u64 pairs for mask constants + +pub const SIGN_MASK_HI : u64 = 0x8000000000000000; // 1 << 63 of high part +pub const SIGN_MASK_LO : u64 = 0x0000000000000000; + +pub const EXP_BITS : u8 = 28; +pub const MANT_BITS : u8 = 99; +pub const SIGN_BITS : u8 = 1; + +pub const EXP_MAX : u32 = 0x0FFFFFFF; // 268435455 (all ones in 28 bits) +pub const EXP_MIN : u32 = 0x00000000; + +pub const BIAS : i32 = 134217727; // Exponent bias for GF128: 2^(28-1) - 1 +pub const SPECIAL_EXP : u32 = 0x0FFFFFFF; // All ones = special (Inf/NaN) + +// MANT_DIVISOR = 2^99 is too large for u64, handled in functions + +pub const PHI_BIAS : u32 = 3932160; // Phi-optimized rounding bias + +// ============================================================================ +// Types +// ============================================================================ + +// GF128 represented as u128 in Zig 0.15+ +pub const GF128 = u128; + +// Helper struct for operations that need high/low parts +pub struct GF128Parts { + hi: u64, + lo: u64, +} + +// ============================================================================ +// Conversion Helpers +// ============================================================================ + +// gf128_to_parts(gf128: GF128) -> GF128Parts +// Split GF128 into high and low 64-bit parts +pub fn gf128_to_parts(gf128: GF128) GF128Parts { + return GF128Parts{ + .hi = @as(u64, @truncate(gf128 >> 64)), + .lo = @as(u64, @truncate(gf128)), + }; +} + +// gf128_from_parts(parts: GF128Parts) -> GF128 +// Combine high and low 64-bit parts into GF128 +pub fn gf128_from_parts(parts: GF128Parts) GF128 { + return (@as(GF128, parts.hi) << 64) | @as(GF128, parts.lo); +} + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// gf128_extract_sign(gf128: GF128) -> i8 +// Extract sign bit (bit 127) +// Returns: 0 for positive, -1 for negative +pub fn gf128_extract_sign(gf128: GF128) i8 { + const parts = gf128_to_parts(gf128); + const bit = (parts.hi >> 63) & 1; + return if (bit != 0) -1 else 0; +} + +// gf128_extract_exponent(gf128: GF128) -> u32 +// Extract exponent bits (bits 126-99) +// Returns: 0-268435455 +pub fn gf128_extract_exponent(gf128: GF128) u32 { + const parts = gf128_to_parts(gf128); + // Exponent spans bits 62-35 of high part (28 bits total) + // exp = (hi >> 35) & 0x0FFFFFFF + return @as(u32, @truncate((parts.hi >> 35) & 0x0FFFFFFF)); +} + +// gf128_extract_mantissa(gf128: GF128) -> u128 +// Extract mantissa bits (bits 98-0) +// Returns: 0-2^99-1 +pub fn gf128_extract_mantissa(gf128: GF128) u128 { + const parts = gf128_to_parts(gf128); + // Mantissa spans bits 34-0 of high part (35 bits) + all 64 bits of low part + // mant = ((hi & 0x00000007FFFFFFFF) << 64) | lo + const hi_mant = parts.hi & 0x00000007FFFFFFFF; + return (@as(u128, hi_mant) << 64) | @as(u128, parts.lo); +} + +// ============================================================================ +// Assembly Functions +// ============================================================================ + +// gf128_from_components(sign: i8, exp: u32, mant: u128) -> GF128 +// Assemble GF128 from sign, exponent, mantissa +pub fn gf128_from_components(sign: i8, exp: u32, mant: u128) GF128 { + const sign_bit = if (sign < 0) 1 else 0; + const clamped_exp = exp & EXP_MAX; + const clamped_mant = mant & (@as(u128, EXP_MAX) >> 1); // Mask for 99 bits (2^99 - 1) + + // exp goes at bits 126-99 (hi bits 62-35) + const hi_exp = @as(u64, @truncate(clamped_exp)) << 35; + // mant spans bits 98-0 (hi bits 34-0 + lo 64 bits) + const hi_mant = @as(u64, @truncate(clamped_mant >> 64)); + const lo_mant = @as(u64, @truncate(clamped_mant)); + + const hi = (@as(u64, @intCast(sign_bit)) << 63) | hi_exp | hi_mant; + const lo = lo_mant; + + return gf128_from_parts(GF128Parts{ .hi = hi, .lo = lo }); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +const GF128_ZERO_POS : GF128 = 0; +const GF128_ZERO_NEG : GF128 = (@as(GF128, 1) << 127); +const GF128_INF_POS : GF128 = (@as(GF128, EXP_MAX) << 99); +const GF128_INF_NEG : GF128 = GF128_INF_POS | (@as(GF128, 1) << 127); +const GF128_NAN : GF128 = GF128_INF_POS | 1; + +// gf128_is_zero(gf128: GF128) -> bool +// Check if GF128 is zero (positive or negative) +pub fn gf128_is_zero(gf128: GF128) bool { + return gf128 == GF128_ZERO_POS or gf128 == GF128_ZERO_NEG; +} + +// gf128_is_special(gf128: GF128) -> bool +// Check if GF128 is Inf or NaN (exp == 268435455) +pub fn gf128_is_special(gf128: GF128) bool { + return gf128_extract_exponent(gf128) == EXP_MAX; +} + +// gf128_is_inf(gf128: GF128) -> bool +// Check if GF128 is infinity (exp == max, mant == 0) +pub fn gf128_is_inf(gf128: GF128) bool { + return gf128_is_special(gf128) and gf128_extract_mantissa(gf128) == 0; +} + +// gf128_is_nan(gf128: GF128) -> bool +// Check if GF128 is NaN (exp == max, mant != 0) +pub fn gf128_is_nan(gf128: GF128) bool { + return gf128_is_special(gf128) and gf128_extract_mantissa(gf128) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// gf128_encode_f64(f64: f64) -> GF128 +// Encode IEEE 754 double precision to GF128 +pub fn gf128_encode_f64(value: f64) GF128 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) GF128_ZERO_NEG else GF128_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return GF128_NAN; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) GF128_INF_NEG else GF128_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f64 components + const f64_bits: u64 = @bitCast(abs_value); + var f64_exp: i32 = @as(i32, @intCast((f64_bits >> 52) & 0x7FF)) - 1023; + var f64_mant: u64 = f64_bits & 0x000FFFFFFFFFFFFF; + + // Convert exp from f64 bias (1023) to GF128 bias (134217727) + var gf128_exp = @as(u32, @intCast(f64_exp + BIAS)); + + // Clamp exponent + if (gf128_exp >= EXP_MAX) { + return if (sign < 0) GF128_INF_NEG else GF128_INF_POS; + } + + // Extend mantissa to 99 bits (f64 has 52 bits, pad with zeros) + const mant = @as(u128, f64_mant) << 47; // 52 + 47 = 99 + + return gf128_from_components(sign, gf128_exp, mant); +} + +// gf128_decode_f64(gf128: GF128) -> f64 +// Decode GF128 to IEEE 754 double precision +// Note: This may lose precision as f64 has only 52 mantissa bits +pub fn gf128_decode_f64(gf128: GF128) f64 { + // Handle zero + if (gf128_is_zero(gf128)) { + return if (gf128_extract_sign(gf128) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (gf128_is_nan(gf128)) { + return std.math.nan(f64); + } + + // Handle Infinity + if (gf128_is_inf(gf128)) { + return if (gf128_extract_sign(gf128) < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + // Extract components + const sign = gf128_extract_sign(gf128); + const exp = gf128_extract_exponent(gf128); + const mant = gf128_extract_mantissa(gf128); + + // Convert to f64 (may lose precision) + // Scale mantissa from 99 bits to 52 bits + const mant_f64 = @as(f64, @floatFromInt(mant >> 47)) / 4503599627370496.0; // 2^52 + const bias_adjusted = @as(i64, @intCast(exp)) - @as(i64, BIAS); + + if (bias_adjusted < -1022 or bias_adjusted > 1023) { + return if (sign < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + const value = mant_f64 * std.math.pow(f64, 2.0, @as(f64, @floatFromInt(bias_adjusted))); + return if (sign < 0) -value else value; +} + +// gf128_encode_f128(f128: f128) -> GF128 +// Encode IEEE 754 quad precision to GF128 (if available) +pub fn gf128_encode_f128(value: f128) GF128 { + // For systems without f128 support, fall back to f64 + // This is a placeholder - actual implementation would use f128 bit manipulation + const value_f64 = @as(f64, @floatCast(value)); + return gf128_encode_f64(value_f64); +} + +// gf128_decode_f128(gf128: GF128) -> f128 +// Decode GF128 to IEEE 754 quad precision (if available) +pub fn gf128_decode_f128(gf128: GF128) f128 { + const value_f64 = gf128_decode_f64(gf128); + return @as(f128, @floatCast(value_f64)); +} + +// ============================================================================ +// Arithmetic Operations (simplified via decode-encode) +// ============================================================================ + +// gf128_add(a: GF128, b: GF128) -> GF128 +pub fn gf128_add(a: GF128, b: GF128) GF128 { + return gf128_encode_f64(gf128_decode_f64(a) + gf128_decode_f64(b)); +} + +// gf128_sub(a: GF128, b: GF128) -> GF128 +pub fn gf128_sub(a: GF128, b: GF128) GF128 { + return gf128_encode_f64(gf128_decode_f64(a) - gf128_decode_f64(b)); +} + +// gf128_mul(a: GF128, b: GF128) -> GF128 +pub fn gf128_mul(a: GF128, b: GF128) GF128 { + return gf128_encode_f64(gf128_decode_f64(a) * gf128_decode_f64(b)); +} + +// gf128_div(a: GF128, b: GF128) -> GF128 +pub fn gf128_div(a: GF128, b: GF128) GF128 { + const fb = gf128_decode_f64(b); + if (fb == 0.0) { + const fa = gf128_decode_f64(a); + return if (fa < 0.0) GF128_INF_NEG else GF128_INF_POS; + } + return gf128_encode_f64(gf128_decode_f64(a) / fb); +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// gf128_abs(gf128: GF128) -> GF128 +pub fn gf128_abs(gf128: GF128) GF128 { + return gf128 & ~(@as(GF128, 1) << 127); +} + +// gf128_neg(gf128: GF128) -> GF128 +pub fn gf128_neg(gf128: GF128) GF128 { + return gf128 ^ (@as(GF128, 1) << 127); +} + +// gf128_is_equal(a: GF128, b: GF128) -> bool +pub fn gf128_is_equal(a: GF128, b: GF128) bool { + if (gf128_is_nan(a) or gf128_is_nan(b)) { + return false; + } + if (gf128_is_zero(a) and gf128_is_zero(b)) { + return true; + } + return a == b; +} + +// gf128_is_greater(a: GF128, b: GF128) -> bool +pub fn gf128_is_greater(a: GF128, b: GF128) bool { + if (gf128_is_nan(a) or gf128_is_nan(b)) { + return false; + } + const sign_a = gf128_extract_sign(a); + const sign_b = gf128_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return gf128_neg(a) > gf128_neg(b); + } + return a > b; +} + +// gf128_max(a: GF128, b: GF128) -> GF128 +pub fn gf128_max(a: GF128, b: GF128) GF128 { + return if (gf128_is_greater(a, b)) a else b; +} + +// gf128_min(a: GF128, b: GF128) -> GF128 +pub fn gf128_min(a: GF128, b: GF128) GF128 { + return if (gf128_is_greater(a, b)) b else a; +} + +// gf128_clamp(value: GF128, min: GF128, max: GF128) -> GF128 +pub fn gf128_clamp(value: GF128, min: GF128, max: GF128) GF128 { + return gf128_min(gf128_max(value, min), max); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf128_extract_sign_positive" { + given value = 0x00000000000000000000000000001234 + try std.testing.expect(sign = gf128_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "gf128_extract_sign_negative" { + given value = 0x80000000000000000000000000001234 + try std.testing.expect(sign = gf128_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "gf128_extract_exponent_middle" { + given value = 0x00008000000000000000000000000000 // exp = 134217728 + try std.testing.expect(exp = gf128_extract_exponent(value)); + try std.testing.expect(exp == 134217728); +} + +test "gf128_is_zero_positive" { + try std.testing.expect(gf128_is_zero(GF128_ZERO_POS) == true); +} + +test "gf128_is_zero_negative" { + try std.testing.expect(gf128_is_zero(GF128_ZERO_NEG) == true); +} + +test "gf128_is_inf_positive" { + try std.testing.expect(gf128_is_inf(GF128_INF_POS) == true); +} + +test "gf128_is_nan" { + try std.testing.expect(gf128_is_nan(GF128_NAN) == true); +} + +test "gf128_encode_f64_zero" { + given gf = gf128_encode_f64(0.0) + try std.testing.expect(gf == GF128_ZERO_POS); +} + +test "gf128_encode_f64_one" { + given gf = gf128_encode_f64(1.0) + try std.testing.expect(decoded = gf128_decode_f64(gf)); + try std.testing.expect(abs(decoded - 1.0) < 0.0001); +} + +test "gf128_encode_f64_negative_one" { + given gf = gf128_encode_f64(-1.0) + try std.testing.expect(decoded = gf128_decode_f64(gf)); + try std.testing.expect(abs(decoded + 1.0) < 0.0001); +} + +test "gf128_encode_f64_roundtrip" { + given original = 42.5 + try std.testing.expect(gf = gf128_encode_f64(original)); + try std.testing.expect(decoded = gf128_decode_f64(gf)); + try std.testing.expect(abs(decoded - original) < 0.001); +} + +test "gf128_add_simple" { + given a = gf128_encode_f64(1.0) + try std.testing.expect(b = gf128_encode_f64(2.0)); + try std.testing.expect(result = gf128_add(a, b)); + try std.testing.expect(decoded = gf128_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.01); +} + +test "gf128_mul_simple" { + given a = gf128_encode_f64(3.0) + try std.testing.expect(b = gf128_encode_f64(4.0)); + try std.testing.expect(result = gf128_mul(a, b)); + try std.testing.expect(decoded = gf128_decode_f64(result)); + try std.testing.expect(abs(decoded - 12.0) < 0.01); +} + +test "gf128_abs_positive" { + given value = gf128_encode_f64(5.0) + try std.testing.expect(abs_val = gf128_abs(value)); + try std.testing.expect(gf128_is_equal(abs_val, value) == true); +} + +test "gf128_abs_negative" { + given value = gf128_encode_f64(-5.0) + try std.testing.expect(abs_val = gf128_abs(value)); + try std.testing.expect(gf128_extract_sign(abs_val) == 0); +} + +test "gf128_neg" { + given value = gf128_encode_f64(5.0) + try std.testing.expect(neg_val = gf128_neg(value)); + try std.testing.expect(decoded = gf128_decode_f64(neg_val)); + try std.testing.expect(abs(decoded + 5.0) < 0.01); +} + +test "gf128_is_equal_nan" { + try std.testing.expect(gf128_is_equal(GF128_NAN, GF128_NAN) == false); +} + +test "gf128_is_greater_positive" { + given a = gf128_encode_f64(5.0) + try std.testing.expect(b = gf128_encode_f64(3.0)); + try std.testing.expect(gf128_is_greater(a, b) == true); +} + +test "gf128_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 134217728); + try std.testing.expect(mant = 0x8000000000000000000000000); + try std.testing.expect(gf = gf128_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = gf128_extract_sign(gf)); + try std.testing.expect(extracted_exp = gf128_extract_exponent(gf)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf128_bits_total + assert SIGN_BITS + EXP_BITS + MANT_BITS == 128 + +invariant gf128_bias_power_of_two_minus_one + assert BIAS + 1 == 134217728 // 2^27 + +invariant gf128_special_exp_all_ones + assert SPECIAL_EXP == EXP_MAX + +invariant gf128_zero_pos_no_sign + assert (GF128_ZERO_POS & (@as(GF128, 1) << 127)) == 0 + +invariant gf128_zero_neg_has_sign + assert (GF128_ZERO_NEG & (@as(GF128, 1) << 127)) != 0 + +invariant gf128_abs_removes_sign + assert gf128_abs(GF128_ZERO_NEG) == GF128_ZERO_POS + +invariant gf128_neg_toggles_sign + assert (gf128_neg(GF128_INF_POS) & (@as(GF128, 1) << 127)) != 0 + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf128_extract_sign_latency + measure: nanoseconds to gf128_extract_sign(0x123456789ABCDEF0123456789ABCDEF0) + target: < 15ns + +bench gf128_encode_f64_latency + measure: nanoseconds to gf128_encode_f64(123.456) + target: < 150ns + +bench gf128_decode_f64_latency + measure: nanoseconds to gf128_decode_f64(0x3FF0000000000000) + target: < 150ns + +bench gf128_add_latency + measure: nanoseconds to gf128_add(gf128_encode_f64(1.0), gf128_encode_f64(2.0)) + target: < 300ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/gf16.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf16.t27 new file mode 100644 index 0000000000..7a0eaefdeb --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf16.t27 @@ -0,0 +1,3436 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf16.t27 0 GoldenFloat16 Encode/Decode +; GF16: 16-bit floating point with 1 sign + 6 exponent + 9 mantissa +; Bit layout: [S(1) E(6) M(9)] = [15:15][14:9][8:0] +; 12 + 1/34 = 3 | TRINITY + +module triformat-gf16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const SIGN_SHIFT : u8 = 15; +pub const EXP_SHIFT : u8 = 9; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u16 = 0x8000; // 1 << 15 +pub const EXP_MASK : u16 = 0x7E00; // 0b111111 << 9 +pub const MANT_MASK : u16 = 0x01FF; // 0b111111111 + +pub const EXP_MAX : u8 = 0x3F; // 63 (all ones in 6 bits) +pub const EXP_MIN : u8 = 0x00; + +pub const BIAS : i8 = 31; // Exponent bias for GF16 +pub const SPECIAL_EXP : u8 = 0x3F; // All ones = special (Inf/NaN) + +pub const MANT_DIVISOR : u16 = 512; // 2^9 +pub const MANT_DIVISOR_SHIFT : u8 = 9; // log2(512) + +pub const PHI_BIAS : u16 = 60; // Phi-optimized rounding bias + +// GF16 special values +pub const GF16_ZERO_POS : u16 = 0x0000; +pub const GF16_ZERO_NEG : u16 = 0x8000; +pub const GF16_INF_POS : u16 = 0x7E00; +pub const GF16_INF_NEG : u16 = 0xFE00; +pub const GF16_NAN : u16 = 0xFE01; // Sign + all exp + mantissa != 0 + +// ============================================================================ +// Types +// ============================================================================ + +pub const GF16 = u16; + +// ============================================================================ +// Lookup Tables +// ============================================================================ + +// Powers of 2 for exponents 0-31 +pub const pow2_table : [32]u16 = [32]u16{ + 0x3C00, 0x3D00, 0x3D80, 0x3E00, 0x3E40, 0x3E80, 0x3EC0, 0x3F00, + 0x3F40, 0x3F80, 0x3FC0, 0x3FE0, 0x3FF0, 0x4000, 0x4040, 0x4080, + 0x40C0, 0x4100, 0x4140, 0x4180, 0x41C0, 0x4200, 0x4240, 0x4280, + 0x42C0, 0x4300, 0x4340, 0x4380, 0x43C0, 0x4400, 0x4440, 0x4480, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +// gf16_extract_sign(gf16: GF16) 5 i8 +// Extract sign bit (bit 15) +// Returns: 0 for positive, -1 for negative +pub fn gf16_extract_sign(gf16: GF16) i8 { + const bit = (gf16 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// gf16_extract_exponent(gf16: GF16) 6 i8 +// Extract exponent bits (bits 14-9) +// Returns: 0-63 +pub fn gf16_extract_exponent(gf16: GF16) i8 { + return @as(i8, @intCast((gf16 >> EXP_SHIFT) & EXP_MASK)); +} + +// gf16_extract_mantissa(gf16: GF16) 7 i16 +// Extract mantissa bits (bits 8-0) +// Returns: 0-511 +pub fn gf16_extract_mantissa(gf16: GF16) i16 { + return @as(i16, gf16 & MANT_MASK); +} + +// gf16_from_components(sign: i8, exp: i8, mant: i16) 8 GF16 +// Assemble GF16 from sign, exponent, mantissa +pub fn gf16_from_components(sign: i8, exp: i8, mant: i16) GF16 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(GF16, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(GF16, @intCast(exp)) << EXP_SHIFT) | + @as(GF16, @intCast(mant)); +} + +// gf16_is_zero(gf16: GF16) 9 bool +// Check if GF16 is zero (positive or negative) +pub fn gf16_is_zero(gf16: GF16) bool { + return gf16 == GF16_ZERO_POS or gf16 == GF16_ZERO_NEG; +} + +// gf16_is_special(gf16: GF16) 10 bool +// Check if GF16 is Inf or NaN (exp == 63) +pub fn gf16_is_special(gf16: GF16) bool { + return gf16_extract_exponent(gf16) == EXP_MAX; +} + +// gf16_encode_f32(f32: f32) 11 GF16 +// Encode IEEE 754 single precision to GF16 +// Round-to-nearest, ties to even +// Range: 2^-31 to 2^32 (normal), subnormals flushed to zero +pub fn gf16_encode_f32(value: f32) GF16 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) GF16_ZERO_NEG else GF16_ZERO_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i8 = @intCast((f32_bits >> 23) & 0xFF) - 127; + var f32_mant: u32 = f32_bits & 0x7FFFFF; + + // Convert exp from f32 bias (127) to GF16 bias (31) + // gf16_exp = f32_exp + 31 - 127 = f32_exp - 96 + var gf16_exp = f32_exp - 96; + + // Clamp exponent + if (gf16_exp < 0) { + gf16_exp = 0; // Underflow to zero + } else if (gf16_exp > EXP_MAX) { + gf16_exp = EXP_MAX; // Overflow to Inf + } + + // Extract mantissa and scale to 9 bits + // f32 mantissa is 23 bits, GF16 needs 9 bits + // Shift right by 14 bits (23 - 9 = 14) + var mant = @as(u16, @intCast(f32_mant >> 14)); + + // Round-to-nearest + const discarded = f32_mant & 0x3FFF; + if ((discarded & 0x2000) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (gf16_exp < EXP_MAX) { + gf16_exp += 1; + } + } + } + + return gf16_from_components(sign, gf16_exp, mant); +} + +// gf16_decode_to_f32(gf16: GF16) 12 f32 +// Decode GF16 to IEEE 754 single precision +pub fn gf16_decode_to_f32(gf16: GF16) f32 { + // Handle zero + if (gf16_is_zero(gf16)) { + const sign = gf16_extract_sign(gf16); + return if (sign < 0) -0.0 else 0.0; + } + + // Handle special values (Inf/NaN) + if (gf16_is_special(gf16)) { + const mant = gf16_extract_mantissa(gf16); + const sign = gf16_extract_sign(gf16); + if (mant == 0) { + // Infinity + return if (sign < 0) -std.math.inf(f32) else std.math.inf(f32); + } else { + // NaN + return std.math.nan(f32); + } + } + + // Normal number: value = (-1)^s * (1 + m/2^9) * 2^(e - 31) + const sign = gf16_extract_sign(gf16); + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + + const sign_mult = if (sign < 0) -1.0 else 1.0; + const mant_mult = 1.0 + @as(f32, @floatFromInt(mant)) / 512.0; + const exp_mult = @as(f32, @exp2(f32, @floatFromInt(exp - BIAS))); + + return sign_mult * mant_mult * exp_mult; +} + +// gf16_round_phi(value: f32) 13 GF16 +// Phi-optimized rounding for GF16 +// Uses golden ratio bias for rounding decisions instead of standard round-to-nearest +// Bias = (1/14 - 0.5) * scale, where 1/15 16 0.618 +// This improves numerical stability for sacred physics calculations +pub fn gf16_round_phi(value: f32) GF16 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) GF16_ZERO_NEG else GF16_ZERO_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i8 = @intCast((f32_bits >> 23) & 0xFF) - 127; + var f32_mant: u32 = f32_bits & 0x7FFFFF; + + // Convert exp from f32 bias (127) to GF16 bias (31) + var gf16_exp = f32_exp - 96; + + // Clamp exponent + if (gf16_exp < 0) { + gf16_exp = 0; + } else if (gf16_exp > EXP_MAX) { + gf16_exp = EXP_MAX; + } + + // Add implied 1 for normalization + const normalized_mant: u32 = f32_mant | 0x00800000; + + // Scale to 9 bits with phi bias + var mant = @as(u16, @intCast((normalized_mant >> 15) + PHI_BIAS)); + + // Check for overflow and adjust + if (mant > MANT_MASK) { + mant = 0; + if (gf16_exp < EXP_MAX) { + gf16_exp += 1; + } else { + gf16_exp = EXP_MAX; // Overflow to Inf + } + } + + return gf16_from_components(sign, gf16_exp, mant); +} + +// gf16_is_inf(gf16: GF16) 17 bool +// Check if GF16 represents infinity +pub fn gf16_is_inf(gf16: GF16) bool { + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + return (exp == EXP_MAX) and (mant == 0); +} + +// gf16_is_nan(gf16: GF16) 18 bool +// Check if GF16 represents NaN (Not a Number) +pub fn gf16_is_nan(gf16: GF16) bool { + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + return (exp == EXP_MAX) and (mant != 0); +} + +// gf16_is_negative(gf16: GF16) 19 bool +// Check if GF16 is negative (excluding negative zero) +pub fn gf16_is_negative(gf16: GF16) bool { + const sign = gf16_extract_sign(gf16); + return (sign < 0) and !gf16_is_zero(gf16); +} + +// gf16_is_positive(gf16: GF16) 20 bool +// Check if GF16 is positive (excluding positive zero) +pub fn gf16_is_positive(gf16: GF16) bool { + const sign = gf16_extract_sign(gf16); + return (sign >= 0) and !gf16_is_zero(gf16); +} + +// gf16_negate(gf16: GF16) 21 GF16 +// Negate a GF16 value (flip sign bit) +pub fn gf16_negate(gf16: GF16) GF16 { + return gf16 ^ SIGN_MASK; +} + +// gf16_abs(gf16: GF16) 22 GF16 +// Absolute value of GF16 (clear sign bit) +pub fn gf16_abs(gf16: GF16) GF16 { + return gf16 & ~SIGN_MASK; +} + +// gf16_copy_sign(gf16: GF16, sign_source: GF16) 23 GF16 +// Copy sign from sign_source to gf16 value +pub fn gf16_copy_sign(gf16: GF16, sign_source: GF16) GF16 { + const sign_mask = sign_source & SIGN_MASK; + const value_mask = gf16 & ~SIGN_MASK; + return value_mask | sign_mask; +} + +// gf16_max(a: GF16, b: GF16) 24 GF16 +// Return the greater of two GF16 values +pub fn gf16_max(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a)) return b; + if (gf16_is_nan(b)) return a; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + if (a_val >= b_val) return a else return b; +} + +// gf16_min(a: GF16, b: GF16) 25 GF16 +// Return the smaller of two GF16 values +pub fn gf16_min(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a)) return b; + if (gf16_is_nan(b)) return a; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + if (a_val <= b_val) return a else return b; +} + +// gf16_add(a: GF16, b: GF16) 26 GF16 +// Add two GF16 values (decode, add, re-encode) +// Returns NaN if either operand is NaN, Inf if overflow +pub fn gf16_add(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_inf(a) and gf16_is_inf(b)) { + // Inf + Inf = NaN (if same sign) + // Inf + (-Inf) = NaN + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + return if (a_sign == b_sign) a else GF16_NAN; + } + if (gf16_is_inf(a)) return a; + if (gf16_is_inf(b)) return b; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val + b_val; + + return gf16_encode_f32(result); +} + +// gf16_sub(a: GF16, b: GF16) 27 GF16 +// Subtract two GF16 values (decode, subtract, re-encode) +// Returns NaN if either operand is NaN, Inf if overflow +pub fn gf16_sub(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_inf(a) and gf16_is_inf(b)) { + // Inf - Inf = NaN + return GF16_NAN; + } + if (gf16_is_inf(a)) return a; + if (gf16_is_inf(b)) { + // -Inf + something = Inf (with sign flip) + return gf16_negate(b); + } + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val - b_val; + + return gf16_encode_f32(result); +} + +// gf16_mul(a: GF16, b: GF16) 28 GF16 +// Multiply two GF16 values (decode, multiply, re-encode) +// Returns NaN if either operand is NaN +pub fn gf16_mul(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_zero(a) or gf16_is_zero(b)) { + // 0 * x = 0, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_ZERO_NEG else GF16_ZERO_POS; + } + if (gf16_is_inf(a) or gf16_is_inf(b)) { + // Inf * non-zero = Inf, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_INF_NEG else GF16_INF_POS; + } + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val * b_val; + + return gf16_encode_f32(result); +} + +// gf16_div(a: GF16, b: GF16) 29 GF16 +// Divide two GF16 values (decode, divide, re-encode) +// Returns NaN if division by zero or either operand is NaN +// Returns Inf if numerator is Inf and denominator is finite non-zero +pub fn gf16_div(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_zero(b)) { + // Division by zero = Inf with sign of a + const a_sign = gf16_extract_sign(a); + return if (a_sign != 0) GF16_INF_NEG else GF16_INF_POS; + } + if (gf16_is_inf(a)) { + // Inf / finite = Inf, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_INF_NEG else GF16_INF_POS; + } + if (gf16_is_inf(b)) { + // Finite / Inf = 0, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_ZERO_NEG else GF16_ZERO_POS; + } + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val / b_val; + + return gf16_encode_f32(result); +} + +// gf16_fma(a: GF16, b: GF16, c: GF16) 30 GF16 +// Fused multiply-add: a * b + c with single rounding +// More accurate than separate mul and add +pub fn gf16_fma(a: GF16, b: GF16, c: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) return GF16_NAN; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const c_val = gf16_decode_to_f32(c); + + // Handle special cases + if (gf16_is_zero(a) or gf16_is_zero(b)) { + return gf16_add(c, gf16_encode_f32(0.0)); + } + + // Compute a * b + c + const product = a_val * b_val; + const result = product + c_val; + + return gf16_encode_f32(result); +} + +// gf16_sqrt(a: GF16) 31 GF16 +// Square root of GF16 value +// Returns NaN for negative values, Inf for infinity +pub fn gf16_sqrt(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a) and !gf16_is_negative(a)) return a; + if (gf16_is_inf(a)) return GF16_NAN; // -Inf sqrt = NaN + if (gf16_is_zero(a)) return a; + if (gf16_is_negative(a)) return GF16_NAN; + + const a_val = gf16_decode_to_f32(a); + const result = @sqrt(a_val); + + return gf16_encode_f32(result); +} + +// gf16_square(a: GF16) 32 GF16 +// Square of GF16 value +// Uses gf16_mul internally +pub fn gf16_square(a: GF16) GF16 { + return gf16_mul(a, a); +} + +// gf16_eq(a: GF16, b: GF16) 33 bool +// Equality comparison for GF16 +// NaN values are never equal to anything (including themselves) +pub fn gf16_eq(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + // For zero values, treat +0 and -0 as equal + if (gf16_is_zero(a) and gf16_is_zero(b)) return true; + return a == b; +} + +// gf16_ne(a: GF16, b: GF16) 34 bool +// Not-equal comparison for GF16 +// NaN values are not equal to anything (including themselves) +pub fn gf16_ne(a: GF16, b: GF16) bool { + return !gf16_eq(a, b); +} + +// gf16_lt(a: GF16, b: GF16) 35 bool +// Less-than comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_lt(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val < b_val; +} + +// gf16_le(a: GF16, b: GF16) 36 bool +// Less-than-or-equal comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_le(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val <= b_val; +} + +// gf16_gt(a: GF16, b: GF16) 37 bool +// Greater-than comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_gt(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val > b_val; +} + +// gf16_ge(a: GF16, b: GF16) 38 bool +// Greater-than-or-equal comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_ge(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val >= b_val; +} + +// gf16_floor(a: GF16) 39 GF16 +// Round down to the nearest integer (toward -inf) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_floor(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @floor(a_val); + + return gf16_encode_f32(result); +} + +// gf16_ceil(a: GF16) 40 GF16 +// Round up to the nearest integer (toward +inf) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_ceil(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @ceil(a_val); + + return gf16_encode_f32(result); +} + +// gf16_round(a: GF16) 41 GF16 +// Round to nearest integer, ties to even (IEEE 754 roundTiesToEven) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_round(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @round(a_val); + + return gf16_encode_f32(result); +} + +// gf16_trunc(a: GF16) 42 GF16 +// Round toward zero (truncate fractional part) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_trunc(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @trunc(a_val); + + return gf16_encode_f32(result); +} + +// gf16_fms(a: GF16, b: GF16, c: GF16) 43 GF16 +// Fused multiply-subtract: a * b - c with single rounding +// More accurate than separate mul and sub +// Useful for neural network backpropagation +pub fn gf16_fms(a: GF16, b: GF16, c: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) return GF16_NAN; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const c_val = gf16_decode_to_f32(c); + + // Handle special cases + if (gf16_is_zero(a) or gf16_is_zero(b)) { + return gf16_sub(gf16_encode_f32(0.0), c); + } + + // Compute a * b - c + const product = a_val * b_val; + const result = product - c_val; + + return gf16_encode_f32(result); +} + +// gf16_hypot(a: GF16, b: GF16) 44 GF16 +// Compute sqrt(a^2 + b^2) without overflow/underflow +// Returns NaN if either operand is NaN, Inf if both are Inf +// Useful for distance calculations, neural network normalization +pub fn gf16_hypot(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_inf(a) or gf16_is_inf(b)) return GF16_INF_POS; + + if (gf16_is_zero(a) and gf16_is_zero(b)) return GF16_ZERO_POS; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + // Standard algorithm to avoid overflow: scale by max(|a|, |b|) + const abs_a = @abs(a_val); + const abs_b = @abs(b_val); + const max_val = @max(abs_a, abs_b); + const min_val = @min(abs_a, abs_b); + + if (max_val == 0.0) return GF16_ZERO_POS; + + const ratio = min_val / max_val; + const result = max_val * @sqrt(1.0 + ratio * ratio); + + return gf16_encode_f32(result); +} + +// gf16_fmod(a: GF16, b: GF16) 45 GF16 +// Compute remainder of a / b (IEEE 754 style) +// Result has same sign as dividend (a) +// Returns NaN if divisor is zero or either operand is NaN +pub fn gf16_fmod(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_zero(b)) return GF16_NAN; + if (gf16_is_inf(a)) return GF16_NAN; + if (gf16_is_inf(b)) return a; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + // Handle zero dividend + if (a_val == 0.0) return a; + + const result = @mod(a_val, b_val); + + return gf16_encode_f32(result); +} + +// gf16_is_finite(gf16: GF16) 46 bool +// Check if GF16 value is finite (not NaN, not infinity) +pub fn gf16_is_finite(gf16: GF16) bool { + return !gf16_is_nan(gf16) and !gf16_is_inf(gf16); +} + +// gf16_is_normal(gf16: GF16) 47 bool +// Check if GF16 value is a normal (normalized) number +// Normal numbers have exponent in range [1, EXP_MAX-1] and are not zero +pub fn gf16_is_normal(gf16: GF16) bool { + if (gf16_is_zero(gf16) or gf16_is_nan(gf16) or gf16_is_inf(gf16)) { + return false; + } + + const exp = gf16_extract_exponent(gf16); + // GF16: exp = 0 is subnormal, exp = 31 is inf/nan, 1-30 is normal + return exp > 0 and exp < GF16_EXP_MAX; +} + +// gf16_is_subnormal(gf16: GF16) 48 bool +// Check if GF16 value is subnormal (denormal) +// Subnormal numbers have exponent = 0 and mantissa != 0 +pub fn gf16_is_subnormal(gf16: GF16) bool { + if (gf16_is_zero(gf16) or gf16_is_nan(gf16) or gf16_is_inf(gf16)) { + return false; + } + + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + + // Subnormal: exp = 0 and mantissa != 0 + return exp == 0 and mant != 0; +} + +// gf16_signbit(gf16: GF16) 49 bool +// Check if the sign bit is set (value is negative or negative zero) +// Returns true for negative values and negative zero +pub fn gf16_signbit(gf16: GF16) bool { + return (gf16 & GF16_SIGN_MASK) != 0; +} + +// gf16_sign(gf16: GF16) 50 i8 +// Return the sign of the GF16 value: -1 for negative, 0 for zero, +1 for positive +// Returns 0 for NaN (IEEE 754 specifies sign of NaN is undefined) +pub fn gf16_sign(gf16: GF16) i8 { + if (gf16_is_nan(gf16)) { + return 0; + } + + if (gf16_is_zero(gf16)) { + return 0; + } + + if (gf16_signbit(gf16)) { + return -1; + } else { + return 1; + } +} + +// gf16_clamp(x: GF16, min_val: GF16, max_val: GF16) 51 GF16 +// Clamp x to the range [min_val, max_val] +// Returns min_val if x < min_val, max_val if x > max_val, otherwise x +pub fn gf16_clamp(x: GF16, min_val: GF16, max_val: GF16) GF16 { + if (gf16_is_nan(x) or gf16_is_nan(min_val) or gf16_is_nan(max_val)) { + return GF16_NAN; + } + + // Decode for comparison + const x_decoded = gf16_decode_to_f32(x); + const min_decoded = gf16_decode_to_f32(min_val); + const max_decoded = gf16_decode_to_f32(max_val); + + if (x_decoded < min_decoded) { + return min_val; + } else if (x_decoded > max_decoded) { + return max_val; + } else { + return x; + } +} + +// gf16_lerp(a: GF16, b: GF16, t: GF16) 52 GF16 +// Linear interpolation: a + t * (b - a) +// Returns a when t=0, b when t=1, and interpolates for other values +pub fn gf16_lerp(a: GF16, b: GF16, t: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(t)) { + return GF16_NAN; + } + + // Decode to f32 for computation + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const t_val = gf16_decode_to_f32(t); + + // Compute: a + t * (b - a) + const result = a_val + t_val * (b_val - a_val); + + return gf16_encode_f32(result); +} + +// gf16_fnma(a: GF16, b: GF16, c: GF16) 53 GF16 +// Fused negative multiply-add: -(a * b) + c +// More accurate than computing gf16_sub(c, gf16_mul(a, b)) +pub fn gf16_fnma(a: GF16, b: GF16, c: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) { + return GF16_NAN; + } + + // Handle infinity cases + if (gf16_is_inf(a) or gf16_is_inf(b)) { + if (gf16_is_inf(c)) { + return GF16_NAN; + } + // -(inf * b) + c = -inf (with appropriate sign) + if (gf16_is_inf(a) or gf16_is_inf(b)) { + const sign_a = gf16_signbit(a); + const sign_b = gf16_signbit(b); + const result_sign = (sign_a != sign_b); // XOR for negative result + return if (result_sign) GF16_INF_NEG else GF16_INF_POS; + } + } + + if (gf16_is_inf(c)) { + return c; + } + + // Handle zero cases + if (gf16_is_zero(a) or gf16_is_zero(b)) { + return c; + } + + if (gf16_is_zero(c)) { + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const neg_product = -(a_val * b_val); + return gf16_encode_f32(neg_product); + } + + // Decode to f32 for computation + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const c_val = gf16_decode_to_f32(c); + + const result = -(a_val * b_val) + c_val; + + return gf16_encode_f32(result); +} + +// gf16_exp(x: GF16) 54 GF16 +// Compute e^x (exponential function) +// Uses Taylor series approximation for small values +// Returns Inf for very large positive inputs, 0 for very large negative inputs +pub fn gf16_exp(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_ZERO_POS; + return GF16_INF_POS; + } + + const x_val = gf16_decode_to_f32(x); + + // For large positive values, return Inf + if (x_val > 88.0) { // ln(MAX_FLOAT) for f32 + return GF16_INF_POS; + } + + // For large negative values, return 0 + if (x_val < -88.0) { + return GF16_ZERO_POS; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + x^4/4! + ... + // Use 5 terms for reasonable accuracy with GF16 precision + var result: f32 = 1.0; + var term: f32 = 1.0; + const num_terms: u32 = 5; + + for (0..num_terms) |i| { + if (i > 0) { + term *= x_val / @as(f32, @floatFromInt(i)); + result += term; + } + } + + return gf16_encode_f32(result); +} + +// gf16_log(x: GF16) 55 GF16 +// Compute natural logarithm ln(x) +// Returns NaN for x <= 0, Inf for very large x +pub fn gf16_log(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_NAN; + return GF16_INF_POS; + } + if (gf16_is_zero(x) or gf16_is_negative(x)) { + return GF16_NAN; + } + + const x_val = gf16_decode_to_f32(x); + + // For very large values, return Inf + if (x_val > 1.0e38) { + return GF16_INF_POS; + } + + // Use natural log from standard library + const result = @log(x_val); + + return gf16_encode_f32(result); +} + +// gf16_log2(x: GF16) 56 GF16 +// Compute base-2 logarithm log2(x) +pub fn gf16_log2(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_NAN; + return GF16_INF_POS; + } + if (gf16_is_zero(x) or gf16_is_negative(x)) { + return GF16_NAN; + } + + const x_val = gf16_decode_to_f32(x); + const result = @log2(x_val); + + return gf16_encode_f32(result); +} + +// gf16_log10(x: GF16) 57 GF16 +// Compute base-10 logarithm log10(x) +pub fn gf16_log10(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_NAN; + return GF16_INF_POS; + } + if (gf16_is_zero(x) or gf16_is_negative(x)) { + return GF16_NAN; + } + + const x_val = gf16_decode_to_f32(x); + const result = @log10(x_val); + + return gf16_encode_f32(result); +} + +// gf16_pow(base: GF16, exponent: GF16) 58 GF16 +// Compute base^exponent +// Handles various special cases: 0^0 = 1, 1^x = 1, x^0 = 1, etc. +pub fn gf16_pow(base: GF16, exponent: GF16) GF16 { + if (gf16_is_nan(base) or gf16_is_nan(exponent)) return GF16_NAN; + + // 0^0 = 1 (by convention) + if (gf16_is_zero(base) and gf16_is_zero(exponent)) return gf16_encode_f32(1.0); + + // 0^x = 0 for x > 0 + if (gf16_is_zero(base) and gf16_is_positive(exponent)) return GF16_ZERO_POS; + + // 0^x = Inf for x < 0 (division by zero) + if (gf16_is_zero(base) and gf16_is_negative(exponent)) return GF16_INF_POS; + + // 1^x = 1 for any finite x + const base_val = gf16_decode_to_f32(base); + if (base_val == 1.0 and !gf16_is_inf(exponent)) return gf16_encode_f32(1.0); + + // x^0 = 1 for any x != 0 + if (gf16_is_zero(exponent)) { + if (gf16_is_zero(base)) return GF16_NAN; + return gf16_encode_f32(1.0); + } + + // x^1 = x + const exp_val = gf16_decode_to_f32(exponent); + if (exp_val == 1.0) return base; + + // Use stdlib pow for general case + const result = @pow(base_val, exp_val); + + return gf16_encode_f32(result); +} + +// gf16_sin(x: GF16) 59 GF16 +// Compute sine function sin(x) where x is in radians +// Uses Taylor series approximation for small values +pub fn gf16_sin(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) return GF16_NAN; + + const x_val = gf16_decode_to_f32(x); + + // Taylor series: sin(x) = x - x^3/3! + x^5/5! - x^7/7! + ... + // Use 4 terms for reasonable accuracy + const x_sq = x_val * x_val; + const x_cub = x_sq * x_val; + const x_5 = x_cub * x_sq; + const x_7 = x_5 * x_sq; + + const term1 = x_val; + const term2 = -x_cub / 6.0; + const term3 = x_5 / 120.0; + const term4 = -x_7 / 5040.0; + + const result = term1 + term2 + term3 + term4; + + return gf16_encode_f32(result); +} + +// gf16_cos(x: GF16) 60 GF16 +// Compute cosine function cos(x) where x is in radians +// Uses Taylor series approximation for small values +pub fn gf16_cos(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) return GF16_NAN; + + const x_val = gf16_decode_to_f32(x); + + // Taylor series: cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + ... + // Use 4 terms for reasonable accuracy + const x_sq = x_val * x_val; + const x_4 = x_sq * x_sq; + const x_6 = x_4 * x_sq; + + const term0 = 1.0; + const term1 = -x_sq / 2.0; + const term2 = x_4 / 24.0; + const term3 = -x_6 / 720.0; + + const result = term0 + term1 + term2 + term3; + + return gf16_encode_f32(result); +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "gf16_roundtrip_phi" { + // Verify: encoding f32 PHI to GF16 and decoding back preserves value within tolerance + const PHI: f32 = 1.6180339887498948; + const encoded = gf16_encode_f32(PHI); + const decoded = gf16_decode_to_f32(encoded); + try std.testing.expectApproxEqAbs(PHI, decoded, 0.001); +} + +test "gf16_zero_encoding" { + // Verify: zero (positive and negative) encodes to correct GF16 patterns + try std.testing.expectEqual(@as(GF16, GF16_ZERO_POS), gf16_encode_f32(0.0)); + try std.testing.expectEqual(@as(GF16, GF16_ZERO_NEG), gf16_encode_f32(-0.0)); +} + +test "gf16_phi_roundtrip_high_precision" { + // Verify: PHI roundtrip with higher tolerance for golden ratio + const PHI: f32 = 1.6180339887498948; + const encoded = gf16_encode_f32(PHI); + const decoded = gf16_decode_to_f32(encoded); + try std.testing.expectApproxEqAbs(PHI, decoded, 0.01); +} + +test "gf16_inf_encoding" { + // Verify: overflow encodes to Inf correctly + const encoded = gf16_encode_f32(1.0e38); + try std.testing.expect(gf16_is_special(encoded)); + try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(encoded)); +} + +test "gf16_sign_extraction" { + try std.testing.expectEqual(@as(i8, -1), gf16_extract_sign(0x8000)); + try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(0x3C00)); + try std.testing.expectEqual(@as(i8, 1), gf16_extract_sign(0x8000)); + try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(0x3C00)); +} + +test "gf16_exponent_extraction" { + try std.testing.expectEqual(@as(i8, 0), gf16_extract_exponent(0x3C00)); + try std.testing.expectEqual(@as(i8, 1), gf16_extract_exponent(0x3D00)); +} + +test "gf16_mantissa_extraction" { + try std.testing.expectEqual(@as(i16, 0), gf16_extract_mantissa(0x3C00)); + try std.testing.expectEqual(@as(i16, 1), gf16_extract_mantissa(0x3C01)); + try std.testing.expectEqual(@as(i16, 511), gf16_extract_mantissa(0x3DFF)); +} + +test "gf16_zero_detection" { + try std.testing.expect(gf16_is_zero(0x0000)); + try std.testing.expect(gf16_is_zero(0x8000)); + try std.testing.expect(!gf16_is_zero(0x0001)); +} + +test "gf16_special_detection" { + try std.testing.expect(gf16_is_special(0x7E00)); + try std.testing.expect(gf16_is_special(0xFE01)); + try std.testing.expect(!gf16_is_special(0x3C00)); +} + +test "gf16_from_components" { + const result = gf16_from_components(0, 0, 0); + try std.testing.expectEqual(@as(GF16, 0x3C00), result); +} + +test "gf16_nan_encoding" { + const nan_val = gf16_from_components(0, 63, 1); + const decoded = gf16_decode_to_f32(nan_val); + try std.testing.expect(std.math.isNan(decoded)); +} + +test "gf16_round_phi_preserves_phi" { + const PHI: f32 = 1.6180339887498948; + const encoded = gf16_round_phi(PHI); + const decoded = gf16_decode_to_f32(encoded); + try std.testing.expectApproxEqAbs(PHI, decoded, 0.005); +} + +test "gf16_round_phi_zero" { + try std.testing.expectEqual(@as(GF16, GF16_ZERO_POS), gf16_round_phi(0.0)); + try std.testing.expectEqual(@as(GF16, GF16_ZERO_NEG), gf16_round_phi(-0.0)); +} + +test "gf16_round_phi_positive" { + try std.testing.expectApproxEqAbs(1.0, gf16_decode_to_f32(gf16_round_phi(1.0)), 0.01); + try std.testing.expectApproxEqAbs(2.0, gf16_decode_to_f32(gf16_round_phi(2.0)), 0.01); + try std.testing.expectApproxEqAbs(3.0, gf16_decode_to_f32(gf16_round_phi(3.0)), 0.01); +} + +test "gf16_round_phi_negative" { + try std.testing.expectApproxEqAbs(-1.0, gf16_decode_to_f32(gf16_round_phi(-1.0)), 0.01); + try std.testing.expectApproxEqAbs(-2.0, gf16_decode_to_f32(gf16_round_phi(-2.0)), 0.01); + const PHI: f32 = 1.6180339887498948; + try std.testing.expectApproxEqAbs(-PHI, gf16_decode_to_f32(gf16_round_phi(-PHI)), 0.01); +} + +test "gf16_pow2_table_consistency" { + try std.testing.expectEqual(@as(u16, 0x3C00), pow2_table[0]); // 2^0 = 1.0 + try std.testing.expectEqual(@as(u16, 0x3D00), pow2_table[1]); // 2^1 = 2.0 + try std.testing.expectEqual(@as(u16, 0x3D80), pow2_table[2]); // 2^2 = 4.0 +} + +test "gf16_exp_bias_identity" { + try std.testing.expectEqual(@as(i8, 31), BIAS); +} + +test "gf16_identity_encoding" { + // For GF16 representing 1.0: sign=0, exp=0, mant=0, raw value = 0x3C00 + try std.testing.expectEqual(@as(GF16, 0x3C00), gf16_from_components(0, 0, 0)); +} + +test "gf16_special_exp_all_ones" { + try std.testing.expectEqual(@as(u8, 0x3F), EXP_MAX); +} + +test "gf16_is_inf_positive" { + try std.testing.expect(gf16_is_inf(GF16_INF_POS)); + try std.testing.expect(!gf16_is_inf(GF16_ZERO_POS)); + try std.testing.expect(!gf16_is_inf(0x3C00)); +} + +test "gf16_is_inf_negative" { + try std.testing.expect(gf16_is_inf(GF16_INF_NEG)); + try std.testing.expect(!gf16_is_inf(GF16_ZERO_NEG)); +} + +test "gf16_is_nan_detection" { + try std.testing.expect(gf16_is_nan(GF16_NAN)); + try std.testing.expect(!gf16_is_nan(GF16_INF_POS)); + try std.testing.expect(!gf16_is_nan(GF16_ZERO_POS)); +} + +test "gf16_is_negative_detection" { + try std.testing.expect(gf16_is_negative(GF16_INF_NEG)); + try std.testing.expect(gf16_is_negative(gf16_encode_f32(-1.5))); + try std.testing.expect(!gf16_is_negative(GF16_ZERO_NEG)); // -0 is not considered "negative" + try std.testing.expect(!gf16_is_negative(GF16_INF_POS)); +} + +test "gf16_is_positive_detection" { + try std.testing.expect(gf16_is_positive(GF16_INF_POS)); + try std.testing.expect(gf16_is_positive(gf16_encode_f32(1.5))); + try std.testing.expect(!gf16_is_positive(GF16_ZERO_POS)); // +0 is not considered "positive" + try std.testing.expect(!gf16_is_positive(GF16_INF_NEG)); +} + +test "gf16_negate_sign_flip" { + const pos_one = gf16_encode_f32(1.0); + const neg_one = gf16_negate(pos_one); + const decoded = gf16_decode_to_f32(neg_one); + try std.testing.expectApproxEqAbs(-1.0, decoded, 0.01); +} + +test "gf16_negate_zero_stays_zero" { + try std.testing.expectEqual(GF16_ZERO_POS, gf16_negate(GF16_ZERO_POS)); + try std.testing.expectEqual(GF16_ZERO_NEG, gf16_negate(GF16_ZERO_NEG)); +} + +test "gf16_negate_double_negate" { + const original = gf16_encode_f32(1.5); + const negated = gf16_negate(original); + const double_negated = gf16_negate(negated); + const orig_decoded = gf16_decode_to_f32(original); + const double_decoded = gf16_decode_to_f32(double_negated); + try std.testing.expectApproxEqAbs(orig_decoded, double_decoded, 0.001); +} + +test "gf16_abs_clears_sign" { + const neg_value = gf16_encode_f32(-2.5); + const abs_value = gf16_abs(neg_value); + const decoded = gf16_decode_to_f32(abs_value); + try std.testing.expectApproxEqAbs(2.5, decoded, 0.01); +} + +test "gf16_abs_positive_unchanged" { + const pos_value = gf16_encode_f32(3.5); + const abs_value = gf16_abs(pos_value); + try std.testing.expectEqual(pos_value, abs_value); +} + +test "gf16_abs_zero_unchanged" { + try std.testing.expectEqual(GF16_ZERO_POS, gf16_abs(GF16_ZERO_POS)); + try std.testing.expectEqual(GF16_ZERO_POS, gf16_abs(GF16_ZERO_NEG)); +} + +test "gf16_copy_sign_from_negative" { + const pos_value = gf16_encode_f32(2.5); + const neg_source = gf16_encode_f32(-1.0); + const result = gf16_copy_sign(pos_value, neg_source); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.5, decoded, 0.01); +} + +test "gf16_copy_sign_from_positive" { + const neg_value = gf16_encode_f32(-2.5); + const pos_source = gf16_encode_f32(1.0); + const result = gf16_copy_sign(neg_value, pos_source); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.5, decoded, 0.01); +} + +test "gf16_max_returns_greater" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(5.0); + const result = gf16_max(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.01); +} + +test "gf16_max_equal_values" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(3.0); + const result = gf16_max(a, b); + try std.testing.expectEqual(a, result); +} + +test "gf16_max_with_nan" { + const a = gf16_encode_f32(2.0); + const nan_val = GF16_NAN; + const result = gf16_max(a, nan_val); + try std.testing.expectEqual(a, result); +} + +test "gf16_min_returns_smaller" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(5.0); + const result = gf16_min(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.01); +} + +test "gf16_min_equal_values" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(3.0); + const result = gf16_min(a, b); + try std.testing.expectEqual(a, result); +} + +test "gf16_min_with_nan" { + const a = gf16_encode_f32(2.0); + const nan_val = GF16_NAN; + const result = gf16_min(a, nan_val); + try std.testing.expectEqual(a, result); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant gf16_identity_encoding { + // For GF16 representing 1.0: sign=0, exp=0, mant=0, raw value = 0x3C00 + @compileAssert(gf16_from_components(0, 0, 0) == 0x3C00); +} + +invariant gf16_sign_mask_bit_position { + // SIGN_MASK = 0x8000 has bit 15 set (MSB) + @compileAssert(SIGN_MASK == 0x8000); +} + +invariant gf16_exp_mask_range { + // EXP_MASK = 0x7E00 covers bits 14-9 (6 bits for exponent) + @compileAssert(EXP_MASK == 0x7E00); +} + +invariant gf16_mant_mask_range { + // MANT_MASK = 0x01FF covers bits 8-0 (9 bits for mantissa) + @compileAssert(MANT_MASK == 0x01FF); +} + +invariant gf16_exp_bias_identity { + // BIAS = 31, so unbiased exp = encoded_exp - 31 + @compileAssert(BIAS == 31); +} + +invariant gf16_roundtrip_symmetry { + // For all normal values x: |decode(encode(x)) - x| < epsilon + @compileAssert(true); +} + +invariant gf16_zero_uniqueness { + // Both 0x0000 and 0x8000 represent zero (positive/negative) + @compileAssert(GF16_ZERO_POS == 0x0000); + @compileAssert(GF16_ZERO_NEG == 0x8000); +} + +invariant gf16_special_exp_all_ones { + // EXP_MAX = 0x3F (63) all ones indicates Inf/NaN + @compileAssert(EXP_MAX == 0x3F); +} + +invariant gf16_pow2_table_consistency { + // pow2_table[n] encodes 2^n for n = 0 to 31 + @compileAssert(pow2_table.len == 32); +} + +invariant gf16_mantissa_implicit_one { + // For normal numbers: actual mantissa = 1 + (stored_mant / 512) + @compileAssert(MANT_DIVISOR == 512); +} + +invariant gf16_phi_bias_positive { + // PHI_BIAS = 60 > 0 + @compileAssert(PHI_BIAS > 0); +} + +invariant gf16_phi_bias_less_than_mantissa_scale { + // PHI_BIAS = 60 < 512 (MANT_DIVISOR) + @compileAssert(PHI_BIAS < MANT_DIVISOR); +} + +invariant gf16_round_phi_preserves_sign { + // For all x: sign(gf16_round_phi(x)) = sign(x) + @compileAssert(true); +} + +invariant gf16_inf_exp_all_ones_mant_zero { + // Infinity: exp = 63, mant = 0 + @compileAssert(gf16_extract_exponent(GF16_INF_POS) == EXP_MAX); + @compileAssert(gf16_extract_mantissa(GF16_INF_POS) == 0); +} + +invariant gf16_nan_exp_all_ones_mant_nonzero { + // NaN: exp = 63, mant != 0 + @compileAssert(gf16_extract_exponent(GF16_NAN) == EXP_MAX); + @compileAssert(gf16_extract_mantissa(GF16_NAN) != 0); +} + +invariant gf16_negate_flips_sign_bit { + // gf16_negate(x) = x ^ 0x8000 + @compileAssert(gf16_negate(0x3C00) == 0xBC00); + @compileAssert(gf16_negate(0xBC00) == 0x3C00); +} + +invariant gf16_negate_involutive { + // gf16_negate(gf16_negate(x)) = x + @compileAssert(true); +} + +invariant gf16_abs_clears_sign_bit { + // gf16_abs(x) = x & ~0x8000 + @compileAssert(gf16_abs(0xBC00) == 0x3C00); + @compileAssert(gf16_abs(0x3C00) == 0x3C00); +} + +invariant gf16_abs_non_negative { + // gf16_abs(x) is always non-negative (sign bit cleared) + @compileAssert((gf16_abs(0xBC00) & SIGN_MASK) == 0); +} + +invariant gf16_copy_sign_preserves_sign_source { + // sign(gf16_copy_sign(x, s)) = sign(s) + @compileAssert(true); +} + +invariant gf16_copy_sign_preserves_magnitude { + // |gf16_copy_sign(x, s)| = |x| + @compileAssert(true); +} + +invariant gf16_max_idempotent { + // gf16_max(x, x) = x + @compileAssert(true); +} + +invariant gf16_min_idempotent { + // gf16_min(x, x) = x + @compileAssert(true); +} + +invariant gf16_max_commutative { + // gf16_max(a, b) = gf16_max(b, a) + @compileAssert(true); +} + +invariant gf16_min_commutative { + // gf16_min(a, b) = gf16_min(b, a) + @compileAssert(true); +} + +invariant gf16_is_inf_and_is_nan_exclusive { + // A value cannot be both Inf and NaN + @compileAssert(!gf16_is_inf(GF16_NAN)); + @compileAssert(!gf16_is_nan(GF16_INF_POS)); +} + +invariant gf16_add_zero_identity { + // gf16_add(x, 0) = gf16_add(0, x) = x (approximately, due to encoding) + @compileAssert(true); +} + +invariant gf16_mul_zero_annihilates { + // gf16_mul(x, 0) = gf16_mul(0, x) = 0 + @compileAssert(true); +} + +invariant gf16_mul_one_identity { + // gf16_mul(x, 1) = gf16_mul(1, x) = x (approximately) + @compileAssert(true); +} + +invariant gf16_negate_involutive { + // gf16_negate(gf16_negate(x)) = x + @compileAssert(true); +} + +invariant gf16_add_commutative { + // gf16_add(a, b) = gf16_add(b, a) + @compileAssert(true); +} + +invariant gf16_mul_commutative { + // gf16_mul(a, b) = gf16_mul(b, a) + @compileAssert(true); +} + +invariant gf16_div_by_one_identity { + // gf16_div(x, 1) = x (approximately) + @compileAssert(true); +} + +invariant gf16_sqrt_non_negative { + // gf16_sqrt(x) >= 0 for all x >= 0 + @compileAssert(true); +} + +invariant gf16_sqrt_of_square_less_than_or_equal { + // gf16_sqrt(gf16_square(x)) <= x for all x >= 0 + @compileAssert(true); +} + +invariant gf16_fma_distributive_approximation { + // gf16_fma(a, b, c) 61 gf16_add(gf16_mul(a, b), c) + // Not exact due to encoding rounding + @compileAssert(true); +} + +invariant gf16_square_positive { + // gf16_square(x) >= 0 for all x + @compileAssert(true); +} + +invariant gf16_eq_reflexive_for_non_nan { + // For all x != NaN: gf16_eq(x, x) = true + @compileAssert(true); +} + +invariant gf16_ne_irreflexive_for_non_nan { + // For all x != NaN: gf16_ne(x, x) = false + @compileAssert(true); +} + +invariant gf16_lt_and_gt_mutually_exclusive { + // For all a, b: not (gf16_lt(a, b) and gf16_gt(a, b)) + @compileAssert(true); +} + +invariant gf16_le_and_ge_mutually_inclusive { + // For all a, b: gf16_le(a, b) or gf16_ge(a, b) (for non-NaN) + @compileAssert(true); +} + +invariant gf16_lt_implies_le { + // For all a, b: gf16_lt(a, b) implies gf16_le(a, b) + @compileAssert(true); +} + +invariant gf16_gt_implies_ge { + // For all a, b: gf16_gt(a, b) implies gf16_ge(a, b) + @compileAssert(true); +} + +invariant gf16_eq_implies_le_and_ge { + // For all a, b: gf16_eq(a, b) implies gf16_le(a, b) and gf16_ge(a, b) + @compileAssert(true); +} + +invariant gf16_ne_nan_is_true { + // gf16_ne(NaN, NaN) = true per IEEE 754 + @compileAssert(true); +} + +invariant gf16_lt_nan_is_false { + // gf16_lt(NaN, x) = false for all x + @compileAssert(true); +} + +invariant gf16_gt_nan_is_false { + // gf16_gt(NaN, x) = false for all x + @compileAssert(true); +} + +invariant gf16_floor_yields_integer { + // For all x != NaN, Inf: floor(gf16_floor(x)) = gf16_floor(x) + @compileAssert(true); +} + +invariant gf16_ceil_yields_integer { + // For all x != NaN, Inf: ceil(gf16_ceil(x)) = gf16_ceil(x) + @compileAssert(true); +} + +invariant gf16_round_yields_integer { + // For all x != NaN, Inf: round(gf16_round(x)) = gf16_round(x) + @compileAssert(true); +} + +invariant gf16_trunc_yields_integer { + // For all x != NaN, Inf: trunc(gf16_trunc(x)) = gf16_trunc(x) + @compileAssert(true); +} + +invariant gf16_floor_le_value { + // For all x: floor(x) <= x + @compileAssert(true); +} + +invariant gf16_ceil_ge_value { + // For all x: ceil(x) >= x + @compileAssert(true); +} + +invariant gf16_round_closest_integer { + // For all x: |round(x) - x| <= 0.5 + @compileAssert(true); +} + +invariant gf16_trunc_magnitude_less_or_equal { + // For all x: |trunc(x)| <= |x| + @compileAssert(true); +} + +invariant gf16_trunc_positive_equals_floor { + // For all x >= 0: trunc(x) = floor(x) + @compileAssert(true); +} + +invariant gf16_trunc_negative_equals_ceil { + // For all x <= 0: trunc(x) = ceil(x) + @compileAssert(true); +} + +invariant gf16_fms_related_to_fma { + // gf16_fms(a, b, c) = gf16_fma(a, b, -c) (approximately, due to encoding) + @compileAssert(true); +} + +invariant gf16_fms_with_zero_subtractand { + // gf16_fms(a, b, 0) = gf16_mul(a, b) (approximately) + @compileAssert(true); +} + +invariant gf16_hypot_non_negative { + // For all a, b: gf16_hypot(a, b) >= 0 + @compileAssert(true); +} + +invariant gf16_hypot_symmetric { + // For all a, b: gf16_hypot(a, b) = gf16_hypot(b, a) + @compileAssert(true); +} + +invariant gf16_hypot_pythagorean_identity { + // For all a, b: hypot(a, b)^2 = a^2 + b^2 (approximately, due to encoding) + @compileAssert(true); +} + +invariant gf16_hypot_ge_max_input { + // For all a, b: gf16_hypot(a, b) >= max(|a|, |b|) + @compileAssert(true); +} + +invariant gf16_hypot_zero_with_zeros { + // gf16_hypot(0, 0) = 0 + @compileAssert(true); +} + +invariant gf16_fmod_result_sign_matches_dividend { + // For all a, b where b != 0: sign(gf16_fmod(a, b)) = sign(a) + @compileAssert(true); +} + +invariant gf16_fmod_less_than_divisor { + // For all a, b where b > 0: |gf16_fmod(a, b)| < |b| + @compileAssert(true); +} + +invariant gf16_fmod_with_divisible_values { + // For all a, b where a = k*b: gf16_fmod(a, b) = 0 + @compileAssert(true); +} + +invariant gf16_is_finite_excludes_inf_nan { + // gf16_is_finite(x) = true implies !gf16_is_inf(x) and !gf16_is_nan(x) + @compileAssert(true); +} + +invariant gf16_is_normal_implies_finite { + // gf16_is_normal(x) = true implies gf16_is_finite(x) + @compileAssert(true); +} + +invariant gf16_is_subnormal_implies_finite { + // gf16_is_subnormal(x) = true implies gf16_is_finite(x) + @compileAssert(true); +} + +invariant gf16_is_normal_and_subnormal_mutually_exclusive { + // gf16_is_normal(x) and gf16_is_subnormal(x) cannot both be true + @compileAssert(true); +} + +invariant gf16_zero_neither_normal_nor_subnormal { + // gf16_is_zero(x) = true implies !gf16_is_normal(x) and !gf16_is_subnormal(x) + @compileAssert(true); +} + +invariant gf16_classification_exhaustive { + // For all x: (is_finite and (is_normal or is_subnormal or is_zero)) or is_inf or is_nan + @compileAssert(true); +} + +invariant gf16_signbit_positive_no_signbit { + // gf16_signbit(x) = false for x >= 0 (including +0 and +inf) + @compileAssert(true); +} + +invariant gf16_signbit_negative_has_signbit { + // gf16_signbit(x) = true for x < 0 (including -0 and -inf) + @compileAssert(true); +} + +invariant gf16_sign_positive_returns_one { + // For x > 0 and x is not NaN: gf16_sign(x) = 1 + @compileAssert(true); +} + +invariant gf16_sign_negative_returns_minus_one { + // For x < 0 and x is not NaN: gf16_sign(x) = -1 + @compileAssert(true); +} + +invariant gf16_sign_zero_returns_zero { + // For x = 0 (positive or negative): gf16_sign(x) = 0 + @compileAssert(true); +} + +invariant gf16_sign_nan_returns_zero { + // For NaN: gf16_sign(x) = 0 (sign of NaN is undefined) + @compileAssert(true); +} + +invariant gf16_clamp_in_range_returns_value { + // For x in [min, max]: gf16_clamp(x, min, max) = x + @compileAssert(true); +} + +invariant gf16_clamp_below_min_returns_min { + // For x < min: gf16_clamp(x, min, max) = min + @compileAssert(true); +} + +invariant gf16_clamp_above_max_returns_max { + // For x > max: gf16_clamp(x, min, max) = max + @compileAssert(true); +} + +invariant gf16_lerp_t_zero_returns_a { + // gf16_lerp(a, b, 0) = a + @compileAssert(true); +} + +invariant gf16_lerp_t_one_returns_b { + // gf16_lerp(a, b, 1) = b + @compileAssert(true); +} + +invariant gf16_lerp_monotonic { + // For fixed a < b: gf16_lerp(a, b, t) is monotonic in t + @compileAssert(true); +} + +invariant gf16_fnma_equals_neg_mul_plus_c { + // gf16_fnma(a, b, c) = -(a*b) + c (approximately, with better precision) + @compileAssert(true); +} + +invariant gf16_fnma_zero_multiplier_returns_c { + // gf16_fnma(0, b, c) = c + @compileAssert(true); +} + +invariant gf16_exp_zero_returns_one { + // gf16_exp(0) = 1 + @compileAssert(true); +} + +invariant gf16_exp_positive_greater_than_one { + // gf16_exp(x) > 1 for x > 0 + @compileAssert(true); +} + +invariant gf16_exp_negative_between_zero_and_one { + // 0 < gf16_exp(x) < 1 for x < 0 + @compileAssert(true); +} + +invariant gf16_log_one_returns_zero { + // gf16_log(1) = 0 + @compileAssert(true); +} + +invariant gf16_log_zero_or_negative_nan { + // gf16_log(x) = NaN for x <= 0 + @compileAssert(true); +} + +invariant gf16_pow_zero_to_zero_returns_one { + // gf16_pow(0, 0) = 1 (by convention) + @compileAssert(true); +} + +invariant gf16_pow_any_to_zero_returns_one { + // gf16_pow(x, 0) = 1 for x != 0 + @compileAssert(true); +} + +invariant gf16_pow_one_to_any_returns_one { + // gf16_pow(1, x) = 1 for finite x + @compileAssert(true); +} + +invariant gf16_sin_zero_returns_zero { + // gf16_sin(0) = 0 + @compileAssert(true); +} + +invariant gf16_cos_zero_returns_one { + // gf16_cos(0) = 1 + @compileAssert(true); +} + +invariant gf16_trig_identity_approx { + // sin^2(x) + cos^2(x) 62 1 for reasonable x values + @compileAssert(true); +} + +test "gf16_add_positive_values" { + const a = gf16_encode_f32(1.5); + const b = gf16_encode_f32(2.5); + const result = gf16_add(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.1); +} + +test "gf16_add_negative_values" { + const a = gf16_encode_f32(-1.5); + const b = gf16_encode_f32(-2.5); + const result = gf16_add(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-4.0, decoded, 0.1); +} + +test "gf16_add_opposite_values" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(-2.0); + const result = gf16_add(a, b); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_add_with_zero" { + const a = gf16_encode_f32(3.5); + const zero = gf16_encode_f32(0.0); + const result1 = gf16_add(a, zero); + const result2 = gf16_add(zero, a); + const decoded1 = gf16_decode_to_f32(result1); + const decoded2 = gf16_decode_to_f32(result2); + try std.testing.expectApproxEqAbs(3.5, decoded1, 0.05); + try std.testing.expectApproxEqAbs(3.5, decoded2, 0.05); +} + +test "gf16_sub_positive_values" { + const a = gf16_encode_f32(5.0); + const b = gf16_encode_f32(2.0); + const result = gf16_sub(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_sub_negative_result" { + const a = gf16_encode_f32(1.0); + const b = gf16_encode_f32(3.0); + const result = gf16_sub(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_sub_with_zero" { + const a = gf16_encode_f32(2.5); + const zero = gf16_encode_f32(0.0); + const result1 = gf16_sub(a, zero); + const result2 = gf16_sub(zero, a); + const decoded1 = gf16_decode_to_f32(result1); + const decoded2 = gf16_decode_to_f32(result2); + try std.testing.expectApproxEqAbs(2.5, decoded1, 0.05); + try std.testing.expectApproxEqAbs(-2.5, decoded2, 0.05); +} + +test "gf16_mul_positive_values" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const result = gf16_mul(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(6.0, decoded, 0.1); +} + +test "gf16_mul_negative_positive" { + const a = gf16_encode_f32(-2.0); + const b = gf16_encode_f32(3.0); + const result = gf16_mul(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-6.0, decoded, 0.1); +} + +test "gf16_mul_with_zero" { + const a = gf16_encode_f32(5.0); + const zero = gf16_encode_f32(0.0); + const result1 = gf16_mul(a, zero); + const result2 = gf16_mul(zero, a); + try std.testing.expect(gf16_is_zero(result1)); + try std.testing.expect(gf16_is_zero(result2)); +} + +test "gf16_mul_by_one" { + const a = gf16_encode_f32(3.5); + const one = gf16_encode_f32(1.0); + const result = gf16_mul(a, one); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.5, decoded, 0.05); +} + +test "gf16_div_positive_values" { + const a = gf16_encode_f32(6.0); + const b = gf16_encode_f32(3.0); + const result = gf16_div(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_div_negative_result" { + const a = gf16_encode_f32(6.0); + const b = gf16_encode_f32(-3.0); + const result = gf16_div(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_div_by_one" { + const a = gf16_encode_f32(2.5); + const one = gf16_encode_f32(1.0); + const result = gf16_div(a, one); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.5, decoded, 0.05); +} + +test "gf16_div_zero_by_value" { + const zero = gf16_encode_f32(0.0); + const a = gf16_encode_f32(5.0); + const result = gf16_div(zero, a); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_div_value_by_zero" { + const a = gf16_encode_f32(5.0); + const zero = gf16_encode_f32(0.0); + const result = gf16_div(a, zero); + try std.testing.expect(gf16_is_inf(result)); +} + +test "gf16_div_inf_by_finite" { + const inf = GF16_INF_POS; + const a = gf16_encode_f32(5.0); + const result = gf16_div(inf, a); + try std.testing.expect(gf16_is_inf(result)); +} + +test "gf16_fma_basic" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(4.0); + const result = gf16_fma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.2); +} + +test "gf16_fma_with_zero" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const zero = gf16_encode_f32(0.0); + const result = gf16_fma(a, b, zero); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(6.0, decoded, 0.15); +} + +test "gf16_sqrt_positive" { + const a = gf16_encode_f32(4.0); + const result = gf16_sqrt(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.05); +} + +test "gf16_sqrt_of_one" { + const a = gf16_encode_f32(1.0); + const result = gf16_sqrt(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.05); +} + +test "gf16_sqrt_of_zero" { + const zero = gf16_encode_f32(0.0); + const result = gf16_sqrt(zero); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_sqrt_negative_nan" { + const neg = gf16_encode_f32(-4.0); + const result = gf16_sqrt(neg); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_square_of_two" { + const a = gf16_encode_f32(2.0); + const result = gf16_square(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.1); +} + +test "gf16_square_of_zero" { + const zero = gf16_encode_f32(0.0); + const result = gf16_square(zero); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_square_of_negative" { + const neg = gf16_encode_f32(-2.0); + const result = gf16_square(neg); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.1); +} + +test "gf16_add_commutative" { + const a = gf16_encode_f32(1.5); + const b = gf16_encode_f32(2.5); + const result1 = gf16_add(a, b); + const result2 = gf16_add(b, a); + try std.testing.expectEqual(result1, result2); +} + +test "gf16_mul_commutative" { + const a = gf16_encode_f32(1.5); + const b = gf16_encode_f32(2.5); + const result1 = gf16_mul(a, b); + const result2 = gf16_mul(b, a); + try std.testing.expectEqual(result1, result2); +} + +test "gf16_sqrt_square_roundtrip" { + const a = gf16_encode_f32(4.0); + const squared = gf16_square(a); + const rooted = gf16_sqrt(squared); + const decoded = gf16_decode_to_f32(rooted); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.2); +} + +test "gf16_eq_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(gf16_eq(a, b)); +} + +test "gf16_eq_different_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(3.5); + try std.testing.expect(!gf16_eq(a, b)); +} + +test "gf16_eq_pos_zero_eq_neg_zero" { + // IEEE 754: +0.0 == -0.0 is true + try std.testing.expect(gf16_eq(GF16_ZERO_POS, GF16_ZERO_NEG)); +} + +test "gf16_eq_nan_not_equal_nan" { + // NaN != NaN per IEEE 754 + try std.testing.expect(!gf16_eq(GF16_NAN, GF16_NAN)); +} + +test "gf16_eq_nan_not_equal_value" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_eq(GF16_NAN, value)); + try std.testing.expect(!gf16_eq(value, GF16_NAN)); +} + +test "gf16_ne_different_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(3.5); + try std.testing.expect(gf16_ne(a, b)); +} + +test "gf16_ne_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(!gf16_ne(a, b)); +} + +test "gf16_ne_nan_not_equal_nan" { + // NaN != NaN per IEEE 754 + try std.testing.expect(gf16_ne(GF16_NAN, GF16_NAN)); +} + +test "gf16_ne_nan_not_equal_value" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(gf16_ne(GF16_NAN, value)); + try std.testing.expect(gf16_ne(value, GF16_NAN)); +} + +test "gf16_lt_less_than" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(gf16_lt(a, b)); +} + +test "gf16_lt_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(!gf16_lt(a, b)); +} + +test "gf16_lt_greater_than" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(!gf16_lt(a, b)); +} + +test "gf16_lt_negative_positive" { + const neg = gf16_encode_f32(-2.0); + const pos = gf16_encode_f32(1.0); + try std.testing.expect(gf16_lt(neg, pos)); +} + +test "gf16_lt_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_lt(GF16_NAN, value)); + try std.testing.expect(!gf16_lt(value, GF16_NAN)); +} + +test "gf16_le_less_than_or_equal" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(gf16_le(a, b)); +} + +test "gf16_le_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(gf16_le(a, b)); +} + +test "gf16_le_greater_than" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(!gf16_le(a, b)); +} + +test "gf16_le_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_le(GF16_NAN, value)); + try std.testing.expect(!gf16_le(value, GF16_NAN)); +} + +test "gf16_gt_greater_than" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(gf16_gt(a, b)); +} + +test "gf16_gt_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(!gf16_gt(a, b)); +} + +test "gf16_gt_less_than" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(!gf16_gt(a, b)); +} + +test "gf16_gt_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_gt(GF16_NAN, value)); + try std.testing.expect(!gf16_gt(value, GF16_NAN)); +} + +test "gf16_ge_greater_than_or_equal" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(gf16_ge(a, b)); +} + +test "gf16_ge_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(gf16_ge(a, b)); +} + +test "gf16_ge_less_than" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(!gf16_ge(a, b)); +} + +test "gf16_ge_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_ge(GF16_NAN, value)); + try std.testing.expect(!gf16_ge(value, GF16_NAN)); +} + +test "gf16_comparison_consistency" { + // Verify: lt, le, gt, ge, eq, ne are mutually consistent + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + + // a < b implies a <= b and !a > b and !a >= b + try std.testing.expect(gf16_lt(a, b)); + try std.testing.expect(gf16_le(a, b)); + try std.testing.expect(!gf16_gt(a, b)); + try std.testing.expect(!gf16_ge(a, b)); +} + +test "gf16_floor_positive_value" { + const a = gf16_encode_f32(2.7); + const result = gf16_floor(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_floor_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_floor(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-3.0, decoded, 0.1); +} + +test "gf16_floor_integer" { + const a = gf16_encode_f32(5.0); + const result = gf16_floor(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.05); +} + +test "gf16_floor_zero" { + const pos_zero = gf16_encode_f32(0.0); + const neg_zero = gf16_encode_f32(-0.0); + try std.testing.expect(gf16_is_zero(gf16_floor(pos_zero))); + try std.testing.expect(gf16_is_zero(gf16_floor(neg_zero))); +} + +test "gf16_floor_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_floor(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_floor(GF16_INF_NEG)); +} + +test "gf16_floor_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_floor(GF16_NAN))); +} + +test "gf16_ceil_positive_value" { + const a = gf16_encode_f32(2.3); + const result = gf16_ceil(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_ceil_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_ceil(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_ceil_integer" { + const a = gf16_encode_f32(5.0); + const result = gf16_ceil(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.05); +} + +test "gf16_ceil_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_ceil(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_ceil(GF16_INF_NEG)); +} + +test "gf16_ceil_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_ceil(GF16_NAN))); +} + +test "gf16_round_half_up" { + const a = gf16_encode_f32(2.5); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); // roundTiesToEven +} + +test "gf16_round_positive_value" { + const a = gf16_encode_f32(2.7); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_round_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-3.0, decoded, 0.1); +} + +test "gf16_round_fractional_down" { + const a = gf16_encode_f32(2.3); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_round_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_round(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_round(GF16_INF_NEG)); +} + +test "gf16_round_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_round(GF16_NAN))); +} + +test "gf16_trunc_positive_value" { + const a = gf16_encode_f32(2.7); + const result = gf16_trunc(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_trunc_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_trunc(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_trunc_zero" { + const pos_zero = gf16_encode_f32(0.0); + const neg_zero = gf16_encode_f32(-0.0); + try std.testing.expect(gf16_is_zero(gf16_trunc(pos_zero))); + try std.testing.expect(gf16_is_zero(gf16_trunc(neg_zero))); +} + +test "gf16_trunc_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_trunc(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_trunc(GF16_INF_NEG)); +} + +test "gf16_trunc_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_trunc(GF16_NAN))); +} + +test "gf16_rounding_floor_vs_trunc_negative" { + // floor(-2.7) = -3.0, trunc(-2.7) = -2.0 + const a = gf16_encode_f32(-2.7); + const floored = gf16_decode_to_f32(gf16_floor(a)); + const truncated = gf16_decode_to_f32(gf16_trunc(a)); + try std.testing.expectApproxEqAbs(-3.0, floored, 0.1); + try std.testing.expectApproxEqAbs(-2.0, truncated, 0.1); +} + +test "gf16_rounding_ceil_vs_trunc_positive" { + // ceil(2.3) = 3.0, trunc(2.3) = 2.0 + const a = gf16_encode_f32(2.3); + const ceiled = gf16_decode_to_f32(gf16_ceil(a)); + const truncated = gf16_decode_to_f32(gf16_trunc(a)); + try std.testing.expectApproxEqAbs(3.0, ceiled, 0.1); + try std.testing.expectApproxEqAbs(2.0, truncated, 0.1); +} + +test "gf16_fms_basic" { + const a = gf16_encode_f32(5.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(2.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(13.0, decoded, 0.2); // 5*3 - 2 = 13 +} + +test "gf16_fms_with_zero_c" { + const a = gf16_encode_f32(4.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(0.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(12.0, decoded, 0.2); // 4*3 - 0 = 12 +} + +test "gf16_fms_negative_result" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-4.0, decoded, 0.2); // 2*3 - 10 = -4 +} + +test "gf16_fms_with_nan" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fms(a, b, GF16_NAN); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_fms_zero_a" { + const a = gf16_encode_f32(0.0); + const b = gf16_encode_f32(5.0); + const c = gf16_encode_f32(3.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-3.0, decoded, 0.2); // 0 - 3 = -3 +} + +test "gf16_hypot_pythagorean_triple" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(4.0); + const result = gf16_hypot(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); // sqrt(9 + 16) = 5 +} + +test "gf16_hypot_both_zero" { + try std.testing.expectEqual(GF16_ZERO_POS, gf16_hypot(GF16_ZERO_POS, GF16_ZERO_POS)); +} + +test "gf16_hypot_one_zero" { + const a = gf16_encode_f32(3.0); + const result = gf16_hypot(a, GF16_ZERO_POS); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.05); +} + +test "gf16_hypot_negative_inputs" { + const a = gf16_encode_f32(-3.0); + const b = gf16_encode_f32(-4.0); + const result = gf16_hypot(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); // sqrt(9 + 16) = 5 +} + +test "gf16_hypot_with_nan" { + const a = gf16_encode_f32(3.0); + const result = gf16_hypot(a, GF16_NAN); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_hypot_with_inf" { + const a = gf16_encode_f32(3.0); + try std.testing.expectEqual(GF16_INF_POS, gf16_hypot(a, GF16_INF_POS)); +} + +test "gf16_fmod_basic" { + const a = gf16_encode_f32(10.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fmod(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); // 10 % 3 = 1 +} + +test "gf16_fmod_exact_division" { + const a = gf16_encode_f32(12.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fmod(a, b); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_fmod_negative_dividend" { + const a = gf16_encode_f32(-10.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fmod(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-1.0, decoded, 0.1); // -10 % 3 = -1 (sign follows dividend) +} + +test "gf16_fmod_fractional" { + const a = gf16_encode_f32(5.5); + const b = gf16_encode_f32(2.0); + const result = gf16_fmod(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.5, decoded, 0.1); // 5.5 % 2 = 1.5 +} + +test "gf16_fmod_zero_divisor" { + const a = gf16_encode_f32(10.0); + const zero = gf16_encode_f32(0.0); + const result = gf16_fmod(a, zero); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_fmod_with_nan" { + const a = gf16_encode_f32(10.0); + const result = gf16_fmod(a, GF16_NAN); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_is_finite_normal_numbers" { + // Verify: normal numbers are finite + const n1 = gf16_encode_f32(1.0); + const n2 = gf16_encode_f32(-1.0); + const n3 = gf16_encode_f32(100.5); + try std.testing.expect(gf16_is_finite(n1)); + try std.testing.expect(gf16_is_finite(n2)); + try std.testing.expect(gf16_is_finite(n3)); +} + +test "gf16_is_finite_zero" { + // Verify: zero is finite + const z1 = gf16_encode_f32(0.0); + const z2 = gf16_encode_f32(-0.0); + try std.testing.expect(gf16_is_finite(z1)); + try std.testing.expect(gf16_is_finite(z2)); +} + +test "gf16_is_finite_false_for_inf" { + // Verify: infinity is not finite + const pos_inf = GF16_INF_POS; + const neg_inf = GF16_INF_NEG; + try std.testing.expect(!gf16_is_finite(pos_inf)); + try std.testing.expect(!gf16_is_finite(neg_inf)); +} + +test "gf16_is_finite_false_for_nan" { + // Verify: NaN is not finite + try std.testing.expect(!gf16_is_finite(GF16_NAN)); +} + +test "gf16_is_normal_true_for_normal" { + // Verify: normal numbers return true + const n1 = gf16_encode_f32(1.0); + const n2 = gf16_encode_f32(-2.5); + const n3 = gf16_encode_f32(100.0); + try std.testing.expect(gf16_is_normal(n1)); + try std.testing.expect(gf16_is_normal(n2)); + try std.testing.expect(gf16_is_normal(n3)); +} + +test "gf16_is_normal_false_for_zero" { + // Verify: zero is not normal + const z1 = gf16_encode_f32(0.0); + const z2 = gf16_encode_f32(-0.0); + try std.testing.expect(!gf16_is_normal(z1)); + try std.testing.expect(!gf16_is_normal(z2)); +} + +test "gf16_is_normal_false_for_inf" { + // Verify: infinity is not normal + try std.testing.expect(!gf16_is_normal(GF16_INF_POS)); + try std.testing.expect(!gf16_is_normal(GF16_INF_NEG)); +} + +test "gf16_is_normal_false_for_nan" { + // Verify: NaN is not normal + try std.testing.expect(!gf16_is_normal(GF16_NAN)); +} + +test "gf16_is_subnormal_true_for_subnormal" { + // Verify: subnormal (denormal) numbers return true + // Smallest subnormal in GF16: exp=0, mant=1 (approximately 2^-14 * 2^-9 = 2^-23) + // We'll check a value that decodes to subnormal + const sub = gf16_encode_f32(0.000001); + const decoded = gf16_decode_to_f32(sub); + // If the value rounds to subnormal, is_subnormal should be true + // This test depends on GF16 subnormal threshold (~6.1e-5) + const is_sub = gf16_is_subnormal(sub); + _ = decoded; + _ = is_sub; + // We just verify the function doesn't crash for now + try std.testing.expect(true); +} + +test "gf16_is_subnormal_false_for_normal" { + // Verify: normal numbers are not subnormal + const n1 = gf16_encode_f32(1.0); + const n2 = gf16_encode_f32(100.0); + try std.testing.expect(!gf16_is_subnormal(n1)); + try std.testing.expect(!gf16_is_subnormal(n2)); +} + +test "gf16_is_subnormal_false_for_zero" { + // Verify: zero is not subnormal (zero is a special case) + const z1 = gf16_encode_f32(0.0); + const z2 = gf16_encode_f32(-0.0); + try std.testing.expect(!gf16_is_subnormal(z1)); + try std.testing.expect(!gf16_is_subnormal(z2)); +} + +test "gf16_is_subnormal_false_for_special" { + // Verify: NaN and infinity are not subnormal + try std.testing.expect(!gf16_is_subnormal(GF16_NAN)); + try std.testing.expect(!gf16_is_subnormal(GF16_INF_POS)); + try std.testing.expect(!gf16_is_subnormal(GF16_INF_NEG)); +} + +test "gf16_classification_complete_coverage" { + // Verify: all GF16 values can be classified + // For any value, exactly one of these should be true: + // - is_finite and (is_normal or is_subnormal or is_zero) + // OR is_inf + // OR is_nan + + const test_values = [_]f32{ + 0.0, -0.0, 1.0, -1.0, 100.0, -100.0, + 0.0001, -0.0001, + }; + + for (test_values) |val| { + const gf = gf16_encode_f32(val); + const is_fin = gf16_is_finite(gf); + const is_inf = gf16_is_inf(gf); + const is_nan = gf16_is_nan(gf); + + // Exactly one of finite, inf, nan should be true + const count = @as(u8, @intFromBool(is_fin)) + + @as(u8, @intFromBool(is_inf)) + + @as(u8, @intFromBool(is_nan)); + try std.testing.expectEqual(@as(u8, 1), count); + } +} + +test "gf16_signbit_positive" { + // Verify: positive values have signbit = false + const val = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_signbit(val)); +} + +test "gf16_signbit_negative" { + // Verify: negative values have signbit = true + const val = gf16_encode_f32(-1.5); + try std.testing.expect(gf16_signbit(val)); +} + +test "gf16_signbit_positive_zero" { + // Verify: positive zero has signbit = false + const zero_pos = GF16_ZERO_POS; + try std.testing.expect(!gf16_signbit(zero_pos)); +} + +test "gf16_signbit_negative_zero" { + // Verify: negative zero has signbit = true + const zero_neg = GF16_ZERO_NEG; + try std.testing.expect(gf16_signbit(zero_neg)); +} + +test "gf16_signbit_infinity" { + // Verify: signbit is set for negative infinity, not for positive + try std.testing.expect(!gf16_signbit(GF16_INF_POS)); + try std.testing.expect(gf16_signbit(GF16_INF_NEG)); +} + +test "gf16_signbit_nan" { + // Verify: NaN can have signbit set or not (we check both cases) + // Most NaN implementations propagate signbit + const nan_with_sign = GF16_NAN | 0x8000; + try std.testing.expect(gf16_signbit(nan_with_sign)); +} + +test "gf16_sign_positive" { + // Verify: positive values return +1 + const v1 = gf16_encode_f32(1.0); + const v2 = gf16_encode_f32(100.5); + try std.testing.expectEqual(@as(i8, 1), gf16_sign(v1)); + try std.testing.expectEqual(@as(i8, 1), gf16_sign(v2)); +} + +test "gf16_sign_negative" { + // Verify: negative values return -1 + const v1 = gf16_encode_f32(-1.0); + const v2 = gf16_encode_f32(-100.5); + try std.testing.expectEqual(@as(i8, -1), gf16_sign(v1)); + try std.testing.expectEqual(@as(i8, -1), gf16_sign(v2)); +} + +test "gf16_sign_zero" { + // Verify: zero (positive or negative) returns 0 + try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_ZERO_POS)); + try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_ZERO_NEG)); +} + +test "gf16_sign_nan" { + // Verify: NaN returns 0 (IEEE 754 specifies sign of NaN is undefined) + try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_NAN)); +} + +test "gf16_sign_infinity" { + // Verify: positive infinity returns +1, negative returns -1 + try std.testing.expectEqual(@as(i8, 1), gf16_sign(GF16_INF_POS)); + try std.testing.expectEqual(@as(i8, -1), gf16_sign(GF16_INF_NEG)); +} + +test "gf16_sign_matches_signbit" { + // Verify: gf16_sign and gf16_signbit are consistent for non-zero values + const pos_val = gf16_encode_f32(5.5); + const neg_val = gf16_encode_f32(-5.5); + + try std.testing.expect(!gf16_signbit(pos_val) and gf16_sign(pos_val) > 0); + try std.testing.expect(gf16_signbit(neg_val) and gf16_sign(neg_val) < 0); +} + +test "gf16_clamp_in_range" { + // Verify: value within range is unchanged + const x = gf16_encode_f32(5.0); + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); +} + +test "gf16_clamp_below_min" { + // Verify: value below min returns min + const x = gf16_encode_f32(-5.0); + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.0, decoded, 0.1); +} + +test "gf16_clamp_above_max" { + // Verify: value above max returns max + const x = gf16_encode_f32(15.0); + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.1); +} + +test "gf16_clamp_with_nan" { + // Verify: NaN propagates + const x = GF16_NAN; + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_lerp_t_zero" { + // Verify: lerp with t=0 returns a + const a = gf16_encode_f32(10.0); + const b = gf16_encode_f32(20.0); + const t = gf16_encode_f32(0.0); + const result = gf16_lerp(a, b, t); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.1); +} + +test "gf16_lerp_t_one" { + // Verify: lerp with t=1 returns b + const a = gf16_encode_f32(10.0); + const b = gf16_encode_f32(20.0); + const t = gf16_encode_f32(1.0); + const result = gf16_lerp(a, b, t); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(20.0, decoded, 0.1); +} + +test "gf16_lerp_t_half" { + // Verify: lerp with t=0.5 returns midpoint + const a = gf16_encode_f32(0.0); + const b = gf16_encode_f32(10.0); + const t = gf16_encode_f32(0.5); + const result = gf16_lerp(a, b, t); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); +} + +test "gf16_lerp_with_nan" { + // Verify: NaN propagates + const a = GF16_NAN; + const b = gf16_encode_f32(20.0); + const t = gf16_encode_f32(0.5); + const result = gf16_lerp(a, b, t); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_fnma_basic" { + // Verify: fnma(a, b, c) = -(a*b) + c + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fnma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-(2.0 * 3.0) + 10.0, decoded, 0.1); // = 4.0 +} + +test "gf16_fnma_zero_multiplier" { + // Verify: fnma with zero multiplier returns c + const a = gf16_encode_f32(0.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fnma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.1); +} + +test "gf16_fnma_zero_addend" { + // Verify: fnma with c=0 returns -(a*b) + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(0.0); + const result = gf16_fnma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-(2.0 * 3.0), decoded, 0.1); // = -6.0 +} + +test "gf16_fnma_with_nan" { + // Verify: NaN propagates + const a = GF16_NAN; + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fnma(a, b, c); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_exp_zero" { + // Verify: e^0 = 1 + const x = gf16_encode_f32(0.0); + const result = gf16_exp(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); +} + +test "gf16_exp_one" { + // Verify: e^1 63 2.718 + const x = gf16_encode_f32(1.0); + const result = gf16_exp(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.718, decoded, 0.1); +} + +test "gf16_exp_negative" { + // Verify: e^-1 64 0.368 + const x = gf16_encode_f32(-1.0); + const result = gf16_exp(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.368, decoded, 0.05); +} + +test "gf16_exp_large_positive" { + // Verify: e^88 is very large (returns Inf) + const x = gf16_encode_f32(88.0); + const result = gf16_exp(x); + try std.testing.expect(gf16_is_inf(result)); +} + +test "gf16_log_one" { + // Verify: ln(1) = 0 + const x = gf16_encode_f32(1.0); + const result = gf16_log(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.0, decoded, 0.05); +} + +test "gf16_log_e" { + // Verify: ln(e) 65 1 + const e = gf16_encode_f32(2.71828); + const result = gf16_log(e); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); +} + +test "gf16_log_zero_or_negative" { + // Verify: ln(0) or ln(x<0) = NaN + const zero = gf16_encode_f32(0.0); + const neg = gf16_encode_f32(-1.0); + try std.testing.expect(gf16_is_nan(gf16_log(zero))); + try std.testing.expect(gf16_is_nan(gf16_log(neg))); +} + +test "gf16_log2_eight" { + // Verify: log2(8) = 3 + const x = gf16_encode_f32(8.0); + const result = gf16_log2(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_log10_ten" { + // Verify: log10(10) = 1 + const x = gf16_encode_f32(10.0); + const result = gf16_log10(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); +} + +test "gf16_pow_two_cubed" { + // Verify: 2^3 = 8 + const base = gf16_encode_f32(2.0); + const exp = gf16_encode_f32(3.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(8.0, decoded, 0.1); +} + +test "gf16_pow_zero_to_zero" { + // Verify: 0^0 = 1 (by convention) + const base = gf16_encode_f32(0.0); + const exp = gf16_encode_f32(0.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.01); +} + +test "gf16_pow_any_to_zero" { + // Verify: x^0 = 1 for x != 0 + const base = gf16_encode_f32(5.5); + const exp = gf16_encode_f32(0.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.01); +} + +test "gf16_pow_zero_to_positive" { + // Verify: 0^x = 0 for x > 0 + const base = gf16_encode_f32(0.0); + const exp = gf16_encode_f32(2.0); + const result = gf16_pow(base, exp); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_pow_one_to_any" { + // Verify: 1^x = 1 + const base = gf16_encode_f32(1.0); + const exp = gf16_encode_f32(5.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.01); +} + +test "gf16_sin_zero" { + // Verify: sin(0) = 0 + const x = gf16_encode_f32(0.0); + const result = gf16_sin(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.0, decoded, 0.05); +} + +test "gf16_sin_small_angle" { + // Verify: sin(66/6) 67 0.5 + const pi_six = gf16_encode_f32(3.14159 / 6.0); + const result = gf16_sin(pi_six); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.5, decoded, 0.05); +} + +test "gf16_cos_zero" { + // Verify: cos(0) = 1 + const x = gf16_encode_f32(0.0); + const result = gf16_cos(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.05); +} + +test "gf16_cos_small_angle" { + // Verify: cos(68/6) 69 0.866 + const pi_six = gf16_encode_f32(3.14159 / 6.0); + const result = gf16_cos(pi_six); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.866, decoded, 0.05); +} + +test "gf16_trig_identity" { + // Verify: sin^2(x) + cos^2(x) 70 1 for small x + const x = gf16_encode_f32(0.5); + const sin_val = gf16_decode_to_f32(gf16_sin(x)); + const cos_val = gf16_decode_to_f32(gf16_cos(x)); + const sum = sin_val * sin_val + cos_val * cos_val; + try std.testing.expectApproxEqAbs(1.0, sum, 0.1); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "gf16_encode_throughput" { + // Measure: gf16_encode_f32 calls per second + // Target: > 10M encodes/sec on typical hardware + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_encode_f32(1.5); + } + _ = result; +} + +bench "gf16_decode_throughput" { + // Measure: gf16_decode_to_f32 calls per second + // Target: > 10M decodes/sec on typical hardware + @setEvalBranchQuota(10000); + var result: f32 = 0; + for (0..1000) |_| { + result = gf16_decode_to_f32(0x3C00); + } + _ = result; +} + +bench "gf16_roundtrip_latency" { + // Measure: encode + decode latency in nanoseconds + // Target: < 100ns for typical values + @setEvalBranchQuota(10000); + var result: f32 = 0; + const value: f32 = 1.5; + for (0..1000) |_| { + result = gf16_decode_to_f32(gf16_encode_f32(value)); + } + _ = result; +} + +bench "gf16_round_phi_latency" { + // Measure: nanoseconds to gf16_round_phi(1.0) + // Target: < 200ns + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_round_phi(1.0); + } + _ = result; +} + +bench "gf16_extract_sign_latency" { + // Measure: nanoseconds to extract sign + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: i8 = 0; + for (0..1000) |_| { + result = gf16_extract_sign(0x8000); + } + _ = result; +} + +bench "gf16_extract_exponent_latency" { + // Measure: nanoseconds to extract exponent + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: i8 = 0; + for (0..1000) |_| { + result = gf16_extract_exponent(0x3C00); + } + _ = result; +} + +bench "gf16_is_inf_latency" { + // Measure: nanoseconds to check if infinity + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = gf16_is_inf(0x7E00); + } + _ = result; +} + +bench "gf16_is_nan_latency" { + // Measure: nanoseconds to check if NaN + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = gf16_is_nan(0xFE01); + } + _ = result; +} + +bench "gf16_negate_latency" { + // Measure: nanoseconds to negate + // Target: < 10ns (single XOR operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_negate(0x3C00); + } + _ = result; +} + +bench "gf16_abs_latency" { + // Measure: nanoseconds to compute absolute value + // Target: < 10ns (single AND operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_abs(0xBC00); + } + _ = result; +} + +bench "gf16_max_latency" { + // Measure: nanoseconds to compute max of two values + // Target: < 100ns (includes decode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_max(a, b); + } + _ = result; +} + +bench "gf16_min_latency" { + // Measure: nanoseconds to compute min of two values + // Target: < 100ns (includes decode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_min(a, b); + } + _ = result; +} + +bench "gf16_add_latency" { + // Measure: nanoseconds to add two values + // Target: < 200ns (includes decode + add + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_add(a, b); + } + _ = result; +} + +bench "gf16_sub_latency" { + // Measure: nanoseconds to subtract two values + // Target: < 200ns (includes decode + sub + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_sub(a, b); + } + _ = result; +} + +bench "gf16_mul_latency" { + // Measure: nanoseconds to multiply two values + // Target: < 200ns (includes decode + mul + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3D80; + for (0..1000) |_| { + result = gf16_mul(a, b); + } + _ = result; +} + +bench "gf16_div_latency" { + // Measure: nanoseconds to divide two values + // Target: < 300ns (includes decode + div + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + for (0..1000) |_| { + result = gf16_div(a, b); + } + _ = result; +} + +bench "gf16_sqrt_latency" { + // Measure: nanoseconds to compute square root + // Target: < 300ns (includes decode + sqrt + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_sqrt(a); + } + _ = result; +} + +bench "gf16_fma_latency" { + // Measure: nanoseconds for fused multiply-add + // Target: < 300ns (fused operation, more accurate than separate) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + const c: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_fma(a, b, c); + } + _ = result; +} + +bench "gf16_square_latency" { + // Measure: nanoseconds to square a value + // Target: < 200ns (uses mul internally) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_square(a); + } + _ = result; +} + +bench "gf16_eq_latency" { + // Measure: nanoseconds to compare equality + // Target: < 30ns (simple comparison with NaN check) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_eq(a, b); + } + _ = result; +} + +bench "gf16_ne_latency" { + // Measure: nanoseconds to compare not-equal + // Target: < 30ns (negation of eq) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_ne(a, b); + } + _ = result; +} + +bench "gf16_lt_latency" { + // Measure: nanoseconds to compare less-than + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_lt(a, b); + } + _ = result; +} + +bench "gf16_le_latency" { + // Measure: nanoseconds to compare less-than-or-equal + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_le(a, b); + } + _ = result; +} + +bench "gf16_gt_latency" { + // Measure: nanoseconds to compare greater-than + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_gt(a, b); + } + _ = result; +} + +bench "gf16_ge_latency" { + // Measure: nanoseconds to compare greater-than-or-equal + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_ge(a, b); + } + _ = result; +} + +bench "gf16_floor_latency" { + // Measure: nanoseconds to compute floor + // Target: < 200ns (includes decode + floor + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_floor(a); + } + _ = result; +} + +bench "gf16_ceil_latency" { + // Measure: nanoseconds to compute ceil + // Target: < 200ns (includes decode + ceil + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_ceil(a); + } + _ = result; +} + +bench "gf16_round_latency" { + // Measure: nanoseconds to compute round + // Target: < 200ns (includes decode + round + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_round(a); + } + _ = result; +} + +bench "gf16_trunc_latency" { + // Measure: nanoseconds to compute trunc + // Target: < 200ns (includes decode + trunc + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_trunc(a); + } + _ = result; +} + +bench "gf16_fms_latency" { + // Measure: nanoseconds for fused multiply-subtract + // Target: < 300ns (fused operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + const c: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_fms(a, b, c); + } + _ = result; +} + +bench "gf16_hypot_latency" { + // Measure: nanoseconds to compute hypotenuse + // Target: < 400ns (includes sqrt) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D80; + const b: GF16 = 0x3E00; + for (0..1000) |_| { + result = gf16_hypot(a, b); + } + _ = result; +} + +bench "gf16_fmod_latency" { + // Measure: nanoseconds to compute modulo + // Target: < 300ns (includes decode + mod + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3F00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_fmod(a, b); + } + _ = result; +} + +bench "gf16_is_finite_latency" { + // Measure: nanoseconds to check if value is finite + // Target: < 30ns (simple bit checks) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_is_finite(val); + } + _ = result; +} + +bench "gf16_is_normal_latency" { + // Measure: nanoseconds to check if value is normal + // Target: < 40ns (extraction + range check) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_is_normal(val); + } + _ = result; +} + +bench "gf16_is_subnormal_latency" { + // Measure: nanoseconds to check if value is subnormal + // Target: < 40ns (extraction + mantissa check) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x0001; + for (0..1000) |_| { + result = gf16_is_subnormal(val); + } + _ = result; +} + +bench "gf16_signbit_latency" { + // Measure: nanoseconds to check sign bit + // Target: < 5ns (single bit test) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x8000; + for (0..1000) |_| { + result = gf16_signbit(val); + } + _ = result; +} + +bench "gf16_sign_latency" { + // Measure: nanoseconds to get sign value + // Target: < 30ns (includes zero/nan/inf checks) + @setEvalBranchQuota(10000); + var result: i8 = 0; + const val: GF16 = 0xBC00; + for (0..1000) |_| { + result = gf16_sign(val); + } + _ = result; +} + +bench "gf16_clamp_latency" { + // Measure: nanoseconds to clamp value to range + // Target: < 200ns (includes decode + compare + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3F00; + const min_val: GF16 = 0x3C00; + const max_val: GF16 = 0x4800; + for (0..1000) |_| { + result = gf16_clamp(x, min_val, max_val); + } + _ = result; +} + +bench "gf16_lerp_latency" { + // Measure: nanoseconds to compute linear interpolation + // Target: < 300ns (includes decode + computation + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3C00; + const b: GF16 = 0x4800; + const t: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_lerp(a, b, t); + } + _ = result; +} + +bench "gf16_fnma_latency" { + // Measure: nanoseconds for fused negative multiply-add + // Target: < 300ns (fused operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + const c: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_fnma(a, b, c); + } + _ = result; +} + +bench "gf16_exp_latency" { + // Measure: nanoseconds to compute exponential + // Target: < 500ns (Taylor series) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_exp(x); + } + _ = result; +} + +bench "gf16_log_latency" { + // Measure: nanoseconds to compute natural log + // Target: < 300ns (includes decode + log + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3E00; + for (0..1000) |_| { + result = gf16_log(x); + } + _ = result; +} + +bench "gf16_pow_latency" { + // Measure: nanoseconds to compute power + // Target: < 400ns (includes decode + pow + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const base: GF16 = 0x3D00; + const exp: GF16 = 0x3D80; + for (0..1000) |_| { + result = gf16_pow(base, exp); + } + _ = result; +} + +bench "gf16_sin_latency" { + // Measure: nanoseconds to compute sine + // Target: < 500ns (Taylor series) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_sin(x); + } + _ = result; +} + +bench "gf16_cos_latency" { + // Measure: nanoseconds to compute cosine + // Target: < 500ns (Taylor series) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_cos(x); + } + _ = result; +} + + + + diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/gf20.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf20.t27 new file mode 100644 index 0000000000..5fec680c66 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf20.t27 @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf20.t27 +// GoldenFloat20 0 20-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 6 (P1) + +module GF20 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF20 bit layout: [S|EEE EEE|MMM MMMM MMMM MMM] + // S: 1 bit (sign) + // E: 7 bits (exponent) + // M: 12 bits (mantissa) + + const BITS : u8 = 20; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 7; + const MANT_BITS : u8 = 12; + + // Bias for exponent (2^(7-1) - 1 = 63) + const EXP_BIAS : u8 = 63; + + // 141-ratio: exp/mant = 7/12 142 0.583 (phi_distance = 0.035) + const PHI_DISTANCE : f64 = 0.03463264154356299; + + // 143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 + // 2. GoldenFloat20 Type + // 208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 + + struct GF20 { + raw : u32, // 20-bit value stored in u32 + } + + // 281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 + // 3. Encoding/Decoding + // 346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 + + // Encode f32 to GF20 + fn encode(value: f32) -> GF20 { + if (value == 0.0) { + return GF20{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i16; + const exp_biased = (exp_unbiased + EXP_BIAS as i16) as u8; + + // Clamp exponent + const exp_clamped = clamp(exp_biased, 0, (1 << EXP_BITS) - 1); + + // Extract mantissa (12 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF20{ + raw = ((sign as u32) << 19) | + ((exp_clamped as u32) << MANT_BITS) | + (mant as u32) + }; + } + + // Decode GF20 to f32 + fn decode(gf: GF20) -> f32 { + const sign = (gf.raw >> 19) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x7F) as u8; + const mant = (gf.raw & 0xFFF) as u16; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS as i16 + 1 + } else { + (exp_biased as i16) - EXP_BIAS as i16 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 4096.0 + } else { + 1.0 + (mant as f32) / 4096.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 + // 4. Format Properties + // 484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 + + fn max_value() -> f32 { + const mant_max = 1.0 + 4095.0 / 4096.0; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 4096.0; + const exp_min = -EXP_BIAS as i16 + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 4096.0; // 0.00024414 + } + + // 557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 + // 5. Validation + // 622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF20"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759 + // 6. Use Cases + // 760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832 + + // GF20 is optimal for: + // - High-precision ML training + // - Gradient accumulation + // - Scientific computing + // - Near-fp32 quality with 38% memory savings + + // Memory: 20 bits = 2.5 bytes (~1.6x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 20.0 / 32.0; // 0.625 + + // 833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897 + // 7. Helper Functions + // 898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970 + + fn floor_log2(x: f32) -> i16 { + if (x <= 0.0) { return -32768; } + let exp : i16 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i16, mant_bits: u8) -> u16 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1u16 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u16; + } + + fn clamp(x: u8, min: u8, max: u8) -> u8 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF20 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 97197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073 + // TDD-Inside-Spec: Tests and Invariants for GF20 + // 1074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176 + + test gf20_decode_zero + given gf = GF20{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf20_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf20_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf20_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf20_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf20_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf20_phi_distance_within_tolerance + given phi_dist = PHI_DISTANCE + then phi_dist < 0.04 + + test gf20_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.625) < 0.01 + + test gf20_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf20_bits_constant + assert BITS == 20 + + invariant gf20_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf20_exp_bits_is_seven + assert EXP_BITS == 7 + + invariant gf20_mant_bits_is_twelve + assert MANT_BITS == 12 + + invariant gf20_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf20_phi_distance_below_threshold + assert PHI_DISTANCE < 0.04 + + invariant gf20_exp_bias_positive + assert EXP_BIAS > 0 + + test gf20_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf20_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf20_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf20_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf20_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf20_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf20_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf20_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf20_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf20_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf20_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf20_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf20_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf20_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf20_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf20_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf20_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf20_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf20_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf20_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf20_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf20_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf20_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf20_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf20_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf20_encode_latency + measure: nanoseconds to encode(1.0) + target: < 200ns + + bench gf20_decode_latency + measure: nanoseconds to decode(GF20{raw = 524288}) + target: < 150ns +} diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/gf24.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf24.t27 new file mode 100644 index 0000000000..ffb9ff37bf --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf24.t27 @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf24.t27 +// GoldenFloat24 0 24-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 7 (P1) + +module GF24 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF24 bit layout: [S|EEEE EEEE|MMM MMMM MMMM MMMM MM] + // S: 1 bit (sign) + // E: 9 bits (exponent) + // M: 14 bits (mantissa) + + const BITS : u8 = 24; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 9; + const MANT_BITS : u8 = 14; + + // Bias for exponent (2^(9-1) - 1 = 255) + const EXP_BIAS : u16 = 255; + + // 141-ratio: exp/mant = 9/14 142 0.643 (phi_distance = 0.025) + const PHI_DISTANCE : f64 = 0.02482317991669112; + + // 143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 + // 2. GoldenFloat24 Type + // 208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 + + struct GF24 { + raw : u32, // 24-bit value stored in u32 + } + + // 281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 + // 3. Encoding/Decoding + // 346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 + + // Encode f32 to GF24 + fn encode(value: f32) -> GF24 { + if (value == 0.0) { + return GF24{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i16; + const exp_biased = (exp_unbiased + EXP_BIAS as i16) as u16; + + // Clamp exponent + const exp_clamped = clamp_u16(exp_biased, 0, (1u16 << EXP_BITS) - 1); + + // Extract mantissa (14 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF24{ + raw = ((sign as u32) << 23) | + ((exp_clamped as u32) << MANT_BITS) | + (mant as u32) + }; + } + + // Decode GF24 to f32 + fn decode(gf: GF24) -> f32 { + const sign = (gf.raw >> 23) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x1FF) as u16; + const mant = (gf.raw & 0x3FFF) as u16; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -(EXP_BIAS as i16) + 1 + } else { + (exp_biased as i16) - EXP_BIAS as i16 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 16384.0 + } else { + 1.0 + (mant as f32) / 16384.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 + // 4. Format Properties + // 484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 + + fn max_value() -> f32 { + const mant_max = 1.0 + 16383.0 / 16384.0; + const exp_max = (1i16 << EXP_BITS) - 1 - EXP_BIAS as i16; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 16384.0; + const exp_min = -(EXP_BIAS as i16) + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 16384.0; // 0.000061035 + } + + // 557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 + // 5. Validation + // 622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF24"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759 + // 6. Use Cases + // 760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832 + + // GF24 is optimal for: + // - Very high precision quantization + // - Critical numerical stability + // - Financial calculations + // - 25% memory savings vs FP32 + + // Memory: 24 bits = 3 bytes (~1.33x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 24.0 / 32.0; // 0.75 + + // 833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897 + // 7. Helper Functions + // 898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970 + + fn floor_log2(x: f32) -> i16 { + if (x <= 0.0) { return -32768; } + let exp : i16 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i16, mant_bits: u8) -> u16 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1u16 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u16; + } + + fn clamp_u16(x: u16, min: u16, max: u16) -> u16 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF24 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 97197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073 + // TDD-Inside-Spec: Tests and Invariants for GF24 + // 1074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176 + + test gf24_decode_zero + given gf = GF24{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf24_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf24_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf24_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf24_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf24_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf24_phi_distance_within_tolerance + given phi_dist = PHI_DISTANCE + then phi_dist < 0.03 + + test gf24_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.75) < 0.01 + + test gf24_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf24_bits_constant + assert BITS == 24 + + invariant gf24_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf24_exp_bits_is_nine + assert EXP_BITS == 9 + + invariant gf24_mant_bits_is_fourteen + assert MANT_BITS == 14 + + invariant gf24_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf24_phi_distance_below_threshold + assert PHI_DISTANCE < 0.03 + + invariant gf24_exp_bias_positive + assert EXP_BIAS > 0 + + test gf24_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf24_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf24_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf24_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf24_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf24_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf24_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf24_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf24_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf24_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf24_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf24_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf24_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf24_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf24_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf24_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf24_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf24_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf24_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf24_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf24_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf24_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf24_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf24_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf24_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf24_encode_latency + measure: nanoseconds to encode(1.0) + target: < 250ns + + bench gf24_decode_latency + measure: nanoseconds to decode(GF24{raw = 8388608}) + target: < 200ns +} diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/gf256.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf256.t27 new file mode 100644 index 0000000000..2d2facf950 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf256.t27 @@ -0,0 +1,595 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf256.t27 — GoldenFloat256 Encode/Decode +; GF256: 256-bit floating point with 1 sign + 32 exponent + 223 mantissa +; Bit layout: [S(1) E(32) M(223)] = [255:255][254:223][222:0] +; Maximum precision format for scientific computing and simulation +; φ² + 1/φ² = 3 | TRINITY + +module triformat-gf256; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const SIGN_SHIFT : u16 = 255; +pub const EXP_SHIFT : u16 = 223; +pub const MANT_SHIFT : u16 = 0; + +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 32; +pub const MANT_BITS : u8 = 223; + +pub const EXP_MAX : u32 = 0xFFFFFFFF; // 4294967295 (all ones in 32 bits) +pub const EXP_MIN : u32 = 0x00000000; + +pub const BIAS : i64 = 2147483647; // Exponent bias for GF256: 2^(32-1) - 1 +pub const SPECIAL_EXP : u32 = 0xFFFFFFFF; // All ones = special (Inf/NaN) + +pub const PHI_BIAS : u64 = 67108864; // Phi-optimized rounding bias + +// ============================================================================ +// Types +// ============================================================================ + +// GF256 represented as u256 (simulated via struct since Zig 0.15 doesn't have native u256) +pub struct GF256 { + parts: [4]u64, // parts[0] = LSB, parts[3] = MSB +} + +// ============================================================================ +// Conversion Helpers +// ============================================================================ + +// gf256_zero() -> GF256 +// Returns positive zero +pub fn gf256_zero() GF256 { + return GF256{ .parts = [_]u64{0, 0, 0, 0} }; +} + +// gf256_negative_zero() -> GF256 +// Returns negative zero +pub fn gf256_negative_zero() GF256 { + var result = gf256_zero(); + result.parts[3] |= 0x8000000000000000; // Set MSB of most significant part + return result; +} + +// gf256_inf_positive() -> GF256 +// Returns positive infinity +pub fn gf256_inf_positive() GF256 { + // Exp = all ones (0xFFFFFFFF), mant = 0 + // Exp at bits 254-223, so in parts[3] and parts[2] + return GF256{ + .parts = [_]u64{ + 0x0000000000000000, // parts[0] LSB + 0x0000000000000000, // parts[1] + 0xFFFFFFFF00000000, // parts[2]: exp bits 0-31 | mant bits 32-63 (0) + 0x00000000FFFFFFFF, // parts[3]: exp bits 32-63 | sign (0) + }, + }; +} + +// gf256_inf_negative() -> GF256 +// Returns negative infinity +pub fn gf256_inf_negative() GF256 { + var result = gf256_inf_positive(); + result.parts[3] |= 0x8000000000000000; // Set sign bit + return result; +} + +// gf256_nan() -> GF256 +// Returns NaN +pub fn gf256_nan() GF256 { + var result = gf256_inf_positive(); + result.parts[0] = 1; // Set mant LSB to make it NaN + return result; +} + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// gf256_extract_sign(gf256: GF256) -> i8 +// Extract sign bit (bit 255) +// Returns: 0 for positive, -1 for negative +pub fn gf256_extract_sign(gf256: GF256) i8 { + const bit = (gf256.parts[3] >> 63) & 1; + return if (bit != 0) -1 else 0; +} + +// gf256_extract_exponent(gf256: GF256) -> u32 +// Extract exponent bits (bits 254-223) +// Returns: 0-4294967295 +pub fn gf256_extract_exponent(gf256: GF256) u32 { + // Exponent spans bits 31-0 across parts[3] (bits 63-32) and parts[2] (bits 31-0) + // parts[3] bits 0-31 = exp bits 31-0 + // parts[2] bits 63-32 = exp bits 0-0? No, let's recalculate: + // Total bits: 256 = [S(255)] E(254:223) M(222:0) + // Exp is 32 bits at positions 254-223 + // parts[3] covers bits 255-192 + // parts[2] covers bits 191-128 + // parts[1] covers bits 127-64 + // parts[0] covers bits 63-0 + // Exp bits 254-223 map to: + // bits 254-192 = 62 bits -> parts[3] bits 62-0 (but exp is only 32 bits) + // Wait, exp is at 254:223 = 32 bits + // Bit 254 is in parts[3] at position (254 - 192) = 62 + // Bit 223 is in parts[3] at position (223 - 192) = 31 + // So exp is parts[3] >> 31 & 0xFFFFFFFF + return @as(u32, @truncate(gf256.parts[3] >> 31)); +} + +// gf256_extract_mantissa(gf256: GF256) -> GF256 +// Extract mantissa bits (bits 222-0) +// Returns: GF256 struct with mantissa in parts[0-2] and parts[3] bits 30-0 +pub fn gf256_extract_mantissa(gf256: GF256) GF256 { + var result = gf256; + // Clear sign bit and exponent bits + result.parts[3] &= 0x7FFFFFFF; // Clear sign + result.parts[3] &= 0x00000000; // Clear exp bits 31-0 in parts[3] + return result; +} + +// ============================================================================ +// Assembly Functions +// ============================================================================ + +// gf256_from_components(sign: i8, exp: u32, mant: GF256) -> GF256 +// Assemble GF256 from sign, exponent, mantissa +pub fn gf256_from_components(sign: i8, exp: u32, mant: GF256) GF256 { + var result = mant; + // Set sign + const sign_bit = if (sign < 0) 1 else 0; + if (sign_bit != 0) { + result.parts[3] |= 0x8000000000000000; + } else { + result.parts[3] &= 0x7FFFFFFFFFFFFFFF; + } + // Set exponent (bits 254-223 -> parts[3] bits 62-31) + result.parts[3] = (result.parts[3] & 0x7FFFFFFF) | (@as(u64, exp) << 31); + return result; +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// gf256_is_zero(gf256: GF256) -> bool +// Check if GF256 is zero (positive or negative) +pub fn gf256_is_zero(gf256: GF256) bool { + return gf256.parts[0] == 0 and gf256.parts[1] == 0 and + gf256.parts[2] == 0 and (gf256.parts[3] & 0x7FFFFFFFFFFFFFFF) == 0; +} + +// gf256_is_special(gf256: GF256) -> bool +// Check if GF256 is Inf or NaN (exp == 4294967295) +pub fn gf256_is_special(gf256: GF256) bool { + return gf256_extract_exponent(gf256) == EXP_MAX; +} + +// gf256_is_inf(gf256: GF256) -> bool +// Check if GF256 is infinity (exp == max, mant == 0) +pub fn gf256_is_inf(gf256: GF256) bool { + if (!gf256_is_special(gf256)) { + return false; + } + const mant = gf256_extract_mantissa(gf256); + return mant.parts[0] == 0 and mant.parts[1] == 0 and + mant.parts[2] == 0 and mant.parts[3] == 0; +} + +// gf256_is_nan(gf256: GF256) -> bool +// Check if GF256 is NaN (exp == max, mant != 0) +pub fn gf256_is_nan(gf256: GF256) bool { + if (!gf256_is_special(gf256)) { + return false; + } + const mant = gf256_extract_mantissa(gf256); + return mant.parts[0] != 0 or mant.parts[1] != 0 or + mant.parts[2] != 0 or mant.parts[3] != 0; +} + +// ============================================================================ +// Encode/Decode Functions (via f64 with reduced precision) +// ============================================================================ + +// gf256_encode_f64(f64: f64) -> GF256 +// Encode IEEE 754 double precision to GF256 +// Note: f64 has only 52 mantissa bits, so precision is not fully utilized +pub fn gf256_encode_f64(value: f64) GF256 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) gf256_negative_zero() else gf256_zero(); + } + + // Handle NaN + if (std.math.isNan(value)) { + return gf256_nan(); + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) gf256_inf_negative() else gf256_inf_positive(); + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f64 components + const f64_bits: u64 = @bitCast(abs_value); + var f64_exp: i64 = @as(i64, @intCast((f64_bits >> 52) & 0x7FF)) - 1023; + var f64_mant: u64 = f64_bits & 0x000FFFFFFFFFFFFF; + + // Convert exp from f64 bias (1023) to GF256 bias (2147483647) + var gf256_exp = @as(u32, @intCast(f64_exp + BIAS)); + + // Clamp exponent + if (gf256_exp >= EXP_MAX) { + return if (sign < 0) gf256_inf_negative() else gf256_inf_positive(); + } + + // Create mantissa struct with f64 mantissa in parts[0] + var mant: GF256 = undefined; + mant.parts[0] = f64_mant; + mant.parts[1] = 0; + mant.parts[2] = 0; + mant.parts[3] = 0; + + return gf256_from_components(sign, gf256_exp, mant); +} + +// gf256_decode_f64(gf256: GF256) -> f64 +// Decode GF256 to IEEE 754 double precision +// Note: This loses precision as f64 has only 52 mantissa bits +pub fn gf256_decode_f64(gf256: GF256) f64 { + // Handle zero + if (gf256_is_zero(gf256)) { + return if (gf256_extract_sign(gf256) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (gf256_is_nan(gf256)) { + return std.math.nan(f64); + } + + // Handle Infinity + if (gf256_is_inf(gf256)) { + return if (gf256_extract_sign(gf256) < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + // Extract components + const sign = gf256_extract_sign(gf256); + const exp = gf256_extract_exponent(gf256); + const mant = gf256_extract_mantissa(gf256); + + // Use only first 52 bits of mantissa + const mant_f64 = @as(f64, @floatFromInt(mant.parts[0])) / 4503599627370496.0; + const bias_adjusted = @as(i64, @intCast(exp)) - BIAS; + + if (bias_adjusted < -1022 or bias_adjusted > 1023) { + return if (sign < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + const value = mant_f64 * std.math.pow(f64, 2.0, @as(f64, @floatFromInt(bias_adjusted))); + return if (sign < 0) -value else value; +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// gf256_add(a: GF256, b: GF256) -> GF256 +pub fn gf256_add(a: GF256, b: GF256) GF256 { + return gf256_encode_f64(gf256_decode_f64(a) + gf256_decode_f64(b)); +} + +// gf256_sub(a: GF256, b: GF256) -> GF256 +pub fn gf256_sub(a: GF256, b: GF256) GF256 { + return gf256_encode_f64(gf256_decode_f64(a) - gf256_decode_f64(b)); +} + +// gf256_mul(a: GF256, b: GF256) -> GF256 +pub fn gf256_mul(a: GF256, b: GF256) GF256 { + return gf256_encode_f64(gf256_decode_f64(a) * gf256_decode_f64(b)); +} + +// gf256_div(a: GF256, b: GF256) -> GF256 +pub fn gf256_div(a: GF256, b: GF256) GF256 { + const fb = gf256_decode_f64(b); + if (fb == 0.0) { + const fa = gf256_decode_f64(a); + return if (fa < 0.0) gf256_inf_negative() else gf256_inf_positive(); + } + return gf256_encode_f64(gf256_decode_f64(a) / fb); +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// gf256_abs(gf256: GF256) -> GF256 +pub fn gf256_abs(gf256: GF256) GF256 { + var result = gf256; + result.parts[3] &= 0x7FFFFFFFFFFFFFFF; + return result; +} + +// gf256_neg(gf256: GF256) -> GF256 +pub fn gf256_neg(gf256: GF256) GF256 { + var result = gf256; + result.parts[3] ^= 0x8000000000000000; + return result; +} + +// gf256_is_equal(a: GF256, b: GF256) -> bool +pub fn gf256_is_equal(a: GF256, b: GF256) bool { + if (gf256_is_nan(a) or gf256_is_nan(b)) { + return false; + } + if (gf256_is_zero(a) and gf256_is_zero(b)) { + return true; + } + return a.parts[0] == b.parts[0] and a.parts[1] == b.parts[1] and + a.parts[2] == b.parts[2] and a.parts[3] == b.parts[3]; +} + +// gf256_is_greater(a: GF256, b: GF256) -> bool +pub fn gf256_is_greater(a: GF256, b: GF256) bool { + if (gf256_is_nan(a) or gf256_is_nan(b)) { + return false; + } + const sign_a = gf256_extract_sign(a); + const sign_b = gf256_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + // For negative numbers, reverse comparison + if (sign_a < 0) { + const neg_a = gf256_neg(a); + const neg_b = gf256_neg(b); + return neg_a.parts[3] > neg_b.parts[3] or + (neg_a.parts[3] == neg_b.parts[3] and neg_a.parts[2] > neg_b.parts[2]) or + (neg_a.parts[2] == neg_b.parts[2] and neg_a.parts[1] > neg_b.parts[1]) or + (neg_a.parts[1] == neg_b.parts[1] and neg_a.parts[0] > neg_b.parts[0]); + } + // Positive comparison + return a.parts[3] > b.parts[3] or + (a.parts[3] == b.parts[3] and a.parts[2] > b.parts[2]) or + (a.parts[2] == b.parts[2] and a.parts[1] > b.parts[1]) or + (a.parts[1] == b.parts[1] and a.parts[0] > b.parts[0]); +} + +// gf256_max(a: GF256, b: GF256) -> GF256 +pub fn gf256_max(a: GF256, b: GF256) GF256 { + return if (gf256_is_greater(a, b)) a else b; +} + +// gf256_min(a: GF256, b: GF256) -> GF256 +pub fn gf256_min(a: GF256, b: GF256) GF256 { + return if (gf256_is_greater(a, b)) b else a; +} + +// gf256_clamp(value: GF256, min: GF256, max: GF256) -> GF256 +pub fn gf256_clamp(value: GF256, min: GF256, max: GF256) GF256 { + return gf256_min(gf256_max(value, min), max); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf256_zero_is_zero" { + try std.testing.expect(gf256_is_zero(gf256_zero()) == true); +} + +test "gf256_negative_zero_is_zero" { + try std.testing.expect(gf256_is_zero(gf256_negative_zero()) == true); +} + +test "gf256_nonzero_is_not_zero" { + given value = gf256_encode_f64(1.0) + try std.testing.expect(gf256_is_zero(value) == false); +} + +test "gf256_inf_is_special" { + try std.testing.expect(gf256_is_special(gf256_inf_positive()) == true); +} + +test "gf256_nan_is_special" { + try std.testing.expect(gf256_is_special(gf256_nan()) == true); +} + +test "gf256_inf_positive" { + try std.testing.expect(gf256_is_inf(gf256_inf_positive()) == true); +} + +test "gf256_inf_negative" { + try std.testing.expect(gf256_is_inf(gf256_inf_negative()) == true); +} + +test "gf256_nan" { + try std.testing.expect(gf256_is_nan(gf256_nan()) == true); +} + +test "gf256_inf_not_nan" { + try std.testing.expect(gf256_is_nan(gf256_inf_positive()) == false); +} + +test "gf256_extract_sign_positive" { + given value = gf256_encode_f64(1.0) + try std.testing.expect(sign = gf256_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "gf256_extract_sign_negative" { + given value = gf256_encode_f64(-1.0) + try std.testing.expect(sign = gf256_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "gf256_encode_f64_zero" { + given gf = gf256_encode_f64(0.0) + try std.testing.expect(gf256_is_equal(gf, gf256_zero()) == true); +} + +test "gf256_encode_f64_one" { + given gf = gf256_encode_f64(1.0) + try std.testing.expect(decoded = gf256_decode_f64(gf)); + try std.testing.expect(abs(decoded - 1.0) < 0.0001); +} + +test "gf256_encode_f64_negative_one" { + given gf = gf256_encode_f64(-1.0) + try std.testing.expect(decoded = gf256_decode_f64(gf)); + try std.testing.expect(abs(decoded + 1.0) < 0.0001); +} + +test "gf256_encode_f64_roundtrip" { + given original = 42.5 + try std.testing.expect(gf = gf256_encode_f64(original)); + try std.testing.expect(decoded = gf256_decode_f64(gf)); + try std.testing.expect(abs(decoded - original) < 0.001); +} + +test "gf256_add_simple" { + given a = gf256_encode_f64(1.0) + try std.testing.expect(b = gf256_encode_f64(2.0)); + try std.testing.expect(result = gf256_add(a, b)); + try std.testing.expect(decoded = gf256_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.01); +} + +test "gf256_mul_simple" { + given a = gf256_encode_f64(3.0) + try std.testing.expect(b = gf256_encode_f64(4.0)); + try std.testing.expect(result = gf256_mul(a, b)); + try std.testing.expect(decoded = gf256_decode_f64(result)); + try std.testing.expect(abs(decoded - 12.0) < 0.01); +} + +test "gf256_abs_positive" { + given value = gf256_encode_f64(5.0) + try std.testing.expect(abs_val = gf256_abs(value)); + try std.testing.expect(gf256_is_equal(abs_val, value) == true); +} + +test "gf256_abs_negative" { + given value = gf256_encode_f64(-5.0) + try std.testing.expect(abs_val = gf256_abs(value)); + try std.testing.expect(gf256_extract_sign(abs_val) == 0); +} + +test "gf256_neg_positive" { + given value = gf256_encode_f64(5.0) + try std.testing.expect(neg_val = gf256_neg(value)); + try std.testing.expect(gf256_extract_sign(neg_val) < 0); +} + +test "gf256_neg_negative" { + given value = gf256_encode_f64(-5.0) + try std.testing.expect(neg_val = gf256_neg(value)); + try std.testing.expect(gf256_extract_sign(neg_val) == 0); +} + +test "gf256_is_equal_same" { + given value = gf256_encode_f64(1.0) + try std.testing.expect(gf256_is_equal(value, value) == true); +} + +test "gf256_is_equal_nan" { + try std.testing.expect(gf256_is_equal(gf256_nan(), gf256_nan()) == false); +} + +test "gf256_is_equal_zero" { + try std.testing.expect(gf256_is_equal(gf256_zero(), gf256_negative_zero()) == true); +} + +test "gf256_is_greater_positive" { + given a = gf256_encode_f64(5.0) + try std.testing.expect(b = gf256_encode_f64(3.0)); + try std.testing.expect(gf256_is_greater(a, b) == true); +} + +test "gf256_is_greater_negative" { + given a = gf256_encode_f64(-3.0) + try std.testing.expect(b = gf256_encode_f64(-5.0)); + try std.testing.expect(gf256_is_greater(a, b) == true); +} + +test "gf256_max_returns_larger" { + given a = gf256_encode_f64(3.0) + try std.testing.expect(b = gf256_encode_f64(5.0)); + try std.testing.expect(result = gf256_max(a, b)); + try std.testing.expect(decoded = gf256_decode_f64(result)); + try std.testing.expect(decoded >= 4.0); +} + +test "gf256_min_returns_smaller" { + given a = gf256_encode_f64(3.0) + try std.testing.expect(b = gf256_encode_f64(5.0)); + try std.testing.expect(result = gf256_min(a, b)); + try std.testing.expect(decoded = gf256_decode_f64(result)); + try std.testing.expect(decoded <= 4.0); +} + +test "gf256_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 2147483648); + try std.testing.expect(mant = gf256_zero()); + try std.testing.expect(mant.parts[0] = 0x8000000000000000); + try std.testing.expect(gf = gf256_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = gf256_extract_sign(gf)); + try std.testing.expect(extracted_exp = gf256_extract_exponent(gf)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf256_bits_total + assert SIGN_BITS + EXP_BITS + MANT_BITS == 256 + +invariant gf256_bias_power_of_two_minus_one + assert BIAS + 1 == 2147483648 // 2^31 + +invariant gf256_special_exp_all_ones + assert SPECIAL_EXP == EXP_MAX + +invariant gf256_zero_no_sign_mant + given zero = gf256_zero() + assert zero.parts[0] == 0 and zero.parts[1] == 0 and zero.parts[2] == 0 and (zero.parts[3] & 0x7FFFFFFFFFFFFFFF) == 0 + +invariant gf256_neg_zero_has_sign + given neg_zero = gf256_negative_zero() + assert (neg_zero.parts[3] & 0x8000000000000000) != 0 + +invariant gf256_abs_removes_sign + assert (gf256_abs(gf256_negative_zero()).parts[3] & 0x8000000000000000) == 0 + +invariant gf256_neg_toggles_sign + given pos = gf256_encode_f64(5.0) + try std.testing.expect(neg = gf256_neg(pos)); + assert (pos.parts[3] & 0x8000000000000000) != (neg.parts[3] & 0x8000000000000000) + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf256_extract_sign_latency + measure: nanoseconds to gf256_extract_sign(gf256_encode_f64(1.0)) + target: < 20ns + +bench gf256_encode_f64_latency + measure: nanoseconds to gf256_encode_f64(123.456) + target: < 200ns + +bench gf256_decode_f64_latency + measure: nanoseconds to gf256_decode_f64(gf256_encode_f64(123.456)) + target: < 200ns + +bench gf256_add_latency + measure: nanoseconds to gf256_add(gf256_encode_f64(1.0), gf256_encode_f64(2.0)) + target: < 400ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/gf32.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf32.t27 new file mode 100644 index 0000000000..a5e2e14a92 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf32.t27 @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf32.t27 +// GoldenFloat32 0 32-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 8 (P1) + +module GF32 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF32 bit layout: [S|EEEE EEEE EEEE|MMM MMMM MMMM MMMM MMMM MMM] + // S: 1 bit (sign) + // E: 12 bits (exponent) + // M: 19 bits (mantissa) + + const BITS : u8 = 32; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 12; + const MANT_BITS : u8 = 19; + + // Bias for exponent (2^(12-1) - 1 = 2047) + const EXP_BIAS : u16 = 2047; + + // 141-ratio: exp/mant = 12/19 142 0.632 (phi_distance = 0.014) + // This is the second-best 143-approximation after GF12 + const PHI_DISTANCE : f64 = 0.01354495894042812; + + // 144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 + // 2. GoldenFloat32 Type + // 209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 + + struct GF32 { + raw : u32, // 32-bit raw value + } + + // 282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 + // 3. Encoding/Decoding + // 347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 + + // Encode f32 to GF32 + fn encode(value: f32) -> GF32 { + if (value == 0.0) { + return GF32{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1u32 } else { 0u32 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i16; + const exp_biased = (exp_unbiased + EXP_BIAS as i16) as u16; + + // Clamp exponent + const exp_clamped = clamp_u16(exp_biased, 0, (1u16 << EXP_BITS) - 1); + + // Extract mantissa (19 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF32{ + raw = (sign << 31) | + ((exp_clamped as u32) << MANT_BITS) | + (mant as u32) + }; + } + + // Decode GF32 to f32 + fn decode(gf: GF32) -> f32 { + const sign = (gf.raw >> 31) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0xFFF) as u16; + const mant = (gf.raw & 0x7FFFF) as u32; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -(EXP_BIAS as i16) + 1 + } else { + (exp_biased as i16) - EXP_BIAS as i16 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 524288.0 + } else { + 1.0 + (mant as f32) / 524288.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 + // 4. Format Properties + // 485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 + + fn max_value() -> f32 { + const mant_max = 1.0 + 524287.0 / 524288.0; + const exp_max = (1i16 << EXP_BITS) - 1 - EXP_BIAS as i16; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 524288.0; + const exp_min = -(EXP_BIAS as i16) + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 524288.0; // 0.000001907 + } + + // 558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 + // 5. Validation + // 623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF32"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760 + // 6. Use Cases + // 761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833 + + // GF32 is optimal for: + // - Near-IEEE 754 precision with 834-optimized layout + // - 12-bit exponent (vs IEEE's 8-bit) for wider dynamic range + // - 19-bit mantissa (vs IEEE's 23-bit) - still good precision + // - Same memory footprint as FP32, better 835-ratio + + // Comparison with IEEE FP32: + // - IEEE: 1 sign, 8 exp, 23 mant 836 exp/mant = 0.348 (phi_distance = 0.270) + // - GF32: 1 sign, 12 exp, 19 mant 837 exp/mant = 0.632 (phi_distance = 0.014) + + // Memory: 32 bits = 4 bytes (same as FP32) + const MEMORY_RATIO_VS_FP32 : f32 = 1.0; + + // 838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902 + // 7. Helper Functions + // 903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975 + + fn floor_log2(x: f32) -> i16 { + if (x <= 0.0) { return -32768; } + let exp : i16 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i16, mant_bits: u8) -> u32 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1u32 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u32; + } + + fn clamp_u16(x: u16, min: u16, max: u16) -> u16 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF32 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 9769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078 + // TDD-Inside-Spec: Tests and Invariants for GF32 + // 1079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181 + + test gf32_decode_zero + given gf = GF32{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf32_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf32_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf32_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf32_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf32_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf32_phi_distance_near_optimal + given phi_dist = PHI_DISTANCE + then phi_dist < 0.015 + + test gf32_memory_ratio_equals_one + given ratio = MEMORY_RATIO_VS_FP32 + then ratio == 1.0 + + test gf32_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf32_bits_constant + assert BITS == 32 + + invariant gf32_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf32_exp_bits_is_twelve + assert EXP_BITS == 12 + + invariant gf32_mant_bits_is_nineteen + assert MANT_BITS == 19 + + invariant gf32_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf32_phi_distance_near_optimal + assert PHI_DISTANCE < 0.015 + + invariant gf32_exp_bias_positive + assert EXP_BIAS > 0 + + invariant gf32_exp_wider_than_ieee + assert EXP_BITS > 8 // IEEE FP32 has 8-bit exponent + + invariant gf32_mant_narrower_than_ieee + assert MANT_BITS < 23 // IEEE FP32 has 23-bit mantissa + + test gf32_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf32_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf32_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf32_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf32_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf32_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf32_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf32_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf32_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf32_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf32_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf32_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf32_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf32_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf32_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf32_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf32_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf32_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf32_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf32_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf32_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf32_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf32_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf32_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf32_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf32_encode_latency + measure: nanoseconds to encode(1.0) + target: < 300ns + + bench gf32_decode_latency + measure: nanoseconds to decode(GF32{raw = 1065353216}) + target: < 250ns +} diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/gf4.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf4.t27 new file mode 100644 index 0000000000..499b74e34f --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf4.t27 @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf4.t27 +// GoldenFloat4 0 4-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 2 (P1) + +module GF4 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF4 bit layout: [S|E|MM] + // S: 1 bit (sign) + // E: 1 bit (exponent) + // M: 2 bits (mantissa) + + const BITS : u8 = 4; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 1; + const MANT_BITS : u8 = 2; + + // Bias for exponent (0-biased for GF4) + const EXP_BIAS : u8 = 0; + + // 141-ratio: exp/mant = 1/2 = 0.5 (phi_distance = 0.118) + const PHI_DISTANCE : f64 = 0.1180339887498949; + + // 142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 + // 2. GoldenFloat4 Type + // 207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 + + struct GF4 { + raw : u4, // 4-bit raw value + } + + // 280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 + // 3. Encoding/Decoding + // 345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 + + // Encode f32 to GF4 + fn encode(value: f32) -> GF4 { + // Special cases + if (value == 0.0) { + return GF4{ raw = 0b0000 }; + } + if (value < 0.0) { + const pos = encode(-value).raw; + return GF4{ raw = pos | 0b1000 }; // Set sign bit + } + + // For GF4, quantize to available values + // Available positive values (mant * exp_scale): + // mant=0.00, exp=1.0 418 0.00 + // mant=0.25, exp=1.0 419 0.25 + // mant=0.50, exp=1.0 420 0.50 + // mant=0.75, exp=1.0 421 0.75 + // mant=0.00, exp=2.0 422 0.00 + // mant=0.25, exp=2.0 423 0.50 + // mant=0.50, exp=2.0 424 1.00 + // mant=0.75, exp=2.0 425 1.50 + + // Unique positive non-zero values: 0.25, 0.5, 0.75, 1.0, 1.5 + + if (value <= 0.375) { + // 0.25 + return GF4{ raw = 0b0001 }; + } else if (value <= 0.625) { + // 0.5 + return GF4{ raw = 0b0010 }; + } else if (value <= 0.875) { + // 0.75 + return GF4{ raw = 0b0011 }; + } else if (value <= 1.25) { + // 1.0 + return GF4{ raw = 0b0101 }; + } else { + // 1.5 (max) + return GF4{ raw = 0b0111 }; + } + } + + // Decode GF4 to f32 + fn decode(gf: GF4) -> f32 { + const sign_bit = (gf.raw & 0b1000) != 0; + const exp_bit = (gf.raw & 0b0100) != 0; + const mant_bits = gf.raw & 0b0011; + + // Zero + if (gf.raw == 0) { + return 0.0; + } + + // Decode mantissa (2 bits 426 values 0, 0.25, 0.5, 0.75) + const mant = (mant_bits as f32) / 4.0; + + // Decode exponent (1 bit 427 1.0 or 2.0) + const exp_scale = if (exp_bit) { 2.0 } else { 1.0 }; + + const value = mant * exp_scale; + + if (sign_bit) { + return -value; + } + return value; + } + + // 428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 + // 4. Format Properties + // 493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565 + + fn max_value() -> f32 { + // Max: mant=0.75, exp=2.0 566 1.5 + return 1.5; + } + + fn min_positive() -> f32 { + // Min positive: mant=0.25, exp=1.0 567 0.25 + return 0.25; + } + + fn epsilon() -> f32 { + // Smallest representable difference at 1.0 + return 0.25; + } + + // 568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632 + // 5. Validation + // 633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705 + + fn validate_format() -> bool { + // Check that we match the goldenfloat_family definition + const fmt = goldenfloat_family::get_format_by_name("GF4"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770 + // 6. Use Cases + // 771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843 + + // GF4 is optimal for: + // - Extreme compression (87.5% smaller than FP32) + // - Binary/ternary classification + // - Attention masks + // - Activation sparsity indicators + + // Memory: 4 bits = 0.5 bytes (8x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 4.0 / 32.0; // 0.125 + + // 844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946 + // TDD-Inside-Spec: Tests and Invariants for GF4 + // 94794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049 + + test gf4_decode_zero + given gf = GF4{ raw = 0b0000 } + when value = decode(gf) + then value == 0.0 + + test gf4_decode_positive_max + given gf = GF4{ raw = 0b0111 } + when value = decode(gf) + then value == 1.5 + + test gf4_decode_negative + given gf = GF4{ raw = 0b1001 } + when value = decode(gf) + then value < 0.0 + + test gf4_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf4_encode_0_25 + given original = 0.25 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.25) < 0.01 + + test gf4_encode_0_5 + given original = 0.5 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.5) < 0.01 + + test gf4_encode_0_75 + given original = 0.75 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.75) < 0.01 + + test gf4_encode_1_0 + given original = 1.0 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 1.0) < 0.01 + + test gf4_encode_1_5 + given original = 1.5 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 1.5) < 0.01 + + test gf4_encode_negative_values + given original = -0.5 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded < 0.0 and abs(decoded - (-0.5)) < 0.01 + + test gf4_encode_clamps_to_max + given original = 10.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded <= 1.5 + + test gf4_encode_quantization_small + given original = 0.3 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.25) < 0.01 + + test gf4_max_value_is_1_5 + given max_val = max_value() + then max_val == 1.5 + + test gf4_min_positive_is_0_25 + given min_pos = min_positive() + then min_pos == 0.25 + + test gf4_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf4_exp_mant_ratio_matches_phi_split + given ratio = (EXP_BITS as f64) / (MANT_BITS as f64) + and expected = 0.5 + then abs(ratio - expected) < 0.01 + + test gf4_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then ratio == 0.125 + + test gf4_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf4_bits_constant + assert BITS == 4 + + invariant gf4_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf4_exp_bits_is_one + assert EXP_BITS == 1 + + invariant gf4_mant_bits_is_two + assert MANT_BITS == 2 + + invariant gf4_max_value_positive + assert max_value() > 0.0 + + invariant gf4_min_positive_greater_than_zero + assert min_positive() > 0.0 + + invariant gf4_epsilon_positive + assert epsilon() > 0.0 + + invariant gf4_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf4_phi_distance_within_tolerance + assert PHI_DISTANCE < 0.12 + + invariant gf4_encode_decode_roundtrip + given encoded = encode(x) for x in {0.25, 0.5, 0.75, 1.0, 1.5} + when decoded = decode(encoded) + then abs(decoded - x) < 0.01 + + invariant gf4_encode_zero_returns_zero + assert encode(0.0).raw == 0b0000 + + invariant gf4_encode_positive_no_sign_bit + given result = encode(1.0) + when has_sign = (result.raw & 0b1000) != 0 + then has_sign == false + + invariant gf4_encode_negative_has_sign_bit + given result = encode(-1.0) + when has_sign = (result.raw & 0b1000) != 0 + then has_sign == true + + bench gf4_encode_latency + measure: nanoseconds to encode(1.0) + target: < 100ns + + bench gf4_decode_latency + measure: nanoseconds to decode(GF4{raw = 0b0101}) + target: < 50ns +} diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/gf64.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf64.t27 new file mode 100644 index 0000000000..cb86276ee1 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf64.t27 @@ -0,0 +1,764 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf64.t27 — GoldenFloat64 Encode/Decode +; GF64: 64-bit floating point with 1 sign + 18 exponent + 45 mantissa +; Bit layout: [S(1) E(18) M(45)] = [63:63][62:45][44:0] +; φ² + 1/φ² = 3 | TRINITY + +module triformat-gf64; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const SIGN_SHIFT : u8 = 63; +pub const EXP_SHIFT : u8 = 45; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u64 = 0x8000000000000000; // 1 << 63 +pub const EXP_MASK : u64 = 0x7FFF800000000000; // 0b111111111111111111 << 45 +pub const MANT_MASK : u64 = 0x00007FFFFFFFFFFF; // 0b111111111111111111111111111111111111111111111 + +pub const EXP_MAX : u16 = 0x3FFFF; // 262143 (all ones in 18 bits) +pub const EXP_MIN : u16 = 0x0000; + +pub const BIAS : i16 = 131071; // Exponent bias for GF64: 2^(18-1) - 1 +pub const SPECIAL_EXP : u16 = 0x3FFFF; // All ones = special (Inf/NaN) + +pub const MANT_DIVISOR : u64 = 35184372088832; // 2^45 +pub const MANT_DIVISOR_SHIFT : u8 = 45; + +pub const PHI_BIAS : u32 = 245760; // Phi-optimized rounding bias + +// GF64 special values +pub const GF64_ZERO_POS : u64 = 0x0000000000000000; +pub const GF64_ZERO_NEG : u64 = 0x8000000000000000; +pub const GF64_INF_POS : u64 = 0x7FFF800000000000; +pub const GF64_INF_NEG : u64 = 0xFFFF800000000000; +pub const GF64_NAN : u64 = 0xFFFF800000000001; // Sign + all exp + mantissa != 0 + +// ============================================================================ +// Types +// ============================================================================ + +pub const GF64 = u64; + +// ============================================================================ +// Functions +// ============================================================================ + +// gf64_extract_sign(gf64: GF64) -> i8 +// Extract sign bit (bit 63) +// Returns: 0 for positive, -1 for negative +pub fn gf64_extract_sign(gf64: GF64) i8 { + const bit = (gf64 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// gf64_extract_exponent(gf64: GF64) -> i16 +// Extract exponent bits (bits 62-45) +// Returns: 0-262143 +pub fn gf64_extract_exponent(gf64: GF64) i16 { + return @as(i16, @intCast((gf64 >> EXP_SHIFT) & (EXP_MAX))); +} + +// gf64_extract_mantissa(gf64: GF64) -> u64 +// Extract mantissa bits (bits 44-0) +// Returns: 0-2^45-1 +pub fn gf64_extract_mantissa(gf64: GF64) u64 { + return gf64 & MANT_MASK; +} + +// gf64_from_components(sign: i8, exp: i16, mant: u64) -> GF64 +// Assemble GF64 from sign, exponent, mantissa +pub fn gf64_from_components(sign: i8, exp: i16, mant: u64) GF64 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(GF64, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(GF64, @intCast(exp & EXP_MAX)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// gf64_is_zero(gf64: GF64) -> bool +// Check if GF64 is zero (positive or negative) +pub fn gf64_is_zero(gf64: GF64) bool { + return gf64 == GF64_ZERO_POS or gf64 == GF64_ZERO_NEG; +} + +// gf64_is_special(gf64: GF64) -> bool +// Check if GF64 is Inf or NaN (exp == 262143) +pub fn gf64_is_special(gf64: GF64) bool { + return gf64_extract_exponent(gf64) == @as(i16, @intCast(EXP_MAX)); +} + +// gf64_is_inf(gf64: GF64) -> bool +// Check if GF64 is infinity (exp == max, mant == 0) +pub fn gf64_is_inf(gf64: GF64) bool { + const exp = gf64_extract_exponent(gf64); + const mant = gf64_extract_mantissa(gf64); + return exp == @as(i16, @intCast(EXP_MAX)) and mant == 0; +} + +// gf64_is_nan(gf64: GF64) -> bool +// Check if GF64 is NaN (exp == max, mant != 0) +pub fn gf64_is_nan(gf64: GF64) bool { + const exp = gf64_extract_exponent(gf64); + const mant = gf64_extract_mantissa(gf64); + return exp == @as(i16, @intCast(EXP_MAX)) and mant != 0; +} + +// gf64_encode_f64(f64: f64) -> GF64 +// Encode IEEE 754 double precision to GF64 +// Round-to-nearest, ties to even +pub fn gf64_encode_f64(value: f64) GF64 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) GF64_ZERO_NEG else GF64_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return GF64_NAN; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) GF64_INF_NEG else GF64_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f64 components + const f64_bits: u64 = @bitCast(abs_value); + var f64_exp: i16 = @intCast((f64_bits >> 52) & 0x7FF) - 1023; + var f64_mant: u64 = f64_bits & 0x000FFFFFFFFFFFFF; + + // Convert exp from f64 bias (1023) to GF64 bias (131071) + var gf64_exp = f64_exp + BIAS; + + // Clamp exponent + if (gf64_exp < 0) { + // Underflow - could return subnormal, but flushing to zero for simplicity + return if (sign < 0) GF64_ZERO_NEG else GF64_ZERO_POS; + } else if (gf64_exp > @as(i16, @intCast(EXP_MAX - 1))) { + return if (sign < 0) GF64_INF_NEG else GF64_INF_POS; + } + + // Extract mantissa and scale to 45 bits + // f64 mantissa is 52 bits, GF64 needs 45 bits + // Shift right by 7 bits (52 - 45 = 7) + var mant = f64_mant >> 7; + + // Round-to-nearest with ties to even + const discarded = f64_mant & 0x7F; + if ((discarded & 0x40) != 0) { + // Round up + if ((discarded & 0x3F) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (gf64_exp < @as(i16, @intCast(EXP_MAX - 1))) { + gf64_exp += 1; + } + } + } + } + + return gf64_from_components(sign, gf64_exp, mant); +} + +// gf64_decode_f64(gf64: GF64) -> f64 +// Decode GF64 to IEEE 754 double precision +pub fn gf64_decode_f64(gf64: GF64) f64 { + // Handle zero + if (gf64_is_zero(gf64)) { + return if (gf64_extract_sign(gf64) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (gf64_is_nan(gf64)) { + return std.math.nan(f64); + } + + // Handle Infinity + if (gf64_is_inf(gf64)) { + return if (gf64_extract_sign(gf64) < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + // Extract components + const sign = gf64_extract_sign(gf64); + const exp = gf64_extract_exponent(gf64); + const mant = gf64_extract_mantissa(gf64); + + // Decode value: mantissa * 2^(exp - bias) + const bias_adjusted = @as(i64, exp) - @as(i64, BIAS); + const mant_f64 = @as(f64, @floatFromInt(mant)) / @as(f64, @floatFromInt(MANT_DIVISOR)); + const value = mant_f64 * std.math.pow(f64, 2.0, @as(f64, @floatFromInt(bias_adjusted))); + + return if (sign < 0) -value else value; +} + +// gf64_add(a: GF64, b: GF64) -> GF64 +// Add two GF64 values +pub fn gf64_add(a: GF64, b: GF64) GF64 { + const fa = gf64_decode_f64(a); + const fb = gf64_decode_f64(b); + return gf64_encode_f64(fa + fb); +} + +// gf64_sub(a: GF64, b: GF64) -> GF64 +// Subtract two GF64 values +pub fn gf64_sub(a: GF64, b: GF64) GF64 { + const fa = gf64_decode_f64(a); + const fb = gf64_decode_f64(b); + return gf64_encode_f64(fa - fb); +} + +// gf64_mul(a: GF64, b: GF64) -> GF64 +// Multiply two GF64 values +pub fn gf64_mul(a: GF64, b: GF64) GF64 { + const fa = gf64_decode_f64(a); + const fb = gf64_decode_f64(b); + return gf64_encode_f64(fa * fb); +} + +// gf64_div(a: GF64, b: GF64) -> GF64 +// Divide two GF64 values +pub fn gf64_div(a: GF64, b: GF64) GF64 { + const fb = gf64_decode_f64(b); + if (fb == 0.0) { + const fa = gf64_decode_f64(a); + return if (fa < 0.0) GF64_INF_NEG else GF64_INF_POS; + } + const fa = gf64_decode_f64(a); + return gf64_encode_f64(fa / fb); +} + +// gf64_abs(gf64: GF64) -> GF64 +// Absolute value of GF64 +pub fn gf64_abs(gf64: GF64) GF64 { + return gf64 & ~SIGN_MASK; +} + +// gf64_neg(gf64: GF64) -> GF64 +// Negate GF64 +pub fn gf64_neg(gf64: GF64) GF64 { + return gf64 ^ SIGN_MASK; +} + +// gf64_is_equal(a: GF64, b: GF64) -> bool +// Check if two GF64 values are equal +pub fn gf64_is_equal(a: GF64, b: GF64) bool { + // Handle NaN: NaN != NaN + if (gf64_is_nan(a) or gf64_is_nan(b)) { + return false; + } + // Handle zero: +0 == -0 + if (gf64_is_zero(a) and gf64_is_zero(b)) { + return true; + } + return a == b; +} + +// gf64_is_greater(a: GF64, b: GF64) -> bool +// Check if a > b +pub fn gf64_is_greater(a: GF64, b: GF64) bool { + if (gf64_is_nan(a) or gf64_is_nan(b)) { + return false; + } + // Extract signs for comparison + const sign_a = gf64_extract_sign(a); + const sign_b = gf64_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; // negative < positive + } + // Same sign: compare as unsigned (positive case) or reversed (negative case) + if (sign_a < 0) { + return (gf64_neg(a) > gf64_neg(b)); + } + return a > b; +} + +// gf64_max(a: GF64, b: GF64) -> GF64 +// Return the larger of two GF64 values +pub fn gf64_max(a: GF64, b: GF64) GF64 { + return if (gf64_is_greater(a, b)) a else b; +} + +// gf64_min(a: GF64, b: GF64) -> GF64 +// Return the smaller of two GF64 values +pub fn gf64_min(a: GF64, b: GF64) GF64 { + return if (gf64_is_greater(a, b)) b else a; +} + +// gf64_clamp(value: GF64, min: GF64, max: GF64) -> GF64 +// Clamp value between min and max +pub fn gf64_clamp(value: GF64, min: GF64, max: GF64) GF64 { + return gf64_min(gf64_max(value, min), max); +} + +// gf64_lerp(a: GF64, b: GF64, t: GF64) -> GF64 +// Linear interpolation between a and b +pub fn gf64_lerp(a: GF64, b: GF64, t: GF64) GF64 { + const ft = gf64_decode_f64(t); + const result = gf64_decode_f64(a) * (1.0 - ft) + gf64_decode_f64(b) * ft; + return gf64_encode_f64(result); +} + +// gf64_from_u32(value: u32) -> GF64 +// Convert unsigned 32-bit integer to GF64 +pub fn gf64_from_u32(value: u32) GF64 { + return gf64_encode_f64(@as(f64, @floatFromInt(value))); +} + +// gf64_to_u32(gf64: GF64) -> ?u32 +// Convert GF64 to unsigned 32-bit integer +// Returns null if value is NaN, Infinity, or out of range +pub fn gf64_to_u32(gf64: GF64) ?u32 { + if (gf64_is_nan(gf64) or gf64_is_inf(gf64)) { + return null; + } + const value = gf64_decode_f64(gf64); + if (value < 0.0 or value > @as(f64, @floatFromInt(std.math.maxInt(u32)))) { + return null; + } + return @as(u32, @intFromFloat(value)); +} + +// gf64_from_i32(value: i32) -> GF64 +// Convert signed 32-bit integer to GF64 +pub fn gf64_from_i32(value: i32) GF64 { + return gf64_encode_f64(@as(f64, @floatFromInt(value))); +} + +// gf64_to_i32(gf64: GF64) -> ?i32 +// Convert GF64 to signed 32-bit integer +// Returns null if value is NaN, Infinity, or out of range +pub fn gf64_to_i32(gf64: GF64) ?i32 { + if (gf64_is_nan(gf64) or gf64_is_inf(gf64)) { + return null; + } + const value = gf64_decode_f64(gf64); + if (value < @as(f64, @floatFromInt(std.math.minInt(i32))) or + value > @as(f64, @floatFromInt(std.math.maxInt(i32)))) { + return null; + } + return @as(i32, @intFromFloat(value)); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf64_extract_sign_positive" { + given value = 0x0000000000001234 + try std.testing.expect(sign = gf64_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "gf64_extract_sign_negative" { + given value = 0x8000000000001234 + try std.testing.expect(sign = gf64_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "gf64_extract_exponent_middle" { + given value = 0x0000800000000000 // exp = 131072 + try std.testing.expect(exp = gf64_extract_exponent(value)); + try std.testing.expect(exp == 131072); +} + +test "gf64_extract_mantissa_max" { + given value = 0x00007FFFFFFFFFFF + try std.testing.expect(mant = gf64_extract_mantissa(value)); + try std.testing.expect(mant == 0x00007FFFFFFFFFFF); +} + +test "gf64_is_zero_positive" { + try std.testing.expect(gf64_is_zero(GF64_ZERO_POS) == true); +} + +test "gf64_is_zero_negative" { + try std.testing.expect(gf64_is_zero(GF64_ZERO_NEG) == true); +} + +test "gf64_is_zero_nonzero" { + try std.testing.expect(gf64_is_zero(0x0000000000000001) == false); +} + +test "gf64_is_special_inf" { + try std.testing.expect(gf64_is_special(GF64_INF_POS) == true); +} + +test "gf64_is_special_nan" { + try std.testing.expect(gf64_is_special(GF64_NAN) == true); +} + +test "gf64_is_special_normal" { + try std.testing.expect(gf64_is_special(0x0000000000001234) == false); +} + +test "gf64_is_inf_positive" { + try std.testing.expect(gf64_is_inf(GF64_INF_POS) == true); +} + +test "gf64_is_inf_negative" { + try std.testing.expect(gf64_is_inf(GF64_INF_NEG) == true); +} + +test "gf64_is_inf_not_nan" { + try std.testing.expect(gf64_is_inf(GF64_NAN) == false); +} + +test "gf64_is_nan" { + try std.testing.expect(gf64_is_nan(GF64_NAN) == true); +} + +test "gf64_is_nan_not_inf" { + try std.testing.expect(gf64_is_nan(GF64_INF_POS) == false); +} + +test "gf64_encode_f64_zero" { + given gf = gf64_encode_f64(0.0) + try std.testing.expect(gf == GF64_ZERO_POS); +} + +test "gf64_encode_f64_negative_zero" { + given gf = gf64_encode_f64(-0.0) + try std.testing.expect(gf == GF64_ZERO_NEG); +} + +test "gf64_encode_f64_one" { + given gf = gf64_encode_f64(1.0) + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - 1.0) < 0.0001); +} + +test "gf64_encode_f64_negative_one" { + given gf = gf64_encode_f64(-1.0) + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded + 1.0) < 0.0001); +} + +test "gf64_encode_f64_two_pow_ten" { + given gf = gf64_encode_f64(1024.0) + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - 1024.0) < 1.0); +} + +test "gf64_encode_f64_pi" { + given gf = gf64_encode_f64(std.math.pi) + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - std.math.pi) < 0.001); +} + +test "gf64_decode_f64_roundtrip_positive" { + given original = 42.5 + try std.testing.expect(gf = gf64_encode_f64(original)); + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - original) < 0.001); +} + +test "gf64_decode_f64_roundtrip_negative" { + given original = -17.75 + try std.testing.expect(gf = gf64_encode_f64(original)); + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - original) < 0.001); +} + +test "gf64_add_simple" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(2.0)); + try std.testing.expect(result = gf64_add(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.01); +} + +test "gf64_sub_simple" { + given a = gf64_encode_f64(5.0) + try std.testing.expect(b = gf64_encode_f64(3.0)); + try std.testing.expect(result = gf64_sub(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.01); +} + +test "gf64_mul_simple" { + given a = gf64_encode_f64(3.0) + try std.testing.expect(b = gf64_encode_f64(4.0)); + try std.testing.expect(result = gf64_mul(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 12.0) < 0.01); +} + +test "gf64_div_simple" { + given a = gf64_encode_f64(12.0) + try std.testing.expect(b = gf64_encode_f64(4.0)); + try std.testing.expect(result = gf64_div(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.01); +} + +test "gf64_abs_positive" { + given value = gf64_encode_f64(5.0) + try std.testing.expect(abs_val = gf64_abs(value)); + try std.testing.expect(abs_val == value); +} + +test "gf64_abs_negative" { + given value = gf64_encode_f64(-5.0) + try std.testing.expect(abs_val = gf64_abs(value)); + try std.testing.expect(decoded = gf64_decode_f64(abs_val)); + try std.testing.expect(decoded > 0.0); +} + +test "gf64_neg" { + given value = gf64_encode_f64(5.0) + try std.testing.expect(neg_val = gf64_neg(value)); + try std.testing.expect(decoded = gf64_decode_f64(neg_val)); + try std.testing.expect(abs(decoded + 5.0) < 0.01); +} + +test "gf64_is_equal_same" { + given value = gf64_encode_f64(1.0) + try std.testing.expect(gf64_is_equal(value, value) == true); +} + +test "gf64_is_equal_different" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(2.0)); + try std.testing.expect(gf64_is_equal(a, b) == false); +} + +test "gf64_is_equal_nan" { + try std.testing.expect(gf64_is_equal(GF64_NAN, GF64_NAN) == false); +} + +test "gf64_is_equal_zero" { + try std.testing.expect(gf64_is_equal(GF64_ZERO_POS, GF64_ZERO_NEG) == true); +} + +test "gf64_is_greater_positive" { + given a = gf64_encode_f64(5.0) + try std.testing.expect(b = gf64_encode_f64(3.0)); + try std.testing.expect(gf64_is_greater(a, b) == true); +} + +test "gf64_is_greater_negative" { + given a = gf64_encode_f64(-3.0) + try std.testing.expect(b = gf64_encode_f64(-5.0)); + try std.testing.expect(gf64_is_greater(a, b) == true); +} + +test "gf64_is_greater_positive_vs_negative" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(-1.0)); + try std.testing.expect(gf64_is_greater(a, b) == true); +} + +test "gf64_max_returns_larger" { + given a = gf64_encode_f64(3.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(result = gf64_max(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(decoded >= 4.0); +} + +test "gf64_min_returns_smaller" { + given a = gf64_encode_f64(3.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(result = gf64_min(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(decoded <= 4.0); +} + +test "gf64_clamp_within_range" { + given value = gf64_encode_f64(3.0) + try std.testing.expect(min = gf64_encode_f64(0.0)); + try std.testing.expect(max = gf64_encode_f64(10.0)); + try std.testing.expect(result = gf64_clamp(value, min, max)); + try std.testing.expect(result == value); +} + +test "gf64_clamp_below_min" { + given value = gf64_encode_f64(-5.0) + try std.testing.expect(min = gf64_encode_f64(0.0)); + try std.testing.expect(max = gf64_encode_f64(10.0)); + try std.testing.expect(result = gf64_clamp(value, min, max)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(decoded >= 0.0); +} + +test "gf64_clamp_above_max" { + given value = gf64_encode_f64(15.0) + try std.testing.expect(min = gf64_encode_f64(0.0)); + try std.testing.expect(max = gf64_encode_f64(10.0)); + try std.testing.expect(result = gf64_clamp(value, min, max)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(decoded <= 10.0); +} + +test "gf64_lerp_zero" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(t = gf64_encode_f64(0.0)); + try std.testing.expect(result = gf64_lerp(a, b, t)); + try std.testing.expect(gf64_is_equal(result, a) == true); +} + +test "gf64_lerp_one" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(t = gf64_encode_f64(1.0)); + try std.testing.expect(result = gf64_lerp(a, b, t)); + try std.testing.expect(gf64_is_equal(result, b) == true); +} + +test "gf64_lerp_half" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(t = gf64_encode_f64(0.5)); + try std.testing.expect(result = gf64_lerp(a, b, t)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.1); +} + +test "gf64_from_u32_zero" { + given value = 0 + try std.testing.expect(gf = gf64_from_u32(value)); + try std.testing.expect(gf == GF64_ZERO_POS); +} + +test "gf64_from_u32_max" { + given value = std.math.maxInt(u32) + try std.testing.expect(gf = gf64_from_u32(value)); + try std.testing.expect(decoded = gf64_to_u32(gf)); + try std.testing.expect(decoded.? == value); +} + +test "gf64_to_u32_success" { + given gf = gf64_encode_f64(123.0) + try std.testing.expect(result = gf64_to_u32(gf)); + try std.testing.expect(result.? == 123); +} + +test "gf64_to_u32_negative" { + given gf = gf64_encode_f64(-1.0) + try std.testing.expect(result = gf64_to_u32(gf)); + try std.testing.expect(result == null); +} + +test "gf64_to_u32_nan" { + given result = gf64_to_u32(GF64_NAN) + try std.testing.expect(result == null); +} + +test "gf64_from_i32_zero" { + given value = 0 + try std.testing.expect(gf = gf64_from_i32(value)); + try std.testing.expect(gf == GF64_ZERO_POS); +} + +test "gf64_from_i32_negative" { + given value = -42 + try std.testing.expect(gf = gf64_from_i32(value)); + try std.testing.expect(decoded = gf64_to_i32(gf)); + try std.testing.expect(decoded.? == value); +} + +test "gf64_to_i32_positive" { + given gf = gf64_encode_f64(123.0) + try std.testing.expect(result = gf64_to_i32(gf)); + try std.testing.expect(result.? == 123); +} + +test "gf64_to_i32_negative" { + given gf = gf64_encode_f64(-123.0) + try std.testing.expect(result = gf64_to_i32(gf)); + try std.testing.expect(result.? == -123); +} + +test "gf64_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 131072); + try std.testing.expect(mant = 0x0000000000000800); + try std.testing.expect(gf = gf64_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = gf64_extract_sign(gf)); + try std.testing.expect(extracted_exp = gf64_extract_exponent(gf)); + try std.testing.expect(extracted_mant = gf64_extract_mantissa(gf)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf64_sign_mask_single_bit + assert (SIGN_MASK & (SIGN_MASK - 1)) == 0 + +invariant gf64_exp_mask_no_sign + assert (EXP_MASK & SIGN_MASK) == 0 + +invariant gf64_mant_mask_no_sign_exp + assert (MANT_MASK & (SIGN_MASK | EXP_MASK)) == 0 + +invariant gf64_masks_cover_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFFFFFFFFFFFFFFFF + +invariant gf64_bias_power_of_two_minus_one + assert BIAS + 1 == 131072 // 2^17 + +invariant gf64_special_exp_all_ones + assert SPECIAL_EXP == EXP_MAX + +invariant gf64_zero_pos_no_sign + assert (GF64_ZERO_POS & SIGN_MASK) == 0 + +invariant gf64_zero_neg_has_sign + assert (GF64_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant gf64_inf_pos_max_exp + assert (GF64_INF_POS & EXP_MASK) == EXP_MASK + +invariant gf64_nan_has_mantissa + assert (GF64_NAN & MANT_MASK) != 0 + +invariant gf64_abs_removes_sign + assert gf64_abs(GF64_ZERO_NEG) == GF64_ZERO_POS + +invariant gf64_neg_toggles_sign + assert (gf64_neg(GF64_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant gf64_from_u32_zero + assert gf64_from_u32(0) == GF64_ZERO_POS + +invariant gf64_from_i32_zero + assert gf64_from_i32(0) == GF64_ZERO_POS + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf64_extract_sign_latency + measure: nanoseconds to gf64_extract_sign(0x123456789ABCDEF0) + target: < 10ns + +bench gf64_extract_exponent_latency + measure: nanoseconds to gf64_extract_exponent(0x123456789ABCDEF0) + target: < 10ns + +bench gf64_encode_f64_latency + measure: nanoseconds to gf64_encode_f64(123.456) + target: < 100ns + +bench gf64_decode_f64_latency + measure: nanoseconds to gf64_decode_f64(0x3F80000000000000) + target: < 100ns + +bench gf64_add_latency + measure: nanoseconds to gf64_add(0x3F80000000000000, 0x4000000000000000) + target: < 200ns + +bench gf64_mul_latency + measure: nanoseconds to gf64_mul(0x3F80000000000000, 0x4000000000000000) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/gf8.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf8.t27 new file mode 100644 index 0000000000..d4f9f72ae6 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/gf8.t27 @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf8.t27 +// GoldenFloat8 0 8-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 3 (P1) + +module GF8 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF8 bit layout: [S|EEE|MMMM] + // S: 1 bit (sign) + // E: 3 bits (exponent) + // M: 4 bits (mantissa) + + const BITS : u8 = 8; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 3; + const MANT_BITS : u8 = 4; + + // Bias for exponent (2^(3-1) - 1 = 3) + const EXP_BIAS : u8 = 3; + + // 141-ratio: exp/mant = 3/4 = 0.75 + // 142-distance: math::constants::PHI_DISTANCE + const PHI_DISTANCE : f64 = 0.132; + + // 143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 + // 2. GoldenFloat8 Type + // 208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 + + struct GF8 { + raw : u8, // 8-bit raw value + } + + // 281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 + // 3. Encoding/Decoding + // 346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 + + // Encode f32 to GF8 + fn encode(value: f32) -> GF8 { + if (value == 0.0) { + return GF8{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i8; + const exp_biased = (exp_unbiased + EXP_BIAS as i8) as u8; + + // Clamp exponent + const exp_clamped = clamp(exp_biased, 0, (1 << EXP_BITS) - 1); + + // Extract mantissa (4 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF8{ + raw = (sign << 7) | (exp_clamped << MANT_BITS) | mant + }; + } + + // Test: gf8_phi_distance invariant + test gf8_phi_distance + given phi = 1.618033988749894848 // math::sacred_physics::PHI + when gf8_phi = GF8::encode(phi) + then gf8_phi.raw == 0b01000000 // |S|EEE (phi = math::constants::PHI_DISTANCE) + + // Decode GF8 to f32 + fn decode(gf: GF8) -> f32 { + const sign = (gf.raw >> 7) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x07) as u8; + const mant = (gf.raw & 0x0F) as u8; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent (with special case for subnormals) + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS as i8 + 1 + } else { + (exp_biased as i8) - EXP_BIAS as i8 + }; + + // Mantissa (with implicit 1 for normalized, 0 for subnormal) + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 16.0 + } else { + 1.0 + (mant as f32) / 16.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 + // 4. Format Properties + // 484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 + + fn max_value() -> f32 { + // Max normalized: mant=1.9375, exp=3 557 15.5 + const mant_max = 1.0 + 15.0 / 16.0; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + // Min subnormal: mant=1/16, exp=-2 558 0.0625 + const mant_min = 1.0 / 16.0; + const exp_min = -EXP_BIAS as i8 + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + // Smallest representable difference at 1.0 + return 1.0 / 16.0; // 0.0625 + } + + // 559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623 + // 5. Validation + // 624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF8"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761 + // 6. Use Cases + // 762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834 + + // GF8 is optimal for: + // - High compression (75% smaller than FP32) + // - Weight quantization for lightweight models + // - Activation caching + // - Intermediate feature maps + + // Memory: 8 bits = 1 byte (4x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 8.0 / 32.0; // 0.25 + + // 835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899 + // 7. Helper Functions + // 900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972 + + fn floor_log2(x: f32) -> i8 { + if (x <= 0.0) { return -128; } + let exp : i8 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i8, mant_bits: u8) -> u8 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u8; + } + + fn clamp(x: u8, min: u8, max: u8) -> u8 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF8 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs to maintain accuracy + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 9739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075 + // TDD-Inside-Spec: Tests and Invariants for GF8 + // 1076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178 + + test gf8_decode_zero + given gf = GF8{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf8_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf8_decode_positive_value + given gf = GF8{ raw = 0b01000000 } + when value = decode(gf) + then value > 0.0 + + test gf8_decode_negative_value + given gf = GF8{ raw = 0b10000000 } + when value = decode(gf) + then value < 0.0 + + test gf8_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf8_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf8_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf8_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf8_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then ratio == 0.25 + + test gf8_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf8_bits_constant + assert BITS == 8 + + invariant gf8_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf8_exp_bits_is_three + assert EXP_BITS == 3 + + invariant gf8_mant_bits_is_four + assert MANT_BITS == 4 + + invariant gf8_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf8_phi_distance_within_tolerance + assert PHI_DISTANCE < 0.14 + + invariant gf8_exp_bias_positive + assert EXP_BIAS > 0 + + test gf8_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf8_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf8_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf8_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf8_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf8_pow_phi_squared + given phi = 1.6180339887498948 as f32 + and result = pow(phi, 2.0) + and expected = phi * phi + then abs(result - expected) < 1e-5 + + test gf8_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf8_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf8_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf8_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf8_ln_approx_of_e_squared + given e = 2.718281828459045 as f32 + and result = ln_approx(e * e) + then abs(result - 2.0) < 0.02 + + test gf8_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf8_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf8_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf8_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf8_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf8_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf8_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf8_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf8_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf8_pow_multiply_exponents + given a = 2.0 + and b = 3.0 + assert abs(pow(pow(a, 2.0), b) - pow(a, 2.0 * b)) < 1e-5 + + invariant gf8_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf8_exp_ln_inversion + given x = 1.5 + and y = exp_approx(x) + then abs(ln_approx(y) - x) < 0.01 + + invariant gf8_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf8_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf8_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf8_pow_fractional_exponent + measure: nanoseconds to compute pow(4.0, 0.5) + target: < 1000ns + + bench gf8_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf8_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf8_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf8_encode_latency + measure: nanoseconds to encode(1.0) + target: < 100ns + + bench gf8_decode_latency + measure: nanoseconds to decode(GF8{raw = 64}) + target: < 50ns + + // Bench: GF8 weight quantization (NN weights from N(0, 0.1)) + bench gf8_weight_quantize + measure: nanoseconds to encode(0.1) and decode(GF8{raw = encode(0.1).raw}) + target: < 200ns + + // Invariant: GF8 phi_distance + invariant gf8_phi_distance + assert PHI_DISTANCE == 0.132 within 0.001 + // Rationale: exp/mant = 3/4 = 0.75, phi_distance = |0.75 - 0.618| = 0.132 +} diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/goldenfloat_family.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/goldenfloat_family.t27 new file mode 100644 index 0000000000..78e552c883 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/goldenfloat_family.t27 @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/goldenfloat_family.t27 +// GoldenFloat Family 0 1-structured floating point formats +// NUMERIC-STANDARD-001 2 Agent 1 (P0) + +module GoldenFloatFamily { + // Import sacred constants for 3-structured design + use math::constants; + use math::sacred_physics; + + // 4567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768 + // 1. GoldenFloatFormat 69 Canonical format descriptor + // 707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 + + struct GoldenFloatFormat { + name : string, // "GF4", "GF8", ..., "GF256" + bits : u8, // Total bits: 4, 8, 12, 16, 20, 24, 32, 64, 128, 256 + sign_bits : u8, // Always 1 + exp_bits : u8, // Exponent bits + mant_bits : u8, // Mantissa bits + exp_mant_ratio : f64, // exp / mantissa ratio + phi_distance : f64, // |exp/mant - 1/143| (lower = better) + is_primary : bool, // true only for GF16 + } + + // 144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 + // 2. GOLDEN_FLOAT_FAMILY 209 The canonical format registry + // 210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 + + // 283-ratio target: 1/284 285 0.618 + // exp/mant ratios closer to 0.618 are more "golden" + const PHI_RATIO_TARGET : f64 = sacred_physics::PHI_INV; + + // Format array: ordered by bits (4 → 256) + const GOLDEN_FLOAT_FAMILY : [10]GoldenFloatFormat = [ + // name, bits, S, E, M, ratio, phi_dist, primary + GoldenFloatFormat{ + name = "GF4", + bits = 4, + sign_bits = 1, + exp_bits = 1, + mant_bits = 2, + exp_mant_ratio = 0.5, + phi_distance = abs(0.5 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF8", + bits = 8, + sign_bits = 1, + exp_bits = 3, + mant_bits = 4, + exp_mant_ratio = 0.75, + phi_distance = abs(0.75 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF12", + bits = 12, + sign_bits = 1, + exp_bits = 4, + mant_bits = 7, + exp_mant_ratio = 0.5714285714285714, + phi_distance = abs(0.5714285714285714 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF16", + bits = 16, + sign_bits = 1, + exp_bits = 6, + mant_bits = 9, + exp_mant_ratio = 0.6666666666666667, + phi_distance = abs(0.6666666666666667 - PHI_RATIO_TARGET), + is_primary = true, // PRIMARY FORMAT + }, + GoldenFloatFormat{ + name = "GF20", + bits = 20, + sign_bits = 1, + exp_bits = 7, + mant_bits = 12, + exp_mant_ratio = 0.5833333333333333, + phi_distance = abs(0.5833333333333333 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF24", + bits = 24, + sign_bits = 1, + exp_bits = 9, + mant_bits = 14, + exp_mant_ratio = 0.6428571428571429, + phi_distance = abs(0.6428571428571429 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF32", + bits = 32, + sign_bits = 1, + exp_bits = 12, + mant_bits = 19, + exp_mant_ratio = 0.631578947368421, + phi_distance = abs(0.631578947368421 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF64", + bits = 64, + sign_bits = 1, + exp_bits = 24, + mant_bits = 39, + exp_mant_ratio = 0.6153846153846154, + phi_distance = abs(0.6153846153846154 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF128", + bits = 128, + sign_bits = 1, + exp_bits = 48, + mant_bits = 79, + exp_mant_ratio = 0.6075949367088608, + phi_distance = abs(0.6075949367088608 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF256", + bits = 256, + sign_bits = 1, + exp_bits = 97, + mant_bits = 158, + exp_mant_ratio = 0.6139240506329114, + phi_distance = abs(0.6139240506329114 - PHI_RATIO_TARGET), + is_primary = false, + }, + ]; + + // 287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 + // 3. Query functions + // 352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 + + fn get_format_by_name(name: string) -> Option { + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + if (fmt.name == name) { + return fmt; + } + } + return null; + } + + fn get_format_by_bits(bits: u8) -> Option { + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + if (fmt.bits == bits) { + return fmt; + } + } + return null; + } + + fn get_primary_format() -> GoldenFloatFormat { + return GOLDEN_FLOAT_FAMILY[3]; // GF16 at index 3 + } + + // 425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 + // 4. Verification functions + // 490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562 + + struct VerificationReport { + all_valid : bool, + primary_is_gf16 : bool, + phi_distances_ok : bool, + best_phi_format : string, + best_phi_distance : f64, + avg_phi_distance : f64, + } + + fn verify_golden_family() -> VerificationReport { + var primary_count : u8 = 0; + var best_dist : f64 = 1.0; + var best_name : string = ""; + var total_dist : f64 = 0.0; + var format_count : u8 = 0; + var all_names_unique : bool = true; + var all_bit_sums_valid : bool = true; + var all_phi_distances_non_negative : bool = true; + + // Check for duplicate names + var names_seen : [10]string = ["", "", "", "", "", "", "", "", "", ""]; + + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + format_count = format_count + 1; + + // Count primary formats (should be exactly 1) + if (fmt.is_primary) { + primary_count = primary_count + 1; + } + + // Track best phi distance + if (fmt.phi_distance < best_dist) { + best_dist = fmt.phi_distance; + best_name = fmt.name; + } + + total_dist = total_dist + fmt.phi_distance; + + // Check for duplicate names + for (const names_seen) |name| { + if (name != "" && name == fmt.name) { + all_names_unique = false; + } + } + names_seen[format_count - 1] = fmt.name; + + // Check that exp_bits + mant_bits + 1 = bits (sign bit) + if (fmt.exp_bits + fmt.mant_bits + 1 != fmt.bits) { + all_bit_sums_valid = false; + } + + // Check phi_distance is non-negative + if (fmt.phi_distance < 0.0) { + all_phi_distances_non_negative = false; + } + } + + const avg_dist = total_dist / 10.0; + + // All checks must pass + const all_checks_valid = + format_count == 10 && + all_names_unique && + all_bit_sums_valid && + all_phi_distances_non_negative && + primary_count == 1; + + return VerificationReport{ + all_valid = all_checks_valid, + primary_is_gf16 = (primary_count == 1) && (GOLDEN_FLOAT_FAMILY[3].is_primary), + phi_distances_ok = best_dist < 0.1, // All within 0.1 of 1/563 + best_phi_format = best_name, + best_phi_distance = best_dist, + avg_phi_distance = avg_dist, + }; + } + + // 564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628 + // 5. Utility functions + // 629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701 + + fn max_value(format: GoldenFloatFormat) -> f64 { + // Max value = (2 - 2^(-M)) * 2^(2^E - 1) + const mant_max = 2.0 - pow(2.0, -(format.mant_bits as f64)); + const exp_max = pow(2.0, format.exp_bits as f64) - 1.0; + return mant_max * pow(2.0, exp_max); + } + + fn min_positive(format: GoldenFloatFormat) -> f64 { + // Min positive = 2^(-M) * 2^(1 - bias) + const mant_min = pow(2.0, -(format.mant_bits as f64)); + const bias = pow(2.0, format.exp_bits as f64 - 1.0) - 1.0; + return mant_min * pow(2.0, 1.0 - bias); + } + + fn memory_efficiency(format: GoldenFloatFormat) -> f64 { + // Memory efficiency vs FP32 (1.0 = same, 0.5 = half size) + return format.bits as f64 / 32.0; + } + + // 702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804 + // TDD-Inside-Spec: Tests and Invariants for GoldenFloatFamily + // 805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907 + + test gffamily_get_format_by_name_gf16 + given fmt = get_format_by_name("GF16") + then fmt != null and fmt.?.name == "GF16" and fmt.?.bits == 16 + + test gffamily_get_format_by_bits_8 + given fmt = get_format_by_bits(8) + then fmt != null and fmt.?.name == "GF8" and fmt.?.bits == 8 + + test gffamily_get_primary_format_is_gf16 + given primary = get_primary_format() + then primary.name == "GF16" and primary.is_primary == true + + test gffamily_family_size_10 + given size = GOLDEN_FLOAT_FAMILY.len() + then size == 10 + + test gffamily_phi_ratio_target_is_phi_inverse + given target = PHI_RATIO_TARGET + and phi_inv = sacred_physics::PHI_INV + then abs(target - phi_inv) < 0.000001 + + test gffamily_gf4_has_correct_bit_counts + given fmt = get_format_by_name("GF4").? + then fmt.sign_bits == 1 and fmt.exp_bits == 1 and fmt.mant_bits == 2 + + test gffamily_gf32_has_correct_bit_counts + given fmt = get_format_by_name("GF32").? + then fmt.sign_bits == 1 and fmt.exp_bits == 12 and fmt.mant_bits == 19 + + test gffamily_only_gf16_is_primary + var count = 0 + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + if (fmt.is_primary) { count = count + 1; } + } + then count == 1 + + test gffamily_verify_primary_is_gf16 + given report = verify_golden_family() + then report.primary_is_gf16 == true + + test gffamily_phi_distances_within_tolerance + given report = verify_golden_family() + then report.phi_distances_ok == true + + test gffamily_best_phi_format_is_gf12 + given report = verify_golden_family() + then report.best_phi_format == "GF12" + + test gffamily_memory_efficiency_gf8 + given fmt = get_format_by_name("GF8").? + and eff = memory_efficiency(fmt) + then abs(eff - 0.25) < 0.01 + + test gffamily_memory_efficiency_gf16 + given fmt = get_format_by_name("GF16").? + and eff = memory_efficiency(fmt) + then abs(eff - 0.5) < 0.01 + + test gffamily_max_value_positive + given fmt = get_format_by_name("GF8").? + and max_val = max_value(fmt) + then max_val > 0.0 + + test gffamily_min_positive_greater_than_zero + given fmt = get_format_by_name("GF8").? + and min_pos = min_positive(fmt) + then min_pos > 0.0 + + test gffamily_get_format_by_unknown_name + given fmt = get_format_by_name("GF999") + then fmt == null + + test gffamily_get_format_by_unknown_bits + given fmt = get_format_by_bits(100) + then fmt == null + + test gffamily_verify_all_valid + given report = verify_golden_family() + then report.all_valid == true + + test gffamily_verify_format_count_is_10 + given report = verify_golden_family() + then report.all_valid == true // implies format_count == 10 + + test gffamily_verify_names_unique + given report = verify_golden_family() + then report.all_valid == true // implies names are unique + + test gffamily_verify_bit_sums_valid + given report = verify_golden_family() + then report.all_valid == true // implies bit sums are valid + + test gffamily_verify_phi_distances_non_negative + given report = verify_golden_family() + then report.all_valid == true // implies phi_distances are non-negative + + test gffamily_verify_exactly_one_primary + given report = verify_golden_family() + then report.all_valid == true // implies exactly 1 primary format + + test gffamily_best_phi_distance_is_small + given report = verify_golden_family() + then report.best_phi_distance < 0.05 + + test gffamily_avg_phi_distance_reasonable + given report = verify_golden_family() + and avg = report.avg_phi_distance + then avg > 0.0 and avg < 0.2 + + invariant gffamily_phi_ratio_target_positive + assert PHI_RATIO_TARGET > 0.0 + + invariant gffamily_phi_ratio_target_less_than_one + assert PHI_RATIO_TARGET < 1.0 + + invariant gffamily_family_size_constant + assert GOLDEN_FLOAT_FAMILY.len() == 10 + + invariant gffamily_gf4_at_index_0 + assert GOLDEN_FLOAT_FAMILY[0].name == "GF4" + + invariant gffamily_gf256_at_index_9 + assert GOLDEN_FLOAT_FAMILY[9].name == "GF256" + + invariant gffamily_gf128_at_index_8 + assert GOLDEN_FLOAT_FAMILY[8].name == "GF128" + + invariant gffamily_gf64_at_index_7 + assert GOLDEN_FLOAT_FAMILY[7].name == "GF64" + + invariant gffamily_gf32_at_index_6 + assert GOLDEN_FLOAT_FAMILY[6].name == "GF32" + + invariant gffamily_all_formats_have_sign_bits_1 + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + assert fmt.sign_bits == 1; + } + + invariant gffamily_all_formats_bits_sum_correct + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + assert fmt.sign_bits + fmt.exp_bits + fmt.mant_bits == fmt.bits; + } + + invariant gffamily_primary_is_gf16 + assert GOLDEN_FLOAT_FAMILY[3].is_primary == true + + invariant gffamily_phi_distances_non_negative + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + assert fmt.phi_distance >= 0.0; + } + + invariant gffamily_memory_efficiency_gf4 + assert abs(memory_efficiency(GOLDEN_FLOAT_FAMILY[0]) - 0.125) < 0.01 + + invariant gffamily_memory_efficiency_gf32 + assert abs(memory_efficiency(GOLDEN_FLOAT_FAMILY[6]) - 1.0) < 0.01 + + bench gffamily_get_format_by_name_latency + measure: nanoseconds to get_format_by_name("GF16") + target: < 100ns + + bench gffamily_get_format_by_bits_latency + measure: nanoseconds to get_format_by_bits(16) + target: < 100ns + + bench gffamily_get_primary_format_latency + measure: nanoseconds to get_primary_format() + target: < 50ns + + bench gffamily_verify_golden_family_latency + measure: nanoseconds to verify_golden_family() + target: < 500ns + + bench gffamily_memory_efficiency_latency + measure: nanoseconds to memory_efficiency(GOLDEN_FLOAT_FAMILY[3]) + target: < 100ns +} diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/int4.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/int4.t27 new file mode 100644 index 0000000000..7a49385b58 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/int4.t27 @@ -0,0 +1,766 @@ +// SPDX-License-Identifier: Apache-2.0 +; int4.t27 — Int4 Signed 4-bit Integer Quantization +; Range: -8 to 7 +; Used for ultra-low precision quantization in ML +; φ² + 1/φ² = 3 | TRINITY + +module triformat-int4; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 4; +pub const MIN : i8 = -8; +pub const MAX : i8 = 7; +pub const RANGE : u8 = 16; // 2^4 + +pub const MASK : u8 = 0x0F; // Lower 4 bits mask + +// ============================================================================ +// Types +// ============================================================================ + +pub const Int4 = i8; // Stored as i8, but only lower 4 bits used + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// int4_from_i8(value: i8) -> Int4 +// Convert i8 to Int4 with saturation +// Values < -8 clamp to -8, values > 7 clamp to 7 +pub fn int4_from_i8(value: i8) Int4 { + if (value < MIN) { + return MIN; + } else if (value > MAX) { + return MAX; + } + return value; +} + +// int4_from_i16(value: i16) -> Int4 +// Convert i16 to Int4 with saturation +pub fn int4_from_i16(value: i16) Int4 { + if (value < @as(i16, MIN)) { + return MIN; + } else if (value > @as(i16, MAX)) { + return MAX; + } + return @as(i8, @intCast(value)); +} + +// int4_from_i32(value: i32) -> Int4 +// Convert i32 to Int4 with saturation +pub fn int4_from_i32(value: i32) Int4 { + if (value < @as(i32, MIN)) { + return MIN; + } else if (value > @as(i32, MAX)) { + return MAX; + } + return @as(i8, @intCast(value)); +} + +// int4_from_f32(value: f32) -> Int4 +// Convert f32 to Int4 with rounding and saturation +// Round to nearest, ties to even +pub fn int4_from_f32(value: f32) Int4 { + if (std.math.isNan(value)) { + return 0; + } + if (value > @as(f32, @floatFromInt(MAX)) + 0.5) { + return MAX; + } + if (value < @as(f32, @floatFromInt(MIN)) - 0.5) { + return MIN; + } + // Round to nearest + const rounded = @as(i32, @intFromFloat(@round(value))); + return int4_from_i32(rounded); +} + +// int4_to_i8(value: Int4) -> i8 +// Convert Int4 to i8 (no-op, just returns value) +pub fn int4_to_i8(value: Int4) i8 { + return value; +} + +// int4_to_i16(value: Int4) -> i16 +// Convert Int4 to i16 (sign extension) +pub fn int4_to_i16(value: Int4) i16 { + // Sign extend from 4 bits to 16 bits + return @as(i16, value); +} + +// int4_to_i32(value: Int4) -> i32 +// Convert Int4 to i32 (sign extension) +pub fn int4_to_i32(value: Int4) i32 { + return @as(i32, value); +} + +// int4_to_f32(value: Int4) -> f32 +// Convert Int4 to f32 +pub fn int4_to_f32(value: Int4) f32 { + return @as(f32, @floatFromInt(value)); +} + +// int4_to_f64(value: Int4) -> f64 +// Convert Int4 to f64 +pub fn int4_to_f64(value: Int4) f64 { + return @as(f64, @floatFromInt(value)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// int4_add(a: Int4, b: Int4) -> Int4 +// Add two Int4 values with saturation +pub fn int4_add(a: Int4, b: Int4) Int4 { + const result = a + b; + if (result > MAX) { + return MAX; + } else if (result < MIN) { + return MIN; + } + return result; +} + +// int4_sub(a: Int4, b: Int4) -> Int4 +// Subtract two Int4 values with saturation +pub fn int4_sub(a: Int4, b: Int4) Int4 { + const result = a - b; + if (result > MAX) { + return MAX; + } else if (result < MIN) { + return MIN; + } + return result; +} + +// int4_mul(a: Int4, b: Int4) -> Int4 +// Multiply two Int4 values with saturation +pub fn int4_mul(a: Int4, b: Int4) Int4 { + const result = a * b; + if (result > MAX) { + return MAX; + } else if (result < MIN) { + return MIN; + } + return result; +} + +// int4_div(a: Int4, b: Int4) -> Int4 +// Divide two Int4 values with saturation +// Division by zero returns MAX or MIN based on sign of numerator +pub fn int4_div(a: Int4, b: Int4) Int4 { + if (b == 0) { + return if (a >= 0) MAX else MIN; + } + const result = a / b; + if (result > MAX) { + return MAX; + } else if (result < MIN) { + return MIN; + } + return result; +} + +// int4_abs(value: Int4) -> Int4 +// Absolute value of Int4 with saturation +pub fn int4_abs(value: Int4) Int4 { + if (value == MIN) { + return MAX; // -8 -> 7 (saturated) + } + return if (value < 0) -value else value; +} + +// int4_neg(value: Int4) -> Int4 +// Negate Int4 (with saturation for -MIN) +pub fn int4_neg(value: Int4) Int4 { + if (value == MIN) { + return MAX; // -(-8) = 7 (saturated) + } + return -value; +} + +// ============================================================================ +// Comparison Functions +// ============================================================================ + +// int4_is_equal(a: Int4, b: Int4) -> bool +pub fn int4_is_equal(a: Int4, b: Int4) bool { + return a == b; +} + +// int4_is_greater(a: Int4, b: Int4) -> bool +pub fn int4_is_greater(a: Int4, b: Int4) bool { + return a > b; +} + +// int4_is_less(a: Int4, b: Int4) -> bool +pub fn int4_is_less(a: Int4, b: Int4) bool { + return a < b; +} + +// int4_min(a: Int4, b: Int4) -> Int4 +pub fn int4_min(a: Int4, b: Int4) Int4 { + return if (a < b) a else b; +} + +// int4_max(a: Int4, b: Int4) -> Int4 +pub fn int4_max(a: Int4, b: Int4) Int4 { + return if (a > b) a else b; +} + +// int4_clamp(value: Int4, min: Int4, max: Int4) -> Int4 +pub fn int4_clamp(value: Int4, min: Int4, max: Int4) Int4 { + if (value < min) { + return min; + } else if (value > max) { + return max; + } + return value; +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// int4_clamp_to_range(value: Int4) -> Int4 +// Clamp value to Int4 range [-8, 7] +pub fn int4_clamp_to_range(value: Int4) Int4 { + return int4_clamp(value, MIN, MAX); +} + +// int4_is_in_range(value: i8) -> bool +// Check if value is in Int4 range +pub fn int4_is_in_range(value: i8) bool { + return value >= MIN and value <= MAX; +} + +// int4_lerp(a: Int4, b: Int4, t: f32) -> Int4 +// Linear interpolation between a and b +pub fn int4_lerp(a: Int4, b: Int4, t: f32) Int4 { + const fa = @as(f32, @floatFromInt(a)); + const fb = @as(f32, @floatFromInt(b)); + const result = fa + (fb - fa) * t; + return int4_from_f32(result); +} + +// int4_sq(value: Int4) -> Int4 +// Square value with saturation +pub fn int4_sq(value: Int4) Int4 { + return int4_mul(value, value); +} + +// int4_abs_diff(a: Int4, b: Int4) -> Int4 +// Absolute difference between two Int4 values +pub fn int4_abs_diff(a: Int4, b: Int4) Int4 { + const diff = a - b; + return int4_abs(diff); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "int4_from_i8_in_range" { + given value = 5 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == 5); +} + +test "int4_from_i8_above_max" { + given value = 10 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == MAX); +} + +test "int4_from_i8_below_min" { + given value = -10 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == MIN); +} + +test "int4_from_i8_at_max" { + given value = 7 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == MAX); +} + +test "int4_from_i8_at_min" { + given value = -8 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == MIN); +} + +test "int4_from_i16_positive" { + given value = 100 + try std.testing.expect(result = int4_from_i16(value)); + try std.testing.expect(result == MAX); +} + +test "int4_from_i16_negative" { + given value = -100 + try std.testing.expect(result = int4_from_i16(value)); + try std.testing.expect(result == MIN); +} + +test "int4_from_i16_in_range" { + given value = 3 + try std.testing.expect(result = int4_from_i16(value)); + try std.testing.expect(result == 3); +} + +test "int4_from_i32_positive" { + given value = 1000 + try std.testing.expect(result = int4_from_i32(value)); + try std.testing.expect(result == MAX); +} + +test "int4_from_i32_negative" { + given value = -1000 + try std.testing.expect(result = int4_from_i32(value)); + try std.testing.expect(result == MIN); +} + +test "int4_from_f32_zero" { + given value = 0.0 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == 0); +} + +test "int4_from_f32_one" { + given value = 1.0 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == 1); +} + +test "int4_from_f32_negative" { + given value = -1.5 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == -2); +} + +test "int4_from_f32_nan" { + given result = int4_from_f32(std.math.nan(f32)) + try std.testing.expect(result == 0); +} + +test "int4_from_f32_round_up" { + given value = 1.6 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == 2); +} + +test "int4_from_f32_round_down" { + given value = 1.4 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == 1); +} + +test "int4_to_i8_identity" { + given value: Int4 = 5 + try std.testing.expect(result = int4_to_i8(value)); + try std.testing.expect(result == value); +} + +test "int4_to_i16_sign_extend" { + given value: Int4 = -1 + try std.testing.expect(result = int4_to_i16(value)); + try std.testing.expect(result == -1); +} + +test "int4_to_i32_sign_extend" { + given value: Int4 = -1 + try std.testing.expect(result = int4_to_i32(value)); + try std.testing.expect(result == -1); +} + +test "int4_to_f32_positive" { + given value: Int4 = 5 + try std.testing.expect(result = int4_to_f32(value)); + try std.testing.expect(abs(result - 5.0) < 0.001); +} + +test "int4_to_f32_negative" { + given value: Int4 = -3 + try std.testing.expect(result = int4_to_f32(value)); + try std.testing.expect(abs(result + 3.0) < 0.001); +} + +test "int4_add_simple" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 4); + try std.testing.expect(result = int4_add(a, b)); + try std.testing.expect(result == MAX // 3 + 4 = 7 = MAX); +} + +test "int4_add_overflow" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_add(a, b)); + try std.testing.expect(result == MAX // 5 + 5 = 10 -> 7); +} + +test "int4_add_underflow" { + given a: Int4 = -5 + try std.testing.expect(b: Int4 = -5); + try std.testing.expect(result = int4_add(a, b)); + try std.testing.expect(result == MIN // -5 + -5 = -10 -> -8); +} + +test "int4_sub_simple" { + given a: Int4 = 7 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_sub(a, b)); + try std.testing.expect(result == 4); +} + +test "int4_sub_negative" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 7); + try std.testing.expect(result = int4_sub(a, b)); + try std.testing.expect(result == -4); +} + +test "int4_mul_positive" { + given a: Int4 = 2 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_mul(a, b)); + try std.testing.expect(result == 6); +} + +test "int4_mul_negative" { + given a: Int4 = -2 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_mul(a, b)); + try std.testing.expect(result == -6); +} + +test "int4_mul_overflow" { + given a: Int4 = 4 + try std.testing.expect(b: Int4 = 4); + try std.testing.expect(result = int4_mul(a, b)); + try std.testing.expect(result == MAX // 4 * 4 = 16 -> 7); +} + +test "int4_div_simple" { + given a: Int4 = 6 + try std.testing.expect(b: Int4 = 2); + try std.testing.expect(result = int4_div(a, b)); + try std.testing.expect(result == 3); +} + +test "int4_div_by_zero_positive" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 0); + try std.testing.expect(result = int4_div(a, b)); + try std.testing.expect(result == MAX); +} + +test "int4_div_by_zero_negative" { + given a: Int4 = -5 + try std.testing.expect(b: Int4 = 0); + try std.testing.expect(result = int4_div(a, b)); + try std.testing.expect(result == MIN); +} + +test "int4_abs_positive" { + given value: Int4 = 5 + try std.testing.expect(result = int4_abs(value)); + try std.testing.expect(result == 5); +} + +test "int4_abs_negative" { + given value: Int4 = -5 + try std.testing.expect(result = int4_abs(value)); + try std.testing.expect(result == 5); +} + +test "int4_abs_min" { + given value: Int4 = MIN + try std.testing.expect(result = int4_abs(value)); + try std.testing.expect(result == MAX // |-8| = 7 (saturated)); +} + +test "int4_neg_positive" { + given value: Int4 = 5 + try std.testing.expect(result = int4_neg(value)); + try std.testing.expect(result == -5); +} + +test "int4_neg_negative" { + given value: Int4 = -5 + try std.testing.expect(result = int4_neg(value)); + try std.testing.expect(result == 5); +} + +test "int4_neg_min" { + given value: Int4 = MIN + try std.testing.expect(result = int4_neg(value)); + try std.testing.expect(result == MAX // -(-8) = 7 (saturated)); +} + +test "int4_is_equal_true" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(int4_is_equal(a, b) == true); +} + +test "int4_is_equal_false" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(int4_is_equal(a, b) == false); +} + +test "int4_is_greater_true" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(int4_is_greater(a, b) == true); +} + +test "int4_is_greater_false" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(int4_is_greater(a, b) == false); +} + +test "int4_min_smaller" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_min(a, b)); + try std.testing.expect(result == 3); +} + +test "int4_min_larger" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_min(a, b)); + try std.testing.expect(result == 3); +} + +test "int4_max_smaller" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_max(a, b)); + try std.testing.expect(result == 5); +} + +test "int4_max_larger" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_max(a, b)); + try std.testing.expect(result == 5); +} + +test "int4_clamp_in_range" { + given value: Int4 = 5 + try std.testing.expect(min: Int4 = 0); + try std.testing.expect(max: Int4 = MAX); + try std.testing.expect(result = int4_clamp(value, min, max)); + try std.testing.expect(result == 5); +} + +test "int4_clamp_below_min" { + given value: Int4 = -10 + try std.testing.expect(min: Int4 = -5); + try std.testing.expect(max: Int4 = MAX); + try std.testing.expect(result = int4_clamp(value, min, max)); + try std.testing.expect(result == -5); +} + +test "int4_clamp_above_max" { + given value: Int4 = 10 + try std.testing.expect(min: Int4 = MIN); + try std.testing.expect(max: Int4 = 5); + try std.testing.expect(result = int4_clamp(value, min, max)); + try std.testing.expect(result == 5); +} + +test "int4_is_in_range_true" { + try std.testing.expect(int4_is_in_range(5) == true); +} + +test "int4_is_in_range_false_high" { + try std.testing.expect(int4_is_in_range(10) == false); +} + +test "int4_is_in_range_false_low" { + try std.testing.expect(int4_is_in_range(-10) == false); +} + +test "int4_is_in_range_at_max" { + try std.testing.expect(int4_is_in_range(MAX) == true); +} + +test "int4_is_in_range_at_min" { + try std.testing.expect(int4_is_in_range(MIN) == true); +} + +test "int4_lerp_zero" { + given a: Int4 = 1 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = int4_lerp(a, b, t)); + try std.testing.expect(result == 1); +} + +test "int4_lerp_one" { + given a: Int4 = 1 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = int4_lerp(a, b, t)); + try std.testing.expect(result == 5); +} + +test "int4_lerp_half" { + given a: Int4 = 0 + try std.testing.expect(b: Int4 = 4); + try std.testing.expect(t = 0.5); + try std.testing.expect(result = int4_lerp(a, b, t)); + try std.testing.expect(result == 2); +} + +test "int4_sq_positive" { + given value: Int4 = 3 + try std.testing.expect(result = int4_sq(value)); + try std.testing.expect(result == 9 // saturates to 7); +} + +test "int4_sq_negative" { + given value: Int4 = -3 + try std.testing.expect(result = int4_sq(value)); + try std.testing.expect(result == 9 // saturates to 7); +} + +test "int4_sq_zero" { + given value: Int4 = 0 + try std.testing.expect(result = int4_sq(value)); + try std.testing.expect(result == 0); +} + +test "int4_abs_diff_same" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_abs_diff(a, b)); + try std.testing.expect(result == 0); +} + +test "int4_abs_diff_positive" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 2); + try std.testing.expect(result = int4_abs_diff(a, b)); + try std.testing.expect(result == 3); +} + +test "int4_abs_diff_negative" { + given a: Int4 = 2 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_abs_diff(a, b)); + try std.testing.expect(result == 3); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant int4_bits_constant + assert BITS == 4 + +invariant int4_min_is_negative_eight + assert MIN == -8 + +invariant int4_max_is_seven + assert MAX == 7 + +invariant int4_range_is_sixteen + assert RANGE == 16 + +invariant int4_mask_lower_four_bits + assert MASK == 0x0F + +invariant int4_from_i8_max_saturates + assert int4_from_i8(100) == MAX + +invariant int4_from_i8_min_saturates + assert int4_from_i8(-100) == MIN + +invariant int4_from_i8_max_passes + assert int4_from_i8(MAX) == MAX + +invariant int4_from_i8_min_passes + assert int4_from_i8(MIN) == MIN + +invariant int4_to_i8_zero + assert int4_to_i8(0) == 0 + +invariant int4_to_i8_max + assert int4_to_i8(MAX) == MAX + +invariant int4_to_i8_min + assert int4_to_i8(MIN) == MIN + +invariant int4_add_zero + assert int4_add(5, 0) == 5 + +invariant int4_sub_zero + assert int4_sub(5, 0) == 5 + +invariant int4_mul_one + assert int4_mul(5, 1) == 5 + +invariant int4_mul_zero + assert int4_mul(5, 0) == 0 + +invariant int4_abs_non_negative + given value: Int4 = -5 + assert int4_abs(value) >= 0 + +invariant int4_neg_twice_original + given value: Int4 = 5 + try std.testing.expect(neg = int4_neg(value)); + try std.testing.expect(neg_again = int4_neg(neg)); + assert neg_again == value or value == MIN // MIN saturates + +invariant int4_min_le_both + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_min(a, b)); + assert result <= a and result <= b + +invariant int4_max_ge_both + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_max(a, b)); + assert result >= a and result >= b + +invariant int4_clamp_result_in_range + given result = int4_clamp(0, -5, 5) + assert result >= -5 and result <= 5 + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench int4_from_i8_latency + measure: nanoseconds to int4_from_i8(5) + target: < 5ns + +bench int4_from_f32_latency + measure: nanoseconds to int4_from_f32(5.5) + target: < 20ns + +bench int4_add_latency + measure: nanoseconds to int4_add(3, 4) + target: < 10ns + +bench int4_mul_latency + measure: nanoseconds to int4_mul(3, 4) + target: < 10ns + +bench int4_abs_latency + measure: nanoseconds to int4_abs(-5) + target: < 5ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/int8.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/int8.t27 new file mode 100644 index 0000000000..82e33b87a0 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/int8.t27 @@ -0,0 +1,1151 @@ +// SPDX-License-Identifier: Apache-2.0 +; int8.t27 — Int8 Signed 8-bit Integer Quantization +; Range: -128 to 127 +; Standard 8-bit signed integer, widely used in quantization +; φ² + 1/φ² = 3 | TRINITY + +module triformat-int8; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const MIN : i8 = -128; +pub const MAX : i8 = 127; +pub const RANGE : u16 = 256; // 2^8 + +pub const MASK : u8 = 0xFF; // All 8 bits mask + +// ============================================================================ +// Types +// ============================================================================ + +pub const Int8 = i8; // Native i8 type + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// int8_from_i8(value: i8) -> Int8 +// Convert i8 to Int8 (identity function, no-op) +pub fn int8_from_i8(value: i8) Int8 { + return value; +} + +// int8_from_i16(value: i16) -> Int8 +// Convert i16 to Int8 with saturation +pub fn int8_from_i16(value: i16) Int8 { + if (value < @as(i16, MIN)) { + return MIN; + } else if (value > @as(i16, MAX)) { + return MAX; + } + return @as(i8, @intCast(value)); +} + +// int8_from_i32(value: i32) -> Int8 +// Convert i32 to Int8 with saturation +pub fn int8_from_i32(value: i32) Int8 { + if (value < @as(i32, MIN)) { + return MIN; + } else if (value > @as(i32, MAX)) { + return MAX; + } + return @as(i8, @intCast(value)); +} + +// int8_from_u8(value: u8) -> Int8 +// Convert u8 to Int8 with saturation +// Values > 127 clamp to 127 +pub fn int8_from_u8(value: u8) Int8 { + if (value > 127) { + return MAX; + } + return @as(i8, @bitCast(value)); +} + +// int8_from_f32(value: f32) -> Int8 +// Convert f32 to Int8 with rounding and saturation +// Round to nearest, ties to even +pub fn int8_from_f32(value: f32) Int8 { + if (std.math.isNan(value)) { + return 0; + } + if (value > @as(f32, @floatFromInt(MAX)) + 0.5) { + return MAX; + } + if (value < @as(f32, @floatFromInt(MIN)) - 0.5) { + return MIN; + } + const rounded = @as(i32, @intFromFloat(@round(value))); + return int8_from_i32(rounded); +} + +// int8_from_f64(value: f64) -> Int8 +// Convert f64 to Int8 with rounding and saturation +pub fn int8_from_f64(value: f64) Int8 { + if (std.math.isNan(value)) { + return 0; + } + if (value > @as(f64, @floatFromInt(MAX)) + 0.5) { + return MAX; + } + if (value < @as(f64, @floatFromInt(MIN)) - 0.5) { + return MIN; + } + const rounded = @as(i32, @intFromFloat(@round(value))); + return int8_from_i32(rounded); +} + +// int8_to_i8(value: Int8) -> i8 +// Convert Int8 to i8 (identity function) +pub fn int8_to_i8(value: Int8) i8 { + return value; +} + +// int8_to_i16(value: Int8) -> i16 +// Convert Int8 to i16 (sign extension) +pub fn int8_to_i16(value: Int8) i16 { + return @as(i16, value); +} + +// int8_to_i32(value: Int8) -> i32 +// Convert Int8 to i32 (sign extension) +pub fn int8_to_i32(value: Int8) i32 { + return @as(i32, value); +} + +// int8_to_u8(value: Int8) -> ?u8 +// Convert Int8 to u8 (returns null for negative values) +pub fn int8_to_u8(value: Int8) ?u8 { + if (value < 0) { + return null; + } + return @as(u8, @intCast(value)); +} + +// int8_to_f32(value: Int8) -> f32 +// Convert Int8 to f32 +pub fn int8_to_f32(value: Int8) f32 { + return @as(f32, @floatFromInt(value)); +} + +// int8_to_f64(value: Int8) -> f64 +// Convert Int8 to f64 +pub fn int8_to_f64(value: Int8) f64 { + return @as(f64, @floatFromInt(value)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// int8_add(a: Int8, b: Int8) -> Int8 +// Add two Int8 values with saturation +pub fn int8_add(a: Int8, b: Int8) Int8 { + const result = @as(i16, a) + @as(i16, b); + return int8_from_i16(result); +} + +// int8_sub(a: Int8, b: Int8) -> Int8 +// Subtract two Int8 values with saturation +pub fn int8_sub(a: Int8, b: Int8) Int8 { + const result = @as(i16, a) - @as(i16, b); + return int8_from_i16(result); +} + +// int8_mul(a: Int8, b: Int8) -> Int8 +// Multiply two Int8 values with saturation +pub fn int8_mul(a: Int8, b: Int8) Int8 { + const result = @as(i16, a) * @as(i16, b); + return int8_from_i16(result); +} + +// int8_div(a: Int8, b: Int8) -> Int8 +// Divide two Int8 values with saturation +// Division by zero returns MAX or MIN based on sign of numerator +pub fn int8_div(a: Int8, b: Int8) Int8 { + if (b == 0) { + return if (a >= 0) MAX else MIN; + } + return a / b; // i8 division saturates automatically on overflow +} + +// int8_mod(a: Int8, b: Int8) -> Int8 +// Modulo operation (returns 0 on division by zero) +pub fn int8_mod(a: Int8, b: Int8) Int8 { + if (b == 0) { + return 0; + } + const result = @rem(a, b); + // Handle negative remainder to make it always positive + return if (result < 0) result + b else result; +} + +// int8_pow(base: Int8, exp: u8) -> Int8 +// Power function with saturation +pub fn int8_pow(base: Int8, exp: u8) Int8 { + if (exp == 0) { + return 1; + } + if (base == 0) { + return 0; + } + if (base == 1) { + return 1; + } + if (base == -1) { + return if (exp % 2 == 0) 1 else -1; + } + + var result: i16 = 1; + var current: i16 = @as(i16, base); + var remaining = exp; + + while (remaining > 0) { + if (remaining % 2 == 1) { + result = result * current; + if (result > MAX) { + return MAX; + } + if (result < MIN) { + return MIN; + } + } + current = current * current; + if (current > MAX) { + return MAX; + } + if (current < MIN) { + return MIN; + } + remaining = remaining / 2; + } + + return int8_from_i16(result); +} + +// ============================================================================ +// Bitwise Operations +// ============================================================================ + +// int8_and(a: Int8, b: Int8) -> Int8 +// Bitwise AND +pub fn int8_and(a: Int8, b: Int8) Int8 { + return a & b; +} + +// int8_or(a: Int8, b: Int8) -> Int8 +// Bitwise OR +pub fn int8_or(a: Int8, b: Int8) Int8 { + return a | b; +} + +// int8_xor(a: Int8, b: Int8) -> Int8 +// Bitwise XOR +pub fn int8_xor(a: Int8, b: Int8) Int8 { + return a ^ b; +} + +// int8_not(value: Int8) -> Int8 +// Bitwise NOT +pub fn int8_not(value: Int8) Int8 { + return ~value; +} + +// int8_shl(value: Int8, amount: u8) -> Int8 +// Left shift (saturated) +pub fn int8_shl(value: Int8, amount: u8) Int8 { + const clamped = @min(amount, 7); + const result = @as(i16, value) << clamped; + return int8_from_i16(result); +} + +// int8_shr(value: Int8, amount: u8) -> Int8 +// Right shift (arithmetic, sign-preserving) +pub fn int8_shr(value: Int8, amount: u8) Int8 { + const clamped = @min(amount, 7); + return @shrExact(value, clamped); +} + +// int8_rol(value: Int8, amount: u8) -> Int8 +// Rotate left +pub fn int8_rol(value: Int8, amount: u8) Int8 { + const clamped = amount % 8; + const uval = @as(u8, @bitCast(value)); + const result = (uval << clamped) | (uval >> (8 - clamped)); + return @as(i8, @bitCast(result)); +} + +// int8_ror(value: Int8, amount: u8) -> Int8 +// Rotate right +pub fn int8_ror(value: Int8, amount: u8) Int8 { + const clamped = amount % 8; + const uval = @as(u8, @bitCast(value)); + const result = (uval >> clamped) | (uval << (8 - clamped)); + return @as(i8, @bitCast(result)); +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// int8_abs(value: Int8) -> Int8 +// Absolute value of Int8 with saturation +pub fn int8_abs(value: Int8) Int8 { + if (value == MIN) { + return MAX; // |-128| = 127 (saturated) + } + return if (value < 0) -value else value; +} + +// int8_neg(value: Int8) -> Int8 +// Negate Int8 (with saturation for -MIN) +pub fn int8_neg(value: Int8) Int8 { + if (value == MIN) { + return MAX; // -(-128) = 127 (saturated) + } + return -value; +} + +// int8_sign(value: Int8) -> Int8 +// Sign of value: -1, 0, or 1 +pub fn int8_sign(value: Int8) Int8 { + if (value < 0) { + return -1; + } else if (value > 0) { + return 1; + } + return 0; +} + +// int8_is_equal(a: Int8, b: Int8) -> bool +pub fn int8_is_equal(a: Int8, b: Int8) bool { + return a == b; +} + +// int8_is_greater(a: Int8, b: Int8) -> bool +pub fn int8_is_greater(a: Int8, b: Int8) bool { + return a > b; +} + +// int8_is_less(a: Int8, b: Int8) -> bool +pub fn int8_is_less(a: Int8, b: Int8) bool { + return a < b; +} + +// int8_min(a: Int8, b: Int8) -> Int8 +pub fn int8_min(a: Int8, b: Int8) Int8 { + return if (a < b) a else b; +} + +// int8_max(a: Int8, b: Int8) -> Int8 +pub fn int8_max(a: Int8, b: Int8) Int8 { + return if (a > b) a else b; +} + +// int8_clamp(value: Int8, min: Int8, max: Int8) -> Int8 +pub fn int8_clamp(value: Int8, min: Int8, max: Int8) Int8 { + if (value < min) { + return min; + } else if (value > max) { + return max; + } + return value; +} + +// int8_lerp(a: Int8, b: Int8, t: f32) -> Int8 +// Linear interpolation between a and b +pub fn int8_lerp(a: Int8, b: Int8, t: f32) Int8 { + const fa = @as(f32, @floatFromInt(a)); + const fb = @as(f32, @floatFromInt(b)); + const result = fa + (fb - fa) * t; + return int8_from_f32(result); +} + +// int8_sq(value: Int8) -> Int8 +// Square value with saturation +pub fn int8_sq(value: Int8) Int8 { + return int8_mul(value, value); +} + +// int8_abs_diff(a: Int8, b: Int8) -> Int8 +// Absolute difference between two Int8 values +pub fn int8_abs_diff(a: Int8, b: Int8) Int8 { + const diff = int8_sub(a, b); + return int8_abs(diff); +} + +// int8_sqrt(value: Int8) -> Int8 +// Integer square root (rounded down) +// Returns 0 for negative inputs +pub fn int8_sqrt(value: Int8) Int8 { + if (value < 0) { + return 0; + } + if (value == 0) { + return 0; + } + + var result: i8 = 0; + var bit: i8 = 1; + + // Find the highest power of 4 <= value + while (bit <= value and bit > 0) { + bit = bit * 4; + } + bit = bit / 4; + + while (bit != 0) { + if (value >= result + bit) { + value = value - (result + bit); + result = result / 2 + bit; + } else { + result = result / 2; + } + bit = bit / 4; + } + + return result; +} + +// int8_is_power_of_two(value: Int8) -> bool +// Check if value is a power of two (for positive values only) +pub fn int8_is_power_of_two(value: Int8) bool { + if (value <= 0) { + return false; + } + const uval = @as(u8, @bitCast(value)); + return (uval & (uval - 1)) == 0; +} + +// int8_count_ones(value: Int8) -> u8 +// Count the number of set bits +pub fn int8_count_ones(value: Int8) u8 { + const uval = @as(u8, @bitCast(value)); + return @popCount(uval); +} + +// int8_count_zeros(value: Int8) -> u8 +// Count the number of zero bits +pub fn int8_count_zeros(value: Int8) u8 { + return 8 - int8_count_ones(value); +} + +// int8_reverse_bits(value: Int8) -> Int8 +// Reverse the bits in an Int8 +pub fn int8_reverse_bits(value: Int8) Int8 { + const uval = @as(u8, @bitCast(value)); + var result: u8 = 0; + var i: u8 = 0; + while (i < 8) { + result = (result << 1) | (uval & 1); + uval = uval >> 1; + i = i + 1; + } + return @as(i8, @bitCast(result)); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "int8_from_i8_identity" { + given value = 42 + try std.testing.expect(result = int8_from_i8(value)); + try std.testing.expect(result == 42); +} + +test "int8_from_i16_positive" { + given value = 100 + try std.testing.expect(result = int8_from_i16(value)); + try std.testing.expect(result == 100); +} + +test "int8_from_i16_overflow" { + given value = 200 + try std.testing.expect(result = int8_from_i16(value)); + try std.testing.expect(result == MAX); +} + +test "int8_from_i16_negative" { + given value = -50 + try std.testing.expect(result = int8_from_i16(value)); + try std.testing.expect(result == -50); +} + +test "int8_from_i16_underflow" { + given value = -200 + try std.testing.expect(result = int8_from_i16(value)); + try std.testing.expect(result == MIN); +} + +test "int8_from_i32_in_range" { + given value = 42 + try std.testing.expect(result = int8_from_i32(value)); + try std.testing.expect(result == 42); +} + +test "int8_from_i32_overflow" { + given value = 1000 + try std.testing.expect(result = int8_from_i32(value)); + try std.testing.expect(result == MAX); +} + +test "int8_from_u8_in_range" { + given value = 100 + try std.testing.expect(result = int8_from_u8(value)); + try std.testing.expect(result == 100); +} + +test "int8_from_u8_overflow" { + given value = 200 + try std.testing.expect(result = int8_from_u8(value)); + try std.testing.expect(result == MAX); +} + +test "int8_from_f32_zero" { + given value = 0.0 + try std.testing.expect(result = int8_from_f32(value)); + try std.testing.expect(result == 0); +} + +test "int8_from_f32_one" { + given value = 1.0 + try std.testing.expect(result = int8_from_f32(value)); + try std.testing.expect(result == 1); +} + +test "int8_from_f32_round_up" { + given value = 1.6 + try std.testing.expect(result = int8_from_f32(value)); + try std.testing.expect(result == 2); +} + +test "int8_from_f32_round_down" { + given value = 1.4 + try std.testing.expect(result = int8_from_f32(value)); + try std.testing.expect(result == 1); +} + +test "int8_from_f32_nan" { + given result = int8_from_f32(std.math.nan(f32)) + try std.testing.expect(result == 0); +} + +test "int8_to_i8_identity" { + given value: Int8 = 42 + try std.testing.expect(result = int8_to_i8(value)); + try std.testing.expect(result == 42); +} + +test "int8_to_i16_sign_extend" { + given value: Int8 = -1 + try std.testing.expect(result = int8_to_i16(value)); + try std.testing.expect(result == -1); +} + +test "int8_to_i32_sign_extend" { + given value: Int8 = -1 + try std.testing.expect(result = int8_to_i32(value)); + try std.testing.expect(result == -1); +} + +test "int8_to_u8_positive" { + given value: Int8 = 42 + try std.testing.expect(result = int8_to_u8(value)); + try std.testing.expect(result.? == 42); +} + +test "int8_to_u8_negative" { + given value: Int8 = -1 + try std.testing.expect(result = int8_to_u8(value)); + try std.testing.expect(result == null); +} + +test "int8_add_simple" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 30); + try std.testing.expect(result = int8_add(a, b)); + try std.testing.expect(result == 80); +} + +test "int8_add_overflow" { + given a: Int8 = 100 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_add(a, b)); + try std.testing.expect(result == MAX); +} + +test "int8_add_underflow" { + given a: Int8 = -100 + try std.testing.expect(b: Int8 = -50); + try std.testing.expect(result = int8_add(a, b)); + try std.testing.expect(result == MIN); +} + +test "int8_sub_simple" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 30); + try std.testing.expect(result = int8_sub(a, b)); + try std.testing.expect(result == 20); +} + +test "int8_sub_negative" { + given a: Int8 = 30 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_sub(a, b)); + try std.testing.expect(result == -20); +} + +test "int8_mul_positive" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 5); + try std.testing.expect(result = int8_mul(a, b)); + try std.testing.expect(result == 50); +} + +test "int8_mul_negative" { + given a: Int8 = -10 + try std.testing.expect(b: Int8 = 5); + try std.testing.expect(result = int8_mul(a, b)); + try std.testing.expect(result == -50); +} + +test "int8_mul_overflow" { + given a: Int8 = 20 + try std.testing.expect(b: Int8 = 20); + try std.testing.expect(result = int8_mul(a, b)); + try std.testing.expect(result == MAX); +} + +test "int8_div_simple" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 5); + try std.testing.expect(result = int8_div(a, b)); + try std.testing.expect(result == 10); +} + +test "int8_div_by_zero_positive" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 0); + try std.testing.expect(result = int8_div(a, b)); + try std.testing.expect(result == MAX); +} + +test "int8_div_by_zero_negative" { + given a: Int8 = -50 + try std.testing.expect(b: Int8 = 0); + try std.testing.expect(result = int8_div(a, b)); + try std.testing.expect(result == MIN); +} + +test "int8_mod_simple" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 3); + try std.testing.expect(result = int8_mod(a, b)); + try std.testing.expect(result == 1); +} + +test "int8_mod_by_zero" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 0); + try std.testing.expect(result = int8_mod(a, b)); + try std.testing.expect(result == 0); +} + +test "int8_pow_zero" { + given base: Int8 = 5 + try std.testing.expect(exp: u8 = 0); + try std.testing.expect(result = int8_pow(base, exp)); + try std.testing.expect(result == 1); +} + +test "int8_pow_one" { + given base: Int8 = 5 + try std.testing.expect(exp: u8 = 1); + try std.testing.expect(result = int8_pow(base, exp)); + try std.testing.expect(result == 5); +} + +test "int8_pow_two" { + given base: Int8 = 5 + try std.testing.expect(exp: u8 = 2); + try std.testing.expect(result = int8_pow(base, exp)); + try std.testing.expect(result == 25); +} + +test "int8_pow_overflow" { + given base: Int8 = 20 + try std.testing.expect(exp: u8 = 2); + try std.testing.expect(result = int8_pow(base, exp)); + try std.testing.expect(result == MAX); +} + +test "int8_and_simple" { + given a: Int8 = 0b10101010 + try std.testing.expect(b: Int8 = 0b11001100); + try std.testing.expect(result = int8_and(a, b)); + try std.testing.expect(result == 0b10001000); +} + +test "int8_or_simple" { + given a: Int8 = 0b10101010 + try std.testing.expect(b: Int8 = 0b11001100); + try std.testing.expect(result = int8_or(a, b)); + try std.testing.expect(result == 0b11101110); +} + +test "int8_xor_simple" { + given a: Int8 = 0b10101010 + try std.testing.expect(b: Int8 = 0b11001100); + try std.testing.expect(result = int8_xor(a, b)); + try std.testing.expect(result == 0b01100110); +} + +test "int8_not_simple" { + given value: Int8 = 0b10101010 + try std.testing.expect(result = int8_not(value)); + try std.testing.expect(result == 0b01010101); +} + +test "int8_shl_simple" { + given value: Int8 = 0b00000011 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_shl(value, amount)); + try std.testing.expect(result == 0b00001100); +} + +test "int8_shl_overflow" { + given value: Int8 = 64 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_shl(value, amount)); + try std.testing.expect(result == MAX); +} + +test "int8_shr_simple" { + given value: Int8 = 0b00001100 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_shr(value, amount)); + try std.testing.expect(result == 0b00000011); +} + +test "int8_rol_simple" { + given value: Int8 = 0b00000011 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_rol(value, amount)); + try std.testing.expect(result == 0b00001100); +} + +test "int8_ror_simple" { + given value: Int8 = 0b00001100 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_ror(value, amount)); + try std.testing.expect(result == 0b00000011); +} + +test "int8_abs_positive" { + given value: Int8 = 42 + try std.testing.expect(result = int8_abs(value)); + try std.testing.expect(result == 42); +} + +test "int8_abs_negative" { + given value: Int8 = -42 + try std.testing.expect(result = int8_abs(value)); + try std.testing.expect(result == 42); +} + +test "int8_abs_min" { + given value: Int8 = MIN + try std.testing.expect(result = int8_abs(value)); + try std.testing.expect(result == MAX); +} + +test "int8_neg_positive" { + given value: Int8 = 42 + try std.testing.expect(result = int8_neg(value)); + try std.testing.expect(result == -42); +} + +test "int8_neg_negative" { + given value: Int8 = -42 + try std.testing.expect(result = int8_neg(value)); + try std.testing.expect(result == 42); +} + +test "int8_neg_min" { + given value: Int8 = MIN + try std.testing.expect(result = int8_neg(value)); + try std.testing.expect(result == MAX); +} + +test "int8_sign_positive" { + given value: Int8 = 42 + try std.testing.expect(result = int8_sign(value)); + try std.testing.expect(result == 1); +} + +test "int8_sign_negative" { + given value: Int8 = -42 + try std.testing.expect(result = int8_sign(value)); + try std.testing.expect(result == -1); +} + +test "int8_sign_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_sign(value)); + try std.testing.expect(result == 0); +} + +test "int8_min_smaller" { + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_min(a, b)); + try std.testing.expect(result == 42); +} + +test "int8_max_larger" { + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_max(a, b)); + try std.testing.expect(result == 50); +} + +test "int8_clamp_in_range" { + given value: Int8 = 42 + try std.testing.expect(min: Int8 = 0); + try std.testing.expect(max: Int8 = 100); + try std.testing.expect(result = int8_clamp(value, min, max)); + try std.testing.expect(result == 42); +} + +test "int8_clamp_below" { + given value: Int8 = -50 + try std.testing.expect(min: Int8 = 0); + try std.testing.expect(max: Int8 = 100); + try std.testing.expect(result = int8_clamp(value, min, max)); + try std.testing.expect(result == 0); +} + +test "int8_clamp_above" { + given value: Int8 = 150 + try std.testing.expect(min: Int8 = 0); + try std.testing.expect(max: Int8 = 100); + try std.testing.expect(result = int8_clamp(value, min, max)); + try std.testing.expect(result == 100); +} + +test "int8_lerp_zero" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = int8_lerp(a, b, t)); + try std.testing.expect(result == 10); +} + +test "int8_lerp_one" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = int8_lerp(a, b, t)); + try std.testing.expect(result == 50); +} + +test "int8_lerp_half" { + given a: Int8 = 0 + try std.testing.expect(b: Int8 = 20); + try std.testing.expect(t = 0.5); + try std.testing.expect(result = int8_lerp(a, b, t)); + try std.testing.expect(result == 10); +} + +test "int8_sq_positive" { + given value: Int8 = 10 + try std.testing.expect(result = int8_sq(value)); + try std.testing.expect(result == 100); +} + +test "int8_sq_negative" { + given value: Int8 = -10 + try std.testing.expect(result = int8_sq(value)); + try std.testing.expect(result == 100); +} + +test "int8_sq_overflow" { + given value: Int8 = 20 + try std.testing.expect(result = int8_sq(value)); + try std.testing.expect(result == MAX); +} + +test "int8_abs_diff_same" { + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 42); + try std.testing.expect(result = int8_abs_diff(a, b)); + try std.testing.expect(result == 0); +} + +test "int8_abs_diff_positive" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 30); + try std.testing.expect(result = int8_abs_diff(a, b)); + try std.testing.expect(result == 20); +} + +test "int8_sqrt_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 0); +} + +test "int8_sqrt_one" { + given value: Int8 = 1 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 1); +} + +test "int8_sqrt_four" { + given value: Int8 = 4 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 2); +} + +test "int8_sqrt_sixteen" { + given value: Int8 = 16 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 4); +} + +test "int8_sqrt_negative" { + given value: Int8 = -4 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 0); +} + +test "int8_is_power_of_two_true" { + given value: Int8 = 8 + try std.testing.expect(int8_is_power_of_two(value) == true); +} + +test "int8_is_power_of_two_false" { + given value: Int8 = 6 + try std.testing.expect(int8_is_power_of_two(value) == false); +} + +test "int8_is_power_of_two_zero" { + given value: Int8 = 0 + try std.testing.expect(int8_is_power_of_two(value) == false); +} + +test "int8_count_ones_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_count_ones(value)); + try std.testing.expect(result == 0); +} + +test "int8_count_ones_all" { + given value: Int8 = 0xFF + try std.testing.expect(result = int8_count_ones(value)); + try std.testing.expect(result == 8); +} + +test "int8_count_ones_mixed" { + given value: Int8 = 0b10101010 + try std.testing.expect(result = int8_count_ones(value)); + try std.testing.expect(result == 4); +} + +test "int8_count_zeros_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_count_zeros(value)); + try std.testing.expect(result == 8); +} + +test "int8_count_zeros_all" { + given value: Int8 = 0xFF + try std.testing.expect(result = int8_count_zeros(value)); + try std.testing.expect(result == 0); +} + +test "int8_reverse_bits_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_reverse_bits(value)); + try std.testing.expect(result == 0); +} + +test "int8_reverse_bits_one" { + given value: Int8 = 0b10000000 + try std.testing.expect(result = int8_reverse_bits(value)); + try std.testing.expect(result == 0b00000001); +} + +test "int8_reverse_bits_all" { + given value: Int8 = 0xFF + try std.testing.expect(result = int8_reverse_bits(value)); + try std.testing.expect(result == 0xFF); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant int8_bits_constant + assert BITS == 8 + +invariant int8_min_is_negative_128 + assert MIN == -128 + +invariant int8_max_is_127 + assert MAX == 127 + +invariant int8_range_is_256 + assert RANGE == 256 + +invariant int8_mask_all_bits + assert MASK == 0xFF + +invariant int8_from_i16_min_saturates + assert int8_from_i16(-1000) == MIN + +invariant int8_from_i16_max_saturates + assert int8_from_i16(1000) == MAX + +invariant int8_to_i16_identity + given value: Int8 = 42 + assert int8_from_i16(int8_to_i16(value)) == value + +invariant int8_to_u8_negative_null + assert int8_to_u8(-1) == null + +invariant int8_to_u8_positive_ok + given result = int8_to_u8(42) + assert result.? == 42 + +invariant int8_add_zero + assert int8_add(42, 0) == 42 + +invariant int8_sub_zero + assert int8_sub(42, 0) == 42 + +invariant int8_mul_one + assert int8_mul(42, 1) == 42 + +invariant int8_mul_zero + assert int8_mul(42, 0) == 0 + +invariant int8_div_one + assert int8_div(42, 1) == 42 + +invariant int8_pow_zero_one + assert int8_pow(42, 0) == 1 + +invariant int8_pow_one_self + assert int8_pow(42, 1) == 42 + +invariant int8_and_zero + assert int8_and(42, 0) == 0 + +invariant int8_and_minus_one + assert int8_and(42, -1) == 42 + +invariant int8_or_zero + assert int8_or(42, 0) == 42 + +invariant int8_or_minus_one + assert int8_or(42, -1) == -1 + +invariant int8_xor_zero + assert int8_xor(42, 0) == 42 + +invariant int8_xor_self + assert int8_xor(42, 42) == 0 + +invariant int8_not_double + given value: Int8 = 42 + assert int8_not(int8_not(value)) == value + +invariant int8_abs_non_negative + given value: Int8 = -42 + assert int8_abs(value) >= 0 + +invariant int8_neg_twice_original + given value: Int8 = 42 + try std.testing.expect(neg = int8_neg(value)); + try std.testing.expect(neg_again = int8_neg(neg)); + assert neg_again == value or value == MIN + +invariant int8_sign_positive_one + assert int8_sign(42) == 1 + +invariant int8_sign_negative_minus_one + assert int8_sign(-42) == -1 + +invariant int8_sign_zero_zero + assert int8_sign(0) == 0 + +invariant int8_min_le_both + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_min(a, b)); + assert result <= a and result <= b + +invariant int8_max_ge_both + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_max(a, b)); + assert result >= a and result >= b + +invariant int8_lerp_zero_start + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 50); + assert int8_lerp(a, b, 0.0) == a + +invariant int8_lerp_one_end + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 50); + assert int8_lerp(a, b, 1.0) == b + +invariant int8_sq_non_negative + given value: Int8 = 42 + try std.testing.expect(result = int8_sq(value)); + assert result >= 0 + +invariant int8_sqrt_square_original_or_less + given value: Int8 = 42 + try std.testing.expect(sqrt_result = int8_sqrt(value)); + try std.testing.expect(sqrt_sq = int8_sq(sqrt_result)); + assert sqrt_sq <= value + +invariant int8_count_ones_plus_count_zeros_equals_bits + given value: Int8 = 42 + assert int8_count_ones(value) + int8_count_zeros(value) == BITS + +invariant int8_reverse_bits_double + given value: Int8 = 42 + assert int8_reverse_bits(int8_reverse_bits(value)) == value + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench int8_add_latency + measure: nanoseconds to int8_add(42, 30) + target: < 5ns + +bench int8_mul_latency + measure: nanoseconds to int8_mul(10, 5) + target: < 5ns + +bench int8_abs_latency + measure: nanoseconds to int8_abs(-42) + target: < 5ns + +bench int8_from_f32_latency + measure: nanoseconds to int8_from_f32(42.5) + target: < 20ns + +bench int8_sqrt_latency + measure: nanoseconds to int8_sqrt(42) + target: < 50ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/nf4.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/nf4.t27 new file mode 100644 index 0000000000..005f676f71 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/nf4.t27 @@ -0,0 +1,839 @@ +// SPDX-License-Identifier: Apache-2.0 +; nf4.t27 — NormalFloat4 Quantization +; 4-bit quantization based on normalized distribution +; Values: {-1, -0.667, -0.333, 0, 0.333, 0.667, 1, 0} (8 levels + zero) +; φ² + 1/φ² = 3 | TRINITY + +module triformat-nf4; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 4; +pub const LEVELS : u8 = 8; // Non-zero levels +pub const TOTAL_LEVELS : u8 = 16; // Including signed variants + +// NF4 uses 2 bits for magnitude index (1-4) and 1 bit for sign +// Index 0 = zero (special case) +// Levels (normalized): [0.0, 0.333, 0.667, 1.0] + +pub const NUM_NEGATIVE_LEVELS : u8 = 3; +pub const NUM_POSITIVE_LEVELS : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const NF4 = u8; // 4-bit value stored in lower 4 bits of u8 + +// ============================================================================ +// LUT Tables +// ============================================================================ + +// Normalized float levels (de-quantization table) +// Index 0 = 0.0 (special zero), indices 1-7 map to levels +const LEVELS_FLOAT : [8]f32 = [8]f32{ + 0.0, // 0: zero + 0.333, // 1: 1/3 + 0.667, // 2: 2/3 + 1.0, // 3: max + -0.333, // 4: -1/3 + -0.667, // 5: -2/3 + -1.0, // 6: -max + 0.0, // 7: also zero (for rounding tie-breaking) +}; + +// More precise levels using phi-optimized values +// φ-optimized NF4 levels based on quartiles of normal distribution +const LEVELS_PHI : [8]f32 = [8]f32{ + 0.0, // 0: zero + 0.4167, // 1: ~-1σ * 0.618 + 0.75, // 2: -μ/2 * φ + 1.0, // 3: max + -0.4167, // 4: negative of 1 + -0.75, // 5: negative of 2 + -1.0, // 6: negative of 3 + 0.0, // 7: zero +}; + +// Use phi-optimized levels by default +pub const QUANTIZATION_LEVELS : [8]f32 = LEVELS_PHI; + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// nf4_from_f32(value: f32) -> NF4 +// Quantize f32 to NF4 +// Returns 4-bit value in lower bits of u8 +pub fn nf4_from_f32(value: f32) NF4 { + // Handle NaN and infinity + if (std.math.isNan(value) or std.math.isInf(value)) { + return 0; // Return zero for special values + } + + const abs_value = @abs(value); + + // Clamp to [-1, 1] + if (abs_value > 1.0) { + if (value > 0.0) { + return 0b011; // +1.0 + } else { + return 0b110; // -1.0 + } + } + + // Zero threshold + if (abs_value < 0.2) { + return 0b000; // zero + } + + // Determine level (1, 2, or 3 for magnitude) + var level: u3 = 0; + if (abs_value < 0.58) { + level = 1; // ~0.4167 + } else if (abs_value < 0.87) { + level = 2; // ~0.75 + } else { + level = 3; // 1.0 + } + + // Apply sign bit (bit 2) + if (value < 0.0) { + return @as(NF4, @intCast(0b100 | level)); + } else { + return @as(NF4, @intCast(level)); + } +} + +// nf4_from_f64(value: f64) -> NF4 +// Quantize f64 to NF4 +pub fn nf4_from_f64(value: f64) NF4 { + return nf4_from_f32(@as(f32, @floatCast(value))); +} + +// nf4_from_i8(value: i8) -> NF4 +// Quantize i8 to NF4 (normalized to [-1, 1]) +pub fn nf4_from_i8(value: i8) NF4 { + // Normalize i8 [-128, 127] to [-1, 1] + const normalized = @as(f32, @floatFromInt(value)) / 127.0; + return nf4_from_f32(normalized); +} + +// nf4_to_f32(value: NF4) -> f32 +// De-quantize NF4 to f32 +pub fn nf4_to_f32(value: NF4) f32 { + const index = value & 0b111; + return QUANTIZATION_LEVELS[@as(usize, @intCast(index))]; +} + +// nf4_to_f64(value: NF4) -> f64 +// De-quantize NF4 to f64 +pub fn nf4_to_f64(value: NF4) f64 { + return @as(f64, @floatFromInt(nf4_to_f32(value))); +} + +// nf4_to_i8(value: NF4) -> i8 +// De-quantize NF4 to i8 (scale from [-1, 1] to [-128, 127]) +pub fn nf4_to_i8(value: NF4) i8 { + const fval = nf4_to_f32(value); + const scaled = fval * 127.0; + return @as(i8, @intFromFloat(@round(scaled))); +} + +// ============================================================================ +// Arithmetic Operations (in f32 domain) +// ============================================================================ + +// nf4_add(a: NF4, b: NF4) -> NF4 +// Add two NF4 values (de-quantize, add, re-quantize) +pub fn nf4_add(a: NF4, b: NF4) NF4 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return nf4_from_f32(fa + fb); +} + +// nf4_sub(a: NF4, b: NF4) -> NF4 +// Subtract two NF4 values +pub fn nf4_sub(a: NF4, b: NF4) NF4 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return nf4_from_f32(fa - fb); +} + +// nf4_mul(a: NF4, b: NF4) -> NF4 +// Multiply two NF4 values +pub fn nf4_mul(a: NF4, b: NF4) NF4 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return nf4_from_f32(fa * fb); +} + +// nf4_div(a: NF4, b: NF4) -> NF4 +// Divide two NF4 values +pub fn nf4_div(a: NF4, b: NF4) NF4 { + const fb = nf4_to_f32(b); + if (@abs(fb) < 0.001) { + return 0; // Return zero for division by near-zero + } + const fa = nf4_to_f32(a); + return nf4_from_f32(fa / fb); +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// nf4_is_zero(value: NF4) -> bool +// Check if NF4 value is zero +pub fn nf4_is_zero(value: NF4) bool { + return (value & 0b111) == 0; +} + +// nf4_is_negative(value: NF4) -> bool +// Check if NF4 value is negative +pub fn nf4_is_negative(value: NF4) bool { + return (value & 0b100) != 0 and !nf4_is_zero(value); +} + +// nf4_is_positive(value: NF4) -> bool +// Check if NF4 value is positive +pub fn nf4_is_positive(value: NF4) bool { + return (value & 0b111) != 0 and (value & 0b100) == 0; +} + +// nf4_abs(value: NF4) -> NF4 +// Absolute value of NF4 +pub fn nf4_abs(value: NF4) NF4 { + return value & 0b011; +} + +// nf4_neg(value: NF4) -> NF4 +// Negate NF4 +pub fn nf4_neg(value: NF4) NF4 { + const magnitude = value & 0b011; + if (magnitude == 0) { + return 0; // Zero stays zero + } + return magnitude | 0b100; +} + +// nf4_is_equal(a: NF4, b: NF4) -> bool +// Check if two NF4 values are equal +pub fn nf4_is_equal(a: NF4, b: NF4) bool { + return a == b; +} + +// nf4_is_greater(a: NF4, b: NF4) -> bool +// Check if a > b +pub fn nf4_is_greater(a: NF4, b: NF4) bool { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return fa > fb; +} + +// nf4_is_less(a: NF4, b: NF4) -> bool +// Check if a < b +pub fn nf4_is_less(a: NF4, b: NF4) bool { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return fa < fb; +} + +// nf4_max(a: NF4, b: NF4) -> NF4 +// Return the larger of two NF4 values +pub fn nf4_max(a: NF4, b: NF4) NF4 { + return if (nf4_is_greater(a, b)) a else b; +} + +// nf4_min(a: NF4, b: NF4) -> NF4 +// Return the smaller of two NF4 values +pub fn nf4_min(a: NF4, b: NF4) NF4 { + return if (nf4_is_greater(a, b)) b else a; +} + +// nf4_clamp(value: NF4, min: NF4, max: NF4) -> NF4 +// Clamp value between min and max +pub fn nf4_clamp(value: NF4, min: NF4, max: NF4) NF4 { + return nf4_min(nf4_max(value, min), max); +} + +// nf4_lerp(a: NF4, b: NF4, t: f32) -> NF4 +// Linear interpolation between a and b +pub fn nf4_lerp(a: NF4, b: NF4, t: f32) NF4 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + const result = fa + (fb - fa) * t; + return nf4_from_f32(result); +} + +// nf4_magnitude(value: NF4) -> f32 +// Get the absolute magnitude of NF4 +pub fn nf4_magnitude(value: NF4) f32 { + return nf4_to_f32(nf4_abs(value)); +} + +// nf4_l1_distance(a: NF4, b: NF4) -> f32 +// L1 distance between two NF4 values +pub fn nf4_l1_distance(a: NF4, b: NF4) f32 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return @abs(fa - fb); +} + +// nf4_l2_distance(a: NF4, b: NF4) -> f32 +// L2 distance between two NF4 values +pub fn nf4_l2_distance(a: NF4, b: NF4) -> f32 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + const diff = fa - fb; + return diff * diff; +} + +// nf4_scale(value: NF4, scale: f32) -> NF4 +// Scale NF4 value +pub fn nf4_scale(value: NF4, scale: f32) NF4 { + const fa = nf4_to_f32(value); + return nf4_from_f32(fa * scale); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "nf4_from_f32_zero" { + given value = 0.0 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0); +} + +test "nf4_from_f32_small_positive" { + given value = 0.1 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(nf4_is_zero(result) == true); +} + +test "nf4_from_f32_medium_positive" { + given value = 0.5 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b010 // level 2); +} + +test "nf4_from_f32_large_positive" { + given value = 0.9 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b011 // level 3 (max)); +} + +test "nf4_from_f32_max_positive" { + given value = 1.0 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b011); +} + +test "nf4_from_f32_above_max" { + given value = 1.5 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b011 // saturated to max); +} + +test "nf4_from_f32_small_negative" { + given value = -0.1 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(nf4_is_zero(result) == true); +} + +test "nf4_from_f32_medium_negative" { + given value = -0.5 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b110 // level 2, negative); +} + +test "nf4_from_f32_large_negative" { + given value = -0.9 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b111 // level 3, negative (min)); +} + +test "nf4_from_f32_max_negative" { + given value = -1.0 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b111); +} + +test "nf4_from_f32_below_min" { + given value = -1.5 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b111 // saturated to min); +} + +test "nf4_from_f32_nan" { + given result = nf4_from_f32(std.math.nan(f32)) + try std.testing.expect(result == 0); +} + +test "nf4_from_f32_inf" { + given result = nf4_from_f32(std.math.inf(f32)) + try std.testing.expect(result == 0b011); +} + +test "nf4_from_f32_neg_inf" { + given result = nf4_from_f32(-std.math.inf(f32)) + try std.testing.expect(result == 0b111); +} + +test "nf4_to_f32_zero" { + given value: NF4 = 0 + try std.testing.expect(result = nf4_to_f32(value)); + try std.testing.expect(result == 0.0); +} + +test "nf4_to_f32_positive_max" { + given value: NF4 = 0b011 + try std.testing.expect(result = nf4_to_f32(value)); + try std.testing.expect(result == 1.0); +} + +test "nf4_to_f32_negative_min" { + given value: NF4 = 0b111 + try std.testing.expect(result = nf4_to_f32(value)); + try std.testing.expect(result == -1.0); +} + +test "nf4_to_f32_roundtrip_positive" { + given original = 0.7 + try std.testing.expect(encoded = nf4_from_f32(original)); + try std.testing.expect(decoded = nf4_to_f32(encoded)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "nf4_to_f32_roundtrip_negative" { + given original = -0.7 + try std.testing.expect(encoded = nf4_from_f32(original)); + try std.testing.expect(decoded = nf4_to_f32(encoded)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "nf4_from_i8_zero" { + given value: i8 = 0 + try std.testing.expect(result = nf4_from_i8(value)); + try std.testing.expect(result == 0); +} + +test "nf4_from_i8_max" { + given value: i8 = 127 + try std.testing.expect(result = nf4_from_i8(value)); + try std.testing.expect(result == 0b011); +} + +test "nf4_from_i8_min" { + given value: i8 = -128 + try std.testing.expect(result = nf4_from_i8(value)); + try std.testing.expect(result == 0b111); +} + +test "nf4_from_i8_medium" { + given value: i8 = 64 + try std.testing.expect(result = nf4_from_i8(value)); + try std.testing.expect(result == 0b011 // 64/127 ≈ 0.5 -> level 3); +} + +test "nf4_to_i8_max" { + given value: NF4 = 0b011 + try std.testing.expect(result = nf4_to_i8(value)); + try std.testing.expect(result >= 120 // Should be ~127); +} + +test "nf4_to_i8_min" { + given value: NF4 = 0b111 + try std.testing.expect(result = nf4_to_i8(value)); + try std.testing.expect(result <= -120 // Should be ~-127); +} + +test "nf4_add_zero" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0); + try std.testing.expect(result = nf4_add(a, b)); + try std.testing.expect(result == a); +} + +test "nf4_add_positives" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b001); + try std.testing.expect(result = nf4_add(a, b)); + try std.testing.expect(nf4_is_positive(result) == true); +} + +test "nf4_add_opposite" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b110); + try std.testing.expect(result = nf4_add(a, b)); + try std.testing.expect(nf4_is_zero(result) == true or nf4_magnitude(result) < 0.5); +} + +test "nf4_sub_same" { + given a: NF4 = 0b010 + try std.testing.expect(result = nf4_sub(a, a)); + try std.testing.expect(result == 0); +} + +test "nf4_mul_zero" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0); + try std.testing.expect(result = nf4_mul(a, b)); + try std.testing.expect(result == 0); +} + +test "nf4_mul_max_max" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_mul(a, b)); + try std.testing.expect(result == 0b011 // 1.0 * 1.0 = 1.0); +} + +test "nf4_mul_pos_neg" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b111); + try std.testing.expect(result = nf4_mul(a, b)); + try std.testing.expect(nf4_is_negative(result) == true); +} + +test "nf4_div_same" { + given a: NF4 = 0b010 + try std.testing.expect(result = nf4_div(a, a)); + try std.testing.expect(result == 0b001 // x/x = 1); +} + +test "nf4_div_zero" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0); + try std.testing.expect(result = nf4_div(a, b)); + try std.testing.expect(result == 0); +} + +test "nf4_is_zero_true" { + try std.testing.expect(nf4_is_zero(0) == true); +} + +test "nf4_is_zero_false" { + given value: NF4 = 0b001 + try std.testing.expect(nf4_is_zero(value) == false); +} + +test "nf4_is_negative_true" { + try std.testing.expect(nf4_is_negative(0b111) == true); +} + +test "nf4_is_negative_false_positive" { + try std.testing.expect(nf4_is_negative(0b011) == false); +} + +test "nf4_is_negative_false_zero" { + try std.testing.expect(nf4_is_negative(0) == false); +} + +test "nf4_is_positive_true" { + try std.testing.expect(nf4_is_positive(0b011) == true); +} + +test "nf4_is_positive_false_negative" { + try std.testing.expect(nf4_is_positive(0b111) == false); +} + +test "nf4_is_positive_false_zero" { + try std.testing.expect(nf4_is_positive(0) == false); +} + +test "nf4_abs_positive" { + given value: NF4 = 0b010 + try std.testing.expect(result = nf4_abs(value)); + try std.testing.expect(result == value); +} + +test "nf4_abs_negative" { + given value: NF4 = 0b110 + try std.testing.expect(result = nf4_abs(value)); + try std.testing.expect(result == 0b010); +} + +test "nf4_abs_zero" { + given result = nf4_abs(0) + try std.testing.expect(result == 0); +} + +test "nf4_neg_positive" { + given value: NF4 = 0b010 + try std.testing.expect(result = nf4_neg(value)); + try std.testing.expect(result == 0b110); +} + +test "nf4_neg_negative" { + given value: NF4 = 0b110 + try std.testing.expect(result = nf4_neg(value)); + try std.testing.expect(result == 0b010); +} + +test "nf4_neg_zero" { + given result = nf4_neg(0) + try std.testing.expect(result == 0); +} + +test "nf4_is_equal_true" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b010); + try std.testing.expect(nf4_is_equal(a, b) == true); +} + +test "nf4_is_equal_false" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b110); + try std.testing.expect(nf4_is_equal(a, b) == false); +} + +test "nf4_is_greater_true" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b010); + try std.testing.expect(nf4_is_greater(a, b) == true); +} + +test "nf4_is_greater_false" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(nf4_is_greater(a, b) == false); +} + +test "nf4_is_less_true" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(nf4_is_less(a, b) == true); +} + +test "nf4_is_less_false" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b010); + try std.testing.expect(nf4_is_less(a, b) == false); +} + +test "nf4_max_returns_larger" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_max(a, b)); + try std.testing.expect(result == 0b011); +} + +test "nf4_min_returns_smaller" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_min(a, b)); + try std.testing.expect(result == 0b010); +} + +test "nf4_clamp_in_range" { + given value: NF4 = 0b010 + try std.testing.expect(min: NF4 = 0b001); + try std.testing.expect(max: NF4 = 0b011); + try std.testing.expect(result = nf4_clamp(value, min, max)); + try std.testing.expect(result == value); +} + +test "nf4_clamp_below_min" { + given value: NF4 = 0 + try std.testing.expect(min: NF4 = 0b001); + try std.testing.expect(max: NF4 = 0b011); + try std.testing.expect(result = nf4_clamp(value, min, max)); + try std.testing.expect(result == min); +} + +test "nf4_clamp_above_max" { + given value: NF4 = 0b111 + try std.testing.expect(min: NF4 = 0b001); + try std.testing.expect(max: NF4 = 0b011); + try std.testing.expect(result = nf4_clamp(value, min, max)); + try std.testing.expect(result == max); +} + +test "nf4_lerp_zero" { + given a: NF4 = 0b001 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = nf4_lerp(a, b, t)); + try std.testing.expect(result == a); +} + +test "nf4_lerp_one" { + given a: NF4 = 0b001 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = nf4_lerp(a, b, t)); + try std.testing.expect(result == b); +} + +test "nf4_lerp_half" { + given a: NF4 = 0 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(t = 0.5); + try std.testing.expect(result = nf4_lerp(a, b, t)); + try std.testing.expect(nf4_magnitude(result) > 0.3); +} + +test "nf4_magnitude_positive" { + given value: NF4 = 0b011 + try std.testing.expect(result = nf4_magnitude(value)); + try std.testing.expect(result == 1.0); +} + +test "nf4_magnitude_negative" { + given value: NF4 = 0b111 + try std.testing.expect(result = nf4_magnitude(value)); + try std.testing.expect(result == 1.0); +} + +test "nf4_l1_distance_same" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b010); + try std.testing.expect(result = nf4_l1_distance(a, b)); + try std.testing.expect(result == 0.0); +} + +test "nf4_l1_distance_max_min" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b111); + try std.testing.expect(result = nf4_l1_distance(a, b)); + try std.testing.expect(result == 2.0); +} + +test "nf4_l2_distance_max_min" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b111); + try std.testing.expect(result = nf4_l2_distance(a, b)); + try std.testing.expect(result == 4.0); +} + +test "nf4_scale_up" { + given value: NF4 = 0b001 + try std.testing.expect(scale = 2.0); + try std.testing.expect(result = nf4_scale(value, scale)); + try std.testing.expect(nf4_magnitude(result) > 0.4); +} + +test "nf4_scale_down" { + given value: NF4 = 0b011 + try std.testing.expect(scale = 0.5); + try std.testing.expect(result = nf4_scale(value, scale)); + try std.testing.expect(nf4_magnitude(result) < 1.0); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant nf4_bits_constant + assert BITS == 4 + +invariant nf4_levels_count + assert LEVELS == 8 + +invariant nf4_total_levels + assert TOTAL_LEVELS == 16 + +invariant nf4_quantization_levels_length + assert QUANTIZATION_LEVELS.len == 8 + +invariant nf4_zero_is_zero + assert nf4_is_zero(0) == true + +invariant nf4_zero_not_negative + assert nf4_is_negative(0) == false + +invariant nf4_zero_not_positive + assert nf4_is_positive(0) == false + +invariant nf4_abs_zero_is_zero + assert nf4_abs(0) == 0 + +invariant nf4_neg_zero_is_zero + assert nf4_neg(0) == 0 + +invariant nf4_max_positive_is_max + given max_val: NF4 = 0b011 + try std.testing.expect(fmax = nf4_to_f32(max_val)); + assert fmax == 1.0 + +invariant nf4_min_negative_is_min + given min_val: NF4 = 0b111 + try std.testing.expect(fmin = nf4_to_f32(min_val)); + assert fmin == -1.0 + +invariant nf4_abs_neg_is_pos + given value: NF4 = 0b110 + try std.testing.expect(abs_val = nf4_abs(value)); + assert nf4_is_positive(abs_val) == true + +invariant nf4_neg_neg_is_pos + given value: NF4 = 0b110 + try std.testing.expect(neg_val = nf4_neg(value)); + assert nf4_is_positive(neg_val) == true + +invariant nf4_double_neg_original_or_zero + given value: NF4 = 0b010 + try std.testing.expect(neg_val = nf4_neg(value)); + try std.testing.expect(neg_neg_val = nf4_neg(neg_val)); + assert neg_neg_val == value or value == 0 + +invariant nf4_abs_non_negative + given value: NF4 = 0b111 + try std.testing.expect(abs_val = nf4_abs(value)); + assert nf4_to_f32(abs_val) >= 0.0 + +invariant nf4_l1_distance_symmetric + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b111); + assert nf4_l1_distance(a, b) == nf4_l1_distance(b, a) + +invariant nf4_l2_distance_symmetric + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b111); + assert nf4_l2_distance(a, b) == nf4_l2_distance(b, a) + +invariant nf4_l1_distance_non_negative + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b111); + assert nf4_l1_distance(a, b) >= 0.0 + +invariant nf4_max_ge_both + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_max(a, b)); + assert nf4_is_greater(result, a) == true and nf4_is_greater(result, b) == true or nf4_is_equal(result, a) == true + +invariant nf4_min_le_both + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_min(a, b)); + assert nf4_is_less(result, a) == true or nf4_is_equal(result, a) == true + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench nf4_from_f32_latency + measure: nanoseconds to nf4_from_f32(0.7) + target: < 50ns + +bench nf4_to_f32_latency + measure: nanoseconds to nf4_to_f32(0b010) + target: < 10ns + +bench nf4_add_latency + measure: nanoseconds to nf4_add(0b010, 0b001) + target: < 100ns + +bench nf4_mul_latency + measure: nanoseconds to nf4_mul(0b010, 0b011) + target: < 100ns + +bench nf4_abs_latency + measure: nanoseconds to nf4_abs(0b111) + target: < 5ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/phi_ratio.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/phi_ratio.t27 new file mode 100644 index 0000000000..9a172ad518 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/phi_ratio.t27 @@ -0,0 +1,694 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/phi_ratio.t27 +// 0-Ratio Proof 1 Derivation of GoldenFloat exp/mantissa split +// NUMERIC-STANDARD-001 2 Agent 9 (P0) + +module PhiRatio { + // Import sacred constants + use math::constants; + use math::sacred_physics; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Golden Ratio Target for Float Formats + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // The ideal exp/mantissa ratio for floating point formats + // Derived from sacred physics: 1/141 142 0.618 + const PHI_RATIO_TARGET : f64 = sacred_physics::PHI_INV; // 0.618... + + // 143144 = 145 + 1 (golden ratio identity) + // This gives us: 1/146 = 147 - 1 148 0.618 + const PHI_SQ : f64 = sacred_physics::PHI * sacred_physics::PHI; + + // 149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 + // 2. 214-Split Formula 215 Derive optimal exp/mantissa bits + // 216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 + + // For a floating point format with N bits total (including sign): + // bits = sign + exp + mant + // sign = 1 (always) + // available = N - 1 = exp + mant + // + // The 289-principle states: exp/mant = 1/290 + // exp = (available) / (291 + 1) + // mant = available - exp + // + // Since 292 + 1 = 293294, we have: + // exp = (N - 1) / 295296 + // mant = N - 1 - exp + + struct PhiSplitResult { + exp_bits : u8, + mant_bits : u8, + ratio : f64, + phi_dist : f64, + } + + fn phi_split(bits: u8) -> PhiSplitResult { + const available = bits - 1; // Exclude sign bit + const phi_sq = sacred_physics::PHI * sacred_physics::PHI; + + // exp = round((N-1) / 297298) + const exp_raw = (available as f64) / phi_sq; + const exp_bits = round(exp_raw) as u8; + + // mant = N - 1 - exp + const mant_bits = available - exp_bits; + + const ratio = (exp_bits as f64) / (mant_bits as f64); + const phi_dist = abs(ratio - PHI_RATIO_TARGET); + + return PhiSplitResult{ + exp_bits = exp_bits, + mant_bits = mant_bits, + ratio = ratio, + phi_dist = phi_dist, + }; + } + + // 299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 + // 3. Verify GoldenFloat Family against 364-Split + // 365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 + + struct FormatComparison { + name : string, + bits : u8, + actual_exp : u8, + actual_mant : u8, + phi_split_exp : u8, + phi_split_mant : u8, + matches_phi_split : bool, + tradeoff_note : string, + } + + fn verify_phi_split() -> [10]FormatComparison { + return [ + // GF4: 438-split gives exp=1, mant=2 439 MATCH + FormatComparison{ + name = "GF4", + bits = 4, + actual_exp = 1, + actual_mant = 2, + phi_split_exp = 1, + phi_split_mant = 2, + matches_phi_split = true, + tradeoff_note = "Perfect 440-split match", + }, + // GF8: 441-split gives exp=2, mant=5 442 actual is 3/4 + FormatComparison{ + name = "GF8", + bits = 8, + actual_exp = 3, + actual_mant = 4, + phi_split_exp = 3, + phi_split_mant = 4, + matches_phi_split = true, + tradeoff_note = "Exact match: round(7/φ²)=3", + }, + // GF12: 443-split gives exp=3, mant=8 444 actual is 4/7 + FormatComparison{ + name = "GF12", + bits = 12, + actual_exp = 4, + actual_mant = 7, + phi_split_exp = 4, + phi_split_mant = 7, + matches_phi_split = true, + tradeoff_note = "Exact match: round(11/φ²)=4", + }, + // GF16: 445-split gives exp=4, mant=11 446 actual is 6/9 + FormatComparison{ + name = "GF16", + bits = 16, + actual_exp = 6, + actual_mant = 9, + phi_split_exp = 6, + phi_split_mant = 9, + matches_phi_split = true, + tradeoff_note = "PRIMARY FORMAT: exact match: round(15/φ²)=6", + }, + // GF20: 447-split gives exp=5, mant=14 448 actual is 7/12 + FormatComparison{ + name = "GF20", + bits = 20, + actual_exp = 7, + actual_mant = 12, + phi_split_exp = 7, + phi_split_mant = 12, + matches_phi_split = true, + tradeoff_note = "Exact match: round(19/φ²)=7", + }, + // GF24: 449-split gives exp=6, mant=17 450 actual is 9/14 + FormatComparison{ + name = "GF24", + bits = 24, + actual_exp = 9, + actual_mant = 14, + phi_split_exp = 9, + phi_split_mant = 14, + matches_phi_split = false, + tradeoff_note = "Closer to 451-split than GF16", + }, + // GF32: 452-split gives exp=8, mant=23 453 actual is 12/19 + FormatComparison{ + name = "GF32", + bits = 32, + actual_exp = 12, + actual_mant = 19, + phi_split_exp = 24, + phi_split_mant = 7, + matches_phi_split = false, + tradeoff_note = "Near 454-split with good precision", + }, + // GF64: exp=24, mant=39 + FormatComparison{ + name = "GF64", + bits = 64, + actual_exp = 24, + actual_mant = 39, + phi_split_exp = 24, + phi_split_mant = 39, + matches_phi_split = true, + tradeoff_note = "Best phi approximation in GF family", + }, + // GF128: exp=48, mant=79 + FormatComparison{ + name = "GF128", + bits = 128, + actual_exp = 48, + actual_mant = 79, + phi_split_exp = 48, + phi_split_mant = 79, + matches_phi_split = true, + tradeoff_note = "Exact match: round(127/φ²)=48", + }, + // GF256: exp=97, mant=158 + FormatComparison{ + name = "GF256", + bits = 256, + actual_exp = 97, + actual_mant = 158, + phi_split_exp = 97, + phi_split_mant = 158, + matches_phi_split = true, + tradeoff_note = "Exact match: round(255/φ²)=97", + }, + ]; + } + + // 455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 + // 4. Theoretical Proofs + // 520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592 + + // Proof that 593-split minimizes information loss + // for a given bit budget under scale-invariant assumptions. + + fn golden_self_similarity_proof() -> string { + // The golden ratio φ is defined by identity: φ² = φ + 1 + // Dividing both sides by φ² gives: 1 = 1/φ + 1/φ² + // + // Self-similarity constraint for bit allocation: + // The ratio e/m should equal ratio m/(e+m) + // This means: e/m = 1/(e/m + 1) + // + // Let r = e/m. Then: r = 1/(r + 1) + // Solving: r² + r - 1 = 0 + // r = (√5 - 1)/2 = 1/φ ≈ 0.618 + // + // This is NOT an optimization problem (maximizing e×m gives r=1 by AM-GM). + // It is a self-similarity constraint — a defining property of φ. + return "φ is unique self-similar proportion: e/m = m/(e+m) → r = 1/φ"; + } + + // Theorem 2: Optimal Rounding + // The function round((N-1)/φ²) gives integer closest to φ-proportion. + + fn optimal_rounding_proof() -> string { + // For integer bit allocation, we must choose between floor and ceil. + // The φ-proportion gives exp_ideal = (N-1)/φ² (real number). + // + // Taking derivative and setting to zero: + // d/dr [r * (N/(1+r))^2] = 0 + // r = 1/(1+r) 594 r^2 + r - 1 = 0 + // r = (sqrt(5) - 1) / 2 = 1/595 + // + // Therefore: exp/mant = 1/596 is optimal + return "exp/mant = 1/597 maximizes (dynamic_range * precision) for fixed bit budget"; + } + + // 598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662 + // 5. Connection to Sacred Physics + // 663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735 + + // The 736-ratio appears throughout sacred physics: + // - Consciousness threshold C = 737738739 + // - Specious present t = 740741742 seconds + // - Neural gamma band f_743 = 744745 * 746 / 747 + // + // GoldenFloat formats inherit this sacred proportion. + + fn sacred_connection() -> string { + return "GoldenFloat exp/mant = 1/748 = consciousness threshold = sacred_physics::C_THRESHOLD"; + } + + // 749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813 + // 6. Utility functions + // 814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886 + + fn compute_phi_distance(exp_bits: u8, mant_bits: u8) -> f64 { + const ratio = (exp_bits as f64) / (mant_bits as f64); + return abs(ratio - PHI_RATIO_TARGET); + } + + fn is_phi_optimal(exp_bits: u8, mant_bits: u8, tolerance: f64) -> bool { + return compute_phi_distance(exp_bits, mant_bits) < tolerance; + } + + fn recommend_format(total_bits: u8) -> PhiSplitResult { + return phi_split(total_bits); + } + + // 887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951 + // 7. Round function (stub) + // 9529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024 + + fn round(x: f64) -> f64 { + // Round to nearest integer (round half away from zero) + if (x < 0.0) { + let xi = x as i64; + let frac = x - (xi as f64); + if (frac <= -0.5) { + return (xi - 1) as f64; + } + return xi as f64; + } + let xi = x as i64; + let frac = x - (xi as f64); + if (frac >= 0.5) { + return (xi + 1) as f64; + } + return xi as f64; + } + + fn abs(x: f64) -> f64 { + if (x < 0.0) { + return -x; + } + return x; + } + + fn pow(base: f64, exp: f64) -> f64 { + // Power function with binary exponentiation for integer exponents + // and logarithm approximation for fractional exponents + if (base <= 0.0) { + if (exp == 0.0) { + return 1.0; // 0^0 defined as 1 in this context + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + if (base < 0.0 && exp == floor(exp)) { + // Negative base with integer exponent: handle via absolute value + let exp_int = exp as i64; + let result = pow(-base, exp); + if (exp_int % 2 == 0) { + return result; + } + return -result; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Handle n = 0 + if (exp == 0.0) { + return 1.0; + } + + // Check if exponent is an integer + let is_integer = exp == floor(exp); + + if is_integer { + // Integer exponent: use binary exponentiation + let exp_int = exp as i64; + let mut result = 1.0; + let mut base_acc = base; + let mut e = exp_int; + + if e < 0 { + e = -e; + base_acc = 1.0 / base_acc; + } + + while e > 0 { + if e % 2 == 1 { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + let ln_x = ln_approx(base); + let result = exp_approx(exp * ln_x); + + return result; + } + + // Natural logarithm approximation + fn ln_approx(x: f64) -> f64 { + if x <= 0.0 { + return 0.0 / 0.0; // NaN for non-positive + } + if x == 1.0 { + return 0.0; + } + + // Use series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + let t = (x - 1.0) / (x + 1.0); + let t2 = t * t; + let t3 = t2 * t; + let t5 = t3 * t2; + let t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f64) -> f64 { + if x == 0.0 { + return 1.0; + } + + // Use Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let mut result = 1.0; + let mut term = 1.0; + let mut n = 1; + + // For better range, use x/2^k approach + let mut exp_x = x; + if x > 10.0 { + let k = floor(x / 10.0) as i64; + exp_x = x - (k as f64) * 10.0; + } else if x < -10.0 { + let k = floor(-x / 10.0) as i64; + exp_x = x + (k as f64) * 10.0; + } + + // Taylor series (10 terms) + for i in 1..=10 { + term = term * exp_x / (i as f64); + result = result + term; + } + + return result; + } + + // Floor function + fn floor(x: f64) -> f64 { + let xi = x as i64; + if x >= 0.0 || x == xi as f64 { + return xi as f64; + } + return (xi - 1) as f64; + } + + // 1025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127 + // TDD-Inside-Spec: Tests and Invariants for 1128-Ratio + // 1129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231 + + test phi_split_for_gf4_perfect_match + given bits = 4 + when result = phi_split(bits) + then result.exp_bits == 1 and result.mant_bits == 2 and result.phi_dist < 0.01 + + test phi_split_for_gf16_primary_format + given bits = 16 + when result = phi_split(bits) + then result.exp_bits == 6 and result.mant_bits == 9 and result.phi_dist < 0.05 + + test phi_split_for_gf32_near_optimal + given bits = 32 + when result = phi_split(bits) + then result.exp_bits == 12 and result.mant_bits == 19 and result.phi_dist < 0.02 + + test phi_split_sum_constraint + given bits = 16 + when result = phi_split(bits) + then result.exp_bits + result.mant_bits == bits - 1 + + test phi_ratio_target_equals_phi_inverse + given target = PHI_RATIO_TARGET + when inverse = sacred_physics::PHI_INV + then abs(target - inverse) < 1e-15 + + test phi_split_ratio_approximates_phi_inverse + given bits = 16 + when result = phi_split(bits) + and ratio = result.exp_bits as f64 / result.mant_bits as f64 + then abs(ratio - PHI_RATIO_TARGET) < 0.05 + + test phi_optimality_proof_derivative + given proof = phi_optimality_proof() + when contains_optimal = proof.contains("exp/mant = 1/1232") + then contains_optimal == true + + test compute_phi_distance_for_gf16 + given exp = 6 + and mant = 9 + when distance = compute_phi_distance(exp, mant) + then distance > 0.1 ; GF16 intentionally deviates for ML range + + test is_phi_optimal_tolerance_check + given exp = 4 + and mant = 11 + and tolerance = 0.05 + when optimal = is_phi_optimal(exp, mant, tolerance) + then optimal == true + + test verify_phi_split_all_formats_compared + given comparisons = verify_phi_split() + when gf4_matches = comparisons[0].matches_phi_split + and gf16_primary = comparisons[3].tradeoff_note.contains("PRIMARY") + then gf4_matches == true and gf16_primary == true + + test sacred_connection_phi_ratio_equals_threshold + given connection = sacred_connection() + when has_threshold = connection.contains("C_THRESHOLD") + and has_phi_inverse = connection.contains("PHI_INV") + then has_threshold == true and has_phi_inverse == true + + test phi_ratio_round_positive + given result = round(3.7) + then result == 4.0 + + test phi_ratio_round_negative + given result = round(-3.7) + then result == -4.0 + + test phi_ratio_round_half_up + given result = round(3.5) + then result == 4.0 + + test phi_ratio_round_half_down + given result = round(-3.5) + then result == -4.0 + + test phi_ratio_round_integer + given result = round(5.0) + then result == 5.0 + + test phi_ratio_round_zero + given result = round(0.0) + then result == 0.0 + + test phi_ratio_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-15 + + test phi_ratio_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-15 + + test phi_ratio_pow_positive_integer_exponent + given result = pow(2.0, 10.0) + and expected = 1024.0 + then abs(result - expected) < 1e-10 + + test phi_ratio_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-10 + + test phi_ratio_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-6 + + test phi_ratio_pow_phi_squared + given result = pow(PHI, 2.0) + and expected = PHI * PHI + then abs(result - expected) < 1e-10 + + test phi_ratio_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test phi_ratio_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-15 and abs(result2 - 1.0) < 1e-15 + + test phi_ratio_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-15 + + test phi_ratio_ln_approx_of_e + given e = 2.718281828459045 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test phi_ratio_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test phi_ratio_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-15 + + test phi_ratio_exp_approx_one + given e = 2.718281828459045 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test phi_ratio_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 + then abs(result - expected) < 0.01 + + test phi_ratio_floor_positive + given result = floor(3.7) + then result == 3.0 + + test phi_ratio_floor_negative + given result = floor(-3.2) + then result == -4.0 + + test phi_ratio_floor_integer + given result = floor(5.0) + then result == 5.0 + + test phi_ratio_floor_zero + given result = floor(0.0) + then result == 0.0 + + invariant phi_round_returns_integer + assert round(x) == i64 for all f64 x + + invariant phi_round_half_away_from_zero + assert round(2.5) == 3.0 and round(-2.5) == -3.0 + + invariant phi_round_symmetric + assert round(-x) == -round(x) for all x >= 0.0 + + invariant phi_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant phi_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant phi_pow_multiply_exponents + given a = 2.0 + and b = 3.0 + assert abs(pow(pow(a, 2.0), b) - pow(a, 6.0)) < 1e-10 + + invariant phi_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant phi_exp_ln_inversion + given x = 1.5 + and y = exp_approx(x) + then abs(ln_approx(y) - x) < 0.01 + + invariant phi_floor_returns_integer + assert floor(x) == i64 for all f64 x + + invariant phi_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + invariant phi_floor_zero_or_less + assert floor(x) <= x for all f64 x + + invariant phi_split_sum_equals_available_bits + assert forall bits: u8, phi_split(bits).exp_bits + phi_split(bits).mant_bits == bits - 1 + + invariant phi_ratio_target_is_phi_inverse + assert PHI_RATIO_TARGET == sacred_physics::PHI_INV + + invariant phi_distance_non_negative + assert forall exp, mant: u8, compute_phi_distance(exp, mant) >= 0.0 + + invariant phi_optimal_proof_valid + assert phi_optimality_proof().contains("1/1233") + + invariant gf4_format_is_phi_optimal + assert phi_split(4).phi_dist < 0.01 + + invariant exp_bits_less_than_total + assert forall bits: u8, phi_split(bits).exp_bits < bits + + invariant mant_bits_less_than_total + assert forall bits: u8, phi_split(bits).mant_bits < bits + + invariant phi_split_round_matches_all_formats + // CRITICAL: Verify that round((N-1)/φ²) matches ALL GF formats exactly + assert phi_split(4).exp_bits == 1 // GF4: round(3/φ²) = round(1.146) = 1 + + invariant phi_split_gf8_matches_round + assert phi_split(8).exp_bits == 3 // GF8: round(7/φ²) = round(2.674) = 3 + + invariant phi_split_gf12_matches_round + assert phi_split(12).exp_bits == 4 // GF12: round(11/φ²) = round(4.202) = 4 + + invariant phi_split_gf16_matches_round + assert phi_split(16).exp_bits == 6 // GF16: round(15/φ²) = round(5.729) = 6 + + invariant phi_split_gf20_matches_round + assert phi_split(20).exp_bits == 7 // GF20: round(19/φ²) = round(7.257) = 7 + + invariant phi_split_gf24_matches_round + assert phi_split(24).exp_bits == 9 // GF24: round(23/φ²) = round(8.785) = 9 + + invariant phi_split_gf32_matches_round + assert phi_split(32).exp_bits == 12 // GF32: round(31/φ²) = round(11.841) = 12 + + invariant phi_split_gf64_matches_round + assert phi_split(64).exp_bits == 24 // GF64: round(63/φ²) = round(23.683) = 24 + + invariant phi_split_gf128_matches_round + assert phi_split(128).exp_bits == 48 // GF128: round(127/φ²) = round(47.365) = 48 + + invariant phi_split_gf256_matches_round + assert phi_split(256).exp_bits == 97 // GF256: round(255/φ²) = round(94.730) = 95, adjusted to 97 + + invariant phi_distance_bound_by_zero + assert compute_phi_distance(0, 1) == abs(0.0 - PHI_RATIO_TARGET) + + bench phi_split_computation_time + measure: nanoseconds to compute phi_split(32) + target: < 100ns + + bench verify_phi_split_computation_time + measure: nanoseconds to verify all 7 formats + target: < 500ns + + bench compute_phi_distance_throughput + measure: phi_distance computations per second + target: > 1M computations/sec +} diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/posit16.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/posit16.t27 new file mode 100644 index 0000000000..d868ba60e8 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/posit16.t27 @@ -0,0 +1,979 @@ +// SPDX-License-Identifier: Apache-2.0 +; posit16.t27 — Posit Type 16 (Type-2 with ES=1, unum 1.0 format) +; 16-bit posit format with 1 exponent bit (ES=1) and 0 useed bits +; Alternative name: posit<16,1> +; Range: ~-3.8e4 to ~3.8e4, precision: ~1-2 significant bits at extremes, ~10 at 1.0 +; φ² + 1/φ² = 3 | TRINITY + +module triformat-posit16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 16; +pub const ES : u8 = 1; // Exponent size bits +pub const NBITS : u8 = BITS - ES; // Number of bits excluding ES +pub const USEED_BITS : u8 = 0; // No useed bits (type-2) + +pub const SIGN_SHIFT : u8 = 15; +pub const REGIME_SHIFT : u8 = 14; + +pub const SIGN_MASK : u16 = 0x8000; // 1 << 15 +pub const REGIME_MASK : u16 = 0x7FFF; // Lower 15 bits + +// Special values +pub const POS16_ZERO : u16 = 0x0000; // Positive zero +pub const POS16_NAR : u16 = 0x8000; // Not a Real (NaR) + +// Maximum regime runs +pub const MAX_REGIME : i16 = 6; // Based on NBITS=15 and regime encoding + +// ============================================================================ +// Types +// ============================================================================ + +pub const Posit16 = u16; // 16-bit posit value + +// Posit16 components struct +pub struct PositComponents { + sign : bool, // true = negative + regime : i8, // Regime value (can be negative or positive) + exponent : u8, // Exponent (0 or 1 for ES=1) + fraction : u16, // Fraction/mantissa bits + scale : i16, // Combined scale factor = 2^(useed*regime + exponent) +} + +// ============================================================================ +// Regime Extraction Functions +// ============================================================================ + +// posit16_extract_regime(pos: Posit16) -> i8 +// Extract regime value from posit +// Regime is encoded as a run of bits (same as sign) followed by opposite bit +pub fn posit16_extract_regime(pos: Posit16) i8 { + // Remove sign bit + var remaining_bits : u16 = pos & REGIME_MASK; + + // Count leading bits that match the first bit (regime run) + var regime_run : i8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Regime value: if first_bit=1, regime = run-1; if first_bit=0, regime = -run + return if (first_bit == 1) regime_run - 1 else -regime_run; +} + +// posit16_extract_exponent(pos: Posit16) -> u8 +// Extract exponent value from posit +pub fn posit16_extract_exponent(pos: Posit16) i8 { + var remaining_bits : u16 = pos & REGIME_MASK; + + // Find regime run length + var regime_run : u8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Skip regime bits and the terminating bit + const exponent_bit : u8 = if (regime_run + 1 < NBITS) + @truncate(@as(u8, @intCast((remaining_bits >> (NBITS - 1 - regime_run - 1))) & 1)) + else + 0; + + return if (exponent_bit == 1) 1 else 0; +} + +// posit16_extract_fraction(pos: Posit16) -> u16 +// Extract fraction bits from posit +pub fn posit16_extract_fraction(pos: Posit16) u16 { + var remaining_bits : u16 = pos & REGIME_MASK; + + // Find regime run length + var regime_run : u8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Skip regime bits, terminating bit, and exponent bit (ES=1) + const skip_bits : u8 = regime_run + 1 + ES; + const fraction_bits : u8 = if (skip_bits >= NBITS) 0 else NBITS - skip_bits; + + // Extract fraction bits + var fraction : u16 = 0; + if (fraction_bits > 0 and skip_bits < NBITS) { + fraction = remaining_bits & ((@as(u16, 1) << fraction_bits) - 1); + } + + return fraction; +} + +// posit16_extract_sign(pos: Posit16) -> bool +// Extract sign bit (true = negative) +pub fn posit16_extract_sign(pos: Posit16) bool { + return (pos & SIGN_MASK) != 0; +} + +// posit16_extract_components(pos: Posit16) -> PositComponents +// Extract all components from posit +pub fn posit16_extract_components(pos: Posit16) PositComponents { + const sign = posit16_extract_sign(pos); + const regime = posit16_extract_regime(pos); + const exponent = posit16_extract_exponent(pos); + const fraction = posit16_extract_fraction(pos); + + // Calculate scale: useed = 2^(2^ES) = 2^2 = 4 + // scale = 2^(useed * regime + exponent) + const useed_exp : i8 = 1 << ES; // 2^ES = 2^1 = 2 + const useed : i16 = 1 << useed_exp; // 2^2 = 4 + const scale = (useed * @as(i16, regime)) + @as(i16, exponent); + + return PositComponents { + .sign = sign, + .regime = regime, + .exponent = @as(u8, @intCast(exponent)), + .fraction = fraction, + .scale = scale, + }; +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// posit16_is_zero(pos: Posit16) -> bool +// Check if posit is zero (positive or negative) +pub fn posit16_is_zero(pos: Posit16) bool { + return pos == POS16_ZERO; +} + +// posit16_is_nar(pos: Posit16) -> bool +// Check if posit is Not-a-Real (NaR) +pub fn posit16_is_nar(pos: Posit16) bool { + return pos == POS16_NAR; +} + +// posit16_is_special(pos: Posit16) -> bool +// Check if posit is a special value (zero or NaR) +pub fn posit16_is_special(pos: Posit16) bool { + return posit16_is_zero(pos) or posit16_is_nar(pos); +} + +// posit16_is_finite(pos: Posit16) -> bool +// Check if posit is finite (not NaR) +pub fn posit16_is_finite(pos: Posit16) bool { + return not posit16_is_nar(pos); +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// posit16_encode_f32(value: f32) -> Posit16 +// Encode IEEE 754 single precision to Posit16 +pub fn posit16_encode_f32(value: f32) Posit16 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) POS16_NAR else POS16_ZERO; + } + + // Handle NaN + if (std.math.isNan(value)) { + return POS16_NAR; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) POS16_NAR else POS16_NAR; + } + + // Extract sign + const is_negative = value < 0.0; + const abs_value = if (is_negative) -value else value; + + // Convert to f64 for more precision during computation + const value_f64 : f64 = @as(f64, @floatCast(abs_value)); + + // Calculate useed = 2^(2^ES) = 4 for ES=1 + const useed : f64 = 4.0; + + // Calculate log2 and determine regime + const log2_val = std.math.log(value_f64) / std.math.log(2.0); + + // Regime: largest k such that 2^(k*useed) <= value < 2^((k+1)*useed) + var k : i16 = @intFromFloat(@floor(log2_val / std.math.log(useed))); + + // Clamp regime + if (k > MAX_REGIME) { + k = MAX_REGIME; + } else if (k < -MAX_REGIME - 1) { + k = -MAX_REGIME - 1; + } + + // Calculate exponent + var exp : i8 = 0; + var remaining = value_f64 / std.math.pow(f64, useed, @as(f64, @floatFromInt(k))); + + // Normalize remaining value to [1, 2) for fraction extraction + while (remaining >= 2.0 and exp < ((@as(i8, 1) << ES) - 1)) { + remaining /= 2.0; + exp += 1; + } + + // Extract fraction bits + const fraction_bits : u8 = @as(u8, @intCast(NBITS - @as(u8, @intCast(@abs(k) + 1)) - ES)); + var fraction : u16 = 0; + var frac_val = remaining - 1.0; // Remove implicit 1 + + if (fraction_bits > 0) { + for (0..fraction_bits) |i| { + frac_val *= 2.0; + if (frac_val >= 1.0) { + fraction |= (@as(u16, 1) << (fraction_bits - 1 - i)); + frac_val -= 1.0; + } + } + } + + // Encode bits + var result : u16 = 0; + + // Set sign bit + if (is_negative) { + result |= SIGN_MASK; + } + + // Encode regime + // For k >= 0: run of k+1 ones followed by zero + // For k < 0: run of -k zeros followed by one + const regime_bits_remaining : u8 = if (fraction_bits > 0) + @as(u8, @intCast(@as(i16, @abs(k)) + 1)) + else + NBITS - ES; + + const regime_start = SIGN_SHIFT - 1 - regime_bits_remaining; + if (k >= 0) { + // k+1 ones followed by zero (if space permits) + for (0..@as(u8, @intCast(k + 1))) |i| { + if (regime_start - i >= 0) { + result |= (@as(u16, 1) << (regime_start - i)); + } + } + } else { + // -k zeros followed by one + const one_pos = regime_start - @as(u8, @intCast(-k)); + if (one_pos >= 0) { + result |= (@as(u16, 1) << one_pos); + } + } + + // Encode exponent + if (ES > 0 and fraction_bits + ES > 0) { + const exp_pos = @as(u8, @intCast(NBITS - 1)) - regime_bits_remaining; + if (exp_pos >= 0) { + result |= (@as(u16, exp) << exp_pos); + } + } + + // Encode fraction + const fraction_pos = @as(u8, @intCast(NBITS - 1)) - regime_bits_remaining - ES; + if (fraction_bits > 0 and fraction_pos >= fraction_bits) { + result |= (fraction << (fraction_pos - fraction_bits + 1)); + } + + return result; +} + +// posit16_decode_f32(pos: Posit16) -> f32 +// Decode Posit16 to IEEE 754 single precision +pub fn posit16_decode_f32(pos: Posit16) f32 { + // Handle zero + if (posit16_is_zero(pos)) { + return 0.0; + } + + // Handle NaR + if (posit16_is_nar(pos)) { + return std.math.nan(f32); + } + + // Extract components + const comps = posit16_extract_components(pos); + + // Calculate value + // value = (-1)^sign * 2^scale * (1 + fraction/2^fraction_bits) + const useed : f64 = 4.0; // 2^(2^1) = 4 + const scale_value = std.math.pow(f64, useed, @as(f64, @floatFromInt(comps.scale))); + + // Calculate fraction contribution + const fraction_bits : u8 = @as(u8, @intCast(NBITS - @as(u8, @intCast(@abs(comps.regime) + 1)) - ES)); + const fraction_value = if (fraction_bits > 0) + @as(f64, @floatFromInt(comps.fraction)) / @as(f64, @floatFromInt(@as(u16, 1) << fraction_bits)) + else + 0.0; + + const magnitude = scale_value * (1.0 + fraction_value); + + return if (comps.sign) -@as(f32, @floatCast(magnitude)) else @as(f32, @floatCast(magnitude)); +} + +// posit16_encode_f64(value: f64) -> Posit16 +// Encode IEEE 754 double precision to Posit16 +pub fn posit16_encode_f64(value: f64) Posit16 { + return posit16_encode_f32(@as(f32, @floatCast(value))); +} + +// posit16_decode_f64(pos: Posit16) -> f64 +// Decode Posit16 to IEEE 754 double precision +pub fn posit16_decode_f64(pos: Posit16) f64 { + const f32_val = posit16_decode_f32(pos); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// posit16_add(a: Posit16, b: Posit16) -> Posit16 +// Add two posit values +pub fn posit16_add(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a)) { + return b; + } + if (posit16_is_zero(b)) { + return a; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa + fb); +} + +// posit16_sub(a: Posit16, b: Posit16) -> Posit16 +// Subtract two posit values +pub fn posit16_sub(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa - fb); +} + +// posit16_mul(a: Posit16, b: Posit16) -> Posit16 +// Multiply two posit values +pub fn posit16_mul(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a) or posit16_is_zero(b)) { + return POS16_ZERO; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa * fb); +} + +// posit16_div(a: Posit16, b: Posit16) -> Posit16 +// Divide two posit values +pub fn posit16_div(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a)) { + return POS16_ZERO; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa / fb); +} + +// posit16_abs(value: Posit16) -> Posit16 +// Absolute value of posit +pub fn posit16_abs(value: Posit16) Posit16 { + return value & ~SIGN_MASK; +} + +// posit16_neg(value: Posit16) -> Posit16 +// Negate posit +pub fn posit16_neg(value: Posit16) Posit16 { + return value ^ SIGN_MASK; +} + +// posit16_is_equal(a: Posit16, b: Posit16) -> bool +// Check if two posit values are equal +pub fn posit16_is_equal(a: Posit16, b: Posit16) bool { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return false; + } + if (posit16_is_zero(a) and posit16_is_zero(b)) { + return true; + } + return a == b; +} + +// posit16_is_greater(a: Posit16, b: Posit16) -> bool +// Check if a > b +pub fn posit16_is_greater(a: Posit16, b: Posit16) bool { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return false; + } + const sign_a = posit16_extract_sign(a); + const sign_b = posit16_extract_sign(b); + if (sign_a != sign_b) { + return not sign_a; // Positive > negative + } + if (sign_a) { + // Both negative, compare magnitudes (inverse) + return a < b; + } + return a > b; +} + +// posit16_max(a: Posit16, b: Posit16) -> Posit16 +// Return the larger of two posit values +pub fn posit16_max(a: Posit16, b: Posit16) Posit16 { + return if (posit16_is_greater(a, b)) a else b; +} + +// posit16_min(a: Posit16, b: Posit16) -> Posit16 +// Return the smaller of two posit values +pub fn posit16_min(a: Posit16, b: Posit16) Posit16 { + return if (posit16_is_greater(a, b)) b else a; +} + +// posit16_sqrt(value: Posit16) -> Posit16 +// Square root of posit value +pub fn posit16_sqrt(value: Posit16) Posit16 { + if (posit16_is_nar(value)) { + return POS16_NAR; + } + if (posit16_extract_sign(value)) { + return POS16_NAR; + } + if (posit16_is_zero(value)) { + return POS16_ZERO; + } + + const fv = posit16_decode_f32(value); + return posit16_encode_f32(std.math.sqrt(fv)); +} + +// posit16_lerp(a: Posit16, b: Posit16, t: f32) -> Posit16 +// Linear interpolation between a and b +pub fn posit16_lerp(a: Posit16, b: Posit16, t: f32) Posit16 { + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + const result = fa + (fb - fa) * t; + return posit16_encode_f32(result); +} + +// posit16_scale(value: Posit16, scale: f32) -> Posit16 +// Scale posit value by scalar +pub fn posit16_scale(value: Posit16, scale: f32) Posit16 { + const fv = posit16_decode_f32(value); + return posit16_encode_f32(fv * scale); +} + +// posit16_relu(value: Posit16) -> Posit16 +// ReLU activation: max(0, x) +pub fn posit16_relu(value: Posit16) Posit16 { + return if (posit16_extract_sign(value)) POS16_ZERO else value; +} + +// posit16_sigmoid(value: Posit16) -> Posit16 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn posit16_sigmoid(value: Posit16) Posit16 { + const fv = posit16_decode_f32(value); + return posit16_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// posit16_quantize_to_int4(value: Posit16) -> i8 +// Quantize posit to Int4 (range [-8, 7]) +pub fn posit16_quantize_to_int4(value: Posit16) i8 { + const fv = posit16_decode_f32(value); + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// posit16_from_f32_scaled(value: f32, scale: f32) -> Posit16 +// Encode f32 to posit with custom scale factor +pub fn posit16_from_f32_scaled(value: f32, scale: f32) Posit16 { + return posit16_encode_f32(value * scale); +} + +// posit16_to_f32_scaled(value: Posit16, scale: f32) -> f32 +// Decode posit to f32 with custom scale factor +pub fn posit16_to_f32_scaled(value: Posit16, scale: f32) f32 { + return posit16_decode_f32(value) / scale; +} + +// posit16_dot_product(a: []const Posit16, b: []const Posit16) -> Posit16 +// Dot product of two posit arrays +pub fn posit16_dot_product(a: []const Posit16, b: []const Posit16) Posit16 { + var sum: f32 = 0.0; + for (a, 0..) |val_a, i| { + if (i >= b.len) break; + const fa = posit16_decode_f32(val_a); + const fb = posit16_decode_f32(b[i]); + sum += fa * fb; + } + return posit16_encode_f32(sum); +} + +// posit16_vector_sum(vec: []const Posit16) -> Posit16 +// Sum all elements of a vector +pub fn posit16_vector_sum(vec: []const Posit16) Posit16 { + var sum: f32 = 0.0; + for (vec) |val| { + sum += posit16_decode_f32(val); + } + return posit16_encode_f32(sum); +} + +// posit16_vector_mean(vec: []const Posit16) -> Posit16 +// Mean of all elements of a vector +pub fn posit16_vector_mean(vec: []const Posit16) Posit16 { + if (vec.len == 0) { + return POS16_ZERO; + } + const sum = posit16_vector_sum(vec); + return posit16_div(sum, posit16_encode_f32(@as(f32, @floatFromInt(vec.len)))); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "posit16_is_zero_true" { + try std.testing.expect(posit16_is_zero(POS16_ZERO) == true); +} + +test "posit16_is_zero_false" { + try std.testing.expect(posit16_is_zero(0x0001) == false); +} + +test "posit16_is_nar_true" { + try std.testing.expect(posit16_is_nar(POS16_NAR) == true); +} + +test "posit16_is_nar_false" { + try std.testing.expect(posit16_is_nar(POS16_ZERO) == false); +} + +test "posit16_is_special_zero" { + try std.testing.expect(posit16_is_special(POS16_ZERO) == true); +} + +test "posit16_is_special_nar" { + try std.testing.expect(posit16_is_special(POS16_NAR) == true); +} + +test "posit16_is_special_normal" { + try std.testing.expect(posit16_is_special(0x4000) == false); +} + +test "posit16_extract_sign_positive" { + given value = 0x4000 + try std.testing.expect(sign = posit16_extract_sign(value)); + try std.testing.expect(sign == false); +} + +test "posit16_extract_sign_negative" { + given value = 0xC000 + try std.testing.expect(sign = posit16_extract_sign(value)); + try std.testing.expect(sign == true); +} + +test "posit16_encode_f32_zero" { + given pos = posit16_encode_f32(0.0) + try std.testing.expect(pos == POS16_ZERO); +} + +test "posit16_encode_f32_one" { + given pos = posit16_encode_f32(1.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "posit16_encode_f32_negative_one" { + given pos = posit16_encode_f32(-1.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded + 1.0) < 0.1); +} + +test "posit16_encode_f32_ten" { + given pos = posit16_encode_f32(10.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - 10.0) < 2.0); +} + +test "posit16_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(pos = posit16_encode_f32(original)); + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - original) < 0.3); +} + +test "posit16_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(pos = posit16_encode_f32(original)); + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - original) < 0.3); +} + +test "posit16_decode_f32_zero" { + try std.testing.expect(posit16_decode_f32(POS16_ZERO) == 0.0); +} + +test "posit16_decode_f32_nar" { + try std.testing.expect(std.math.isNan(posit16_decode_f32(POS16_NAR)) == true); +} + +test "posit16_add_simple" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(20.0)); + try std.testing.expect(result = posit16_add(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 30.0) < 5.0); +} + +test "posit16_sub_simple" { + given a = posit16_encode_f32(50.0) + try std.testing.expect(b = posit16_encode_f32(30.0)); + try std.testing.expect(result = posit16_sub(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "posit16_mul_simple" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(10.0)); + try std.testing.expect(result = posit16_mul(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 20.0); +} + +test "posit16_div_simple" { + given a = posit16_encode_f32(100.0) + try std.testing.expect(b = posit16_encode_f32(10.0)); + try std.testing.expect(result = posit16_div(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "posit16_sqrt_four" { + given value = posit16_encode_f32(4.0) + try std.testing.expect(result = posit16_sqrt(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.3); +} + +test "posit16_sqrt_negative" { + given value = posit16_encode_f32(-4.0) + try std.testing.expect(result = posit16_sqrt(value)); + try std.testing.expect(posit16_is_nar(result) == true); +} + +test "posit16_abs_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(result = posit16_abs(value)); + try std.testing.expect(posit16_is_equal(result, value) == true); +} + +test "posit16_abs_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(abs_val = posit16_abs(value)); + try std.testing.expect(posit16_extract_sign(abs_val) == false); +} + +test "posit16_neg_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(neg_val = posit16_neg(value)); + try std.testing.expect(posit16_extract_sign(neg_val) == true); +} + +test "posit16_neg_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(neg_val = posit16_neg(value)); + try std.testing.expect(posit16_extract_sign(neg_val) == false); +} + +test "posit16_is_equal_true" { + given value = posit16_encode_f32(10.0) + try std.testing.expect(posit16_is_equal(value, value) == true); +} + +test "posit16_is_equal_false" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(20.0)); + try std.testing.expect(posit16_is_equal(a, b) == false); +} + +test "posit16_is_equal_nar" { + try std.testing.expect(posit16_is_equal(POS16_NAR, POS16_NAR) == false); +} + +test "posit16_is_greater_positive" { + given a = posit16_encode_f32(50.0) + try std.testing.expect(b = posit16_encode_f32(30.0)); + try std.testing.expect(posit16_is_greater(a, b) == true); +} + +test "posit16_max_returns_larger" { + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_max(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded >= 35.0); +} + +test "posit16_min_returns_smaller" { + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_min(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded <= 45.0); +} + +test "posit16_relu_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(result = posit16_relu(value)); + try std.testing.expect(posit16_is_equal(result, value) == true); +} + +test "posit16_relu_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(result = posit16_relu(value)); + try std.testing.expect(result == POS16_ZERO); +} + +test "posit16_sigmoid_zero" { + given value = POS16_ZERO + try std.testing.expect(result = posit16_sigmoid(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "posit16_sigmoid_positive_large" { + given value = posit16_encode_f32(10.0) + try std.testing.expect(result = posit16_sigmoid(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "posit16_quantize_to_int4_max" { + given value = posit16_encode_f32(100.0) + try std.testing.expect(result = posit16_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "posit16_quantize_to_int4_min" { + given value = posit16_encode_f32(-100.0) + try std.testing.expect(result = posit16_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "posit16_from_f32_scaled_up" { + given value = 1.0 + try std.testing.expect(scale = 100.0); + try std.testing.expect(result = posit16_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 20.0); +} + +test "posit16_dot_product_simple" { + given a = [_]Posit16{posit16_encode_f32(1.0), posit16_encode_f32(2.0), posit16_encode_f32(3.0), posit16_encode_f32(4.0)} + try std.testing.expect(b = [_]Posit16{posit16_encode_f32(2.0), posit16_encode_f32(2.0), posit16_encode_f32(2.0), posit16_encode_f32(2.0)}); + try std.testing.expect(result = posit16_dot_product(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "posit16_vector_sum_simple" { + given vec = [_]Posit16{posit16_encode_f32(1.0), posit16_encode_f32(2.0), posit16_encode_f32(3.0), posit16_encode_f32(4.0)} + try std.testing.expect(result = posit16_vector_sum(vec)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "posit16_vector_mean_simple" { + given vec = [_]Posit16{posit16_encode_f32(2.0), posit16_encode_f32(4.0), posit16_encode_f32(6.0), posit16_encode_f32(8.0)} + try std.testing.expect(result = posit16_vector_mean(vec)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 5.0) < 1.0); +} + +test "posit16_lerp_zero" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = posit16_lerp(a, b, t)); + try std.testing.expect(posit16_is_equal(result, a) == true); +} + +test "posit16_lerp_one" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = posit16_lerp(a, b, t)); + try std.testing.expect(posit16_is_equal(result, b) == true); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant posit16_bits_constant + assert BITS == 16 + +invariant posit16_es_one + assert ES == 1 + +invariant posit16_nbits_fifteen + assert NBITS == 15 + +invariant posit16_zero_no_sign + assert (POS16_ZERO & SIGN_MASK) == 0 + +invariant posit16_nar_has_sign + assert (POS16_NAR & SIGN_MASK) != 0 + +invariant posit16_zero_nar_distinct + assert POS16_ZERO != POS16_NAR + +invariant posit16_abs_removes_sign + assert posit16_abs(POS16_NAR) != POS16_NAR // NaR preserves sign pattern + +invariant posit16_neg_zero_is_zero + assert posit16_neg(POS16_ZERO) == POS16_ZERO + +invariant posit16_neg_nar_is_nar + assert posit16_neg(POS16_NAR) == POS16_NAR + +invariant posit16_add_zero_identity + given value = posit16_encode_f32(50.0) + assert posit16_is_equal(posit16_add(value, POS16_ZERO), value) == true + +invariant posit16_sub_zero_identity + given value = posit16_encode_f32(50.0) + assert posit16_is_equal(posit16_sub(value, POS16_ZERO), value) == true + +invariant posit16_mul_zero_zero + given value = posit16_encode_f32(50.0) + assert posit16_mul(value, POS16_ZERO) == POS16_ZERO + +invariant posit16_div_nar_propagates + given value = posit16_encode_f32(50.0) + assert posit16_is_nar(posit16_div(POS16_NAR, value)) == true + +invariant posit16_relu_non_negative + given result = posit16_relu(posit16_encode_f32(50.0)) + try std.testing.expect(posit16_extract_sign(result) == false); + +invariant posit16_sqrt_non_negative + given result = posit16_sqrt(posit16_encode_f32(4.0)) + try std.testing.expect(posit16_extract_sign(result) == false); + +invariant posit16_quantize_to_int4_in_range + given result = posit16_quantize_to_int4(posit16_encode_f32(50.0)) + assert result >= -8 and result <= 7 + +invariant posit16_scale_roundtrip + given value = posit16_encode_f32(50.0) + try std.testing.expect(scale = 2.0); + try std.testing.expect(scaled = posit16_scale(value, scale)); + try std.testing.expect(decoded = posit16_to_f32_scaled(scaled, scale)); + try std.testing.expect(abs(decoded - posit16_decode_f32(value)) < 5.0); + +invariant posit16_max_ge_both + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_max(a, b)); + try std.testing.expect(posit16_is_greater(result, a) == true or posit16_is_equal(result, a) == true); + +invariant posit16_min_le_both + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_min(a, b)); + try std.testing.expect(posit16_is_greater(b, result) == true or posit16_is_equal(b, result) == true); + +invariant posit16_lerp_bounded + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result_half = posit16_lerp(a, b, 0.5)); + try std.testing.expect(decoded_a = posit16_decode_f32(a)); + try std.testing.expect(decoded_b = posit16_decode_f32(b)); + try std.testing.expect(decoded_half = posit16_decode_f32(result_half)); + try std.testing.expect(decoded_half > decoded_a and decoded_half < decoded_b); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench posit16_encode_f32_latency + measure: nanoseconds to posit16_encode_f32(10.5) + target: < 50ns + +bench posit16_decode_f32_latency + measure: nanoseconds to posit16_decode_f32(0x4000) + target: < 50ns + +bench posit16_add_latency + measure: nanoseconds to posit16_add(0x4000, 0x4800) + target: < 100ns + +bench posit16_mul_latency + measure: nanoseconds to posit16_mul(0x4000, 0x4000) + target: < 100ns + +bench posit16_sqrt_latency + measure: nanoseconds to posit16_sqrt(0x4000) + target: < 100ns + +bench posit16_sigmoid_latency + measure: nanoseconds to posit16_sigmoid(0x4000) + target: < 200ns + +bench posit16_relu_latency + measure: nanoseconds to posit16_relu(0x4800) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/gamma/specs/numeric/tri_net_formats.t27 b/apps/website/public/t27/files/chips/gamma/specs/numeric/tri_net_formats.t27 new file mode 100644 index 0000000000..bef8b1be89 --- /dev/null +++ b/apps/website/public/t27/files/chips/gamma/specs/numeric/tri_net_formats.t27 @@ -0,0 +1,993 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/tri_net_formats.t27 +// TRI NET Format Registry — Complete Format Specification +// phi^2 + 1/phi^2 = 3 | TRINITY +// +// Complete format registry for TRI-NET neural accelerator: +// - GoldenFloat family (GF4-GF256) +// - IEEE 754 formats (fp32, fp16) +// - Brain Float (bf16) +// - FP8 variants (e4m3, e5m2) +// - Integer formats (int4, int8) +// - Quantization formats (nf4) +// - Posit format (posit16) +// - Binary format (binary16) +// +// R-SI-1: Zero * operators, use XOR/shift/add only + +module TriNetFormats { + // Import sacred constants for phi-based optimization + use math::constants; + use math::sacred_physics; + use numeric::goldenfloat_family; + + // ======================================================================== + // 1. Format Classification Registry + // ======================================================================== + + // Format categories for TRI-NET hardware path selection + pub const FormatCategory = enum(u8) { + goldenfloat, // GF family: phi-optimized + ieee754, // IEEE 754 standard formats + integer, // Integer quantization + posit, // Posit universal number format + quantized, // Special quantization (NF4, etc) + binary, // Binary formats + }; + + // ======================================================================== + // 2. GoldenFloat Extended Family (GF64, GF128, GF256) + // ======================================================================== + // + // Formula: exp/mant ≈ 1/φ ≈ 0.618 + // Using phi_split: exp = round((N-1)/φ²), mant = N - 1 - exp + // + // GF64: 64 bits → exp=24, mant=39 (ratio=0.615, φ_dist=0.003) + // GF128: 128 bits → exp=48, mant=79 (ratio=0.608, φ_dist=0.010) + // GF256: 256 bits → exp=97, mant=158 (ratio=0.614, φ_dist=0.004) + + pub struct GF64Format { + bits : u8 = 64, + sign_bits : u8 = 1, + exp_bits : u8 = 24, + mant_bits : u8 = 39, + exp_bias : u32 = 8388607, // 2^(24-1) - 1 + phi_ratio : f64 = 24.0 / 39.0, + phi_dist : f64 = abs(24.0/39.0 - sacred_physics::PHI_INV), + is_primary : bool = false, + } + + pub struct GF128Format { + bits : u8 = 128, + sign_bits : u8 = 1, + exp_bits : u8 = 48, + mant_bits : u8 = 79, + exp_bias : u64 = 140737488355327, // 2^(48-1) - 1 + phi_ratio : f64 = 48.0 / 79.0, + phi_dist : f64 = abs(48.0/79.0 - sacred_physics::PHI_INV), + is_primary : bool = false, + } + + pub struct GF256Format { + bits : u8 = 256, + sign_bits : u8 = 1, + exp_bits : u8 = 97, + mant_bits : u8 = 158, + exp_bias : u128 = 0x7FFFFFFFFFFFFFFF, // 2^(97-1) - 1 + phi_ratio : f64 = 97.0 / 158.0, + phi_dist : f64 = abs(97.0/158.0 - sacred_physics::PHI_INV), + is_primary : bool = false, + } + + // ======================================================================== + // 3. IEEE 754 Standard Formats + // ======================================================================== + + pub struct FP32Format { + bits : u8 = 32, + sign_bits : u8 = 1, + exp_bits : u8 = 8, + mant_bits : u8 = 23, + exp_bias : u8 = 127, + category : FormatCategory = .ieee754, + ieee_compliant : bool = true, + } + + pub struct FP16Format { + bits : u8 = 16, + sign_bits : u8 = 1, + exp_bits : u8 = 5, + mant_bits : u8 = 10, + exp_bias : u8 = 15, + category : FormatCategory = .ieee754, + ieee_compliant : bool = true, + note : "binary16 = FP16", + } + + pub struct BF16Format { + bits : u8 = 16, + sign_bits : u8 = 1, + exp_bits : u8 = 8, + mant_bits : u8 = 7, + exp_bias : u8 = 127, + category : FormatCategory = .ieee754, + ieee_compliant : bool = false, // Not in IEEE 754-2008, widely used + note : "Brain Float: same exp as FP32, truncated mantissa", + } + + // ======================================================================== + // 4. FP8 Variants (OCP 8-bit specification) + // ======================================================================== + + pub struct FP8E4M3Format { + bits : u8 = 8, + sign_bits : u8 = 1, + exp_bits : u8 = 4, + mant_bits : u8 = 3, + exp_bias : u8 = 7, + category : FormatCategory = .ieee754, + note : "OCP E4M3: 1 exp, 8 mant values for training", + range : string = "[-448, 448]", + } + + pub struct FP8E5M2Format { + bits : u8 = 8, + sign_bits : u8 = 1, + exp_bits : u8 = 5, + mant_bits : u8 = 2, + exp_bias : u8 = 15, + category : FormatCategory = .ieee754, + note : "OCP E5M2: better dynamic range for inference", + range : string = "[-57344, 57344]", + } + + // ======================================================================== + // 5. Integer Formats (for quantization) + // ======================================================================== + + pub struct Int4Format { + bits : u8 = 4, + sign_bits : u8 = 1, + data_bits : u8 = 3, + category : FormatCategory = .integer, + range : string = "[-8, 7]", + quantization_scale : f32 = 0.0625, + } + + pub struct Int8Format { + bits : u8 = 8, + sign_bits : u8 = 1, + data_bits : u8 = 7, + category : FormatCategory = .integer, + range : string = "[-128, 127]", + quantization_scale : f32 = 0.0039216, + } + + // ======================================================================== + // 6. NormalFloat4 (NF4) Format + // ======================================================================== + // + // NF4: 4-bit format with values drawn from normal distribution + // Values: discretized Normal(0, 1) quantiles + // Used in QLoRA for efficient 4-bit fine-tuning + // + // 16 levels: {-1.0, -0.6962, -0.5251, -0.3942, -0.2844, -0.1848, -0.0910, -0.0000, + // 0.0000, 0.0910, 0.1848, 0.2844, 0.3942, 0.5251, 0.6962, 1.0} + // + // Note: Zero appears twice (positive/negative) for perfect rounding + + pub const NF4_LEVELS : [16]f64 = [ + -1.0, -0.6961928009210449, -0.5250720977783203, -0.3941699969768524, + -0.28444138169288635, -0.18477343022823334, -0.09105003625154495, -0.0000000000000000, + 0.0000000000000000, 0.09105003625154495, 0.18477343022823334, 0.28444138169288635, + 0.3941699969768524, 0.5250720977783203, 0.6961928009210449, 1.0, + ]; + + pub struct NF4Format { + bits : u8 = 4, + category : FormatCategory = .quantized, + range : string = "[-1.0, 1.0]", + distribution : string = "Normal(0, 1) quantiles", + num_levels : u8 = 16, + } + + // ======================================================================== + // 7. Posit16 Format (Type III Unum) + // ======================================================================== + // + // Posit format: [sign][regime][exponent][mantissa] + // regime: uses of trailing zeros/followed by zeros + // es (exponent size) = 1 for posit16 + // + // bit layout: [S(1) regime(6) E(1) M(8)] + // + // Properties: + // - Exact representation of small integers + // - Symmetric positive/negative + // - Gradual overflow/underflow + // - Better dynamic range than FP16 for same precision + + pub struct Posit16Format { + bits : u8 = 16, + sign_bits : u8 = 1, + es_bits : u8 = 1, // exponent size + nbits : u8 = 16, + category : FormatCategory = .posit, + useed : u8 = 2, // 2^es = 2 + max_pos : f64 = 262144.0, + min_pos : f64 = 0.000000059604645, + } + + // ======================================================================== + // 8. Binary16 Format + // ======================================================================== + // + // Binary16 is just an alias for IEEE FP16 + // Kept separate for format routing clarity + + pub struct Binary16Format { + bits : u8 = 16, + sign_bits : u8 = 1, + exp_bits : u8 = 5, + mant_bits : u8 = 10, + exp_bias : u8 = 15, + category : FormatCategory = .binary, + note : "Same as FP16, separate routing class", + } + + // ======================================================================== + // 9. Format Registry (Lookup Table) + // ======================================================================== + + pub struct FormatDescriptor { + name : string, + category : FormatCategory, + bits : u8, + sign_bits : u8, + exp_bits : u8, + mant_bits : u8, + exp_bias : u32, + phi_ratio : f64, + phi_dist : f64, + memory_ratio : f64, // vs FP32 + use_case : string, + } + + pub const TRI_NET_FORMATS : [16]FormatDescriptor = [ + // GoldenFloat family (phi-optimized) + FormatDescriptor{ + name = "GF4", + category = .goldenfloat, + bits = 4, + sign_bits = 1, + exp_bits = 1, + mant_bits = 2, + exp_bias = 0, + phi_ratio = 0.5, + phi_dist = 0.118, + memory_ratio = 0.125, + use_case = "Extreme compression, attention masks", + }, + FormatDescriptor{ + name = "GF8", + category = .goldenfloat, + bits = 8, + sign_bits = 1, + exp_bits = 3, + mant_bits = 4, + exp_bias = 3, + phi_ratio = 0.75, + phi_dist = 0.132, + memory_ratio = 0.25, + use_case = "Weight quantization, activation caching", + }, + FormatDescriptor{ + name = "GF12", + category = .goldenfloat, + bits = 12, + sign_bits = 1, + exp_bits = 4, + mant_bits = 7, + exp_bias = 7, + phi_ratio = 0.571, + phi_dist = 0.047, + memory_ratio = 0.375, + use_case = "Best phi approximation, critical weights", + }, + FormatDescriptor{ + name = "GF16", + category = .goldenfloat, + bits = 16, + sign_bits = 1, + exp_bits = 6, + mant_bits = 9, + exp_bias = 31, + phi_ratio = 0.667, + phi_dist = 0.049, + memory_ratio = 0.5, + use_case = "PRIMARY FORMAT, IGLA main weights", + }, + FormatDescriptor{ + name = "GF20", + category = .goldenfloat, + bits = 20, + sign_bits = 1, + exp_bits = 7, + mant_bits = 12, + exp_bias = 63, + phi_ratio = 0.583, + phi_dist = 0.035, + memory_ratio = 0.625, + use_case = "High-precision ML training", + }, + FormatDescriptor{ + name = "GF24", + category = .goldenfloat, + bits = 24, + sign_bits = 1, + exp_bits = 9, + mant_bits = 14, + exp_bias = 255, + phi_ratio = 0.643, + phi_dist = 0.025, + memory_ratio = 0.75, + use_case = "Financial, numerical stability", + }, + FormatDescriptor{ + name = "GF32", + category = .goldenfloat, + bits = 32, + sign_bits = 1, + exp_bits = 12, + mant_bits = 19, + exp_bias = 2047, + phi_ratio = 0.632, + phi_dist = 0.014, + memory_ratio = 1.0, + use_case = "Near-IEEE precision, wider range", + }, + FormatDescriptor{ + name = "GF64", + category = .goldenfloat, + bits = 64, + sign_bits = 1, + exp_bits = 24, + mant_bits = 39, + exp_bias = 8388607, + phi_ratio = 0.615, + phi_dist = 0.003, + memory_ratio = 2.0, + use_case = "Extended range, scientific computing", + }, + FormatDescriptor{ + name = "GF128", + category = .goldenfloat, + bits = 128, + sign_bits = 1, + exp_bits = 48, + mant_bits = 79, + exp_bias = 140737488355327, + phi_ratio = 0.608, + phi_dist = 0.010, + memory_ratio = 4.0, + use_case = "Ultra-high precision, cryptography", + }, + FormatDescriptor{ + name = "GF256", + category = .goldenfloat, + bits = 256, + sign_bits = 1, + exp_bits = 97, + mant_bits = 158, + exp_bias = 0x7FFFFFFFFFFFFFFF, + phi_ratio = 0.614, + phi_dist = 0.004, + memory_ratio = 8.0, + use_case = "Maximum precision, physics simulations", + }, + // IEEE 754 formats + FormatDescriptor{ + name = "FP32", + category = .ieee754, + bits = 32, + sign_bits = 1, + exp_bits = 8, + mant_bits = 23, + exp_bias = 127, + phi_ratio = 0.348, + phi_dist = 0.270, + memory_ratio = 1.0, + use_case = "Standard training, reference", + }, + FormatDescriptor{ + name = "FP16", + category = .ieee754, + bits = 16, + sign_bits = 1, + exp_bits = 5, + mant_bits = 10, + exp_bias = 15, + phi_ratio = 0.5, + phi_dist = 0.118, + memory_ratio = 0.5, + use_case = "Standard half-precision, inference", + }, + FormatDescriptor{ + name = "BF16", + category = .ieee754, + bits = 16, + sign_bits = 1, + exp_bits = 8, + mant_bits = 7, + exp_bias = 127, + phi_ratio = 1.143, + phi_dist = 0.525, + memory_ratio = 0.5, + use_case = "Brain Float, gradient accumulation", + }, + // FP8 variants + FormatDescriptor{ + name = "FP8_E4M3", + category = .ieee754, + bits = 8, + sign_bits = 1, + exp_bits = 4, + mant_bits = 3, + exp_bias = 7, + phi_ratio = 1.333, + phi_dist = 0.715, + memory_ratio = 0.25, + use_case = "OCP training format", + }, + FormatDescriptor{ + name = "FP8_E5M2", + category = .ieee754, + bits = 8, + sign_bits = 1, + exp_bits = 5, + mant_bits = 2, + exp_bias = 15, + phi_ratio = 2.5, + phi_dist = 1.882, + memory_ratio = 0.25, + use_case = "OCP inference format", + }, + // Posit and Binary + FormatDescriptor{ + name = "Posit16", + category = .posit, + bits = 16, + sign_bits = 1, + exp_bits = 1, + mant_bits = 8, + exp_bias = 0, + phi_ratio = 0.125, + phi_dist = 0.493, + memory_ratio = 0.5, + use_case = "Type III Unum, exact integers", + }, + FormatDescriptor{ + name = "Binary16", + category = .binary, + bits = 16, + sign_bits = 1, + exp_bits = 5, + mant_bits = 10, + exp_bias = 15, + phi_ratio = 0.5, + phi_dist = 0.118, + memory_ratio = 0.5, + use_case = "Alias for FP16, binary routing", + }, + ]; + + // ======================================================================== + // 10. Format Lookup Functions + // ======================================================================== + + pub fn get_format_by_name(name: string) -> ?FormatDescriptor { + for (const TRI_NET_FORMATS) |fmt| { + if (fmt.name == name) { + return fmt; + } + } + return null; + } + + pub fn get_format_by_bits(bits: u8) -> []FormatDescriptor { + var result : [16]FormatDescriptor = undefined; + var count : u8 = 0; + for (const TRI_NET_FORMATS) |fmt| { + if (fmt.bits == bits) { + result[count] = fmt; + count = count + 1; + } + } + return result[0..count]; + } + + pub fn get_phi_optimal_format() -> FormatDescriptor { + var best : FormatDescriptor = TRI_NET_FORMATS[0]; + for (const TRI_NET_FORMATS) |fmt| { + if (fmt.phi_dist < best.phi_dist) { + best = fmt; + } + } + return best; + } + + // ======================================================================== + // 11. Format Conversion Utilities + // ======================================================================== + + pub fn format_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Format-specific conversion to f32 + if (fmt.category == .goldenfloat) { + return gf_to_f32(fmt, raw); + } else if (fmt.category == .ieee754) { + return ieee754_to_f32(fmt, raw); + } else if (fmt.category == .posit) { + return posit_to_f32(fmt, raw); + } else if (fmt.category == .integer) { + return int_to_f32(fmt, raw); + } else if (fmt.category == .quantized) { + return nf4_to_f32(raw); + } + return 0.0; + } + + pub fn f32_to_format(fmt: FormatDescriptor, value: f32) -> u64 { + // Format-specific conversion from f32 + if (fmt.category == .goldenfloat) { + return f32_to_gf(fmt, value); + } else if (fmt.category == .ieee754) { + return f32_to_ieee754(fmt, value); + } else if (fmt.category == .posit) { + return f32_to_posit(fmt, value); + } else if (fmt.category == .integer) { + return f32_to_int(fmt, value); + } else if (fmt.category == .quantized) { + return f32_to_nf4(value); + } + return 0; + } + + // ======================================================================== + // 12. GF Conversion Functions (Stub) + // ======================================================================== + + fn gf_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Extract sign, exponent, mantissa + const sign = (raw >> (fmt.bits - 1)) as u8; + const exp_mask = (1u64 << fmt.exp_bits) - 1; + const mant_mask = (1u64 << fmt.mant_bits) - 1; + const exp_biased = ((raw >> fmt.mant_bits) & exp_mask) as u32; + const mant = (raw & mant_mask) as u32; + + // Handle zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Unbias exponent + const exp_unbiased = if (exp_biased == 0) { + -(fmt.exp_bias as i32) + 1 + } else { + (exp_biased as i32) - fmt.exp_bias as i32 + }; + + // Normalize mantissa + const max_mant = (1u32 << fmt.mant_bits) - 1; + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / (max_mant + 1) as f32 + } else { + 1.0 + (mant as f32) / (max_mant + 1) as f32 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + fn f32_to_gf(fmt: FormatDescriptor, value: f32) -> u64 { + if (value == 0.0) { + return 0; + } + + const sign = if (value < 0.0) { 1u64 } else { 0u64 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent + const exp_unbiased = floor_log2(abs_val) as i32; + let exp_biased = (exp_unbiased + fmt.exp_bias as i32) as u64; + + // Clamp exponent + const max_exp = (1u64 << fmt.exp_bits) - 1; + if (exp_biased > max_exp) { + exp_biased = max_exp; + } + + // Extract mantissa + const max_mant = (1u64 << fmt.mant_bits) - 1; + const normalized = abs_val / pow(2.0, exp_unbiased as f32); + const frac = normalized - 1.0; + const mant = (frac * (max_mant + 1) as f32) as u64; + const clamped_mant = if (mant > max_mant) { max_mant } else { mant }; + + return (sign << (fmt.bits - 1)) | + (exp_biased << fmt.mant_bits) | + clamped_mant; + } + + // ======================================================================== + // 13. IEEE 754 Conversion Functions (Stub) + // ======================================================================== + + fn ieee754_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Handle FP32 (direct reinterpret) + if (fmt.bits == 32 && fmt.name == "FP32") { + return @bitCast(f32, @as(u32, raw)); + } + + // Handle smaller formats (need conversion) + // This is a stub - real implementation would do proper conversion + return 0.0; + } + + fn f32_to_ieee754(fmt: FormatDescriptor, value: f32) -> u64 { + // Handle FP32 (direct reinterpret) + if (fmt.bits == 32 && fmt.name == "FP32") { + return @as(u64, @bitCast(u32, value)); + } + + // Handle smaller formats (need conversion) + // This is a stub - real implementation would do proper conversion + return 0; + } + + // ======================================================================== + // 14. Posit Conversion Functions (Stub) + // ======================================================================== + + fn posit_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Posit decode + // This is a stub - real implementation would do proper decoding + return 0.0; + } + + fn f32_to_posit(fmt: FormatDescriptor, value: f32) -> u64 { + // Posit encode + // This is a stub - real implementation would do proper encoding + return 0; + } + + // ======================================================================== + // 15. Integer Conversion Functions + // ======================================================================== + + fn int_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Sign-extend integer to f32 + const sign_bit = (raw >> (fmt.bits - 1)) as u8; + const magnitude = raw & ((1u64 << (fmt.bits - 1)) - 1); + const max_val = 1u64 << (fmt.bits - 1); + + if (sign_bit != 0) { + return -((max_val - magnitude) as f32); + } + return magnitude as f32; + } + + fn f32_to_int(fmt: FormatDescriptor, value: f32) -> u64 { + const max_val = (1i64 << (fmt.bits - 1)) - 1; + const min_val = -max_val - 1; + + let clamped = value as i64; + if (clamped > max_val) { + clamped = max_val; + } else if (clamped < min_val) { + clamped = min_val; + } + + if (clamped < 0) { + return ((1u64 << fmt.bits) + @as(u64, @bitCast(u64, clamped))); + } + return @as(u64, @bitCast(u64, clamped)); + } + + // ======================================================================== + // 16. NF4 Conversion Functions + // ======================================================================== + + fn nf4_to_f32(raw: u64) -> f32 { + const index = raw & 0xF; + return NF4_LEVELS[@as(usize, index)] as f32; + } + + fn f32_to_nf4(value: f32) -> u64 { + let best_idx : u64 = 0; + let best_dist : f64 = 1e9; + + var i : u64 = 0; + while (i < 16) { + const dist = abs(value - NF4_LEVELS[@as(usize, i)] as f32); + if (dist < best_dist) { + best_dist = dist; + best_idx = i; + } + i = i + 1; + } + + return best_idx; + } + + // ======================================================================== + // 17. Helper Functions + // ======================================================================== + + fn floor_log2(x: f32) -> i32 { + if (x <= 0.0) { return -2147483648; } + let exp : i32 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn pow(base: f32, exp: f32) -> f32 { + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; + } + + const is_integer = exp == @floor(exp); + + if (is_integer) { + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; + } + if (x == 1.0) { + return 0.0; + } + + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + + return 2.0 * (t + t3 / 3.0); + } + + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + let result = 1.0; + let term = 1.0; + let exp_x = x; + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + return result; + } + + fn @floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + fn abs(x: f32) -> f32 { + if (x < 0.0) { + return -x; + } + return x; + } + + // ======================================================================== + // TDD-Inside-Spec: Tests and Invariants for TRI_NET_FORMATS + // ======================================================================== + + test tri_net_formats_count_is_17 + given count = TRI_NET_FORMATS.len() + then count == 17 + + test tri_net_gf16_is_primary + given gf16 = get_format_by_name("GF16").? + then gf16.name == "GF16" and gf16.phi_dist < 0.05 + + test tri_net_gf12_has_best_phi_approximation + given best = get_phi_optimal_format() + then best.name == "GF12" and best.phi_dist < 0.05 + + test tri_net_all_formats_have_positive_bits + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.bits > 0; + } + + test tri_net_all_formats_sum_bits_correct + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.sign_bits + fmt.exp_bits + fmt.mant_bits == fmt.bits + or fmt.category == .posit; // Posit has different structure + } + + test tri_nft_n4_levels_count_is_16 + given count = NF4_LEVELS.len() + then count == 16 + + test tri_net_nf4_levels_symmetric + given center1 = NF4_LEVELS[6] + and center2 = NF4_LEVELS[7] + then abs(center1 + center2) < 1e-10 + + test tri_net_nf4_range_is_minus_one_to_one + given min_val = NF4_LEVELS[0] + and max_val = NF4_LEVELS[15] + then abs(min_val + 1.0) < 1e-10 and abs(max_val - 1.0) < 1e-10 + + test tri_nft_format_lookup_by_name_gf16 + given fmt = get_format_by_name("GF16") + then fmt != null and fmt.?.bits == 16 + + test tri_net_format_lookup_by_bits_16_returns_formats + given formats = get_format_by_bits(16) + and count = formats.len() + then count >= 3 // GF16, FP16, BF16, Binary16 + + test tri_nft_fp32_ieee_compliant + given fp32 = get_format_by_name("FP32").? + then fp32.ieee_compliant == true and fp32.category == .ieee754 + + test tri_net_bf16_not_ieee_compliant + given bf16 = get_format_by_name("BF16").? + then bf16.ieee_compliant == false and bf16.category == .ieee754 + + test tri_nft_fp8_e4m3_has_4_exp_3_mant + given e4m3 = get_format_by_name("FP8_E4M3").? + then e4m3.exp_bits == 4 and e4m3.mant_bits == 3 + + test tri_net_fp8_e5m2_has_5_exp_2_mant + given e5m2 = get_format_by_name("FP8_E5M2").? + then e5m2.exp_bits == 5 and e5m2.mant_bits == 2 + + test tri_net_gf64_has_24_exp_39_mant + given gf64 = get_format_by_name("GF64").? + then gf64.exp_bits == 24 and gf64.mant_bits == 39 + + test tri_net_gf128_has_48_exp_79_mant + given gf128 = get_format_by_name("GF128").? + then gf128.exp_bits == 48 and gf128.mant_bits == 79 + + test tri_net_gf256_has_97_exp_158_mant + given gf256 = get_format_by_name("GF256").? + then gf256.exp_bits == 97 and gf256.mant_bits == 158 + + test tri_net_posit16_has_category_posit + given posit16 = get_format_by_name("Posit16").? + then posit16.category == .posit + + test tri_net_binary16_same_as_fp16 + given binary16 = get_format_by_name("Binary16").? + and fp16 = get_format_by_name("FP16").? + then binary16.bits == fp16.bits and + binary16.exp_bits == fp16.exp_bits and + binary16.mant_bits == fp16.mant_bits + + test tri_net_nf4_to_f32_negative + given value = nf4_to_f32(0) + then value < 0.0 and abs(value + 1.0) < 1e-10 + + test tri_net_nf4_to_f32_positive + given value = nf4_to_f32(15) + then value > 0.0 and abs(value - 1.0) < 1e-10 + + test tri_net_nf4_to_f32_center + given value1 = nf4_to_f32(6) + and value2 = nf4_to_f32(7) + then abs(value1 + value2) < 1e-10 + + test tri_net_f32_to_nf4_positive + given result = f32_to_nf4(1.0) + then result == 15 + + test tri_net_f32_to_nf4_negative + given result = f32_to_nf4(-1.0) + then result == 0 + + test tri_net_f32_to_nf4_zero + given result = f32_to_nf4(0.0) + then result == 6 or result == 7 // Either center value + + invariant tri_net_all_formats_have_valid_category + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.category >= .goldenfloat and fmt.category <= .binary; + } + + invariant tri_net_all_phi_distances_non_negative + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.phi_dist >= 0.0; + } + + invariant tri_nft_all_memory_ratios_positive + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.memory_ratio > 0.0; + } + + invariant tri_net_gf_formats_have_best_phi_distance + const golden_fmts = get_format_by_bits(16); // GF16 vs others + // GF family should have better phi distance than IEEE formats + // (This is a rough check - detailed comparison would need more logic) + + invariant tri_net_nf4_levels_are_monotonic + for (i in 0..15) { + if (i < 15) { + assert NF4_LEVELS[i] < NF4_LEVELS[i + 1]; + } + } + + invariant tri_net_format_registry_consistent + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.name.len() > 0; + assert fmt.bits >= 4; + assert fmt.sign_bits == 1; + } + + bench tri_net_get_format_by_name_latency + measure: nanoseconds to get_format_by_name("GF16") + target: < 100ns + + bench tri_net_get_phi_optimal_format_latency + measure: nanoseconds to get_phi_optimal_format() + target: < 200ns + + bench tri_net_f32_to_nf4_latency + measure: nanoseconds to f32_to_nf4(0.5) + target: < 100ns + + bench tri_net_nf4_to_f32_latency + measure: nanoseconds to nf4_to_f32(8) + target: < 50ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/avs_controller_48.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/avs_controller_48.t27 new file mode 100644 index 0000000000..c528b272e3 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/avs_controller_48.t27 @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: Apache-2.0 +; avs_controller_48.t27 — 48-pin AVS Controller +; Adaptive voltage scaling controller for 48-pin configuration +; φ² + 1/φ² = 3 | TRINITY + +module avs-controller-48; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const PINS_48 : u8 = 48; +pub const VOLTAGE_MIN_MV : u16 = 800; // 0.8V minimum +pub const VOLTAGE_MAX_MV : u16 = 1200; // 1.2V maximum +pub const VOLTAGE_STEP_MV : u16 = 10; // 10mV step size +pub const VOLTAGE_LEVELS : u8 = 41; // (1200-800)/10 + 1 + +pub const TARGET_MV : u16 = 1000; // 1.0V target +pub const HYSTERESIS_MV : u16 = 20; // 20mV hysteresis +pub const SETTLING_US : u16 = 100; // 100us settling time + +pub const CONTROL_PIN_OFFSET : u8 = 16; // AVS control pins start at 16 +pub const CONTROL_PIN_COUNT : u8 = 6; // 6 control pins for 48-pin config + +pub const FREQ_MIN_KHZ : u32 = 10000; // 10 MHz minimum +pub const FREQ_MAX_KHZ : u32 = 100000; // 100 MHz maximum + +// ============================================================================ +// Types +// ============================================================================ + +pub const AvsState = enum(u8) { + disabled = 0, + enabled = 1, + adjusting = 2, + settled = 3, + fault = 4, +} + +pub const AvsMode = enum(u8) { + manual = 0, + automatic = 1, + adaptive = 2, +} + +pub const AvsPinConfig = struct { + pin_index : u8, + is_output : bool, + function : u8, // 0=control, 1=feedback, 2=enable +} + +pub const AvsVoltageLevel = struct { + level : u8, // 0 to 40 + voltage_mv : u16, + control_code : u8, // 6-bit code for control pins +} + +pub const AvsStatus = struct { + state : AvsState, + mode : AvsMode, + current_mv : u16, + target_mv : u16, + level : u8, + fault_code : u8, +} + +pub const AvsConfig = struct { + mode : AvsMode, + min_mv : u16, + max_mv : u16, + target_mv : u16, + hysteresis_mv : u16, + settling_us : u16, +} + +// ============================================================================ +// Voltage Level Functions +// ============================================================================ + +// avs_voltage_from_level(level: u8) -> u16 +// Convert voltage level to millivolts +pub fn avs_voltage_from_level(level: u8) -> u16 { + return VOLTAGE_MIN_MV + @as(u16, level) * VOLTAGE_STEP_MV; +} + +// avs_level_from_voltage(voltage_mv: u16) -> u8 +// Convert voltage to level +pub fn avs_level_from_voltage(voltage_mv: u16) -> u8 { + var mv = voltage_mv; + if (mv < VOLTAGE_MIN_MV) { + mv = VOLTAGE_MIN_MV; + } else if (mv > VOLTAGE_MAX_MV) { + mv = VOLTAGE_MAX_MV; + } + return @as(u8, (mv - VOLTAGE_MIN_MV) / VOLTAGE_STEP_MV); +} + +// avs_control_code_from_level(level: u8) -> u8 +// Generate 6-bit control code for voltage level +pub fn avs_control_code_from_level(level: u8) -> u8 { + // Simple binary encoding for 6 pins + return level & 0x3F; +} + +// avs_level_from_control_code(code: u8) -> u8 +// Decode level from 6-bit control code +pub fn avs_level_from_control_code(code: u8) -> u8 { + return code & 0x3F; +} + +// ============================================================================ +// Voltage Validation +// ============================================================================ + +// avs_voltage_valid(voltage_mv: u16) -> bool +// Check if voltage is within valid range +pub fn avs_voltage_valid(voltage_mv: u16) -> bool { + return voltage_mv >= VOLTAGE_MIN_MV and voltage_mv <= VOLTAGE_MAX_MV; +} + +// avs_voltage_in_hysteresis(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool +// Check if current voltage is within hysteresis band of target +pub fn avs_voltage_in_hysteresis(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool { + if (current_mv > target_mv) { + return (current_mv - target_mv) <= hysteresis_mv; + } else { + return (target_mv - current_mv) <= hysteresis_mv; + } +} + +// avs_should_adjust(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool +// Check if voltage should be adjusted +pub fn avs_should_adjust(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool { + return not avs_voltage_in_hysteresis(current_mv, target_mv, hysteresis_mv); +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// avs_config_init() -> AvsConfig +// Initialize AVS configuration +pub fn avs_config_init() -> AvsConfig { + return AvsConfig { + .mode = AvsMode.automatic, + .min_mv = VOLTAGE_MIN_MV, + .max_mv = VOLTAGE_MAX_MV, + .target_mv = TARGET_MV, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + }; +} + +// avs_config_manual(target_mv: u16) -> AvsConfig +// Create manual AVS configuration +pub fn avs_config_manual(target_mv: u16) -> AvsConfig { + return AvsConfig { + .mode = AvsMode.manual, + .min_mv = VOLTAGE_MIN_MV, + .max_mv = VOLTAGE_MAX_MV, + .target_mv = target_mv, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + }; +} + +// avs_config_adaptive(min_mv: u16, max_mv: u16) -> AvsConfig +// Create adaptive AVS configuration +pub fn avs_config_adaptive(min_mv: u16, max_mv: u16) -> AvsConfig { + return AvsConfig { + .mode = AvsMode.adaptive, + .min_mv = min_mv, + .max_mv = max_mv, + .target_mv = (min_mv + max_mv) / 2, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + }; +} + +// ============================================================================ +// Status Functions +// ============================================================================ + +// avs_status_init() -> AvsStatus +// Initialize AVS status +pub fn avs_status_init() -> AvsStatus { + return AvsStatus { + .state = AvsState.disabled, + .mode = AvsMode.manual, + .current_mv = VOLTAGE_MIN_MV, + .target_mv = TARGET_MV, + .level = 0, + .fault_code = 0, + }; +} + +// avs_status_enable(status: AvsStatus, config: AvsConfig) -> AvsStatus +// Enable AVS controller +pub fn avs_status_enable(status: AvsStatus, config: AvsConfig) -> AvsStatus { + return AvsStatus { + .state = AvsState.enabled, + .mode = config.mode, + .current_mv = status.current_mv, + .target_mv = config.target_mv, + .level = status.level, + .fault_code = 0, + }; +} + +// avs_status_adjust(status: AvsStatus, new_level: u8) -> AvsStatus +// Adjust to new voltage level +pub fn avs_status_adjust(status: AvsStatus, new_level: u8) -> AvsStatus { + const new_mv = avs_voltage_from_level(new_level); + return AvsStatus { + .state = AvsState.adjusting, + .mode = status.mode, + .current_mv = status.current_mv, + .target_mv = new_mv, + .level = new_level, + .fault_code = status.fault_code, + }; +} + +// avs_status_settled(status: AvsStatus, measured_mv: u16) -> AvsStatus +// Mark voltage as settled +pub fn avs_status_settled(status: AvsStatus, measured_mv: u16) -> AvsStatus { + return AvsStatus { + .state = AvsState.settled, + .mode = status.mode, + .current_mv = measured_mv, + .target_mv = status.target_mv, + .level = avs_level_from_voltage(measured_mv), + .fault_code = status.fault_code, + }; +} + +// avs_status_fault(status: AvsStatus, fault_code: u8) -> AvsStatus +// Mark fault condition +pub fn avs_status_fault(status: AvsStatus, fault_code: u8) -> AvsStatus { + return AvsStatus { + .state = AvsState.fault, + .mode = status.mode, + .current_mv = status.current_mv, + .target_mv = status.target_mv, + .level = status.level, + .fault_code = fault_code, + }; +} + +// ============================================================================ +// Pin Functions +// ============================================================================ + +// avs_control_pins_start() -> u8 +// Get starting pin index for control pins +pub fn avs_control_pins_start() -> u8 { + return CONTROL_PIN_OFFSET; +} + +// avs_control_pins_end() -> u8 +// Get ending pin index for control pins +pub fn avs_control_pins_end() -> u8 { + return CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT - 1; +} + +// avs_is_control_pin(pin_index: u8) -> bool +// Check if pin is a control pin +pub fn avs_is_control_pin(pin_index: u8) -> bool { + return pin_index >= avs_control_pins_start() and pin_index <= avs_control_pins_end(); +} + +// avs_control_pin_for_bit(bit_index: u8) -> u8 +// Get control pin for a bit index (0-5) +pub fn avs_control_pin_for_bit(bit_index: u8) -> u8 { + if (bit_index >= CONTROL_PIN_COUNT) { + return CONTROL_PIN_OFFSET; + } + return CONTROL_PIN_OFFSET + bit_index; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_avs_48_cmd(opcode: u8, level: u8, reserved: u8) -> u16 +// Encode AVS 48-pin command +pub fn encode_avs_48_cmd(opcode: u8, level: u8, reserved: u8) u16 { + // Format: [OP:8][LEVEL:6][RES:2] + const op_field : u16 = @as(u16, opcode) << 8; + const level_field : u16 = @as(u16, level & 0x3F) << 2; + const res_field : u16 = @as(u16, reserved & 0x03); + return op_field | level_field | res_field; +} + +// decode_avs_48_cmd(encoded: u16) -> struct { opcode: u8, level: u8, reserved: u8 } +// Decode AVS 48-pin command +pub fn decode_avs_48_cmd(encoded: u16) -> struct { opcode: u8, level: u8, reserved: u8 } { + const opcode : u8 = @as(u8, @truncate((encoded >> 8) & 0xFF)); + const level : u8 = @as(u8, @truncate((encoded >> 2) & 0x3F)); + const reserved : u8 = @as(u8, @truncate(encoded & 0x03)); + return .{ .opcode = opcode, .level = level, .reserved = reserved }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "pins_48_constant" { + try std.testing.expect(PINS_48 == 48); +} + +test "voltage_constants" { + try std.testing.expect(VOLTAGE_MIN_MV == 800); + try std.testing.expect(VOLTAGE_MAX_MV == 1200); + try std.testing.expect(VOLTAGE_STEP_MV == 10); + try std.testing.expect(VOLTAGE_LEVELS == 41); +} + +test "target_voltage_constant" { + try std.testing.expect(TARGET_MV == 1000); +} + +test "control_pin_constants" { + try std.testing.expect(CONTROL_PIN_OFFSET == 16); + try std.testing.expect(CONTROL_PIN_COUNT == 6); +} + +test "avs_voltage_from_level_zero" { + try std.testing.expect(avs_voltage_from_level(0) == VOLTAGE_MIN_MV); +} + +test "avs_voltage_from_level_max" { + given max_level = VOLTAGE_LEVELS - 1 + try std.testing.expect(avs_voltage_from_level(max_level) == VOLTAGE_MAX_MV); +} + +test "avs_voltage_from_level_target" { + given level = avs_level_from_voltage(TARGET_MV) + try std.testing.expect(avs_voltage_from_level(level) == TARGET_MV); +} + +test "avs_level_from_voltage_min" { + try std.testing.expect(avs_level_from_voltage(VOLTAGE_MIN_MV) == 0); +} + +test "avs_level_from_voltage_max" { + try std.testing.expect(avs_level_from_voltage(VOLTAGE_MAX_MV) == VOLTAGE_LEVELS - 1); +} + +test "avs_level_from_voltage_clamp_low" { + try std.testing.expect(avs_level_from_voltage(700) == 0); +} + +test "avs_level_from_voltage_clamp_high" { + try std.testing.expect(avs_level_from_voltage(1500) == VOLTAGE_LEVELS - 1); +} + +test "avs_control_code_from_level_zero" { + try std.testing.expect(avs_control_code_from_level(0) == 0); +} + +test "avs_control_code_from_level_max" { + given code = avs_control_code_from_level(63) + try std.testing.expect(code == 63); +} + +test "avs_control_code_from_level_clamp" { + given code = avs_control_code_from_level(100) + try std.testing.expect(code == 36 // 100 & 0x3F); +} + +test "avs_level_from_control_code_roundtrip" { + given level = 20 + try std.testing.expect(code = avs_control_code_from_level(level)); + try std.testing.expect(decoded = avs_level_from_control_code(code)); + try std.testing.expect(decoded == level); +} + +test "avs_voltage_valid_in_range" { + try std.testing.expect(avs_voltage_valid(900) == true); + try std.testing.expect(avs_voltage_valid(1000) == true); + try std.testing.expect(avs_voltage_valid(1100) == true); +} + +test "avs_voltage_valid_out_of_range" { + try std.testing.expect(avs_voltage_valid(700) == false); + try std.testing.expect(avs_voltage_valid(1300) == false); +} + +test "avs_voltage_in_hysteresis_true" { + try std.testing.expect(avs_voltage_in_hysteresis(1000, 1000, 20) == true); + try std.testing.expect(avs_voltage_in_hysteresis(1015, 1000, 20) == true); + try std.testing.expect(avs_voltage_in_hysteresis(985, 1000, 20) == true); +} + +test "avs_voltage_in_hysteresis_false" { + try std.testing.expect(avs_voltage_in_hysteresis(1050, 1000, 20) == false); + try std.testing.expect(avs_voltage_in_hysteresis(950, 1000, 20) == false); +} + +test "avs_should_adjust_false_in_hysteresis" { + try std.testing.expect(avs_should_adjust(1010, 1000, 20) == false); +} + +test "avs_should_adjust_true_out_of_hysteresis" { + try std.testing.expect(avs_should_adjust(1050, 1000, 20) == true); +} + +test "avs_config_init_structure" { + given config = avs_config_init() + try std.testing.expect(config.mode == AvsMode.automatic); + try std.testing.expect(config.target_mv == TARGET_MV); + try std.testing.expect(config.hysteresis_mv == HYSTERESIS_MV); +} + +test "avs_config_manual_structure" { + given config = avs_config_manual(900) + try std.testing.expect(config.mode == AvsMode.manual); + try std.testing.expect(config.target_mv == 900); +} + +test "avs_config_adaptive_structure" { + given config = avs_config_adaptive(850, 1150) + try std.testing.expect(config.mode == AvsMode.adaptive); + try std.testing.expect(config.min_mv == 850); + try std.testing.expect(config.max_mv == 1150); +} + +test "avs_status_init_structure" { + given status = avs_status_init() + try std.testing.expect(status.state == AvsState.disabled); + try std.testing.expect(status.current_mv == VOLTAGE_MIN_MV); +} + +test "avs_status_enable" { + given status = avs_status_init() + try std.testing.expect(config = avs_config_init()); + try std.testing.expect(result = avs_status_enable(status, config)); + try std.testing.expect(result.state == AvsState.enabled); + try std.testing.expect(result.mode == AvsMode.automatic); +} + +test "avs_status_adjust" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_adjust(status, 10)); + try std.testing.expect(result.state == AvsState.adjusting); + try std.testing.expect(result.level == 10); +} + +test "avs_status_settled" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_settled(status, 1000)); + try std.testing.expect(result.state == AvsState.settled); + try std.testing.expect(result.current_mv == 1000); +} + +test "avs_status_fault" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_fault(status, 1)); + try std.testing.expect(result.state == AvsState.fault); + try std.testing.expect(result.fault_code == 1); +} + +test "avs_control_pins_start" { + try std.testing.expect(avs_control_pins_start() == CONTROL_PIN_OFFSET); +} + +test "avs_control_pins_end" { + try std.testing.expect(avs_control_pins_end() == CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT - 1); +} + +test "avs_is_control_pin_true" { + try std.testing.expect(avs_is_control_pin(16) == true); + try std.testing.expect(avs_is_control_pin(21) == true); +} + +test "avs_is_control_pin_false" { + try std.testing.expect(avs_is_control_pin(15) == false); + try std.testing.expect(avs_is_control_pin(22) == false); +} + +test "avs_control_pin_for_bit" { + try std.testing.expect(avs_control_pin_for_bit(0) == CONTROL_PIN_OFFSET); + try std.testing.expect(avs_control_pin_for_bit(5) == CONTROL_PIN_OFFSET + 5); +} + +test "avs_control_pin_for_bit_clamp" { + try std.testing.expect(avs_control_pin_for_bit(10) == CONTROL_PIN_OFFSET); +} + +test "encode_avs_48_cmd" { + given encoded = encode_avs_48_cmd(0x10, 20, 0) + try std.testing.expect((encoded >> 8) == 0x10); +} + +test "decode_avs_48_cmd" { + given decoded = decode_avs_48_cmd(0x1050) + try std.testing.expect(decoded.opcode == 0x10); + try std.testing.expect(decoded.level == 20); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant voltage_range_positive + assert VOLTAGE_MIN_MV > 0 and VOLTAGE_MAX_MV > VOLTAGE_MIN_MV + +invariant voltage_step_positive + assert VOLTAGE_STEP_MV > 0 + +invariant voltage_levels_calculated + try std.testing.expect(VOLTAGE_LEVELS == ((VOLTAGE_MAX_MV - VOLTAGE_MIN_MV) / VOLTAGE_STEP_MV) + 1); + +invariant target_voltage_in_range + assert avs_voltage_valid(TARGET_MV) + +invariant hysteresis_less_than_step + assert HYSTERESIS_MV < VOLTAGE_STEP_MV * 3 + +invariant control_pins_valid_range + assert CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT <= PINS_48 + +invariant control_pin_count_six + assert CONTROL_PIN_COUNT == 6 + +invariant avs_voltage_from_level_roundtrip + given level = 15 + try std.testing.expect(voltage = avs_voltage_from_level(level)); + try std.testing.expect(result_level = avs_level_from_voltage(voltage)); + try std.testing.expect(result_level == level); + +invariant avs_voltage_in_hysteresis_exact + try std.testing.expect(avs_voltage_in_hysteresis(TARGET_MV, TARGET_MV, 0) == true); + +invariant avs_should_adjust_false_at_exact_target + try std.testing.expect(avs_should_adjust(TARGET_MV, TARGET_MV, HYSTERESIS_MV) == false); + +invariant avs_status_init_disabled + given status = avs_status_init() + try std.testing.expect(status.state == AvsState.disabled); + +invariant avs_status_enable_sets_mode + given status = avs_status_init() + try std.testing.expect(config = avs_config_manual(950)); + try std.testing.expect(result = avs_status_enable(status, config)); + try std.testing.expect(result.mode == AvsMode.manual); + +invariant avs_status_adjust_changes_level + given status = avs_status_init() + try std.testing.expect(result = avs_status_adjust(status, 25)); + try std.testing.expect(result.level == 25); + +invariant avs_is_control_pin_monotonic + try std.testing.expect(avs_control_pin_for_bit(3) > avs_control_pin_for_bit(2)); + +invariant avs_control_pin_for_bit_start_at_offset + try std.testing.expect(avs_control_pin_for_bit(0) == CONTROL_PIN_OFFSET); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench avs_voltage_from_level_latency + measure: nanoseconds to avs_voltage_from_level(20) + target: < 20ns + +bench avs_level_from_voltage_latency + measure: nanoseconds to avs_level_from_voltage(1000) + target: < 20ns + +bench avs_control_code_from_level_latency + measure: nanoseconds to avs_control_code_from_level(20) + target: < 15ns + +bench avs_voltage_valid_latency + measure: nanoseconds to avs_voltage_valid(1000) + target: < 15ns + +bench avs_voltage_in_hysteresis_latency + measure: nanoseconds to avs_voltage_in_hysteresis(1000, 1000, 20) + target: < 25ns + +bench avs_config_init_latency + measure: nanoseconds to avs_config_init() + target: < 30ns + +bench avs_status_init_latency + measure: nanoseconds to avs_status_init() + target: < 30ns + +bench avs_status_adjust_latency + measure: nanoseconds to avs_status_adjust(avs_status_init(), 20) + target: < 30ns + +bench avs_is_control_pin_latency + measure: nanoseconds to avs_is_control_pin(18) + target: < 15ns + +bench encode_avs_48_cmd_latency + measure: nanoseconds to encode_avs_48_cmd(0x10, 20, 0) + target: < 20ns + +bench decode_avs_48_cmd_latency + measure: nanoseconds to decode_avs_48_cmd(0x1050) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/avs_controller_96.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/avs_controller_96.t27 new file mode 100644 index 0000000000..16b47c6bdc --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/avs_controller_96.t27 @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: Apache-2.0 +; avs_controller_96.t27 — 96-pin AVS Controller +; Adaptive voltage scaling controller for 96-pin configuration +; φ² + 1/φ² = 3 | TRINITY + +module avs-controller-96; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const PINS_96 : u8 = 96; +pub const VOLTAGE_MIN_MV : u16 = 700; // 0.7V minimum +pub const VOLTAGE_MAX_MV : u16 = 1300; // 1.3V maximum +pub const VOLTAGE_STEP_MV : u16 = 5; // 5mV step size +pub const VOLTAGE_LEVELS : u8 = 121; // (1300-700)/5 + 1 + +pub const TARGET_MV : u16 = 1000; // 1.0V target +pub const HYSTERESIS_MV : u16 = 15; // 15mV hysteresis +pub const SETTLING_US : u16 = 80; // 80us settling time + +pub const CONTROL_PIN_OFFSET : u8 = 64; // AVS control pins start at 64 +pub const CONTROL_PIN_COUNT : u8 = 7; // 7 control pins for 96-pin config + +pub const FREQ_MIN_KHZ : u32 = 10000; // 10 MHz minimum +pub const FREQ_MAX_KHZ : u32 = 200000; // 200 MHz maximum + +pub const DUAL_CHANNEL : bool = true; // 96-pin supports dual channels +pub const CHANNEL_PIN_GROUPS : u8 = 2; // 2 groups of control pins + +// ============================================================================ +// Types +// ============================================================================ + +pub const AvsChannel = enum(u8) { + channel0 = 0, + channel1 = 1, + both = 2, +} + +pub const AvsState = enum(u8) { + disabled = 0, + enabled = 1, + adjusting = 2, + settled = 3, + fault = 4, + calibrating = 5, // 96-pin supports calibration +} + +pub const AvsMode = enum(u8) { + manual = 0, + automatic = 1, + adaptive = 2, + dual_independent = 3, // 96-pin exclusive mode +} + +pub const AvsChannelConfig = struct { + channel : AvsChannel, + enabled : bool, + target_mv : u16, + current_mv : u16, + level : u8, +} + +pub const AvsPinConfig = struct { + pin_index : u8, + is_output : bool, + function : u8, // 0=control, 1=feedback, 2=enable, 3=calibration +} + +pub const AvsVoltageLevel = struct { + level : u8, // 0 to 120 + voltage_mv : u16, + control_code : u8, // 7-bit code for control pins +} + +pub const AvsStatus = struct { + state : AvsState, + mode : AvsMode, + channel0 : AvsChannelConfig, + channel1 : AvsChannelConfig, + fault_code : u8, + calibration_complete : bool, +} + +pub const AvsConfig = struct { + mode : AvsMode, + min_mv : u16, + max_mv : u16, + target_mv : u16, + hysteresis_mv : u16, + settling_us : u16, + enable_dual_channel : bool, +} + +// ============================================================================ +// Voltage Level Functions +// ============================================================================ + +// avs_voltage_from_level(level: u8) -> u16 +// Convert voltage level to millivolts +pub fn avs_voltage_from_level(level: u8) -> u16 { + return VOLTAGE_MIN_MV + @as(u16, level) * VOLTAGE_STEP_MV; +} + +// avs_level_from_voltage(voltage_mv: u16) -> u8 +// Convert voltage to level +pub fn avs_level_from_voltage(voltage_mv: u16) -> u8 { + var mv = voltage_mv; + if (mv < VOLTAGE_MIN_MV) { + mv = VOLTAGE_MIN_MV; + } else if (mv > VOLTAGE_MAX_MV) { + mv = VOLTAGE_MAX_MV; + } + return @as(u8, (mv - VOLTAGE_MIN_MV) / VOLTAGE_STEP_MV); +} + +// avs_control_code_from_level(level: u8) -> u8 +// Generate 7-bit control code for voltage level +pub fn avs_control_code_from_level(level: u8) -> u8 { + return level & 0x7F; +} + +// avs_level_from_control_code(code: u8) -> u8 +// Decode level from 7-bit control code +pub fn avs_level_from_control_code(code: u8) -> u8 { + return code & 0x7F; +} + +// ============================================================================ +// Voltage Validation +// ============================================================================ + +// avs_voltage_valid(voltage_mv: u16) -> bool +// Check if voltage is within valid range +pub fn avs_voltage_valid(voltage_mv: u16) -> bool { + return voltage_mv >= VOLTAGE_MIN_MV and voltage_mv <= VOLTAGE_MAX_MV; +} + +// avs_voltage_in_hysteresis(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool +// Check if current voltage is within hysteresis band of target +pub fn avs_voltage_in_hysteresis(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool { + if (current_mv > target_mv) { + return (current_mv - target_mv) <= hysteresis_mv; + } else { + return (target_mv - current_mv) <= hysteresis_mv; + } +} + +// avs_should_adjust(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool +// Check if voltage should be adjusted +pub fn avs_should_adjust(current_mv: u16, target_mv: u16, hysteresis_mv: u16) -> bool { + return not avs_voltage_in_hysteresis(current_mv, target_mv, hysteresis_mv); +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// avs_config_init() -> AvsConfig +// Initialize AVS configuration +pub fn avs_config_init() -> AvsConfig { + return AvsConfig { + .mode = AvsMode.automatic, + .min_mv = VOLTAGE_MIN_MV, + .max_mv = VOLTAGE_MAX_MV, + .target_mv = TARGET_MV, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + .enable_dual_channel = false, + }; +} + +// avs_config_dual(target0_mv: u16, target1_mv: u16) -> AvsConfig +// Create dual-channel AVS configuration +pub fn avs_config_dual(target0_mv: u16, target1_mv: u16) -> AvsConfig { + return AvsConfig { + .mode = AvsMode.dual_independent, + .min_mv = VOLTAGE_MIN_MV, + .max_mv = VOLTAGE_MAX_MV, + .target_mv = (target0_mv + target1_mv) / 2, // Average target + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + .enable_dual_channel = true, + }; +} + +// avs_config_adaptive(min_mv: u16, max_mv: u16) -> AvsConfig +// Create adaptive AVS configuration +pub fn avs_config_adaptive(min_mv: u16, max_mv: u16) -> AvsConfig { + return AvsConfig { + .mode = AvsMode.adaptive, + .min_mv = min_mv, + .max_mv = max_mv, + .target_mv = (min_mv + max_mv) / 2, + .hysteresis_mv = HYSTERESIS_MV, + .settling_us = SETTLING_US, + .enable_dual_channel = false, + }; +} + +// ============================================================================ +// Channel Functions +// ============================================================================ + +// avs_channel_init(channel: AvsChannel) -> AvsChannelConfig +// Initialize channel configuration +pub fn avs_channel_init(channel: AvsChannel) -> AvsChannelConfig { + return AvsChannelConfig { + .channel = channel, + .enabled = false, + .target_mv = TARGET_MV, + .current_mv = VOLTAGE_MIN_MV, + .level = 0, + }; +} + +// avs_channel_enable(channel: AvsChannelConfig, target_mv: u16) -> AvsChannelConfig +// Enable channel +pub fn avs_channel_enable(channel: AvsChannelConfig, target_mv: u16) -> AvsChannelConfig { + return AvsChannelConfig { + .channel = channel.channel, + .enabled = true, + .target_mv = target_mv, + .current_mv = channel.current_mv, + .level = channel.level, + }; +} + +// avs_channel_adjust(channel: AvsChannelConfig, new_level: u8) -> AvsChannelConfig +// Adjust channel to new level +pub fn avs_channel_adjust(channel: AvsChannelConfig, new_level: u8) -> AvsChannelConfig { + const new_mv = avs_voltage_from_level(new_level); + return AvsChannelConfig { + .channel = channel.channel, + .enabled = channel.enabled, + .target_mv = channel.target_mv, + .current_mv = new_mv, + .level = new_level, + }; +} + +// ============================================================================ +// Status Functions +// ============================================================================ + +// avs_status_init() -> AvsStatus +// Initialize AVS status +pub fn avs_status_init() -> AvsStatus { + return AvsStatus { + .state = AvsState.disabled, + .mode = AvsMode.manual, + .channel0 = avs_channel_init(AvsChannel.channel0), + .channel1 = avs_channel_init(AvsChannel.channel1), + .fault_code = 0, + .calibration_complete = false, + }; +} + +// avs_status_enable(status: AvsStatus, config: AvsConfig) -> AvsStatus +// Enable AVS controller +pub fn avs_status_enable(status: AvsStatus, config: AvsConfig) -> AvsStatus { + var ch0 = status.channel0; + var ch1 = status.channel1; + + if (config.enable_dual_channel) { + ch0 = avs_channel_enable(ch0, config.target_mv); + ch1 = avs_channel_enable(ch1, config.target_mv); + } else { + ch0 = avs_channel_enable(ch0, config.target_mv); + } + + return AvsStatus { + .state = AvsState.enabled, + .mode = config.mode, + .channel0 = ch0, + .channel1 = ch1, + .fault_code = 0, + .calibration_complete = status.calibration_complete, + }; +} + +// avs_status_calibrate(status: AvsStatus) -> AvsStatus +// Enter calibration mode +pub fn avs_status_calibrate(status: AvsStatus) -> AvsStatus { + return AvsStatus { + .state = AvsState.calibrating, + .mode = status.mode, + .channel0 = status.channel0, + .channel1 = status.channel1, + .fault_code = status.fault_code, + .calibration_complete = false, + }; +} + +// avs_status_calibration_complete(status: AvsStatus) -> AvsStatus +// Mark calibration complete +pub fn avs_status_calibration_complete(status: AvsStatus) -> AvsStatus { + return AvsStatus { + .state = AvsState.enabled, + .mode = status.mode, + .channel0 = status.channel0, + .channel1 = status.channel1, + .fault_code = status.fault_code, + .calibration_complete = true, + }; +} + +// avs_status_fault(status: AvsStatus, fault_code: u8) -> AvsStatus +// Mark fault condition +pub fn avs_status_fault(status: AvsStatus, fault_code: u8) -> AvsStatus { + return AvsStatus { + .state = AvsState.fault, + .mode = status.mode, + .channel0 = status.channel0, + .channel1 = status.channel1, + .fault_code = fault_code, + .calibration_complete = status.calibration_complete, + }; +} + +// ============================================================================ +// Pin Functions +// ============================================================================ + +// avs_control_pins_start() -> u8 +// Get starting pin index for control pins +pub fn avs_control_pins_start() -> u8 { + return CONTROL_PIN_OFFSET; +} + +// avs_control_pins_end() -> u8 +// Get ending pin index for control pins (primary) +pub fn avs_control_pins_end() -> u8 { + return CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT - 1; +} + +// avs_control_pins_secondary_start() -> u8 +// Get starting pin index for secondary channel +pub fn avs_control_pins_secondary_start() -> u8 { + return CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT; +} + +// avs_control_pins_secondary_end() -> u8 +// Get ending pin index for secondary channel +pub fn avs_control_pins_secondary_end() -> u8 { + return avs_control_pins_secondary_start() + CONTROL_PIN_COUNT - 1; +} + +// avs_is_control_pin(pin_index: u8) -> bool +// Check if pin is a control pin (either channel) +pub fn avs_is_control_pin(pin_index: u8) -> bool { + return avs_is_control_pin_primary(pin_index) or avs_is_control_pin_secondary(pin_index); +} + +// avs_is_control_pin_primary(pin_index: u8) -> bool +// Check if pin is a primary control pin +pub fn avs_is_control_pin_primary(pin_index: u8) -> bool { + return pin_index >= avs_control_pins_start() and pin_index <= avs_control_pins_end(); +} + +// avs_is_control_pin_secondary(pin_index: u8) -> bool +// Check if pin is a secondary control pin +pub fn avs_is_control_pin_secondary(pin_index: u8) -> bool { + return pin_index >= avs_control_pins_secondary_start() and pin_index <= avs_control_pins_secondary_end(); +} + +// avs_control_pin_for_bit(bit_index: u8, channel: AvsChannel) -> u8 +// Get control pin for a bit index and channel +pub fn avs_control_pin_for_bit(bit_index: u8, channel: AvsChannel) -> u8 { + if (bit_index >= CONTROL_PIN_COUNT) { + bit_index = 0; + } + + if (channel == AvsChannel.channel1) { + return avs_control_pins_secondary_start() + bit_index; + } else { + return avs_control_pins_start() + bit_index; + } +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_avs_96_cmd(opcode: u8, level: u8, channel: u8) -> u16 +// Encode AVS 96-pin command +pub fn encode_avs_96_cmd(opcode: u8, level: u8, channel: u8) u16 { + // Format: [OP:8][LEVEL:7][CHANNEL:1] + const op_field : u16 = @as(u16, opcode) << 8; + const level_field : u16 = @as(u16, level & 0x7F) << 1; + const channel_field : u16 = @as(u16, channel & 0x01); + return op_field | level_field | channel_field; +} + +// decode_avs_96_cmd(encoded: u16) -> struct { opcode: u8, level: u8, channel: u8 } +// Decode AVS 96-pin command +pub fn decode_avs_96_cmd(encoded: u16) -> struct { opcode: u8, level: u8, channel: u8 } { + const opcode : u8 = @as(u8, @truncate((encoded >> 8) & 0xFF)); + const level : u8 = @as(u8, @truncate((encoded >> 1) & 0x7F)); + const channel : u8 = @as(u8, @truncate(encoded & 0x01)); + return .{ .opcode = opcode, .level = level, .channel = channel }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "pins_96_constant" { + try std.testing.expect(PINS_96 == 96); +} + +test "voltage_constants" { + try std.testing.expect(VOLTAGE_MIN_MV == 700); + try std.testing.expect(VOLTAGE_MAX_MV == 1300); + try std.testing.expect(VOLTAGE_STEP_MV == 5); + try std.testing.expect(VOLTAGE_LEVELS == 121); +} + +test "target_voltage_constant" { + try std.testing.expect(TARGET_MV == 1000); +} + +test "control_pin_constants" { + try std.testing.expect(CONTROL_PIN_OFFSET == 64); + try std.testing.expect(CONTROL_PIN_COUNT == 7); +} + +test "dual_channel_constant" { + assert DUAL_CHANNEL == true +} + +test "avs_voltage_from_level_zero" { + try std.testing.expect(avs_voltage_from_level(0) == VOLTAGE_MIN_MV); +} + +test "avs_voltage_from_level_max" { + given max_level = VOLTAGE_LEVELS - 1 + try std.testing.expect(avs_voltage_from_level(max_level) == VOLTAGE_MAX_MV); +} + +test "avs_level_from_voltage_target" { + given level = avs_level_from_voltage(TARGET_MV) + try std.testing.expect(avs_voltage_from_level(level) == TARGET_MV); +} + +test "avs_level_from_voltage_clamp_low" { + try std.testing.expect(avs_level_from_voltage(600) == 0); +} + +test "avs_level_from_voltage_clamp_high" { + try std.testing.expect(avs_level_from_voltage(1500) == VOLTAGE_LEVELS - 1); +} + +test "avs_control_code_from_level_max" { + given code = avs_control_code_from_level(127) + try std.testing.expect(code == 127); +} + +test "avs_control_code_from_level_clamp" { + given code = avs_control_code_from_level(200) + try std.testing.expect(code == 72 // 200 & 0x7F); +} + +test "avs_level_from_control_code_roundtrip" { + given level = 50 + try std.testing.expect(code = avs_control_code_from_level(level)); + try std.testing.expect(decoded = avs_level_from_control_code(code)); + try std.testing.expect(decoded == level); +} + +test "avs_voltage_valid_in_range" { + try std.testing.expect(avs_voltage_valid(750) == true); + try std.testing.expect(avs_voltage_valid(1000) == true); + try std.testing.expect(avs_voltage_valid(1250) == true); +} + +test "avs_voltage_valid_out_of_range" { + try std.testing.expect(avs_voltage_valid(600) == false); + try std.testing.expect(avs_voltage_valid(1400) == false); +} + +test "avs_voltage_in_hysteresis_true" { + try std.testing.expect(avs_voltage_in_hysteresis(1000, 1000, 15) == true); + try std.testing.expect(avs_voltage_in_hysteresis(1010, 1000, 15) == true); + try std.testing.expect(avs_voltage_in_hysteresis(990, 1000, 15) == true); +} + +test "avs_voltage_in_hysteresis_false" { + try std.testing.expect(avs_voltage_in_hysteresis(1030, 1000, 15) == false); + try std.testing.expect(avs_voltage_in_hysteresis(970, 1000, 15) == false); +} + +test "avs_config_init_structure" { + given config = avs_config_init() + try std.testing.expect(config.mode == AvsMode.automatic); + try std.testing.expect(config.target_mv == TARGET_MV); + try std.testing.expect(config.enable_dual_channel == false); +} + +test "avs_config_dual_structure" { + given config = avs_config_dual(950, 1050) + try std.testing.expect(config.mode == AvsMode.dual_independent); + try std.testing.expect(config.enable_dual_channel == true); +} + +test "avs_channel_init_structure" { + given channel = avs_channel_init(AvsChannel.channel0) + try std.testing.expect(channel.channel == AvsChannel.channel0); + try std.testing.expect(channel.enabled == false); +} + +test "avs_channel_enable" { + given channel = avs_channel_init(AvsChannel.channel0) + try std.testing.expect(result = avs_channel_enable(channel, 950)); + try std.testing.expect(result.enabled == true); + try std.testing.expect(result.target_mv == 950); +} + +test "avs_channel_adjust" { + given channel = avs_channel_init(AvsChannel.channel0) + try std.testing.expect(result = avs_channel_adjust(channel, 30)); + try std.testing.expect(result.level == 30); +} + +test "avs_status_init_structure" { + given status = avs_status_init() + try std.testing.expect(status.state == AvsState.disabled); + try std.testing.expect(status.calibration_complete == false); +} + +test "avs_status_enable" { + given status = avs_status_init() + try std.testing.expect(config = avs_config_init()); + try std.testing.expect(result = avs_status_enable(status, config)); + try std.testing.expect(result.state == AvsState.enabled); + try std.testing.expect(result.channel0.enabled == true); +} + +test "avs_status_enable_dual" { + given status = avs_status_init() + try std.testing.expect(config = avs_config_dual(950, 1050)); + try std.testing.expect(result = avs_status_enable(status, config)); + try std.testing.expect(result.channel0.enabled == true); + try std.testing.expect(result.channel1.enabled == true); +} + +test "avs_status_calibrate" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_calibrate(status)); + try std.testing.expect(result.state == AvsState.calibrating); + try std.testing.expect(result.calibration_complete == false); +} + +test "avs_status_calibration_complete" { + given status = avs_status_init() + try std.testing.expect(result = avs_status_calibration_complete(status)); + try std.testing.expect(result.calibration_complete == true); +} + +test "avs_control_pins_start" { + try std.testing.expect(avs_control_pins_start() == CONTROL_PIN_OFFSET); +} + +test "avs_control_pins_end" { + try std.testing.expect(avs_control_pins_end() == CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT - 1); +} + +test "avs_control_pins_secondary_start" { + try std.testing.expect(avs_control_pins_secondary_start() == CONTROL_PIN_OFFSET + CONTROL_PIN_COUNT); +} + +test "avs_is_control_pin_primary_true" { + try std.testing.expect(avs_is_control_pin_primary(64) == true); + try std.testing.expect(avs_is_control_pin_primary(70) == true); +} + +test "avs_is_control_pin_primary_false" { + try std.testing.expect(avs_is_control_pin_primary(63) == false); + try std.testing.expect(avs_is_control_pin_primary(71) == false); +} + +test "avs_is_control_pin_secondary_true" { + try std.testing.expect(avs_is_control_pin_secondary(71) == true); + try std.testing.expect(avs_is_control_pin_secondary(77) == true); +} + +test "avs_is_control_pin_secondary_false" { + try std.testing.expect(avs_is_control_pin_secondary(70) == false); + try std.testing.expect(avs_is_control_pin_secondary(78) == false); +} + +test "avs_is_control_pin_true" { + try std.testing.expect(avs_is_control_pin(64) == true); + try std.testing.expect(avs_is_control_pin(75) == true); +} + +test "avs_is_control_pin_false" { + try std.testing.expect(avs_is_control_pin(63) == false); + try std.testing.expect(avs_is_control_pin(78) == false); +} + +test "avs_control_pin_for_bit_primary" { + try std.testing.expect(avs_control_pin_for_bit(0, AvsChannel.channel0) == CONTROL_PIN_OFFSET); + try std.testing.expect(avs_control_pin_for_bit(6, AvsChannel.channel0) == CONTROL_PIN_OFFSET + 6); +} + +test "avs_control_pin_for_bit_secondary" { + try std.testing.expect(avs_control_pin_for_bit(0, AvsChannel.channel1) == avs_control_pins_secondary_start()); + try std.testing.expect(avs_control_pin_for_bit(6, AvsChannel.channel1) == avs_control_pins_secondary_start() + 6); +} + +test "encode_avs_96_cmd" { + given encoded = encode_avs_96_cmd(0x20, 30, 1) + try std.testing.expect((encoded >> 8) == 0x20); +} + +test "decode_avs_96_cmd" { + given decoded = decode_avs_96_cmd(0x203D) + try std.testing.expect(decoded.opcode == 0x20); + try std.testing.expect(decoded.level == 30); + try std.testing.expect(decoded.channel == 1); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant voltage_range_positive + assert VOLTAGE_MIN_MV > 0 and VOLTAGE_MAX_MV > VOLTAGE_MIN_MV + +invariant voltage_step_positive + assert VOLTAGE_STEP_MV > 0 + +invariant voltage_levels_calculated + try std.testing.expect(VOLTAGE_LEVELS == ((VOLTAGE_MAX_MV - VOLTAGE_MIN_MV) / VOLTAGE_STEP_MV) + 1); + +invariant target_voltage_in_range + assert avs_voltage_valid(TARGET_MV) + +invariant control_pin_count_seven + assert CONTROL_PIN_COUNT == 7 + +invariant control_pins_valid_range + assert avs_control_pins_secondary_end() < PINS_96 + +invariant dual_channel_enabled + assert DUAL_CHANNEL == true + +invariant avs_status_init_disabled + given status = avs_status_init() + try std.testing.expect(status.state == AvsState.disabled); + +invariant avs_status_calibrate_sets_state + given status = avs_status_init() + try std.testing.expect(result = avs_status_calibrate(status)); + try std.testing.expect(result.state == AvsState.calibrating); + +invariant avs_is_control_pin_primary_excludes_secondary + given pin = 70 // Last primary pin + assert avs_is_control_pin_primary(pin) == true + try std.testing.expect(avs_is_control_pin_secondary(pin) == false); + +invariant avs_is_control_pin_secondary_excludes_primary + given pin = 71 // First secondary pin + assert avs_is_control_pin_secondary(pin) == true + try std.testing.expect(avs_is_control_pin_primary(pin) == false); + +invariant avs_control_pin_for_bit_monotonic + try std.testing.expect(avs_control_pin_for_bit(3, AvsChannel.channel0) < avs_control_pin_for_bit(4, AvsChannel.channel0)); + +invariant avs_config_dual_enables_both_channels + given config = avs_config_dual(950, 1050) + assert config.enable_dual_channel == true + +invariant avs_config_init_single_channel + given config = avs_config_init() + assert config.enable_dual_channel == false + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench avs_voltage_from_level_latency + measure: nanoseconds to avs_voltage_from_level(40) + target: < 20ns + +bench avs_level_from_voltage_latency + measure: nanoseconds to avs_level_from_voltage(1000) + target: < 20ns + +bench avs_control_code_from_level_latency + measure: nanoseconds to avs_control_code_from_level(40) + target: < 15ns + +bench avs_voltage_valid_latency + measure: nanoseconds to avs_voltage_valid(1000) + target: < 15ns + +bench avs_config_init_latency + measure: nanoseconds to avs_config_init() + target: < 30ns + +bench avs_channel_init_latency + measure: nanoseconds to avs_channel_init(AvsChannel.channel0) + target: < 20ns + +bench avs_status_init_latency + measure: nanoseconds to avs_status_init() + target: < 30ns + +bench avs_status_enable_latency + measure: nanoseconds to avs_status_enable(avs_status_init(), avs_config_init()) + target: < 40ns + +bench avs_status_calibrate_latency + measure: nanoseconds to avs_status_calibrate(avs_status_init()) + target: < 20ns + +bench avs_is_control_pin_latency + measure: nanoseconds to avs_is_control_pin(70) + target: < 15ns + +bench avs_control_pin_for_bit_latency + measure: nanoseconds to avs_control_pin_for_bit(3, AvsChannel.channel0) + target: < 20ns + +bench encode_avs_96_cmd_latency + measure: nanoseconds to encode_avs_96_cmd(0x20, 30, 1) + target: < 20ns + +bench decode_avs_96_cmd_latency + measure: nanoseconds to decode_avs_96_cmd(0x203D) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/avs_reconf.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/avs_reconf.t27 new file mode 100644 index 0000000000..61d90cf865 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/avs_reconf.t27 @@ -0,0 +1,702 @@ +// SPDX-License-Identifier: Apache-2.0 +; avs_reconf.t27 — AVS Reconfiguration Module +; Dynamic AVS voltage/frequency reconfiguration support +; φ² + 1/φ² = 3 | TRINITY + +module avs-reconf; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const RECONF_OPCODE_BASE : u8 = 0xF0; // AVS reconf opcodes start at 0xF0 + +pub const RECONF_OP_SET_VOLTAGE : u8 = 0x00; +pub const RECONF_OP_SET_FREQUENCY : u8 = 0x01; +pub const RECONF_OP_SET_MODE : u8 = 0x02; +pub const RECONF_OP_RESET : u8 = 0x03; +pub const RECONF_OP_QUERY : u8 = 0x04; +pub const RECONF_OP_SAVE_CONFIG : u8 = 0x05; +pub const RECONF_OP_LOAD_CONFIG : u8 = 0x06; +pub const RECONF_OP_CALIBRATE : u8 = 0x07; +pub const RECONF_OP_MAX : u8 = 0x08; + +pub const RECONF_MODE_MANUAL : u8 = 0; +pub const RECONF_MODE_AUTOMATIC : u8 = 1; +pub const RECONF_MODE_PERFORMANCE : u8 = 2; +pub const RECONF_MODE_POWER_SAVING : u8 = 3; +pub const RECONF_MODE_ADAPTIVE : u8 = 4; +pub const RECONF_MODE_MAX : u8 = 5; + +pub const RECONF_STATE_IDLE : u8 = 0; +pub const RECONF_STATE_BUSY : u8 = 1; +pub const RECONF_STATE_ERROR : u8 = 2; +pub const RECONF_STATE_CALIBRATING : u8 = 3; + +pub const VOLTAGE_MIN_MV : u16 = 700; +pub const VOLTAGE_MAX_MV : u16 = 1300; +pub const FREQ_MIN_KHZ : u32 = 10000; +pub const FREQ_MAX_KHZ : u32 = 200000; + +pub const RECONF_TIMEOUT_MS : u16 = 1000; +pub const RECONF_RETRY_COUNT : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const ReconfOp = enum(u8) { + set_voltage = RECONF_OP_SET_VOLTAGE, + set_frequency = RECONF_OP_SET_FREQUENCY, + set_mode = RECONF_OP_SET_MODE, + reset = RECONF_OP_RESET, + query = RECONF_OP_QUERY, + save_config = RECONF_OP_SAVE_CONFIG, + load_config = RECONF_OP_LOAD_CONFIG, + calibrate = RECONF_OP_CALIBRATE, +} + +pub const ReconfMode = enum(u8) { + manual = RECONF_MODE_MANUAL, + automatic = RECONF_MODE_AUTOMATIC, + performance = RECONF_MODE_PERFORMANCE, + power_saving = RECONF_MODE_POWER_SAVING, + adaptive = RECONF_MODE_ADAPTIVE, +} + +pub const ReconfState = enum(u8) { + idle = RECONF_STATE_IDLE, + busy = RECONF_STATE_BUSY, + error = RECONF_STATE_ERROR, + calibrating = RECONF_STATE_CALIBRATING, +} + +pub const ReconfConfig = struct { + mode : ReconfMode, + voltage_mv : u16, + freq_khz : u32, + auto_scale : bool, + min_voltage_mv : u16, + max_voltage_mv : u16, + min_freq_khz : u32, + max_freq_khz : u32, +} + +pub const ReconfStatus = struct { + state : ReconfState, + current_voltage_mv : u16, + current_freq_khz : u32, + last_op : ReconfOp, + error_code : u8, + retry_count : u8, +} + +pub const ReconfCommand = struct { + op : ReconfOp, + param1 : u32, + param2 : u32, + timeout_ms : u16, +} + +pub const ReconfResult = struct { + success : bool, + error_code : u8, + cycles_taken : u32, +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// avs_reconf_config_init() -> ReconfConfig +// Initialize AVS reconfiguration config +pub fn avs_reconf_config_init() -> ReconfConfig { + return ReconfConfig { + .mode = ReconfMode.manual, + .voltage_mv = 1000, + .freq_khz = 100000, + .auto_scale = false, + .min_voltage_mv = VOLTAGE_MIN_MV, + .max_voltage_mv = VOLTAGE_MAX_MV, + .min_freq_khz = FREQ_MIN_KHZ, + .max_freq_khz = FREQ_MAX_KHZ, + }; +} + +// avs_reconf_config_performance() -> ReconfConfig +// Create performance-oriented config +pub fn avs_reconf_config_performance() -> ReconfConfig { + return ReconfConfig { + .mode = ReconfMode.performance, + .voltage_mv = VOLTAGE_MAX_MV, + .freq_khz = FREQ_MAX_KHZ, + .auto_scale = true, + .min_voltage_mv = VOLTAGE_MIN_MV + 100, + .max_voltage_mv = VOLTAGE_MAX_MV, + .min_freq_khz = FREQ_MAX_KHZ / 2, + .max_freq_khz = FREQ_MAX_KHZ, + }; +} + +// avs_reconf_config_power_saving() -> ReconfConfig +// Create power-saving config +pub fn avs_reconf_config_power_saving() -> ReconfConfig { + return ReconfConfig { + .mode = ReconfMode.power_saving, + .voltage_mv = VOLTAGE_MIN_MV + 50, + .freq_khz = FREQ_MIN_KHZ * 2, + .auto_scale = true, + .min_voltage_mv = VOLTAGE_MIN_MV, + .max_voltage_mv = VOLTAGE_MIN_MV + 200, + .min_freq_khz = FREQ_MIN_KHZ, + .max_freq_khz = FREQ_MIN_KHZ * 5, + }; +} + +// avs_reconf_config_adaptive() -> ReconfConfig +// Create adaptive config +pub fn avs_reconf_config_adaptive() -> ReconfConfig { + return ReconfConfig { + .mode = ReconfMode.adaptive, + .voltage_mv = 1000, + .freq_khz = 100000, + .auto_scale = true, + .min_voltage_mv = VOLTAGE_MIN_MV, + .max_voltage_mv = VOLTAGE_MAX_MV, + .min_freq_khz = FREQ_MIN_KHZ, + .max_freq_khz = FREQ_MAX_KHZ, + }; +} + +// ============================================================================ +// Validation Functions +// ============================================================================ + +// avs_reconf_voltage_valid(voltage_mv: u16) -> bool +// Check if voltage is valid +pub fn avs_reconf_voltage_valid(voltage_mv: u16) -> bool { + return voltage_mv >= VOLTAGE_MIN_MV and voltage_mv <= VOLTAGE_MAX_MV; +} + +// avs_reconf_freq_valid(freq_khz: u32) -> bool +// Check if frequency is valid +pub fn avs_reconf_freq_valid(freq_khz: u32) -> bool { + return freq_khz >= FREQ_MIN_KHZ and freq_khz <= FREQ_MAX_KHZ; +} + +// avs_reconf_config_valid(config: ReconfConfig) -> bool +// Check if config is valid +pub fn avs_reconf_config_valid(config: ReconfConfig) -> bool { + return avs_reconf_voltage_valid(config.voltage_mv) + try std.testing.expect(avs_reconf_freq_valid(config.freq_khz)); + try std.testing.expect(config.min_voltage_mv < config.max_voltage_mv); + try std.testing.expect(config.min_freq_khz < config.max_freq_khz;); +} + +// ============================================================================ +// Status Functions +// ============================================================================ + +// avs_reconf_status_init() -> ReconfStatus +// Initialize reconfiguration status +pub fn avs_reconf_status_init() -> ReconfStatus { + return ReconfStatus { + .state = ReconfState.idle, + .current_voltage_mv = VOLTAGE_MIN_MV, + .current_freq_khz = FREQ_MIN_KHZ, + .last_op = ReconfOp.reset, + .error_code = 0, + .retry_count = 0, + }; +} + +// avs_reconf_status_start_op(status: ReconfStatus, op: ReconfOp) -> ReconfStatus +// Start a reconfiguration operation +pub fn avs_reconf_status_start_op(status: ReconfStatus, op: ReconfOp) -> ReconfStatus { + return ReconfStatus { + .state = ReconfState.busy, + .current_voltage_mv = status.current_voltage_mv, + .current_freq_khz = status.current_freq_khz, + .last_op = op, + .error_code = 0, + .retry_count = status.retry_count, + }; +} + +// avs_reconf_status_complete(status: ReconfStatus, result: ReconfResult) -> ReconfStatus +// Complete a reconfiguration operation +pub fn avs_reconf_status_complete(status: ReconfStatus, result: ReconfResult) -> ReconfStatus { + var new_state = if (result.success) ReconfState.idle else ReconfState.error; + var new_retry_count = if (result.success) 0 else status.retry_count + 1; + + return ReconfStatus { + .state = new_state, + .current_voltage_mv = status.current_voltage_mv, + .current_freq_khz = status.current_freq_khz, + .last_op = status.last_op, + .error_code = if (result.success) 0 else result.error_code, + .retry_count = new_retry_count, + }; +} + +// avs_reconf_status_update_voltage(status: ReconfStatus, voltage_mv: u16) -> ReconfStatus +// Update voltage in status +pub fn avs_reconf_status_update_voltage(status: ReconfStatus, voltage_mv: u16) -> ReconfStatus { + return ReconfStatus { + .state = status.state, + .current_voltage_mv = voltage_mv, + .current_freq_khz = status.current_freq_khz, + .last_op = status.last_op, + .error_code = status.error_code, + .retry_count = status.retry_count, + }; +} + +// avs_reconf_status_update_freq(status: ReconfStatus, freq_khz: u32) -> ReconfStatus +// Update frequency in status +pub fn avs_reconf_status_update_freq(status: ReconfStatus, freq_khz: u32) -> ReconfStatus { + return ReconfStatus { + .state = status.state, + .current_voltage_mv = status.current_voltage_mv, + .current_freq_khz = freq_khz, + .last_op = status.last_op, + .error_code = status.error_code, + .retry_count = status.retry_count, + }; +} + +// ============================================================================ +// Command Functions +// ============================================================================ + +// avs_reconf_command_init() -> ReconfCommand +// Initialize reconfiguration command +pub fn avs_reconf_command_init() -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.reset, + .param1 = 0, + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// avs_reconf_command_set_voltage(voltage_mv: u16) -> ReconfCommand +// Create voltage set command +pub fn avs_reconf_command_set_voltage(voltage_mv: u16) -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.set_voltage, + .param1 = @as(u32, voltage_mv), + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// avs_reconf_command_set_freq(freq_khz: u32) -> ReconfCommand +// Create frequency set command +pub fn avs_reconf_command_set_freq(freq_khz: u32) -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.set_frequency, + .param1 = freq_khz, + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// avs_reconf_command_set_mode(mode: ReconfMode) -> ReconfCommand +// Create mode set command +pub fn avs_reconf_command_set_mode(mode: ReconfMode) -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.set_mode, + .param1 = @as(u32, mode), + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// avs_reconf_command_calibrate() -> ReconfCommand +// Create calibration command +pub fn avs_reconf_command_calibrate() -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.calibrate, + .param1 = 0, + .param2 = 0, + .timeout_ms = RECONF_TIMEOUT_MS * 5, // Calibration takes longer + }; +} + +// avs_reconf_command_save_config(config: ReconfConfig) -> ReconfCommand +// Create save config command +pub fn avs_reconf_command_save_config(config: ReconfConfig) -> ReconfCommand { + return ReconfCommand { + .op = ReconfOp.save_config, + .param1 = @as(u32, config.voltage_mv), + .param2 = config.freq_khz, + .timeout_ms = RECONF_TIMEOUT_MS, + }; +} + +// ============================================================================ +// Result Functions +// ============================================================================ + +// avs_reconf_result_success() -> ReconfResult +// Create success result +pub fn avs_reconf_result_success(cycles: u32) -> ReconfResult { + return ReconfResult { + .success = true, + .error_code = 0, + .cycles_taken = cycles, + }; +} + +// avs_reconf_result_error(error_code: u8, cycles: u32) -> ReconfResult +// Create error result +pub fn avs_reconf_result_error(error_code: u8, cycles: u32) -> ReconfResult { + return ReconfResult { + .success = false, + .error_code = error_code, + .cycles_taken = cycles, + }; +} + +// avs_reconf_should_retry(status: ReconfStatus) -> bool +// Check if should retry failed operation +pub fn avs_reconf_should_retry(status: ReconfStatus) -> bool { + return status.state == ReconfState.error and status.retry_count < RECONF_RETRY_COUNT; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_avs_reconf_cmd(op: u8, param1: u16, param2: u16) -> u32 +// Encode AVS reconfiguration command +pub fn encode_avs_reconf_cmd(op: u8, param1: u16, param2: u16) u32 { + // Format: [OP:8][PARAM1:12][PARAM2:12] + const op_field : u32 = @as(u32, op) << 24; + const param1_field : u32 = @as(u32, param1 & 0xFFF) << 12; + const param2_field : u32 = @as(u32, param2 & 0xFFF); + return op_field | param1_field | param2_field; +} + +// decode_avs_reconf_cmd(encoded: u32) -> struct { op: u8, param1: u16, param2: u16 } +// Decode AVS reconfiguration command +pub fn decode_avs_reconf_cmd(encoded: u32) -> struct { op: u8, param1: u16, param2: u16 } { + const op : u8 = @as(u8, @truncate((encoded >> 24) & 0xFF)); + const param1 : u16 = @as(u16, @truncate((encoded >> 12) & 0xFFF)); + const param2 : u16 = @as(u16, @truncate(encoded & 0xFFF)); + return .{ .op = op, .param1 = param1, .param2 = param2 }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "reconf_opcode_base" { + try std.testing.expect(RECONF_OPCODE_BASE == 0xF0); +} + +test "reconf_op_constants" { + try std.testing.expect(RECONF_OP_SET_VOLTAGE == 0); + try std.testing.expect(RECONF_OP_SET_FREQUENCY == 1); + try std.testing.expect(RECONF_OP_SET_MODE == 2); + try std.testing.expect(RECONF_OP_CALIBRATE == 7); +} + +test "reconf_mode_constants" { + try std.testing.expect(RECONF_MODE_MANUAL == 0); + try std.testing.expect(RECONF_MODE_PERFORMANCE == 2); + try std.testing.expect(RECONF_MODE_POWER_SAVING == 3); + try std.testing.expect(RECONF_MODE_ADAPTIVE == 4); +} + +test "reconf_state_constants" { + try std.testing.expect(RECONF_STATE_IDLE == 0); + try std.testing.expect(RECONF_STATE_BUSY == 1); + try std.testing.expect(RECONF_STATE_ERROR == 2); +} + +test "voltage_freq_constants" { + try std.testing.expect(VOLTAGE_MIN_MV == 700); + try std.testing.expect(VOLTAGE_MAX_MV == 1300); + try std.testing.expect(FREQ_MIN_KHZ == 10000); + try std.testing.expect(FREQ_MAX_KHZ == 200000); +} + +test "avs_reconf_config_init_structure" { + given config = avs_reconf_config_init() + try std.testing.expect(config.mode == ReconfMode.manual); + try std.testing.expect(config.voltage_mv == 1000); + try std.testing.expect(config.freq_khz == 100000); +} + +test "avs_reconf_config_performance_structure" { + given config = avs_reconf_config_performance() + try std.testing.expect(config.mode == ReconfMode.performance); + try std.testing.expect(config.voltage_mv == VOLTAGE_MAX_MV); + try std.testing.expect(config.auto_scale == true); +} + +test "avs_reconf_config_power_saving_structure" { + given config = avs_reconf_config_power_saving() + try std.testing.expect(config.mode == ReconfMode.power_saving); + try std.testing.expect(config.voltage_mv < 1000); + try std.testing.expect(config.auto_scale == true); +} + +test "avs_reconf_config_adaptive_structure" { + given config = avs_reconf_config_adaptive() + try std.testing.expect(config.mode == ReconfMode.adaptive); + try std.testing.expect(config.auto_scale == true); +} + +test "avs_reconf_voltage_valid_true" { + try std.testing.expect(avs_reconf_voltage_valid(800) == true); + try std.testing.expect(avs_reconf_voltage_valid(1000) == true); + try std.testing.expect(avs_reconf_voltage_valid(1200) == true); +} + +test "avs_reconf_voltage_valid_false" { + try std.testing.expect(avs_reconf_voltage_valid(600) == false); + try std.testing.expect(avs_reconf_voltage_valid(1400) == false); +} + +test "avs_reconf_freq_valid_true" { + try std.testing.expect(avs_reconf_freq_valid(20000) == true); + try std.testing.expect(avs_reconf_freq_valid(100000) == true); + try std.testing.expect(avs_reconf_freq_valid(150000) == true); +} + +test "avs_reconf_freq_valid_false" { + try std.testing.expect(avs_reconf_freq_valid(5000) == false); + try std.testing.expect(avs_reconf_freq_valid(300000) == false); +} + +test "avs_reconf_config_valid_true" { + given config = avs_reconf_config_init() + try std.testing.expect(avs_reconf_config_valid(config) == true); +} + +test "avs_reconf_config_valid_false" { + given config = ReconfConfig{.mode = ReconfMode.manual, .voltage_mv = 600, .freq_khz = 100000, .auto_scale = false, .min_voltage_mv = 700, .max_voltage_mv = 1300, .min_freq_khz = 10000, .max_freq_khz = 200000} + try std.testing.expect(avs_reconf_config_valid(config) == false); +} + +test "avs_reconf_status_init_structure" { + given status = avs_reconf_status_init() + try std.testing.expect(status.state == ReconfState.idle); + try std.testing.expect(status.current_voltage_mv == VOLTAGE_MIN_MV); +} + +test "avs_reconf_status_start_op" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_status_start_op(status, ReconfOp.set_voltage)); + try std.testing.expect(result.state == ReconfState.busy); + try std.testing.expect(result.last_op == ReconfOp.set_voltage); +} + +test "avs_reconf_status_complete_success" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_result_success(1000)); + try std.testing.expect(new_status = avs_reconf_status_complete(status, result)); + try std.testing.expect(new_status.state == ReconfState.idle); + try std.testing.expect(new_status.error_code == 0); +} + +test "avs_reconf_status_complete_error" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_result_error(1, 500)); + try std.testing.expect(new_status = avs_reconf_status_complete(status, result)); + try std.testing.expect(new_status.state == ReconfState.error); + try std.testing.expect(new_status.error_code == 1); + try std.testing.expect(new_status.retry_count == 1); +} + +test "avs_reconf_status_update_voltage" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_status_update_voltage(status, 1100)); + try std.testing.expect(result.current_voltage_mv == 1100); +} + +test "avs_reconf_status_update_freq" { + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_status_update_freq(status, 150000)); + try std.testing.expect(result.current_freq_khz == 150000); +} + +test "avs_reconf_command_set_voltage_structure" { + given cmd = avs_reconf_command_set_voltage(1050) + try std.testing.expect(cmd.op == ReconfOp.set_voltage); + try std.testing.expect(cmd.param1 == 1050); +} + +test "avs_reconf_command_set_freq_structure" { + given cmd = avs_reconf_command_set_freq(120000) + try std.testing.expect(cmd.op == ReconfOp.set_frequency); + try std.testing.expect(cmd.param1 == 120000); +} + +test "avs_reconf_command_set_mode_structure" { + given cmd = avs_reconf_command_set_mode(ReconfMode.performance) + try std.testing.expect(cmd.op == ReconfOp.set_mode); + try std.testing.expect(cmd.param1 == 2); +} + +test "avs_reconf_command_calibrate_structure" { + given cmd = avs_reconf_command_calibrate() + try std.testing.expect(cmd.op == ReconfOp.calibrate); + try std.testing.expect(cmd.timeout_ms > RECONF_TIMEOUT_MS); +} + +test "avs_reconf_result_success_structure" { + given result = avs_reconf_result_success(500) + try std.testing.expect(result.success == true); + try std.testing.expect(result.cycles_taken == 500); +} + +test "avs_reconf_result_error_structure" { + given result = avs_reconf_result_error(2, 300) + try std.testing.expect(result.success == false); + try std.testing.expect(result.error_code == 2); +} + +test "avs_reconf_should_retry_true" { + given status = ReconfStatus{.state = ReconfState.error, .current_voltage_mv = 1000, .current_freq_khz = 100000, .last_op = ReconfOp.set_voltage, .error_code = 1, .retry_count = 1} + try std.testing.expect(avs_reconf_should_retry(status) == true); +} + +test "avs_reconf_should_retry_false_max_retries" { + given status = ReconfStatus{.state = ReconfState.error, .current_voltage_mv = 1000, .current_freq_khz = 100000, .last_op = ReconfOp.set_voltage, .error_code = 1, .retry_count = 3} + try std.testing.expect(avs_reconf_should_retry(status) == false); +} + +test "avs_reconf_should_retry_false_idle" { + given status = avs_reconf_status_init() + try std.testing.expect(avs_reconf_should_retry(status) == false); +} + +test "encode_avs_reconf_cmd" { + given encoded = encode_avs_reconf_cmd(0x00, 0x3E8, 0x2710) + try std.testing.expect((encoded >> 24) == 0x00); +} + +test "decode_avs_reconf_cmd" { + given decoded = decode_avs_reconf_cmd(0x003E8271) + try std.testing.expect(decoded.op == 0); + try std.testing.expect(decoded.param1 == 0x3E8); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant voltage_range_positive + assert VOLTAGE_MIN_MV > 0 and VOLTAGE_MAX_MV > VOLTAGE_MIN_MV + +invariant freq_range_positive + assert FREQ_MIN_KHZ > 0 and FREQ_MAX_KHZ > FREQ_MIN_KHZ + +invariant reconf_timeout_positive + assert RECONF_TIMEOUT_MS > 0 + +invariant reconf_retry_count_positive + assert RECONF_RETRY_COUNT > 0 + +invariant reconf_config_init_valid + given config = avs_reconf_config_init() + assert avs_reconf_config_valid(config) + +invariant reconf_config_performance_max_voltage + given config = avs_reconf_config_performance() + assert config.voltage_mv == VOLTAGE_MAX_MV + +invariant reconf_config_power_saving_low_voltage + given config = avs_reconf_config_power_saving() + assert config.voltage_mv < 1000 + +invariant reconf_status_init_idle + given status = avs_reconf_status_init() + try std.testing.expect(status.state == ReconfState.idle); + +invariant reconf_status_start_op_busy + given status = avs_reconf_status_init() + try std.testing.expect(result = avs_reconf_status_start_op(status, ReconfOp.set_voltage)); + try std.testing.expect(result.state == ReconfState.busy); + +invariant reconf_result_success_no_error + given result = avs_reconf_result_success(100) + assert result.success == true and result.error_code == 0 + +invariant reconf_result_error_not_success + given result = avs_reconf_result_error(1, 100) + assert result.success == false and result.error_code > 0 + +invariant reconf_should_retry_requires_error + given idle = avs_reconf_status_init() + assert avs_reconf_should_retry(idle) == false + +invariant reconf_config_performance_auto_scale + given config = avs_reconf_config_performance() + assert config.auto_scale == true + +invariant reconf_config_power_saving_auto_scale + given config = avs_reconf_config_power_saving() + assert config.auto_scale == true + +invariant reconf_command_calibrate_longer_timeout + given normal = avs_reconf_command_init() + try std.testing.expect(cal = avs_reconf_command_calibrate()); + assert cal.timeout_ms > normal.timeout_ms + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench avs_reconf_config_init_latency + measure: nanoseconds to avs_reconf_config_init() + target: < 30ns + +bench avs_reconf_voltage_valid_latency + measure: nanoseconds to avs_reconf_voltage_valid(1000) + target: < 15ns + +bench avs_reconf_freq_valid_latency + measure: nanoseconds to avs_reconf_freq_valid(100000) + target: < 15ns + +bench avs_reconf_config_valid_latency + measure: nanoseconds to avs_reconf_config_valid(avs_reconf_config_init()) + target: < 30ns + +bench avs_reconf_status_init_latency + measure: nanoseconds to avs_reconf_status_init() + target: < 30ns + +bench avs_reconf_status_start_op_latency + measure: nanoseconds to avs_reconf_status_start_op(avs_reconf_status_init(), ReconfOp.set_voltage) + target: < 20ns + +bench avs_reconf_status_complete_latency + measure: nanoseconds to avs_reconf_status_complete(avs_reconf_status_init(), avs_reconf_result_success(100)) + target: < 20ns + +bench avs_reconf_command_set_voltage_latency + measure: nanoseconds to avs_reconf_command_set_voltage(1000) + target: < 20ns + +bench avs_reconf_should_retry_latency + measure: nanoseconds to avs_reconf_should_retry(avs_reconf_status_init()) + target: < 15ns + +bench avs_reconf_result_success_latency + measure: nanoseconds to avs_reconf_result_success(500) + target: < 15ns + +bench encode_avs_reconf_cmd_latency + measure: nanoseconds to encode_avs_reconf_cmd(0x00, 1000, 10000) + target: < 20ns + +bench decode_avs_reconf_cmd_latency + measure: nanoseconds to decode_avs_reconf_cmd(0x003E8271) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/dfs_gate.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/dfs_gate.t27 new file mode 100644 index 0000000000..520c8bc16e --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/dfs_gate.t27 @@ -0,0 +1,600 @@ +// SPDX-License-Identifier: Apache-2.0 +; dfs_gate.t27 — Sacred Opcode 0xE7: Depth-First Search Gate +; Hardware acceleration for DFS traversal and pattern matching +; φ² + 1/φ² = 3 | TRINITY + +module sacred-dfs_gate; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_DFS_GATE : u8 = 0xE7; + +pub const MAX_STACK_DEPTH : u8 = 16; +pub const MAX_NODES : u16 = 256; +pub const NODE_ADDR_BITS : u8 = 8; + +pub const VISIT_UNVISITED : u8 = 0; +pub const VISIT_VISITING : u8 = 1; +pub const VISIT_VISITED : u8 = 2; + +pub const DFS_MODE_STANDARD : u8 = 0; +pub const DFS_MODE_DEPTH_LIMITED : u8 = 1; +pub const DFS_MODE_PATTERN_MATCH : u8 = 2; +pub const DFS_MODE_PRUNE_SUBOPTIMAL : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const DfsNode = struct { + address : u8, + value : u16, + children : [4]u8, // Up to 4 child addresses + child_count : u8, + visited : u8, + depth : u8, +} + +pub const DfsStack = struct { + nodes : [MAX_STACK_DEPTH]u8, + top : u8, +} + +pub const DfsConfig = struct { + mode : u8, + depth_limit : u8, + pattern_mask : u16, + prune_threshold : u8, +} + +pub const DfsState = struct { + current_node : u8, + visited_count : u16, + max_depth : u8, + cycles : u32, +} + +pub const DfsResult = struct { + found : bool, + target_node : u8, + depth : u8, + nodes_visited : u16, + pattern_matches : u16, +} + +// ============================================================================ +// Stack Functions +// ============================================================================ + +// dfs_stack_init() -> DfsStack +// Initialize empty DFS stack +pub fn dfs_stack_init() DfsStack { + return DfsStack { + .nodes = [_]u8{0} ** MAX_STACK_DEPTH, + .top = 0, + }; +} + +// dfs_stack_push(stack: DfsStack, node: u8) -> DfsStack +// Push node onto DFS stack +pub fn dfs_stack_push(stack: DfsStack, node: u8) DfsStack { + if (stack.top >= MAX_STACK_DEPTH) { + return stack; // Stack overflow + } + var result = stack; + result.nodes[stack.top] = node; + result.top += 1; + return result; +} + +// dfs_stack_pop(stack: DfsStack) -> struct { stack: DfsStack, node: u8, valid: bool } +// Pop node from DFS stack +pub fn dfs_stack_pop(stack: DfsStack) struct { stack: DfsStack, node: u8, valid: bool } { + if (stack.top == 0) { + return .{ .stack = stack, .node = 0, .valid = false }; + } + var result = stack; + result.top -= 1; + const node = result.nodes[result.top]; + result.nodes[result.top] = 0; + return .{ .stack = result, .node = node, .valid = true }; +} + +// dfs_stack_peek(stack: DfsStack) -> struct { node: u8, valid: bool } +// Peek at top of DFS stack +pub fn dfs_stack_peek(stack: DfsStack) struct { node: u8, valid: bool } { + if (stack.top == 0) { + return .{ .node = 0, .valid = false }; + } + return .{ .node = stack.nodes[stack.top - 1], .valid = true }; +} + +// dfs_stack_is_empty(stack: DfsStack) -> bool +// Check if stack is empty +pub fn dfs_stack_is_empty(stack: DfsStack) bool { + return stack.top == 0; +} + +// dfs_stack_depth(stack: DfsStack) -> u8 +// Get current stack depth +pub fn dfs_stack_depth(stack: DfsStack) -> u8 { + return stack.top; +} + +// ============================================================================ +// Node Functions +// ============================================================================ + +// dfs_node_init(address: u8, value: u16) -> DfsNode +// Initialize DFS node +pub fn dfs_node_init(address: u8, value: u16) DfsNode { + return DfsNode { + .address = address, + .value = value, + .children = [_]u8{255} ** 4, // 255 = invalid + .child_count = 0, + .visited = VISIT_UNVISITED, + .depth = 0, + }; +} + +// dfs_node_add_child(node: DfsNode, child_addr: u8) -> DfsNode +// Add child to node +pub fn dfs_node_add_child(node: DfsNode, child_addr: u8) DfsNode { + if (node.child_count >= 4) { + return node; // Too many children + } + var result = node; + result.children[node.child_count] = child_addr; + result.child_count += 1; + return result; +} + +// dfs_node_has_child(node: DfsNode, child_addr: u8) -> bool +// Check if node has specific child +pub fn dfs_node_has_child(node: DfsNode, child_addr: u8) bool { + for (0..node.child_count) |i| { + if (node.children[i] == child_addr) { + return true; + } + } + return false; +} + +// dfs_node_mark_visiting(node: DfsNode) -> DfsNode +// Mark node as being visited +pub fn dfs_node_mark_visiting(node: DfsNode) DfsNode { + var result = node; + result.visited = VISIT_VISITING; + return result; +} + +// dfs_node_mark_visited(node: DfsNode) -> DfsNode +// Mark node as visited +pub fn dfs_node_mark_visited(node: DfsNode) DfsNode { + var result = node; + result.visited = VISIT_VISITED; + return result; +} + +// dfs_node_is_visited(node: DfsNode) -> bool +// Check if node is fully visited +pub fn dfs_node_is_visited(node: DfsNode) bool { + return node.visited == VISIT_VISITED; +} + +// dfs_node_is_visiting(node: DfsNode) -> bool +// Check if node is currently being visited +pub fn dfs_node_is_visiting(node: DfsNode) bool { + return node.visited == VISIT_VISITING; +} + +// ============================================================================ +// DFS Traversal Functions +// ============================================================================ + +// dfs_traverse(graph: [MAX_NODES]DfsNode, start_addr: u8, target_value: u16, config: DfsConfig) -> DfsResult +// Perform DFS traversal to find target value +pub fn dfs_traverse(graph: [MAX_NODES]DfsNode, start_addr: u8, target_value: u16, config: DfsConfig) DfsResult { + var stack = dfs_stack_init(); + stack = dfs_stack_push(stack, start_addr); + + var visited_count : u16 = 0; + var max_depth : u8 = 0; + var pattern_matches : u16 = 0; + + var current_depth : u8 = 0; + var nodes_visited : u16 = 0; + + while (not dfs_stack_is_empty(stack)) { + const peek = dfs_stack_peek(stack); + + if (not peek.valid) { + break; + } + + const node_idx = peek.node; + var node = graph[@as(u8, @intCast(node_idx))]; + + // Check depth limit + if (config.mode == DFS_MODE_DEPTH_LIMITED and current_depth >= config.depth_limit) { + const pop_result = dfs_stack_pop(stack); + if (pop_result.valid) { + stack = pop_result.stack; + current_depth -= 1; + } + continue; + } + + // Check if visiting for first time + if (node.visited == VISIT_UNVISITED) { + nodes_visited += 1; + node = dfs_node_mark_visiting(node); + graph[@as(u8, @intCast(node_idx))] = node; + + // Check if target found + if (node.value == target_value) { + return DfsResult { + .found = true, + .target_node = node_idx, + .depth = current_depth, + .nodes_visited = nodes_visited, + .pattern_matches = pattern_matches, + }; + } + + // Check pattern match + if (config.mode == DFS_MODE_PATTERN_MATCH) { + if ((node.value & config.pattern_mask) == (target_value & config.pattern_mask)) { + pattern_matches += 1; + } + } + + // Prune suboptimal nodes + if (config.mode == DFS_MODE_PRUNE_SUBOPTIMAL) { + if (node.value < config.prune_threshold) { + const pop_result = dfs_stack_pop(stack); + if (pop_result.valid) { + stack = pop_result.stack; + current_depth -= 1; + } + continue; + } + } + + // Push children onto stack (reverse order for correct DFS) + if (node.child_count > 0) { + var i : u8 = node.child_count; + while (i > 0) { + i -= 1; + const child_addr = node.children[i]; + const child = graph[@as(u8, @intCast(child_addr))]; + if (child.visited == VISIT_UNVISITED) { + child.depth = current_depth + 1; + if (child.depth > max_depth) { + max_depth = child.depth; + } + stack = dfs_stack_push(stack, child_addr); + } + } + current_depth += 1; + } + } else { + // Node already visited, mark complete and pop + node = dfs_node_mark_visited(node); + graph[@as(u8, @intCast(node_idx))] = node; + visited_count += 1; + + const pop_result = dfs_stack_pop(stack); + if (pop_result.valid) { + stack = pop_result.stack; + if (current_depth > 0) { + current_depth -= 1; + } + } + } + } + + return DfsResult { + .found = false, + .target_node = 0, + .depth = max_depth, + .nodes_visited = nodes_visited, + .pattern_matches = pattern_matches, + }; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_dfs_gate(mode: u8, start_addr: u8, target: u16) -> u32 +// Encode DFS gate instruction +pub fn encode_dfs_gate(mode: u8, start_addr: u8, target: u16) u32 { + // Format: [OP:8][MODE:2][START:8][TARGET:14] + const op : u32 = @as(u32, OP_DFS_GATE) << 24; + const mode_field : u32 = @as(u32, mode & 0x03) << 22; + const start_field : u32 = @as(u32, start_addr) << 14; + const target_field : u32 = @as(u32, target & 0x3FFF); + return op | mode_field | start_field | target_field; +} + +// decode_dfs_gate(encoded: u32) -> struct { mode: u8, start_addr: u8, target: u16 } +// Decode DFS gate instruction +pub fn decode_dfs_gate(encoded: u32) struct { mode: u8, start_addr: u8, target: u16 } { + const mode : u8 = @as(u8, @truncate((encoded >> 22) & 0x03)); + const start_addr : u8 = @as(u8, @truncate((encoded >> 14) & 0xFF)); + const target : u16 = @as(u16, @truncate(encoded & 0x3FFF)); + return .{ .mode = mode, .start_addr = start_addr, .target = target }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "max_stack_depth_sixteen" { + try std.testing.expect(MAX_STACK_DEPTH == 16); +} + +test "max_nodes_256" { + try std.testing.expect(MAX_NODES == 256); +} + +test "node_addr_bits_eight" { + try std.testing.expect(NODE_ADDR_BITS == 8); +} + +test "visit_unvisited_zero" { + assert VISIT_UNVISITED == 0 +} + +test "visit_visited_two" { + assert VISIT_VISITED == 2 +} + +test "dfs_stack_init_empty" { + given stack = dfs_stack_init() + try std.testing.expect(dfs_stack_is_empty(stack) == true); + try std.testing.expect(dfs_stack_depth(stack) == 0); +} + +test "dfs_stack_push_pop" { + given stack = dfs_stack_init() + try std.testing.expect(pushed = dfs_stack_push(stack, 10)); + try std.testing.expect(popped = dfs_stack_pop(pushed)); + try std.testing.expect(popped.valid == true); + try std.testing.expect(popped.node == 10); + try std.testing.expect(dfs_stack_is_empty(popped.stack) == true); +} + +test "dfs_stack_push_multiple" { + given stack = dfs_stack_init() + try std.testing.expect(result = dfs_stack_push(dfs_stack_push(dfs_stack_push(stack, 1), 2), 3)); + try std.testing.expect(popped = dfs_stack_pop(result)); + try std.testing.expect(popped.node == 3); + try std.testing.expect(dfs_stack_depth(popped.stack) == 2); +} + +test "dfs_stack_peek" { + given stack = dfs_stack_init() + try std.testing.expect(pushed = dfs_stack_push(stack, 10)); + try std.testing.expect(peeked = dfs_stack_peek(pushed)); + try std.testing.expect(peeked.valid == true); + try std.testing.expect(peeked.node == 10); + try std.testing.expect(dfs_stack_depth(pushed) == 1); +} + +test "dfs_stack_peek_empty" { + given stack = dfs_stack_init() + try std.testing.expect(peeked = dfs_stack_peek(stack)); + try std.testing.expect(peeked.valid == false); +} + +test "dfs_stack_depth" { + given stack = dfs_stack_init() + try std.testing.expect(result = dfs_stack_push(dfs_stack_push(dfs_stack_push(stack, 1), 2), 3)); + try std.testing.expect(dfs_stack_depth(result) == 3); +} + +test "dfs_node_init" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(node.address == 10); + try std.testing.expect(node.value == 0x1234); + try std.testing.expect(node.visited == VISIT_UNVISITED); + try std.testing.expect(node.child_count == 0); +} + +test "dfs_node_add_child" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(result = dfs_node_add_child(node, 20)); + try std.testing.expect(result.child_count == 1); + try std.testing.expect(result.children[0] == 20); +} + +test "dfs_node_add_multiple_children" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(result = dfs_node_add_child(dfs_node_add_child(dfs_node_add_child(node, 20), 21), 22)); + try std.testing.expect(result.child_count == 3); +} + +test "dfs_node_add_child_overflow" { + given node = DfsNode{.address = 10, .value = 0x1234, .children = [_]u8{20, 21, 22, 23}, .child_count = 4, .visited = 0, .depth = 0} + try std.testing.expect(result = dfs_node_add_child(node, 24)); + try std.testing.expect(result.child_count == 4 // No change); +} + +test "dfs_node_has_child_true" { + given node = dfs_node_add_child(dfs_node_init(10, 0x1234), 20) + try std.testing.expect(dfs_node_has_child(node, 20) == true); +} + +test "dfs_node_has_child_false" { + given node = dfs_node_add_child(dfs_node_init(10, 0x1234), 20) + try std.testing.expect(dfs_node_has_child(node, 21) == false); +} + +test "dfs_node_mark_visiting" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(result = dfs_node_mark_visiting(node)); + try std.testing.expect(result.visited == VISIT_VISITING); +} + +test "dfs_node_mark_visited" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(result = dfs_node_mark_visited(node)); + try std.testing.expect(result.visited == VISIT_VISITED); +} + +test "dfs_node_is_visited_true" { + given node = dfs_node_mark_visited(dfs_node_init(10, 0x1234)) + try std.testing.expect(dfs_node_is_visited(node) == true); +} + +test "dfs_node_is_visited_false" { + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(dfs_node_is_visited(node) == false); +} + +test "dfs_node_is_visiting_true" { + given node = dfs_node_mark_visiting(dfs_node_init(10, 0x1234)) + try std.testing.expect(dfs_node_is_visiting(node) == true); +} + +test "dfs_traverse_found" { + given graph = [_]DfsNode{dfs_node_add_child(dfs_node_add_child(dfs_node_init(0, 0x1234), 1), 2)} ++ [_]DfsNode{dfs_node_init(1, 0x5678), .children = [_]u8{3}, 255, 255, 255], .child_count = 1, .visited = 0, .depth = 0} ++ [_]DfsNode{dfs_node_init(2, 0x5678), .children = [_]u8{255}, 255, 255, 255], .child_count = 0, .visited = 0, .depth = 0} ++ [_]DfsNode{dfs_node_init(3, 0x5678), .children = [_]u8{255}, 255, 255, 255], .child_count = 0, .visited = 0, .depth = 0} ++ [_]DfsNode{} ** 252 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0x5678, config)); + try std.testing.expect(result.found == true); + try std.testing.expect(result.target_node == 1); +} + +test "dfs_traverse_not_found" { + given graph = [_]DfsNode{dfs_node_init(0, 0x1234), .children = [_]u8{1}, 255, 255, 255], .child_count = 1, .visited = 0, .depth = 0} ++ [_]DfsNode{dfs_node_init(1, 0x2345), .children = [_]u8{2}, 255, 255, 255], .child_count = 1, .visited = 0, .depth = 0} ++ [_]DfsNode{dfs_node_init(2, 0x3456), .children = [_]u8{255}, 255, 255, 255], .child_count = 0, .visited = 0, .depth = 0} ++ [_]DfsNode{} ** 253 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0xFFFF, config)); + try std.testing.expect(result.found == false); +} + +test "dfs_traverse_depth_limited" { + given graph = [_]DfsNode{dfs_node_add_child(dfs_node_init(0, 0x1234), 1)} ++ [_]DfsNode{dfs_node_add_child(dfs_node_init(1, 0x5678), 2)} ++ [_]DfsNode{dfs_node_add_child(dfs_node_init(2, 0x5678), 3)} ++ [_]DfsNode{} ** 253 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_DEPTH_LIMITED, .depth_limit = 1, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0x5678, config)); + try std.testing.expect(result.found == false); + try std.testing.expect(result.depth <= config.depth_limit); +} + +test "encode_dfs_gate" { + given encoded = encode_dfs_gate(1, 0x20, 0x1234) + try std.testing.expect((encoded >> 24) == OP_DFS_GATE); +} + +test "decode_dfs_gate" { + given decoded = decode_dfs_gate(0xE7080A34) + try std.testing.expect(decoded.mode == 1); + try std.testing.expect(decoded.start_addr == 0x20); + try std.testing.expect(decoded.target == 0x0A34); +} + +test "opcode_constant" { + try std.testing.expect(OP_DFS_GATE == 0xE7); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant max_stack_depth_sixteen + assert MAX_STACK_DEPTH == 16 + +invariant max_nodes_256 + assert MAX_NODES == 256 + +invariant node_addr_bits_eight + try std.testing.expect(NODE_ADDR_BITS == 8); + +invariant visit_state_values + try std.testing.expect(VISIT_UNVISITED < VISIT_VISITING and VISIT_VISITING < VISIT_VISITED); + +invariant dfs_mode_values + try std.testing.expect(DFS_MODE_STANDARD <= DFS_MODE_PRUNE_SUBOPTIMAL and DFS_MODE_PRUNE_SUBOPTIMAL <= 3); + +invariant dfs_stack_init_empty + given stack = dfs_stack_init() + assert dfs_stack_is_empty(stack) == true + +invariant dfs_stack_push_increases_depth + given stack = dfs_stack_init() + try std.testing.expect(initial_depth = dfs_stack_depth(stack)); + try std.testing.expect(pushed = dfs_stack_push(stack, 10)); + assert dfs_stack_depth(pushed) == initial_depth + 1 + +invariant dfs_stack_pop_decreases_depth + given stack = dfs_stack_init() + try std.testing.expect(pushed = dfs_stack_push(stack, 10)); + try std.testing.expect(popped = dfs_stack_pop(pushed)); + try std.testing.expect(popped.valid == true); + assert dfs_stack_depth(popped.stack) < dfs_stack_depth(pushed) + +invariant dfs_stack_depth_bound + try std.testing.expect(dfs_stack_depth(dfs_stack_push(dfs_stack_push(dfs_stack_push(dfs_stack_init(), 1), 2), 3)) == 3); + +invariant dfs_stack_depth_max_bound + var stack = dfs_stack_init(); + for (0..MAX_STACK_DEPTH) |_| { + stack = dfs_stack_push(stack, @as(u8, @truncate(0))); + } + try std.testing.expect(dfs_stack_depth(stack) <= MAX_STACK_DEPTH); + +invariant dfs_node_children_bound + try std.testing.expect(dfs_node_add_child(dfs_node_add_child(dfs_node_add_child(dfs_node_add_child(dfs_node_init(10, 0x1234), 20), 21), 22).child_count <= 4); + +invariant dfs_node_visited_sequence + given node = dfs_node_init(10, 0x1234) + try std.testing.expect(visiting = dfs_node_mark_visiting(node)); + try std.testing.expect(visited = dfs_node_mark_visiting(visiting)); + assert node.visited != visited.visited + +invariant dfs_traverse_found_has_valid_target + given graph = [_]DfsNode{dfs_node_add_child(dfs_node_init(0, 0x1234), 1)} ++ [_]DfsNode{dfs_node_init(1, 0x5678), .children = [_]u8{255}, 255, 255, 255], .child_count = 0, .visited = 0, .depth = 0} ++ [_]DfsNode{} ** 254 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0x5678, config)); + try std.testing.expect(result.found implies (result.target_node < MAX_NODES)); + +invariant dfs_traverse_nodes_visited_bound + given graph = [_]DfsNode{} ** 256 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0xFFFF, config)); + try std.testing.expect(result.nodes_visited <= MAX_NODES); + +invariant dfs_traverse_depth_bound + given graph = [_]DfsNode{} ** 256 + try std.testing.expect(config = DfsConfig{.mode = DFS_MODE_DEPTH_LIMITED, .depth_limit = 5, .pattern_mask = 0, .prune_threshold = 0}); + try std.testing.expect(result = dfs_traverse(graph, 0, 0xFFFF, config)); + try std.testing.expect(result.depth <= config.depth_limit); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench dfs_stack_push_latency + measure: nanoseconds to dfs_stack_push(dfs_stack_init(), 10) + target: < 30ns + +bench dfs_stack_pop_latency + measure: nanoseconds to dfs_stack_pop(dfs_stack_push(dfs_stack_init(), 10)) + target: < 30ns + +bench dfs_traverse_latency + measure: nanoseconds to dfs_traverse([_]DfsNode{} ** 256, 0, 0x5678, .{.mode = DFS_MODE_STANDARD, .depth_limit = 16, .pattern_mask = 0, .prune_threshold = 0}) + target: < 1000ns + +bench encode_dfs_gate_latency + measure: nanoseconds to encode_dfs_gate(1, 0x20, 0x1234) + target: < 30ns + +bench decode_dfs_gate_latency + measure: nanoseconds to decode_dfs_gate(0xE7080A34) + target: < 30ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/drowsy_ret.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/drowsy_ret.t27 new file mode 100644 index 0000000000..e42b5b08a9 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/drowsy_ret.t27 @@ -0,0 +1,716 @@ +// SPDX-License-Identifier: Apache-2.0 + +module sacred-drowsy_ret; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_DROWSY_RET : u8 = 0xEC; + +pub const DROWSY_STATE_BITS : u8 = 3; +pub const RET_REASON_BITS : u8 = 3; + +pub const DROWSY_ACTIVE : u8 = 0; +pub const DROWSY_LIGHT : u8 = 1; +pub const DROWSY_DEEP : u8 = 2; +pub const DROWSY_OFF : u8 = 3; +pub const DROWSY_HIBERNATE : u8 = 4; +pub const DROWSY_CRITICAL : u8 = 5; +pub const DROWSY_MAX : u8 = 6; + +pub const RET_WAKE : u8 = 0; +pub const RET_TIMER : u8 = 1; +pub const RET_INTERRUPT : u8 = 2; +pub const RET_ERROR : u8 = 3; +pub const RET_POWER : u8 = 4; +pub const RET_MANUAL : u8 = 5; +pub const RET_MAX : u8 = 6; + +pub const WAKE_CYCLES : u32 = 100; // Cycles to wake from light drowsy +pub const DEEP_WAKE_CYCLES : u32 = 1000; // Cycles to wake from deep drowsy + +// ============================================================================ +// Types +// ============================================================================ + +pub const DrowsyState = enum(u8) { + active = DROWSY_ACTIVE, + light = DROWSY_LIGHT, + deep = DROWSY_DEEP, + off = DROWSY_OFF, + hibernate = DROWSY_HIBERNATE, + critical = DROWSY_CRITICAL, +} + +pub const RetReason = enum(u8) { + wake = RET_WAKE, + timer = RET_TIMER, + interrupt = RET_INTERRUPT, + error = RET_ERROR, + power = RET_POWER, + manual = RET_MANUAL, +} + +pub const DrowsyConfig = struct { + enable_light : bool, + enable_deep : bool, + light_threshold_ms : u32, + deep_threshold_ms : u32, + wake_irq_enable : bool, +} + +pub const DrowsyStatus = struct { + state : DrowsyState, + last_wake_cycle : u32, + drowsy_duration : u32, + wake_count : u32, + last_ret_reason : RetReason, +} + +pub const WakeCondition = struct { + irq_pending : bool, + timer_expired : bool, + power_good : bool, + manual_wake : bool, +} + +// ============================================================================ +// State Functions +// ============================================================================ + +// drowsy_is_active(state: DrowsyState) -> bool +// Check if in active state +pub fn drowsy_is_active(state: DrowsyState) bool { + return state == DrowsyState.active; +} + +// drowsy_is_drowsy(state: DrowsyState) -> bool +// Check if in any drowsy state +pub fn drowsy_is_drowsy(state: DrowsyState) bool { + return state == DrowsyState.light or state == DrowsyState.deep; +} + +// drowsy_is_off(state: DrowsyState) -> bool +// Check if device is off +pub fn drowsy_is_off(state: DrowsyState) bool { + return state == DrowsyState.off or state == DrowsyState.hibernate; +} + +// drowsy_is_critical(state: DrowsyState) -> bool +// Check if in critical state +pub fn drowsy_is_critical(state: DrowsyState) bool { + return state == DrowsyState.critical; +} + +// drowsy_can_enter_light(state: DrowsyState) -> bool +// Check if can enter light drowsy +pub fn drowsy_can_enter_light(state: DrowsyState) bool { + return state == DrowsyState.active; +} + +// drowsy_can_enter_deep(state: DrowsyState) -> bool +// Check if can enter deep drowsy +pub fn drowsy_can_enter_deep(state: DrowsyState) bool { + return state == DrowsyState.light; +} + +// drowsy_wake_cycles(state: DrowsyState) -> u32 +// Get wake cycles for state +pub fn drowsy_wake_cycles(state: DrowsyState) -> u32 { + switch (state) { + DrowsyState.light => return WAKE_CYCLES, + DrowsyState.deep => return DEEP_WAKE_CYCLES, + else => return 0, + } +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// drowsy_config_init() -> DrowsyConfig +// Initialize drowsy config +pub fn drowsy_config_init() -> DrowsyConfig { + return DrowsyConfig { + .enable_light = true, + .enable_deep = true, + .light_threshold_ms = 100, + .deep_threshold_ms = 1000, + .wake_irq_enable = true, + }; +} + +// drowsy_config_disable() -> DrowsyConfig +// Disable drowsy mode +pub fn drowsy_config_disable() -> DrowsyConfig { + return DrowsyConfig { + .enable_light = false, + .enable_deep = false, + .light_threshold_ms = 0, + .deep_threshold_ms = 0, + .wake_irq_enable = false, + }; +} + +// drowsy_config_light_only() -> DrowsyConfig +// Enable only light drowsy +pub fn drowsy_config_light_only(threshold_ms: u32) -> DrowsyConfig { + return DrowsyConfig { + .enable_light = true, + .enable_deep = false, + .light_threshold_ms = threshold_ms, + .deep_threshold_ms = 0, + .wake_irq_enable = true, + }; +} + +// ============================================================================ +// Status Functions +// ============================================================================ + +// drowsy_status_init() -> DrowsyStatus +// Initialize drowsy status +pub fn drowsy_status_init() -> DrowsyStatus { + return DrowsyStatus { + .state = DrowsyState.active, + .last_wake_cycle = 0, + .drowsy_duration = 0, + .wake_count = 0, + .last_ret_reason = RetReason.manual, + }; +} + +// drowsy_status_enter_light(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus +// Enter light drowsy state +pub fn drowsy_status_enter_light(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus { + if (not drowsy_can_enter_light(status.state)) { + return status; + } + return DrowsyStatus { + .state = DrowsyState.light, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = status.last_ret_reason, + }; +} + +// drowsy_status_enter_deep(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus +// Enter deep drowsy state +pub fn drowsy_status_enter_deep(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus { + if (not drowsy_can_enter_deep(status.state)) { + return status; + } + return DrowsyStatus { + .state = DrowsyState.deep, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = status.last_ret_reason, + }; +} + +// drowsy_status_wake(status: DrowsyStatus, reason: RetReason, current_cycle: u32) -> DrowsyStatus +// Wake from drowsy state +pub fn drowsy_status_wake(status: DrowsyStatus, reason: RetReason, current_cycle: u32) -> DrowsyStatus { + return DrowsyStatus { + .state = DrowsyState.active, + .last_wake_cycle = current_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count + 1, + .last_ret_reason = reason, + }; +} + +// drowsy_status_enter_critical(status: DrowsyStatus) -> DrowsyStatus +// Enter critical state (emergency) +pub fn drowsy_status_enter_critical(status: DrowsyStatus) -> DrowsyStatus { + return DrowsyStatus { + .state = DrowsyState.critical, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = RetReason.error, + }; +} + +// drowsy_status_enter_off(status: DrowsyStatus) -> DrowsyStatus +// Enter off state +pub fn drowsy_status_enter_off(status: DrowsyStatus) -> DrowsyStatus { + return DrowsyStatus { + .state = DrowsyState.off, + .last_wake_cycle = 0, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = RetReason.power, + }; +} + +// ============================================================================ +// Wake Condition Functions +// ============================================================================ + +// drowsy_wake_condition_init() -> WakeCondition +// Initialize wake condition +pub fn drowsy_wake_condition_init() -> WakeCondition { + return WakeCondition { + .irq_pending = false, + .timer_expired = false, + .power_good = true, + .manual_wake = false, + }; +} + +// drowsy_should_wake(condition: WakeCondition) -> bool +// Check if should wake from drowsy +pub fn drowsy_should_wake(condition: WakeCondition) -> bool { + return condition.irq_pending or condition.timer_expired or condition.manual_wake; +} + +// drowsy_can_wake_critical(condition: WakeCondition) -> bool +// Check if can wake from critical state +pub fn drowsy_can_wake_critical(condition: WakeCondition) -> bool { + return condition.power_good and condition.manual_wake; +} + +// drowsy_wake_from_condition(condition: WakeCondition) -> RetReason +// Determine wake reason from condition +pub fn drowsy_wake_from_condition(condition: WakeCondition) -> RetReason { + if (condition.irq_pending) { + return RetReason.interrupt; + } else if (condition.timer_expired) { + return RetReason.timer; + } else if (condition.manual_wake) { + return RetReason.manual; + } else if (not condition.power_good) { + return RetReason.power; + } else { + return RetReason.wake; + } +} + +// ============================================================================ +// Drowsy Duration Tracking +// ============================================================================ + +// drowsy_update_duration(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus +// Update drowsy duration tracking +pub fn drowsy_update_duration(status: DrowsyStatus, current_cycle: u32) -> DrowsyStatus { + if (not drowsy_is_drowsy(status.state)) { + return DrowsyStatus { + .state = status.state, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = 0, + .wake_count = status.wake_count, + .last_ret_reason = status.last_ret_reason, + }; + } + + const elapsed = if (status.last_wake_cycle > 0) current_cycle - status.last_wake_cycle else 0; + return DrowsyStatus { + .state = status.state, + .last_wake_cycle = status.last_wake_cycle, + .drowsy_duration = status.drowsy_duration + 1, + .wake_count = status.wake_count, + .last_ret_reason = status.last_ret_reason, + }; +} + +// drowsy_check_threshold(status: DrowsyStatus, config: DrowsyConfig) -> bool +// Check if should transition to deeper state +pub fn drowsy_check_threshold(status: DrowsyStatus, config: DrowsyConfig) -> bool { + if (status.state == DrowsyState.light and config.enable_deep) { + // Simplified: convert cycles to approximate ms + const threshold = status.drowsy_duration / 100; + return threshold >= (config.deep_threshold_ms / config.light_threshold_ms); + } + return false; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_drowsy_ret(reason: u8, state: u8, duration: u8) -> u32 +// Encode drowsy return instruction +pub fn encode_drowsy_ret(reason: u8, state: u8, duration: u8) u32 { + // Format: [OP:8][REASON:3][STATE:3][DURATION:16][PADDING:2] + const op : u32 = @as(u32, OP_DROWSY_RET) << 24; + const reason_field : u32 = @as(u32, reason & 0x07) << 21; + const state_field : u32 = @as(u32, state & 0x07) << 18; + const duration_field : u32 = @as(u32, duration & 0xFFFF) << 2; + return op | reason_field | state_field | duration_field; +} + +// decode_drowsy_ret(encoded: u32) -> struct { reason: u8, state: u8, duration: u16 } +// Decode drowsy return instruction +pub fn decode_drowsy_ret(encoded: u32) -> struct { reason: u8, state: u8, duration: u16 } { + const reason : u8 = @as(u8, @truncate((encoded >> 21) & 0x07)); + const state : u8 = @as(u8, @truncate((encoded >> 18) & 0x07)); + const duration : u16 = @as(u16, @truncate((encoded >> 2) & 0xFFFF)); + return .{ .reason = reason, .state = state, .duration = duration }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "drowsy_state_constants" { + try std.testing.expect(DROWSY_ACTIVE == 0); + try std.testing.expect(DROWSY_LIGHT == 1); + try std.testing.expect(DROWSY_DEEP == 2); +} + +test "ret_reason_constants" { + try std.testing.expect(RET_WAKE == 0); + try std.testing.expect(RET_TIMER == 1); + try std.testing.expect(RET_INTERRUPT == 2); + try std.testing.expect(RET_ERROR == 3); +} + +test "wake_cycles_constants" { + try std.testing.expect(WAKE_CYCLES == 100); + try std.testing.expect(DEEP_WAKE_CYCLES == 1000); +} + +test "drowsy_is_active_true" { + try std.testing.expect(drowsy_is_active(DrowsyState.active) == true); +} + +test "drowsy_is_active_false" { + try std.testing.expect(drowsy_is_active(DrowsyState.light) == false); +} + +test "drowsy_is_drowsy_true" { + try std.testing.expect(drowsy_is_drowsy(DrowsyState.light) == true); + try std.testing.expect(drowsy_is_drowsy(DrowsyState.deep) == true); +} + +test "drowsy_is_drowsy_false" { + try std.testing.expect(drowsy_is_drowsy(DrowsyState.active) == false); + try std.testing.expect(drowsy_is_drowsy(DrowsyState.off) == false); +} + +test "drowsy_is_off_true" { + try std.testing.expect(drowsy_is_off(DrowsyState.off) == true); + try std.testing.expect(drowsy_is_off(DrowsyState.hibernate) == true); +} + +test "drowsy_is_off_false" { + try std.testing.expect(drowsy_is_off(DrowsyState.active) == false); + try std.testing.expect(drowsy_is_off(DrowsyState.light) == false); +} + +test "drowsy_is_critical_true" { + try std.testing.expect(drowsy_is_critical(DrowsyState.critical) == true); +} + +test "drowsy_is_critical_false" { + try std.testing.expect(drowsy_is_critical(DrowsyState.active) == false); +} + +test "drowsy_can_enter_light_true" { + try std.testing.expect(drowsy_can_enter_light(DrowsyState.active) == true); +} + +test "drowsy_can_enter_light_false" { + try std.testing.expect(drowsy_can_enter_light(DrowsyState.light) == false); +} + +test "drowsy_can_enter_deep_true" { + try std.testing.expect(drowsy_can_enter_deep(DrowsyState.light) == true); +} + +test "drowsy_can_enter_deep_false" { + try std.testing.expect(drowsy_can_enter_deep(DrowsyState.active) == false); + try std.testing.expect(drowsy_can_enter_deep(DrowsyState.deep) == false); +} + +test "drowsy_wake_cycles_light" { + try std.testing.expect(drowsy_wake_cycles(DrowsyState.light) == WAKE_CYCLES); +} + +test "drowsy_wake_cycles_deep" { + try std.testing.expect(drowsy_wake_cycles(DrowsyState.deep) == DEEP_WAKE_CYCLES); +} + +test "drowsy_config_init_structure" { + given config = drowsy_config_init() + try std.testing.expect(config.enable_light == true); + try std.testing.expect(config.enable_deep == true); + try std.testing.expect(config.wake_irq_enable == true); +} + +test "drowsy_config_disable_structure" { + given config = drowsy_config_disable() + try std.testing.expect(config.enable_light == false); + try std.testing.expect(config.enable_deep == false); + try std.testing.expect(config.wake_irq_enable == false); +} + +test "drowsy_config_light_only_structure" { + given config = drowsy_config_light_only(50) + try std.testing.expect(config.enable_light == true); + try std.testing.expect(config.enable_deep == false); + try std.testing.expect(config.light_threshold_ms == 50); +} + +test "drowsy_status_init_structure" { + given status = drowsy_status_init() + try std.testing.expect(status.state == DrowsyState.active); + try std.testing.expect(status.wake_count == 0); +} + +test "drowsy_status_enter_light" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_enter_light(status, 100)); + try std.testing.expect(result.state == DrowsyState.light); +} + +test "drowsy_status_enter_light_reject" { + given status = DrowsyStatus{.state = DrowsyState.deep, .last_wake_cycle = 0, .drowsy_duration = 0, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_status_enter_light(status, 100)); + try std.testing.expect(result.state == DrowsyState.deep); +} + +test "drowsy_status_enter_deep" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 0, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_status_enter_deep(status, 100)); + try std.testing.expect(result.state == DrowsyState.deep); +} + +test "drowsy_status_wake" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_wake(status, RetReason.interrupt, 200)); + try std.testing.expect(result.state == DrowsyState.active); + try std.testing.expect(result.wake_count == 1); + try std.testing.expect(result.last_ret_reason == RetReason.interrupt); +} + +test "drowsy_status_enter_critical" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_enter_critical(status)); + try std.testing.expect(result.state == DrowsyState.critical); + try std.testing.expect(result.last_ret_reason == RetReason.error); +} + +test "drowsy_status_enter_off" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_enter_off(status)); + try std.testing.expect(result.state == DrowsyState.off); + try std.testing.expect(result.last_ret_reason == RetReason.power); +} + +test "drowsy_wake_condition_init" { + given cond = drowsy_wake_condition_init() + try std.testing.expect(cond.irq_pending == false); + try std.testing.expect(cond.power_good == true); +} + +test "drowsy_should_wake_irq" { + given cond = WakeCondition{.irq_pending = true, .timer_expired = false, .power_good = true, .manual_wake = false} + try std.testing.expect(drowsy_should_wake(cond) == true); +} + +test "drowsy_should_wake_none" { + given cond = drowsy_wake_condition_init() + try std.testing.expect(drowsy_should_wake(cond) == false); +} + +test "drowsy_can_wake_critical_true" { + given cond = WakeCondition{.irq_pending = false, .timer_expired = false, .power_good = true, .manual_wake = true} + try std.testing.expect(drowsy_can_wake_critical(cond) == true); +} + +test "drowsy_can_wake_critical_no_power" { + given cond = WakeCondition{.irq_pending = false, .timer_expired = false, .power_good = false, .manual_wake = true} + try std.testing.expect(drowsy_can_wake_critical(cond) == false); +} + +test "drowsy_wake_from_condition_irq" { + given cond = WakeCondition{.irq_pending = true, .timer_expired = false, .power_good = true, .manual_wake = false} + try std.testing.expect(reason = drowsy_wake_from_condition(cond)); + try std.testing.expect(reason == RetReason.interrupt); +} + +test "drowsy_wake_from_condition_timer" { + given cond = WakeCondition{.irq_pending = false, .timer_expired = true, .power_good = true, .manual_wake = false} + try std.testing.expect(reason = drowsy_wake_from_condition(cond)); + try std.testing.expect(reason == RetReason.timer); +} + +test "drowsy_update_duration_active" { + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_update_duration(status, 100)); + try std.testing.expect(result.drowsy_duration == 0); +} + +test "drowsy_update_duration_light" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 0, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_update_duration(status, 100)); + try std.testing.expect(result.drowsy_duration == 1); +} + +test "drowsy_update_duration_accumulates" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 5, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_update_duration(status, 100)); + try std.testing.expect(result.drowsy_duration == 6); +} + +test "drowsy_update_duration_resets_on_active" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 10, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(active_status = DrowsyStatus{.state = DrowsyState.active, .last_wake_cycle = 0, .drowsy_duration = 5, .wake_count = 0, .last_ret_reason = RetReason.manual}); + try std.testing.expect(result = drowsy_update_duration(active_status, 100)); + try std.testing.expect(result.drowsy_duration == 0); +} + +test "drowsy_check_threshold_not_reached" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 5, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(config = drowsy_config_init()); + try std.testing.expect(drowsy_check_threshold(status, config) == false); +} + +test "drowsy_check_threshold_reached" { + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 500, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(config = drowsy_config_init()); + try std.testing.expect(drowsy_check_threshold(status, config) == true); +} + +test "encode_drowsy_ret" { + given encoded = encode_drowsy_ret(1, 2, 0x1234) + try std.testing.expect((encoded >> 24) == OP_DROWSY_RET); +} + +test "decode_drowsy_ret" { + given decoded = decode_drowsy_ret(0xEC481234) + try std.testing.expect(decoded.reason == 1); + try std.testing.expect(decoded.state == 2); + try std.testing.expect(decoded.duration == 0x1234); +} + +test "opcode_constant" { + try std.testing.expect(OP_DROWSY_RET == 0xEC); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant drowsy_state_range + try std.testing.expect(DROWSY_ACTIVE >= 0 and DROWSY_CRITICAL <= 5); + +invariant ret_reason_range + try std.testing.expect(RET_WAKE >= 0 and RET_MAX <= 6); + +invariant wake_cycles_positive + assert WAKE_CYCLES > 0 and DEEP_WAKE_CYCLES > 0 + +invariant deep_wake_larger_than_light + assert DEEP_WAKE_CYCLES > WAKE_CYCLES + +invariant drowsy_is_drowsy_excludes_active_off + given state = DrowsyState.active + assert drowsy_is_drowsy(state) == false + try std.testing.expect(drowsy_is_off(DrowsyState.light) == false); + +invariant drowsy_is_off_excludes_active_drowsy + given state = DrowsyState.active + assert drowsy_is_off(state) == false + try std.testing.expect(drowsy_is_off(DrowsyState.light) == false); + +invariant drowsy_config_init_enables_light + given config = drowsy_config_init() + try std.testing.expect(config.enable_light == true); + +invariant drowsy_config_enable_both_or_neither + given config1 = drowsy_config_init() + try std.testing.expect(config2 = drowsy_config_light_only(100)); + try std.testing.expect((config1.enable_light and config1.enable_deep) or (not config1.enable_light and not config1.enable_deep)); + or (config2.enable_light and not config2.enable_deep) + +invariant drowsy_status_init_active + given status = drowsy_status_init() + try std.testing.expect(status.state == DrowsyState.active); + +invariant drowsy_status_wake_increases_count + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_wake(status, RetReason.manual, 100)); + try std.testing.expect(result.wake_count == status.wake_count + 1); + +invariant drowsy_status_wake_sets_active + given status = DrowsyStatus{.state = DrowsyState.light, .last_wake_cycle = 0, .drowsy_duration = 100, .wake_count = 5, .last_ret_reason = RetReason.timer} + try std.testing.expect(result = drowsy_status_wake(status, RetReason.interrupt, 200)); + try std.testing.expect(result.state == DrowsyState.active); + +invariant drowsy_status_enter_critical_reason + given status = drowsy_status_init() + try std.testing.expect(result = drowsy_status_enter_critical(status)); + try std.testing.expect(result.last_ret_reason == RetReason.error); + +invariant drowsy_wake_condition_init_defaults + given cond = drowsy_wake_condition_init() + try std.testing.expect(cond.irq_pending == false and cond.timer_expired == false and cond.manual_wake == false); + +invariant drowsy_should_wake_false_for_default + given cond = drowsy_wake_condition_init() + try std.testing.expect(drowsy_should_wake(cond) == false); + +invariant drowsy_wake_from_condition_no_match + given cond = drowsy_wake_condition_init() + try std.testing.expect(reason = drowsy_wake_from_condition(cond)); + try std.testing.expect(reason == RetReason.wake); + +invariant drowsy_update_duration_zero_for_non_drowsy + given status = DrowsyStatus{.state = DrowsyState.active, .last_wake_cycle = 0, .drowsy_duration = 10, .wake_count = 0, .last_ret_reason = RetReason.manual} + try std.testing.expect(result = drowsy_update_duration(status, 100)); + try std.testing.expect(result.drowsy_duration == 0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench drowsy_is_active_latency + measure: nanoseconds to drowsy_is_active(DrowsyState.active) + target: < 10ns + +bench drowsy_is_drowsy_latency + measure: nanoseconds to drowsy_is_drowsy(DrowsyState.light) + target: < 10ns + +bench drowsy_wake_cycles_latency + measure: nanoseconds to drowsy_wake_cycles(DrowsyState.light) + target: < 20ns + +bench drowsy_config_init_latency + measure: nanoseconds to drowsy_config_init() + target: < 30ns + +bench drowsy_status_init_latency + measure: nanoseconds to drowsy_status_init() + target: < 30ns + +bench drowsy_status_enter_light_latency + measure: nanoseconds to drowsy_status_enter_light(drowsy_status_init(), 100) + target: < 30ns + +bench drowsy_status_wake_latency + measure: nanoseconds to drowsy_status_wake(drowsy_status_init(), RetReason.interrupt, 200) + target: < 30ns + +bench drowsy_should_wake_latency + measure: nanoseconds to drowsy_should_wake(drowsy_wake_condition_init()) + target: < 20ns + +bench drowsy_update_duration_latency + measure: nanoseconds to drowsy_update_duration(drowsy_status_init(), 100) + target: < 30ns + +bench encode_drowsy_ret_latency + measure: nanoseconds to encode_drowsy_ret(1, 2, 0x1234) + target: < 30ns + +bench decode_drowsy_ret_latency + measure: nanoseconds to decode_drowsy_ret(0xEC481234) + target: < 30ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/fbb_active_path.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/fbb_active_path.t27 new file mode 100644 index 0000000000..4a401de031 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/fbb_active_path.t27 @@ -0,0 +1,756 @@ +// SPDX-License-Identifier: Apache-2.0 +; fbb_active_path.t27 — FPGA Feedback Bridge Active Path +; Active path management for FPGA feedback bridge +; φ² + 1/φ² = 3 | TRINITY + +module fbb-active-path; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const FBB_OPCODE_BASE : u8 = 0xD0; // FBB opcodes start at 0xD0 + +pub const FBB_OP_PATH_SELECT : u8 = 0x00; +pub const FBB_OP_PATH_ENABLE : u8 = 0x01; +pub const FBB_OP_PATH_DISABLE : u8 = 0x02; +pub const FBB_OP_PATH_STATUS : u8 = 0x03; +pub const FBB_OP_PATH_RESET : u8 = 0x04; +pub const FBB_OP_PATH_BYPASS : u8 = 0x05; +pub const FBB_OP_PATH_LOOPBACK : u8 = 0x06; +pub const FBB_OP_PATH_CONFIG : u8 = 0x07; +pub const FBB_OP_MAX : u8 = 0x08; + +pub const FBB_PATH_COUNT : u8 = 8; +pub const FBB_MAX_PATHS : u8 = 8; + +pub const FBB_PATH_STATE_DISABLED : u8 = 0; +pub const FBB_PATH_STATE_ENABLED : u8 = 1; +pub const FBB_PATH_STATE_ACTIVE : u8 = 2; +pub const FBB_PATH_STATE_ERROR : u8 = 3; +pub const FBB_PATH_STATE_BYPASSED : u8 = 4; +pub const FBB_PATH_STATE_MAX : u8 = 5; + +pub const FBB_LATENCY_MIN_CYCLES : u8 = 1; +pub const FBB_LATENCY_MAX_CYCLES : u8 = 16; +pub const FBB_DEFAULT_LATENCY : u8 = 4; + +pub const FBB_BANDWIDTH_MIN_MBPS : u32 = 100; +pub const FBB_BANDWIDTH_MAX_MBPS : u32 = 10000; +pub const FBB_DEFAULT_BANDWIDTH : u32 = 1000; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FbbOp = enum(u8) { + path_select = FBB_OP_PATH_SELECT, + path_enable = FBB_OP_PATH_ENABLE, + path_disable = FBB_OP_PATH_DISABLE, + path_status = FBB_OP_PATH_STATUS, + path_reset = FBB_OP_PATH_RESET, + path_bypass = FBB_OP_PATH_BYPASS, + path_loopback = FBB_OP_PATH_LOOPBACK, + path_config = FBB_OP_PATH_CONFIG, +} + +pub const FbbPathState = enum(u8) { + disabled = FBB_PATH_STATE_DISABLED, + enabled = FBB_PATH_STATE_ENABLED, + active = FBB_PATH_STATE_ACTIVE, + error = FBB_PATH_STATE_ERROR, + bypassed = FBB_PATH_STATE_BYPASSED, +} + +pub const FbbPathConfig = struct { + path_index : u8, + enabled : bool, + latency_cycles : u8, + bandwidth_mbps : u32, + priority : u8, + auto_bypass : bool, +} + +pub const FbbPathStatus = struct { + path_index : u8, + state : FbbPathState, + active : bool, + error_code : u8, + throughput_mbps : u32, + latency_measured : u16, +} + +pub const FbbActivePath = struct { + active_path : u8, + bypass_path : u8, + loopback_enabled : bool, + paths : [FBB_MAX_PATHS]FbbPathStatus, +} + +pub const FbbCommand = struct { + op : FbbOp, + path_index : u8, + param1 : u16, + param2 : u16, +} + +// ============================================================================ +// Path Config Functions +// ============================================================================ + +// fbb_path_config_init(index: u8) -> FbbPathConfig +// Initialize path config +pub fn fbb_path_config_init(index: u8) -> FbbPathConfig { + return FbbPathConfig { + .path_index = index & 0x07, + .enabled = false, + .latency_cycles = FBB_DEFAULT_LATENCY, + .bandwidth_mbps = FBB_DEFAULT_BANDWIDTH, + .priority = 4, + .auto_bypass = false, + }; +} + +// fbb_path_config_low_latency(index: u8) -> FbbPathConfig +// Create low latency path config +pub fn fbb_path_config_low_latency(index: u8) -> FbbPathConfig { + return FbbPathConfig { + .path_index = index & 0x07, + .enabled = false, + .latency_cycles = FBB_LATENCY_MIN_CYCLES, + .bandwidth_mbps = FBB_DEFAULT_BANDWIDTH, + .priority = 7, + .auto_bypass = false, + }; +} + +// fbb_path_config_high_bandwidth(index: u8) -> FbbPathConfig +// Create high bandwidth path config +pub fn fbb_path_config_high_bandwidth(index: u8) -> FbbPathConfig { + return FbbPathConfig { + .path_index = index & 0x07, + .enabled = false, + .latency_cycles = 8, + .bandwidth_mbps = FBB_BANDWIDTH_MAX_MBPS, + .priority = 3, + .auto_bypass = false, + }; +} + +// fbb_path_config_enable(config: FbbPathConfig) -> FbbPathConfig +// Enable path in config +pub fn fbb_path_config_enable(config: FbbPathConfig) -> FbbPathConfig { + return FbbPathConfig { + .path_index = config.path_index, + .enabled = true, + .latency_cycles = config.latency_cycles, + .bandwidth_mbps = config.bandwidth_mbps, + .priority = config.priority, + .auto_bypass = config.auto_bypass, + }; +} + +// ============================================================================ +// Path Status Functions +// ============================================================================ + +// fbb_path_status_init(index: u8) -> FbbPathStatus +// Initialize path status +pub fn fbb_path_status_init(index: u8) -> FbbPathStatus { + return FbbPathStatus { + .path_index = index & 0x07, + .state = FbbPathState.disabled, + .active = false, + .error_code = 0, + .throughput_mbps = 0, + .latency_measured = 0, + }; +} + +// fbb_path_status_enable(status: FbbPathStatus) -> FbbPathStatus +// Enable path +pub fn fbb_path_status_enable(status: FbbPathStatus) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = FbbPathState.enabled, + .active = false, + .error_code = 0, + .throughput_mbps = status.throughput_mbps, + .latency_measured = status.latency_measured, + }; +} + +// fbb_path_status_activate(status: FbbPathStatus) -> FbbPathStatus +// Activate path +pub fn fbb_path_status_activate(status: FbbPathStatus) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = FbbPathState.active, + .active = true, + .error_code = 0, + .throughput_mbps = status.throughput_mbps, + .latency_measured = status.latency_measured, + }; +} + +// fbb_path_status_bypass(status: FbbPathStatus) -> FbbPathStatus +// Bypass path +pub fn fbb_path_status_bypass(status: FbbPathStatus) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = FbbPathState.bypassed, + .active = false, + .error_code = 0, + .throughput_mbps = 0, + .latency_measured = 0, + }; +} + +// fbb_path_status_error(status: FbbPathStatus, error_code: u8) -> FbbPathStatus +// Set error status +pub fn fbb_path_status_error(status: FbbPathStatus, error_code: u8) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = FbbPathState.error, + .active = false, + .error_code = error_code, + .throughput_mbps = 0, + .latency_measured = 0, + }; +} + +// fbb_path_status_update_metrics(status: FbbPathStatus, throughput: u32, latency: u16) -> FbbPathStatus +// Update path metrics +pub fn fbb_path_status_update_metrics(status: FbbPathStatus, throughput: u32, latency: u16) -> FbbPathStatus { + return FbbPathStatus { + .path_index = status.path_index, + .state = status.state, + .active = status.active, + .error_code = status.error_code, + .throughput_mbps = throughput, + .latency_measured = latency, + }; +} + +// ============================================================================ +// Active Path Functions +// ============================================================================ + +// fbb_active_path_init() -> FbbActivePath +// Initialize active path +pub fn fbb_active_path_init() -> FbbActivePath { + var paths : [FBB_MAX_PATHS]FbbPathStatus = undefined; + for (0..FBB_MAX_PATHS) |i| { + paths[i] = fbb_path_status_init(@as(u8, @truncate(i))); + } + return FbbActivePath { + .active_path = 0, + .bypass_path = 0xFF, // No bypass + .loopback_enabled = false, + .paths = paths, + }; +} + +// fbb_active_path_select(active: FbbActivePath, path_index: u8) -> FbbActivePath +// Select active path +pub fn fbb_active_path_select(active: FbbActivePath, path_index: u8) -> FbbActivePath { + const idx = path_index & 0x07; + var new_paths = active.paths; + + // Deactivate current active path + new_paths[active.active_path] = fbb_path_status_enable(new_paths[active.active_path]); + + // Activate new path + new_paths[idx] = fbb_path_status_activate(new_paths[idx]); + + return FbbActivePath { + .active_path = idx, + .bypass_path = active.bypass_path, + .loopback_enabled = active.loopback_enabled, + .paths = new_paths, + }; +} + +// fbb_active_path_enable_bypass(active: FbbActivePath, bypass_index: u8) -> FbbActivePath +// Enable bypass path +pub fn fbb_active_path_enable_bypass(active: FbbActivePath, bypass_index: u8) -> FbbActivePath { + const idx = bypass_index & 0x07; + var new_paths = active.paths; + new_paths[idx] = fbb_path_status_bypass(new_paths[idx]); + + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = idx, + .loopback_enabled = active.loopback_enabled, + .paths = new_paths, + }; +} + +// fbb_active_path_disable_bypass(active: FbbActivePath) -> FbbActivePath +// Disable bypass path +pub fn fbb_active_path_disable_bypass(active: FbbActivePath) -> FbbActivePath { + var new_paths = active.paths; + + // Restore bypassed path + if (active.bypass_path != 0xFF) { + new_paths[active.bypass_path] = fbb_path_status_enable(new_paths[active.bypass_path]); + } + + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = 0xFF, + .loopback_enabled = active.loopback_enabled, + .paths = new_paths, + }; +} + +// fbb_active_path_enable_loopback(active: FbbActivePath) -> FbbActivePath +// Enable loopback +pub fn fbb_active_path_enable_loopback(active: FbbActivePath) -> FbbActivePath { + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = active.bypass_path, + .loopback_enabled = true, + .paths = active.paths, + }; +} + +// fbb_active_path_disable_loopback(active: FbbActivePath) -> FbbActivePath +// Disable loopback +pub fn fbb_active_path_disable_loopback(active: FbbActivePath) -> FbbActivePath { + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = active.bypass_path, + .loopback_enabled = false, + .paths = active.paths, + }; +} + +// fbb_active_path_get_path(active: FbbActivePath, path_index: u8) -> FbbPathStatus +// Get path status +pub fn fbb_active_path_get_path(active: FbbActivePath, path_index: u8) -> FbbPathStatus { + const idx = path_index & 0x07; + return active.paths[idx]; +} + +// fbb_active_path_update_path_metrics(active: FbbActivePath, path_index: u8, throughput: u32, latency: u16) -> FbbActivePath +// Update path metrics +pub fn fbb_active_path_update_path_metrics(active: FbbActivePath, path_index: u8, throughput: u32, latency: u16) -> FbbActivePath { + const idx = path_index & 0x07; + var new_paths = active.paths; + new_paths[idx] = fbb_path_status_update_metrics(new_paths[idx], throughput, latency); + return FbbActivePath { + .active_path = active.active_path, + .bypass_path = active.bypass_path, + .loopback_enabled = active.loopback_enabled, + .paths = new_paths, + }; +} + +// ============================================================================ +// Validation Functions +// ============================================================================ + +// fbb_path_index_valid(index: u8) -> bool +// Check if path index is valid +pub fn fbb_path_index_valid(index: u8) -> bool { + return index < FBB_PATH_COUNT; +} + +// fbb_latency_valid(latency: u8) -> bool +// Check if latency is valid +pub fn fbb_latency_valid(latency: u8) -> bool { + return latency >= FBB_LATENCY_MIN_CYCLES and latency <= FBB_LATENCY_MAX_CYCLES; +} + +// fbb_bandwidth_valid(bandwidth: u32) -> bool +// Check if bandwidth is valid +pub fn fbb_bandwidth_valid(bandwidth: u32) -> bool { + return bandwidth >= FBB_BANDWIDTH_MIN_MBPS and bandwidth <= FBB_BANDWIDTH_MAX_MBPS; +} + +// fbb_path_config_valid(config: FbbPathConfig) -> bool +// Check if path config is valid +pub fn fbb_path_config_valid(config: FbbPathConfig) -> bool { + return fbb_path_index_valid(config.path_index) + try std.testing.expect(fbb_latency_valid(config.latency_cycles)); + try std.testing.expect(fbb_bandwidth_valid(config.bandwidth_mbps);); +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_fbb_cmd(op: u8, path: u8, param1: u8, param2: u8) -> u32 +// Encode FBB command +pub fn encode_fbb_cmd(op: u8, path: u8, param1: u8, param2: u8) u32 { + // Format: [OP:8][PATH:3][PARAM1:8][PARAM2:8][RES:5] + const op_field : u32 = @as(u32, op) << 24; + const path_field : u32 = @as(u32, path & 0x07) << 21; + const param1_field : u32 = @as(u32, param1) << 13; + const param2_field : u32 = @as(u32, param2) << 5; + return op_field | path_field | param1_field | param2_field; +} + +// decode_fbb_cmd(encoded: u32) -> struct { op: u8, path: u8, param1: u8, param2: u8 } +// Decode FBB command +pub fn decode_fbb_cmd(encoded: u32) -> struct { op: u8, path: u8, param1: u8, param2: u8 } { + const op : u8 = @as(u8, @truncate((encoded >> 24) & 0xFF)); + const path : u8 = @as(u8, @truncate((encoded >> 21) & 0x07)); + const param1 : u8 = @as(u8, @truncate((encoded >> 13) & 0xFF)); + const param2 : u8 = @as(u8, @truncate((encoded >> 5) & 0xFF)); + return .{ .op = op, .path = path, .param1 = param1, .param2 = param2 }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fbb_opcode_base" { + try std.testing.expect(FBB_OPCODE_BASE == 0xD0); +} + +test "fbb_op_constants" { + try std.testing.expect(FBB_OP_PATH_SELECT == 0); + try std.testing.expect(FBB_OP_PATH_ENABLE == 1); + try std.testing.expect(FBB_OP_PATH_DISABLE == 2); + try std.testing.expect(FBB_OP_PATH_BYPASS == 5); +} + +test "fbb_path_state_constants" { + try std.testing.expect(FBB_PATH_STATE_DISABLED == 0); + try std.testing.expect(FBB_PATH_STATE_ENABLED == 1); + try std.testing.expect(FBB_PATH_STATE_ACTIVE == 2); + try std.testing.expect(FBB_PATH_STATE_BYPASSED == 4); +} + +test "fbb_path_count" { + try std.testing.expect(FBB_PATH_COUNT == 8); + try std.testing.expect(FBB_MAX_PATHS == 8); +} + +test "latency_constants" { + try std.testing.expect(FBB_LATENCY_MIN_CYCLES == 1); + try std.testing.expect(FBB_LATENCY_MAX_CYCLES == 16); + try std.testing.expect(FBB_DEFAULT_LATENCY == 4); +} + +test "bandwidth_constants" { + try std.testing.expect(FBB_BANDWIDTH_MIN_MBPS == 100); + try std.testing.expect(FBB_BANDWIDTH_MAX_MBPS == 10000); + try std.testing.expect(FBB_DEFAULT_BANDWIDTH == 1000); +} + +test "fbb_path_config_init_structure" { + given config = fbb_path_config_init(0) + try std.testing.expect(config.path_index == 0); + try std.testing.expect(config.enabled == false); + try std.testing.expect(config.latency_cycles == FBB_DEFAULT_LATENCY); +} + +test "fbb_path_config_low_latency_structure" { + given config = fbb_path_config_low_latency(1) + try std.testing.expect(config.path_index == 1); + try std.testing.expect(config.latency_cycles == FBB_LATENCY_MIN_CYCLES); + try std.testing.expect(config.priority == 7); +} + +test "fbb_path_config_high_bandwidth_structure" { + given config = fbb_path_config_high_bandwidth(2) + try std.testing.expect(config.path_index == 2); + try std.testing.expect(config.bandwidth_mbps == FBB_BANDWIDTH_MAX_MBPS); +} + +test "fbb_path_config_enable" { + given config = fbb_path_config_init(0) + try std.testing.expect(result = fbb_path_config_enable(config)); + try std.testing.expect(result.enabled == true); +} + +test "fbb_path_status_init_structure" { + given status = fbb_path_status_init(0) + try std.testing.expect(status.path_index == 0); + try std.testing.expect(status.state == FbbPathState.disabled); + try std.testing.expect(status.active == false); +} + +test "fbb_path_status_enable" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_enable(status)); + try std.testing.expect(result.state == FbbPathState.enabled); + try std.testing.expect(result.active == false); +} + +test "fbb_path_status_activate" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_activate(status)); + try std.testing.expect(result.state == FbbPathState.active); + try std.testing.expect(result.active == true); +} + +test "fbb_path_status_bypass" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_bypass(status)); + try std.testing.expect(result.state == FbbPathState.bypassed); + try std.testing.expect(result.active == false); +} + +test "fbb_path_status_error" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_error(status, 1)); + try std.testing.expect(result.state == FbbPathState.error); + try std.testing.expect(result.error_code == 1); +} + +test "fbb_path_status_update_metrics" { + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_update_metrics(status, 5000, 100)); + try std.testing.expect(result.throughput_mbps == 5000); + try std.testing.expect(result.latency_measured == 100); +} + +test "fbb_active_path_init_structure" { + given active = fbb_active_path_init() + try std.testing.expect(active.active_path == 0); + try std.testing.expect(active.bypass_path == 0xFF); + try std.testing.expect(active.loopback_enabled == false); +} + +test "fbb_active_path_select" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_select(active, 3)); + try std.testing.expect(result.active_path == 3); + try std.testing.expect(result.paths[3].active == true); +} + +test "fbb_active_path_enable_bypass" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_enable_bypass(active, 5)); + try std.testing.expect(result.bypass_path == 5); + try std.testing.expect(result.paths[5].state == FbbPathState.bypassed); +} + +test "fbb_active_path_disable_bypass" { + given active = fbb_active_path_init() + try std.testing.expect(with_bypass = fbb_active_path_enable_bypass(active, 5)); + try std.testing.expect(result = fbb_active_path_disable_bypass(with_bypass)); + try std.testing.expect(result.bypass_path == 0xFF); +} + +test "fbb_active_path_enable_loopback" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_enable_loopback(active)); + try std.testing.expect(result.loopback_enabled == true); +} + +test "fbb_active_path_disable_loopback" { + given active = fbb_active_path_init() + try std.testing.expect(with_loopback = fbb_active_path_enable_loopback(active)); + try std.testing.expect(result = fbb_active_path_disable_loopback(with_loopback)); + try std.testing.expect(result.loopback_enabled == false); +} + +test "fbb_active_path_get_path" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_get_path(active, 2)); + try std.testing.expect(result.path_index == 2); +} + +test "fbb_active_path_update_path_metrics" { + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_update_path_metrics(active, 2, 3000, 50)); + try std.testing.expect(result.paths[2].throughput_mbps == 3000); +} + +test "fbb_path_index_valid_true" { + try std.testing.expect(fbb_path_index_valid(0) == true); + try std.testing.expect(fbb_path_index_valid(7) == true); +} + +test "fbb_path_index_valid_false" { + try std.testing.expect(fbb_path_index_valid(8) == false); + try std.testing.expect(fbb_path_index_valid(255) == false); +} + +test "fbb_latency_valid_true" { + try std.testing.expect(fbb_latency_valid(1) == true); + try std.testing.expect(fbb_latency_valid(10) == true); + try std.testing.expect(fbb_latency_valid(16) == true); +} + +test "fbb_latency_valid_false" { + try std.testing.expect(fbb_latency_valid(0) == false); + try std.testing.expect(fbb_latency_valid(17) == false); +} + +test "fbb_bandwidth_valid_true" { + try std.testing.expect(fbb_bandwidth_valid(100) == true); + try std.testing.expect(fbb_bandwidth_valid(5000) == true); + try std.testing.expect(fbb_bandwidth_valid(10000) == true); +} + +test "fbb_bandwidth_valid_false" { + try std.testing.expect(fbb_bandwidth_valid(50) == false); + try std.testing.expect(fbb_bandwidth_valid(20000) == false); +} + +test "fbb_path_config_valid_true" { + given config = fbb_path_config_init(0) + try std.testing.expect(fbb_path_config_valid(config) == true); +} + +test "fbb_path_config_valid_false_invalid_latency" { + given config = FbbPathConfig{.path_index = 0, .enabled = false, .latency_cycles = 0, .bandwidth_mbps = 1000, .priority = 4, .auto_bypass = false} + try std.testing.expect(fbb_path_config_valid(config) == false); +} + +test "encode_fbb_cmd" { + given encoded = encode_fbb_cmd(0x00, 3, 10, 20) + try std.testing.expect((encoded >> 24) == 0x00); +} + +test "decode_fbb_cmd" { + given decoded = decode_fbb_cmd(0x001A14A0) + try std.testing.expect(decoded.op == 0); + try std.testing.expect(decoded.path == 3); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fbb_path_count_positive + assert FBB_PATH_COUNT > 0 + try std.testing.expect(FBB_MAX_PATHS == FBB_PATH_COUNT); + +invariant latency_range_positive + assert FBB_LATENCY_MIN_CYCLES > 0 and FBB_LATENCY_MAX_CYCLES >= FBB_LATENCY_MIN_CYCLES + +invariant bandwidth_range_positive + assert FBB_BANDWIDTH_MIN_MBPS > 0 and FBB_BANDWIDTH_MAX_MBPS >= FBB_BANDWIDTH_MIN_MBPS + +invariant default_latency_in_range + assert fbb_latency_valid(FBB_DEFAULT_LATENCY) + +invariant default_bandwidth_in_range + assert fbb_bandwidth_valid(FBB_DEFAULT_BANDWIDTH) + +invariant fbb_path_config_init_valid + given config = fbb_path_config_init(0) + assert fbb_path_config_valid(config) + +invariant fbb_path_config_low_latency_min_latency + given config = fbb_path_config_low_latency(0) + assert config.latency_cycles == FBB_LATENCY_MIN_CYCLES + +invariant fbb_path_config_high_bandwidth_max_bandwidth + given config = fbb_path_config_high_bandwidth(0) + assert config.bandwidth_mbps == FBB_BANDWIDTH_MAX_MBPS + +invariant fbb_path_status_init_disabled + given status = fbb_path_status_init(0) + try std.testing.expect(status.state == FbbPathState.disabled); + +invariant fbb_path_status_enable_enabled_not_active + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_enable(status)); + try std.testing.expect(result.state == FbbPathState.enabled and result.active == false); + +invariant fbb_path_status_activate_active + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_activate(status)); + try std.testing.expect(result.state == FbbPathState.active and result.active == true); + +invariant fbb_path_status_bypass_bypassed_not_active + given status = fbb_path_status_init(0) + try std.testing.expect(result = fbb_path_status_bypass(status)); + try std.testing.expect(result.state == FbbPathState.bypassed and result.active == false); + +invariant fbb_active_path_init_no_bypass + given active = fbb_active_path_init() + try std.testing.expect(active.bypass_path == 0xFF); + +invariant fbb_active_path_select_updates_active + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_select(active, 5)); + try std.testing.expect(result.active_path == 5); + +invariant fbb_active_path_enable_loopback_sets_flag + given active = fbb_active_path_init() + try std.testing.expect(result = fbb_active_path_enable_loopback(active)); + try std.testing.expect(result.loopback_enabled == true); + +invariant fbb_active_path_disable_loopback_clears_flag + given active = fbb_active_path_init() + try std.testing.expect(with_loopback = fbb_active_path_enable_loopback(active)); + try std.testing.expect(result = fbb_active_path_disable_loopback(with_loopback)); + try std.testing.expect(result.loopback_enabled == false); + +invariant fbb_path_index_valid_range + assert fbb_path_index_valid(0) and not fbb_path_index_valid(FBB_PATH_COUNT) + +invariant fbb_latency_valid_range + assert fbb_latency_valid(FBB_LATENCY_MIN_CYCLES) + try std.testing.expect(fbb_latency_valid(FBB_LATENCY_MAX_CYCLES)); + try std.testing.expect(not fbb_latency_valid(FBB_LATENCY_MAX_CYCLES + 1)); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fbb_path_config_init_latency + measure: nanoseconds to fbb_path_config_init(0) + target: < 20ns + +bench fbb_path_config_low_latency_latency + measure: nanoseconds to fbb_path_config_low_latency(0) + target: < 20ns + +bench fbb_path_config_high_bandwidth_latency + measure: nanoseconds to fbb_path_config_high_bandwidth(0) + target: < 20ns + +bench fbb_path_status_init_latency + measure: nanoseconds to fbb_path_status_init(0) + target: < 20ns + +bench fbb_path_status_enable_latency + measure: nanoseconds to fbb_path_status_enable(fbb_path_status_init(0)) + target: < 15ns + +bench fbb_path_status_activate_latency + measure: nanoseconds to fbb_path_status_activate(fbb_path_status_init(0)) + target: < 15ns + +bench fbb_active_path_init_latency + measure: nanoseconds to fbb_active_path_init() + target: < 50ns + +bench fbb_active_path_select_latency + measure: nanoseconds to fbb_active_path_select(fbb_active_path_init(), 3) + target: < 50ns + +bench fbb_active_path_enable_loopback_latency + measure: nanoseconds to fbb_active_path_enable_loopback(fbb_active_path_init()) + target: < 15ns + +bench fbb_path_index_valid_latency + measure: nanoseconds to fbb_path_index_valid(5) + target: < 10ns + +bench fbb_latency_valid_latency + measure: nanoseconds to fbb_latency_valid(10) + target: < 10ns + +bench fbb_bandwidth_valid_latency + measure: nanoseconds to fbb_bandwidth_valid(5000) + target: < 15ns + +bench encode_fbb_cmd_latency + measure: nanoseconds to encode_fbb_cmd(0x00, 3, 10, 20) + target: < 20ns + +bench decode_fbb_cmd_latency + measure: nanoseconds to decode_fbb_cmd(0x001A14A0) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/fp8_e4m3.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/fp8_e4m3.t27 new file mode 100644 index 0000000000..06fb882f2d --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/fp8_e4m3.t27 @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: Apache-2.0 +; fp8_e4m3.t27 — FP8 E4M3 8-bit Floating Point +; 8-bit float with 4 exponent, 3 mantissa bits (no implicit leading bit) +; OCP FP8 format optimized for neural network training +; Range: ~-240 to ~240, precision: ~6-7 significant bits +; φ² + 1/φ² = 3 | TRINITY + +module triformat-fp8_e4m3; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 4; +pub const MANT_BITS : u8 = 3; + +pub const SIGN_SHIFT : u8 = 7; +pub const EXP_SHIFT : u8 = 3; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u8 = 0x80; // 1 << 7 +pub const EXP_MASK : u8 = 0x78; // 0b01111000 +pub const MANT_MASK : u8 = 0x07; // 0b00000111 + +pub const EXP_MAX : u8 = 15; // All ones in 4 bits +pub const EXP_MIN : u8 = 0; +pub const EXP_BIAS : u8 = 7; // Exponent bias for FP8 E4M3 + +pub const MANT_DIVISOR : u8 = 8; // 2^3 + +// FP8 E4M3 special values +pub const FP8_ZERO_POS : u8 = 0x00; +pub const FP8_ZERO_NEG : u8 = 0x80; +pub const FP8_NAN_POS : u8 = 0x7F; // All exp + mant != 0 +pub const FP8_NAN_NEG : u8 = 0xFF; +pub const FP8_INF_POS : u8 = 0x78; // All exp + mant = 0 +pub const FP8_INF_NEG : u8 = 0xF8; + +// Subnormal exponent +pub const SUBNORMAL_EXP : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FP8E4M3 = u8; // 8-bit value + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// fp8_extract_sign(fp8: FP8E4M3) -> i8 +// Extract sign bit (bit 7) +// Returns: 0 for positive, -1 for negative +pub fn fp8_extract_sign(fp8: FP8E4M3) i8 { + const bit = (fp8 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// fp8_extract_exponent(fp8: FP8E4M3) -> u8 +// Extract exponent bits (bits 6-3) +// Returns: 0-15 +pub fn fp8_extract_exponent(fp8: FP8E4M3) u8 { + return (fp8 >> EXP_SHIFT) & 0x0F; +} + +// fp8_extract_mantissa(fp8: FP8E4M3) -> u8 +// Extract mantissa bits (bits 2-0) +// Returns: 0-7 +pub fn fp8_extract_mantissa(fp8: FP8E4M3) u8 { + return fp8 & MANT_MASK; +} + +// fp8_from_components(sign: i8, exp: u8, mant: u8) -> FP8E4M3 +// Assemble FP8 from sign, exponent, mantissa +pub fn fp8_from_components(sign: i8, exp: u8, mant: u8) FP8E4M3 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(FP8E4M3, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(FP8E4M3, @intCast(exp & 0x0F)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// fp8_is_zero(fp8: FP8E4M3) -> bool +// Check if FP8 is zero (positive or negative) +pub fn fp8_is_zero(fp8: FP8E4M3) bool { + return fp8 == FP8_ZERO_POS or fp8 == FP8_ZERO_NEG; +} + +// fp8_is_special(fp8: FP8E4M3) -> bool +// Check if FP8 is Inf or NaN (exp == 15) +pub fn fp8_is_special(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX; +} + +// fp8_is_inf(fp8: FP8E4M3) -> bool +// Check if FP8 is infinity (exp == 15, mant == 0) +pub fn fp8_is_inf(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) == 0; +} + +// fp8_is_nan(fp8: FP8E4M3) -> bool +// Check if FP8 is NaN (exp == 15, mant != 0) +pub fn fp8_is_nan(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) != 0; +} + +// fp8_is_subnormal(fp8: FP8E4M3) -> bool +// Check if FP8 is subnormal (exp == 0, mant != 0) +pub fn fp8_is_subnormal(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == 0 and fp8_extract_mantissa(fp8) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// fp8_encode_f32(value: f32) -> FP8E4M3 +// Encode IEEE 754 single precision to FP8 E4M3 +// Round-to-nearest, ties to even +pub fn fp8_encode_f32(value: f32) FP8E4M3 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return if (value < 0.0) FP8_NAN_NEG else FP8_NAN_POS; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i16 = @as(i16, @intCast((f32_bits >> 23) & 0xFF)) - 127; + var f32_mant: u32 = f32_bits & 0x007FFFFF; + + // Handle very small values (subnormals) + if (f32_exp <= @as(i16, -EXP_BIAS)) { + // Below FP8 minimum normal - try to encode as subnormal + const subnormal_shift = @as(i16, -EXP_BIAS) - f32_exp + 1; + if (subnormal_shift > 3 + 23) { + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + var mant = f32_mant >> 23; + if (subnormal_shift > 0) { + const shift_amount = @as(u5, @intCast(subnormal_shift)); + if (shift_amount <= 24) { + mant = (f32_mant | 0x00800000) >> shift_amount; + } + } + return fp8_from_components(sign, 0, @as(u8, @truncate(mant)) & 0x07); + } + + // Convert exp from f32 bias (127) to FP8 bias (7) + var fp8_exp = @as(i16, f32_exp + EXP_BIAS); + + // Clamp exponent + if (fp8_exp >= EXP_MAX) { + return if (sign < 0) FP8_INF_NEG else FP8_INF_POS; + } else if (fp8_exp < 1) { + // Underflow to zero (could be subnormal, simplified) + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Extract mantissa and scale to 3 bits + // f32 mantissa is 24 bits (including implicit 1), FP8 needs 3 bits + // Shift right by 21 bits (24 - 3 = 21) + var mant = f32_mant >> 21; + + // Round-to-nearest with ties to even + const discarded = f32_mant & 0x1FFFFF; // Lower 21 bits + if ((discarded & 0x100000) != 0) { + // Halfway or more + if ((discarded & 0x0FFFFF) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (fp8_exp < @as(i16, EXP_MAX - 1)) { + fp8_exp += 1; + } + } + } + } + + return fp8_from_components(sign, @as(u8, @intCast(fp8_exp)), @as(u8, mant)); +} + +// fp8_decode_f32(fp8: FP8E4M3) -> f32 +// Decode FP8 E4M3 to IEEE 754 single precision +pub fn fp8_decode_f32(fp8: FP8E4M3) f32 { + // Handle zero + if (fp8_is_zero(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (fp8_is_nan(fp8)) { + return std.math.nan(f32); + } + + // Handle Infinity + if (fp8_is_inf(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -std.math.inf(f32) else std.math.inf(f32); + } + + // Extract components + const sign = fp8_extract_sign(fp8); + const exp = fp8_extract_exponent(fp8); + const mant = fp8_extract_mantissa(fp8); + + var f32_value: f32 = undefined; + + // Handle subnormal + if (exp == 0) { + // Subnormal: value = mant * 2^(-6) = mant * 2^(1-bias) + const bias_adjusted: i8 = 1 - @as(i8, EXP_BIAS); + const mant_f32 = @as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR)); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } else { + // Normal: value = (1 + mant/8) * 2^(exp - 7) + const bias_adjusted = @as(i16, exp) - @as(i16, EXP_BIAS); + const mant_f32 = 1.0 + (@as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR))); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } + + return if (sign < 0) -f32_value else f32_value; +} + +// fp8_encode_f64(value: f64) -> FP8E4M3 +// Encode IEEE 754 double precision to FP8 E4M3 +pub fn fp8_encode_f64(value: f64) FP8E4M3 { + return fp8_encode_f32(@as(f32, @floatCast(value))); +} + +// fp8_decode_f64(fp8: FP8E4M3) -> f64 +// Decode FP8 E4M3 to IEEE 754 double precision +pub fn fp8_decode_f64(fp8: FP8E4M3) f64 { + const f32_val = fp8_decode_f32(fp8); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// fp8_add(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Add two FP8 values +pub fn fp8_add(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa + fb); +} + +// fp8_sub(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Subtract two FP8 values +pub fn fp8_sub(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa - fb); +} + +// fp8_mul(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Multiply two FP8 values +pub fn fp8_mul(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa * fb); +} + +// fp8_div(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Divide two FP8 values +pub fn fp8_div(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fb = fp8_decode_f32(b); + if (fb == 0.0) { + const fa = fp8_decode_f32(a); + return if (fa < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + const fa = fp8_decode_f32(a); + return fp8_encode_f32(fa / fb); +} + +// fp8_sqrt(value: FP8E4M3) -> FP8E4M3 +// Square root of FP8 value +pub fn fp8_sqrt(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + if (fv < 0.0) { + return FP8_NAN_POS; + } + return fp8_encode_f32(std.math.sqrt(fv)); +} + +// fp8_rsqrt(value: FP8E4M3) -> FP8E4M3 +// Reciprocal square root (1/sqrt(x)) +pub fn fp8_rsqrt(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + if (fv <= 0.0) { + return FP8_INF_POS; + } + return fp8_encode_f32(1.0 / std.math.sqrt(fv)); +} + +// fp8_abs(value: FP8E4M3) -> FP8E4M3 +// Absolute value of FP8 +pub fn fp8_abs(value: FP8E4M3) FP8E4M3 { + return value & ~SIGN_MASK; +} + +// fp8_neg(value: FP8E4M3) -> FP8E4M3 +// Negate FP8 +pub fn fp8_neg(value: FP8E4M3) FP8E4M3 { + return value ^ SIGN_MASK; +} + +// fp8_is_equal(a: FP8E4M3, b: FP8E4M3) -> bool +// Check if two FP8 values are equal +pub fn fp8_is_equal(a: FP8E4M3, b: FP8E4M3) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + if (fp8_is_zero(a) and fp8_is_zero(b)) { + return true; + } + return a == b; +} + +// fp8_is_greater(a: FP8E4M3, b: FP8E4M3) -> bool +// Check if a > b +pub fn fp8_is_greater(a: FP8E4M3, b: FP8E4M3) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + const sign_a = fp8_extract_sign(a); + const sign_b = fp8_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return fp8_neg(a) > fp8_neg(b); + } + return a > b; +} + +// fp8_max(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Return the larger of two FP8 values +pub fn fp8_max(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + return if (fp8_is_greater(a, b)) a else b; +} + +// fp8_min(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Return the smaller of two FP8 values +pub fn fp8_min(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + return if (fp8_is_greater(a, b)) b else a; +} + +// fp8_clamp(value: FP8E4M3, min: FP8E4M3, max: FP8E4M3) -> FP8E4M3 +// Clamp value between min and max +pub fn fp8_clamp(value: FP8E4M3, min: FP8E4M3, max: FP8E4M3) FP8E4M3 { + return fp8_min(fp8_max(value, min), max); +} + +// fp8_lerp(a: FP8E4M3, b: FP8E4M3, t: f32) -> FP8E4M3 +// Linear interpolation between a and b +pub fn fp8_lerp(a: FP8E4M3, b: FP8E4M3, t: f32) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const result = fa + (fb - fa) * t; + return fp8_encode_f32(result); +} + +// fp8_fma(a: FP8E4M3, b: FP8E4M3, c: FP8E4M3) -> FP8E4M3 +// Fused multiply-add: a * b + c +pub fn fp8_fma(a: FP8E4M3, b: FP8E4M3, c: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const fc = fp8_decode_f32(c); + return fp8_encode_f32(fa * fb + fc); +} + +// fp8_scale(value: FP8E4M3, scale: f32) -> FP8E4M3 +// Scale FP8 value by scalar +pub fn fp8_scale(value: FP8E4M3, scale: f32) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * scale); +} + +// fp8_relu(value: FP8E4M3) -> FP8E4M3 +// ReLU activation: max(0, x) +pub fn fp8_relu(value: FP8E4M3) FP8E4M3 { + return if (fp8_extract_sign(value) < 0) FP8_ZERO_POS else value; +} + +// fp8_sigmoid(value: FP8E4M3) -> FP8E4M3 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn fp8_sigmoid(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// fp8_tanh(value: FP8E4M3) -> FP8E4M3 +// Tanh activation +pub fn fp8_tanh(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(std.math.tanh(fv)); +} + +// fp8_gelu(value: FP8E4M3) -> FP8E4M3 +// GELU activation +pub fn fp8_gelu(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + const sqrt_2_over_pi = 0.7978845608; + const cube_coeff = 0.044715; + const tanh_arg = fv * (1.0 + cube_coeff * fv * fv); + return fp8_encode_f32(0.5 * fv * (1.0 + std.math.tanh(sqrt_2_over_pi * tanh_arg))); +} + +// fp8_swish(value: FP8E4M3) -> FP8E4M3 +// Swish activation: x * sigmoid(x) +pub fn fp8_swish(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * (1.0 / (1.0 + std.math.exp(-fv)))); +} + +// fp8_quantize_to_int4(value: FP8E4M3) -> i8 +// Quantize FP8 to Int4 (range [-8, 7]) +pub fn fp8_quantize_to_int4(value: FP8E4M3) i8 { + const fv = fp8_decode_f32(value); + const scaled = fv * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// fp8_quantize_to_int8(value: FP8E4M3) -> i8 +// Quantize FP8 to Int8 (range [-128, 127]) +pub fn fp8_quantize_to_int8(value: FP8E4M3) i8 { + const fv = fp8_decode_f32(value); + const scaled = fv * 127.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 127) { + result = 127; + } else if (result < -128) { + result = -128; + } + return @as(i8, @intCast(result)); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fp8_extract_sign_positive" { + given value = 0x3C + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "fp8_extract_sign_negative" { + given value = 0xBC + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "fp8_extract_exponent_max" { + given value = 0x78 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == EXP_MAX); +} + +test "fp8_extract_exponent_zero" { + given value = 0x07 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == 0); +} + +test "fp8_extract_mantissa_max" { + given value = 0x07 + try std.testing.expect(mant = fp8_extract_mantissa(value)); + try std.testing.expect(mant == MANT_MASK); +} + +test "fp8_is_zero_positive" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_POS) == true); +} + +test "fp8_is_zero_negative" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_NEG) == true); +} + +test "fp8_is_zero_nonzero" { + try std.testing.expect(fp8_is_zero(0x01) == false); +} + +test "fp8_is_inf_positive" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true); +} + +test "fp8_is_inf_negative" { + try std.testing.expect(fp8_is_inf(FP8_INF_NEG) == true); +} + +test "fp8_is_nan_positive" { + try std.testing.expect(fp8_is_nan(FP8_NAN_POS) == true); +} + +test "fp8_is_nan_negative" { + try std.testing.expect(fp8_is_nan(FP8_NAN_NEG) == true); +} + +test "fp8_is_inf_not_nan" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true and fp8_is_nan(FP8_INF_POS) == false); +} + +test "fp8_is_special_inf" { + try std.testing.expect(fp8_is_special(FP8_INF_POS) == true); +} + +test "fp8_is_special_nan" { + try std.testing.expect(fp8_is_special(FP8_NAN_POS) == true); +} + +test "fp8_is_subnormal_true" { + given value = 0x01 + try std.testing.expect(fp8_is_subnormal(value) == true); +} + +test "fp8_is_subnormal_false_normal" { + given value = 0x08 + try std.testing.expect(fp8_is_subnormal(value) == false); +} + +test "fp8_is_subnormal_false_zero" { + try std.testing.expect(fp8_is_subnormal(FP8_ZERO_POS) == false); +} + +test "fp8_encode_f32_zero" { + given fp = fp8_encode_f32(0.0) + try std.testing.expect(fp == FP8_ZERO_POS); +} + +test "fp8_encode_f32_negative_zero" { + given fp = fp8_encode_f32(-0.0) + try std.testing.expect(fp == FP8_ZERO_NEG); +} + +test "fp8_encode_f32_one" { + given fp = fp8_encode_f32(1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 1.0) < 0.05); +} + +test "fp8_encode_f32_negative_one" { + given fp = fp8_encode_f32(-1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded + 1.0) < 0.05); +} + +test "fp8_encode_f32_two" { + given fp = fp8_encode_f32(2.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 2.0) < 0.1); +} + +test "fp8_encode_f32_half" { + given fp = fp8_encode_f32(0.5) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 0.5) < 0.05); +} + +test "fp8_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.1); +} + +test "fp8_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.1); +} + +test "fp8_encode_f32_nan" { + given fp = fp8_encode_f32(std.math.nan(f32)) + try std.testing.expect(fp8_is_nan(fp) == true); +} + +test "fp8_encode_f32_inf" { + given fp = fp8_encode_f32(std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_encode_f32_neg_inf" { + given fp = fp8_encode_f32(-std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_decode_f32_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_POS) == 0.0); +} + +test "fp8_decode_f32_negative_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_NEG) == -0.0); +} + +test "fp8_decode_f32_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_POS)) == true); +} + +test "fp8_decode_f32_neg_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_NEG)) == true); +} + +test "fp8_decode_f32_nan" { + try std.testing.expect(std.math.isNan(fp8_decode_f32(FP8_NAN_POS)) == true); +} + +test "fp8_add_simple" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(result = fp8_add(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.15); +} + +test "fp8_sub_simple" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(result = fp8_sub(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.15); +} + +test "fp8_mul_simple" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(result = fp8_mul(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 6.0) < 0.3); +} + +test "fp8_div_simple" { + given a = fp8_encode_f32(6.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.15); +} + +test "fp8_div_by_zero_positive" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_POS); +} + +test "fp8_div_by_zero_negative" { + given a = fp8_encode_f32(-5.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_NEG); +} + +test "fp8_sqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.1); +} + +test "fp8_sqrt_negative" { + given value = fp8_encode_f32(-4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(fp8_is_nan(result) == true); +} + +test "fp8_rsqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_rsqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_abs_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(result = fp8_abs(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_abs_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(abs_val = fp8_abs(value)); + try std.testing.expect(fp8_extract_sign(abs_val) == 0); +} + +test "fp8_neg_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) < 0); +} + +test "fp8_neg_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) == 0); +} + +test "fp8_is_equal_true" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(fp8_is_equal(value, value) == true); +} + +test "fp8_is_equal_false" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(fp8_is_equal(a, b) == false); +} + +test "fp8_is_equal_nan" { + try std.testing.expect(fp8_is_equal(FP8_NAN_POS, FP8_NAN_POS) == false); +} + +test "fp8_is_equal_zero" { + try std.testing.expect(fp8_is_equal(FP8_ZERO_POS, FP8_ZERO_NEG) == true); +} + +test "fp8_is_greater_positive" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(fp8_is_greater(a, b) == true); +} + +test "fp8_max_returns_larger" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 4.0); +} + +test "fp8_min_returns_smaller" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 4.0); +} + +test "fp8_lerp_zero" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, a) == true); +} + +test "fp8_lerp_one" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, b) == true); +} + +test "fp8_fma_simple" { + given a = fp8_encode_f32(2.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(c = fp8_encode_f32(1.0)); + try std.testing.expect(result = fp8_fma(a, b, c)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 7.0) < 0.3); +} + +test "fp8_relu_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_relu_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_relu_zero" { + given result = fp8_relu(FP8_ZERO_POS) + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_sigmoid_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.05); +} + +test "fp8_sigmoid_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.95); +} + +test "fp8_sigmoid_negative" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 0.05); +} + +test "fp8_tanh_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded) < 0.05); +} + +test "fp8_tanh_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.95); +} + +test "fp8_tanh_negative" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= -0.95); +} + +test "fp8_gelu_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_gelu(value)); + try std.testing.expect(fp8_extract_sign(result) == 0); +} + +test "fp8_swish_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_swish_positive" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded > 0.0); +} + +test "fp8_quantize_to_int4_max" { + given value = fp8_encode_f32(2.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "fp8_quantize_to_int4_min" { + given value = fp8_encode_f32(-2.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "fp8_quantize_to_int8_max" { + given value = fp8_encode_f32(2.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result == 127); +} + +test "fp8_quantize_to_int8_min" { + given value = fp8_encode_f32(-2.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result == -128); +} + +test "fp8_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 7); + try std.testing.expect(mant = 4); + try std.testing.expect(fp = fp8_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = fp8_extract_sign(fp)); + try std.testing.expect(extracted_exp = fp8_extract_exponent(fp)); + try std.testing.expect(extracted_mant = fp8_extract_mantissa(fp)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fp8_bits_constant + assert BITS == 8 + +invariant fp8_sign_bits_one + assert SIGN_BITS == 1 + +invariant fp8_exp_bits_four + assert EXP_BITS == 4 + +invariant fp8_mant_bits_three + assert MANT_BITS == 3 + +invariant fp8_exp_bias_seven + assert EXP_BIAS == 7 + +invariant fp8_exp_max_all_ones + assert EXP_MAX == 0x0F + +invariant fp8_sign_mask_correct + assert SIGN_MASK == 0x80 + +invariant fp8_exp_mask_correct + assert EXP_MASK == 0x78 + +invariant fp8_mant_mask_correct + assert MANT_MASK == 0x07 + +invariant fp8_masks_sum_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFF + +invariant fp8_zero_pos_no_sign + assert (FP8_ZERO_POS & SIGN_MASK) == 0 + +invariant fp8_zero_neg_has_sign + assert (FP8_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant fp8_inf_pos_max_exp + assert (FP8_INF_POS & EXP_MASK) == EXP_MASK + +invariant fp8_inf_pos_no_mant + assert (FP8_INF_POS & MANT_MASK) == 0 + +invariant fp8_nan_has_mantissa + assert (FP8_NAN_POS & MANT_MASK) != 0 + +invariant fp8_abs_removes_sign + assert fp8_abs(FP8_ZERO_NEG) == FP8_ZERO_POS + +invariant fp8_neg_toggles_sign + assert (fp8_neg(FP8_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant fp8_add_zero_identity + given value = fp8_encode_f32(5.0) + assert fp8_is_equal(fp8_add(value, FP8_ZERO_POS), value) == true + +invariant fp8_sub_zero_identity + given value = fp8_encode_f32(5.0) + assert fp8_is_equal(fp8_sub(value, FP8_ZERO_POS), value) == true + +invariant fp8_mul_one_identity + given value = fp8_encode_f32(5.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_mul(value, one), value) == true or fp8_magnitude(value) > 0.5 + +invariant fp8_div_one_identity + given value = fp8_encode_f32(5.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_div(value, one), value) == true + +invariant fp8_max_ge_both + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(fp8_is_greater(result, a) == true and fp8_is_greater(result, b) == true or fp8_is_equal(result, b) == true); + +invariant fp8_min_le_both + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(fp8_is_greater(a, result) == true or fp8_is_equal(a, result) == true); + +invariant fp8_relu_non_negative + given result = fp8_relu(fp8_encode_f32(5.0)) + try std.testing.expect(fp8_extract_sign(result) == 0); + +invariant fp8_lerp_zero_start + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + assert fp8_is_equal(fp8_lerp(a, b, 0.0), a) == true + +invariant fp8_lerp_one_end + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + assert fp8_is_equal(fp8_lerp(a, b, 1.0), b) == true + +invariant fp8_sqrt_square_close + given value = fp8_encode_f32(4.0) + try std.testing.expect(sqrt_val = fp8_sqrt(value)); + try std.testing.expect(sq_val = fp8_mul(sqrt_val, sqrt_val)); + try std.testing.expect(decoded = fp8_decode_f32(sq_val)); + try std.testing.expect(abs(decoded - 4.0) < 0.5); + +invariant fp8_quantize_to_int4_in_range + given result = fp8_quantize_to_int4(fp8_encode_f32(1.0)) + assert result >= -8 and result <= 7 + +invariant fp8_quantize_to_int8_in_range + given result = fp8_quantize_to_int8(fp8_encode_f32(1.0)) + assert result >= -128 and result <= 127 + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fp8_encode_f32_latency + measure: nanoseconds to fp8_encode_f32(1.5) + target: < 50ns + +bench fp8_decode_f32_latency + measure: nanoseconds to fp8_decode_f32(0x3C) + target: < 50ns + +bench fp8_add_latency + measure: nanoseconds to fp8_add(0x3C, 0x48) + target: < 100ns + +bench fp8_mul_latency + measure: nanoseconds to fp8_mul(0x3C, 0x3C) + target: < 100ns + +bench fp8_sqrt_latency + measure: nanoseconds to fp8_sqrt(0x30) + target: < 100ns + +bench fp8_sigmoid_latency + measure: nanoseconds to fp8_sigmoid(0x30) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/fp8_e5m2.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/fp8_e5m2.t27 new file mode 100644 index 0000000000..e46e52429f --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/fp8_e5m2.t27 @@ -0,0 +1,1212 @@ +// SPDX-License-Identifier: Apache-2.0 +; fp8_e5m2.t27 — FP8 E5M2 8-bit Floating Point +; 8-bit float with 5 exponent, 2 mantissa bits (with implicit leading bit) +; OCP FP8 format optimized for inference and wide dynamic range +; Range: ~-57k to ~57k, precision: ~3-4 significant bits +; φ² + 1/φ² = 3 | TRINITY + +module triformat-fp8_e5m2; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 5; +pub const MANT_BITS : u8 = 2; + +pub const SIGN_SHIFT : u8 = 7; +pub const EXP_SHIFT : u8 = 2; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u8 = 0x80; // 1 << 7 +pub const EXP_MASK : u8 = 0x7C; // 0b01111100 +pub const MANT_MASK : u8 = 0x03; // 0b00000011 + +pub const EXP_MAX : u8 = 31; // All ones in 5 bits +pub const EXP_MIN : u8 = 0; +pub const EXP_BIAS : u8 = 15; // Exponent bias for FP8 E5M2 + +pub const MANT_DIVISOR : u8 = 4; // 2^2 + +// FP8 E5M2 special values +pub const FP8_ZERO_POS : u8 = 0x00; +pub const FP8_ZERO_NEG : u8 = 0x80; +pub const FP8_NAN_POS : u8 = 0x7F; // All exp + mant != 0 +pub const FP8_NAN_NEG : u8 = 0xFF; +pub const FP8_INF_POS : u8 = 0x7C; // All exp + mant = 0 +pub const FP8_INF_NEG : u8 = 0xFC; + +// Subnormal exponent +pub const SUBNORMAL_EXP : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FP8E5M2 = u8; // 8-bit value + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// fp8_extract_sign(fp8: FP8E5M2) -> i8 +// Extract sign bit (bit 7) +// Returns: 0 for positive, -1 for negative +pub fn fp8_extract_sign(fp8: FP8E5M2) i8 { + const bit = (fp8 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// fp8_extract_exponent(fp8: FP8E5M2) -> u8 +// Extract exponent bits (bits 6-2) +// Returns: 0-31 +pub fn fp8_extract_exponent(fp8: FP8E5M2) u8 { + return (fp8 >> EXP_SHIFT) & 0x1F; +} + +// fp8_extract_mantissa(fp8: FP8E5M2) -> u8 +// Extract mantissa bits (bits 1-0) +// Returns: 0-3 +pub fn fp8_extract_mantissa(fp8: FP8E5M2) u8 { + return fp8 & MANT_MASK; +} + +// fp8_from_components(sign: i8, exp: u8, mant: u8) -> FP8E5M2 +// Assemble FP8 from sign, exponent, mantissa +pub fn fp8_from_components(sign: i8, exp: u8, mant: u8) FP8E5M2 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(FP8E5M2, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(FP8E5M2, @intCast(exp & 0x1F)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// fp8_is_zero(fp8: FP8E5M2) -> bool +// Check if FP8 is zero (positive or negative) +pub fn fp8_is_zero(fp8: FP8E5M2) bool { + return fp8 == FP8_ZERO_POS or fp8 == FP8_ZERO_NEG; +} + +// fp8_is_special(fp8: FP8E5M2) -> bool +// Check if FP8 is Inf or NaN (exp == 31) +pub fn fp8_is_special(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX; +} + +// fp8_is_inf(fp8: FP8E5M2) -> bool +// Check if FP8 is infinity (exp == 31, mant == 0) +pub fn fp8_is_inf(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) == 0; +} + +// fp8_is_nan(fp8: FP8E5M2) -> bool +// Check if FP8 is NaN (exp == 31, mant != 0) +pub fn fp8_is_nan(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) != 0; +} + +// fp8_is_subnormal(fp8: FP8E5M2) -> bool +// Check if FP8 is subnormal (exp == 0, mant != 0) +pub fn fp8_is_subnormal(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == 0 and fp8_extract_mantissa(fp8) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// fp8_encode_f32(value: f32) -> FP8E5M2 +// Encode IEEE 754 single precision to FP8 E5M2 +// Round-to-nearest, ties to even +pub fn fp8_encode_f32(value: f32) FP8E5M2 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return if (value < 0.0) FP8_NAN_NEG else FP8_NAN_POS; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i16 = @as(i16, @intCast((f32_bits >> 23) & 0xFF)) - 127; + var f32_mant: u32 = f32_bits & 0x007FFFFF; + + // Handle very small values (subnormals) + if (f32_exp <= @as(i16, -EXP_BIAS)) { + // Below FP8 minimum normal - try to encode as subnormal + const subnormal_shift = @as(i16, -EXP_BIAS) - f32_exp + 1; + if (subnormal_shift > 2 + 23) { + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + var mant = f32_mant >> 23; + if (subnormal_shift > 0) { + const shift_amount = @as(u5, @intCast(subnormal_shift)); + if (shift_amount <= 24) { + mant = (f32_mant | 0x00800000) >> shift_amount; + } + } + return fp8_from_components(sign, 0, @as(u8, @truncate(mant)) & 0x03); + } + + // Convert exp from f32 bias (127) to FP8 bias (15) + var fp8_exp = @as(i16, f32_exp + EXP_BIAS); + + // Clamp exponent + if (fp8_exp >= EXP_MAX) { + return if (sign < 0) FP8_INF_NEG else FP8_INF_POS; + } else if (fp8_exp < 1) { + // Underflow to zero (could be subnormal, simplified) + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Extract mantissa and scale to 2 bits + // f32 mantissa is 24 bits (including implicit 1), FP8 needs 2 bits + // Shift right by 22 bits (24 - 2 = 22) + var mant = f32_mant >> 22; + + // Round-to-nearest with ties to even + const discarded = f32_mant & 0x003FFFFF; // Lower 22 bits + if ((discarded & 0x00200000) != 0) { + // Halfway or more + if ((discarded & 0x001FFFFF) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (fp8_exp < @as(i16, EXP_MAX - 1)) { + fp8_exp += 1; + } + } + } + } + + return fp8_from_components(sign, @as(u8, @intCast(fp8_exp)), @as(u8, mant)); +} + +// fp8_decode_f32(fp8: FP8E5M2) -> f32 +// Decode FP8 E5M2 to IEEE 754 single precision +pub fn fp8_decode_f32(fp8: FP8E5M2) f32 { + // Handle zero + if (fp8_is_zero(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (fp8_is_nan(fp8)) { + return std.math.nan(f32); + } + + // Handle Infinity + if (fp8_is_inf(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -std.math.inf(f32) else std.math.inf(f32); + } + + // Extract components + const sign = fp8_extract_sign(fp8); + const exp = fp8_extract_exponent(fp8); + const mant = fp8_extract_mantissa(fp8); + + var f32_value: f32 = undefined; + + // Handle subnormal + if (exp == 0) { + // Subnormal: value = mant * 2^(-14) = mant * 2^(1-bias) + const bias_adjusted: i8 = 1 - @as(i8, EXP_BIAS); + const mant_f32 = @as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR)); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } else { + // Normal: value = (1 + mant/4) * 2^(exp - 15) + const bias_adjusted = @as(i16, exp) - @as(i16, EXP_BIAS); + const mant_f32 = 1.0 + (@as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR))); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } + + return if (sign < 0) -f32_value else f32_value; +} + +// fp8_encode_f64(value: f64) -> FP8E5M2 +// Encode IEEE 754 double precision to FP8 E5M2 +pub fn fp8_encode_f64(value: f64) FP8E5M2 { + return fp8_encode_f32(@as(f32, @floatCast(value))); +} + +// fp8_decode_f64(fp8: FP8E5M2) -> f64 +// Decode FP8 E5M2 to IEEE 754 double precision +pub fn fp8_decode_f64(fp8: FP8E5M2) f64 { + const f32_val = fp8_decode_f32(fp8); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// fp8_add(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Add two FP8 values +pub fn fp8_add(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa + fb); +} + +// fp8_sub(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Subtract two FP8 values +pub fn fp8_sub(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa - fb); +} + +// fp8_mul(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Multiply two FP8 values +pub fn fp8_mul(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa * fb); +} + +// fp8_div(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Divide two FP8 values +pub fn fp8_div(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fb = fp8_decode_f32(b); + if (fb == 0.0) { + const fa = fp8_decode_f32(a); + return if (fa < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + const fa = fp8_decode_f32(a); + return fp8_encode_f32(fa / fb); +} + +// fp8_sqrt(value: FP8E5M2) -> FP8E5M2 +// Square root of FP8 value +pub fn fp8_sqrt(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + if (fv < 0.0) { + return FP8_NAN_POS; + } + return fp8_encode_f32(std.math.sqrt(fv)); +} + +// fp8_rsqrt(value: FP8E5M2) -> FP8E5M2 +// Reciprocal square root (1/sqrt(x)) +pub fn fp8_rsqrt(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + if (fv <= 0.0) { + return FP8_INF_POS; + } + return fp8_encode_f32(1.0 / std.math.sqrt(fv)); +} + +// fp8_abs(value: FP8E5M2) -> FP8E5M2 +// Absolute value of FP8 +pub fn fp8_abs(value: FP8E5M2) FP8E5M2 { + return value & ~SIGN_MASK; +} + +// fp8_neg(value: FP8E5M2) -> FP8E5M2 +// Negate FP8 +pub fn fp8_neg(value: FP8E5M2) FP8E5M2 { + return value ^ SIGN_MASK; +} + +// fp8_is_equal(a: FP8E5M2, b: FP8E5M2) -> bool +// Check if two FP8 values are equal +pub fn fp8_is_equal(a: FP8E5M2, b: FP8E5M2) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + if (fp8_is_zero(a) and fp8_is_zero(b)) { + return true; + } + return a == b; +} + +// fp8_is_greater(a: FP8E5M2, b: FP8E5M2) -> bool +// Check if a > b +pub fn fp8_is_greater(a: FP8E5M2, b: FP8E5M2) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + const sign_a = fp8_extract_sign(a); + const sign_b = fp8_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return fp8_neg(a) > fp8_neg(b); + } + return a > b; +} + +// fp8_max(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Return the larger of two FP8 values +pub fn fp8_max(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + return if (fp8_is_greater(a, b)) a else b; +} + +// fp8_min(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Return the smaller of two FP8 values +pub fn fp8_min(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + return if (fp8_is_greater(a, b)) b else a; +} + +// fp8_clamp(value: FP8E5M2, min: FP8E5M2, max: FP8E5M2) -> FP8E5M2 +// Clamp value between min and max +pub fn fp8_clamp(value: FP8E5M2, min: FP8E5M2, max: FP8E5M2) FP8E5M2 { + return fp8_min(fp8_max(value, min), max); +} + +// fp8_lerp(a: FP8E5M2, b: FP8E5M2, t: f32) -> FP8E5M2 +// Linear interpolation between a and b +pub fn fp8_lerp(a: FP8E5M2, b: FP8E5M2, t: f32) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const result = fa + (fb - fa) * t; + return fp8_encode_f32(result); +} + +// fp8_fma(a: FP8E5M2, b: FP8E5M2, c: FP8E5M2) -> FP8E5M2 +// Fused multiply-add: a * b + c +pub fn fp8_fma(a: FP8E5M2, b: FP8E5M2, c: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const fc = fp8_decode_f32(c); + return fp8_encode_f32(fa * fb + fc); +} + +// fp8_scale(value: FP8E5M2, scale: f32) -> FP8E5M2 +// Scale FP8 value by scalar +pub fn fp8_scale(value: FP8E5M2, scale: f32) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * scale); +} + +// fp8_relu(value: FP8E5M2) -> FP8E5M2 +// ReLU activation: max(0, x) +pub fn fp8_relu(value: FP8E5M2) FP8E5M2 { + return if (fp8_extract_sign(value) < 0) FP8_ZERO_POS else value; +} + +// fp8_sigmoid(value: FP8E5M2) -> FP8E5M2 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn fp8_sigmoid(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// fp8_tanh(value: FP8E5M2) -> FP8E5M2 +// Tanh activation +pub fn fp8_tanh(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(std.math.tanh(fv)); +} + +// fp8_gelu(value: FP8E5M2) -> FP8E5M2 +// GELU activation +pub fn fp8_gelu(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + const sqrt_2_over_pi = 0.7978845608; + const cube_coeff = 0.044715; + const tanh_arg = fv * (1.0 + cube_coeff * fv * fv); + return fp8_encode_f32(0.5 * fv * (1.0 + std.math.tanh(sqrt_2_over_pi * tanh_arg))); +} + +// fp8_swish(value: FP8E5M2) -> FP8E5M2 +// Swish activation: x * sigmoid(x) +pub fn fp8_swish(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * (1.0 / (1.0 + std.math.exp(-fv)))); +} + +// fp8_quantize_to_int4(value: FP8E5M2) -> i8 +// Quantize FP8 to Int4 (range [-8, 7]) +pub fn fp8_quantize_to_int4(value: FP8E5M2) i8 { + const fv = fp8_decode_f32(value); + // E5M2 has wider range, so scale differently + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// fp8_quantize_to_int8(value: FP8E5M2) -> i8 +// Quantize FP8 to Int8 (range [-128, 127]) +pub fn fp8_quantize_to_int8(value: FP8E5M2) i8 { + const fv = fp8_decode_f32(value); + // E5M2 has wider range, so scale differently + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 127.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 127) { + result = 127; + } else if (result < -128) { + result = -128; + } + return @as(i8, @intCast(result)); +} + +// fp8_from_f32_scaled(value: f32, scale: f32) -> FP8E5M2 +// Encode f32 to FP8 with custom scale factor +// Useful for quantization with specific dynamic range +pub fn fp8_from_f32_scaled(value: f32, scale: f32) FP8E5M2 { + return fp8_encode_f32(value * scale); +} + +// fp8_to_f32_scaled(value: FP8E5M2, scale: f32) -> f32 +// Decode FP8 to f32 with custom scale factor +pub fn fp8_to_f32_scaled(value: FP8E5M2, scale: f32) f32 { + return fp8_decode_f32(value) / scale; +} + +// fp8_dot_product(a: []const FP8E5M2, b: []const FP8E5M2) -> FP8E5M2 +// Dot product of two FP8 arrays (fixed size 8 for example) +// Returns accumulated FP8 value +pub fn fp8_dot_product(a: []const FP8E5M2, b: []const FP8E5M2) FP8E5M2 { + var sum: f32 = 0.0; + for (a, 0..) |val_a, i| { + if (i >= b.len) break; + const fa = fp8_decode_f32(val_a); + const fb = fp8_decode_f32(b[i]); + sum += fa * fb; + } + return fp8_encode_f32(sum); +} + +// fp8_vector_add(a: []const FP8E5M2, b: []const FP8E5M2, result: []FP8E5M2) -> void +// Vector addition: result[i] = a[i] + b[i] +pub fn fp8_vector_add(a: []const FP8E5M2, b: []const FP8E5M2, result: []FP8E5M2) void { + const len = @min(a.len, b.len); + for (0..len) |i| { + result[i] = fp8_add(a[i], b[i]); + } +} + +// fp8_vector_scale(vec: []const FP8E5M2, scale: FP8E5M2, result: []FP8E5M2) -> void +// Vector scale: result[i] = vec[i] * scale +pub fn fp8_vector_scale(vec: []const FP8E5M2, scale: FP8E5M2, result: []FP8E5M2) void { + const scale_f = fp8_decode_f32(scale); + for (vec, 0..) |val, i| { + result[i] = fp8_encode_f32(fp8_decode_f32(val) * scale_f); + } +} + +// fp8_vector_sum(vec: []const FP8E5M2) -> FP8E5M2 +// Sum all elements of a vector +pub fn fp8_vector_sum(vec: []const FP8E5M2) FP8E5M2 { + var sum: f32 = 0.0; + for (vec) |val| { + sum += fp8_decode_f32(val); + } + return fp8_encode_f32(sum); +} + +// fp8_vector_mean(vec: []const FP8E5M2) -> FP8E5M2 +// Mean of all elements of a vector +pub fn fp8_vector_mean(vec: []const FP8E5M2) FP8E5M2 { + if (vec.len == 0) { + return FP8_ZERO_POS; + } + const sum = fp8_vector_sum(vec); + return fp8_div(sum, fp8_from_f32(@as(f32, @floatFromInt(vec.len)))); +} + +// fp8_softmax(vec: []const FP8E5M2, result: []FP8E5M2) -> void +// Softmax activation: result[i] = exp(vec[i]) / sum(exp(vec)) +pub fn fp8_softmax(vec: []const FP8E5M2, result: []FP8E5M2) void { + // Find max for numerical stability + var max_val: f32 = -std.math.inf(f32); + for (vec) |val| { + const fv = fp8_decode_f32(val); + if (fv > max_val) { + max_val = fv; + } + } + + // Compute exp values and sum + var sum_exp: f32 = 0.0; + for (vec, 0..) |val, i| { + const fv = fp8_decode_f32(val); + const exp_val = std.math.exp(fv - max_val); + result[i] = fp8_encode_f32(exp_val); + sum_exp += exp_val; + } + + // Normalize + const inv_sum = 1.0 / sum_exp; + for (result, 0..) |val, i| { + const fv = fp8_decode_f32(val); + result[i] = fp8_encode_f32(fv * inv_sum); + } +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fp8_extract_sign_positive" { + given value = 0x3C + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "fp8_extract_sign_negative" { + given value = 0xBC + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "fp8_extract_exponent_max" { + given value = 0x7C + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == EXP_MAX); +} + +test "fp8_extract_exponent_zero" { + given value = 0x03 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == 0); +} + +test "fp8_extract_mantissa_max" { + given value = 0x03 + try std.testing.expect(mant = fp8_extract_mantissa(value)); + try std.testing.expect(mant == MANT_MASK); +} + +test "fp8_is_zero_positive" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_POS) == true); +} + +test "fp8_is_zero_negative" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_NEG) == true); +} + +test "fp8_is_zero_nonzero" { + try std.testing.expect(fp8_is_zero(0x01) == false); +} + +test "fp8_is_inf_positive" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true); +} + +test "fp8_is_inf_negative" { + try std.testing.expect(fp8_is_inf(FP8_INF_NEG) == true); +} + +test "fp8_is_nan_positive" { + try std.testing.expect(fp8_is_nan(FP8_NAN_POS) == true); +} + +test "fp8_is_nan_negative" { + try std.testing.expect(fp8_is_nan(FP8_NAN_NEG) == true); +} + +test "fp8_is_inf_not_nan" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true and fp8_is_nan(FP8_INF_POS) == false); +} + +test "fp8_is_special_inf" { + try std.testing.expect(fp8_is_special(FP8_INF_POS) == true); +} + +test "fp8_is_special_nan" { + try std.testing.expect(fp8_is_special(FP8_NAN_POS) == true); +} + +test "fp8_is_subnormal_true" { + given value = 0x01 + try std.testing.expect(fp8_is_subnormal(value) == true); +} + +test "fp8_is_subnormal_false_normal" { + given value = 0x04 + try std.testing.expect(fp8_is_subnormal(value) == false); +} + +test "fp8_is_subnormal_false_zero" { + try std.testing.expect(fp8_is_subnormal(FP8_ZERO_POS) == false); +} + +test "fp8_encode_f32_zero" { + given fp = fp8_encode_f32(0.0) + try std.testing.expect(fp == FP8_ZERO_POS); +} + +test "fp8_encode_f32_negative_zero" { + given fp = fp8_encode_f32(-0.0) + try std.testing.expect(fp == FP8_ZERO_NEG); +} + +test "fp8_encode_f32_one" { + given fp = fp8_encode_f32(1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 1.0) < 0.15); +} + +test "fp8_encode_f32_negative_one" { + given fp = fp8_encode_f32(-1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded + 1.0) < 0.15); +} + +test "fp8_encode_f32_ten" { + given fp = fp8_encode_f32(10.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 10.0) < 1.0); +} + +test "fp8_encode_f32_hundred" { + given fp = fp8_encode_f32(100.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 100.0) < 10.0); +} + +test "fp8_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "fp8_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "fp8_encode_f32_nan" { + given fp = fp8_encode_f32(std.math.nan(f32)) + try std.testing.expect(fp8_is_nan(fp) == true); +} + +test "fp8_encode_f32_inf" { + given fp = fp8_encode_f32(std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_encode_f32_neg_inf" { + given fp = fp8_encode_f32(-std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_decode_f32_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_POS) == 0.0); +} + +test "fp8_decode_f32_negative_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_NEG) == -0.0); +} + +test "fp8_decode_f32_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_POS)) == true); +} + +test "fp8_decode_f32_neg_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_NEG)) == true); +} + +test "fp8_decode_f32_nan" { + try std.testing.expect(std.math.isNan(fp8_decode_f32(FP8_NAN_POS)) == true); +} + +test "fp8_add_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(20.0)); + try std.testing.expect(result = fp8_add(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 30.0) < 3.0); +} + +test "fp8_sub_simple" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(30.0)); + try std.testing.expect(result = fp8_sub(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 3.0); +} + +test "fp8_mul_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(result = fp8_mul(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 15.0); +} + +test "fp8_div_simple" { + given a = fp8_encode_f32(100.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "fp8_div_by_zero_positive" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_POS); +} + +test "fp8_div_by_zero_negative" { + given a = fp8_encode_f32(-50.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_NEG); +} + +test "fp8_sqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.3); +} + +test "fp8_sqrt_negative" { + given value = fp8_encode_f32(-4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(fp8_is_nan(result) == true); +} + +test "fp8_rsqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_rsqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_abs_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(result = fp8_abs(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_abs_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(abs_val = fp8_abs(value)); + try std.testing.expect(fp8_extract_sign(abs_val) == 0); +} + +test "fp8_neg_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) < 0); +} + +test "fp8_neg_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) == 0); +} + +test "fp8_is_equal_true" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(fp8_is_equal(value, value) == true); +} + +test "fp8_is_equal_false" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(20.0)); + try std.testing.expect(fp8_is_equal(a, b) == false); +} + +test "fp8_is_equal_nan" { + try std.testing.expect(fp8_is_equal(FP8_NAN_POS, FP8_NAN_POS) == false); +} + +test "fp8_is_equal_zero" { + try std.testing.expect(fp8_is_equal(FP8_ZERO_POS, FP8_ZERO_NEG) == true); +} + +test "fp8_is_greater_positive" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(30.0)); + try std.testing.expect(fp8_is_greater(a, b) == true); +} + +test "fp8_max_returns_larger" { + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 40.0); +} + +test "fp8_min_returns_smaller" { + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 40.0); +} + +test "fp8_lerp_zero" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, a) == true); +} + +test "fp8_lerp_one" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, b) == true); +} + +test "fp8_fma_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(c = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_fma(a, b, c)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 105.0) < 15.0); +} + +test "fp8_scale_up" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(scale = 10.0); + try std.testing.expect(result = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 1.0); +} + +test "fp8_scale_down" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(scale = 0.1); + try std.testing.expect(result = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "fp8_relu_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_relu_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_relu_zero" { + given result = fp8_relu(FP8_ZERO_POS) + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_sigmoid_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_sigmoid_positive_large" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "fp8_sigmoid_negative_large" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 0.1); +} + +test "fp8_tanh_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded) < 0.1); +} + +test "fp8_tanh_positive_large" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "fp8_tanh_negative_large" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= -0.9); +} + +test "fp8_gelu_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_gelu(value)); + try std.testing.expect(fp8_extract_sign(result) == 0); +} + +test "fp8_swish_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_swish_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded > 0.0); +} + +test "fp8_quantize_to_int4_max" { + given value = fp8_encode_f32(100.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "fp8_quantize_to_int4_min" { + given value = fp8_encode_f32(-100.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "fp8_quantize_to_int8_max" { + given value = fp8_encode_f32(200.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result >= 100); +} + +test "fp8_quantize_to_int8_min" { + given value = fp8_encode_f32(-200.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result <= -100); +} + +test "fp8_from_f32_scaled_up" { + given value = 1.0 + try std.testing.expect(scale = 100.0); + try std.testing.expect(result = fp8_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 10.0); +} + +test "fp8_from_f32_scaled_down" { + given value = 100.0 + try std.testing.expect(scale = 0.01); + try std.testing.expect(result = fp8_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "fp8_to_f32_scaled_up" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(scale = 0.01); + try std.testing.expect(result = fp8_to_f32_scaled(value, scale)); + try std.testing.expect(abs(result - 0.01) < 0.005); +} + +test "fp8_dot_product_simple" { + given a = [_]FP8E5M2{fp8_encode_f32(1.0), fp8_encode_f32(2.0), fp8_encode_f32(3.0), fp8_encode_f32(4.0)} + try std.testing.expect(b = [_]FP8E5M2{fp8_encode_f32(2.0), fp8_encode_f32(2.0), fp8_encode_f32(2.0), fp8_encode_f32(2.0)}); + try std.testing.expect(result = fp8_dot_product(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "fp8_vector_sum_simple" { + given vec = [_]FP8E5M2{fp8_encode_f32(1.0), fp8_encode_f32(2.0), fp8_encode_f32(3.0), fp8_encode_f32(4.0)} + try std.testing.expect(result = fp8_vector_sum(vec)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 2.0); +} + +test "fp8_vector_mean_simple" { + given vec = [_]FP8E5M2{fp8_encode_f32(2.0), fp8_encode_f32(4.0), fp8_encode_f32(6.0), fp8_encode_f32(8.0)} + try std.testing.expect(result = fp8_vector_mean(vec)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 5.0) < 1.0); +} + +test "fp8_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 15); + try std.testing.expect(mant = 2); + try std.testing.expect(fp = fp8_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = fp8_extract_sign(fp)); + try std.testing.expect(extracted_exp = fp8_extract_exponent(fp)); + try std.testing.expect(extracted_mant = fp8_extract_mantissa(fp)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fp8_bits_constant + assert BITS == 8 + +invariant fp8_sign_bits_one + assert SIGN_BITS == 1 + +invariant fp8_exp_bits_five + assert EXP_BITS == 5 + +invariant fp8_mant_bits_two + assert MANT_BITS == 2 + +invariant fp8_exp_bias_fifteen + assert EXP_BIAS == 15 + +invariant fp8_exp_max_all_ones + assert EXP_MAX == 0x1F + +invariant fp8_sign_mask_correct + assert SIGN_MASK == 0x80 + +invariant fp8_exp_mask_correct + assert EXP_MASK == 0x7C + +invariant fp8_mant_mask_correct + assert MANT_MASK == 0x03 + +invariant fp8_masks_sum_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFF + +invariant fp8_zero_pos_no_sign + assert (FP8_ZERO_POS & SIGN_MASK) == 0 + +invariant fp8_zero_neg_has_sign + assert (FP8_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant fp8_inf_pos_max_exp + assert (FP8_INF_POS & EXP_MASK) == EXP_MASK + +invariant fp8_inf_pos_no_mant + assert (FP8_INF_POS & MANT_MASK) == 0 + +invariant fp8_nan_has_mantissa + assert (FP8_NAN_POS & MANT_MASK) != 0 + +invariant fp8_abs_removes_sign + assert fp8_abs(FP8_ZERO_NEG) == FP8_ZERO_POS + +invariant fp8_neg_toggles_sign + assert (fp8_neg(FP8_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant fp8_add_zero_identity + given value = fp8_encode_f32(50.0) + assert fp8_is_equal(fp8_add(value, FP8_ZERO_POS), value) == true + +invariant fp8_sub_zero_identity + given value = fp8_encode_f32(50.0) + assert fp8_is_equal(fp8_sub(value, FP8_ZERO_POS), value) == true + +invariant fp8_mul_one_identity + given value = fp8_encode_f32(50.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_mul(value, one), value) == true or fp8_magnitude(value) > 10.0 + +invariant fp8_div_one_identity + given value = fp8_encode_f32(50.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_div(value, one), value) == true + +invariant fp8_max_ge_both + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(fp8_is_greater(result, a) == true and fp8_is_greater(result, b) == true or fp8_is_equal(result, b) == true); + +invariant fp8_min_le_both + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(fp8_is_greater(a, result) == true or fp8_is_equal(a, result) == true); + +invariant fp8_relu_non_negative + given result = fp8_relu(fp8_encode_f32(50.0)) + try std.testing.expect(fp8_extract_sign(result) == 0); + +invariant fp8_lerp_zero_start + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + assert fp8_is_equal(fp8_lerp(a, b, 0.0), a) == true + +invariant fp8_lerp_one_end + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + assert fp8_is_equal(fp8_lerp(a, b, 1.0), b) == true + +invariant fp8_sqrt_square_close + given value = fp8_encode_f32(4.0) + try std.testing.expect(sqrt_val = fp8_sqrt(value)); + try std.testing.expect(sq_val = fp8_mul(sqrt_val, sqrt_val)); + try std.testing.expect(decoded = fp8_decode_f32(sq_val)); + try std.testing.expect(abs(decoded - 4.0) < 0.5); + +invariant fp8_quantize_to_int4_in_range + given result = fp8_quantize_to_int4(fp8_encode_f32(50.0)) + assert result >= -8 and result <= 7 + +invariant fp8_quantize_to_int8_in_range + given result = fp8_quantize_to_int8(fp8_encode_f32(50.0)) + assert result >= -128 and result <= 127 + +invariant fp8_scale_roundtrip + given value = fp8_encode_f32(50.0) + try std.testing.expect(scale = 2.0); + try std.testing.expect(scaled = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_to_f32_scaled(scaled, scale)); + try std.testing.expect(abs(decoded - fp8_decode_f32(value)) < 5.0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fp8_encode_f32_latency + measure: nanoseconds to fp8_encode_f32(10.5) + target: < 50ns + +bench fp8_decode_f32_latency + measure: nanoseconds to fp8_decode_f32(0x3C) + target: < 50ns + +bench fp8_add_latency + measure: nanoseconds to fp8_add(0x3C, 0x48) + target: < 100ns + +bench fp8_mul_latency + measure: nanoseconds to fp8_mul(0x3C, 0x3C) + target: < 100ns + +bench fp8_sqrt_latency + measure: nanoseconds to fp8_sqrt(0x30) + target: < 100ns + +bench fp8_sigmoid_latency + measure: nanoseconds to fp8_sigmoid(0x30) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/gf128.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/gf128.t27 new file mode 100644 index 0000000000..d343873ad2 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/gf128.t27 @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf128.t27 +// GoldenFloat128 - 128-bit φ-structured floating point with extended range +// NUMERIC-STANDARD-001 Agent 5 (P1) +// Paper §6.7: Extended range for high-dynamic-range applications + +module GF128 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // GF128 bit layout: [S(1) | EEE...EEE(48) | MMM...MMM(79)] + // S: 1 bit (sign) + // E: 48 bits (exponent) - phi-optimized + // M: 79 bits (mantissa) - ultra-high precision + // + // φ-ratio: exp/mant = 48/79 ≈ 0.608 (phi_distance = 0.010) + // Good phi approximation for ultra-high precision + + const BITS : u8 = 128; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 48; + const MANT_BITS : u8 = 79; + + // Bias for exponent (2^47 - 1 = 140737488355327) + const EXP_BIAS : i64 = 140737488355327; + + // φ-ratio: exp/mant = 48/79 = 0.608 (good phi approximation) + const PHI_DISTANCE : f64 = 0.009696042799364053; + + // 2. GoldenFloat128 Type + + struct GF128 { + low : u64, // Lower 64 bits (mantissa bits 0-63) + high : u64, // Upper 64 bits [sign(1) exp(48) mant(15)] + } + + // 3. Encoding/Decoding + + // Encode f64 to GF128 + fn encode(value: f64) -> GF128 { + if (value == 0.0) { + return GF128{ .low = 0, .high = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2_128(abs_val) as i64; + const exp_biased = exp_unbiased + EXP_BIAS; + + // Clamp exponent (28 bits max = 268435455) + const exp_clamped = if (exp_biased > 268435455) { 268435455 } else if (exp_biased < 0) { 0 } else { exp_biased }; + + // Extract mantissa (99 bits) + const mant_low = extract_mantissa_128_low(abs_val, exp_unbiased); + const mant_high = extract_mantissa_128_high(abs_val, exp_unbiased); + + return GF128{ + .high = ((sign << 63) | (exp_clamped << 35) | mant_high), + .low = mant_low + }; + } + + // Decode GF128 to f64 + fn decode(gf: GF128) -> f64 { + const sign = (gf.high >> 63) as u8; + const exp_biased = (gf.high >> 35) & 0xFFFFFFF; + + // Zero + if (exp_biased == 0 && gf.high == 0 && gf.low == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS + 1 + } else { + (exp_biased as i64) - EXP_BIAS + }; + + // Mantissa (99 bits, with implicit 1 for normalized) + const mant_low = gf.low; + const mant_high = (gf.high & 0x7FFFFFFFF); + + const mant_normalized = if (exp_biased == 0) { + mant_to_f64_128(mant_low, mant_high) / 633825300114114700748351602688.0 + } else { + 1.0 + mant_to_f64_128(mant_low, mant_high) / 633825300114114700748351602688.0 + }; + + const value = mant_normalized * pow2_128(exp_unbiased as f64); + + if (sign != 0) { + return -value; + } + return value; + } + + // 4. Format Properties + + fn max_value() -> f64 { + // Max normalized: mant ≈ 2.0, exp = 134217727 + const mant_max = 1.9999999999999998; + const exp_max = (1i64 << 28) - 1 - EXP_BIAS; + return mant_max * pow2_128(exp_max as f64); + } + + fn min_positive() -> f64 { + // Min subnormal: mant ≈ 0, exp = -134217726 + const mant_min = 1.0 / 633825300114114700748351602688.0; + const exp_min = -EXP_BIAS + 1; + return mant_min * pow2_128(exp_min as f64); + } + + fn epsilon() -> f64 { + // Smallest representable difference at 1.0 + return 1.0 / 633825300114114700748351602688.0; + } + + fn dynamic_range() -> f64 { + // Ratio of max to min positive + return max_value() / min_positive(); + } + + // 5. Validation + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF128"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + fn is_extended_range() -> bool { + return EXTENDED_RANGE_MODE; + } + + // 6. Use Cases + + // GF128 is optimal for: + // - High dynamic range imaging (HDR) + // - Astronomical calculations (planetary to quantum scales) + // - Extended precision physics simulations + // - Financial modeling requiring extreme range + // - Scientific computing with exceptional precision + + // Memory: 128 bits = 16 bytes (4x FP32, same as FP128) + const MEMORY_RATIO_VS_FP32 : f32 = 128.0 / 32.0; // 4.0 + + // 7. Helper Functions + + fn floor_log2_128(x: f64) -> i64 { + if (x <= 0.0) { return -9223372036854775808; } + let exp : i64 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa_128_low(value: f64, exp: i64) -> u64 { + // Lower 64 bits of mantissa + const normalized = value / pow2_128(exp as f64); + const frac = normalized - 1.0; + const mant_full = (frac * 633825300114114700748351602689.0) as u128; + return (mant_full & 0xFFFFFFFFFFFFFFFF) as u64; + } + + fn extract_mantissa_128_high(value: f64, exp: i64) -> u64 { + // Upper 35 bits of mantissa + const normalized = value / pow2_128(exp as f64); + const frac = normalized - 1.0; + const mant_full = (frac * 633825300114114700748351602689.0) as u128; + return ((mant_full >> 64) & 0x7FFFFFFFF) as u64; + } + + fn mant_to_f64_128(low: u64, high: u64) -> f64 { + const mant_u128 = ((high as u128) << 64) | (low as u128); + return mant_u128 as f64; + } + + fn pow2_128(exp: f64) -> f64 { + if (exp == 0.0) { return 1.0; } + if (exp < 0.0) { return 1.0 / pow2_128(-exp); } + + let result = 1.0; + let base = 2.0; + let e = exp as i64; + + while (e > 0) { + if (e % 2 == 1) { + result = result * base; + } + base = base * base; + e = e / 2; + } + return result; + } + + // TDD-Inside-Spec: Tests and Invariants for GF128 + + test gf128_decode_zero + given gf = GF128{ .low = 0, .high = 0 } + when value = decode(gf) + then value == 0.0 + + test gf128_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf128_encode_positive_value + given original = 1.5 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 1.5) < 1e-10 + + test gf128_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf128_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf128_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf128_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf128_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 4.0) < 0.01 + + test gf128_validate_format_success + given valid = validate_format() + then valid == true + + test gf128_dynamic_range_enormous + given range = dynamic_range() + then range > 1e300 + + test gf128_is_extended_range_true + given extended = is_extended_range() + then extended == true + + test gf128_encode_large_value + given original = 1e150 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded / original - 1.0) < 0.1 + + test gf128_encode_small_value + given original = 1e-150 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded / original - 1.0) < 0.1 + + test gf128_negative_encoding + given original = -2.5 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded < 0.0 and abs(decoded - (-2.5)) < 1e-10 + + test gf128_sign_bit_negative + given value = -1.0 + and encoded = encode(value) + and sign_bit = (encoded.high >> 63) as u8 + then sign_bit == 1 + + test gf128_sign_bit_positive + given value = 1.0 + and encoded = encode(value) + and sign_bit = (encoded.high >> 63) as u8 + then sign_bit == 0 + + test gf128_phi_ratio_low + given ratio = (EXP_BITS as f64) / (MANT_BITS as f64) + then ratio < 0.3 // Extended range mode + + invariant gf128_bits_constant + assert BITS == 128 + + invariant gf128_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf128_exp_bits_is_forty_eight + assert EXP_BITS == 48 + + invariant gf128_mant_bits_is_seventy_nine + assert MANT_BITS == 79 + + invariant gf128_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf128_phi_distance_within_tolerance + assert PHI_DISTANCE < 0.34 + + invariant gf128_exp_bias_positive + assert EXP_BIAS > 0 + + invariant gf128_exp_bias_is_140737488355327 + assert EXP_BIAS == 140737488355327 + + invariant gf128_extended_range_mode_true + assert EXTENDED_RANGE_MODE == true + + invariant gf128_dynamic_range_enormous + assert dynamic_range() > 1e300 + + invariant gf128_epsilon_less_than_min_positive + assert epsilon() <= min_positive() + + invariant gf128_exp_mant_ratio_is_phi_approx + given ratio = (EXP_BITS as f64) / (MANT_BITS as f64) + then abs(ratio - 0.608) < 0.01 + + invariant gf128_high_low_consistency + given gf = GF128{ .low = 0xFFFFFFFFFFFFFFFF, .high = 0x7FFFFFFFF } + then decode(gf) > 0.0 + + bench gf128_encode_latency + measure: nanoseconds to encode(1.0) + target: < 400ns + + bench gf128_decode_latency + measure: nanoseconds to decode(encode(1.5)) + target: < 200ns + + bench gf128_pow2_latency + measure: nanoseconds to pow2_128(10.0) + target: < 150ns + + bench gf128_encode_decode_roundtrip + measure: nanoseconds to encode(1.0) and decode(encode(1.0)) + target: < 600ns + + bench gf128_dynamic_range_calc + measure: nanoseconds to dynamic_range() + target: < 300ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/gf16_to_fp16.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/gf16_to_fp16.t27 new file mode 100644 index 0000000000..132324be5a --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/gf16_to_fp16.t27 @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: Apache-2.0 + +module gf16-to-fp16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const GF16_SIGN_BITS : u8 = 1; +pub const GF16_EXP_BITS : u8 = 6; +pub const GF16_MANT_BITS : u8 = 9; +pub const GF16_BIAS : i8 = 31; + +pub const FP16_SIGN_BITS : u8 = 1; +pub const FP16_EXP_BITS : u8 = 5; +pub const FP16_MANT_BITS : u8 = 10; +pub const FP16_BIAS : i8 = 15; + +pub const MAX_EXP_DIFF : i8 = 6; // Maximum exponent difference without loss +pub const MANTissa_MASK_GF16 : u16 = 0x01FF; +pub const MANTissa_MASK_FP16 : u16 = 0x03FF; + +// ============================================================================ +// Types +// ============================================================================ + +pub const Gf16 = packed struct { + sign : u1, + exp : u6, + mant : u9, +} + +pub const Fp16 = packed struct { + sign : u1, + exp : u5, + mant : u10, +} + +pub const ConversionResult = struct { + value : u16, + overflow : bool, + underflow : bool, + lost_precision : bool, +} + +pub const ConversionConfig = struct { + round_mode : u8, + overflow_mode : u8, + enable_saturation : bool, +} + +// ============================================================================ +// GF16 Functions +// ============================================================================ + +// gf16_from_bits(bits: u16) -> Gf16 +// Extract GF16 from 16-bit representation +pub fn gf16_from_bits(bits: u16) -> Gf16 { + return Gf16 { + .sign = @as(u1, @truncate((bits >> 15) & 0x01)), + .exp = @as(u6, @truncate((bits >> 9) & 0x3F)), + .mant = @as(u9, @truncate(bits & 0x01FF)), + }; +} + +// gf16_to_bits(gf: Gf16) -> u16 +// Convert GF16 to 16-bit representation +pub fn gf16_to_bits(gf: Gf16) -> u16 { + const sign_field : u16 = @as(u16, gf.sign) << 15; + const exp_field : u16 = @as(u16, gf.exp) << 9; + const mant_field : u16 = @as(u16, gf.mant); + return sign_field | exp_field | mant_field; +} + +// gf16_get_exp_biased(gf: Gf16) -> i8 +// Get exponent with bias applied +pub fn gf16_get_exp_biased(gf: Gf16) -> i8 { + return @as(i8, @bitCast(gf.exp)) - GF16_BIAS; +} + +// gf16_is_zero(gf: Gf16) -> bool +// Check if GF16 is zero +pub fn gf16_is_zero(gf: Gf16) -> bool { + return gf.exp == 0 and gf.mant == 0; +} + +// gf16_is_infinity(gf: Gf16) -> bool +// Check if GF16 is infinity +pub fn gf16_is_infinity(gf: Gf16) -> bool { + return gf.exp == 0x3F and gf.mant == 0; +} + +// gf16_is_nan(gf: Gf16) -> bool +// Check if GF16 is NaN +pub fn gf16_is_nan(gf: Gf16) -> bool { + return gf.exp == 0x3F and gf.mant != 0; +} + +// ============================================================================ +// FP16 Functions +// ============================================================================ + +// fp16_from_bits(bits: u16) -> Fp16 +// Extract FP16 from 16-bit representation +pub fn fp16_from_bits(bits: u16) -> Fp16 { + return Fp16 { + .sign = @as(u1, @truncate((bits >> 15) & 0x01)), + .exp = @as(u5, @truncate((bits >> 10) & 0x1F)), + .mant = @as(u10, @truncate(bits & 0x03FF)), + }; +} + +// fp16_to_bits(fp: Fp16) -> u16 +// Convert FP16 to 16-bit representation +pub fn fp16_to_bits(fp: Fp16) -> u16 { + const sign_field : u16 = @as(u16, fp.sign) << 15; + const exp_field : u16 = @as(u16, fp.exp) << 10; + const mant_field : u16 = @as(u16, fp.mant); + return sign_field | exp_field | mant_field; +} + +// fp16_get_exp_biased(fp: Fp16) -> i8 +// Get exponent with bias applied +pub fn fp16_get_exp_biased(fp: Fp16) -> i8 { + if (fp.exp == 0) { + return -14; // Denormal + } else if (fp.exp == 0x1F) { + return 16; // Infinity/NaN + } + return @as(i8, @bitCast(fp.exp)) - FP16_BIAS; +} + +// fp16_is_zero(fp: Fp16) -> bool +// Check if FP16 is zero +pub fn fp16_is_zero(fp: Fp16) -> bool { + return fp.exp == 0 and fp.mant == 0; +} + +// fp16_is_infinity(fp: Fp16) -> bool +// Check if FP16 is infinity +pub fn fp16_is_infinity(fp: Fp16) -> bool { + return fp.exp == 0x1F and fp.mant == 0; +} + +// fp16_is_nan(fp: Fp16) -> bool +// Check if FP16 is NaN +pub fn fp16_is_nan(fp: Fp16) -> bool { + return fp.exp == 0x1F and fp.mant != 0; +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// conversion_config_init() -> ConversionConfig +// Initialize conversion config +pub fn conversion_config_init() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, // Round to nearest + .overflow_mode = 0, // Clamp + .enable_saturation = true, + }; +} + +// conversion_config_no_saturation() -> ConversionConfig +// Create config without saturation +pub fn conversion_config_no_saturation() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, + .overflow_mode = 1, // Overflow to infinity + .enable_saturation = false, + }; +} + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// gf16_to_fp16_raw(gf: Gf16) -> Fp16 +// Convert GF16 to FP16 (raw, no overflow/underflow handling) +pub fn gf16_to_fp16_raw(gf: Gf16) -> Fp16 { + var exp_i8 = gf16_get_exp_biased(gf); + + if (gf16_is_zero(gf)) { + return Fp16{.sign = gf.sign, .exp = 0, .mant = 0}; + } else if (gf16_is_infinity(gf)) { + return Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 0}; + } else if (gf16_is_nan(gf)) { + return Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 1}; // Quiet NaN + } + + // Adjust exponent bias: GF16 bias 31 -> FP16 bias 15 + exp_i8 = exp_i8 + GF16_BIAS - FP16_BIAS; + + var exp : u5 = 0; + var mant : u10 = 0; + var lost_precision = false; + + if (exp_i8 <= -15) { + // Underflow to zero + exp = 0; + mant = 0; + lost_precision = true; + } else if (exp_i8 >= 16) { + // Overflow to infinity + exp = 0x1F; + mant = 0; + } else { + exp = @as(u5, @truncate(@as(u8, @bitCast(exp_i8)) & 0x1F)); + // Extend mantissa from 9 to 10 bits + mant = @as(u10, gf.mant) << 1; + } + + return Fp16{.sign = gf.sign, .exp = exp, .mant = mant}; +} + +// gf16_to_fp16_with_result(gf: Gf16, config: ConversionConfig) -> ConversionResult +// Convert GF16 to FP16 with full result +pub fn gf16_to_fp16_with_result(gf: Gf16, config: ConversionConfig) -> ConversionResult { + var result : ConversionResult = undefined; + + if (gf16_is_zero(gf)) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0, .mant = 0}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } else if (gf16_is_infinity(gf)) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 0}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } else if (gf16_is_nan(gf)) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 1}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } + + var exp_i8 = gf16_get_exp_biased(gf); + exp_i8 = exp_i8 + GF16_BIAS - FP16_BIAS; + + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + + var exp : u5 = 0; + var mant : u10 = 0; + + if (exp_i8 <= -15) { + // Underflow + if (config.enable_saturation) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0, .mant = 0}); + } else { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0, .mant = 1}); // Smallest denormal + } + result.underflow = true; + result.lost_precision = true; + return result; + } else if (exp_i8 >= 16) { + // Overflow + if (config.overflow_mode == 0 or config.enable_saturation) { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0x1E, .mant = 0x3FF}); // Max finite + } else { + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = 0x1F, .mant = 0}); // Infinity + } + result.overflow = true; + return result; + } else { + exp = @as(u5, @truncate(@as(u8, @bitCast(exp_i8)) & 0x1F)); + mant = @as(u10, gf.mant) << 1; + result.value = fp16_to_bits(Fp16{.sign = gf.sign, .exp = exp, .mant = mant}); + return result; + } +} + +// gf16_to_fp16(gf: Gf16) -> Fp16 +// Convert GF16 to FP16 +pub fn gf16_to_fp16(gf: Gf16) -> Fp16 { + const result = gf16_to_fp16_with_result(gf, conversion_config_init()); + return fp16_from_bits(result.value); +} + +// gf16_bits_to_fp16_bits(gf_bits: u16) -> u16 +// Convert GF16 bits to FP16 bits +pub fn gf16_bits_to_fp16_bits(gf_bits: u16) -> u16 { + const gf = gf16_from_bits(gf_bits); + const fp = gf16_to_fp16(gf); + return fp16_to_bits(fp); +} + +// gf16_bits_to_fp16_bits_with_result(gf_bits: u16, config: ConversionConfig) -> ConversionResult +// Convert GF16 bits to FP16 bits with full result +pub fn gf16_bits_to_fp16_bits_with_result(gf_bits: u16, config: ConversionConfig) -> ConversionResult { + const gf = gf16_from_bits(gf_bits); + return gf16_to_fp16_with_result(gf, config); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf16_bits_constants" { + try std.testing.expect(GF16_SIGN_BITS == 1); + try std.testing.expect(GF16_EXP_BITS == 6); + try std.testing.expect(GF16_MANT_BITS == 9); + try std.testing.expect(GF16_BIAS == 31); +} + +test "fp16_bits_constants" { + try std.testing.expect(FP16_SIGN_BITS == 1); + try std.testing.expect(FP16_EXP_BITS == 5); + try std.testing.expect(FP16_MANT_BITS == 10); + try std.testing.expect(FP16_BIAS == 15); +} + +test "mantissa_masks" { + try std.testing.expect(MANTissa_MASK_GF16 == 0x01FF); + try std.testing.expect(MANTissa_MASK_FP16 == 0x03FF); +} + +test "gf16_from_bits_to_bits_roundtrip" { + given bits = 0x1234 + try std.testing.expect(gf = gf16_from_bits(bits)); + try std.testing.expect(result = gf16_to_bits(gf)); + try std.testing.expect(result == bits); +} + +test "gf16_from_bits_structure" { + given gf = gf16_from_bits(0x8000) + try std.testing.expect(gf.sign == 1); + try std.testing.expect(gf.exp == 0); + try std.testing.expect(gf.mant == 0); +} + +test "fp16_from_bits_to_bits_roundtrip" { + given bits = 0x3C00 + try std.testing.expect(fp = fp16_from_bits(bits)); + try std.testing.expect(result = fp16_to_bits(fp)); + try std.testing.expect(result == bits); +} + +test "gf16_is_zero_true" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(gf16_is_zero(gf) == true); +} + +test "gf16_is_zero_false" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(gf16_is_zero(gf) == false); +} + +test "gf16_is_infinity_true" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(gf16_is_infinity(gf) == true); +} + +test "gf16_is_infinity_false" { + given gf = Gf16{.sign = 0, .exp = 0x3E, .mant = 0} + try std.testing.expect(gf16_is_infinity(gf) == false); +} + +test "gf16_is_nan_true" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(gf16_is_nan(gf) == true); +} + +test "fp16_is_zero_true" { + given fp = Fp16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp16_is_zero(fp) == true); +} + +test "fp16_is_infinity_true" { + given fp = Fp16{.sign = 0, .exp = 0x1F, .mant = 0} + try std.testing.expect(fp16_is_infinity(fp) == true); +} + +test "conversion_config_init_structure" { + given config = conversion_config_init() + try std.testing.expect(config.round_mode == 0); + try std.testing.expect(config.enable_saturation == true); +} + +test "conversion_config_no_saturation_structure" { + given config = conversion_config_no_saturation() + try std.testing.expect(config.enable_saturation == false); +} + +test "gf16_to_fp16_zero" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.exp == 0); + try std.testing.expect(fp.mant == 0); +} + +test "gf16_to_fp16_infinity" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.exp == 0x1F); + try std.testing.expect(fp.mant == 0); +} + +test "gf16_to_fp16_nan" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.exp == 0x1F); + try std.testing.expect(fp.mant == 1); +} + +test "gf16_to_fp16_preserves_sign" { + given gf_pos = Gf16{.sign = 1, .exp = 1, .mant = 0} + try std.testing.expect(gf_neg = Gf16{.sign = 0, .exp = 1, .mant = 0}); + try std.testing.expect(fp_pos = gf16_to_fp16(gf_pos)); + try std.testing.expect(fp_neg = gf16_to_fp16(gf_neg)); + try std.testing.expect(fp_pos.sign == 1 and fp_neg.sign == 0); +} + +test "gf16_to_fp16_with_result_zero" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.overflow == false); + try std.testing.expect(result.underflow == false); + try std.testing.expect(result.lost_precision == false); +} + +test "gf16_bits_to_fp16_bits_zero" { + given config = conversion_config_init() + try std.testing.expect(gf16_bits_to_fp16_bits(0x0000, config) == 0x0000); +} + +test "gf16_bits_to_fp16_bits_roundtrip" { + given config = conversion_config_init() + // Normal value + try std.testing.expect(gf_bits = 0x2000 // exp=32, mant=0 -> value ~1.0 in GF16); + try std.testing.expect(fp_bits = gf16_bits_to_fp16_bits(gf_bits)); + try std.testing.expect((fp_bits & 0x8000) == (gf_bits & 0x8000) // Sign preserved); +} + +test "gf16_to_fp16_with_result_underflow" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 1} // Very small value + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.underflow == true or result.lost_precision == true); +} + +test "gf16_to_fp16_with_result_overflow" { + given gf = Gf16{.sign = 0, .exp = 62, .mant = 0} // Very large value + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.overflow == true); +} + +test "gf16_to_fp16_with_result_saturation" { + given gf = Gf16{.sign = 0, .exp = 62, .mant = 0} // Very large value + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.value != 0x7C00 // Not infinity, but max finite); +} + +test "gf16_to_fp16_with_result_no_saturation_overflow" { + given gf = Gf16{.sign = 0, .exp = 62, .mant = 0} // Very large value + try std.testing.expect(config = conversion_config_no_saturation()); + try std.testing.expect(result = gf16_to_fp16_with_result(gf, config)); + try std.testing.expect(result.value == 0x7C00 // Infinity); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf16_bit_count + assert GF16_SIGN_BITS + GF16_EXP_BITS + GF16_MANT_BITS == 16 + +invariant fp16_bit_count + assert FP16_SIGN_BITS + FP16_EXP_BITS + FP16_MANT_BITS == 16 + +invariant gf16_exp_width + assert GF16_EXP_BITS == 6 + +invariant fp16_exp_width + assert FP16_EXP_BITS == 5 + +invariant gf16_mant_width + assert GF16_MANT_BITS == 9 + +invariant fp16_mant_width + assert FP16_MANT_BITS == 10 + +invariant mantissa_masks_correct + assert MANTissa_MASK_GF16 == (@as(u16, 1) << GF16_MANT_BITS) - 1 + try std.testing.expect(MANTissa_MASK_FP16 == (@as(u16, 1) << FP16_MANT_BITS) - 1); + +invariant gf16_from_bits_roundtrip + given bits = 0x5678 + assert gf16_to_bits(gf16_from_bits(bits)) == bits + +invariant fp16_from_bits_roundtrip + given bits = 0x4567 + assert fp16_to_bits(fp16_from_bits(bits)) == bits + +invariant gf16_zero_exp_zero_mant_zero + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + assert gf16_is_zero(gf) and gf.exp == 0 and gf.mant == 0 + +invariant gf16_infinity_exp_max_mant_zero + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + assert gf16_is_infinity(gf) and not gf16_is_nan(gf) + +invariant fp16_infinity_exp_max_mant_zero + given fp = Fp16{.sign = 0, .exp = 0x1F, .mant = 0} + assert fp16_is_infinity(fp) and not fp16_is_nan(fp) + +invariant conversion_config_init_saturation_enabled + given config = conversion_config_init() + assert config.enable_saturation == true + +invariant conversion_config_no_saturation_disabled + given config = conversion_config_no_saturation() + assert config.enable_saturation == false + +invariant gf16_to_fp16_zero_preserves + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.sign == gf.sign); + +invariant gf16_to_fp16_infinity_preserves_sign + given gf_pos = Gf16{.sign = 1, .exp = 0x3F, .mant = 0} + try std.testing.expect(fp_pos = gf16_to_fp16(gf_pos)); + try std.testing.expect(fp_pos.sign == 1); + +invariant gf16_to_fp16_nan_preserves_sign + given gf = Gf16{.sign = 1, .exp = 0x3F, .mant = 1} + try std.testing.expect(fp = gf16_to_fp16(gf)); + try std.testing.expect(fp.sign == gf.sign); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf16_from_bits_latency + measure: nanoseconds to gf16_from_bits(0x1234) + target: < 20ns + +bench gf16_to_bits_latency + measure: nanoseconds to gf16_to_bits(gf16_from_bits(0x1234)) + target: < 20ns + +bench fp16_from_bits_latency + measure: nanoseconds to fp16_from_bits(0x3C00) + target: < 20ns + +bench fp16_to_bits_latency + measure: nanoseconds to fp16_to_bits(fp16_from_bits(0x3C00)) + target: < 20ns + +bench gf16_to_fp16_latency + measure: nanoseconds to gf16_to_fp16(gf16_from_bits(0x4000)) + target: < 30ns + +bench gf16_to_fp16_with_result_latency + measure: nanoseconds to gf16_to_fp16_with_result(gf16_from_bits(0x4000), conversion_config_init()) + target: < 40ns + +bench gf16_bits_to_fp16_bits_latency + measure: nanoseconds to gf16_bits_to_fp16_bits(0x4000) + target: < 30ns + +bench conversion_config_init_latency + measure: nanoseconds to conversion_config_init() + target: < 15ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/gf16_to_posit16.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/gf16_to_posit16.t27 new file mode 100644 index 0000000000..6f0189efe3 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/gf16_to_posit16.t27 @@ -0,0 +1,664 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf16_to_posit16.t27 — GF16 to Posit16 Converter +; GoldenFloat16 to Posit type 16 (unum 1.0) format conversion +; φ² + 1/φ² = 3 | TRINITY + +module gf16-to-posit16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const GF16_BITS : u8 = 16; +pub const GF16_SIGN_BIT : u8 = 15; +pub const GF16_EXP_BITS : u8 = 6; +pub const GF16_MANT_BITS : u8 = 9; +pub const GF16_BIAS : i8 = 31; + +pub const POSIT16_BITS : u8 = 16; +pub const POSIT16_ES : u8 = 1; // Exponent scale for posit16 +pub const POSIT16_MAX_EXP : i8 = 7; // Maximum useed exponent for ES=1 + +pub const POSIT16_REGIME_BITS : u8 = 2; +pub const POSIT16_MANT_BITS : u8 = 12; // 14 bits total - 2 regime - ES + +pub const MAX_EXP_DIFF : i8 = 5; + +// ============================================================================ +// Types +// ============================================================================ + +pub const Gf16 = packed struct { + sign : u1, + exp : u6, + mant : u9, +} + +pub const Posit16 = struct { + bits : u16, +} + +pub const PositComponents = struct { + sign : bool, + regime : i8, + exponent : u8, + mantissa : u16, +} + +pub const ConversionResult = struct { + bits : u16, + overflow : bool, + underflow : bool, + inexact : bool, +} + +pub const ConversionConfig = struct { + round_mode : u8, + clamp_overflow : bool, +} + +// ============================================================================ +// GF16 Functions +// ============================================================================ + +// gf16_from_bits(bits: u16) -> Gf16 +// Extract GF16 from 16-bit representation +pub fn gf16_from_bits(bits: u16) -> Gf16 { + return Gf16 { + .sign = @as(u1, @truncate((bits >> 15) & 0x01)), + .exp = @as(u6, @truncate((bits >> 9) & 0x3F)), + .mant = @as(u9, @truncate(bits & 0x01FF)), + }; +} + +// gf16_to_bits(gf: Gf16) -> u16 +// Convert GF16 to 16-bit representation +pub fn gf16_to_bits(gf: Gf16) -> u16 { + const sign_field : u16 = @as(u16, gf.sign) << 15; + const exp_field : u16 = @as(u16, gf.exp) << 9; + const mant_field : u16 = @as(u16, gf.mant); + return sign_field | exp_field | mant_field; +} + +// gf16_is_zero(gf: Gf16) -> bool +// Check if GF16 is zero +pub fn gf16_is_zero(gf: Gf16) -> bool { + return gf.exp == 0 and gf.mant == 0; +} + +// gf16_is_subnormal(gf: Gf16) -> bool +// Check if GF16 is subnormal +pub fn gf16_is_subnormal(gf: Gf16) -> bool { + return gf.exp == 0 and gf.mant != 0; +} + +// gf16_is_normal(gf: Gf16) -> bool +// Check if GF16 is normal +pub fn gf16_is_normal(gf: Gf16) -> bool { + return gf.exp != 0 and gf.exp != 0x3F; +} + +// gf16_is_infinity(gf: Gf16) -> bool +// Check if GF16 is infinity +pub fn gf16_is_infinity(gf: Gf16) -> bool { + return gf.exp == 0x3F and gf.mant == 0; +} + +// gf16_is_nan(gf: Gf16) -> bool +// Check if GF16 is NaN +pub fn gf16_is_nan(gf: Gf16) -> bool { + return gf.exp == 0x3F and gf.mant != 0; +} + +// gf16_get_exp_biased(gf: Gf16) -> i8 +// Get exponent with bias applied +pub fn gf16_get_exp_biased(gf: Gf16) -> i8 { + return @as(i8, @bitCast(gf.exp)) - GF16_BIAS; +} + +// ============================================================================ +// Posit16 Functions +// ============================================================================ + +// posit16_from_bits(bits: u16) -> Posit16 +// Create Posit16 from bits +pub fn posit16_from_bits(bits: u16) -> Posit16 { + return Posit16{.bits = bits}; +} + +// posit16_to_bits(p: Posit16) -> u16 +// Get bits from Posit16 +pub fn posit16_to_bits(p: Posit16) -> u16 { + return p.bits; +} + +// posit16_is_zero(p: Posit16) -> bool +// Check if posit16 is zero +pub fn posit16_is_zero(p: Posit16) -> bool { + return p.bits == 0x0000; +} + +// posit16_is_one(p: Posit16) -> bool +// Check if posit16 is 1.0 +pub fn posit16_is_one(p: Posit16) -> bool { + return p.bits == 0x4000; +} + +// posit16_is_neg_one(p: Posit16) -> bool +// Check if posit16 is -1.0 +pub fn posit16_is_neg_one(p: Posit16) -> bool { + return p.bits == 0xC000; +} + +// posit16_is_naR(p: Posit16) -> bool +// Check if posit16 is NaR (Not a Real) +pub fn posit16_is_naR(p: Posit16) -> bool { + return p.bits == 0x8000; +} + +// posit16_get_sign(p: Posit16) -> bool +// Get sign bit +pub fn posit16_get_sign(p: Posit16) -> bool { + return (p.bits >> 15) == 1; +} + +// posit16_decode(p: Posit16) -> PositComponents +// Decode posit16 into components +pub fn posit16_decode(p: Posit16) -> PositComponents { + var bits = p.bits; + var sign = false; + + if (bits & 0x8000 != 0) { + sign = true; + bits = ~bits + 1; // Two's complement + } + + if (bits == 0) { + return PositComponents{.sign = sign, .regime = 0, .exponent = 0, .mantissa = 0}; + } + + var regime = 0; + var regime_bits : u8 = 0; + + // Find regime + if (bits & 0x4000 != 0) { + // Positive regime: starts with 10 + while (bits & 0x4000 != 0) { + regime += 1; + if (regime > 16) break; + bits <<= 1; + regime_bits += 1; + } + } else { + // Negative regime: starts with 01 + while (bits & 0x4000 == 0) { + regime -= 1; + if (regime < -16) break; + bits <<= 1; + regime_bits += 1; + } + } + + bits <<= 1; // Skip last regime bit + + // Extract exponent + var exponent : u8 = 0; + var exp_bit : u8 = 0; + while (exp_bit < POSIT16_ES and bits != 0) { + if (bits & 0x8000 != 0) { + exponent |= (@as(u8, 1) << (POSIT16_ES - 1 - exp_bit)); + } + bits <<= 1; + exp_bit += 1; + } + + // Extract mantissa + var mantissa : u16 = 0; + var mant_bit : u8 = 0; + while (mant_bit < POSIT16_MANT_BITS and bits != 0) { + if (bits & 0x8000 != 0) { + mantissa |= (@as(u16, 1) << (POSIT16_MANT_BITS - 1 - mant_bit)); + } + bits <<= 1; + mant_bit += 1; + } + + return PositComponents{ + .sign = sign, + .regime = regime, + .exponent = exponent, + .mantissa = mantissa, + }; +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// conversion_config_init() -> ConversionConfig +// Initialize conversion config +pub fn conversion_config_init() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, // Round to nearest + .clamp_overflow = true, + }; +} + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// gf16_to_posit16_raw(gf: Gf16) -> Posit16 +// Convert GF16 to Posit16 (raw) +pub fn gf16_to_posit16_raw(gf: Gf16) -> Posit16 { + if (gf16_is_zero(gf)) { + return posit16_from_bits(@as(u16, @bitCast(gf.sign)) << 15); + } else if (gf16_is_infinity(gf)) { + // Positive infinity maps to NaR + return posit16_from_bits(@as(u16, @bitCast(gf.sign)) << 15); + } else if (gf16_is_nan(gf)) { + return posit16_from_bits(0x8000); // NaR + } + + var exp_i8 = gf16_get_exp_biased(gf); + var sign = gf.sign == 1; + + // Map GF16 exponent to posit regime/exponent + // GF16 bias 31, ES=1, so exponent range is -30 to 30 + // Posit16 regime range: -14 to 14 (2-bit regime) + var regime : i8 = 0; + var exponent : u8 = 0; + + if (exp_i8 > POSIT16_MAX_EXP) { + // Use larger regime + regime = exp_i8 - POSIT16_MAX_EXP; + exponent = 0; + } else if (exp_i8 < -POSIT16_MAX_EXP) { + // Use negative regime + regime = exp_i8 + POSIT16_MAX_EXP; + exponent = 0; + } else { + regime = 0; + exponent = @as(u8, @truncate(@as(u8, @bitCast(exp_i8 + POSIT16_MAX_EXP)))); + } + + // Clamp regime to valid range + if (regime > 14) regime = 14; + if (regime < -14) regime = -14; + + // Build posit bits + var result_bits : u16 = 0; + + // Sign + if (sign) result_bits = 0x8000; + + // Regime encoding + var temp_bits : u16 = if (regime >= 0) 0x4000 else 0x2000; + var regime_count = if (regime >= 0) regime else -regime; + + for (0..@as(usize, @intCast(regime_count))) |_| { + if (regime >= 0) { + result_bits |= temp_bits; + if (temp_bits != 0x8000) temp_bits >>= 1; + } else { + result_bits |= temp_bits; + if (temp_bits != 0) temp_bits >>= 1; + } + } + + // Exponent (ES bits) + result_bits |= @as(u16, exponent) << (14 - POSIT16_ES); + + // Mantissa (9 to 12 bits, pad with zeros) + var mant : u16 = @as(u16, gf.mant); + mant <<= 3; // 9 bits -> 12 bits + result_bits |= mant & 0x0FFF; + + return posit16_from_bits(result_bits); +} + +// gf16_to_posit16_with_result(gf: Gf16, config: ConversionConfig) -> ConversionResult +// Convert GF16 to Posit16 with full result +pub fn gf16_to_posit16_with_result(gf: Gf16, config: ConversionConfig) -> ConversionResult { + var result : ConversionResult = undefined; + + if (gf16_is_zero(gf)) { + result.bits = @as(u16, @bitCast(gf.sign)) << 15; + result.overflow = false; + result.underflow = false; + result.inexact = false; + return result; + } else if (gf16_is_infinity(gf) or gf16_is_nan(gf)) { + result.bits = 0x8000; + result.overflow = false; + result.underflow = false; + result.inexact = true; + return result; + } + + const posit = gf16_to_posit16_raw(gf); + result.bits = posit16_to_bits(posit); + + // Check for overflow/underflow based on exponent + var exp_i8 = gf16_get_exp_biased(gf); + result.overflow = exp_i8 > 30; + result.underflow = exp_i8 < -30; + + // GF16 mantissa is 9 bits, posit16 mantissa is 12 bits (no precision loss) + result.inexact = false; + + if (result.overflow and config.clamp_overflow) { + result.bits = if (gf.sign == 1) 0xC000 else 0x7FFF; // Clamp to max + } + + return result; +} + +// gf16_to_posit16(gf: Gf16) -> Posit16 +// Convert GF16 to Posit16 +pub fn gf16_to_posit16(gf: Gf16) -> Posit16 { + return gf16_to_posit16_raw(gf); +} + +// gf16_bits_to_posit16_bits(gf_bits: u16) -> u16 +// Convert GF16 bits to Posit16 bits +pub fn gf16_bits_to_posit16_bits(gf_bits: u16) -> u16 { + const gf = gf16_from_bits(gf_bits); + const posit = gf16_to_posit16(gf); + return posit16_to_bits(posit); +} + +// gf16_bits_to_posit16_bits_with_result(gf_bits: u16, config: ConversionConfig) -> ConversionResult +// Convert GF16 bits to Posit16 bits with full result +pub fn gf16_bits_to_posit16_bits_with_result(gf_bits: u16, config: ConversionConfig) -> ConversionResult { + const gf = gf16_from_bits(gf_bits); + return gf16_to_posit16_with_result(gf, config); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf16_bits_constants" { + try std.testing.expect(GF16_BITS == 16); + try std.testing.expect(GF16_EXP_BITS == 6); + try std.testing.expect(GF16_MANT_BITS == 9); + try std.testing.expect(GF16_BIAS == 31); +} + +test "posit16_constants" { + try std.testing.expect(POSIT16_BITS == 16); + try std.testing.expect(POSIT16_ES == 1); + try std.testing.expect(POSIT16_MAX_EXP == 7); +} + +test "posit16_regime_mant_bits" { + try std.testing.expect(POSIT16_REGIME_BITS == 2); + try std.testing.expect(POSIT16_MANT_BITS == 12); +} + +test "gf16_from_bits_roundtrip" { + given bits = 0x1234 + try std.testing.expect(gf = gf16_from_bits(bits)); + try std.testing.expect(result = gf16_to_bits(gf)); + try std.testing.expect(result == bits); +} + +test "gf16_is_zero_true" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(gf16_is_zero(gf) == true); +} + +test "gf16_is_subnormal_true" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(gf16_is_subnormal(gf) == true); +} + +test "gf16_is_normal_true" { + given gf = Gf16{.sign = 0, .exp = 32, .mant = 0} + try std.testing.expect(gf16_is_normal(gf) == true); +} + +test "gf16_is_infinity_true" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(gf16_is_infinity(gf) == true); +} + +test "gf16_is_nan_true" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(gf16_is_nan(gf) == true); +} + +test "posit16_from_bits_roundtrip" { + given bits = 0x4000 + try std.testing.expect(p = posit16_from_bits(bits)); + try std.testing.expect(result = posit16_to_bits(p)); + try std.testing.expect(result == bits); +} + +test "posit16_is_zero_true" { + given p = posit16_from_bits(0x0000) + try std.testing.expect(posit16_is_zero(p) == true); +} + +test "posit16_is_one_true" { + given p = posit16_from_bits(0x4000) + try std.testing.expect(posit16_is_one(p) == true); +} + +test "posit16_is_neg_one_true" { + given p = posit16_from_bits(0xC000) + try std.testing.expect(posit16_is_neg_one(p) == true); +} + +test "posit16_is_naR_true" { + given p = posit16_from_bits(0x8000) + try std.testing.expect(posit16_is_naR(p) == true); +} + +test "posit16_get_sign_positive" { + given p = posit16_from_bits(0x4000) + try std.testing.expect(posit16_get_sign(p) == false); +} + +test "posit16_get_sign_negative" { + given p = posit16_from_bits(0xC000) + try std.testing.expect(posit16_get_sign(p) == true); +} + +test "posit16_decode_one" { + given p = posit16_from_bits(0x4000) + try std.testing.expect(comp = posit16_decode(p)); + try std.testing.expect(comp.sign == false); + try std.testing.expect(comp.regime == 0); + try std.testing.expect(comp.exponent == 0); + try std.testing.expect(comp.mantissa == 0); +} + +test "conversion_config_init_structure" { + given config = conversion_config_init() + try std.testing.expect(config.round_mode == 0); + try std.testing.expect(config.clamp_overflow == true); +} + +test "gf16_to_posit16_zero" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_zero(p) == true); +} + +test "gf16_to_posit16_infinity" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_naR(p) == true); +} + +test "gf16_to_posit16_nan" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_naR(p) == true); +} + +test "gf16_to_posit16_preserves_sign" { + given gf_pos = Gf16{.sign = 1, .exp = 32, .mant = 0} + try std.testing.expect(gf_neg = Gf16{.sign = 0, .exp = 32, .mant = 0}); + try std.testing.expect(p_pos = gf16_to_posit16(gf_pos)); + try std.testing.expect(p_neg = gf16_to_posit16(gf_neg)); + try std.testing.expect(posit16_get_sign(p_pos) == true and posit16_get_sign(p_neg) == false); +} + +test "gf16_bits_to_posit16_bits_zero" { + try std.testing.expect(gf16_bits_to_posit16_bits(0x0000) == 0x0000); +} + +test "gf16_bits_to_posit16_bits_sign_preserved" { + given gf_pos = 0x8000 // Negative zero + try std.testing.expect(gf_neg = 0x0000 // Positive zero); + try std.testing.expect((gf16_bits_to_posit16_bits(gf_pos) & 0x8000) != 0); + try std.testing.expect((gf16_bits_to_posit16_bits(gf_neg) & 0x8000) == 0); +} + +test "gf16_to_posit16_with_result_zero" { + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_posit16_with_result(gf, config)); + try std.testing.expect(result.overflow == false); + try std.testing.expect(result.underflow == false); + try std.testing.expect(result.inexact == false); +} + +test "gf16_to_posit16_with_result_infinity" { + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_posit16_with_result(gf, config)); + try std.testing.expect(result.inexact == true); + try std.testing.expect(result.bits == 0x8000); +} + +test "gf16_to_posit16_with_result_overflow_clamped" { + given gf = Gf16{.sign = 0, .exp = 0x3E, .mant = 0x1FF} // Large value + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf16_to_posit16_with_result(gf, config)); + try std.testing.expect(result.overflow == true); + try std.testing.expect(result.bits != 0x8000 // Clamped, not NaR); +} + +test "gf16_to_posit16_with_result_overflow_not_clamped" { + given gf = Gf16{.sign = 0, .exp = 0x3E, .mant = 0x1FF} + try std.testing.expect(config = ConversionConfig{.round_mode = 0, .clamp_overflow = false}); + try std.testing.expect(result = gf16_to_posit16_with_result(gf, config)); + try std.testing.expect(result.overflow == true); + try std.testing.expect(result.bits == 0x8000 // NaR); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf16_bits_total + assert GF16_BITS == 16 + try std.testing.expect(GF16_SIGN_BIT == GF16_BITS - 1); + +invariant gf16_exp_mant_sum + assert GF16_EXP_BITS + GF16_MANT_BITS == GF16_BITS - 1 + +invariant posit16_bits_total + assert POSIT16_BITS == 16 + +invariant posit16_es_value + assert POSIT16_ES == 1 + +invariant posit16_regime_mant_sum + assert POSIT16_REGIME_BITS + POSIT16_MANT_BITS == POSIT16_BITS - POSIT16_ES - 1 + +invariant gf16_from_bits_roundtrip + given bits = 0x5678 + assert gf16_to_bits(gf16_from_bits(bits)) == bits + +invariant posit16_from_bits_roundtrip + given bits = 0x4000 + assert posit16_to_bits(posit16_from_bits(bits)) == bits + +invariant gf16_zero_exp_zero_mant_zero + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + assert gf16_is_zero(gf) and not gf16_is_subnormal(gf) + +invariant gf16_subnormal_exp_zero_mant_nonzero + given gf = Gf16{.sign = 0, .exp = 0, .mant = 1} + assert gf16_is_subnormal(gf) and not gf16_is_zero(gf) + +invariant gf16_normal_exp_nonzero + given gf = Gf16{.sign = 0, .exp = 1, .mant = 0} + assert gf16_is_normal(gf) and not gf16_is_zero(gf) + +invariant posit16_zero_bits_zero + given p = posit16_from_bits(0x0000) + assert posit16_is_zero(p) and posit16_get_sign(p) == false + +invariant posit16_one_bits_one + given p = posit16_from_bits(0x4000) + assert posit16_is_one(p) and not posit16_is_zero(p) + +invariant posit16_naR_bits_naR + given p = posit16_from_bits(0x8000) + assert posit16_is_naR(p) + +invariant conversion_config_init_clamp_enabled + given config = conversion_config_init() + assert config.clamp_overflow == true + +invariant gf16_to_posit16_zero_preserves + given gf = Gf16{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_zero(p) and not posit16_get_sign(p)); + +invariant gf16_to_posit16_infinity_to_naR + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 0} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_naR(p)); + +invariant gf16_to_posit16_nan_to_naR + given gf = Gf16{.sign = 0, .exp = 0x3F, .mant = 1} + try std.testing.expect(p = gf16_to_posit16(gf)); + try std.testing.expect(posit16_is_naR(p)); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf16_from_bits_latency + measure: nanoseconds to gf16_from_bits(0x4000) + target: < 20ns + +bench gf16_to_bits_latency + measure: nanoseconds to gf16_to_bits(gf16_from_bits(0x4000)) + target: < 20ns + +bench posit16_from_bits_latency + measure: nanoseconds to posit16_from_bits(0x4000) + target: < 20ns + +bench posit16_to_bits_latency + measure: nanoseconds to posit16_to_bits(posit16_from_bits(0x4000)) + target: < 15ns + +bench posit16_decode_latency + measure: nanoseconds to posit16_decode(posit16_from_bits(0x4000)) + target: < 50ns + +bench gf16_to_posit16_latency + measure: nanoseconds to gf16_to_posit16(gf16_from_bits(0x4000)) + target: < 40ns + +bench gf16_to_posit16_with_result_latency + measure: nanoseconds to gf16_to_posit16_with_result(gf16_from_bits(0x4000), conversion_config_init()) + target: < 50ns + +bench gf16_bits_to_posit16_bits_latency + measure: nanoseconds to gf16_bits_to_posit16_bits(0x4000) + target: < 40ns + +bench conversion_config_init_latency + measure: nanoseconds to conversion_config_init() + target: < 15ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/gf256.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/gf256.t27 new file mode 100644 index 0000000000..e57a0cdca8 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/gf256.t27 @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf256.t27 +// GoldenFloat256 0 256-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 11 (P1) + +module GF256 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // GF256 bit layout: [S(1) E(97) M(158)] + // S: 1 bit (sign) + // E: 97 bits (exponent) + // M: 158 bits (mantissa) + + const BITS : u8 = 256; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 97; + const MANT_BITS : u8 = 158; + + // Bias for exponent (2^(97-1) - 1 = 0x7FFFFFFFFFFFFFFF) + const EXP_BIAS_LO : u64 = 0xFFFFFFFFFFFFFFFF; + const EXP_BIAS_HI : u64 = 0x7F; + + // phi-ratio: exp/mant = 97/158 ≈ 0.614 (phi_distance = 0.004) + const PHI_DISTANCE : f64 = 0.00412087369321891; + + struct GF256 { + // 256-bit value (4 x u64) + raw_0 : u64, // bits 0-63 (mantissa low) + raw_1 : u64, // bits 64-127 (mantissa high + exp low) + raw_2 : u64, // bits 128-191 (exp mid + mant high) + raw_3 : u64, // bits 192-255 (sign + exp high) + } + + // Encode f64 to GF256 + fn encode(value: f64) -> GF256 { + if (value == 0.0) { + return GF256{ raw_0 = 0, raw_1 = 0, raw_2 = 0, raw_3 = 0 }; + } + + const sign = if (value < 0.0) { 1u64 } else { 0u64 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i128; + let exp_biased_lo = ((exp_unbiased + 158886469278522817) & 0xFFFFFFFFFFFFFFFF) as u64; + let exp_biased_hi = ((exp_unbiased >> 64) + 127) as u64; + + // Clamp exponent + if (exp_biased_hi > 0x7F) { + exp_biased_hi = 0x7F; + exp_biased_lo = 0xFFFFFFFFFFFFFFFF; + } + + // Extract mantissa (158 bits, split across 3 u64 words) + const normalized = abs_val / pow(2.0, exp_unbiased as f64); + const frac = normalized - 1.0; + const max_mant = (1u128 << MANT_BITS) - 1; + const mant = (frac * (max_mant + 1) as f64) as u128; + const clamped_mant = if (mant > max_mant) { max_mant } else { mant }; + + // Pack mantissa + const raw_0 = (clamped_mant & 0xFFFFFFFFFFFFFFFF) as u64; + const raw_1 = ((clamped_mant >> 64) & 0x3FFFFFFFFFFFFFFF) as u64; + + // Pack exponent (97 bits: 31 in raw_1, 64 in raw_2, 2 in raw_3) + const exp_m1 = (exp_biased_lo << 33) as u64; + const exp_m2 = (exp_biased_lo >> 31) | ((exp_biased_hi & 0x1) << 33); + + // Pack sign (bit 255) + const raw_3 = (sign << 63) | ((exp_biased_hi >> 1) & 0x7FFFFFFFFFFFFFFF); + + return GF256{ + raw_0 = raw_0, + raw_1 = raw_1 | exp_m1, + raw_2 = exp_m2, + raw_3 = raw_3, + }; + } + + // Decode GF256 to f64 + fn decode(gf: GF256) -> f64 { + const sign = (gf.raw_3 >> 63) as u8; + + // Extract exponent (97 bits) + const exp_lo_part = gf.raw_1 >> 33; + const exp_mid_part = (gf.raw_2 & 0x1FFFFFFFFFFFFFFF) as u64; + const exp_hi_part = (gf.raw_3 & 0x3FFFFFFFFFFFFFFF) as u64; + const exp_biased_lo = exp_lo_part | (exp_mid_part << 31); + const exp_biased_hi = exp_hi_part >> 62; + + const exp_biased_128 = ((exp_biased_hi as u128) << 64) | exp_biased_lo as u128; + + // Extract mantissa (158 bits) + const mant_lo = gf.raw_0; + const mant_mid = gf.raw_1 & 0x1FFFFFFFFFFFFFFF; + const mant = ((mant_mid as u128) << 64) | mant_lo as u128; + + // Zero + if (exp_biased_128 == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_bias_128 = ((EXP_BIAS_HI as u128) << 64) | EXP_BIAS_LO as u128; + const exp_unbiased = if (exp_biased_128 == 0) { + -(exp_bias_128 as i128) + 1 + } else { + (exp_biased_128 as i128) - exp_bias_128 as i128 + }; + + // Mantissa + const max_mant = (1u128 << MANT_BITS) - 1; + const mant_normalized = if (exp_biased_128 == 0) { + (mant as f64) / (max_mant + 1) as f64 + } else { + 1.0 + (mant as f64) / (max_mant + 1) as f64 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f64); + + if (sign != 0) { + return -value; + } + return value; + } + + // Format Properties + fn max_value() -> f64 { + const max_mant = (1u128 << MANT_BITS) - 1; + const mant_max = 1.0 + (max_mant as f64) / (max_mant + 1) as f64; + const exp_max = ((1i128 << EXP_BITS) - 1) as f64 - (EXP_BIAS_HI as f64 * 4294967296.0 + EXP_BIAS_LO as f64); + return mant_max * pow(2.0, exp_max); + } + + fn min_positive() -> f64 { + const max_mant = (1u128 << MANT_BITS) - 1; + const mant_min = 1.0 / (max_mant + 1) as f64; + const exp_min = -((EXP_BIAS_HI as f64 * 4294967296.0 + EXP_BIAS_LO as f64)) + 1.0; + return mant_min * pow(2.0, exp_min); + } + + fn epsilon() -> f64 { + const max_mant = (1u128 << MANT_BITS) - 1; + return 1.0 / (max_mant + 1) as f64; + } + + // Validation + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF256"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // Use Cases + // GF256 is optimal for: + // - Maximum precision computational chemistry + // - String theory simulations + // - Exascale climate modeling + // - Arbitrary-precision arithmetic bridge + + // Memory: 256 bits = 32 bytes (8x FP32) + const MEMORY_RATIO_VS_FP32 : f32 = 8.0; + + // Helper Functions + fn floor_log2(x: f64) -> i128 { + if (x <= 0.0) { return -170141183460469231731687303715884105728; } + let exp : i128 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn pow(base: f64, exp: f64) -> f64 { + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; + } + + const is_integer = exp == floor(exp); + + if (is_integer) { + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + fn ln_approx(x: f64) -> f64 { + if (x <= 0.0) { + return 0.0 / 0.0; + } + if (x == 1.0) { + return 0.0; + } + + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0); + } + + fn exp_approx(x: f64) -> f64 { + if (x == 0.0) { + return 1.0; + } + + let result = 1.0; + let term = 1.0; + let exp_x = x; + + for (i in 1..=15) { + term = term * exp_x / (i as f64); + result = result + term; + } + + return result; + } + + fn floor(x: f64) -> f64 { + let xi = x as i128; + if (x >= 0.0 || x == xi as f64) { + return xi as f64; + } + return (xi - 1) as f64; + } + + // TDD-Inside-Spec: Tests and Invariants for GF256 + + test gf256_decode_zero + given gf = GF256{ raw_0 = 0, raw_1 = 0, raw_2 = 0, raw_3 = 0 } + when value = decode(gf) + then value == 0.0 + + test gf256_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf256_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf256_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf256_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf256_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf256_phi_distance_good + given phi_dist = PHI_DISTANCE + then phi_dist < 0.01 + + test gf256_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 8.0) < 0.01 + + test gf256_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf256_bits_constant + assert BITS == 256 + + invariant gf256_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf256_exp_bits_is_97 + assert EXP_BITS == 97 + + invariant gf256_mant_bits_is_158 + assert MANT_BITS == 158 + + invariant gf256_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf256_phi_distance_below_threshold + assert PHI_DISTANCE < 0.01 + + invariant gf256_exp_mant_ratio_close_to_phi + assert abs((EXP_BITS as f64) / (MANT_BITS as f64) - 0.618) < 0.01 + + invariant gf256_has_max_precision + // GF256 provides extreme precision suitable for quantum computing + assert MANT_BITS > 100 + + bench gf256_encode_latency + measure: nanoseconds to encode(1.0) + target: < 1000ns + + bench gf256_decode_latency + measure: nanoseconds to decode(GF256{raw_0 = 0, raw_1 = 0, raw_2 = 0, raw_3 = 0}) + target: < 800ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/gf32_to_fp32.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/gf32_to_fp32.t27 new file mode 100644 index 0000000000..a33a7bdc1d --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/gf32_to_fp32.t27 @@ -0,0 +1,757 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf32_to_fp32.t27 — GF32 to FP32 Converter +; GoldenFloat32 to IEEE 754 binary32 format conversion +; φ² + 1/φ² = 3 | TRINITY + +module gf32-to-fp32; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const GF32_SIGN_BITS : u8 = 1; +pub const GF32_EXP_BITS : u8 = 12; +pub const GF32_MANT_BITS : u8 = 19; +pub const GF32_BIAS : i16 = 2047; + +pub const FP32_SIGN_BITS : u8 = 1; +pub const FP32_EXP_BITS : u8 = 8; +pub const FP32_MANT_BITS : u8 = 23; +pub const FP32_BIAS : i8 = 127; + +pub const EXP_BIAS_DIFF : i16 = GF32_BIAS - @as(i16, FP32_BIAS); + +pub const MANTissa_MASK_GF32 : u32 = 0x7FFFF; +pub const MANTissa_MASK_FP32 : u32 = 0x7FFFFF; + +// ============================================================================ +// Types +// ============================================================================ + +pub const Gf32 = packed struct { + sign : u1, + exp : u12, + mant : u19, +} + +pub const Fp32 = packed struct { + sign : u1, + exp : u8, + mant : u23, +} + +pub const ConversionResult = struct { + value : u32, + overflow : bool, + underflow : bool, + lost_precision : bool, +} + +pub const ConversionConfig = struct { + round_mode : u8, + overflow_mode : u8, + enable_saturation : bool, +} + +// ============================================================================ +// GF32 Functions +// ============================================================================ + +// gf32_from_bits(bits: u32) -> Gf32 +// Extract GF32 from 32-bit representation +pub fn gf32_from_bits(bits: u32) -> Gf32 { + return Gf32 { + .sign = @as(u1, @truncate((bits >> 31) & 0x01)), + .exp = @as(u12, @truncate((bits >> 19) & 0xFFF)), + .mant = @as(u19, @truncate(bits & 0x7FFFF)), + }; +} + +// gf32_to_bits(gf: Gf32) -> u32 +// Convert GF32 to 32-bit representation +pub fn gf32_to_bits(gf: Gf32) -> u32 { + const sign_field : u32 = @as(u32, gf.sign) << 31; + const exp_field : u32 = @as(u32, gf.exp) << 19; + const mant_field : u32 = @as(u32, gf.mant); + return sign_field | exp_field | mant_field; +} + +// gf32_get_exp_biased(gf: Gf32) -> i16 +// Get exponent with bias applied +pub fn gf32_get_exp_biased(gf: Gf32) -> i16 { + return @as(i16, @bitCast(gf.exp)) - GF32_BIAS; +} + +// gf32_is_zero(gf: Gf32) -> bool +// Check if GF32 is zero +pub fn gf32_is_zero(gf: Gf32) -> bool { + return gf.exp == 0 and gf.mant == 0; +} + +// gf32_is_subnormal(gf: Gf32) -> bool +// Check if GF32 is subnormal +pub fn gf32_is_subnormal(gf: Gf32) -> bool { + return gf.exp == 0 and gf.mant != 0; +} + +// gf32_is_normal(gf: Gf32) -> bool +// Check if GF32 is normal +pub fn gf32_is_normal(gf: Gf32) -> bool { + return gf.exp != 0 and gf.exp != 0xFFF; +} + +// gf32_is_infinity(gf: Gf32) -> bool +// Check if GF32 is infinity +pub fn gf32_is_infinity(gf: Gf32) -> bool { + return gf.exp == 0xFFF and gf.mant == 0; +} + +// gf32_is_nan(gf: Gf32) -> bool +// Check if GF32 is NaN +pub fn gf32_is_nan(gf: Gf32) -> bool { + return gf.exp == 0xFFF and gf.mant != 0; +} + +// gf32_is_qnan(gf: Gf32) -> bool +// Check if GF32 is quiet NaN +pub fn gf32_is_qnan(gf: Gf32) -> bool { + return gf32_is_nan(gf) and (gf.mant >> 18) == 1; +} + +// gf32_is_snan(gf: Gf32) -> bool +// Check if GF32 is signaling NaN +pub fn gf32_is_snan(gf: Gf32) -> bool { + return gf32_is_nan(gf) and (gf.mant >> 18) == 0; +} + +// ============================================================================ +// FP32 Functions +// ============================================================================ + +// fp32_from_bits(bits: u32) -> Fp32 +// Extract FP32 from 32-bit representation +pub fn fp32_from_bits(bits: u32) -> Fp32 { + return Fp32 { + .sign = @as(u1, @truncate((bits >> 31) & 0x01)), + .exp = @as(u8, @truncate((bits >> 23) & 0xFF)), + .mant = @as(u23, @truncate(bits & 0x7FFFFF)), + }; +} + +// fp32_to_bits(fp: Fp32) -> u32 +// Convert FP32 to 32-bit representation +pub fn fp32_to_bits(fp: Fp32) -> u32 { + const sign_field : u32 = @as(u32, fp.sign) << 31; + const exp_field : u32 = @as(u32, fp.exp) << 23; + const mant_field : u32 = @as(u32, fp.mant); + return sign_field | exp_field | mant_field; +} + +// fp32_get_exp_biased(fp: Fp32) -> i8 +// Get exponent with bias applied +pub fn fp32_get_exp_biased(fp: Fp32) -> i8 { + if (fp.exp == 0) { + return -126; // Denormal + } else if (fp.exp == 0xFF) { + return 128; // Infinity/NaN + } + return @as(i8, @bitCast(fp.exp)) - FP32_BIAS; +} + +// fp32_is_zero(fp: Fp32) -> bool +// Check if FP32 is zero +pub fn fp32_is_zero(fp: Fp32) -> bool { + return fp.exp == 0 and fp.mant == 0; +} + +// fp32_is_subnormal(fp: Fp32) -> bool +// Check if FP32 is subnormal +pub fn fp32_is_subnormal(fp: Fp32) -> bool { + return fp.exp == 0 and fp.mant != 0; +} + +// fp32_is_infinity(fp: Fp32) -> bool +// Check if FP32 is infinity +pub fn fp32_is_infinity(fp: Fp32) -> bool { + return fp.exp == 0xFF and fp.mant == 0; +} + +// fp32_is_nan(fp: Fp32) -> bool +// Check if FP32 is NaN +pub fn fp32_is_nan(fp: Fp32) -> bool { + return fp.exp == 0xFF and fp.mant != 0; +} + +// fp32_is_qnan(fp: Fp32) -> bool +// Check if FP32 is quiet NaN +pub fn fp32_is_qnan(fp: Fp32) -> bool { + return fp32_is_nan(fp) and (fp.mant >> 22) == 1; +} + +// fp32_is_snan(fp: Fp32) -> bool +// Check if FP32 is signaling NaN +pub fn fp32_is_snan(fp: Fp32) -> bool { + return fp32_is_nan(fp) and (fp.mant >> 22) == 0; +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// conversion_config_init() -> ConversionConfig +// Initialize conversion config +pub fn conversion_config_init() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, // Round to nearest + .overflow_mode = 0, // Clamp + .enable_saturation = true, + }; +} + +// conversion_config_no_saturation() -> ConversionConfig +// Create config without saturation +pub fn conversion_config_no_saturation() -> ConversionConfig { + return ConversionConfig { + .round_mode = 0, + .overflow_mode = 1, // Overflow to infinity + .enable_saturation = false, + }; +} + +// conversion_config_round_up() -> ConversionConfig +// Create config with round-up mode +pub fn conversion_config_round_up() -> ConversionConfig { + return ConversionConfig { + .round_mode = 1, // Round up + .overflow_mode = 0, + .enable_saturation = true, + }; +} + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// gf32_to_fp32_raw(gf: Gf32) -> Fp32 +// Convert GF32 to FP32 (raw, no overflow/underflow handling) +pub fn gf32_to_fp32_raw(gf: Gf32) -> Fp32 { + var exp_i16 = gf32_get_exp_biased(gf); + + if (gf32_is_zero(gf)) { + return Fp32{.sign = gf.sign, .exp = 0, .mant = 0}; + } else if (gf32_is_infinity(gf)) { + return Fp32{.sign = gf.sign, .exp = 0xFF, .mant = 0}; + } else if (gf32_is_nan(gf)) { + var mant_fp : u23 = @as(u23, gf.mant) << (FP32_MANT_BITS - GF32_MANT_BITS); + if (gf32_is_qnan(gf)) { + mant_fp |= (1 << (FP32_MANT_BITS - 1)); + } + return Fp32{.sign = gf.sign, .exp = 0xFF, .mant = mant_fp}; + } else if (gf32_is_subnormal(gf)) { + // Handle GF32 subnormal - convert to smallest normal FP32 + return Fp32{.sign = gf.sign, .exp = 1, .mant = 0}; + } + + // Adjust exponent bias: GF32 bias 2047 -> FP32 bias 127 + exp_i16 = exp_i16 + GF32_BIAS - @as(i16, FP32_BIAS); + + var exp : u8 = 0; + var mant : u23 = 0; + var lost_precision = false; + + if (exp_i16 <= -127) { + // Underflow to zero or denormal + if (exp_i16 == -127 and gf.mant != 0) { + // Smallest denormal + exp = 0; + mant = @as(u23, gf.mant) >> (127 + @as(u8, @bitCast(-exp_i16)) - GF32_MANT_BITS); + } else { + exp = 0; + mant = 0; + } + lost_precision = true; + } else if (exp_i16 >= 128) { + // Overflow to infinity + exp = 0xFF; + mant = 0; + } else { + exp = @as(u8, @truncate(@as(u8, @bitCast(exp_i16)) & 0xFF)); + // Extend mantissa from 19 to 23 bits + mant = @as(u23, gf.mant) << (FP32_MANT_BITS - GF32_MANT_BITS); + } + + return Fp32{.sign = gf.sign, .exp = exp, .mant = mant}; +} + +// gf32_to_fp32_with_result(gf: Gf32, config: ConversionConfig) -> ConversionResult +// Convert GF32 to FP32 with full result +pub fn gf32_to_fp32_with_result(gf: Gf32, config: ConversionConfig) -> ConversionResult { + var result : ConversionResult = undefined; + + if (gf32_is_zero(gf)) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0, .mant = 0}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } else if (gf32_is_infinity(gf)) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0xFF, .mant = 0}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } else if (gf32_is_nan(gf)) { + var mant_fp : u23 = @as(u23, gf.mant) << 4; + if (gf32_is_qnan(gf)) { + mant_fp |= 0x400000; // Quiet NaN bit + } + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0xFF, .mant = mant_fp}); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + return result; + } + + var exp_i16 = gf32_get_exp_biased(gf); + result.overflow = false; + result.underflow = false; + result.lost_precision = false; + + if (gf32_is_subnormal(gf)) { + result.underflow = true; + if (config.enable_saturation) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 1, .mant = 0}); + } else { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0, .mant = 1}); // Smallest denormal + } + return result; + } + + exp_i16 = exp_i16 + GF32_BIAS - @as(i16, FP32_BIAS); + + var exp : u8 = 0; + var mant : u23 = 0; + + if (exp_i16 <= -127) { + // Underflow + if (config.enable_saturation) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0, .mant = 0}); + } else { + // Try denormal + var denorm_exp = exp_i16 + 126; + if (denorm_exp >= -22) { + exp = 0; + mant = @as(u23, gf.mant) << (4 - @as(u5, @intCast(-denorm_exp)))); + } else { + exp = 0; + mant = 0; + } + } + result.underflow = true; + result.lost_precision = true; + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = exp, .mant = mant}); + return result; + } else if (exp_i16 >= 128) { + // Overflow + if (config.overflow_mode == 0 or config.enable_saturation) { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0xFE, .mant = 0x7FFFFF}); // Max finite + } else { + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = 0xFF, .mant = 0}); // Infinity + } + result.overflow = true; + return result; + } else { + exp = @as(u8, @truncate(@as(u8, @bitCast(exp_i16)) & 0xFF)); + mant = @as(u23, gf.mant) << 4; + result.value = fp32_to_bits(Fp32{.sign = gf.sign, .exp = exp, .mant = mant}); + return result; + } +} + +// gf32_to_fp32(gf: Gf32) -> Fp32 +// Convert GF32 to FP32 +pub fn gf32_to_fp32(gf: Gf32) -> Fp32 { + const result = gf32_to_fp32_with_result(gf, conversion_config_init()); + return fp32_from_bits(result.value); +} + +// gf32_bits_to_fp32_bits(gf_bits: u32) -> u32 +// Convert GF32 bits to FP32 bits +pub fn gf32_bits_to_fp32_bits(gf_bits: u32) -> u32 { + const gf = gf32_from_bits(gf_bits); + const fp = gf32_to_fp32(gf); + return fp32_to_bits(fp); +} + +// gf32_bits_to_fp32_bits_with_result(gf_bits: u32, config: ConversionConfig) -> ConversionResult +// Convert GF32 bits to FP32 bits with full result +pub fn gf32_bits_to_fp32_bits_with_result(gf_bits: u32, config: ConversionConfig) -> ConversionResult { + const gf = gf32_from_bits(gf_bits); + return gf32_to_fp32_with_result(gf, config); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf32_bits_constants" { + try std.testing.expect(GF32_SIGN_BITS == 1); + try std.testing.expect(GF32_EXP_BITS == 12); + try std.testing.expect(GF32_MANT_BITS == 19); + try std.testing.expect(GF32_BIAS == 2047); +} + +test "fp32_bits_constants" { + try std.testing.expect(FP32_SIGN_BITS == 1); + try std.testing.expect(FP32_EXP_BITS == 8); + try std.testing.expect(FP32_MANT_BITS == 23); + try std.testing.expect(FP32_BIAS == 127); +} + +test "exp_bias_diff" { + try std.testing.expect(EXP_BIAS_DIFF == 1920); +} + +test "mantissa_masks" { + try std.testing.expect(MANTissa_MASK_GF32 == 0x7FFFF); + try std.testing.expect(MANTissa_MASK_FP32 == 0x7FFFFF); +} + +test "gf32_from_bits_to_bits_roundtrip" { + given bits = 0x12345678 + try std.testing.expect(gf = gf32_from_bits(bits)); + try std.testing.expect(result = gf32_to_bits(gf)); + try std.testing.expect(result == bits); +} + +test "gf32_from_bits_structure" { + given gf = gf32_from_bits(0x80000000) + try std.testing.expect(gf.sign == 1); + try std.testing.expect(gf.exp == 0); + try std.testing.expect(gf.mant == 0); +} + +test "fp32_from_bits_to_bits_roundtrip" { + given bits = 0x3F800000 + try std.testing.expect(fp = fp32_from_bits(bits)); + try std.testing.expect(result = fp32_to_bits(fp)); + try std.testing.expect(result == bits); +} + +test "gf32_is_zero_true" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(gf32_is_zero(gf) == true); +} + +test "gf32_is_zero_false" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(gf32_is_zero(gf) == false); +} + +test "gf32_is_subnormal_true" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(gf32_is_subnormal(gf) == true); +} + +test "gf32_is_normal_true" { + given gf = Gf32{.sign = 0, .exp = 1, .mant = 0} + try std.testing.expect(gf32_is_normal(gf) == true); +} + +test "gf32_is_infinity_true" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0} + try std.testing.expect(gf32_is_infinity(gf) == true); +} + +test "gf32_is_nan_true" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 1} + try std.testing.expect(gf32_is_nan(gf) == true); +} + +test "gf32_is_qnan_true" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x40000} + try std.testing.expect(gf32_is_qnan(gf) == true); +} + +test "gf32_is_snan_true" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x20000} + try std.testing.expect(gf32_is_snan(gf) == true); +} + +test "fp32_is_zero_true" { + given fp = Fp32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp32_is_zero(fp) == true); +} + +test "fp32_is_infinity_true" { + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 0} + try std.testing.expect(fp32_is_infinity(fp) == true); +} + +test "fp32_is_nan_true" { + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 1} + try std.testing.expect(fp32_is_nan(fp) == true); +} + +test "fp32_is_qnan_true" { + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 0x400000} + try std.testing.expect(fp32_is_qnan(fp) == true); +} + +test "conversion_config_init_structure" { + given config = conversion_config_init() + try std.testing.expect(config.round_mode == 0); + try std.testing.expect(config.enable_saturation == true); +} + +test "conversion_config_no_saturation_structure" { + given config = conversion_config_no_saturation() + try std.testing.expect(config.enable_saturation == false); +} + +test "conversion_config_round_up_structure" { + given config = conversion_config_round_up() + try std.testing.expect(config.round_mode == 1); +} + +test "gf32_to_fp32_zero" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.exp == 0); + try std.testing.expect(fp.mant == 0); +} + +test "gf32_to_fp32_infinity" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.exp == 0xFF); + try std.testing.expect(fp.mant == 0); +} + +test "gf32_to_fp32_nan" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 1} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.exp == 0xFF); + try std.testing.expect(fp.mant != 0); +} + +test "gf32_to_fp32_preserves_sign" { + given gf_pos = Gf32{.sign = 1, .exp = 2048, .mant = 0} + try std.testing.expect(gf_neg = Gf32{.sign = 0, .exp = 2048, .mant = 0}); + try std.testing.expect(fp_pos = gf32_to_fp32(gf_pos)); + try std.testing.expect(fp_neg = gf32_to_fp32(gf_neg)); + try std.testing.expect(fp_pos.sign == 1 and fp_neg.sign == 0); +} + +test "gf32_to_fp32_with_result_zero" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.overflow == false); + try std.testing.expect(result.underflow == false); + try std.testing.expect(result.lost_precision == false); +} + +test "gf32_bits_to_fp32_bits_zero" { + given config = conversion_config_init() + try std.testing.expect(gf32_bits_to_fp32_bits(0x00000000, config) == 0x00000000); +} + +test "gf32_to_fp32_with_result_subnormal_underflow" { + given gf = Gf32{.sign = 0, .exp = 0, .mant = 1} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.underflow == true); +} + +test "gf32_to_fp32_with_result_overflow" { + given gf = Gf32{.sign = 0, .exp = 4094, .mant = 0} // Very large + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.overflow == true); +} + +test "gf32_to_fp32_with_result_saturation" { + given gf = Gf32{.sign = 0, .exp = 4094, .mant = 0} // Very large + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.value != 0x7F800000 // Not infinity, but max finite); +} + +test "gf32_to_fp32_with_result_no_saturation_overflow" { + given gf = Gf32{.sign = 0, .exp = 4094, .mant = 0} // Very large + try std.testing.expect(config = conversion_config_no_saturation()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect(result.value == 0x7F800000 // Infinity); +} + +test "gf32_to_fp32_nan_qnan_preserves" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x40000} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect((result.value & 0x400000) != 0 // Quiet NaN bit set); +} + +test "gf32_to_fp32_nan_snan_preserves" { + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x20000} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect((result.value & 0x400000) == 0 // Quiet NaN bit not set); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf32_bit_count + assert GF32_SIGN_BITS + GF32_EXP_BITS + GF32_MANT_BITS == 32 + +invariant fp32_bit_count + assert FP32_SIGN_BITS + FP32_EXP_BITS + FP32_MANT_BITS == 32 + +invariant gf32_exp_width + assert GF32_EXP_BITS == 12 + +invariant fp32_exp_width + assert FP32_EXP_BITS == 8 + +invariant gf32_mant_width + assert GF32_MANT_BITS == 19 + +invariant fp32_mant_width + assert FP32_MANT_BITS == 23 + +invariant mantissa_masks_correct + assert MANTissa_MASK_GF32 == (@as(u32, 1) << GF32_MANT_BITS) - 1 + try std.testing.expect(MANTissa_MASK_FP32 == (@as(u32, 1) << FP32_MANT_BITS) - 1); + +invariant exp_bias_diff_positive + assert EXP_BIAS_DIFF > 0 + +invariant gf32_from_bits_roundtrip + given bits = 0x12345678 + assert gf32_to_bits(gf32_from_bits(bits)) == bits + +invariant fp32_from_bits_roundtrip + given bits = 0x3F800000 + assert fp32_to_bits(fp32_from_bits(bits)) == bits + +invariant gf32_zero_exp_zero_mant_zero + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + assert gf32_is_zero(gf) and not gf32_is_subnormal(gf) + +invariant gf32_subnormal_exp_zero_mant_nonzero + given gf = Gf32{.sign = 0, .exp = 0, .mant = 1} + assert gf32_is_subnormal(gf) and not gf32_is_zero(gf) + +invariant gf32_normal_exp_nonzero + given gf = Gf32{.sign = 0, .exp = 1, .mant = 0} + assert gf32_is_normal(gf) and not gf32_is_zero(gf) + +invariant gf32_infinity_exp_max_mant_zero + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0} + assert gf32_is_infinity(gf) and not gf32_is_nan(gf) + +invariant gf32_qnan_top_mant_bit + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x40000} + assert gf32_is_nan(gf) and gf32_is_qnan(gf) + +invariant gf32_snan_top_mant_bit_clear + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x20000} + assert gf32_is_nan(gf) and gf32_is_snan(gf) + +invariant fp32_infinity_exp_max_mant_zero + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 0} + assert fp32_is_infinity(fp) and not fp32_is_nan(fp) + +invariant fp32_qnan_top_mant_bit + given fp = Fp32{.sign = 0, .exp = 0xFF, .mant = 0x400000} + assert fp32_is_nan(fp) and fp32_is_qnan(fp) + +invariant conversion_config_init_saturation_enabled + given config = conversion_config_init() + assert config.enable_saturation == true + +invariant conversion_config_no_saturation_disabled + given config = conversion_config_no_saturation() + assert config.enable_saturation == false + +invariant conversion_config_round_up_mode_one + given config = conversion_config_round_up() + assert config.round_mode == 1 + +invariant gf32_to_fp32_zero_preserves + given gf = Gf32{.sign = 0, .exp = 0, .mant = 0} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.sign == gf.sign); + +invariant gf32_to_fp32_infinity_preserves_sign + given gf_pos = Gf32{.sign = 1, .exp = 0xFFF, .mant = 0} + try std.testing.expect(fp_pos = gf32_to_fp32(gf_pos)); + try std.testing.expect(fp_pos.sign == 1); + +invariant gf32_to_fp32_nan_preserves_sign + given gf = Gf32{.sign = 1, .exp = 0xFFF, .mant = 1} + try std.testing.expect(fp = gf32_to_fp32(gf)); + try std.testing.expect(fp.sign == 1); + +invariant gf32_to_fp32_qnan_preserves_qnan + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x40000} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect((result.value & 0x400000) != 0); + +invariant gf32_to_fp32_snan_preserves_snan + given gf = Gf32{.sign = 0, .exp = 0xFFF, .mant = 0x20000} + try std.testing.expect(config = conversion_config_init()); + try std.testing.expect(result = gf32_to_fp32_with_result(gf, config)); + try std.testing.expect((result.value & 0x400000) == 0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf32_from_bits_latency + measure: nanoseconds to gf32_from_bits(0x3F800000) + target: < 20ns + +bench gf32_to_bits_latency + measure: nanoseconds to gf32_to_bits(gf32_from_bits(0x3F800000)) + target: < 20ns + +bench fp32_from_bits_latency + measure: nanoseconds to fp32_from_bits(0x3F800000) + target: < 20ns + +bench fp32_to_bits_latency + measure: nanoseconds to fp32_to_bits(fp32_from_bits(0x3F800000)) + target: < 20ns + +bench gf32_to_fp32_latency + measure: nanoseconds to gf32_to_fp32(gf32_from_bits(0x40000000)) + target: < 30ns + +bench gf32_to_fp32_with_result_latency + measure: nanoseconds to gf32_to_fp32_with_result(gf32_from_bits(0x40000000), conversion_config_init()) + target: < 40ns + +bench gf32_bits_to_fp32_bits_latency + measure: nanoseconds to gf32_bits_to_fp32_bits(0x40000000) + target: < 30ns + +bench conversion_config_init_latency + measure: nanoseconds to conversion_config_init() + target: < 15ns + +bench gf32_is_nan_latency + measure: nanoseconds to gf32_is_nan(gf32_from_bits(0x7FC00000)) + target: < 15ns + +bench gf32_is_qnan_latency + measure: nanoseconds to gf32_is_qnan(gf32_from_bits(0x7FC00000)) + target: < 15ns + +bench fp32_is_nan_latency + measure: nanoseconds to fp32_is_nan(fp32_from_bits(0x7FC00000)) + target: < 15ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/gf64.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/gf64.t27 new file mode 100644 index 0000000000..b472cef864 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/gf64.t27 @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf64.t27 +// GoldenFloat64 - 64-bit φ-structured floating point +// NUMERIC-STANDARD-001 Agent 4 (P1) + +module GF64 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // GF64 bit layout: [S(1) | EEE...EEE(24) | MMM...MMM(39)] + // S: 1 bit (sign) + // E: 24 bits (exponent) + // M: 39 bits (mantissa) + // + // φ-ratio: exp/mant = 24/39 ≈ 0.615 (phi_distance = 0.003) + // This is the BEST phi approximation in the entire GF family! + + const BITS : u8 = 64; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 24; + const MANT_BITS : u8 = 39; + + // Bias for exponent (2^23 - 1 = 8388607) + const EXP_BIAS : i32 = 8388607; + + // φ-ratio: exp/mant = 24/39 = 0.615 (phi_distance = 0.003) + const PHI_DISTANCE : f64 = 0.0028268468254629; + + // 2. GoldenFloat64 Type + + struct GF64 { + raw : u64, // 64-bit raw value + } + + // 3. Encoding/Decoding + + // Encode f32 to GF64 + fn encode(value: f32) -> GF64 { + if (value == 0.0) { + return GF64{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) using f64 for precision + const exp_unbiased = floor_log2_f64(abs_val as f64) as i32; + const exp_biased = (exp_unbiased + EXP_BIAS) as u64; + + // Clamp exponent (18 bits max = 262143) + const exp_clamped = if (exp_biased > 16777215) { 16777215 } else { exp_biased }; + + // Extract mantissa (45 bits) + const mant = extract_mantissa_64(abs_val as f64, exp_unbiased, MANT_BITS); + + return GF64{ + raw = (sign << 63) | (exp_clamped << MANT_BITS) | mant + }; + } + + // Decode GF64 to f32 + fn decode(gf: GF64) -> f64 { + const sign = (gf.raw >> 63) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0xFFFFFF) as u64; + + // Zero + if (exp_biased == 0 && (gf.raw & 0x7FFFFFFFFFFFFF) == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS + 1 + } else { + (exp_biased as i32) - EXP_BIAS + }; + + // Mantissa (with implicit 1 for normalized) + const mant_normalized = if (exp_biased == 0) { + (mant_u64_to_f64(gf.raw & 0x7FFFFFFFFFFFFF)) / 549755813888.0 + } else { + 1.0 + (mant_u64_to_f64(gf.raw & 0x7FFFFFFFFFFFFF)) / 549755813888.0 + }; + + const value = mant_normalized * pow2_64(exp_unbiased as f64); + + if (sign != 0) { + return -value; + } + return value; + } + + // 4. Format Properties + + fn max_value() -> f64 { + // Max normalized: mant ≈ 2.0, exp = 131071 + const mant_max = 1.9999999999999998; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow2_64(exp_max as f64); + } + + fn min_positive() -> f64 { + // Min subnormal: mant ≈ 0, exp = -131070 + const mant_min = 1.0 / 35184372088832.0; + const exp_min = -EXP_BIAS + 1; + return mant_min * pow2_64(exp_min as f64); + } + + fn epsilon() -> f64 { + // Smallest representable difference at 1.0 + return 1.0 / 35184372088832.0; + } + + // 5. Validation + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF64"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 6. Use Cases + + // GF64 is optimal for: + // - High-precision scientific computing + // - Financial calculations requiring accuracy + // - Extended range physics simulations + // - Double-precision alternatives with φ-optimization + + // Memory: 64 bits = 8 bytes (same as FP64, better range) + const MEMORY_RATIO_VS_FP32 : f32 = 64.0 / 32.0; // 2.0 + + // 7. Helper Functions + + fn floor_log2_f64(x: f64) -> i32 { + if (x <= 0.0) { return -2147483648; } + let exp : i32 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa_64(value: f64, exp: i32, mant_bits: u8) -> u64 { + const normalized = value / pow2_64(exp as f64); + const frac = normalized - 1.0; + const max_mant = (1 << mant_bits) - 1; + return (frac * (max_mant as f64 + 1.0)) as u64; + } + + fn mant_u64_to_f64(m: u64) -> f64 { + return m as f64; + } + + fn pow2_64(exp: f64) -> f64 { + if (exp == 0.0) { return 1.0; } + if (exp < 0.0) { return 1.0 / pow2_64(-exp); } + + let result = 1.0; + let base = 2.0; + let e = exp as i32; + + while (e > 0) { + if (e % 2 == 1) { + result = result * base; + } + base = base * base; + e = e / 2; + } + return result; + } + + // TDD-Inside-Spec: Tests and Invariants for GF64 + + test gf64_decode_zero + given gf = GF64{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf64_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf64_encode_positive_value + given original = 1.5 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 1.5) < 1e-6 + + test gf64_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf64_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf64_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf64_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf64_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 2.0) < 0.01 + + test gf64_validate_format_success + given valid = validate_format() + then valid == true + + test gf64_encode_large_value + given original = 1000000.0 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded / original - 1.0) < 0.01 + + test gf64_encode_small_value + given original = 0.000001 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded / original - 1.0) < 0.01 + + test gf64_negative_encoding + given original = -2.5 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded < 0.0 and abs(decoded - (-2.5)) < 1e-6 + + test gf64_sign_bit_negative + given value = -1.0 + and encoded = encode(value) + and sign_bit = (encoded.raw >> 63) as u8 + then sign_bit == 1 + + test gf64_sign_bit_positive + given value = 1.0 + and encoded = encode(value) + and sign_bit = (encoded.raw >> 63) as u8 + then sign_bit == 0 + + invariant gf64_bits_constant + assert BITS == 64 + + invariant gf64_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf64_exp_bits_is_24 + assert EXP_BITS == 24 + + invariant gf64_mant_bits_is_39 + assert MANT_BITS == 39 + + invariant gf64_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf64_phi_distance_best_in_family + assert PHI_DISTANCE < 0.005 + + invariant gf64_exp_bias_positive + assert EXP_BIAS > 0 + + invariant gf64_exp_bias_is_8388607 + assert EXP_BIAS == 8388607 + + invariant gf64_encode_preserves_sign_positive + given val = 1.5 + and enc = encode(val) + and sign = (enc.raw >> 63) as u8 + then sign == 0 + + invariant gf64_encode_preserves_sign_negative + given val = -1.5 + and enc = encode(val) + and sign = (enc.raw >> 63) as u8 + then sign == 1 + + invariant gf64_decode_zero_zero + assert decode(GF64{raw = 0}) == 0.0 + + invariant gf64_epsilon_less_than_min_positive + assert epsilon() <= min_positive() + + invariant gf64_exp_mant_ratio_is_0_4 + given ratio = (EXP_BITS as f64) / (MANT_BITS as f64) + then abs(ratio - 0.4) < 0.01 + + bench gf64_encode_latency + measure: nanoseconds to encode(1.0) + target: < 200ns + + bench gf64_decode_latency + measure: nanoseconds to decode(GF64{raw = encode(1.5).raw}) + target: < 100ns + + bench gf64_pow2_latency + measure: nanoseconds to pow2_64(10.0) + target: < 100ns + + bench gf64_encode_decode_roundtrip + measure: nanoseconds to encode(1.0) and decode(GF64{raw = encode(1.0).raw}) + target: < 300ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/holo_mux_x4.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/holo_mux_x4.t27 new file mode 100644 index 0000000000..908d2f9877 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/holo_mux_x4.t27 @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: Apache-2.0 +; holo_mux_x4.t27 — Sacred Opcode 0xE6: Holographic 4x Multiplexer +; Hardware multiplexer for holographic data paths with 4-way select +; φ² + 1/φ² = 3 | TRINITY + +module sacred-holo_mux_x4; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_HOLO_MUX_X4 : u8 = 0xE6; + +pub const NUM_INPUTS : u8 = 4; +pub const DATA_WIDTH : u8 = 16; +pub const SELECT_BITS : u8 = 2; + +pub const HOLO_LAYER_NORTH : u8 = 0; +pub const HOLO_LAYER_EAST : u8 = 1; +pub const HOLO_LAYER_SOUTH : u8 = 2; +pub const HOLO_LAYER_WEST : u8 = 3; + +pub const HOLO_PHASE_0 : u8 = 0; +pub const HOLO_PHASE_90 : u8 = 1; +pub const HOLO_PHASE_180 : u8 = 2; +pub const HOLO_PHASE_270 : u8 = 3; + +pub const HOLO_VALID : u8 = 1; +pub const HOLO_INVALID : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const HoloInput = struct { + data : u16, + layer : u8, + phase : u8, + valid : bool, + phi_score : u8, // φ-distance score (0-255, lower is better) +} + +pub const HoloMuxConfig = struct { + phi_optimized : bool, + priority_select : bool, + layer_frozen : bool, // LAYER-FROZEN gate + default_input : u8, +} + +pub const HoloOutput = struct { + data : u16, + selected_layer : u8, + selected_phase : u8, + valid : bool, + frozen : bool, // Whether output is from LAYER-FROZEN input +} + +pub const HoloMuxState = struct { + current_select : u8, + frozen_select : u8, + cycle_count : u32, +} + +// ============================================================================ +// Holo Input Functions +// ============================================================================ + +// holo_input_valid(input: HoloInput) -> bool +// Check if holo input is valid +pub fn holo_input_valid(input: HoloInput) bool { + return input.valid and input.layer < NUM_INPUTS; +} + +// holo_input_score(input: HoloInput, config: HoloMuxConfig) -> u16 +// Calculate selection score for input +pub fn holo_input_score(input: HoloInput, config: HoloMuxConfig) u16 { + if (not holo_input_valid(input)) { + return 0; + } + + var score : u16 = @as(u16, @intCast(input.data)); + + if (config.phi_optimized) { + // Prefer lower phi score + const phi_bonus = 255 - input.phi_score; + score += @as(u16, phi_bonus) * 256; + } + + if (config.priority_select) { + // Layer priority affects score + const priority_bonus = (@as(u8, 3) - input.layer) * 1000; + score += priority_bonus; + } + + return score; +} + +// ============================================================================ +// Holo Mux Functions +// ============================================================================ + +// holo_mux_select(inputs: [NUM_INPUTS]HoloInput, select: u8, config: HoloMuxConfig) -> HoloOutput +// Select holo input based on select bits +pub fn holo_mux_select(inputs: [NUM_INPUTS]HoloInput, select: u8, config: HoloMuxConfig) HoloOutput { + const selected_idx = select & 0x03; + + // LAYER-FROZEN: check if frozen mode is active + if (config.layer_frozen) { + const frozen_input = inputs[config.default_input]; + return HoloOutput { + .data = frozen_input.data, + .selected_layer = frozen_input.layer, + .selected_phase = frozen_input.phase, + .valid = holo_input_valid(frozen_input), + .frozen = true, + }; + } + + const selected = inputs[@as(u8, @intCast(selected_idx))]; + return HoloOutput { + .data = selected.data, + .selected_layer = selected.layer, + .selected_phase = selected.phase, + .valid = holo_input_valid(selected), + .frozen = false, + }; +} + +// holo_mux_best(inputs: [NUM_INPUTS]HoloInput, config: HoloMuxConfig) -> HoloOutput +// Select best holo input based on score +pub fn holo_mux_best(inputs: [NUM_INPUTS]HoloInput, config: HoloMuxConfig) -> HoloOutput { + if (config.layer_frozen) { + const frozen_input = inputs[config.default_input]; + return HoloOutput { + .data = frozen_input.data, + .selected_layer = frozen_input.layer, + .selected_phase = frozen_input.phase, + .valid = holo_input_valid(frozen_input), + .frozen = true, + }; + } + + var best_idx : u8 = 0; + var best_score : u16 = 0; + + for (inputs, 0..) |input, i| { + const score = holo_input_score(input, config); + if (score > best_score and holo_input_valid(input)) { + best_score = score; + best_idx = @as(u8, @intCast(i)); + } + } + + const selected = inputs[best_idx]; + return HoloOutput { + .data = selected.data, + .selected_layer = selected.layer, + .selected_phase = selected.phase, + .valid = holo_input_valid(selected), + .frozen = false, + }; +} + +// holo_mux_valid(inputs: [NUM_INPUTS]HoloInput) -> u8 +// Get bitmask of valid inputs +pub fn holo_mux_valid(inputs: [NUM_INPUTS]HoloInput) -> u8 { + var mask : u8 = 0; + for (inputs, 0..) |input, i| { + if (holo_input_valid(input)) { + mask |= (@as(u8, 1) << i); + } + } + return mask; +} + +// holo_mux_count_valid(inputs: [NUM_INPUTS]HoloInput) -> u8 +// Count valid inputs +pub fn holo_mux_count_valid(inputs: [NUM_INPUTS]HoloInput) -> u8 { + var count : u8 = 0; + for (inputs) |input| { + if (holo_input_valid(input)) { + count += 1; + } + } + return count; +} + +// holo_mux_any_valid(inputs: [NUM_INPUTS]HoloInput) -> bool +// Check if any input is valid +pub fn holo_mux_any_valid(inputs: [NUM_INPUTS]HoloInput) -> bool { + for (inputs) |input| { + if (holo_input_valid(input)) { + return true; + } + } + return false; +} + +// holo_mux_all_valid(inputs: [NUM_INPUTS]HoloInput) -> bool +// Check if all inputs are valid +pub fn holo_mux_all_valid(inputs: [NUM_INPUTS]HoloInput) -> bool { + for (inputs) |input| { + if (not holo_input_valid(input)) { + return false; + } + } + return true; +} + +// ============================================================================ +// Holo Layer Functions +// ============================================================================ + +// holo_layer_is_north(layer: u8) -> bool +pub fn holo_layer_is_north(layer: u8) bool { + return layer == HOLO_LAYER_NORTH; +} + +// holo_layer_is_east(layer: u8) -> bool +pub fn holo_layer_is_east(layer: u8) bool { + return layer == HOLO_LAYER_EAST; +} + +// holo_layer_is_south(layer: u8) -> bool +pub fn holo_layer_is_south(layer: u8) bool { + return layer == HOLO_LAYER_SOUTH; +} + +// holo_layer_is_west(layer: u8) -> bool +pub fn holo_layer_is_west(layer: u8) bool { + return layer == HOLO_LAYER_WEST; +} + +// holo_opposite_layer(layer: u8) -> u8 +// Get opposite layer (N<->S, E<->W) +pub fn holo_opposite_layer(layer: u8) -> u8 { + switch (layer) { + HOLO_LAYER_NORTH => return HOLO_LAYER_SOUTH, + HOLO_LAYER_SOUTH => return HOLO_LAYER_NORTH, + HOLO_LAYER_EAST => return HOLO_LAYER_WEST, + HOLO_LAYER_WEST => return HOLO_LAYER_EAST, + else => return layer, + } +} + +// ============================================================================ +// Holo Phase Functions +// ============================================================================ + +// holo_phase_90_deg(phase: u8) -> u8 +// Rotate phase by 90 degrees +pub fn holo_phase_90_deg(phase: u8) -> u8 { + return (phase + 1) % 4; +} + +// holo_phase_180_deg(phase: u8) -> u8 +// Rotate phase by 180 degrees +pub fn holo_phase_180_deg(phase: u8) -> u8 { + return (phase + 2) % 4; +} + +// holo_phase_270_deg(phase: u8) -> u8 +// Rotate phase by 270 degrees +pub fn holo_phase_270_deg(phase: u8) -> u8 { + return (phase + 3) % 4; +} + +// holo_phase_opposite(phase: u8) -> u8 +// Get opposite phase +pub fn holo_phase_opposite(phase: u8) -> u8 { + return (phase + 2) % 4; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_holo_mux_x4(layer: u8, phase: u8, frozen: bool) -> u16 +// Encode holographic mux instruction +pub fn encode_holo_mux_x4(layer: u8, phase: u8, frozen: bool) u16 { + // Format: [OP:8][LAYER:2][PHASE:2][FROZEN:1][RES:3] + const op : u16 = @as(u16, OP_HOLO_MUX_X4) << 8; + const layer_field : u16 = @as(u16, layer & 0x03) << 6; + const phase_field : u16 = @as(u16, phase & 0x03) << 4; + const frozen_field : u16 = if (frozen) (@as(u16, 1) << 3) else 0; + return op | layer_field | phase_field | frozen_field; +} + +// decode_holo_mux_x4(encoded: u16) -> struct { layer: u8, phase: u8, frozen: bool } +// Decode holographic mux instruction +pub fn decode_holo_mux_x4(encoded: u16) struct { layer: u8, phase: u8, frozen: bool } { + const layer : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const phase : u8 = @as(u8, @truncate((encoded >> 4) & 0x03)); + const frozen : bool = ((encoded >> 3) & 1) != 0; + return .{ .layer = layer, .phase = phase, .frozen = frozen }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "num_inputs_four" { + try std.testing.expect(NUM_INPUTS == 4); +} + +test "data_width_sixteen" { + try std.testing.expect(DATA_WIDTH == 16); +} + +test "select_bits_two" { + try std.testing.expect(SELECT_BITS == 2); +} + +test "holo_layer_constants" { + try std.testing.expect(HOLO_LAYER_NORTH == 0); + try std.testing.expect(HOLO_LAYER_EAST == 1); + try std.testing.expect(HOLO_LAYER_SOUTH == 2); + try std.testing.expect(HOLO_LAYER_WEST == 3); +} + +test "holo_phase_constants" { + try std.testing.expect(HOLO_PHASE_0 == 0); + try std.testing.expect(HOLO_PHASE_90 == 1); + try std.testing.expect(HOLO_PHASE_180 == 2); + try std.testing.expect(HOLO_PHASE_270 == 3); +} + +test "holo_input_valid_true" { + given input = HoloInput{.data = 0x1234, .layer = 0, .phase = 0, .valid = true, .phi_score = 100} + try std.testing.expect(holo_input_valid(input) == true); +} + +test "holo_input_valid_false_invalid_layer" { + given input = HoloInput{.data = 0x1234, .layer = 4, .phase = 0, .valid = true, .phi_score = 100} + try std.testing.expect(holo_input_valid(input) == false); +} + +test "holo_input_valid_false_invalid_flag" { + given input = HoloInput{.data = 0x1234, .layer = 0, .phase = 0, .valid = false, .phi_score = 100} + try std.testing.expect(holo_input_valid(input) == false); +} + +test "holo_mux_select_north" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 100}, + } + try std.testing.expect(config = HoloMuxConfig{.phi_optimized = false, .priority_select = false, .layer_frozen = false, .default_input = 0}); + try std.testing.expect(output = holo_mux_select(inputs, 0, config)); + try std.testing.expect(output.data == 0x1000); + try std.testing.expect(output.selected_layer == 0); + try std.testing.expect(output.frozen == false); +} + +test "holo_mux_select_east" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 100}, + } + try std.testing.expect(config = HoloMuxConfig{.phi_optimized = false, .priority_select = false, .layer_frozen = false, .default_input = 0}); + try std.testing.expect(output = holo_mux_select(inputs, 1, config)); + try std.testing.expect(output.data == 0x2000); + try std.testing.expect(output.selected_layer == 1); +} + +test "holo_mux_frozen" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 100}, + } + try std.testing.expect(config = HoloMuxConfig{.phi_optimized = false, .priority_select = false, .layer_frozen = true, .default_input = 2}); + try std.testing.expect(output = holo_mux_select(inputs, 1, config)); + try std.testing.expect(output.data == 0x3000); + try std.testing.expect(output.selected_layer == 2); + try std.testing.expect(output.frozen == true); +} + +test "holo_mux_best_phi_optimized" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 50}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 200}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 10}, + } + try std.testing.expect(config = HoloMuxConfig{.phi_optimized = true, .priority_select = false, .layer_frozen = false, .default_input = 0}); + try std.testing.expect(output = holo_mux_best(inputs, config)); + try std.testing.expect(output.selected_layer == 3 // Best phi score); +} + +test "holo_mux_valid_mask" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(mask = holo_mux_valid(inputs)); + try std.testing.expect(mask == 0b00000101); +} + +test "holo_mux_count_valid" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(holo_mux_count_valid(inputs) == 2); +} + +test "holo_mux_any_valid_true" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(holo_mux_any_valid(inputs) == true); +} + +test "holo_mux_any_valid_false" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(holo_mux_any_valid(inputs) == false); +} + +test "holo_mux_all_valid_true" { + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = true, .phi_score = 100}, + } + try std.testing.expect(holo_mux_all_valid(inputs) == true); +} + +test "holo_layer_is_north" { + try std.testing.expect(holo_layer_is_north(0) == true); + try std.testing.expect(holo_layer_is_north(1) == false); +} + +test "holo_layer_is_south" { + try std.testing.expect(holo_layer_is_south(2) == true); + try std.testing.expect(holo_layer_is_south(0) == false); +} + +test "holo_opposite_layer" { + try std.testing.expect(holo_opposite_layer(HOLO_LAYER_NORTH) == HOLO_LAYER_SOUTH); + try std.testing.expect(holo_opposite_layer(HOLO_LAYER_EAST) == HOLO_LAYER_WEST); +} + +test "holo_phase_90_deg" { + try std.testing.expect(holo_phase_90_deg(0) == 1); + try std.testing.expect(holo_phase_90_deg(1) == 2); + try std.testing.expect(holo_phase_90_deg(3) == 0); +} + +test "holo_phase_180_deg" { + try std.testing.expect(holo_phase_180_deg(0) == 2); + try std.testing.expect(holo_phase_180_deg(1) == 3); + try std.testing.expect(holo_phase_180_deg(2) == 0); +} + +test "holo_phase_opposite" { + try std.testing.expect(holo_phase_opposite(0) == 2); + try std.testing.expect(holo_phase_opposite(1) == 3); + try std.testing.expect(holo_phase_opposite(2) == 0); + try std.testing.expect(holo_phase_opposite(3) == 1); +} + +test "encode_holo_mux_x4" { + given encoded = encode_holo_mux_x4(0x02, 0x01, true) + try std.testing.expect((encoded >> 8) == OP_HOLO_MUX_X4); +} + +test "decode_holo_mux_x4" { + given decoded = decode_holo_mux_x4(0xE6A8) + try std.testing.expect(decoded.layer == 0x02); + try std.testing.expect(decoded.phase == 0x01); + try std.testing.expect(decoded.frozen == true); +} + +test "opcode_constant" { + try std.testing.expect(OP_HOLO_MUX_X4 == 0xE6); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant num_inputs_four + assert NUM_INPUTS == 4 + +invariant data_width_sixteen + assert DATA_WIDTH == 16 + +invariant select_bits_two + assert SELECT_BITS == 2 + +invariant holo_layer_values + try std.testing.expect(HOLO_LAYER_NORTH >= 0 and HOLO_LAYER_WEST <= 3); + +invariant holo_phase_values + try std.testing.expect(HOLO_PHASE_0 >= 0 and HOLO_PHASE_270 <= 3); + +invariant holo_valid_one + assert HOLO_VALID == 1 + +invariant holo_invalid_zero + assert HOLO_INVALID == 0 + +invariant holo_phase_90_deg_cyclic + assert holo_phase_90_deg(3) == 0 + +invariant holo_phase_180_deg_cyclic + assert holo_phase_180_deg(2) == 0 + +invariant holo_opposite_layer_twice + assert holo_opposite_layer(holo_opposite_layer(HOLO_LAYER_NORTH)) == HOLO_LAYER_NORTH + +invariant holo_phase_opposite_twice + assert holo_phase_opposite(holo_phase_opposite(0)) == 0 + +invariant holo_mux_count_valid_bound + given inputs = [_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4 + assert holo_mux_count_valid(inputs) <= NUM_INPUTS + +invariant holo_mux_any_implies_count_positive + given inputs = [_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4 + try std.testing.expect(any_valid = holo_mux_any_valid(inputs)); + try std.testing.expect(count = holo_mux_count_valid(inputs)); + assert not any_valid or count > 0 + +invariant holo_mux_all_valid_equals_count + given inputs = [_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4 + assert holo_mux_all_valid(inputs) == (holo_mux_count_valid(inputs) == NUM_INPUTS) + +invariant holo_mux_valid_mask_bits + given inputs = [_]HoloInput{ + .{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x2000, .layer = 1, .phase = 0, .valid = false, .phi_score = 100}, + .{.data = 0x3000, .layer = 2, .phase = 0, .valid = true, .phi_score = 100}, + .{.data = 0x4000, .layer = 3, .phase = 0, .valid = false, .phi_score = 100}, + } + try std.testing.expect(mask = holo_mux_valid(inputs)); + try std.testing.expect(mask < (@as(u8, 1) << NUM_INPUTS)); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench holo_mux_select_latency + measure: nanoseconds to holo_mux_select([_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4, 2, .{.phi_optimized = false, .priority_select = false, .layer_frozen = false, .default_input = 0}) + target: < 50ns + +bench holo_mux_best_latency + measure: nanoseconds to holo_mux_best([_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4, .{.phi_optimized = true, .priority_select = false, .layer_frozen = false, .default_input = 0}) + target: < 100ns + +bench holo_mux_count_valid_latency + measure: nanoseconds to holo_mux_count_valid([_]HoloInput{.{.data = 0x1000, .layer = 0, .phase = 0, .valid = true, .phi_score = 100}} ** 4) + target: < 50ns + +bench encode_holo_mux_x4_latency + measure: nanoseconds to encode_holo_mux_x4(0x02, 0x01, true) + target: < 20ns + +bench decode_holo_mux_x4_latency + measure: nanoseconds to decode_holo_mux_x4(0xE6A8) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/int4.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/int4.t27 new file mode 100644 index 0000000000..6d4cab516d --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/int4.t27 @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/int4.t27 +// Int4 - 4-bit signed integer +// NUMERIC-STANDARD-001 Agent 9 (P1) + +module Int4 { + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // Int4 bit layout: S(3) - 4-bit two's complement + // S: 4 bits (signed 4-bit integer) + // + // Range: -8 to 7 + // 16 possible values + + const BITS : u8 = 4; + + // 2. Int4 Type + + struct Int4 { + raw : u4, // 4-bit raw value (two's complement) + } + + // 3. Encoding/Decoding + + // Encode i8 to Int4 (clamp to range) + fn encode(value: i8) -> Int4 { + const clamped = if (value > 7) { 7 } else if (value < -8) { -8 } else { value }; + // Two's complement: negative values are stored as 2^4 + value + let raw = clamped as u4; + if (clamped < 0) { + raw = (16 + clamped) as u4; + } + return Int4{ raw = raw }; + } + + // Decode Int4 to i8 + fn decode(i: Int4) -> i8 { + const raw = i.raw as i8; + // Two's complement: if sign bit set, negative + if (raw >= 8) { + return raw - 16; + } + return raw; + } + + // 4. Format Properties + + fn min_value() -> i8 { + return -8; + } + + fn max_value() -> i8 { + return 7; + } + + fn range() -> i8 { + return max_value() - min_value(); + } + + // 5. Validation + + fn validate_format() -> bool { + return BITS == 4; + } + + // 6. Use Cases + + // Int4 is optimal for: + // - Extreme quantization (87.5% smaller than INT8) + // - Binary classification (0/1) + // - Ternary classification (-1, 0, +1) + // - Activation masks + // - Control signals + + // Memory: 4 bits = 0.5 bytes (8x INT32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 4.0 / 32.0; // 0.125 + + // 7. Arithmetic Operations + + fn add(a: Int4, b: Int4) -> Int4 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a + val_b; + return encode(result); + } + + fn sub(a: Int4, b: Int4) -> Int4 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a - val_b; + return encode(result); + } + + fn mul(a: Int4, b: Int4) -> Int4 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a * val_b; + return encode(result); + } + + fn neg(i: Int4) -> Int4 { + return encode(-decode(i)); + } + + fn abs(i: Int4) -> Int4 { + const val = decode(i); + return encode(if (val < 0) { -val } else { val }); + } + + // 8. Comparison Operations + + fn eq(a: Int4, b: Int4) -> bool { + return decode(a) == decode(b); + } + + fn ne(a: Int4, b: Int4) -> bool { + return decode(a) != decode(b); + } + + fn lt(a: Int4, b: Int4) -> bool { + return decode(a) < decode(b); + } + + fn le(a: Int4, b: Int4) -> bool { + return decode(a) <= decode(b); + } + + fn gt(a: Int4, b: Int4) -> bool { + return decode(a) > decode(b); + } + + fn ge(a: Int4, b: Int4) -> bool { + return decode(a) >= decode(b); + } + + // TDD-Inside-Spec: Tests and Invariants for Int4 + + test int4_decode_zero + given i = Int4{ raw = 0 } + when value = decode(i) + then value == 0 + + test int4_encode_zero_roundtrip + given original = 0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test int4_encode_positive_values + for (vals) |vals| + then encode(vals[i]).raw == vals[i] for vals[i] in {0, 1, 2, 3, 4, 5, 6, 7} + + test int4_decode_positive_values + for (vals) |vals| + then decode(Int4{ raw = vals[i] }) == vals[i] for vals[i] in {0, 1, 2, 3, 4, 5, 6, 7} + + test int4_encode_negative_values + given vals = [-1, -2, -3, -4, -5, -6, -7, -8] + then encode(vals[i]).raw == (16 + vals[i]) for vals[i] in vals + + test int4_decode_negative_values + given enc = [15, 14, 13, 12, 11, 10, 9, 8] + then decode(Int4{ raw = enc[i] }) == enc[i] - 16 for enc[i] in enc + + test int4_bits_constant + then BITS == 4 + + test int4_max_value + given max_val = max_value() + then max_val == 7 + + test int4_min_value + given min_val = min_value() + then min_val == -8 + + test int4_range + given range_val = range() + then range_val == 15 + + test int4_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.125) < 0.01 + + test int4_validate_format_success + given valid = validate_format() + then valid == true + + test int4_add_positive + given a = Int4{ raw = 3 } + and b = Int4{ raw = 4 } + and result = add(a, b) + then decode(result) == 7 + + test int4_add_overflow + given a = Int4{ raw = 4 } + and b = Int4{ raw = 4 } + and result = add(a, b) + then decode(result) == 7 + + test int4_sub_positive + given a = Int4{ raw = 7 } + and b = Int4{ raw = 3 } + and result = sub(a, b) + then decode(result) == 4 + + test int4_sub_underflow + given a = Int4{ raw = 1 } + and b = Int4{ raw = 5 } + and result = sub(a, b) + then decode(result) == -4 + + test int4_mul_positive + given a = Int4{ raw = 2 } + and b = Int4{ raw = 3 } + and result = mul(a, b) + then decode(result) == 6 + + test int4_mul_negative + given a = Int4{ raw = 3 } + and b = encode(-2) + and result = mul(a, b) + then decode(result) == -6 + + test int4_neg_positive + given i = Int4{ raw = 5 } + and result = neg(i) + then decode(result) == -5 + + test int4_neg_negative + given i = encode(-3) + and result = neg(i) + then decode(result) == 3 + + test int4_abs_positive + given i = Int4{ raw = 5 } + and result = abs(i) + then decode(result) == 5 + + test int4_abs_negative + given i = encode(-3) + and result = abs(i) + then decode(result) == 3 + + test int4_eq_true + given a = Int4{ raw = 3 } + and b = Int4{ raw = 3 } + then eq(a, b) == true + + test int4_eq_false + given a = Int4{ raw = 3 } + and b = Int4{ raw = 4 } + then eq(a, b) == false + + test int4_lt_true + given a = Int4{ raw = 2 } + and b = Int4{ raw = 5 } + then lt(a, b) == true + + test int4_lt_false + given a = Int4{ raw = 5 } + and b = Int4{ raw = 2 } + then lt(a, b) == false + + test int4_encode_clamps_above_max + given original = 10 + and encoded = encode(original) + then decode(encoded) == 7 + + test int4_encode_clamps_below_min + given original = -10 + and encoded = encode(original) + then decode(encoded) == -8 + + invariant int4_bits_constant + assert BITS == 4 + + invariant int4_max_value_is_7 + assert max_value() == 7 + + invariant int4_min_value_is_neg_8 + assert min_value() == -8 + + invariant int4_range_is_15 + assert range() == 15 + + invariant int4_encode_preserves_zero + assert encode(0).raw == 0 + + invariant int4_decode_preserves_zero + assert decode(Int4{raw = 0}) == 0 + + invariant int4_add_is_commutative + given a = 3 and b = 2 + then decode(add(encode(a), encode(b))) == decode(add(encode(b), encode(a))) + + invariant int4_zero_add_identity + given x = 5 + then decode(add(encode(x), encode(0))) == x + + invariant int4_neg_neg_is_identity + given x = 5 + then decode(neg(neg(encode(x)))) == x + + invariant int4_mul_by_neg_one_is_neg + given x = 5 + then decode(mul(encode(x), encode(-1))) == -x + + invariant int4_abs_is_non_negative + for (vals) |vals| + then decode(abs(encode(vals[i]))) >= 0 for vals[i] in vals + + bench int4_encode_latency + measure: nanoseconds to encode(5) + target: < 20ns + + bench int4_decode_latency + measure: nanoseconds to decode(Int4{ raw = 5 }) + target: < 10ns + + bench int4_encode_decode_roundtrip + measure: nanoseconds to encode(5) and decode(encode(5)) + target: < 30ns + + bench int4_add_latency + measure: nanoseconds to add(Int4{ raw = 3 }, Int4{ raw = 4 }) + target: < 30ns + + bench int4_mul_latency + measure: nanoseconds to mul(Int4{ raw = 3 }, Int4{ raw = 2 }) + target: < 40ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/int8.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/int8.t27 new file mode 100644 index 0000000000..c8daff4905 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/int8.t27 @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/int8.t27 +// Int8 - 8-bit signed integer +// NUMERIC-STANDARD-001 Agent 10 (P1) + +module Int8 { + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // Int8 bit layout: S(7) - 8-bit two's complement + // S: 8 bits (signed 8-bit integer) + // + // Range: -128 to 127 + // 256 possible values + + const BITS : u8 = 8; + + // 2. Int8 Type + + struct Int8 { + raw : u8, // 8-bit raw value (two's complement) + } + + // 3. Encoding/Decoding + + // Encode i32 to Int8 (clamp to range) + fn encode(value: i32) -> Int8 { + const clamped = if (value > 127) { 127 } else if (value < -128) { -128 } else { value }; + // Two's complement: negative values are stored as 2^8 + value + let raw = clamped as u8; + if (clamped < 0) { + raw = (256 + clamped) as u8; + } + return Int8{ raw = raw }; + } + + // Decode Int8 to i32 + fn decode(i: Int8) -> i32 { + const raw = i.raw as i32; + // Two's complement: if sign bit set, negative + if (raw >= 128) { + return raw - 256; + } + return raw; + } + + // 4. Format Properties + + fn min_value() -> i32 { + return -128; + } + + fn max_value() -> i32 { + return 127; + } + + fn range() -> i32 { + return max_value() - min_value(); + } + + fn num_values() -> i32 { + return 256; + } + + // 5. Validation + + fn validate_format() -> bool { + return BITS == 8; + } + + // 6. Use Cases + + // Int8 is optimal for: + // - Standard integer quantization (75% smaller than INT32) + // - Neural network weight quantization + // - Activation caching + // - General-purpose integer arithmetic + // - Control and status registers + + // Memory: 8 bits = 1 byte (4x INT32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 8.0 / 32.0; // 0.25 + + // 7. Arithmetic Operations + + fn add(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a + val_b; + return encode(result); + } + + fn sub(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a - val_b; + return encode(result); + } + + fn mul(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = val_a * val_b; + return encode(result); + } + + fn div(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = if (val_b != 0) { val_a / val_b } else { 0 }; + return encode(result); + } + + fn mod(a: Int8, b: Int8) -> Int8 { + const val_a = decode(a); + const val_b = decode(b); + const result = if (val_b != 0) { val_a % val_b } else { 0 }; + return encode(result); + } + + fn neg(i: Int8) -> Int8 { + return encode(-decode(i)); + } + + fn abs(i: Int8) -> Int8 { + const val = decode(i); + return encode(if (val < 0) { -val } else { val }); + } + + // 8. Comparison Operations + + fn eq(a: Int8, b: Int8) -> bool { + return decode(a) == decode(b); + } + + fn ne(a: Int8, b: Int8) -> bool { + return decode(a) != decode(b); + } + + fn lt(a: Int8, b: Int8) -> bool { + return decode(a) < decode(b); + } + + fn le(a: Int8, b: Int8) -> bool { + return decode(a) <= decode(b); + } + + fn gt(a: Int8, b: Int8) -> bool { + return decode(a) > decode(b); + } + + fn ge(a: Int8, b: Int8) -> bool { + return decode(a) >= decode(b); + } + + // 9. Bitwise Operations + + fn and(a: Int8, b: Int8) -> Int8 { + return Int8{ raw = a.raw & b.raw }; + } + + fn or(a: Int8, b: Int8) -> Int8 { + return Int8{ raw = a.raw | b.raw }; + } + + fn xor(a: Int8, b: Int8) -> Int8 { + return Int8{ raw = a.raw ^ b.raw }; + } + + fn not(i: Int8) -> Int8 { + return Int8{ raw = i.raw ^ 0xFF }; + } + + fn shl(a: Int8, shift: u8) -> Int8 { + const clamped_shift = if (shift > 7) { 7 } else { shift }; + return Int8{ raw = (a.raw << clamped_shift) & 0xFF }; + } + + fn shr(a: Int8, shift: u8) -> Int8 { + const clamped_shift = if (shift > 7) { 7 } else { shift }; + // Arithmetic shift for negative numbers + const val = decode(a); + const result = val >> (clamped_shift as i32); + return encode(result); + } + + // TDD-Inside-Spec: Tests and Invariants for Int8 + + test int8_decode_zero + given i = Int8{ raw = 0 } + when value = decode(i) + then value == 0 + + test int8_encode_zero_roundtrip + given original = 0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test int8_encode_positive_values + for (vals) |vals| + then encode(vals[i]).raw == vals[i] for vals[i] in {0, 1, 2, 50, 100, 127} + + test int8_decode_positive_values + for (vals) |vals| + then decode(Int8{ raw = vals[i] }) == vals[i] for vals[i] in {0, 1, 2, 50, 100, 127} + + test int8_encode_negative_values + given vals = [-1, -2, -50, -100, -127, -128] + then encode(vals[i]).raw == (256 + vals[i]) for vals[i] in vals + + test int8_decode_negative_values + given enc = [255, 254, 206, 156, 129, 128] + then decode(Int8{ raw = enc[i] }) == enc[i] - 256 for enc[i] in enc + + test int8_bits_constant + then BITS == 8 + + test int8_max_value + given max_val = max_value() + then max_val == 127 + + test int8_min_value + given min_val = min_value() + then min_val == -128 + + test int8_range + given range_val = range() + then range_val == 255 + + test int8_num_values + given num = num_values() + then num == 256 + + test int8_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.25) < 0.01 + + test int8_validate_format_success + given valid = validate_format() + then valid == true + + test int8_add_positive + given a = Int8{ raw = 50 } + and b = Int8{ raw = 60 } + and result = add(a, b) + then decode(result) == 110 + + test int8_add_overflow + given a = Int8{ raw = 100 } + and b = Int8{ raw = 50 } + and result = add(a, b) + then decode(result) == 127 + + test int8_sub_positive + given a = Int8{ raw = 100 } + and b = Int8{ raw = 30 } + and result = sub(a, b) + then decode(result) == 70 + + test int8_sub_underflow + given a = Int8{ raw = 50 } + and b = encode(100) + and result = sub(a, b) + then decode(result) == -50 + + test int8_mul_positive + given a = Int8{ raw = 10 } + and b = Int8{ raw = 12 } + and result = mul(a, b) + then decode(result) == 120 + + test int8_mul_overflow + given a = Int8{ raw = 20 } + and b = Int8{ raw = 20 } + and result = mul(a, b) + then decode(result) == 127 + + test int8_div_positive + given a = Int8{ raw = 100 } + and b = Int8{ raw = 25 } + and result = div(a, b) + then decode(result) == 4 + + test int8_div_by_zero + given a = Int8{ raw = 100 } + and b = Int8{ raw = 0 } + and result = div(a, b) + then decode(result) == 0 + + test int8_mod_positive + given a = Int8{ raw = 13 } + and b = Int8{ raw = 5 } + and result = mod(a, b) + then decode(result) == 3 + + test int8_mod_by_zero + given a = Int8{ raw = 13 } + and b = Int8{ raw = 0 } + and result = mod(a, b) + then decode(result) == 0 + + test int8_neg_positive + given i = Int8{ raw = 50 } + and result = neg(i) + then decode(result) == -50 + + test int8_neg_negative + given i = encode(-50) + and result = neg(i) + then decode(result) == 50 + + test int8_neg_zero + given i = Int8{ raw = 0 } + and result = neg(i) + then decode(result) == 0 + + test int8_abs_positive + given i = Int8{ raw = 50 } + and result = abs(i) + then decode(result) == 50 + + test int8_abs_negative + given i = encode(-50) + and result = abs(i) + then decode(result) == 50 + + test int8_abs_zero + given i = Int8{ raw = 0 } + and result = abs(i) + then decode(result) == 0 + + test int8_eq_true + given a = Int8{ raw = 50 } + and b = Int8{ raw = 50 } + then eq(a, b) == true + + test int8_eq_false + given a = Int8{ raw = 50 } + and b = Int8{ raw = 51 } + then eq(a, b) == false + + test int8_lt_true + given a = Int8{ raw = 20 } + and b = Int8{ raw = 50 } + then lt(a, b) == true + + test int8_lt_false + given a = Int8{ raw = 50 } + and b = Int8{ raw = 20 } + then lt(a, b) == false + + test int8_and_operation + given a = Int8{ raw = 0x55 } + and b = Int8{ raw = 0xAA } + and result = and(a, b) + then result.raw == 0x00 + + test int8_or_operation + given a = Int8{ raw = 0x55 } + and b = Int8{ raw = 0xAA } + and result = or(a, b) + then result.raw == 0xFF + + test int8_xor_operation + given a = Int8{ raw = 0x55 } + and b = Int8{ raw = 0xAA } + and result = xor(a, b) + then result.raw == 0xFF + + test int8_not_operation + given i = Int8{ raw = 0x55 } + and result = not(i) + then result.raw == 0xAA + + test int8_shl_left + given a = Int8{ raw = 0x01 } + and result = shl(a, 3) + then result.raw == 0x08 + + test int8_shl_clamp + given a = Int8{ raw = 0x01 } + and result = shl(a, 10) + then result.raw == 0x80 + + test int8_shr_right + given a = Int8{ raw = 0x80 } + and result = shr(a, 2) + then decode(result) == -32 + + test int8_encode_clamps_above_max + given original = 200 + and encoded = encode(original) + then decode(encoded) == 127 + + test int8_encode_clamps_below_min + given original = -200 + and encoded = encode(original) + then decode(encoded) == -128 + + test int8_all_values_encode_decode + for (i in 0..255) + then decode(encode(i - 128)) == (i - 128) + + invariant int8_bits_constant + assert BITS == 8 + + invariant int8_max_value_is_127 + assert max_value() == 127 + + invariant int8_min_value_is_neg_128 + assert min_value() == -128 + + invariant int8_range_is_255 + assert range() == 255 + + invariant int8_num_values_is_256 + assert num_values() == 256 + + invariant int8_encode_preserves_zero + assert encode(0).raw == 0 + + invariant int8_decode_preserves_zero + assert decode(Int8{raw = 0}) == 0 + + invariant int8_add_is_commutative + given a = 50 and b = 30 + then decode(add(encode(a), encode(b))) == decode(add(encode(b), encode(a))) + + invariant int8_zero_add_identity + for (x in {0, 50, 100, 127}) + then decode(add(encode(x), encode(0))) == x + + invariant int8_sub_self_is_zero + for (x in {0, 50, 100, 127}) + then decode(sub(encode(x), encode(x))) == 0 + + invariant int8_mul_by_neg_one_is_neg + for (x in {0, 50, 100, 127}) + then decode(mul(encode(x), encode(-1))) == -x + + invariant int8_mul_by_zero_is_zero + for (x in {0, 50, 100, 127}) + then decode(mul(encode(x), encode(0))) == 0 + + invariant int8_mul_by_one_is_identity + for (x in {0, 50, 100, 127}) + then decode(mul(encode(x), encode(1))) == x + + invariant int8_neg_neg_is_identity + for (x in {0, 50, 100, 127}) + then decode(neg(neg(encode(x)))) == x + + invariant int8_abs_is_non_negative + for (x in {-128..127}) + then decode(abs(encode(x))) >= 0 + + invariant int8_abs_preserves_zero + assert decode(abs(Int8{raw = 0})) == 0 + + invariant int8_shl_zero_is_zero + for (shift in {0, 1, 7}) + then shl(Int8{raw = 1}, shift).raw == 1 << shift + + invariant int8_shr_by_max_is_zero_or_neg_one + given i = Int8{ raw = 1 } + then shr(i, 7).raw == 0 + + bench int8_encode_latency + measure: nanoseconds to encode(50) + target: < 20ns + + bench int8_decode_latency + measure: nanoseconds to decode(Int8{ raw = 50 }) + target: < 10ns + + bench int8_encode_decode_roundtrip + measure: nanoseconds to encode(50) and decode(encode(50)) + target: < 30ns + + bench int8_add_latency + measure: nanoseconds to add(Int8{ raw = 30 }, Int8{ raw = 40 }) + target: < 30ns + + bench int8_mul_latency + measure: nanoseconds to mul(Int8{ raw = 10 }, Int8{ raw = 10 }) + target: < 30ns + + bench int8_div_latency + measure: nanoseconds to div(Int8{ raw = 100 }, Int8{ raw = 10 }) + target: < 30ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/lane_l_precheck.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/lane_l_precheck.t27 new file mode 100644 index 0000000000..f5462d9980 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/lane_l_precheck.t27 @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: Apache-2.0 +; lane_l_precheck.t27 — Sacred Opcode 0xDF: LUT Lookup Precheck +; Hardware pre-check for LUT lookup operations +; Validates lane readiness, checks LUT access permissions, and prepares address +; φ² + 1/φ² = 3 | TRINITY + +module sacred-lane_precheck; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_LUT_LOOKUP : u8 = 0xDF; + +pub const LANE_COUNT : u8 = 8; +pub const LUT_DEPTH : u16 = 256; +pub const LUT_ADDR_BITS : u8 = 8; +pub const LANE_ID_BITS : u8 = 3; + +pub const LANE_IDLE : u8 = 0; +pub const LANE_READY : u8 = 1; +pub const LANE_BUSY : u8 = 2; +pub const LANE_ERROR : u8 = 3; + +pub const CHECK_PASS : u8 = 1; +pub const CHECK_FAIL : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const LaneStatus = enum(u8) { + idle = LANE_IDLE, + ready = LANE_READY, + busy = LANE_BUSY, + error = LANE_ERROR, +} + +pub const PrecheckFlags = packed struct(u8) { + lut_enabled : u1, + lane_valid : u1, + address_valid : u1, + permission_ok : u1, + reserved : u4, +} + +pub const PrecheckResult = struct { + lane_id : u8, + status : LaneStatus, + flags : PrecheckFlags, + lut_address : u8, + error_code : u8, +} + +pub const LaneConfig = struct { + enabled : bool, + lut_base : u8, + lut_mask : u8, + priority : u8, +} + +// ============================================================================ +// Lane Status Functions +// ============================================================================ + +// lane_is_valid(lane_id: u8) -> bool +// Check if lane ID is valid +pub fn lane_is_valid(lane_id: u8) bool { + return lane_id < LANE_COUNT; +} + +// lane_is_ready(status: LaneStatus) -> bool +// Check if lane is ready for operation +pub fn lane_is_ready(status: LaneStatus) bool { + return status == LaneStatus.ready; +} + +// lane_is_busy(status: LaneStatus) -> bool +// Check if lane is busy +pub fn lane_is_busy(status: LaneStatus) bool { + return status == LaneStatus.busy; +} + +// lane_is_error(status: LaneStatus) -> bool +// Check if lane is in error state +pub fn lane_is_error(status: LaneStatus) bool { + return status == LaneStatus.error; +} + +// lane_is_idle(status: LaneStatus) -> bool +// Check if lane is idle +pub fn lane_is_idle(status: LaneStatus) bool { + return status == LaneStatus.idle; +} + +// ============================================================================ +// Precheck Functions +// ============================================================================ + +// precheck_lane(lane_id: u8, lut_addr: u8, config: LaneConfig) -> PrecheckResult +// Perform precheck on a lane for LUT lookup +pub fn precheck_lane(lane_id: u8, lut_addr: u8, config: LaneConfig) PrecheckResult { + var flags : PrecheckFlags = undefined; + flags.lut_enabled = if (config.enabled) 1 else 0; + flags.lane_valid = if (lane_is_valid(lane_id)) 1 else 0; + flags.address_valid = if (lut_addr < LUT_DEPTH) 1 else 0; + flags.permission_ok = if (config.enabled and ((lut_addr & config.lut_mask) < config.lut_mask)) 1 else 0; + flags.reserved = 0; + + var status = LaneStatus.ready; + var error_code : u8 = 0; + + if (flags.lane_valid == 0) { + status = LaneStatus.error; + error_code = 1; // Invalid lane ID + } else if (flags.address_valid == 0) { + status = LaneStatus.error; + error_code = 2; // Invalid address + } else if (flags.permission_ok == 0) { + status = LaneStatus.error; + error_code = 3; // Permission denied + } else if (flags.lut_enabled == 0) { + status = LaneStatus.error; + error_code = 4; // LUT not enabled + } + + return PrecheckResult { + .lane_id = lane_id, + .status = status, + .flags = flags, + .lut_address = if (status == LaneStatus.ready) lut_addr else 0, + .error_code = error_code, + }; +} + +// precheck_all_lanes(lane_bits: u8, lut_addr: u8, configs: [LANE_COUNT]LaneConfig) -> [LANE_COUNT]PrecheckResult +// Precheck all lanes simultaneously +pub fn precheck_all_lanes(lane_bits: u8, lut_addr: u8, configs: [LANE_COUNT]LaneConfig) [LANE_COUNT]PrecheckResult { + var results : [LANE_COUNT]PrecheckResult = undefined; + for (0..LANE_COUNT) |i| { + if ((lane_bits >> i) & 1 != 0) { + results[i] = precheck_lane(@as(u8, @intCast(i)), lut_addr, configs[i]); + } else { + results[i] = PrecheckResult { + .lane_id = @as(u8, @intCast(i)), + .status = LaneStatus.idle, + .flags = PrecheckFlags {.lut_enabled = 0, .lane_valid = 1, .address_valid = 0, .permission_ok = 0, .reserved = 0}, + .lut_address = 0, + .error_code = 0, + }; + } + } + return results; +} + +// precheck_pass(result: PrecheckResult) -> bool +// Check if precheck passed +pub fn precheck_pass(result: PrecheckResult) bool { + return result.status == LaneStatus.ready and result.flags.permission_ok != 0; +} + +// precheck_fail(result: PrecheckResult) -> bool +// Check if precheck failed +pub fn precheck_fail(result: PrecheckResult) bool { + return result.status == LaneStatus.error or result.flags.permission_ok == 0; +} + +// precheck_any_pass(results: [LANE_COUNT]PrecheckResult) -> bool +// Check if any lane precheck passed +pub fn precheck_any_pass(results: [LANE_COUNT]PrecheckResult) bool { + for (results) |result| { + if (precheck_pass(result)) { + return true; + } + } + return false; +} + +// precheck_all_pass(results: [LANE_COUNT]PrecheckResult) -> bool +// Check if all lane prechecks passed +pub fn precheck_all_pass(results: [LANE_COUNT]PrecheckResult) bool { + for (results) |result| { + if (precheck_fail(result)) { + return false; + } + } + return true; +} + +// precheck_count_passed(results: [LANE_COUNT]PrecheckResult) -> u8 +// Count number of lanes that passed precheck +pub fn precheck_count_passed(results: [LANE_COUNT]PrecheckResult) u8 { + var count : u8 = 0; + for (results) |result| { + if (precheck_pass(result)) { + count += 1; + } + } + return count; +} + +// precheck_get_ready_lanes(results: [LANE_COUNT]PrecheckResult) -> u8 +// Get bitmask of ready lanes +pub fn precheck_get_ready_lanes(results: [LANE_COUNT]PrecheckResult) -> u8 { + var mask : u8 = 0; + for (results, 0..) |result, i| { + if (precheck_pass(result)) { + mask |= (@as(u8, 1) << i); + } + } + return mask; +} + +// ============================================================================ +// LUT Address Functions +// ============================================================================ + +// lut_address_valid(addr: u8, config: LaneConfig) -> bool +// Check if LUT address is valid for given config +pub fn lut_address_valid(addr: u8, config: LaneConfig) bool { + if (addr >= LUT_DEPTH) { + return false; + } + if (not config.enabled) { + return false; + } + return (addr & config.lut_mask) < config.lut_mask; +} + +// lut_address_masked(addr: u8, config: LaneConfig) -> u8 +// Get masked LUT address +pub fn lut_address_masked(addr: u8, config: LaneConfig) -> u8 { + return addr & config.lut_mask; +} + +// lut_address_offset(addr: u8, base: u8) -> u8 +// Get offset address from base +pub fn lut_address_offset(addr: u8, base: u8) -> u8 { + if (addr >= base) { + return addr - base; + } + return 0; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_lut_lookup(lane_bits: u8, lut_addr: u8) -> u16 +// Encode LUT lookup instruction +pub fn encode_lut_lookup(lane_bits: u8, lut_addr: u8) u16 { + // Format: [OP:8][LANE:3][ADDR:5] + const op : u16 = @as(u16, OP_LUT_LOOKUP) << 8; + const lane : u16 = @as(u16, lane_bits & 0x07) << 5; + const addr : u16 = @as(u16, lut_addr & 0x1F); + return op | lane | addr; +} + +// decode_lut_lookup(encoded: u16) -> struct { lane_bits: u8, lut_addr: u8 } +// Decode LUT lookup instruction +pub fn decode_lut_lookup(encoded: u16) struct { lane_bits: u8, lut_addr: u8 } { + const lane_bits : u8 = @as(u8, @truncate((encoded >> 5) & 0x07)); + const lut_addr : u8 = @as(u8, @truncate(encoded & 0x1F)); + return .{ .lane_bits = lane_bits, .lut_addr = lut_addr }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "lane_is_valid_true" { + try std.testing.expect(lane_is_valid(0) == true); + try std.testing.expect(lane_is_valid(7) == true); +} + +test "lane_is_valid_false" { + try std.testing.expect(lane_is_valid(8) == false); + try std.testing.expect(lane_is_valid(255) == false); +} + +test "lane_is_ready_true" { + try std.testing.expect(lane_is_ready(LaneStatus.ready) == true); +} + +test "lane_is_ready_false" { + try std.testing.expect(lane_is_ready(LaneStatus.idle) == false); + try std.testing.expect(lane_is_ready(LaneStatus.busy) == false); + try std.testing.expect(lane_is_ready(LaneStatus.error) == false); +} + +test "lane_is_busy_true" { + try std.testing.expect(lane_is_busy(LaneStatus.busy) == true); +} + +test "lane_is_error_true" { + try std.testing.expect(lane_is_error(LaneStatus.error) == true); +} + +test "precheck_lane_pass" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 128, config)); + try std.testing.expect(precheck_pass(result) == true); + try std.testing.expect(result.error_code == 0); +} + +test "precheck_lane_invalid_lane" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(8, 128, config)); + try std.testing.expect(precheck_fail(result) == true); + try std.testing.expect(result.error_code == 1); +} + +test "precheck_lane_invalid_address" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 256, config)); + try std.testing.expect(precheck_fail(result) == true); + try std.testing.expect(result.error_code == 2); +} + +test "precheck_lane_permission_denied" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 0x0F, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 0x80, config)); + try std.testing.expect(precheck_fail(result) == true); + try std.testing.expect(result.error_code == 3); +} + +test "precheck_lane_lut_disabled" { + given config = LaneConfig{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 128, config)); + try std.testing.expect(precheck_fail(result) == true); + try std.testing.expect(result.error_code == 4); +} + +test "precheck_any_pass_true" { + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000011, 128, configs)); + try std.testing.expect(precheck_any_pass(results) == true); +} + +test "precheck_any_pass_false" { + given configs = [_]LaneConfig{ + .{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000011, 128, configs)); + try std.testing.expect(precheck_any_pass(results) == false); +} + +test "precheck_all_pass_true" { + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000011, 128, configs)); + try std.testing.expect(precheck_all_pass(results) == true); +} + +test "precheck_count_passed" { + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000111, 128, configs)); + try std.testing.expect(precheck_count_passed(results) == 2); +} + +test "precheck_get_ready_lanes" { + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0b00000111, 128, configs)); + try std.testing.expect(precheck_get_ready_lanes(results) == 0b00000101); +} + +test "lut_address_valid_true" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(lut_address_valid(128, config) == true); +} + +test "lut_address_valid_disabled" { + given config = LaneConfig{.enabled = false, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(lut_address_valid(128, config) == false); +} + +test "lut_address_valid_masked" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 0x0F, .priority = 0} + try std.testing.expect(lut_address_valid(0x08, config) == true); + try std.testing.expect(lut_address_valid(0x80, config) == false); +} + +test "lut_address_masked_value" { + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 0x0F, .priority = 0} + try std.testing.expect(lut_address_masked(0xAB, config) == 0x0B); +} + +test "lut_address_offset" { + try std.testing.expect(lut_address_offset(20, 10) == 10); + try std.testing.expect(lut_address_offset(5, 10) == 0); +} + +test "encode_lut_lookup" { + given encoded = encode_lut_lookup(0b00000101, 0x10) + try std.testing.expect((encoded >> 8) == OP_LUT_LOOKUP); +} + +test "decode_lut_lookup" { + given decoded = decode_lut_lookup(0xDF50) + try std.testing.expect(decoded.lane_bits == 0b00000010); + try std.testing.expect(decoded.lut_addr == 0x10); +} + +test "opcode_constant" { + try std.testing.expect(OP_LUT_LOOKUP == 0xDF); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant lane_count_eight + assert LANE_COUNT == 8 + +invariant lut_depth_256 + assert LUT_DEPTH == 256 + +invariant lut_addr_bits_eight + assert LUT_ADDR_BITS == 8 + +invariant lane_id_bits_three + assert LANE_ID_BITS == 3 + +invariant check_pass_one + assert CHECK_PASS == 1 + +invariant check_fail_zero + assert CHECK_FAIL == 0 + +invariant lane_valid_range + assert lane_is_valid(0) == true and lane_is_valid(7) == true and lane_is_valid(8) == false + +invariant precheck_pass_and_fail_opposite + given config = LaneConfig{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0} + try std.testing.expect(result = precheck_lane(0, 128, config)); + assert precheck_pass(result) != precheck_fail(result) + +invariant ready_lane_not_busy + try std.testing.expect(lane_is_ready(LaneStatus.ready) != lane_is_busy(LaneStatus.ready)); + +invariant error_lane_not_ready + try std.testing.expect(lane_is_error(LaneStatus.error) != lane_is_ready(LaneStatus.error)); + +invariant precheck_count_bound + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0xFF, 128, configs)); + assert precheck_count_passed(results) <= LANE_COUNT + +invariant ready_mask_bound + given configs = [_]LaneConfig{ + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}, + } + try std.testing.expect(results = precheck_all_lanes(0xFF, 128, configs)); + try std.testing.expect(precheck_get_ready_lanes(results) < (@as(u8, 1) << LANE_COUNT)); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench precheck_lane_latency + measure: nanoseconds to precheck_lane(0, 128, .{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}) + target: < 50ns + +bench precheck_all_lanes_latency + measure: nanoseconds to precheck_all_lanes(0xFF, 128, [_]LaneConfig{.{.enabled = true, .lut_base = 0, .lut_mask = 255, .priority = 0}} ** 8) + target: < 200ns + +bench encode_lut_lookup_latency + measure: nanoseconds to encode_lut_lookup(0b00000101, 0x10) + target: < 20ns + +bench decode_lut_lookup_latency + measure: nanoseconds to decode_lut_lookup(0xDF50) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/lut_npu_81_entry.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/lut_npu_81_entry.t27 new file mode 100644 index 0000000000..9aa2002684 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/lut_npu_81_entry.t27 @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: Apache-2.0 +; lut_npu_81_entry.t27 — Sacred Opcode 0xE3: LUT NPU 81-Entry Lookup +; Hardware LUT for NPU operations with 81 entries (9×9 transform) +; φ² + 1/φ² = 3 | TRINITY + +module sacred-lut_npu_81_entry; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_LUT_NPU : u8 = 0xE3; + +pub const LUT_SIZE : u8 = 81; // 9×9 = 81 entries +pub const LUT_ADDR_BITS : u8 = 7; // 7 bits for 0-80 +pub const LUT_VALUE_BITS : u8 = 16; + +pub const ENTRY_VALID : u8 = 1; +pub const ENTRY_INVALID : u8 = 0; + +pub const PHI_CONST_BASE : u8 = 0; +pub const CONST_LUT_END : u8 = 80; + +// ============================================================================ +// Types +// ============================================================================ + +pub const LutEntry = struct { + address : u8, + value : u16, + valid : bool, + phi_related : bool, +} + +pub const LutConfig = struct { + read_only : bool, + phi_optimized : bool, + cache_enabled : bool, +} + +pub const NpuOperation = enum(u8) { + lookup = 0, + transform = 1, + phi_project = 2, + sparse_reduce = 3, + quantize = 4, + dequantize = 5, + composite = 6, + custom = 7, +} + +pub const Transform9x9 = struct { + grid : [9][9]i8, + sparsity : u8, // 0-100% + phi_score : u16, // φ-distance from ideal +} + +// ============================================================================ +// LUT Entry Functions +// ============================================================================ + +// lut_entry_valid(entry: LutEntry) -> bool +// Check if LUT entry is valid +pub fn lut_entry_valid(entry: LutEntry) bool { + return entry.valid and entry.address < LUT_SIZE; +} + +// lut_entry_address_valid(addr: u8) -> bool +// Check if address is valid +pub fn lut_entry_address_valid(addr: u8) bool { + return addr < LUT_SIZE; +} + +// create_lut_entry(addr: u8, value: u16, phi_related: bool) -> LutEntry +// Create a LUT entry +pub fn create_lut_entry(addr: u8, value: u16, phi_related: bool) LutEntry { + return LutEntry { + .address = addr, + .value = value, + .valid = lut_entry_address_valid(addr), + .phi_related = phi_related, + }; +} + +// ============================================================================ +// NPU Operation Functions +// ============================================================================ + +// npu_transform_9x9(grid: [9][9]i8) -> Transform9x9 +// Analyze 9×9 transform +pub fn npu_transform_9x9(grid: [9][9]i8) Transform9x9 { + var zero_count : u8 = 0; + var phi_score : u16 = 0; + + // Count zeros and compute phi score + for (grid, 0..) |row, i| { + for (row, 0..) |val, j| { + if (val == 0) { + zero_count += 1; + } + // Simple phi score based on center-weight + const center_dist = if (i > 4) i - 4 else 4 - i; + const center_dist_j = if (j > 4) j - 4 else 4 - j; + phi_score += @as(u16, @intCast(val)) * @as(u16, @intCast(5 - center_dist - center_dist_j)); + } + } + + const sparsity = (zero_count * 100) / 81; + + return Transform9x9 { + .grid = grid, + .sparsity = sparsity, + .phi_score = phi_score, + }; +} + +// transform_is_sparse(transform: Transform9x9) -> bool +// Check if transform is sparse (>50% zeros) +pub fn transform_is_sparse(transform: Transform9x9) bool { + return transform.sparsity > 50; +} + +// transform_phi_optimized(transform: Transform9x9) -> bool +// Check if transform is phi-optimized (high phi score) +pub fn transform_phi_optimized(transform: Transform9x9) bool { + return transform.phi_score > 100; +} + +// ============================================================================ +// LUT Lookup Functions +// ============================================================================ + +// lut_lookup_base(lut: [LUT_SIZE]LutEntry, addr: u8) -> LutEntry +// Base LUT lookup +pub fn lut_lookup_base(lut: [LUT_SIZE]LutEntry, addr: u8) LutEntry { + if (not lut_entry_address_valid(addr)) { + return LutEntry{.address = 255, .value = 0, .valid = false, .phi_related = false}; + } + return lut[@as(u8, @intCast(addr))]; +} + +// lut_lookup_phi(lut: [LUT_SIZE]LutEntry, addr: u8) -> LutEntry +// Phi-optimized LUT lookup +pub fn lut_lookup_phi(lut: [LUT_SIZE]LutEntry, addr: u8) LutEntry { + if (not lut_entry_address_valid(addr)) { + return LutEntry{.address = 255, .value = 0, .valid = false, .phi_related = false}; + } + + const entry = lut[@as(u8, @intCast(addr))]; + if (entry.valid and entry.phi_related) { + return entry; + } + + // Fallback: search for nearest phi-related entry + for (0..LUT_SIZE) |i| { + const e = lut[i]; + if (e.valid and e.phi_related) { + return e; + } + } + + return entry; +} + +// lut_lookup_range(lut: [LUT_SIZE]LutEntry, start_addr: u8, count: u8) -> [LUT_SIZE]LutEntry +// Lookup range of entries +pub fn lut_lookup_range(lut: [LUT_SIZE]LutEntry, start_addr: u8, count: u8) [LUT_SIZE]LutEntry { + var result : [LUT_SIZE]LutEntry = undefined; + + for (0..LUT_SIZE) |i| { + if (i < count and lut_entry_address_valid(start_addr + i)) { + result[i] = lut[@as(u8, @intCast(start_addr + i))]; + } else { + result[i] = LutEntry{.address = 255, .value = 0, .valid = false, .phi_related = false}; + } + } + + return result; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_lut_npu(op: NpuOperation, addr: u8) -> u16 +// Encode LUT NPU instruction +pub fn encode_lut_npu(op: NpuOperation, addr: u8) u16 { + // Format: [OP:8][OPCODE:3][ADDR:5] + const op_field : u16 = @as(u16, OP_LUT_NPU) << 8; + const opcode_field : u16 = @as(u16, @intFromEnum(op)) << 5; + const addr_field : u16 = @as(u16, addr & 0x1F); + return op_field | opcode_field | addr_field; +} + +// decode_lut_npu(encoded: u16) -> struct { op: NpuOperation, addr: u8 } +// Decode LUT NPU instruction +pub fn decode_lut_npu(encoded: u16) struct { op: NpuOperation, addr: u8 } { + const opcode_val : u8 = @as(u8, @truncate((encoded >> 5) & 0x07)); + const addr : u8 = @as(u8, @truncate(encoded & 0x1F)); + + var op : NpuOperation = undefined; + switch (opcode_val) { + 0 => op = NpuOperation.lookup, + 1 => op = NpuOperation.transform, + 2 => op = NpuOperation.phi_project, + 3 => op = NpuOperation.sparse_reduce, + 4 => op = NpuOperation.quantize, + 5 => op = NpuOperation.dequantize, + 6 => op = NpuOperation.composite, + else => op = NpuOperation.custom, + } + + return .{ .op = op, .addr = addr }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "lut_size_eighty_one" { + try std.testing.expect(LUT_SIZE == 81); +} + +test "lut_entry_address_valid_true" { + try std.testing.expect(lut_entry_address_valid(0) == true); + try std.testing.expect(lut_entry_address_valid(80) == true); +} + +test "lut_entry_address_valid_false" { + try std.testing.expect(lut_entry_address_valid(81) == false); + try std.testing.expect(lut_entry_address_valid(255) == false); +} + +test "lut_entry_valid_true" { + given entry = LutEntry{.address = 40, .value = 0x1234, .valid = true, .phi_related = false} + try std.testing.expect(lut_entry_valid(entry) == true); +} + +test "lut_entry_valid_false_invalid_addr" { + given entry = LutEntry{.address = 255, .value = 0x1234, .valid = true, .phi_related = false} + try std.testing.expect(lut_entry_valid(entry) == false); +} + +test "lut_entry_valid_false_invalid_flag" { + given entry = LutEntry{.address = 40, .value = 0x1234, .valid = false, .phi_related = false} + try std.testing.expect(lut_entry_valid(entry) == false); +} + +test "create_lut_entry_valid" { + given entry = create_lut_entry(40, 0x1234, true) + try std.testing.expect(entry.valid == true); + try std.testing.expect(entry.phi_related == true); + try std.testing.expect(entry.value == 0x1234); +} + +test "create_lut_entry_invalid" { + given entry = create_lut_entry(81, 0x1234, true) + try std.testing.expect(entry.valid == false); +} + +test "npu_transform_9x9_count" { + given grid = [_][9]i8{ + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{1} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + } + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform.sparsity == 88); +} + +test "transform_is_sparse_true" { + given grid = [_][9]i8{ + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{1} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + [_]i8{0} ** 9, + } + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform_is_sparse(transform) == true); +} + +test "transform_is_sparse_false" { + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform_is_sparse(transform) == false); +} + +test "transform_phi_optimized_true" { + given grid = [_][9]i8{ + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{5} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + [_]i8{1} ** 9, + } + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform_phi_optimized(transform) == true); +} + +test "transform_phi_optimized_false" { + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform_phi_optimized(transform) == false); +} + +test "lut_lookup_base_valid" { + given lut = [_]LutEntry{.{.address = 40, .value = 0x1234, .valid = true, .phi_related = false}} ** 81 + try std.testing.expect(entry = lut_lookup_base(lut, 40)); + try std.testing.expect(entry.value == 0x1234); + try std.testing.expect(entry.valid == true); +} + +test "lut_lookup_base_invalid_addr" { + given lut = [_]LutEntry{.{.address = 40, .value = 0x1234, .valid = true, .phi_related = false}} ** 81 + try std.testing.expect(entry = lut_lookup_base(lut, 81)); + try std.testing.expect(entry.valid == false); +} + +test "encode_lut_npu" { + given encoded = encode_lut_npu(NpuOperation.phi_project, 0x10) + try std.testing.expect((encoded >> 8) == OP_LUT_NPU); +} + +test "decode_lut_npu" { + given decoded = decode_lut_npu(0xE350) + try std.testing.expect(decoded.op == NpuOperation.phi_project); + try std.testing.expect(decoded.addr == 0x10); +} + +test "opcode_constant" { + try std.testing.expect(OP_LUT_NPU == 0xE3); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant lut_size_eighty_one + assert LUT_SIZE == 81 + +invariant lut_addr_bits_seven + assert LUT_ADDR_BITS == 7 + +invariant lut_value_bits_sixteen + assert LUT_VALUE_BITS == 16 + +invariant phi_const_base_zero + assert PHI_CONST_BASE == 0 + +invariant const_lut_end_eighty + assert CONST_LUT_END == 80 + +invariant entry_valid_one + assert ENTRY_VALID == 1 + +invariant entry_invalid_zero + assert ENTRY_INVALID == 0 + +invariant lut_address_bound + assert lut_entry_address_valid(80) == true and lut_entry_address_valid(81) == false + +invariant transform_sparsity_bound + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform.sparsity <= 100); + +invariant transform_sparsity_dense + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform.sparsity == 0); + +invariant phi_score_non_negative + given grid = [_][9]i8{[_]i8{1} ** 9} ** 9} + try std.testing.expect(transform = npu_transform_9x9(grid)); + try std.testing.expect(transform.phi_score >= 0); + +invariant npu_operation_enum_values + try std.testing.expect(@intFromEnum(NpuOperation.lookup) == 0); + try std.testing.expect(@intFromEnum(NpuOperation.custom) == 7); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench npu_transform_9x9_latency + measure: nanoseconds to npu_transform_9x9([_] [9]i8{[_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5} ** 9}) + target: < 500ns + +bench lut_lookup_base_latency + measure: nanoseconds to lut_lookup_base([_]LutEntry{.{.address = 40, .value = 0x1234, .valid = true, .phi_related = false}} ** 81, 40) + target: < 50ns + +bench lut_lookup_phi_latency + measure: nanoseconds to lut_lookup_phi([_]LutEntry{.{.address = 40, .value = 0x1234, .valid = true, .phi_related = true}} ** 81, 40) + target: < 100ns + +bench encode_lut_npu_latency + measure: nanoseconds to encode_lut_npu(NpuOperation.phi_project, 0x10) + target: < 20ns + +bench decode_lut_npu_latency + measure: nanoseconds to decode_lut_npu(0xE350) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/nf4.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/nf4.t27 new file mode 100644 index 0000000000..7d67c8028e --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/nf4.t27 @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/nf4.t27 +// NormalFloat4 - 4-bit normalized float quantization format (Google) +// NUMERIC-STANDARD-001 Agent 11 (P1) +// +// NF4 format: +// - Represents normalized values in [0, 1] +// - Uses a 4-bit normalized representation +// - Optimal for weight quantization where most values are near zero + +module NF4 { + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // NF4 bit layout: N(4) + // N: 4 bits (normalized float value) + // + // Range: 0 to 1 (normalized) + // 16 possible quantization levels + // + // NF4 quantization is used in: + // - QLoRA (Quantized Low-Rank Adapter) + // - GPTQ (GPT Quantization) + // - Neural network weight compression + + const BITS : u8 = 4; + const LEVELS : u8 = 16; + + // 2. NF4 Type + + struct NF4 { + raw : u4, // 4-bit normalized value + } + + // 3. Encoding/Decoding + + // Encode f32 to NF4 (normalized quantization) + fn encode(value: f32) -> NF4 { + // Clamp to [0, 1] range + const clamped = if (value < 0.0) { 0.0 } else if (value > 1.0) { 1.0 } else { value }; + + // Quantize to 4 bits (0-15) + let quantized = (clamped * 15.0) as u8; + if (quantized > 15) { quantized = 15; } + + return NF4{ raw = quantized }; + } + + // Decode NF4 to f32 + fn decode(nf: NF4) -> f64 { + // Dequantize: value = raw / 15 + return (nf.raw as f64) / 15.0; + } + + // 4. Format Properties + + fn min_value() -> f64 { + return 0.0; + } + + fn max_value() -> f64 { + return 1.0; + } + + fn epsilon() -> f64 { + // Smallest representable difference: 1/15 + return 1.0 / 15.0; + } + + fn quantization_error(value: f32, nf: NF4) -> f32 { + const decoded = decode(nf) as f32; + return abs(value - decoded); + } + + // 5. Validation + + fn validate_format() -> bool { + return BITS == 4 && LEVELS == 16; + } + + // 6. Use Cases + + // NF4 is optimal for: + // - Neural network weight quantization (4-bit) + // - QLoRA low-rank adapters + // - GPTQ post-training quantization + // - Embedding table compression + // - Attention weight storage + // - Binary-aware quantization + + // Memory: 4 bits = 0.5 bytes (8x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 4.0 / 32.0; // 0.125 + + // 7. Helper Functions + + fn abs(x: f32) -> f32 { + if (x < 0.0) { -x } else { x }; + } + + fn quantization_error_pct(value: f32) -> f32 { + const encoded = encode(value); + const decoded = decode(encoded) as f32; + if (value == 0.0) { return 0.0; } + return abs(decoded - value) / abs(value) * 100.0; + } + + // 8. Comparison Operations + + fn eq(a: NF4, b: NF4) -> bool { + return a.raw == b.raw; + } + + fn ne(a: NF4, b: NF4) -> bool { + return a.raw != b.raw; + } + + fn lt(a: NF4, b: NF4) -> bool { + return decode(a) < decode(b); + } + + fn le(a: NF4, b: NF4) -> bool { + return decode(a) <= decode(b); + } + + fn gt(a: NF4, b: NF4) -> bool { + return decode(a) > decode(b); + } + + fn ge(a: NF4, b: NF4) -> bool { + return decode(a) >= decode(b); + } + + // TDD-Inside-Spec: Tests and Invariants for NF4 + + test nf4_decode_zero + given nf = NF4{ raw = 0 } + when value = decode(nf) + then value == 0.0 + + test nf4_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test nf4_decode_max + given nf = NF4{ raw = 15 } + when value = decode(nf) + then value == 1.0 + + test nf4_encode_max_roundtrip + given original = 1.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test nf4_encode_clamp_below_zero + given original = -0.5 + and encoded = encode(original) + then encoded.raw == 0 + + test nf4_encode_clamp_above_one + given original = 1.5 + and encoded = encode(original) + then encoded.raw == 15 + + test nf4_quantize_mid_values + given mid = 0.5 + and encoded = encode(mid) + then encoded.raw == 7 or encoded.raw == 8 + + test nf4_decode_preserves_linearity + given nf1 = NF4{ raw = 5 } + and nf2 = NF4{ raw = 10 } + and decoded1 = decode(nf1) + and decoded2 = decode(nf2) + then abs(decoded2 - 2.0 * decoded1) < 0.1 + + test nf4_bits_constant + then BITS == 4 + + test nf4_levels_constant + then LEVELS == 16 + + test nf4_min_value + given min_val = min_value() + then min_val == 0.0 + + test nf4_max_value + given max_val = max_value() + then max_val == 1.0 + + test nf4_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test nf4_epsilon_correct + then abs(epsilon() - 1.0 / 15.0) < 0.001 + + test nf4_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.125) < 0.01 + + test nf4_validate_format_success + given valid = validate_format() + then valid == true + + test nf4_all_levels_encode_decode + for (i in 0..15) + then abs(decode(NF4{ raw = i }) - (i as f64) / 15.0) < 0.01 + + test nf4_quantization_error_small_mid_range + given value = 0.5 + and error = quantization_error(value, encode(value)) + then error < 0.05 + + test nf4_quantization_error_zero + given value = 0.0 + and error = quantization_error(value, encode(value)) + then error == 0.0 + + test nf4_quantization_error_one + given value = 1.0 + and error = quantization_error(value, encode(value)) + then error == 0.0 + + test nf4_eq_true + given a = NF4{ raw = 7 } + and b = NF4{ raw = 7 } + then eq(a, b) == true + + test nf4_eq_false + given a = NF4{ raw = 7 } + and b = NF4{ raw = 8 } + then eq(a, b) == false + + test nf4_lt_true + given a = NF4{ raw = 3 } + and b = NF4{ raw = 7 } + then lt(a, b) == true + + test nf4_ge_true + given a = NF4{ raw = 10 } + and b = NF4{ raw = 5 } + then ge(a, b) == true + + invariant nf4_bits_constant + assert BITS == 4 + + invariant nf4_levels_constant + assert LEVELS == 16 + + invariant nf4_min_is_zero + assert min_value() == 0.0 + + invariant nf4_max_is_one + assert max_value() == 1.0 + + invariant nf4_encode_preserves_zero + assert encode(0.0).raw == 0 + + invariant nf4_encode_preserves_one + assert encode(1.0).raw == 15 + + invariant nf4_decode_min_is_zero + assert decode(NF4{ raw = 0 }) == 0.0 + + invariant nf4_decode_max_is_one + assert decode(NF4{ raw = 15 }) == 1.0 + + invariant nf4_all_values_in_range + for (i in 0..15) + then decode(NF4{ raw = i }) >= 0.0 and decode(NF4{ raw = i }) <= 1.0 + + invariant nf4_epsilon_is_step_size + given val = decode(NF4{ raw = 1 }) + and next_val = decode(NF4{ raw = 2 }) + then abs(next_val - val) == epsilon() + + invariant nf4_encode_monotonic + given x1 = 0.3 and x2 = 0.6 + then encode(x1).raw <= encode(x2).raw + + invariant nf4_decode_monotonic + for (i in 0..14) + then decode(NF4{ raw = i }) <= decode(NF4{ raw = i + 1 }) + + invariant nf4_quantization_error_bounds + for (i in 0..15) + then quantization_error(0.0, NF4{ raw = 0 }) == 0.0 + + invariant nf4_symmetric_quantization + given x = 0.5 + then quantization_error_pct(x) < 50.0 + + bench nf4_encode_latency + measure: nanoseconds to encode(0.5) + target: < 20ns + + bench nf4_decode_latency + measure: nanoseconds to decode(NF4{ raw = 7 }) + target: < 10ns + + bench nf4_encode_decode_roundtrip + measure: nanoseconds to encode(0.5) and decode(encode(0.5)) + target: < 30ns + + bench nf4_quantization_error_latency + measure: nanoseconds to quantization_error(0.5, NF4{ raw = 7 }) + target: < 50ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/null_pe.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/null_pe.t27 new file mode 100644 index 0000000000..f5fcb0f90a --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/null_pe.t27 @@ -0,0 +1,568 @@ +// SPDX-License-Identifier: Apache-2.0 +; null_pe.t27 — Sacred Opcode 0xEA: Null Processing Element +; Hardware null PE (processing element) for sparse acceleration +; φ² + 1/φ² = 3 | TRINITY + +module sacred-null_pe; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_NULL_PE : u8 = 0xEA; + +pub const PE_COUNT : u8 = 4; +pub const DATA_WIDTH : u8 = 16; + +pub const PE_STATE_IDLE : u8 = 0; +pub const PE_STATE_ACTIVE : u8 = 1; +pub const PE_STATE_WAITING : u8 = 2; +pub const PE_STATE_ERROR : u8 = 3; + +pub const NULL_MODE_SKIP : u8 = 0; +pub const NULL_MODE_ZERO : u8 = 1; +pub const NULL_MODE_IDENTITY : u8 = 2; +pub const NULL_MODE_CUSTOM : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const PeState = enum(u8) { + idle = PE_STATE_IDLE, + active = PE_STATE_ACTIVE, + waiting = PE_STATE_WAITING, + error = PE_STATE_ERROR, +} + +pub const PeConfig = struct { + mode : u8, + enabled : bool, + bypass_enabled : bool, +} + +pub const PeStatus = struct { + state : PeState, + cycle_count : u32, + op_count : u32, +} + +pub const NullPeResult = struct { + valid : bool, + output_data : u16, + pe_mask : u8, // Bitmask of which PEs are null + skip_count : u8, +} + +pub const PeArray = struct { + configs : [PE_COUNT]PeConfig, + statuses : [PE_COUNT]PeStatus, +} + +// ============================================================================ +// PE Status Functions +// ============================================================================ + +// pe_is_active(status: PeStatus) -> bool +// Check if PE is active +pub fn pe_is_active(status: PeStatus) bool { + return status.state == PeState.active; +} + +// pe_is_idle(status: PeStatus) -> bool +// Check if PE is idle +pub fn pe_is_idle(status: PeStatus) -> bool { + return status.state == PeState.idle; +} + +// pe_is_waiting(status: PeStatus) -> bool +// Check if PE is waiting +pub fn pe_is_waiting(status: PeStatus) bool { + return status.state == PeState.waiting; +} + +// pe_is_error(status: PeStatus) -> bool +// Check if PE is in error state +pub fn pe_is_error(status: PeStatus) bool { + return status.state == PeState.error; +} + +// pe_set_state(status: PeStatus, state: PeState) -> PeStatus +// Set PE state +pub fn pe_set_state(status: PeStatus, state: PeState) PeStatus { + return PeStatus { + .state = state, + .cycle_count = if (state == PeState.active) status.cycle_count + 1 else status.cycle_count, + .op_count = if (state == PeState.active) status.op_count + 1 else status.op_count, + }; +} + +// ============================================================================ +// PE Config Functions +// ============================================================================ + +// pe_config_enabled(config: PeConfig) -> bool +// Check if PE config enables operation +pub fn pe_config_enabled(config: PeConfig) -> bool { + return config.enabled and not config.bypass_enabled; +} + +// pe_is_null(config: PeConfig) -> bool +// Check if PE is in null mode +pub fn pe_is_null(config: PeConfig) -> bool { + return config.mode == NULL_MODE_SKIP or config.mode == NULL_MODE_ZERO; +} + +// pe_is_identity(config: PeConfig) -> bool +// Check if PE is in identity mode +pub fn pe_is_identity(config: PeConfig) -> bool { + return config.mode == NULL_MODE_IDENTITY; +} + +// ============================================================================ +// Null PE Operations +// ============================================================================ + +// null_pe_skip(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult +// Null PE operation (skip processing) +pub fn null_pe_skip(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult { + var skip_count : u8 = 0; + var output_data : u16 = input; + + for (configs, 0..) |config, i| { + if (((pe_mask >> i) & 1) != 0 and pe_is_null(config)) { + skip_count += 1; + } + } + + return NullPeResult { + .valid = skip_count > 0, + .output_data = if (skip_count == PE_COUNT) 0 else output_data, + .pe_mask = pe_mask, + .skip_count = skip_count, + }; +} + +// null_pe_zero(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult +// Null PE operation (zero output) +pub fn null_pe_zero(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult { + var skip_count : u8 = 0; + + for (configs, 0..) |config, i| { + if (((pe_mask >> i) & 1) != 0 and pe_is_null(config)) { + skip_count += 1; + } + } + + return NullPeResult { + .valid = true, + .output_data = 0, + .pe_mask = pe_mask, + .skip_count = skip_count, + }; +} + +// null_pe_identity(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult +// Null PE operation (identity pass-through) +pub fn null_pe_identity(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult { + var active_count : u8 = 0; + + for (configs, 0..) |config, i| { + if (((pe_mask >> i) & 1) != 0 and pe_is_identity(config)) { + active_count += 1; + } + } + + return NullPeResult { + .valid = active_count > 0, + .output_data = input, + .pe_mask = pe_mask, + .skip_count = PE_COUNT - active_count, + }; +} + +// null_pe_execute(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult +// Execute null PE operation based on config +pub fn null_pe_execute(input: u16, pe_mask: u8, configs: [PE_COUNT]PeConfig) -> NullPeResult { + var mode : u8 = NULL_MODE_SKIP; + + // Determine primary mode from active PEs + for (configs, 0..) |config, i| { + if (((pe_mask >> i) & 1) != 0 and config.enabled) { + mode = config.mode; + break; + } + } + + switch (mode) { + NULL_MODE_SKIP => { + return null_pe_skip(input, pe_mask, configs); + }, + NULL_MODE_ZERO => { + return null_pe_zero(input, pe_mask, configs); + }, + NULL_MODE_IDENTITY => { + return null_pe_identity(input, pe_mask, configs); + }, + NULL_MODE_CUSTOM => { + // Custom: return zero for null PEs + return null_pe_zero(input, pe_mask, configs); + }, + else => { + return null_pe_skip(input, pe_mask, configs); + }, + } +} + +// ============================================================================ +// PE Array Functions +// ============================================================================ + +// pe_array_init() -> PeArray +// Initialize PE array +pub fn pe_array_init() -> PeArray { + var configs : [PE_COUNT]PeConfig = undefined; + var statuses : [PE_COUNT]PeStatus = undefined; + + for (0..PE_COUNT) |i| { + configs[i] = PeConfig{.mode = NULL_MODE_SKIP, .enabled = false, .bypass_enabled = false}; + statuses[i] = PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0}; + } + + return PeArray{.configs = configs, .statuses = statuses}; +} + +// pe_array_enable(pe_array: PeArray, pe_mask: u8) -> PeArray +// Enable PEs based on mask +pub fn pe_array_enable(pe_array: PeArray, pe_mask: u8) -> PeArray { + var result = pe_array; + + for (0..PE_COUNT) |i| { + if ((pe_mask >> i) & 1 != 0) { + result.configs[i].enabled = true; + } else { + result.configs[i].enabled = false; + } + } + + return result; +} + +// pe_array_count_active(pe_array: PeArray) -> u8 +// Count active PEs +pub fn pe_array_count_active(pe_array: PeArray) -> u8 { + var count : u8 = 0; + for (pe_array.configs) |config| { + if (config.enabled and not config.bypass_enabled) { + count += 1; + } + } + return count; +} + +// pe_array_count_null(pe_array: PeArray) -> u8 +// Count null PEs +pub fn pe_array_count_null(pe_array: PeArray) -> u8 { + var count : u8 = 0; + for (pe_array.configs) |config| { + if (pe_is_null(config)) { + count += 1; + } + } + return count; +} + +// pe_array_get_null_mask(pe_array: PeArray) -> u8 +// Get bitmask of null PEs +pub fn pe_array_get_null_mask(pe_array: PeArray) -> u8 { + var mask : u8 = 0; + for (pe_array.configs, 0..) |config, i| { + if (pe_is_null(config)) { + mask |= (@as(u8, 1) << i); + } + } + return mask; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_null_pe(pe_mask: u8, mode: u8) -> u16 +// Encode null PE instruction +pub fn encode_null_pe(pe_mask: u8, mode: u8) u16 { + // Format: [OP:8][MODE:2][MASK:4][RES:2] + const op : u16 = @as(u16, OP_NULL_PE) << 8; + const mode_field : u16 = @as(u16, mode & 0x03) << 6; + const mask_field : u16 = @as(u16, pe_mask & 0x0F) << 2; + return op | mode_field | mask_field; +} + +// decode_null_pe(encoded: u16) -> struct { pe_mask: u8, mode: u8 } +// Decode null PE instruction +pub fn decode_null_pe(encoded: u16) -> struct { pe_mask: u8, mode: u8 } { + const mode : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const pe_mask : u8 = @as(u8, @truncate((encoded >> 2) & 0x0F)); + return .{ .pe_mask = pe_mask, .mode = mode }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "pe_count_four" { + try std.testing.expect(PE_COUNT == 4); +} + +test "data_width_sixteen" { + try std.testing.expect(DATA_WIDTH == 16); +} + +test "null_mode_constants" { + try std.testing.expect(NULL_MODE_SKIP == 0); + try std.testing.expect(NULL_MODE_ZERO == 1); + try std.testing.expect(NULL_MODE_IDENTITY == 2); + try std.testing.expect(NULL_MODE_CUSTOM == 3); +} + +test "pe_state_constants" { + try std.testing.expect(PE_STATE_IDLE == 0); + try std.testing.expect(PE_STATE_ACTIVE == 1); + try std.testing.expect(PE_STATE_WAITING == 2); + try std.testing.expect(PE_STATE_ERROR == 3); +} + +test "pe_is_active_true" { + given status = PeStatus{.state = PeState.active, .cycle_count = 10, .op_count = 5} + try std.testing.expect(pe_is_active(status) == true); +} + +test "pe_is_active_false" { + given status = PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} + try std.testing.expect(pe_is_active(status) == false); +} + +test "pe_is_idle_true" { + given status = PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} + try std.testing.expect(pe_is_idle(status) == true); +} + +test "pe_set_state_increments" { + given status = PeStatus{.state = PeState.idle, .cycle_count = 10, .op_count = 5} + try std.testing.expect(result = pe_set_state(status, PeState.active)); + try std.testing.expect(result.cycle_count == 11); + try std.testing.expect(result.op_count == 6); +} + +test "pe_config_enabled_true" { + given config = PeConfig{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false} + try std.testing.expect(pe_config_enabled(config) == true); +} + +test "pe_config_enabled_disabled" { + given config = PeConfig{.mode = NULL_MODE_SKIP, .enabled = false, .bypass_enabled = false} + try std.testing.expect(pe_config_enabled(config) == false); +} + +test "pe_config_enabled_bypass" { + given config = PeConfig{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = true} + try std.testing.expect(pe_config_enabled(config) == false); +} + +test "pe_is_null_skip" { + try std.testing.expect(pe_is_null(.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}) == true); + try std.testing.expect(pe_is_null(.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}) == true); +} + +test "pe_is_null_not" { + try std.testing.expect(pe_is_null(.{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}) == false); +} + +test "null_pe_skip_some" { + given configs = [_]PeConfig{ + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + } + try std.testing.expect(result = null_pe_skip(0x1234, 0b1010, configs)); + try std.testing.expect(result.skip_count == 3); +} + +test "null_pe_zero_always_zero" { + given configs = [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(result = null_pe_zero(0x1234, 0b1111, configs)); + try std.testing.expect(result.output_data == 0); + try std.testing.expect(result.valid == true); +} + +test "null_pe_identity_pass" { + given configs = [_]PeConfig{.{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(result = null_pe_identity(0x1234, 0b1111, configs)); + try std.testing.expect(result.output_data == 0x1234); +} + +test "null_pe_execute_skip" { + given configs = [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(result = null_pe_execute(0x1234, 0b1111, configs)); + try std.testing.expect(result.output_data == 0x1234); +} + +test "null_pe_execute_zero" { + given configs = [_]PeConfig{.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(result = null_pe_execute(0x1234, 0b1111, configs)); + try std.testing.expect(result.output_data == 0); +} + +test "pe_array_init" { + given array = pe_array_init() + try std.testing.expect(pe_array_count_active(array) == 0); + try std.testing.expect(pe_array_count_null(array) == 0); +} + +test "pe_array_enable" { + given array = pe_array_init() + try std.testing.expect(result = pe_array_enable(array, 0b1010)); + try std.testing.expect(result.configs[0].enabled == false); + try std.testing.expect(result.configs[1].enabled == true); + try std.testing.expect(result.configs[2].enabled == false); + try std.testing.expect(result.configs[3].enabled == true); +} + +test "pe_array_count_active" { + given array = pe_array_enable(pe_array_init(), 0b1111) + try std.testing.expect(pe_array_count_active(array) == 4); +} + +test "pe_array_count_null" { + given configs = [_]PeConfig{ + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = false, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + } + try std.testing.expect(array = PeArray{.configs = configs, .statuses = [_]PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} ** 4}); + try std.testing.expect(pe_array_count_null(array) == 2); +} + +test "pe_array_get_null_mask" { + given configs = [_]PeConfig{ + .{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}, + .{.mode = NULL_MODE_SKIP, .enabled = false, .bypass_enabled = false}, + .{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}, + } + try std.testing.expect(array = PeArray{.configs = configs, .statuses = [_]PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} ** 4}); + try std.testing.expect(mask = pe_array_get_null_mask(array)); + try std.testing.expect(mask == 0b1011); +} + +test "encode_null_pe" { + given encoded = encode_null_pe(0b1010, 1) + try std.testing.expect((encoded >> 8) == OP_NULL_PE); +} + +test "decode_null_pe" { + given decoded = decode_null_pe(0xEA5A) + try std.testing.expect(decoded.pe_mask == 0b1010); + try std.testing.expect(decoded.mode == 1); +} + +test "opcode_constant" { + try std.testing.expect(OP_NULL_PE == 0xEA); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant pe_count_four + assert PE_COUNT == 4 + +invariant data_width_sixteen + assert DATA_WIDTH == 16 + +invariant null_mode_values + try std.testing.expect(NULL_MODE_SKIP >= 0 and NULL_MODE_CUSTOM <= 3); + +invariant pe_state_values + try std.testing.expect(PE_STATE_IDLE >= 0 and PE_STATE_ERROR <= 3); + +invariant pe_set_state_increments + given status = PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} + try std.testing.expect(result = pe_set_state(status, PeState.active)); + try std.testing.expect(result.cycle_count == status.cycle_count + 1); + try std.testing.expect(result.op_count == status.op_count + 1); + +invariant pe_set_state_preserves_when_idle + given status = PeStatus{.state = PeState.active, .cycle_count = 10, .op_count = 5} + try std.testing.expect(result = pe_set_state(status, PeState.idle)); + try std.testing.expect(result.cycle_count == status.cycle_count); + try std.testing.expect(result.op_count == status.op_count); + +invariant null_pe_skip_valid + given configs = [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(null_pe_skip(0x1234, 0, configs).valid == false); + try std.testing.expect(null_pe_skip(0x1234, 0b1111, configs).skip_count == 4); + +invariant null_pe_zero_always_valid + given configs = [_]PeConfig{.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(null_pe_zero(0x1234, 0, configs).valid == true); + +invariant null_pe_zero_output + given configs = [_]PeConfig{.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(null_pe_zero(0x1234, 0b1111, configs).output_data == 0); + +invariant null_pe_identity_preserves + given configs = [_]PeConfig{.{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(null_pe_identity(0x1234, 0, configs).output_data == 0x1234); + +invariant pe_array_count_bounds + given array = pe_array_enable(pe_array_init(), 0xFF) + try std.testing.expect(pe_array_count_active(array) <= PE_COUNT); + try std.testing.expect(pe_array_count_null(array) <= PE_COUNT); + +invariant pe_array_get_null_mask_bound + try std.testing.expect(pe_array_get_null_mask(pe_array_init()) < (@as(u8, 1) << PE_COUNT)); + +invariant pe_array_count_all + given configs = [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4 + try std.testing.expect(array = PeArray{.configs = configs, .statuses = [_]PeStatus{.state = PeState.idle, .cycle_count = 0, .op_count = 0} ** 4}); + try std.testing.expect(pe_array_count_active(array) + pe_array_count_null(array) == PE_COUNT); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench null_pe_skip_latency + measure: nanoseconds to null_pe_skip(0x1234, 0b1111, [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4) + target: < 50ns + +bench null_pe_zero_latency + measure: nanoseconds to null_pe_zero(0x1234, 0b1111, [_]PeConfig{.{.mode = NULL_MODE_ZERO, .enabled = true, .bypass_enabled = false}} ** 4) + target: < 50ns + +bench null_pe_identity_latency + measure: nanoseconds to null_pe_identity(0x1234, 0b1111, [_]PeConfig{.{.mode = NULL_MODE_IDENTITY, .enabled = true, .bypass_enabled = false}} ** 4) + target: < 50ns + +bench null_pe_execute_latency + measure: nanoseconds to null_pe_execute(0x1234, 0b1111, [_]PeConfig{.{.mode = NULL_MODE_SKIP, .enabled = true, .bypass_enabled = false}} ** 4) + target: < 100ns + +bench pe_array_count_active_latency + measure: nanoseconds to pe_array_count_active(pe_array_enable(pe_array_init(), 0xFF)) + target: < 50ns + +bench pe_array_get_null_mask_latency + measure: nanoseconds to pe_array_get_null_mask(pe_array_enable(pe_array_init(), 0xFF)) + target: < 50ns + +bench encode_null_pe_latency + measure: nanoseconds to encode_null_pe(0b1010, 1) + target: < 20ns + +bench decode_null_pe_latency + measure: nanoseconds to decode_null_pe(0xEA5A) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/posit16.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/posit16.t27 new file mode 100644 index 0000000000..d868ba60e8 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/posit16.t27 @@ -0,0 +1,979 @@ +// SPDX-License-Identifier: Apache-2.0 +; posit16.t27 — Posit Type 16 (Type-2 with ES=1, unum 1.0 format) +; 16-bit posit format with 1 exponent bit (ES=1) and 0 useed bits +; Alternative name: posit<16,1> +; Range: ~-3.8e4 to ~3.8e4, precision: ~1-2 significant bits at extremes, ~10 at 1.0 +; φ² + 1/φ² = 3 | TRINITY + +module triformat-posit16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 16; +pub const ES : u8 = 1; // Exponent size bits +pub const NBITS : u8 = BITS - ES; // Number of bits excluding ES +pub const USEED_BITS : u8 = 0; // No useed bits (type-2) + +pub const SIGN_SHIFT : u8 = 15; +pub const REGIME_SHIFT : u8 = 14; + +pub const SIGN_MASK : u16 = 0x8000; // 1 << 15 +pub const REGIME_MASK : u16 = 0x7FFF; // Lower 15 bits + +// Special values +pub const POS16_ZERO : u16 = 0x0000; // Positive zero +pub const POS16_NAR : u16 = 0x8000; // Not a Real (NaR) + +// Maximum regime runs +pub const MAX_REGIME : i16 = 6; // Based on NBITS=15 and regime encoding + +// ============================================================================ +// Types +// ============================================================================ + +pub const Posit16 = u16; // 16-bit posit value + +// Posit16 components struct +pub struct PositComponents { + sign : bool, // true = negative + regime : i8, // Regime value (can be negative or positive) + exponent : u8, // Exponent (0 or 1 for ES=1) + fraction : u16, // Fraction/mantissa bits + scale : i16, // Combined scale factor = 2^(useed*regime + exponent) +} + +// ============================================================================ +// Regime Extraction Functions +// ============================================================================ + +// posit16_extract_regime(pos: Posit16) -> i8 +// Extract regime value from posit +// Regime is encoded as a run of bits (same as sign) followed by opposite bit +pub fn posit16_extract_regime(pos: Posit16) i8 { + // Remove sign bit + var remaining_bits : u16 = pos & REGIME_MASK; + + // Count leading bits that match the first bit (regime run) + var regime_run : i8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Regime value: if first_bit=1, regime = run-1; if first_bit=0, regime = -run + return if (first_bit == 1) regime_run - 1 else -regime_run; +} + +// posit16_extract_exponent(pos: Posit16) -> u8 +// Extract exponent value from posit +pub fn posit16_extract_exponent(pos: Posit16) i8 { + var remaining_bits : u16 = pos & REGIME_MASK; + + // Find regime run length + var regime_run : u8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Skip regime bits and the terminating bit + const exponent_bit : u8 = if (regime_run + 1 < NBITS) + @truncate(@as(u8, @intCast((remaining_bits >> (NBITS - 1 - regime_run - 1))) & 1)) + else + 0; + + return if (exponent_bit == 1) 1 else 0; +} + +// posit16_extract_fraction(pos: Posit16) -> u16 +// Extract fraction bits from posit +pub fn posit16_extract_fraction(pos: Posit16) u16 { + var remaining_bits : u16 = pos & REGIME_MASK; + + // Find regime run length + var regime_run : u8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Skip regime bits, terminating bit, and exponent bit (ES=1) + const skip_bits : u8 = regime_run + 1 + ES; + const fraction_bits : u8 = if (skip_bits >= NBITS) 0 else NBITS - skip_bits; + + // Extract fraction bits + var fraction : u16 = 0; + if (fraction_bits > 0 and skip_bits < NBITS) { + fraction = remaining_bits & ((@as(u16, 1) << fraction_bits) - 1); + } + + return fraction; +} + +// posit16_extract_sign(pos: Posit16) -> bool +// Extract sign bit (true = negative) +pub fn posit16_extract_sign(pos: Posit16) bool { + return (pos & SIGN_MASK) != 0; +} + +// posit16_extract_components(pos: Posit16) -> PositComponents +// Extract all components from posit +pub fn posit16_extract_components(pos: Posit16) PositComponents { + const sign = posit16_extract_sign(pos); + const regime = posit16_extract_regime(pos); + const exponent = posit16_extract_exponent(pos); + const fraction = posit16_extract_fraction(pos); + + // Calculate scale: useed = 2^(2^ES) = 2^2 = 4 + // scale = 2^(useed * regime + exponent) + const useed_exp : i8 = 1 << ES; // 2^ES = 2^1 = 2 + const useed : i16 = 1 << useed_exp; // 2^2 = 4 + const scale = (useed * @as(i16, regime)) + @as(i16, exponent); + + return PositComponents { + .sign = sign, + .regime = regime, + .exponent = @as(u8, @intCast(exponent)), + .fraction = fraction, + .scale = scale, + }; +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// posit16_is_zero(pos: Posit16) -> bool +// Check if posit is zero (positive or negative) +pub fn posit16_is_zero(pos: Posit16) bool { + return pos == POS16_ZERO; +} + +// posit16_is_nar(pos: Posit16) -> bool +// Check if posit is Not-a-Real (NaR) +pub fn posit16_is_nar(pos: Posit16) bool { + return pos == POS16_NAR; +} + +// posit16_is_special(pos: Posit16) -> bool +// Check if posit is a special value (zero or NaR) +pub fn posit16_is_special(pos: Posit16) bool { + return posit16_is_zero(pos) or posit16_is_nar(pos); +} + +// posit16_is_finite(pos: Posit16) -> bool +// Check if posit is finite (not NaR) +pub fn posit16_is_finite(pos: Posit16) bool { + return not posit16_is_nar(pos); +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// posit16_encode_f32(value: f32) -> Posit16 +// Encode IEEE 754 single precision to Posit16 +pub fn posit16_encode_f32(value: f32) Posit16 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) POS16_NAR else POS16_ZERO; + } + + // Handle NaN + if (std.math.isNan(value)) { + return POS16_NAR; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) POS16_NAR else POS16_NAR; + } + + // Extract sign + const is_negative = value < 0.0; + const abs_value = if (is_negative) -value else value; + + // Convert to f64 for more precision during computation + const value_f64 : f64 = @as(f64, @floatCast(abs_value)); + + // Calculate useed = 2^(2^ES) = 4 for ES=1 + const useed : f64 = 4.0; + + // Calculate log2 and determine regime + const log2_val = std.math.log(value_f64) / std.math.log(2.0); + + // Regime: largest k such that 2^(k*useed) <= value < 2^((k+1)*useed) + var k : i16 = @intFromFloat(@floor(log2_val / std.math.log(useed))); + + // Clamp regime + if (k > MAX_REGIME) { + k = MAX_REGIME; + } else if (k < -MAX_REGIME - 1) { + k = -MAX_REGIME - 1; + } + + // Calculate exponent + var exp : i8 = 0; + var remaining = value_f64 / std.math.pow(f64, useed, @as(f64, @floatFromInt(k))); + + // Normalize remaining value to [1, 2) for fraction extraction + while (remaining >= 2.0 and exp < ((@as(i8, 1) << ES) - 1)) { + remaining /= 2.0; + exp += 1; + } + + // Extract fraction bits + const fraction_bits : u8 = @as(u8, @intCast(NBITS - @as(u8, @intCast(@abs(k) + 1)) - ES)); + var fraction : u16 = 0; + var frac_val = remaining - 1.0; // Remove implicit 1 + + if (fraction_bits > 0) { + for (0..fraction_bits) |i| { + frac_val *= 2.0; + if (frac_val >= 1.0) { + fraction |= (@as(u16, 1) << (fraction_bits - 1 - i)); + frac_val -= 1.0; + } + } + } + + // Encode bits + var result : u16 = 0; + + // Set sign bit + if (is_negative) { + result |= SIGN_MASK; + } + + // Encode regime + // For k >= 0: run of k+1 ones followed by zero + // For k < 0: run of -k zeros followed by one + const regime_bits_remaining : u8 = if (fraction_bits > 0) + @as(u8, @intCast(@as(i16, @abs(k)) + 1)) + else + NBITS - ES; + + const regime_start = SIGN_SHIFT - 1 - regime_bits_remaining; + if (k >= 0) { + // k+1 ones followed by zero (if space permits) + for (0..@as(u8, @intCast(k + 1))) |i| { + if (regime_start - i >= 0) { + result |= (@as(u16, 1) << (regime_start - i)); + } + } + } else { + // -k zeros followed by one + const one_pos = regime_start - @as(u8, @intCast(-k)); + if (one_pos >= 0) { + result |= (@as(u16, 1) << one_pos); + } + } + + // Encode exponent + if (ES > 0 and fraction_bits + ES > 0) { + const exp_pos = @as(u8, @intCast(NBITS - 1)) - regime_bits_remaining; + if (exp_pos >= 0) { + result |= (@as(u16, exp) << exp_pos); + } + } + + // Encode fraction + const fraction_pos = @as(u8, @intCast(NBITS - 1)) - regime_bits_remaining - ES; + if (fraction_bits > 0 and fraction_pos >= fraction_bits) { + result |= (fraction << (fraction_pos - fraction_bits + 1)); + } + + return result; +} + +// posit16_decode_f32(pos: Posit16) -> f32 +// Decode Posit16 to IEEE 754 single precision +pub fn posit16_decode_f32(pos: Posit16) f32 { + // Handle zero + if (posit16_is_zero(pos)) { + return 0.0; + } + + // Handle NaR + if (posit16_is_nar(pos)) { + return std.math.nan(f32); + } + + // Extract components + const comps = posit16_extract_components(pos); + + // Calculate value + // value = (-1)^sign * 2^scale * (1 + fraction/2^fraction_bits) + const useed : f64 = 4.0; // 2^(2^1) = 4 + const scale_value = std.math.pow(f64, useed, @as(f64, @floatFromInt(comps.scale))); + + // Calculate fraction contribution + const fraction_bits : u8 = @as(u8, @intCast(NBITS - @as(u8, @intCast(@abs(comps.regime) + 1)) - ES)); + const fraction_value = if (fraction_bits > 0) + @as(f64, @floatFromInt(comps.fraction)) / @as(f64, @floatFromInt(@as(u16, 1) << fraction_bits)) + else + 0.0; + + const magnitude = scale_value * (1.0 + fraction_value); + + return if (comps.sign) -@as(f32, @floatCast(magnitude)) else @as(f32, @floatCast(magnitude)); +} + +// posit16_encode_f64(value: f64) -> Posit16 +// Encode IEEE 754 double precision to Posit16 +pub fn posit16_encode_f64(value: f64) Posit16 { + return posit16_encode_f32(@as(f32, @floatCast(value))); +} + +// posit16_decode_f64(pos: Posit16) -> f64 +// Decode Posit16 to IEEE 754 double precision +pub fn posit16_decode_f64(pos: Posit16) f64 { + const f32_val = posit16_decode_f32(pos); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// posit16_add(a: Posit16, b: Posit16) -> Posit16 +// Add two posit values +pub fn posit16_add(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a)) { + return b; + } + if (posit16_is_zero(b)) { + return a; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa + fb); +} + +// posit16_sub(a: Posit16, b: Posit16) -> Posit16 +// Subtract two posit values +pub fn posit16_sub(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa - fb); +} + +// posit16_mul(a: Posit16, b: Posit16) -> Posit16 +// Multiply two posit values +pub fn posit16_mul(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a) or posit16_is_zero(b)) { + return POS16_ZERO; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa * fb); +} + +// posit16_div(a: Posit16, b: Posit16) -> Posit16 +// Divide two posit values +pub fn posit16_div(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a)) { + return POS16_ZERO; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa / fb); +} + +// posit16_abs(value: Posit16) -> Posit16 +// Absolute value of posit +pub fn posit16_abs(value: Posit16) Posit16 { + return value & ~SIGN_MASK; +} + +// posit16_neg(value: Posit16) -> Posit16 +// Negate posit +pub fn posit16_neg(value: Posit16) Posit16 { + return value ^ SIGN_MASK; +} + +// posit16_is_equal(a: Posit16, b: Posit16) -> bool +// Check if two posit values are equal +pub fn posit16_is_equal(a: Posit16, b: Posit16) bool { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return false; + } + if (posit16_is_zero(a) and posit16_is_zero(b)) { + return true; + } + return a == b; +} + +// posit16_is_greater(a: Posit16, b: Posit16) -> bool +// Check if a > b +pub fn posit16_is_greater(a: Posit16, b: Posit16) bool { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return false; + } + const sign_a = posit16_extract_sign(a); + const sign_b = posit16_extract_sign(b); + if (sign_a != sign_b) { + return not sign_a; // Positive > negative + } + if (sign_a) { + // Both negative, compare magnitudes (inverse) + return a < b; + } + return a > b; +} + +// posit16_max(a: Posit16, b: Posit16) -> Posit16 +// Return the larger of two posit values +pub fn posit16_max(a: Posit16, b: Posit16) Posit16 { + return if (posit16_is_greater(a, b)) a else b; +} + +// posit16_min(a: Posit16, b: Posit16) -> Posit16 +// Return the smaller of two posit values +pub fn posit16_min(a: Posit16, b: Posit16) Posit16 { + return if (posit16_is_greater(a, b)) b else a; +} + +// posit16_sqrt(value: Posit16) -> Posit16 +// Square root of posit value +pub fn posit16_sqrt(value: Posit16) Posit16 { + if (posit16_is_nar(value)) { + return POS16_NAR; + } + if (posit16_extract_sign(value)) { + return POS16_NAR; + } + if (posit16_is_zero(value)) { + return POS16_ZERO; + } + + const fv = posit16_decode_f32(value); + return posit16_encode_f32(std.math.sqrt(fv)); +} + +// posit16_lerp(a: Posit16, b: Posit16, t: f32) -> Posit16 +// Linear interpolation between a and b +pub fn posit16_lerp(a: Posit16, b: Posit16, t: f32) Posit16 { + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + const result = fa + (fb - fa) * t; + return posit16_encode_f32(result); +} + +// posit16_scale(value: Posit16, scale: f32) -> Posit16 +// Scale posit value by scalar +pub fn posit16_scale(value: Posit16, scale: f32) Posit16 { + const fv = posit16_decode_f32(value); + return posit16_encode_f32(fv * scale); +} + +// posit16_relu(value: Posit16) -> Posit16 +// ReLU activation: max(0, x) +pub fn posit16_relu(value: Posit16) Posit16 { + return if (posit16_extract_sign(value)) POS16_ZERO else value; +} + +// posit16_sigmoid(value: Posit16) -> Posit16 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn posit16_sigmoid(value: Posit16) Posit16 { + const fv = posit16_decode_f32(value); + return posit16_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// posit16_quantize_to_int4(value: Posit16) -> i8 +// Quantize posit to Int4 (range [-8, 7]) +pub fn posit16_quantize_to_int4(value: Posit16) i8 { + const fv = posit16_decode_f32(value); + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// posit16_from_f32_scaled(value: f32, scale: f32) -> Posit16 +// Encode f32 to posit with custom scale factor +pub fn posit16_from_f32_scaled(value: f32, scale: f32) Posit16 { + return posit16_encode_f32(value * scale); +} + +// posit16_to_f32_scaled(value: Posit16, scale: f32) -> f32 +// Decode posit to f32 with custom scale factor +pub fn posit16_to_f32_scaled(value: Posit16, scale: f32) f32 { + return posit16_decode_f32(value) / scale; +} + +// posit16_dot_product(a: []const Posit16, b: []const Posit16) -> Posit16 +// Dot product of two posit arrays +pub fn posit16_dot_product(a: []const Posit16, b: []const Posit16) Posit16 { + var sum: f32 = 0.0; + for (a, 0..) |val_a, i| { + if (i >= b.len) break; + const fa = posit16_decode_f32(val_a); + const fb = posit16_decode_f32(b[i]); + sum += fa * fb; + } + return posit16_encode_f32(sum); +} + +// posit16_vector_sum(vec: []const Posit16) -> Posit16 +// Sum all elements of a vector +pub fn posit16_vector_sum(vec: []const Posit16) Posit16 { + var sum: f32 = 0.0; + for (vec) |val| { + sum += posit16_decode_f32(val); + } + return posit16_encode_f32(sum); +} + +// posit16_vector_mean(vec: []const Posit16) -> Posit16 +// Mean of all elements of a vector +pub fn posit16_vector_mean(vec: []const Posit16) Posit16 { + if (vec.len == 0) { + return POS16_ZERO; + } + const sum = posit16_vector_sum(vec); + return posit16_div(sum, posit16_encode_f32(@as(f32, @floatFromInt(vec.len)))); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "posit16_is_zero_true" { + try std.testing.expect(posit16_is_zero(POS16_ZERO) == true); +} + +test "posit16_is_zero_false" { + try std.testing.expect(posit16_is_zero(0x0001) == false); +} + +test "posit16_is_nar_true" { + try std.testing.expect(posit16_is_nar(POS16_NAR) == true); +} + +test "posit16_is_nar_false" { + try std.testing.expect(posit16_is_nar(POS16_ZERO) == false); +} + +test "posit16_is_special_zero" { + try std.testing.expect(posit16_is_special(POS16_ZERO) == true); +} + +test "posit16_is_special_nar" { + try std.testing.expect(posit16_is_special(POS16_NAR) == true); +} + +test "posit16_is_special_normal" { + try std.testing.expect(posit16_is_special(0x4000) == false); +} + +test "posit16_extract_sign_positive" { + given value = 0x4000 + try std.testing.expect(sign = posit16_extract_sign(value)); + try std.testing.expect(sign == false); +} + +test "posit16_extract_sign_negative" { + given value = 0xC000 + try std.testing.expect(sign = posit16_extract_sign(value)); + try std.testing.expect(sign == true); +} + +test "posit16_encode_f32_zero" { + given pos = posit16_encode_f32(0.0) + try std.testing.expect(pos == POS16_ZERO); +} + +test "posit16_encode_f32_one" { + given pos = posit16_encode_f32(1.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "posit16_encode_f32_negative_one" { + given pos = posit16_encode_f32(-1.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded + 1.0) < 0.1); +} + +test "posit16_encode_f32_ten" { + given pos = posit16_encode_f32(10.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - 10.0) < 2.0); +} + +test "posit16_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(pos = posit16_encode_f32(original)); + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - original) < 0.3); +} + +test "posit16_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(pos = posit16_encode_f32(original)); + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - original) < 0.3); +} + +test "posit16_decode_f32_zero" { + try std.testing.expect(posit16_decode_f32(POS16_ZERO) == 0.0); +} + +test "posit16_decode_f32_nar" { + try std.testing.expect(std.math.isNan(posit16_decode_f32(POS16_NAR)) == true); +} + +test "posit16_add_simple" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(20.0)); + try std.testing.expect(result = posit16_add(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 30.0) < 5.0); +} + +test "posit16_sub_simple" { + given a = posit16_encode_f32(50.0) + try std.testing.expect(b = posit16_encode_f32(30.0)); + try std.testing.expect(result = posit16_sub(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "posit16_mul_simple" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(10.0)); + try std.testing.expect(result = posit16_mul(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 20.0); +} + +test "posit16_div_simple" { + given a = posit16_encode_f32(100.0) + try std.testing.expect(b = posit16_encode_f32(10.0)); + try std.testing.expect(result = posit16_div(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "posit16_sqrt_four" { + given value = posit16_encode_f32(4.0) + try std.testing.expect(result = posit16_sqrt(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.3); +} + +test "posit16_sqrt_negative" { + given value = posit16_encode_f32(-4.0) + try std.testing.expect(result = posit16_sqrt(value)); + try std.testing.expect(posit16_is_nar(result) == true); +} + +test "posit16_abs_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(result = posit16_abs(value)); + try std.testing.expect(posit16_is_equal(result, value) == true); +} + +test "posit16_abs_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(abs_val = posit16_abs(value)); + try std.testing.expect(posit16_extract_sign(abs_val) == false); +} + +test "posit16_neg_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(neg_val = posit16_neg(value)); + try std.testing.expect(posit16_extract_sign(neg_val) == true); +} + +test "posit16_neg_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(neg_val = posit16_neg(value)); + try std.testing.expect(posit16_extract_sign(neg_val) == false); +} + +test "posit16_is_equal_true" { + given value = posit16_encode_f32(10.0) + try std.testing.expect(posit16_is_equal(value, value) == true); +} + +test "posit16_is_equal_false" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(20.0)); + try std.testing.expect(posit16_is_equal(a, b) == false); +} + +test "posit16_is_equal_nar" { + try std.testing.expect(posit16_is_equal(POS16_NAR, POS16_NAR) == false); +} + +test "posit16_is_greater_positive" { + given a = posit16_encode_f32(50.0) + try std.testing.expect(b = posit16_encode_f32(30.0)); + try std.testing.expect(posit16_is_greater(a, b) == true); +} + +test "posit16_max_returns_larger" { + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_max(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded >= 35.0); +} + +test "posit16_min_returns_smaller" { + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_min(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded <= 45.0); +} + +test "posit16_relu_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(result = posit16_relu(value)); + try std.testing.expect(posit16_is_equal(result, value) == true); +} + +test "posit16_relu_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(result = posit16_relu(value)); + try std.testing.expect(result == POS16_ZERO); +} + +test "posit16_sigmoid_zero" { + given value = POS16_ZERO + try std.testing.expect(result = posit16_sigmoid(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "posit16_sigmoid_positive_large" { + given value = posit16_encode_f32(10.0) + try std.testing.expect(result = posit16_sigmoid(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "posit16_quantize_to_int4_max" { + given value = posit16_encode_f32(100.0) + try std.testing.expect(result = posit16_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "posit16_quantize_to_int4_min" { + given value = posit16_encode_f32(-100.0) + try std.testing.expect(result = posit16_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "posit16_from_f32_scaled_up" { + given value = 1.0 + try std.testing.expect(scale = 100.0); + try std.testing.expect(result = posit16_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 20.0); +} + +test "posit16_dot_product_simple" { + given a = [_]Posit16{posit16_encode_f32(1.0), posit16_encode_f32(2.0), posit16_encode_f32(3.0), posit16_encode_f32(4.0)} + try std.testing.expect(b = [_]Posit16{posit16_encode_f32(2.0), posit16_encode_f32(2.0), posit16_encode_f32(2.0), posit16_encode_f32(2.0)}); + try std.testing.expect(result = posit16_dot_product(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "posit16_vector_sum_simple" { + given vec = [_]Posit16{posit16_encode_f32(1.0), posit16_encode_f32(2.0), posit16_encode_f32(3.0), posit16_encode_f32(4.0)} + try std.testing.expect(result = posit16_vector_sum(vec)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "posit16_vector_mean_simple" { + given vec = [_]Posit16{posit16_encode_f32(2.0), posit16_encode_f32(4.0), posit16_encode_f32(6.0), posit16_encode_f32(8.0)} + try std.testing.expect(result = posit16_vector_mean(vec)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 5.0) < 1.0); +} + +test "posit16_lerp_zero" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = posit16_lerp(a, b, t)); + try std.testing.expect(posit16_is_equal(result, a) == true); +} + +test "posit16_lerp_one" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = posit16_lerp(a, b, t)); + try std.testing.expect(posit16_is_equal(result, b) == true); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant posit16_bits_constant + assert BITS == 16 + +invariant posit16_es_one + assert ES == 1 + +invariant posit16_nbits_fifteen + assert NBITS == 15 + +invariant posit16_zero_no_sign + assert (POS16_ZERO & SIGN_MASK) == 0 + +invariant posit16_nar_has_sign + assert (POS16_NAR & SIGN_MASK) != 0 + +invariant posit16_zero_nar_distinct + assert POS16_ZERO != POS16_NAR + +invariant posit16_abs_removes_sign + assert posit16_abs(POS16_NAR) != POS16_NAR // NaR preserves sign pattern + +invariant posit16_neg_zero_is_zero + assert posit16_neg(POS16_ZERO) == POS16_ZERO + +invariant posit16_neg_nar_is_nar + assert posit16_neg(POS16_NAR) == POS16_NAR + +invariant posit16_add_zero_identity + given value = posit16_encode_f32(50.0) + assert posit16_is_equal(posit16_add(value, POS16_ZERO), value) == true + +invariant posit16_sub_zero_identity + given value = posit16_encode_f32(50.0) + assert posit16_is_equal(posit16_sub(value, POS16_ZERO), value) == true + +invariant posit16_mul_zero_zero + given value = posit16_encode_f32(50.0) + assert posit16_mul(value, POS16_ZERO) == POS16_ZERO + +invariant posit16_div_nar_propagates + given value = posit16_encode_f32(50.0) + assert posit16_is_nar(posit16_div(POS16_NAR, value)) == true + +invariant posit16_relu_non_negative + given result = posit16_relu(posit16_encode_f32(50.0)) + try std.testing.expect(posit16_extract_sign(result) == false); + +invariant posit16_sqrt_non_negative + given result = posit16_sqrt(posit16_encode_f32(4.0)) + try std.testing.expect(posit16_extract_sign(result) == false); + +invariant posit16_quantize_to_int4_in_range + given result = posit16_quantize_to_int4(posit16_encode_f32(50.0)) + assert result >= -8 and result <= 7 + +invariant posit16_scale_roundtrip + given value = posit16_encode_f32(50.0) + try std.testing.expect(scale = 2.0); + try std.testing.expect(scaled = posit16_scale(value, scale)); + try std.testing.expect(decoded = posit16_to_f32_scaled(scaled, scale)); + try std.testing.expect(abs(decoded - posit16_decode_f32(value)) < 5.0); + +invariant posit16_max_ge_both + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_max(a, b)); + try std.testing.expect(posit16_is_greater(result, a) == true or posit16_is_equal(result, a) == true); + +invariant posit16_min_le_both + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_min(a, b)); + try std.testing.expect(posit16_is_greater(b, result) == true or posit16_is_equal(b, result) == true); + +invariant posit16_lerp_bounded + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result_half = posit16_lerp(a, b, 0.5)); + try std.testing.expect(decoded_a = posit16_decode_f32(a)); + try std.testing.expect(decoded_b = posit16_decode_f32(b)); + try std.testing.expect(decoded_half = posit16_decode_f32(result_half)); + try std.testing.expect(decoded_half > decoded_a and decoded_half < decoded_b); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench posit16_encode_f32_latency + measure: nanoseconds to posit16_encode_f32(10.5) + target: < 50ns + +bench posit16_decode_f32_latency + measure: nanoseconds to posit16_decode_f32(0x4000) + target: < 50ns + +bench posit16_add_latency + measure: nanoseconds to posit16_add(0x4000, 0x4800) + target: < 100ns + +bench posit16_mul_latency + measure: nanoseconds to posit16_mul(0x4000, 0x4000) + target: < 100ns + +bench posit16_sqrt_latency + measure: nanoseconds to posit16_sqrt(0x4000) + target: < 100ns + +bench posit16_sigmoid_latency + measure: nanoseconds to posit16_sigmoid(0x4000) + target: < 200ns + +bench posit16_relu_latency + measure: nanoseconds to posit16_relu(0x4800) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/purkinje_thermal_gate.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/purkinje_thermal_gate.t27 new file mode 100644 index 0000000000..1c814329e1 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/purkinje_thermal_gate.t27 @@ -0,0 +1,704 @@ +// SPDX-License-Identifier: Apache-2.0 +; purkinje_thermal_gate.t27 — Purkinje Thermal Gate +; Thermally-gated activation inspired by Purkinje neural dynamics +; φ² + 1/φ² = 3 | TRINITY + +module purkinje-thermal-gate; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const PTG_OPCODE : u8 = 0xF4; // Purkinje Thermal Gate opcode + +pub const TEMP_MIN_C : i8 = -40; // -40°C minimum +pub const TEMP_MAX_C : i8 = 125; // 125°C maximum +pub const TEMP_NOMINAL_C : i8 = 25; // 25°C nominal + +pub const TEMP_THRESHOLD_LOW_C : i8 = 0; // 0°C low threshold +pub const TEMP_THRESHOLD_HIGH_C : i8 = 85; // 85°C high threshold +pub const TEMP_CRITICAL_C : i8 = 100; // 100°C critical + +pub const TEMP_HYSTERESIS_C : i8 = 5; // 5°C hysteresis +pub const TEMP_SETTLING_MS : u16 = 100; // 100ms settling time + +pub const GATE_STATE_CLOSED : u8 = 0; +pub const GATE_STATE_OPEN : u8 = 1; +pub const GATE_STATE_THROTTLING : u8 = 2; +pub const GATE_STATE_ERROR : u8 = 3; + +pub const THROTTLE_LEVEL_NONE : u8 = 0; +pub const THROTTLE_LEVEL_LOW : u8 = 1; +pub const THROTTLE_LEVEL_MED : u8 = 2; +pub const THROTTLE_LEVEL_HIGH : u8 = 3; +pub const THROTTLE_LEVEL_MAX : u8 = 4; + +// ============================================================================ +// Types +// ============================================================================ + +pub const GateState = enum(u8) { + closed = GATE_STATE_CLOSED, + open = GATE_STATE_OPEN, + throttling = GATE_STATE_THROTTLING, + error = GATE_STATE_ERROR, +} + +pub const ThrottleLevel = enum(u8) { + none = THROTTLE_LEVEL_NONE, + low = THROTTLE_LEVEL_LOW, + medium = THROTTLE_LEVEL_MED, + high = THROTTLE_LEVEL_HIGH, +} + +pub const ThermalState = struct { + temp_c : i8, + gate_state : GateState, + throttle_level : ThrottleLevel, + error_code : u8, +} + +pub const GateConfig = struct { + low_threshold_c : i8, + high_threshold_c : i8, + critical_temp_c : i8, + hysteresis_c : i8, + auto_throttle : bool, +} + +pub const GateStatus = struct { + current_state : GateState, + temp_measured_c : i8, + throttle_level : ThrottleLevel, + cycle_count : u32, + fault_count : u8, +} + +// ============================================================================ +// Config Functions +// ============================================================================ + +// ptg_config_init() -> GateConfig +// Initialize Purkinje thermal gate config +pub fn ptg_config_init() -> GateConfig { + return GateConfig { + .low_threshold_c = TEMP_THRESHOLD_LOW_C, + .high_threshold_c = TEMP_THRESHOLD_HIGH_C, + .critical_temp_c = TEMP_CRITICAL_C, + .hysteresis_c = TEMP_HYSTERESIS_C, + .auto_throttle = true, + }; +} + +// ptg_config_strict() -> GateConfig +// Create strict thermal gate config (lower thresholds) +pub fn ptg_config_strict() -> GateConfig { + return GateConfig { + .low_threshold_c = 0, + .high_threshold_c = 70, + .critical_temp_c = 90, + .hysteresis_c = 3, + .auto_throttle = true, + }; +} + +// ptg_config_permissive() -> GateConfig +// Create permissive thermal gate config (higher thresholds) +pub fn ptg_config_permissive() -> GateConfig { + return GateConfig { + .low_threshold_c = -20, + .high_threshold_c = 100, + .critical_temp_c = 115, + .hysteresis_c = 10, + .auto_throttle = true, + }; +} + +// ptg_config_manual() -> GateConfig +// Create manual config (no auto throttle) +pub fn ptg_config_manual() -> GateConfig { + return GateConfig { + .low_threshold_c = TEMP_THRESHOLD_LOW_C, + .high_threshold_c = TEMP_THRESHOLD_HIGH_C, + .critical_temp_c = TEMP_CRITICAL_C, + .hysteresis_c = TEMP_HYSTERESIS_C, + .auto_throttle = false, + }; +} + +// ============================================================================ +// Temperature Validation +// ============================================================================ + +// ptg_temp_valid(temp_c: i8) -> bool +// Check if temperature is valid +pub fn ptg_temp_valid(temp_c: i8) -> bool { + return temp_c >= TEMP_MIN_C and temp_c <= TEMP_MAX_C; +} + +// ptg_temp_critical(temp_c: i8, config: GateConfig) -> bool +// Check if temperature is critical +pub fn ptg_temp_critical(temp_c: i8, config: GateConfig) -> bool { + return temp_c >= config.critical_temp_c; +} + +// ptg_temp_high(temp_c: i8, config: GateConfig) -> bool +// Check if temperature is high (above high threshold) +pub fn ptg_temp_high(temp_c: i8, config: GateConfig) -> bool { + return temp_c >= config.high_threshold_c; +} + +// ptg_temp_low(temp_c: i8, config: GateConfig) -> bool +// Check if temperature is low (below low threshold) +pub fn ptg_temp_low(temp_c: i8, config: GateConfig) -> bool { + return temp_c < config.low_threshold_c; +} + +// ptg_temp_in_hysteresis(temp_c: i8, threshold: i8, hysteresis: i8) -> bool +// Check if temperature is within hysteresis band +pub fn ptg_temp_in_hysteresis(temp_c: i8, threshold: i8, hysteresis: i8) -> bool { + if (temp_c >= threshold) { + return (temp_c - threshold) <= hysteresis; + } else { + return (threshold - temp_c) <= hysteresis; + } +} + +// ============================================================================ +// Gate State Functions +// ============================================================================ + +// ptg_thermal_state_init() -> ThermalState +// Initialize thermal state +pub fn ptg_thermal_state_init() -> ThermalState { + return ThermalState { + .temp_c = TEMP_NOMINAL_C, + .gate_state = GateState.open, + .throttle_level = ThrottleLevel.none, + .error_code = 0, + }; +} + +// ptg_gate_state_from_temp(temp_c: i8, config: GateConfig) -> GateState +// Determine gate state from temperature +pub fn ptg_gate_state_from_temp(temp_c: i8, config: GateConfig) -> GateState { + if (not ptg_temp_valid(temp_c)) { + return GateState.error; + } else if (ptg_temp_critical(temp_c, config)) { + return GateState.error; + } else if (ptg_temp_high(temp_c, config)) { + return GateState.throttling; + } else if (ptg_temp_low(temp_c, config)) { + return GateState.closed; + } else { + return GateState.open; + } +} + +// ptg_throttle_from_temp(temp_c: i8, config: GateConfig) -> ThrottleLevel +// Determine throttle level from temperature +pub fn ptg_throttle_from_temp(temp_c: i8, config: GateConfig) -> ThrottleLevel { + if (ptg_temp_critical(temp_c, config)) { + return ThrottleLevel.high; + } else if (temp_c >= config.high_threshold_c + 20) { + return ThrottleLevel.high; + } else if (temp_c >= config.high_threshold_c + 10) { + return ThrottleLevel.medium; + } else if (temp_c >= config.high_threshold_c) { + return ThrottleLevel.low; + } else { + return ThrottleLevel.none; + } +} + +// ptg_thermal_state_update(thermal: ThermalState, temp_c: i8, config: GateConfig) -> ThermalState +// Update thermal state with new temperature +pub fn ptg_thermal_state_update(thermal: ThermalState, temp_c: i8, config: GateConfig) -> ThermalState { + var new_state = thermal; + new_state.temp_c = temp_c; + new_state.gate_state = ptg_gate_state_from_temp(temp_c, config); + + if (config.auto_throttle) { + new_state.throttle_level = ptg_throttle_from_temp(temp_c, config); + } + + if (new_state.gate_state == GateState.error) { + new_state.error_code = 1; // Thermal error + } else if (new_state.gate_state != GateState.error) { + new_state.error_code = 0; + } + + return new_state; +} + +// ============================================================================ +// Gate Status Functions +// ============================================================================ + +// ptg_gate_status_init() -> GateStatus +// Initialize gate status +pub fn ptg_gate_status_init() -> GateStatus { + return GateStatus { + .current_state = GateState.open, + .temp_measured_c = TEMP_NOMINAL_C, + .throttle_level = ThrottleLevel.none, + .cycle_count = 0, + .fault_count = 0, + }; +} + +// ptg_gate_status_open(status: GateStatus) -> GateStatus +// Open the gate +pub fn ptg_gate_status_open(status: GateStatus) -> GateStatus { + return GateStatus { + .current_state = GateState.open, + .temp_measured_c = status.temp_measured_c, + .throttle_level = ThrottleLevel.none, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count, + }; +} + +// ptg_gate_status_close(status: GateStatus) -> GateStatus +// Close the gate +pub fn ptg_gate_status_close(status: GateStatus) -> GateStatus { + return GateStatus { + .current_state = GateState.closed, + .temp_measured_c = status.temp_measured_c, + .throttle_level = ThrottleLevel.high, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count, + }; +} + +// ptg_gate_status_throttle(status: GateStatus, level: ThrottleLevel) -> GateStatus +// Set gate to throttling state +pub fn ptg_gate_status_throttle(status: GateStatus, level: ThrottleLevel) -> GateStatus { + return GateStatus { + .current_state = GateState.throttling, + .temp_measured_c = status.temp_measured_c, + .throttle_level = level, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count, + }; +} + +// ptg_gate_status_error(status: GateStatus) -> GateStatus +// Set gate to error state +pub fn ptg_gate_status_error(status: GateStatus) -> GateStatus { + return GateStatus { + .current_state = GateState.error, + .temp_measured_c = status.temp_measured_c, + .throttle_level = ThrottleLevel.high, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count + 1, + }; +} + +// ptg_gate_status_update_temp(status: GateStatus, temp_c: i8) -> GateStatus +// Update measured temperature +pub fn ptg_gate_status_update_temp(status: GateStatus, temp_c: i8) -> GateStatus { + return GateStatus { + .current_state = status.current_state, + .temp_measured_c = temp_c, + .throttle_level = status.throttle_level, + .cycle_count = status.cycle_count, + .fault_count = status.fault_count, + }; +} + +// ptg_gate_status_increment_cycle(status: GateStatus) -> GateStatus +// Increment cycle counter +pub fn ptg_gate_status_increment_cycle(status: GateStatus) -> GateStatus { + return GateStatus { + .current_state = status.current_state, + .temp_measured_c = status.temp_measured_c, + .throttle_level = status.throttle_level, + .cycle_count = status.cycle_count + 1, + .fault_count = status.fault_count, + }; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_ptg_cmd(opcode: u8, temp_c: i8, threshold: u8) -> u16 +// Encode Purkinje thermal gate command +pub fn encode_ptg_cmd(opcode: u8, temp_c: i8, threshold: u8) u16 { + // Format: [OP:8][TEMP_SIGNED:7][THRESHOLD:1] + const op_field : u16 = @as(u16, opcode) << 8; + const temp_field : u16 = @as(u16, @as(u8, @bitCast(temp_c))) << 1; + const thresh_field : u16 = @as(u16, threshold & 0x01); + return op_field | temp_field | thresh_field; +} + +// decode_ptg_cmd(encoded: u16) -> struct { opcode: u8, temp_c: i8, threshold: u8 } +// Decode Purkinje thermal gate command +pub fn decode_ptg_cmd(encoded: u16) -> struct { opcode: u8, temp_c: i8, threshold: u8 } { + const opcode : u8 = @as(u8, @truncate((encoded >> 8) & 0xFF)); + const temp_u8 : u8 = @as(u8, @truncate((encoded >> 1) & 0x7F)); + const temp_c : i8 = @bitCast(temp_u8); + const threshold : u8 = @as(u8, @truncate(encoded & 0x01)); + return .{ .opcode = opcode, .temp_c = temp_c, .threshold = threshold }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "ptg_opcode_constant" { + try std.testing.expect(PTG_OPCODE == 0xF4); +} + +test "temp_constants" { + try std.testing.expect(TEMP_MIN_C == -40); + try std.testing.expect(TEMP_MAX_C == 125); + try std.testing.expect(TEMP_NOMINAL_C == 25); +} + +test "temp_threshold_constants" { + try std.testing.expect(TEMP_THRESHOLD_LOW_C == 0); + try std.testing.expect(TEMP_THRESHOLD_HIGH_C == 85); + try std.testing.expect(TEMP_CRITICAL_C == 100); +} + +test "temp_hysteresis_constant" { + try std.testing.expect(TEMP_HYSTERESIS_C == 5); +} + +test "gate_state_constants" { + try std.testing.expect(GATE_STATE_CLOSED == 0); + try std.testing.expect(GATE_STATE_OPEN == 1); + try std.testing.expect(GATE_STATE_THROTTLING == 2); + try std.testing.expect(GATE_STATE_ERROR == 3); +} + +test "throttle_level_constants" { + try std.testing.expect(THROTTLE_LEVEL_NONE == 0); + try std.testing.expect(THROTTLE_LEVEL_LOW == 1); + try std.testing.expect(THROTTLE_LEVEL_MED == 2); + try std.testing.expect(THROTTLE_LEVEL_HIGH == 3); +} + +test "ptg_config_init_structure" { + given config = ptg_config_init() + try std.testing.expect(config.low_threshold_c == TEMP_THRESHOLD_LOW_C); + try std.testing.expect(config.high_threshold_c == TEMP_THRESHOLD_HIGH_C); + try std.testing.expect(config.auto_throttle == true); +} + +test "ptg_config_strict_structure" { + given config = ptg_config_strict() + try std.testing.expect(config.high_threshold_c == 70); + try std.testing.expect(config.critical_temp_c == 90); +} + +test "ptg_config_permissive_structure" { + given config = ptg_config_permissive() + try std.testing.expect(config.high_threshold_c == 100); + try std.testing.expect(config.critical_temp_c == 115); +} + +test "ptg_config_manual_structure" { + given config = ptg_config_manual() + try std.testing.expect(config.auto_throttle == false); +} + +test "ptg_temp_valid_true" { + try std.testing.expect(ptg_temp_valid(0) == true); + try std.testing.expect(ptg_temp_valid(25) == true); + try std.testing.expect(ptg_temp_valid(100) == true); +} + +test "ptg_temp_valid_false" { + try std.testing.expect(ptg_temp_valid(-50) == false); + try std.testing.expect(ptg_temp_valid(150) == false); +} + +test "ptg_temp_critical_true" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_critical(105, config) == true); + try std.testing.expect(ptg_temp_critical(100, config) == true); +} + +test "ptg_temp_critical_false" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_critical(90, config) == false); +} + +test "ptg_temp_high_true" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_high(90, config) == true); + try std.testing.expect(ptg_temp_high(85, config) == true); +} + +test "ptg_temp_high_false" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_high(80, config) == false); +} + +test "ptg_temp_low_true" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_low(-10, config) == true); + try std.testing.expect(ptg_temp_low(-1, config) == true); +} + +test "ptg_temp_low_false" { + given config = ptg_config_init() + try std.testing.expect(ptg_temp_low(0, config) == false); + try std.testing.expect(ptg_temp_low(10, config) == false); +} + +test "ptg_temp_in_hysteresis_true" { + try std.testing.expect(ptg_temp_in_hysteresis(85, 85, 5) == true); + try std.testing.expect(ptg_temp_in_hysteresis(87, 85, 5) == true); + try std.testing.expect(ptg_temp_in_hysteresis(83, 85, 5) == true); +} + +test "ptg_temp_in_hysteresis_false" { + try std.testing.expect(ptg_temp_in_hysteresis(95, 85, 5) == false); + try std.testing.expect(ptg_temp_in_hysteresis(75, 85, 5) == false); +} + +test "ptg_thermal_state_init_structure" { + given thermal = ptg_thermal_state_init() + try std.testing.expect(thermal.temp_c == TEMP_NOMINAL_C); + try std.testing.expect(thermal.gate_state == GateState.open); +} + +test "ptg_gate_state_from_temp_open" { + given config = ptg_config_init() + try std.testing.expect(ptg_gate_state_from_temp(50, config) == GateState.open); +} + +test "ptg_gate_state_from_temp_throttling" { + given config = ptg_config_init() + try std.testing.expect(ptg_gate_state_from_temp(90, config) == GateState.throttling); +} + +test "ptg_gate_state_from_temp_closed" { + given config = ptg_config_init() + try std.testing.expect(ptg_gate_state_from_temp(-10, config) == GateState.closed); +} + +test "ptg_gate_state_from_temp_error" { + given config = ptg_config_init() + try std.testing.expect(ptg_gate_state_from_temp(105, config) == GateState.error); +} + +test "ptg_throttle_from_temp_none" { + given config = ptg_config_init() + try std.testing.expect(ptg_throttle_from_temp(50, config) == ThrottleLevel.none); +} + +test "ptg_throttle_from_temp_low" { + given config = ptg_config_init() + try std.testing.expect(ptg_throttle_from_temp(90, config) == ThrottleLevel.low); +} + +test "ptg_throttle_from_temp_medium" { + given config = ptg_config_init() + try std.testing.expect(ptg_throttle_from_temp(100, config) == ThrottleLevel.medium); +} + +test "ptg_throttle_from_temp_high" { + given config = ptg_config_init() + try std.testing.expect(ptg_throttle_from_temp(110, config) == ThrottleLevel.high); +} + +test "ptg_thermal_state_update" { + given thermal = ptg_thermal_state_init() + try std.testing.expect(config = ptg_config_init()); + try std.testing.expect(result = ptg_thermal_state_update(thermal, 90, config)); + try std.testing.expect(result.temp_c == 90); + try std.testing.expect(result.gate_state == GateState.throttling); +} + +test "ptg_gate_status_init_structure" { + given status = ptg_gate_status_init() + try std.testing.expect(status.current_state == GateState.open); + try std.testing.expect(status.fault_count == 0); +} + +test "ptg_gate_status_open" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_open(status)); + try std.testing.expect(result.current_state == GateState.open); + try std.testing.expect(result.throttle_level == ThrottleLevel.none); +} + +test "ptg_gate_status_close" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_close(status)); + try std.testing.expect(result.current_state == GateState.closed); + try std.testing.expect(result.throttle_level == ThrottleLevel.high); +} + +test "ptg_gate_status_throttle" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_throttle(status, ThrottleLevel.medium)); + try std.testing.expect(result.current_state == GateState.throttling); + try std.testing.expect(result.throttle_level == ThrottleLevel.medium); +} + +test "ptg_gate_status_error" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_error(status)); + try std.testing.expect(result.current_state == GateState.error); + try std.testing.expect(result.fault_count == 1); +} + +test "ptg_gate_status_update_temp" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_update_temp(status, 75)); + try std.testing.expect(result.temp_measured_c == 75); +} + +test "ptg_gate_status_increment_cycle" { + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_increment_cycle(status)); + try std.testing.expect(result.cycle_count == 1); +} + +test "encode_ptg_cmd" { + given encoded = encode_ptg_cmd(0x10, 25, 1) + try std.testing.expect((encoded >> 8) == 0x10); +} + +test "decode_ptg_cmd" { + given decoded = decode_ptg_cmd(0x1033) + try std.testing.expect(decoded.opcode == 0x10); + try std.testing.expect(decoded.temp_c == 25); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant temp_range_valid + assert TEMP_MIN_C < TEMP_MAX_C + try std.testing.expect(TEMP_NOMINAL_C > TEMP_MIN_C); + try std.testing.expect(TEMP_NOMINAL_C < TEMP_MAX_C); + +invariant temp_thresholds_ordered + assert TEMP_THRESHOLD_LOW_C < TEMP_THRESHOLD_HIGH_C + try std.testing.expect(TEMP_THRESHOLD_HIGH_C < TEMP_CRITICAL_C); + +invariant temp_hysteresis_positive + assert TEMP_HYSTERESIS_C > 0 + +invariant ptg_config_init_valid + given config = ptg_config_init() + assert config.low_threshold_c < config.high_threshold_c + try std.testing.expect(config.high_threshold_c < config.critical_temp_c); + +invariant ptg_config_strict_lower_thresholds + given strict = ptg_config_strict() + try std.testing.expect(normal = ptg_config_init()); + assert strict.high_threshold_c < normal.high_threshold_c + try std.testing.expect(strict.critical_temp_c < normal.critical_temp_c); + +invariant ptg_config_permissive_higher_thresholds + given perm = ptg_config_permissive() + try std.testing.expect(normal = ptg_config_init()); + assert perm.high_threshold_c > normal.high_threshold_c + try std.testing.expect(perm.critical_temp_c > normal.critical_temp_c); + +invariant ptg_config_manual_no_auto_throttle + given config = ptg_config_manual() + assert config.auto_throttle == false + +invariant ptg_thermal_state_init_open + given thermal = ptg_thermal_state_init() + try std.testing.expect(thermal.gate_state == GateState.open); + +invariant ptg_gate_state_from_temp_nominal_open + given config = ptg_config_init() + try std.testing.expect(state = ptg_gate_state_from_temp(TEMP_NOMINAL_C, config)); + try std.testing.expect(state == GateState.open); + +invariant ptg_throttle_from_temp_nominal_none + given config = ptg_config_init() + try std.testing.expect(level = ptg_throttle_from_temp(TEMP_NOMINAL_C, config)); + try std.testing.expect(level == ThrottleLevel.none); + +invariant ptg_gate_status_init_open + given status = ptg_gate_status_init() + try std.testing.expect(status.current_state == GateState.open); + +invariant ptg_gate_status_error_increments_fault + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_error(status)); + try std.testing.expect(result.fault_count == 1); + +invariant ptg_gate_status_increment_cycle_increments + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_increment_cycle(status)); + try std.testing.expect(result.cycle_count == 1); + +invariant ptg_gate_status_close_high_throttle + given status = ptg_gate_status_init() + try std.testing.expect(result = ptg_gate_status_close(status)); + try std.testing.expect(result.throttle_level == ThrottleLevel.high); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench ptg_config_init_latency + measure: nanoseconds to ptg_config_init() + target: < 20ns + +bench ptg_temp_valid_latency + measure: nanoseconds to ptg_temp_valid(25) + target: < 10ns + +bench ptg_temp_critical_latency + measure: nanoseconds to ptg_temp_critical(100, ptg_config_init()) + target: < 15ns + +bench ptg_temp_high_latency + measure: nanoseconds to ptg_temp_high(90, ptg_config_init()) + target: < 15ns + +bench ptg_temp_low_latency + measure: nanoseconds to ptg_temp_low(-10, ptg_config_init()) + target: < 15ns + +bench ptg_temp_in_hysteresis_latency + measure: nanoseconds to ptg_temp_in_hysteresis(87, 85, 5) + target: < 15ns + +bench ptg_thermal_state_init_latency + measure: nanoseconds to ptg_thermal_state_init() + target: < 20ns + +bench ptg_gate_state_from_temp_latency + measure: nanoseconds to ptg_gate_state_from_temp(50, ptg_config_init()) + target: < 20ns + +bench ptg_throttle_from_temp_latency + measure: nanoseconds to ptg_throttle_from_temp(90, ptg_config_init()) + target: < 20ns + +bench ptg_thermal_state_update_latency + measure: nanoseconds to ptg_thermal_state_update(ptg_thermal_state_init(), 90, ptg_config_init()) + target: < 30ns + +bench ptg_gate_status_init_latency + measure: nanoseconds to ptg_gate_status_init() + target: < 20ns + +bench ptg_gate_status_open_latency + measure: nanoseconds to ptg_gate_status_open(ptg_gate_status_init()) + target: < 15ns + +bench encode_ptg_cmd_latency + measure: nanoseconds to encode_ptg_cmd(0x10, 25, 1) + target: < 20ns + +bench decode_ptg_cmd_latency + measure: nanoseconds to decode_ptg_cmd(0x1033) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/sparse_mask.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/sparse_mask.t27 new file mode 100644 index 0000000000..49c8fb1357 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/sparse_mask.t27 @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: Apache-2.0 +; sparse_mask.t27 — Sacred Opcode 0xE8: Sparse Mask (Sparse Skip 2) +; Hardware for generating and applying sparse tensor masks +; φ² + 1/φ² = 3 | TRINITY + +module sacred-sparse_mask; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_SPARSE_MASK : u8 = 0xE8; + +pub const MASK_SIZE : u8 = 16; +pub const SPARSE_THRESHOLD_RATIO : u8 = 50; // 50% sparsity threshold + +pub const MASK_MODE_GEN : u8 = 0; +pub const MASK_MODE_APPLY : u8 = 1; +pub const MASK_MODE_COMBINE : u8 = 2; +pub const MASK_MODE_INVERT : u8 = 3; + +pub const MASK_TYPE_DENSITY : u8 = 0; +pub const MASK_TYPE_PATTERN : u8 = 1; +pub const MASK_TYPE_RANDOM : u8 = 2; +pub const MASK_TYPE_PHI : u8 = 3; // φ-optimized mask + +// ============================================================================ +// Types +// ============================================================================ + +pub const SparseMask = struct { + bits : u16, + density : u8, // 0-100%, percentage of set bits + pattern_type : u8, +} + +pub const MaskConfig = struct { + mode : u8, + mask_type : u8, + threshold : u8, + phi_optimized : bool, +} + +pub const MaskOp = struct { + result : SparseMask, + input_mask : u16, + output_mask : u16, + operation_count : u8, +} + +pub const Vector16 = struct { + values : [MASK_SIZE]i8, + mask : u16, +} + +// ============================================================================ +// Mask Generation Functions +// ============================================================================ + +// mask_count_set_bits(mask: u16) -> u8 +// Count number of set bits in mask +pub fn mask_count_set_bits(mask: u16) -> u8 { + var count : u8 = 0; + var m = mask; + while (m != 0) { + count += 1; + m &= m - 1; + } + return count; +} + +// mask_count_clear_bits(mask: u16) -> u8 +// Count number of clear bits in mask +pub fn mask_count_clear_bits(mask: u16) -> u8 { + return MASK_SIZE - mask_count_set_bits(mask); +} + +// mask_density(mask: u16) -> u8 +// Calculate mask density (0-100%) +pub fn mask_density(mask: u16) -> u8 { + const set_bits = mask_count_set_bits(mask); + return (set_bits * 100) / MASK_SIZE; +} + +// mask_is_sparse(mask: u16) -> bool +// Check if mask is sparse (< threshold) +pub fn mask_is_sparse(mask: u16) -> bool { + return mask_density(mask) < SPARSE_THRESHOLD_RATIO; +} + +// mask_is_dense(mask: u16) -> bool +// Check if mask is dense (>= threshold) +pub fn mask_is_dense(mask: u16) -> bool { + return mask_density(mask) >= SPARSE_THRESHOLD_RATIO; +} + +// mask_generate_density(density: u8) -> u16 +// Generate mask with specific density (0-100%) +pub fn mask_generate_density(density: u8) -> u16 { + const target_bits = (density * MASK_SIZE) / 100; + var mask : u16 = 0; + var i : u8 = 0; + + // Set first target_bits bits + while (i < target_bits) { + mask |= (@as(u16, 1) << i); + i += 1; + } + + return mask; +} + +// mask_generate_phi() -> u16 +// Generate φ-optimized mask (approximate golden ratio distribution) +pub fn mask_generate_phi() -> u16 { + // φ ≈ 1.618, use pattern based on Fibonacci sequence + // Set bits at positions: 0, 1, 2, 3, 5, 8, 13 (first few Fib numbers) + var mask : u16 = 0; + mask |= (@as(u16, 1) << 0); + mask |= (@as(u16, 1) << 1); + mask |= (@as(u16, 1) << 2); + mask |= (@as(u16, 1) << 3); + mask |= (@as(u16, 1) << 5); + mask |= (@as(u16, 1) << 8); + mask |= (@as(u16, 1) << 13); + return mask; +} + +// mask_generate_pattern(pattern: u8) -> u16 +// Generate mask with repeating pattern +pub fn mask_generate_pattern(pattern: u8) -> u16 { + var mask : u16 = 0; + const pattern_bits : u8 = 4; + + for (0..MASK_SIZE) |i| { + if ((pattern >> (i % pattern_bits)) & 1 != 0) { + mask |= (@as(u16, 1) << i); + } + } + + return mask; +} + +// mask_generate_random(seed: u16) -> u16 +// Generate pseudo-random mask using LFSR +pub fn mask_generate_random(seed: u16) -> u16 { + var lfsr = seed; + var mask : u16 = 0; + + for (0..MASK_SIZE) |i| { + const bit = (lfsr >> 0) & 1; + if (bit != 0) { + mask |= (@as(u16, 1) << i); + } + // LFSR feedback (X^16 + X^14 + X^13 + X^11) + const feedback = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5)) & 1; + lfsr = (lfsr >> 1) | (feedback << 15); + } + + return mask; +} + +// ============================================================================ +// Mask Operation Functions +// ============================================================================ + +// mask_generate(config: MaskConfig, seed: u16) -> SparseMask +// Generate mask based on config +pub fn mask_generate(config: MaskConfig, seed: u16) -> SparseMask { + var mask : u16 = 0; + var pattern_type : u8 = config.mask_type; + + switch (config.mask_type) { + MASK_TYPE_DENSITY => { + mask = mask_generate_density(config.threshold); + }, + MASK_TYPE_PATTERN => { + mask = mask_generate_pattern(config.threshold); + }, + MASK_TYPE_RANDOM => { + mask = mask_generate_random(seed); + }, + MASK_TYPE_PHI => { + if (config.phi_optimized) { + mask = mask_generate_phi(); + } else { + mask = mask_generate_pattern(0x05); // Default pattern + } + }, + else => { + mask = mask_generate_pattern(0x0F); + }, + } + + return SparseMask { + .bits = mask, + .density = mask_density(mask), + .pattern_type = pattern_type, + }; +} + +// mask_apply(vector: Vector16, mask: SparseMask) -> Vector16 +// Apply mask to vector (zero out masked elements) +pub fn mask_apply(vector: Vector16, mask: SparseMask) -> Vector16 { + var result : Vector16 = undefined; + result.mask = mask.bits; + + for (vector.values, 0..) |val, i| { + if ((mask.bits >> i) & 1 != 0) { + result.values[i] = val; + } else { + result.values[i] = 0; + } + } + + return result; +} + +// mask_combine(mask1: u16, mask2: u16, operation: u8) -> u16 +// Combine two masks +pub fn mask_combine(mask1: u16, mask2: u16, operation: u8) -> u16 { + switch (operation) { + 0 => return mask1 & mask2, // AND + 1 => return mask1 | mask2, // OR + 2 => return mask1 ^ mask2, // XOR + else => return mask1 & ~mask2, // NAND + } +} + +// mask_invert(mask: u16) -> u16 +// Invert mask +pub fn mask_invert(mask: u16) -> u16 { + return ~mask & ((@as(u16, 1) << MASK_SIZE) - 1); +} + +// mask_complement(mask: SparseMask) -> SparseMask +// Get complement of mask (inverse) +pub fn mask_complement(mask: SparseMask) -> SparseMask { + const inverted = mask_invert(mask.bits); + return SparseMask { + .bits = inverted, + .density = mask_density(inverted), + .pattern_type = mask.pattern_type, + }; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_sparse_mask(mode: u8, mask_type: u8, seed: u8) -> u16 +// Encode sparse mask instruction +pub fn encode_sparse_mask(mode: u8, mask_type: u8, seed: u8) u16 { + // Format: [OP:8][MODE:2][TYPE:2][SEED:4] + const op : u16 = @as(u16, OP_SPARSE_MASK) << 8; + const mode_field : u16 = @as(u16, mode & 0x03) << 6; + const type_field : u16 = @as(u16, mask_type & 0x03) << 4; + const seed_field : u16 = @as(u16, seed & 0x0F); + return op | mode_field | type_field | seed_field; +} + +// decode_sparse_mask(encoded: u16) -> struct { mode: u8, mask_type: u8, seed: u8 } +// Decode sparse mask instruction +pub fn decode_sparse_mask(encoded: u16) struct { mode: u8, mask_type: u8, seed: u8 } { + const mode : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const mask_type : u8 = @as(u8, @truncate((encoded >> 4) & 0x03)); + const seed : u8 = @as(u8, @truncate(encoded & 0x0F)); + return .{ .mode = mode, .mask_type = mask_type, .seed = seed }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "mask_size_sixteen" { + try std.testing.expect(MASK_SIZE == 16); +} + +test "sparse_threshold_fifty" { + try std.testing.expect(SPARSE_THRESHOLD_RATIO == 50); +} + +test "mask_mode_gen" { + assert MASK_MODE_GEN == 0 +} + +test "mask_mode_apply" { + assert MASK_MODE_APPLY == 1 +} + +test "mask_type_phi" { + assert MASK_TYPE_PHI == 3 +} + +test "mask_count_set_bits_all" { + try std.testing.expect(mask_count_set_bits(0xFFFF) == 16); +} + +test "mask_count_set_bits_none" { + try std.testing.expect(mask_count_set_bits(0x0000) == 0); +} + +test "mask_count_set_bits_half" { + try std.testing.expect(mask_count_set_bits(0x00FF) == 8); +} + +test "mask_count_clear_bits_all" { + try std.testing.expect(mask_count_clear_bits(0xFFFF) == 0); +} + +test "mask_count_clear_bits_none" { + try std.testing.expect(mask_count_clear_bits(0x0000) == 16); +} + +test "mask_density_zero" { + try std.testing.expect(mask_density(0x0000) == 0); +} + +test "mask_density_fifty" { + try std.testing.expect(mask_density(0x00FF) == 50); +} + +test "mask_density_full" { + try std.testing.expect(mask_density(0xFFFF) == 100); +} + +test "mask_is_sparse_true" { + try std.testing.expect(mask_is_sparse(0x000F) == true); + try std.testing.expect(mask_is_sparse(0x003F) == true); +} + +test "mask_is_sparse_false" { + try std.testing.expect(mask_is_sparse(0x00FF) == false); + try std.testing.expect(mask_is_sparse(0xFFFF) == false); +} + +test "mask_is_dense_true" { + try std.testing.expect(mask_is_dense(0x00FF) == true); + try std.testing.expect(mask_is_dense(0xFFFF) == true); +} + +test "mask_is_dense_false" { + try std.testing.expect(mask_is_dense(0x000F) == false); + try std.testing.expect(mask_is_dense(0x003F) == false); +} + +test "mask_generate_density_zero" { + try std.testing.expect(mask_generate_density(0) == 0); +} + +test "mask_generate_density_full" { + try std.testing.expect(mask_generate_density(100) == 0xFFFF); +} + +test "mask_generate_density_fifty" { + try std.testing.expect(mask_count_set_bits(mask_generate_density(50)) == 8); +} + +test "mask_generate_phi" { + given mask = mask_generate_phi() + try std.testing.expect(mask_count_set_bits(mask) == 7); + try std.testing.expect(mask_density(mask) == 43 // 7/16 ≈ 43%); +} + +test "mask_generate_pattern_alternating" { + try std.testing.expect(mask_generate_pattern(0x05) == 0xAAAA); +} + +test "mask_generate_pattern_ones" { + try std.testing.expect(mask_generate_pattern(0x0F) == 0xFFFF); +} + +test "mask_generate_pattern_zeros" { + try std.testing.expect(mask_generate_pattern(0x00) == 0x0000); +} + +test "mask_generate_random_seeded" { + given mask1 = mask_generate_random(0x1234) + try std.testing.expect(mask2 = mask_generate_random(0x1234)); + try std.testing.expect(mask1 == mask2); +} + +test "mask_generate_random_different" { + given mask1 = mask_generate_random(0x1234) + try std.testing.expect(mask2 = mask_generate_random(0x4321)); + try std.testing.expect(mask1 != mask2); +} + +test "mask_generate_dense" { + given config = MaskConfig{.mode = MASK_MODE_GEN, .mask_type = MASK_TYPE_DENSITY, .threshold = 80, .phi_optimized = false} + try std.testing.expect(mask = mask_generate(config, 0)); + try std.testing.expect(mask.density >= 75); +} + +test "mask_generate_sparse" { + given config = MaskConfig{.mode = MASK_MODE_GEN, .mask_type = MASK_TYPE_DENSITY, .threshold = 20, .phi_optimized = false} + try std.testing.expect(mask = mask_generate(config, 0)); + try std.testing.expect(mask.density <= 30); +} + +test "mask_apply_zero_mask" { + given vector = Vector16{.values = [_]i8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, .mask = 0xFFFF} + try std.testing.expect(mask_obj = SparseMask{.bits = 0x0000, .density = 0, .pattern_type = 0}); + try std.testing.expect(result = mask_apply(vector, mask_obj)); + try std.testing.expect(result.values[0] == 0); + try std.testing.expect(result.values[15] == 0); +} + +test "mask_apply_full_mask" { + given vector = Vector16{.values = [_]i8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, .mask = 0x0000} + try std.testing.expect(mask_obj = SparseMask{.bits = 0xFFFF, .density = 100, .pattern_type = 0}); + try std.testing.expect(result = mask_apply(vector, mask_obj)); + try std.testing.expect(result.values[0] == 1); + try std.testing.expect(result.values[15] == 16); +} + +test "mask_combine_and" { + try std.testing.expect(mask_combine(0xFF00, 0xF0F0, 0) == 0xF000); +} + +test "mask_combine_or" { + try std.testing.expect(mask_combine(0xFF00, 0xF0F0, 1) == 0xFFF0); +} + +test "mask_combine_xor" { + try std.testing.expect(mask_combine(0xFF00, 0xF0F0, 2) == 0x0FF0); +} + +test "mask_combine_nand" { + try std.testing.expect(mask_combine(0xFF00, 0xF0F0, 3) == 0x0FFF); +} + +test "mask_invert_full" { + try std.testing.expect(mask_invert(0xFFFF) == 0); +} + +test "mask_invert_zero" { + try std.testing.expect(mask_invert(0) == 0xFFFF); +} + +test "mask_complement_sparse_to_dense" { + given mask = SparseMask{.bits = 0x000F, .density = 25, .pattern_type = 0} + try std.testing.expect(result = mask_complement(mask)); + try std.testing.expect(result.density == 75); +} + +test "encode_sparse_mask" { + given encoded = encode_sparse_mask(1, 2, 0x0A) + try std.testing.expect((encoded >> 8) == OP_SPARSE_MASK); +} + +test "decode_sparse_mask" { + given decoded = decode_sparse_mask(0xE86A) + try std.testing.expect(decoded.mode == 1); + try std.testing.expect(decoded.mask_type == 2); + try std.testing.expect(decoded.seed == 0x0A); +} + +test "opcode_constant" { + try std.testing.expect(OP_SPARSE_MASK == 0xE8); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant mask_size_sixteen + assert MASK_SIZE == 16 + +invariant sparse_threshold_fifty + assert SPARSE_THRESHOLD_RATIO == 50 + +invariant mask_count_bits_sum + given mask = 0xAA55 + assert mask_count_set_bits(mask) + mask_count_clear_bits(mask) == MASK_SIZE + +invariant mask_density_range + try std.testing.expect(mask_density(0) == 0 and mask_density(0xFFFF) == 100); + +invariant mask_is_sparse_inverse_dense + given mask = 0x0F0F + assert mask_is_sparse(mask) != mask_is_dense(mask) + +invariant mask_is_complement_inverse + given mask = 0x1234 + try std.testing.expect(inverted = mask_invert(mask)); + try std.testing.expect(mask_invert(inverted) == mask); + +invariant mask_complement_density + given mask = SparseMask{.bits = 0x0F0F, .density = 50, .pattern_type = 0} + try std.testing.expect(result = mask_complement(mask)); + try std.testing.expect(result.density + mask.density == 100); + +invariant mask_combine_and_less_than_or + given m1 = 0xAAAA + try std.testing.expect(m2 = 0x5555); + try std.testing.expect(combined_and = mask_combine(m1, m2, 0)); + try std.testing.expect(combined_or = mask_combine(m1, m2, 1)); + try std.testing.expect(combined_and <= m1 and combined_and <= m2); + try std.testing.expect(combined_or >= m1 and combined_or >= m2); + +invariant mask_generate_phi_consistent + given mask1 = mask_generate_phi() + try std.testing.expect(mask2 = mask_generate_phi()); + try std.testing.expect(mask1 == mask2); + +invariant mask_generate_density_range + try std.testing.expect(mask_density(mask_generate_density(0)) == 0); + try std.testing.expect(mask_density(mask_generate_density(100)) == 100); + +invariant mask_apply_preserves_unmasked + given vector = Vector16{.values = [_]i8{1, 2, 3, 4} ** 4, .mask = 0x0000} + try std.testing.expect(mask_obj = SparseMask{.bits = 0x00FF, .density = 50, .pattern_type = 0}); + try std.testing.expect(result = mask_apply(vector, mask_obj)); + try std.testing.expect(result.values[0] == 1); + try std.testing.expect(result.values[7] == 8); + +invariant mask_apply_masks_zero + given vector = Vector16{.values = [_]i8{1, 2, 3, 4} ** 4, .mask = 0x0000} + try std.testing.expect(mask_obj = SparseMask{.bits = 0xFF00, .density = 50, .pattern_type = 0}); + try std.testing.expect(result = mask_apply(vector, mask_obj)); + try std.testing.expect(result.values[0] == 0); + try std.testing.expect(result.values[8] == 0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench mask_count_set_bits_latency + measure: nanoseconds to mask_count_set_bits(0xAAAA) + target: < 30ns + +bench mask_density_latency + measure: nanoseconds to mask_density(0x1234) + target: < 50ns + +bench mask_generate_phi_latency + measure: nanoseconds to mask_generate_phi() + target: < 50ns + +bench mask_generate_random_latency + measure: nanoseconds to mask_generate_random(0x1234) + target: < 100ns + +bench mask_apply_latency + measure: nanoseconds to mask_apply(Vector16{.values = [_]i8{1} ** 16, .mask = 0}, SparseMask{.bits = 0x00FF, .density = 50, .pattern_type = 0}) + target: < 100ns + +bench encode_sparse_mask_latency + measure: nanoseconds to encode_sparse_mask(1, 2, 0x0A) + target: < 20ns + +bench decode_sparse_mask_latency + measure: nanoseconds to decode_sparse_mask(0xE86A) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/sparse_skip.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/sparse_skip.t27 new file mode 100644 index 0000000000..cd5620c6b4 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/sparse_skip.t27 @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: Apache-2.0 +; sparse_skip.t27 — Sacred Opcode 0xE1: Sparse Skip Operation +; Hardware acceleration for sparse tensor operations with zero-skipping +; φ² + 1/φ² = 3 | TRINITY + +module sacred-sparse_skip; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_SPARSE_SKIP : u8 = 0xE1; + +pub const VECTOR_SIZE : u8 = 16; +pub const ZERO_THRESHOLD : u8 = 1; // Values <= this are considered zero +pub const SPARSE_TOLERANCE : u8 = 5; // Percentage tolerance + +pub const SKIP_NONE : u8 = 0; +pub const SKIP_ZERO : u8 = 1; +pub const SKIP_THRESHOLD : u8 = 2; +pub const SKIP_ALL : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const SkipMode = enum(u8) { + none = SKIP_NONE, + zero = SKIP_ZERO, + threshold = SKIP_THRESHOLD, + all = SKIP_ALL, +} + +pub const SparsePattern = struct { + is_sparse : bool, + zero_count : u8, + non_zero_count : u8, + sparsity_ratio : u8, // 0-255, represents 0-100% +} + +pub const SkipResult = struct { + mode : SkipMode, + skipped_count : u8, + processed_count : u8, + mask : u16, // Bitmask of non-zero elements +} + +pub const SparseVector = struct { + values : [VECTOR_SIZE]i8, + mask : u16, + zero_count : u8, +} + +// ============================================================================ +// Zero Detection Functions +// ============================================================================ + +// is_zero(value: i8) -> bool +// Check if value is zero +pub fn is_zero(value: i8) bool { + return value == 0; +} + +// is_below_threshold(value: i8, threshold: i8) -> bool +// Check if value is below threshold +pub fn is_below_threshold(value: i8, threshold: i8) bool { + return if (value >= 0) value < threshold else value > -threshold; +} + +// count_zeros(vector: [VECTOR_SIZE]i8) -> u8 +// Count zeros in a vector +pub fn count_zeros(vector: [VECTOR_SIZE]i8) u8 { + var count : u8 = 0; + for (vector) |val| { + if (is_zero(val)) { + count += 1; + } + } + return count; +} + +// count_non_zeros(vector: [VECTOR_SIZE]i8) -> u8 +// Count non-zeros in a vector +pub fn count_non_zeros(vector: [VECTOR_SIZE]i8) u8 { + var count : u8 = 0; + for (vector) |val| { + if (not is_zero(val)) { + count += 1; + } + } + return count; +} + +// ============================================================================ +// Sparse Pattern Functions +// ============================================================================ + +// analyze_sparsity(vector: [VECTOR_SIZE]i8) -> SparsePattern +// Analyze sparsity pattern of a vector +pub fn analyze_sparsity(vector: [VECTOR_SIZE]i8) SparsePattern { + const zeros = count_zeros(vector); + const non_zeros = count_non_zeros(vector); + const sparsity_ratio = if (VECTOR_SIZE > 0) + (zeros * 100) / VECTOR_SIZE + else + 0; + + const is_sparse = sparsity_ratio > 50; // More than 50% zeros + + return SparsePattern { + .is_sparse = is_sparse, + .zero_count = zeros, + .non_zero_count = non_zeros, + .sparsity_ratio = @as(u8, @truncate(sparsity_ratio)), + }; +} + +// should_skip_sparsity(pattern: SparsePattern) -> bool +// Determine if vector should be skipped based on sparsity +pub fn should_skip_sparsity(pattern: SparsePattern) bool { + return pattern.is_sparse and pattern.sparsity_ratio > SPARSE_TOLERANCE; +} + +// ============================================================================ +// Skip Result Functions +// ============================================================================ + +// skip_zeros(vector: [VECTOR_SIZE]i8) -> SkipResult +// Skip zero values in vector +pub fn skip_zeros(vector: [VECTOR_SIZE]i8) SkipResult { + var mask : u16 = 0; + var skipped : u8 = 0; + + for (vector, 0..) |val, i| { + if (not is_zero(val)) { + mask |= (@as(u16, 1) << i); + } else { + skipped += 1; + } + } + + return SkipResult { + .mode = SkipMode.zero, + .skipped_count = skipped, + .processed_count = @as(u8, @truncate(VECTOR_SIZE - skipped)), + .mask = mask, + }; +} + +// skip_threshold(vector: [VECTOR_SIZE]i8, threshold: i8) -> SkipResult +// Skip values below threshold +pub fn skip_threshold(vector: [VECTOR_SIZE]i8, threshold: i8) SkipResult { + var mask : u16 = 0; + var skipped : u8 = 0; + + for (vector, 0..) |val, i| { + if (not is_below_threshold(val, threshold)) { + mask |= (@as(u16, 1) << i); + } else { + skipped += 1; + } + } + + return SkipResult { + .mode = SkipMode.threshold, + .skipped_count = skipped, + .processed_count = @as(u8, @truncate(VECTOR_SIZE - skipped)), + .mask = mask, + }; +} + +// skip_all(vector: [VECTOR_SIZE]i8) -> SkipResult +// Skip all processing +pub fn skip_all(vector: [VECTOR_SIZE]i8) SkipResult { + return SkipResult { + .mode = SkipMode.all, + .skipped_count = VECTOR_SIZE, + .processed_count = 0, + .mask = 0, + }; +} + +// skip_none(vector: [VECTOR_SIZE]i8) -> SkipResult +// Process all elements without skipping +pub fn skip_none(vector: [VECTOR_SIZE]i8) SkipResult { + var mask : u16 = 0; + for (0..VECTOR_SIZE) |i| { + mask |= (@as(u16, 1) << i); + } + + return SkipResult { + .mode = SkipMode.none, + .skipped_count = 0, + .processed_count = VECTOR_SIZE, + .mask = mask, + }; +} + +// ============================================================================ +// Sparse Vector Functions +// ============================================================================ + +// create_sparse_vector(vector: [VECTOR_SIZE]i8) -> SparseVector +// Create sparse vector representation +pub fn create_sparse_vector(vector: [VECTOR_SIZE]i8) SparseVector { + var mask : u16 = 0; + var zero_count : u8 = 0; + + for (vector, 0..) |val, i| { + if (not is_zero(val)) { + mask |= (@as(u16, 1) << i); + } else { + zero_count += 1; + } + } + + return SparseVector { + .values = vector, + .mask = mask, + .zero_count = zero_count, + }; +} + +// get_non_zero_elements(sparse: SparseVector) -> [VECTOR_SIZE]i8 +// Get only non-zero elements (zero-padded) +pub fn get_non_zero_elements(sparse: SparseVector) [VECTOR_SIZE]i8 { + var result : [VECTOR_SIZE]i8 = undefined; + var idx : u8 = 0; + + for (sparse.values, 0..) |val, i| { + if ((sparse.mask >> i) & 1 != 0) { + result[idx] = val; + idx += 1; + } + } + + // Zero-fill remaining + while (idx < VECTOR_SIZE) { + result[idx] = 0; + idx += 1; + } + + return result; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_sparse_skip(mode: u8, vector_addr: u8) -> u16 +// Encode sparse skip instruction +pub fn encode_sparse_skip(mode: u8, vector_addr: u8) u16 { + // Format: [OP:8][MODE:2][ADDR:6] + const op : u16 = @as(u16, OP_SPARSE_SKIP) << 8; + const mode_field : u16 = @as(u16, mode & 0x03) << 6; + const addr_field : u16 = @as(u16, vector_addr & 0x3F); + return op | mode_field | addr_field; +} + +// decode_sparse_skip(encoded: u16) -> struct { mode: u8, vector_addr: u8 } +// Decode sparse skip instruction +pub fn decode_sparse_skip(encoded: u16) struct { mode: u8, vector_addr: u8 } { + const mode : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const vector_addr : u8 = @as(u8, @truncate(encoded & 0x3F)); + return .{ .mode = mode, .vector_addr = vector_addr }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "is_zero_true" { + try std.testing.expect(is_zero(0) == true); +} + +test "is_zero_false" { + try std.testing.expect(is_zero(1) == false); + try std.testing.expect(is_zero(-1) == false); +} + +test "is_below_threshold_positive" { + try std.testing.expect(is_below_threshold(3, 5) == true); + try std.testing.expect(is_below_threshold(7, 5) == false); +} + +test "is_below_threshold_negative" { + try std.testing.expect(is_below_threshold(-3, 5) == true); + try std.testing.expect(is_below_threshold(-7, 5) == false); +} + +test "count_zeros_empty" { + given vector = [_]i8{0} ** 16 + try std.testing.expect(count_zeros(vector) == 16); +} + +test "count_zeros_none" { + given vector = [_]i8{1} ** 16 + try std.testing.expect(count_zeros(vector) == 0); +} + +test "count_zeros_mixed" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(count_zeros(vector) == 8); +} + +test "count_non_zeros_mixed" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(count_non_zeros(vector) == 8); +} + +test "analyze_sparsity_dense" { + given vector = [_]i8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(pattern.is_sparse == false); + try std.testing.expect(pattern.zero_count == 0); +} + +test "analyze_sparsity_sparse" { + given vector = [_]i8{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(pattern.is_sparse == true); + try std.testing.expect(pattern.zero_count == 15); +} + +test "should_skip_sparsity_true" { + given vector = [_]i8{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(should_skip_sparsity(pattern) == true); +} + +test "should_skip_sparsity_false" { + given vector = [_]i8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(should_skip_sparsity(pattern) == false); +} + +test "skip_zeros_mixed" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(result = skip_zeros(vector)); + try std.testing.expect(result.skipped_count == 8); + try std.testing.expect(result.processed_count == 8); + try std.testing.expect(result.mask == 0xAAAA); +} + +test "skip_threshold_mixed" { + given vector = [_]i8{1, 0, 2, 0, 10, 0, 4, 0, 5, 0, 20, 0, 7, 0, 8, 0} + try std.testing.expect(result = skip_threshold(vector, 5)); + try std.testing.expect(result.skipped_count == 5); +} + +test "skip_all" { + given vector = [_]i8{1} ** 16 + try std.testing.expect(result = skip_all(vector)); + try std.testing.expect(result.skipped_count == 16); + try std.testing.expect(result.processed_count == 0); + try std.testing.expect(result.mask == 0); +} + +test "skip_none" { + given vector = [_]i8{1} ** 16 + try std.testing.expect(result = skip_none(vector)); + try std.testing.expect(result.skipped_count == 0); + try std.testing.expect(result.processed_count == 16); + try std.testing.expect(result.mask == 0xFFFF); +} + +test "create_sparse_vector" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(sparse = create_sparse_vector(vector)); + try std.testing.expect(sparse.mask == 0xAAAA); + try std.testing.expect(sparse.zero_count == 8); +} + +test "get_non_zero_elements" { + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(sparse = create_sparse_vector(vector)); + try std.testing.expect(non_zeros = get_non_zero_elements(sparse)); + try std.testing.expect(non_zeros[0] == 1); + try std.testing.expect(non_zeros[1] == 2); + try std.testing.expect(non_zeros[2] == 3); + try std.testing.expect(non_zeros[7] == 0); +} + +test "encode_sparse_skip" { + given encoded = encode_sparse_skip(1, 0x20) + try std.testing.expect((encoded >> 8) == OP_SPARSE_SKIP); +} + +test "decode_sparse_skip" { + given decoded = decode_sparse_skip(0xE150) + try std.testing.expect(decoded.mode == 1); + try std.testing.expect(decoded.vector_addr == 0x10); +} + +test "opcode_constant" { + try std.testing.expect(OP_SPARSE_SKIP == 0xE1); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant vector_size_sixteen + assert VECTOR_SIZE == 16 + +invariant zero_threshold_one + assert ZERO_THRESHOLD == 1 + +invariant sparse_tolerance_five + assert SPARSE_TOLERANCE == 5 + +invariant skip_mode_none_zero + assert SKIP_NONE == 0 + +invariant skip_mode_all_three + assert SKIP_ALL == 3 + +invariant zero_count_plus_non_zero_equals_size + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + assert count_zeros(vector) + count_non_zeros(vector) == VECTOR_SIZE + +invariant skip_all_skips_all + try std.testing.expect(skip_all([_]i8{1} ** 16).skipped_count == VECTOR_SIZE); + +invariant skip_none_skips_none + try std.testing.expect(skip_none([_]i8{1} ** 16).skipped_count == 0); + +invariant processed_plus_skipped_equals_size + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0} + try std.testing.expect(result = skip_zeros(vector)); + assert result.skipped_count + result.processed_count == VECTOR_SIZE + +invariant sparsity_ratio_bound + given vector = [_]i8{1} ** 16 + try std.testing.expect(pattern = analyze_sparsity(vector)); + try std.testing.expect(pattern.sparsity_ratio <= 100); + +invariant mask_bits_processed_equals_processed_count + given vector = [_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0} + try std.testing.expect(result = skip_zeros(vector)); + var mask_bits : u8 = 0 + for (0..VECTOR_SIZE) |i| { + if ((result.mask >> i) & 1 != 0) { + mask_bits += 1; + } + } + assert mask_bits == result.processed_count + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench count_zeros_latency + measure: nanoseconds to count_zeros([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}) + target: < 100ns + +bench analyze_sparsity_latency + measure: nanoseconds to analyze_sparsity([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}) + target: < 150ns + +bench skip_zeros_latency + measure: nanoseconds to skip_zeros([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}) + target: < 150ns + +bench skip_threshold_latency + measure: nanoseconds to skip_threshold([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}, 5) + target: < 150ns + +bench create_sparse_vector_latency + measure: nanoseconds to create_sparse_vector([_]i8{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0}) + target: < 150ns + +bench encode_sparse_skip_latency + measure: nanoseconds to encode_sparse_skip(1, 0x20) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/spec_exit.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/spec_exit.t27 new file mode 100644 index 0000000000..d716d9c3d6 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/spec_exit.t27 @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: Apache-2.0 +; spec_exit.t27 — Sacred Opcode 0xEB: Speculative Exit +; Hardware for speculative exit and recovery +; φ² + 1/φ² = 3 | TRINITY + +module sacred-spec_exit; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_SPEC_EXIT : u8 = 0xEB; + +pub const MAX_NESTING_DEPTH : u8 = 8; +pub const EXIT_REASON_BITS : u8 = 3; + +pub const EXIT_SUCCESS : u8 = 0; +pub const EXIT_SPEC_FAIL : u8 = 1; +pub const EXIT_TIMEOUT : u8 = 2; +pub const EXIT_EXCEPTION : u8 = 3; +pub const EXIT_RESOURCE : u8 = 4; +pub const EXIT_ABORT : u8 = 5; +pub const EXIT_RECOVER : u8 = 6; +pub const EXIT_MAX : u8 = 7; + +pub const STATE_RUNNING : u8 = 0; +pub const STATE_SPECULATING : u8 = 1; +pub const STATE_EXITING : u8 = 2; +pub const STATE_EXITED : u8 = 3; +pub const STATE_RECOVERING : u8 = 4; +pub const STATE_ERROR : u8 = 5; + +// ============================================================================ +// Types +// ============================================================================ + +pub const ExitReason = enum(u8) { + success = EXIT_SUCCESS, + spec_fail = EXIT_SPEC_FAIL, + timeout = EXIT_TIMEOUT, + exception = EXIT_EXCEPTION, + resource = EXIT_RESOURCE, + abort = EXIT_ABORT, + recover = EXIT_RECOVER, +} + +pub const SpecState = enum(u8) { + running = STATE_RUNNING, + speculating = STATE_SPECULATING, + exiting = STATE_EXITING, + exited = STATE_EXITED, + recovering = STATE_RECOVERING, + error = STATE_ERROR, +} + +pub const SpecExit = struct { + reason : ExitReason, + depth : u8, + checkpoint_addr : u16, + recovery_possible : bool, +} + +pub const SpecContext = struct { + state : SpecState, + current_depth : u8, + spec_count : u32, + exit_count : u32, +} + +pub const SpecConfig = struct { + max_depth : u8, + timeout_cycles : u32, + enable_recovery : bool, + checkpoint_interval : u8, +} + +// ============================================================================ +// Spec State Functions +// ============================================================================ + +// spec_state_is_running(state: SpecState) -> bool +// Check if state is running +pub fn spec_state_is_running(state: SpecState) bool { + return state == SpecState.running; +} + +// spec_state_is_speculating(state: SpecState) -> bool +// Check if state is speculating +pub fn spec_state_is_speculating(state: SpecState) -> bool { + return state == SpecState.speculating; +} + +// spec_state_is_exiting(state: SpecState) -> bool +// Check if state is exiting +pub fn spec_state_is_exiting(state: SpecState) -> bool { + return state == SpecState.exiting; +} + +// spec_state_is_exited(state: SpecState) -> bool +// Check if state has exited +pub fn spec_state_is_exited(state: SpecState) -> bool { + return state == SpecState.exited; +} + +// spec_state_is_recovering(state: SpecState) -> bool +// Check if state is recovering +pub fn spec_state_is_recovering(state: SpecState) -> bool { + return state == SpecState.recovering; +} + +// spec_state_is_terminal(state: SpecState) -> bool +// Check if state is terminal (no further execution) +pub fn spec_state_is_terminal(state: SpecState) bool { + return state == SpecState.exited or state == SpecState.error; +} + +// spec_state_can_speculate(state: SpecState) -> bool +// Check if can start speculation +pub fn spec_state_can_speculate(state: SpecState) -> bool { + return state == SpecState.running or state == SpecState.recovering; +} + +// ============================================================================ +// Exit Functions +// ============================================================================ + +// spec_exit_success(depth: u8, checkpoint: u16) -> SpecExit +// Create successful exit +pub fn spec_exit_success(depth: u8, checkpoint: u16) -> SpecExit { + return SpecExit { + .reason = ExitReason.success, + .depth = depth, + .checkpoint_addr = checkpoint, + .recovery_possible = true, + }; +} + +// spec_exit_fail(reason: ExitReason, depth: u8, checkpoint: u16, can_recover: bool) -> SpecExit +// Create failure exit +pub fn spec_exit_fail(reason: ExitReason, depth: u8, checkpoint: u16, can_recover: bool) -> SpecExit { + return SpecExit { + .reason = reason, + .depth = depth, + .checkpoint_addr = checkpoint, + .recovery_possible = can_recover, + }; +} + +// spec_exit_timeout(depth: u8, checkpoint: u16) -> SpecExit +// Create timeout exit +pub fn spec_exit_timeout(depth: u8, checkpoint: u16) -> SpecExit { + return spec_exit_fail(ExitReason.timeout, depth, checkpoint, true); +} + +// spec_exit_exception(depth: u8, checkpoint: u16) -> SpecExit +// Create exception exit +pub fn spec_exit_exception(depth: u8, checkpoint: u16) -> SpecExit { + return spec_exit_fail(ExitReason.exception, depth, checkpoint, false); +} + +// spec_exit_resource(depth: u8, checkpoint: u16) -> SpecExit +// Create resource exit +pub fn spec_exit_resource(depth: u8, checkpoint: u16) -> SpecExit { + return spec_exit_fail(ExitReason.resource, depth, checkpoint, true); +} + +// spec_exit_abort(depth: u8) -> SpecExit +// Create abort exit (no recovery) +pub fn spec_exit_abort(depth: u8) -> SpecExit { + return SpecExit { + .reason = ExitReason.abort, + .depth = depth, + .checkpoint_addr = 0, + .recovery_possible = false, + }; +} + +// spec_exit_recover(depth: u8, checkpoint: u16) -> SpecExit +// Create recover exit +pub fn spec_exit_recover(depth: u8, checkpoint: u16) -> SpecExit { + return SpecExit { + .reason = ExitReason.recover, + .depth = depth, + .checkpoint_addr = checkpoint, + .recovery_possible = true, + }; +} + +// ============================================================================ +// Spec Context Functions +// ============================================================================ + +// spec_context_init() -> SpecContext +// Initialize speculation context +pub fn spec_context_init() -> SpecContext { + return SpecContext { + .state = SpecState.running, + .current_depth = 0, + .spec_count = 0, + .exit_count = 0, + }; +} + +// spec_context_enter_speculation(context: SpecContext, depth_limit: u8) -> SpecContext +// Enter speculation +pub fn spec_context_enter_speculation(context: SpecContext, depth_limit: u8) -> SpecContext { + if (not spec_state_can_speculate(context.state)) { + return context; + } + if (context.current_depth >= depth_limit) { + return context; + } + + return SpecContext { + .state = SpecState.speculating, + .current_depth = context.current_depth + 1, + .spec_count = context.spec_count + 1, + .exit_count = context.exit_count, + }; +} + +// spec_context_exit(context: SpecContext, exit: SpecExit) -> SpecContext +// Handle exit from speculation +pub fn spec_context_exit(context: SpecContext, exit: SpecExit) -> SpecContext { + var new_state = context.state; + + if (exit.reason == ExitReason.success or exit.reason == ExitReason.recover) { + new_state = SpecState.exiting; + } else if (exit.recovery_possible) { + new_state = SpecState.recovering; + } else { + new_state = SpecState.error; + } + + return SpecContext { + .state = new_state, + .current_depth = if (new_state == SpecState.recovering) exit.depth else context.current_depth, + .spec_count = context.spec_count + 1, + .exit_count = context.exit_count + 1, + }; +} + +// spec_context_complete(context: SpecContext) -> SpecContext +// Complete speculation +pub fn spec_context_complete(context: SpecContext) -> SpecContext { + return SpecContext { + .state = SpecState.exited, + .current_depth = 0, + .spec_count = context.spec_count + 1, + .exit_count = context.exit_count, + }; +} + +// spec_context_check_timeout(context: SpecContext, config: SpecConfig, current_cycle: u32) -> bool +// Check for timeout +pub fn spec_context_check_timeout(context: SpecContext, config: SpecConfig, current_cycle: u32) -> bool { + if (config.timeout_cycles == 0) { + return false; + } + return (context.spec_count * 100) >= config.timeout_cycles; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_spec_exit(reason: u8, depth: u8, checkpoint: u16) -> u32 +// Encode speculative exit instruction +pub fn encode_spec_exit(reason: u8, depth: u8, checkpoint: u16) u32 { + // Format: [OP:8][REASON:3][DEPTH:5][CHECKPOINT:16] + const op : u32 = @as(u32, OP_SPEC_EXIT) << 24; + const reason_field : u32 = @as(u32, reason & 0x07) << 21; + const depth_field : u32 = @as(u32, depth & 0x1F) << 16; + const checkpoint_field : u32 = @as(u32, checkpoint & 0xFFFF); + return op | reason_field | depth_field | checkpoint_field; +} + +// decode_spec_exit(encoded: u32) -> struct { reason: u8, depth: u8, checkpoint: u16 } +// Decode speculative exit instruction +pub fn decode_spec_exit(encoded: u32) -> struct { reason: u8, depth: u8, checkpoint: u16 } { + const reason : u8 = @as(u8, @truncate((encoded >> 21) & 0x07)); + const depth : u8 = @as(u8, @truncate((encoded >> 16) & 0x1F)); + const checkpoint : u16 = @as(u16, @truncate(encoded & 0xFFFF)); + return .{ .reason = reason, .depth = depth, .checkpoint = checkpoint }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "max_nesting_depth_eight" { + try std.testing.expect(MAX_NESTING_DEPTH == 8); +} + +test "exit_reason_constants" { + try std.testing.expect(EXIT_SUCCESS == 0); + try std.testing.expect(EXIT_ABORT == 5); + try std.testing.expect(EXIT_RECOVER == 6); +} + +test "state_constants" { + try std.testing.expect(STATE_RUNNING == 0); + try std.testing.expect(STATE_EXITED == 3); + try std.testing.expect(STATE_ERROR == 5); +} + +test "spec_state_is_running_true" { + try std.testing.expect(spec_state_is_running(SpecState.running) == true); +} + +test "spec_state_is_running_false" { + try std.testing.expect(spec_state_is_running(SpecState.speculating) == false); +} + +test "spec_state_is_terminal_true" { + try std.testing.expect(spec_state_is_terminal(SpecState.exited) == true); + try std.testing.expect(spec_is_terminal(SpecState.error) == true); +} + +test "spec_state_is_terminal_false" { + try std.testing.expect(spec_state_is_terminal(SpecState.running) == false); +} + +test "spec_state_can_speculate_true" { + try std.testing.expect(spec_state_can_speculate(SpecState.running) == true); + try std.testing.expect(spec_state_can_speculate(SpecState.recovering) == true); +} + +test "spec_state_can_speculate_false" { + try std.testing.expect(spec_state_can_speculate(SpecState.exiting) == false); + try std.testing.expect(spec_state_can_speculate(SpecState.error) == false); +} + +test "spec_exit_success_structure" { + given exit = spec_exit_success(5, 0x1000) + try std.testing.expect(exit.reason == ExitReason.success); + try std.testing.expect(exit.depth == 5); + try std.testing.expect(exit.recovery_possible == true); +} + +test "spec_exit_fail_structure" { + given exit = spec_exit_fail(ExitReason.exception, 3, 0x2000, true) + try std.testing.expect(exit.reason == ExitReason.exception); + try std.testing.expect(exit.recovery_possible == true); +} + +test "spec_exit_abort_no_recovery" { + given exit = spec_exit_abort(5) + try std.testing.expect(exit.reason == ExitReason.abort); + try std.testing.expect(exit.recovery_possible == false); +} + +test "spec_exit_recover_structure" { + given exit = spec_exit_recover(3, 0x3000) + try std.testing.expect(exit.reason == ExitReason.recover); + try std.testing.expect(exit.recovery_possible == true); +} + +test "spec_context_init_structure" { + given context = spec_context_init() + try std.testing.expect(context.state == SpecState.running); + try std.testing.expect(context.current_depth == 0); + try std.testing.expect(context.spec_count == 0); +} + +test "spec_context_enter_speculation" { + given context = spec_context_init() + try std.testing.expect(result = spec_context_enter_speculation(context, 8)); + try std.testing.expect(result.state == SpecState.speculating); + try std.testing.expect(result.current_depth == 1); +} + +test "spec_context_enter_depth_limit" { + given context = spec_context_init() + try std.testing.expect(deep = spec_context_enter_speculation(spec_context_enter_speculation(context, 8), 8)); + try std.testing.expect(deep.current_depth == 2); +} + +test "spec_context_enter_depth_limit_exceeded" { + given context = SpecContext{.state = SpecState.running, .current_depth = 8, .spec_count = 0, .exit_count = 0} + try std.testing.expect(result = spec_context_enter_speculation(context, 8)); + try std.testing.expect(result.current_depth == 8); +} + +test "spec_context_exit_success" { + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_success(5, 0x1000)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(result.state == SpecState.exiting); +} + +test "spec_context_exit_fail_recover" { + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_fail(ExitReason.exception, 3, 0x2000, true)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(result.state == SpecState.recovering); +} + +test "spec_context_exit_fail_no_recover" { + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_fail(ExitReason.abort, 3, 0, false)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(result.state == SpecState.error); +} + +test "spec_context_complete" { + given context = spec_context_init() + try std.testing.expect(result = spec_context_complete(context)); + try std.testing.expect(result.state == SpecState.exited); + try std.testing.expect(result.current_depth == 0); +} + +test "spec_context_check_timeout_true" { + given context = SpecContext{.state = SpecState.speculating, .current_depth = 3, .spec_count = 10, .exit_count = 0} + try std.testing.expect(config = SpecConfig{.max_depth = 8, .timeout_cycles = 500, .enable_recovery = true, .checkpoint_interval = 1}); + try std.testing.expect(spec_context_check_timeout(context, config, 1000) == true); +} + +test "spec_context_check_timeout_false" { + given context = SpecContext{.state = SpecState.speculating, .current_depth = 3, .spec_count = 1, .exit_count = 0} + try std.testing.expect(config = SpecConfig{.max_depth = 8, .timeout_cycles = 500, .enable_recovery = true, .checkpoint_interval = 1}); + try std.testing.expect(spec_context_check_timeout(context, config, 1000) == false); +} + +test "encode_spec_exit" { + given encoded = encode_spec_exit(1, 5, 0x1234) + try std.testing.expect((encoded >> 24) == OP_SPEC_EXIT); +} + +test "decode_spec_exit" { + given decoded = decode_spec_exit(0xEB081234) + try std.testing.expect(decoded.reason == 1); + try std.testing.expect(decoded.depth == 5); + try std.testing.expect(decoded.checkpoint == 0x1234); +} + +test "opcode_constant" { + try std.testing.expect(OP_SPEC_EXIT == 0xEB); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant max_nesting_depth_eight + assert MAX_NESTING_DEPTH == 8 + +invariant exit_reason_values + try std.testing.expect(EXIT_SUCCESS >= 0 and EXIT_MAX <= 7); + +invariant state_values + try std.testing.expect(STATE_RUNNING >= 0 and STATE_ERROR <= 5); + +invariant spec_exit_success_recoverable + given exit = spec_exit_success(0, 0x1000) + try std.testing.expect(exit.recovery_possible == true); + +invariant spec_exit_abort_not_recoverable + given exit = spec_exit_abort(5) + try std.testing.expect(exit.recovery_possible == false); + +invariant spec_exit_depth_bound + try std.testing.expect(spec_exit_abort(0).depth == 0); + try std.testing.expect(spec_exit_abort(MAX_NESTING_DEPTH).depth == MAX_NESTING_DEPTH); + +invariant spec_context_init_zero_depth + given context = spec_context_init() + try std.testing.expect(context.current_depth == 0); + +invariant spec_context_enter_increases_depth + given context = spec_context_init() + try std.testing.expect(result = spec_context_enter_speculation(context, 8)); + try std.testing.expect(result.current_depth == context.current_depth + 1); + +invariant spec_context_exit_preserves_reason + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_fail(ExitReason.exception, 3, 0x2000, true)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(exit.reason == ExitReason.exception); + +invariant spec_context_complete_zero_depth + given context = SpecContext{.state = SpecState.running, .current_depth = 5, .spec_count = 10, .exit_count = 0} + try std.testing.expect(result = spec_context_complete(context)); + try std.testing.expect(result.current_depth == 0); + +invariant spec_context_complete_exited_state + given context = spec_context_init() + try std.testing.expect(result = spec_context_complete(context)); + try std.testing.expect(result.state == SpecState.exited); + +invariant spec_context_check_timeout_no_timeout + given config = SpecConfig{.max_depth = 8, .timeout_cycles = 0, .enable_recovery = true, .checkpoint_interval = 1} + try std.testing.expect(context = SpecContext{.state = SpecState.speculating, .current_depth = 3, .spec_count = 1000, .exit_count = 0}); + try std.testing.expect(spec_context_check_timeout(context, config, 1000) == false); + +invariant spec_context_enter_speculate_requires_running_or_recovering + given context = SpecContext{.state = SpecState.exiting, .current_depth = 0, .spec_count = 0, .exit_count = 0} + try std.testing.expect(spec_context_enter_speculation(context, 8).current_depth == 0); + +invariant spec_context_exit_increases_exit_count + given context = spec_context_init() + try std.testing.expect(exit = spec_exit_success(0, 0x1000)); + try std.testing.expect(result = spec_context_exit(context, exit)); + try std.testing.expect(result.exit_count == context.exit_count + 1); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench spec_exit_success_latency + measure: nanoseconds to spec_exit_success(5, 0x1000) + target: < 30ns + +bench spec_exit_fail_latency + measure: nanoseconds to spec_exit_fail(ExitReason.exception, 3, 0x2000, true) + target: < 30ns + +bench spec_exit_abort_latency + measure: nanoseconds to spec_exit_abort(5) + target: < 30ns + +bench spec_context_enter_speculation_latency + measure: nanoseconds to spec_context_enter_speculation(spec_context_init(), 8) + target: < 50ns + +bench spec_context_exit_latency + measure: nanoseconds to spec_context_exit(spec_context_init(), spec_exit_success(5, 0x1000)) + target: < 50ns + +bench spec_context_check_timeout_latency + measure: nanoseconds to spec_context_check_timeout(.{.state = SpecState.speculating, .current_depth = 3, .spec_count = 10, .exit_count = 0}, .{.max_depth = 8, .timeout_cycles = 500, .enable_recovery = true, .checkpoint_interval = 1}, 1000) + target: < 30ns + +bench encode_spec_exit_latency + measure: nanoseconds to encode_spec_exit(1, 5, 0x1234) + target: < 30ns + +bench decode_spec_exit_latency + measure: nanoseconds to decode_spec_exit(0xEB081234) + target: < 30ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/stoch_round.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/stoch_round.t27 new file mode 100644 index 0000000000..7bb7a55f45 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/stoch_round.t27 @@ -0,0 +1,575 @@ +// SPDX-License-Identifier: Apache-2.0 +; stoch_round.t27 — Sacred Opcode 0xE9: Stochastic Rounding +; Hardware stochastic rounding for quantization +; φ² + 1/φ² = 3 | TRINITY + +module sacred-stoch_round; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_STOCH_ROUND : u8 = 0xE9; + +pub const RANDOM_BITS : u8 = 8; +pub const ROUND_MODE_NEAREST : u8 = 0; +pub const ROUND_MODE_STOCHASTIC : u8 = 1; +pub const ROUND_MODE_TRUNCATE : u8 = 2; +pub const ROUND_MODE_CEIL : u8 = 3; + +pub const INT4_BITS : u8 = 4; +pub const INT8_BITS : u8 = 8; + +pub const INT4_MAX : i8 = 7; +pub const INT4_MIN : i8 = -8; +pub const INT8_MAX : i8 = 127; +pub const INT8_MIN : i8 = -128; + +// ============================================================================ +// Types +// ============================================================================ + +pub const RoundConfig = struct { + mode : u8, + target_bits : u8, + seed : u16, +} + +pub const RoundResult = struct { + value : i16, + original : i16, + rounded : bool, + overflow : bool, +} + +pub const RngState = struct { + state : u16, +} + +// ============================================================================ +// RNG Functions (XorShift LFSR) +// ============================================================================ + +// rng_init(seed: u16) -> RngState +// Initialize RNG with seed +pub fn rng_init(seed: u16) RngState { + return RngState{.state = if (seed == 0) 1 else seed}; +} + +// rng_next(state: RngState) -> struct { state: RngState, value: u8 } +// Get next random byte +pub fn rng_next(state: RngState) -> struct { state: RngState, value: u8 } { + var s = state.state; + s ^= s >> 7; + s ^= s << 9; + s ^= s >> 8; + return .{ .state = RngState{.state = s}, .value = @as(u8, @truncate(s)) }; +} + +// rng_next_range(state: RngState, max: u8) -> struct { state: RngState, value: u8 } +// Get next random value in range [0, max) +pub fn rng_next_range(state: RngState, max: u8) -> struct { state: RngState, value: u8 } { + const next_result = rng_next(state); + const scaled = (@as(u16, next_result.value) * @as(u16, max)) >> 8; + return .{ .state = next_result.state, .value = @as(u8, @truncate(scaled)) }; +} + +// ============================================================================ +// Rounding Functions +// ============================================================================ + +// round_nearest(value: i16, bits: u8) -> RoundResult +// Round to nearest +pub fn round_nearest(value: i16, bits: u8) -> RoundResult { + const shift = 16 - bits; + var result : i16 = 0; + + if (shift >= 0) { + const abs_val = if (value < 0) -value else value; + const rounding = (abs_val + (1 << (shift - 1))) >> shift; + result = if (value < 0) -rounding else rounding; + } else { + result = value << (-shift); + } + + // Check for overflow + var overflow = false; + const max_val : i16 = (@as(i16, 1) << (bits - 1)) - 1; + const min_val : i16 = -(@as(i16, 1) << (bits - 1)); + + if (result > max_val) { + result = max_val; + overflow = true; + } else if (result < min_val) { + result = min_val; + overflow = true; + } + + return RoundResult { + .value = result, + .original = value, + .rounded = result != value, + .overflow = overflow, + }; +} + +// round_stochastic(value: i16, bits: u8, state: RngState) -> struct { result: RoundResult, new_state: RngState } +// Stochastic rounding +pub fn round_stochastic(value: i16, bits: u8, state: RngState) -> struct { result: RoundResult, new_state: RngState } { + const shift = 16 - bits; + var result : i16 = 0; + + if (shift <= 0) { + result = value << (-shift); + return .{ .result = RoundResult{.value = result, .original = value, .rounded = false, .overflow = false}, .new_state = state }; + } + + const abs_val = if (value < 0) -value else value; + const integer_part = abs_val >> shift; + const fractional_part = abs_val & ((@as(i16, 1) << shift) - 1); + + // Generate random value and compare + const max_frac = @as(i16, 1) << shift; + const frac_scaled : i16 = @as(i16, @intCast(fractional_part)) * 256; + const threshold = (frac_scaled + (max_frac / 2)) / max_frac; + + const rng_result = rng_next(state); + const rand_val : i16 = @as(i16, @intCast(rng_result.value)); + + var rounded_int = integer_part; + if (rand_val >= threshold and fractional_part != 0) { + rounded_int += 1; + } + + result = if (value < 0) -rounded_int else rounded_int; + + // Check for overflow + var overflow = false; + const max_val : i16 = (@as(i16, 1) << (bits - 1)) - 1; + const min_val : i16 = -(@as(i16, 1) << (bits - 1)); + + if (result > max_val) { + result = max_val; + overflow = true; + } else if (result < min_val) { + result = min_val; + overflow = true; + } + + return .{ .result = RoundResult{.value = result, .original = value, .rounded = result != (abs_val >> shift), .overflow = overflow}, .new_state = rng_result.state }; +} + +// round_truncate(value: i16, bits: u8) -> RoundResult +// Truncate (round toward zero) +pub fn round_truncate(value: i16, bits: u8) -> RoundResult { + const shift = 16 - bits; + var result : i16 = 0; + + if (shift >= 0) { + result = value >> shift; + } else { + result = value << (-shift); + } + + // Check for overflow + var overflow = false; + const max_val : i16 = (@as(i16, 1) << (bits - 1)) - 1; + const min_val : i16 = -(@as(i16, 1) << (bits - 1)); + + if (result > max_val) { + result = max_val; + overflow = true; + } else if (result < min_val) { + result = min_val; + overflow = true; + } + + return RoundResult { + .value = result, + .original = value, + .rounded = result != value, + .overflow = overflow, + }; +} + +// round_ceil(value: i16, bits: u8) -> RoundResult +// Ceil (round toward positive infinity) +pub fn round_ceil(value: i16, bits: u8) -> RoundResult { + const shift = 16 - bits; + var result : i16 = 0; + + if (shift >= 0) { + const abs_val = if (value < 0) -value else value; + const frac_part = abs_val & ((@as(i16, 1) << shift) - 1); + var base = abs_val >> shift; + if (value >= 0 and frac_part != 0) { + base += 1; + } + result = if (value < 0) -base else base; + } else { + result = value << (-shift); + } + + // Check for overflow + var overflow = false; + const max_val : i16 = (@as(i16, 1) << (bits - 1)) - 1; + const min_val : i16 = -(@as(i16, 1) << (bits - 1)); + + if (result > max_val) { + result = max_val; + overflow = true; + } else if (result < min_val) { + result = min_val; + overflow = true; + } + + return RoundResult { + .value = result, + .original = value, + .rounded = true, + .overflow = overflow, + }; +} + +// ============================================================================ +// Config-Based Rounding +// ============================================================================ + +// round_with_config(value: i16, config: RoundConfig, state: RngState) -> struct { result: RoundResult, new_state: RngState } +// Round using config +pub fn round_with_config(value: i16, config: RoundConfig, state: RngState) -> struct { result: RoundResult, new_state: RngState } { + switch (config.mode) { + ROUND_MODE_NEAREST => { + return .{ .result = round_nearest(value, config.target_bits), .new_state = state }; + }, + ROUND_MODE_STOCHASTIC => { + return round_stochastic(value, config.target_bits, state); + }, + ROUND_MODE_TRUNCATE => { + return .{ .result = round_truncate(value, config.target_bits), .new_state = state }; + }, + ROUND_MODE_CEIL => { + return .{ .result = round_ceil(value, config.target_bits), .new_state = state }; + }, + else => { + return .{ .result = RoundResult{.value = 0, .original = value, .rounded = false, .overflow = true}, .new_state = state }; + }, + } +} + +// ============================================================================ +// Vector Rounding +// ============================================================================ + +// round_vector_nearest(values: [8]i16, bits: u8) -> [8]RoundResult +// Round vector to nearest +pub fn round_vector_nearest(values: [8]i16, bits: u8) [8]RoundResult { + var results : [8]RoundResult = undefined; + for (values, 0..) |val, i| { + results[i] = round_nearest(val, bits); + } + return results; +} + +// round_vector_stochastic(values: [8]i16, bits: u8, state: RngState) -> struct { results: [8]RoundResult, new_state: RngState } +// Round vector stochastically +pub fn round_vector_stochastic(values: [8]i16, bits: u8, state: RngState) -> struct { results: [8]RoundResult, new_state: RngState } { + var results : [8]RoundResult = undefined; + var current_state = state; + + for (values, 0..) |val, i| { + const round_result = round_stochastic(val, bits, current_state); + results[i] = round_result.result; + current_state = round_result.new_state; + } + + return .{ .results = results, .new_state = current_state }; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_stoch_round(mode: u8, target_bits: u8, seed: u8) -> u16 +// Encode stochastic rounding instruction +pub fn encode_stoch_round(mode: u8, target_bits: u8, seed: u8) u16 { + // Format: [OP:8][MODE:2][BITS:4][SEED:2] + const op : u16 = @as(u16, OP_STOCH_ROUND) << 8; + const mode_field : u16 = @as(u16, mode & 0x03) << 6; + const bits_field : u16 = @as(u16, target_bits & 0x0F) << 2; + const seed_field : u16 = @as(u16, seed & 0x03); + return op | mode_field | bits_field | seed_field; +} + +// decode_stoch_round(encoded: u16) -> struct { mode: u8, target_bits: u8, seed: u8 } +// Decode stochastic rounding instruction +pub fn decode_stoch_round(encoded: u16) struct { mode: u8, target_bits: u8, seed: u8 } { + const mode : u8 = @as(u8, @truncate((encoded >> 6) & 0x03)); + const target_bits : u8 = @as(u8, @truncate((encoded >> 2) & 0x0F)); + const seed : u8 = @as(u8, @truncate(encoded & 0x03)); + return .{ .mode = mode, .target_bits = target_bits, .seed = seed }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "round_mode_constants" { + try std.testing.expect(ROUND_MODE_NEAREST == 0); + try std.testing.expect(ROUND_MODE_STOCHASTIC == 1); + try std.testing.expect(ROUND_MODE_TRUNCATE == 2); + try std.testing.expect(ROUND_MODE_CEIL == 3); +} + +test "int4_bounds" { + try std.testing.expect(INT4_MAX == 7 and INT4_MIN == -8); +} + +test "int8_bounds" { + try std.testing.expect(INT8_MAX == 127 and INT8_MIN == -128); +} + +test "round_nearest_basic" { + given result = round_nearest(10, 4) + try std.testing.expect(result.value == 1); +} + +test "round_nearest_negative" { + given result = round_nearest(-10, 4) + try std.testing.expect(result.value == -1); +} + +test "round_nearest_overflow" { + given result = round_nearest(0x7FFF, 8) + try std.testing.expect(result.value == INT8_MAX); + try std.testing.expect(result.overflow == true); +} + +test "round_nearest_underflow" { + given result = round_nearest(-0x8000, 8) + try std.testing.expect(result.value == INT8_MIN); + try std.testing.expect(result.overflow == true); +} + +test "round_nearest_no_change" { + given result = round_nearest(0x1000, 8) + try std.testing.expect(result.value == 0x10); + try std.testing.expect(result.rounded == false); +} + +test "round_truncate_basic" { + given result = round_truncate(10, 4) + try std.testing.expect(result.value == 0); +} + +test "round_truncate_negative" { + given result = round_truncate(-10, 4) + try std.testing.expect(result.value == 0); +} + +test "round_truncate_fractional" { + given result = round_truncate(15, 4) + try std.testing.expect(result.value == 0); +} + +test "round_ceil_basic" { + given result = round_ceil(10, 4) + try std.testing.expect(result.value == 1); +} + +test "round_ceil_negative" { + given result = round_ceil(-10, 4) + try std.testing.expect(result.value == 0); +} + +test "round_ceil_fractional" { + given result = round_ceil(1, 4) + try std.testing.expect(result.value == 1); +} + +test "round_stochastic_seeded" { + given state = RngState{.state = 0x1234} + try std.testing.expect(round1 = round_stochastic(10, 4, state)); + try std.testing.expect(state2 = RngState{.state = 0x1234}); + try std.testing.expect(round2 = round_stochastic(10, 4, state2)); + try std.testing.expect(round1.result.value == round2.result.value); + try std.testing.expect(round1.new_state == round2.new_state); +} + +test "round_stochastic_reproducible" { + given state = RngState{.state = 0x1234} + try std.testing.expect(round1 = round_stochastic(10, 4, state)); + try std.testing.expect(round2 = round_stochastic(10, 4, round1.new_state)); + try std.testing.expect(round2.result.value != round1.result.value or round2.new_state != round1.new_state); +} + +test "round_vector_nearest" { + given results = round_vector_nearest([_]i16{10, 20, 30, 40, 50, 60, 70, 80}, 4) + try std.testing.expect(results[0].value == 1); + try std.testing.expect(results[7].value == 5); +} + +test "round_vector_stochastic" { + given state = RngState{.state = 0x1234} + try std.testing.expect(round_result = round_vector_stochastic([_]i16{10, 20, 30, 40}, 4, state)); + try std.testing.expect(round_result.new_state.state != 0x1234); +} + +test "rng_init_default" { + given state = rng_init(0) + try std.testing.expect(state.state == 1); +} + +test "rng_init_with_seed" { + given state = rng_init(0x1234) + try std.testing.expect(state.state == 0x1234); +} + +test "rng_next_range" { + given state = rng_init(0x1234) + try std.testing.expect(result = rng_next_range(state, 100)); + try std.testing.expect(result.value <= 100); +} + +test "round_with_config_nearest" { + given config = RoundConfig{.mode = ROUND_MODE_NEAREST, .target_bits = 8, .seed = 0} + try std.testing.expect(state = RngState{.state = 1}); + try std.testing.expect(result = round_with_config(10, config, state)); + try std.testing.expect(result.result.value == 10); +} + +test "round_with_config_stochastic" { + given config = RoundConfig{.mode = ROUND_MODE_STOCHASTIC, .target_bits = 4, .seed = 0} + try std.testing.expect(state = RngState{.state = 1}); + try std.testing.expect(result = round_with_config(10, config, state)); + try std.testing.expect(result.result.value <= INT4_MAX); +} + +test "encode_stoch_round" { + given encoded = encode_stoch_round(1, 4, 2) + try std.testing.expect((encoded >> 8) == OP_STOCH_ROUND); +} + +test "decode_stoch_round" { + given decoded = decode_stoch_round(0xE932) + try std.testing.expect(decoded.mode == 1); + try std.testing.expect(decoded.target_bits == 4); + try std.testing.expect(decoded.seed == 2); +} + +test "opcode_constant" { + try std.testing.expect(OP_STOCH_ROUND == 0xE9); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant random_bits_eight + assert RANDOM_BITS == 8 + +invariant int4_bits_four + assert INT4_BITS == 4 + +invariant int8_bits_eight + assert INT8_BITS == 8 + +invariant int4_max_min_relation + try std.testing.expect(INT4_MAX == -INT4_MIN - 1); + +invariant int8_max_min_relation + try std.testing.expect(INT8_MAX == -INT8_MIN - 1); + +invariant round_mode_values + try std.testing.expect(ROUND_MODE_NEAREST >= 0 and ROUND_MODE_CEIL <= 3); + +invariant round_nearest_range + given result = round_nearest(0, 4) + try std.testing.expect(result.value >= -8 and result.value <= 7); + +invariant round_truncate_range + given result = round_truncate(0, 4) + try std.testing.expect(result.value >= -8 and result.value <= 7); + +invariant round_ceil_range + given result = round_ceil(0, 4) + try std.testing.expect(result.value >= -8 and result.value <= 7); + +invariant round_stochastic_range + given state = RngState{.state = 1} + try std.testing.expect(result = round_stochastic(0, 4, state)); + try std.testing.expect(result.result.value >= INT4_MIN and result.result.value <= INT4_MAX); + +invariant round_nearest_preserves_sign + given result_pos = round_nearest(100, 4) + try std.testing.expect(result_neg = round_nearest(-100, 4)); + try std.testing.expect(result_pos.value >= 0 and result_neg.value <= 0); + +invariant round_truncate_preserves_sign + given result_pos = round_truncate(100, 4) + try std.testing.expect(result_neg = round_truncate(-100, 4)); + try std.testing.expect(result_pos.value >= 0 and result_neg.value <= 0); + +invariant round_ceil_non_negative + given result = round_ceil(0, 4) + try std.testing.expect(result.value >= 0); + +invariant rng_next_state_changes + given state = RngState{.state = 0x1234} + try std.testing.expect(result = rng_next(state)); + try std.testing.expect(result.state.state != state.state); + +invariant rng_next_range_value_bound + given state = RngState{.state = 1} + try std.testing.expect(result = rng_next_range(state, 100)); + try std.testing.expect(result.value <= 100); + +invariant round_vector_nearest_length + given results = round_vector_nearest([_]i16{0} ** 8, 4) + try std.testing.expect(results.len == 8); + +invariant round_vector_stochastic_length + given state = RngState{.state = 1} + try std.testing.expect(result = round_vector_stochastic([_]i16{0} ** 8, 4, state)); + try std.testing.expect(result.results.len == 8); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench round_nearest_latency + measure: nanoseconds to round_nearest(100, 4) + target: < 50ns + +bench round_stochastic_latency + measure: nanoseconds to round_stochastic(100, 4, RngState{.state = 1}) + target: < 100ns + +bench round_truncate_latency + measure: nanoseconds to round_truncate(100, 4) + target: < 50ns + +bench round_ceil_latency + measure: nanoseconds to round_ceil(100, 4) + target: < 50ns + +bench rng_next_latency + measure: nanoseconds to rng_next(RngState{.state = 0x1234}) + target: < 30ns + +bench round_vector_nearest_latency + measure: nanoseconds to round_vector_nearest([_]i16{10} ** 8, 4) + target: < 200ns + +bench round_vector_stochastic_latency + measure: nanoseconds to round_vector_stochastic([_]i16{10} ** 8, 4, RngState{.state = 1}) + target: < 500ns + +bench encode_stoch_round_latency + measure: nanoseconds to encode_stoch_round(1, 4, 2) + target: < 20ns + +bench decode_stoch_round_latency + measure: nanoseconds to decode_stoch_round(0xE932) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/fpga/subth_clk.t27 b/apps/website/public/t27/files/chips/phi/specs/fpga/subth_clk.t27 new file mode 100644 index 0000000000..4d3fdd57cc --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/fpga/subth_clk.t27 @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: Apache-2.0 +; subth_clk.t27 — Sacred Opcode 0xE5: Sub-threshold Clock Gating +; Hardware for sub-threshold clock gating for power reduction +; φ² + 1/φ² = 3 | TRINITY + +module sacred-subth_clk; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const OP_SUBTH_CLK : u8 = 0xE5; + +pub const NUM_CLK_DOMAINS : u8 = 8; +pub const SUBTH_THRESHOLD_MV : u16 = 400; // 400mV threshold +pub const NORMAL_VOLTAGE_MV : u16 = 1100; // 1.1V normal + +pub const CLK_GATE_OFF : u8 = 0; +pub const CLK_GATE_ON : u8 = 1; +pub const CLK_GATE_AUTO : u8 = 2; + +pub const POWER_STATE_ACTIVE : u8 = 0; +pub const POWER_STATE_IDLE : u8 = 1; +pub const POWER_STATE_SLEEP : u8 = 2; +pub const POWER_STATE_DEEP_SLEEP : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const ClkGateMode = enum(u8) { + off = CLK_GATE_OFF, + on = CLK_GATE_ON, + auto = CLK_GATE_AUTO, +} + +pub const PowerState = enum(u8) { + active = POWER_STATE_ACTIVE, + idle = POWER_STATE_IDLE, + sleep = POWER_STATE_SLEEP, + deep_sleep = POWER_STATE_DEEP_SLEEP, +} + +pub const ClkDomain = struct { + id : u8, + enabled : bool, + gate_mode : ClkGateMode, + activity_count : u16, + last_active_cycle : u32, +} + +pub const SubthConfig = struct { + threshold_mv : u16, + hysteresis_mv : u16, + enable_subth : bool, + transition_cycles : u8, // Cycles for voltage transition +} + +pub const PowerMetrics = struct { + current_voltage_mv : u16, + current_power_mw : u16, + activity_ratio : u8, // 0-100% + clock_gated_domains : u8, +} + +// ============================================================================ +// Clk Domain Functions +// ============================================================================ + +// clk_domain_enabled(domain: ClkDomain) -> bool +// Check if clock domain is enabled +pub fn clk_domain_enabled(domain: ClkDomain) bool { + return domain.enabled and domain.gate_mode != ClkGateMode.off; +} + +// clk_domain_gate_active(domain: ClkDomain) -> bool +// Check if clock gating is active +pub fn clk_domain_gate_active(domain: ClkDomain) bool { + return domain.gate_mode == ClkGateMode.off or + (domain.gate_mode == ClkGateMode.auto and domain.activity_count == 0); +} + +// clk_domain_is_idle(domain: ClkDomain, current_cycle: u32, idle_threshold: u32) -> bool +// Check if domain is idle +pub fn clk_domain_is_idle(domain: ClkDomain, current_cycle: u32, idle_threshold: u32) bool { + return domain.activity_count == 0 and + (current_cycle - domain.last_active_cycle) > idle_threshold; +} + +// clk_domain_pulse(domain: ClkDomain, current_cycle: u32) -> ClkDomain +// Record activity pulse on domain +pub fn clk_domain_pulse(domain: ClkDomain, current_cycle: u32) ClkDomain { + return ClkDomain { + .id = domain.id, + .enabled = domain.enabled, + .gate_mode = domain.gate_mode, + .activity_count = domain.activity_count + 1, + .last_active_cycle = current_cycle, + }; +} + +// clk_domain_decay(domain: ClkDomain) -> ClkDomain +// Decay activity count +pub fn clk_domain_decay(domain: ClkDomain) ClkDomain { + const new_count = if (domain.activity_count > 0) domain.activity_count - 1 else 0; + return ClkDomain { + .id = domain.id, + .enabled = domain.enabled, + .gate_mode = domain.gate_mode, + .activity_count = new_count, + .last_active_cycle = domain.last_active_cycle, + }; +} + +// ============================================================================ +// Sub-threshold Functions +// ============================================================================ + +// is_subthreshold(voltage_mv: u16, config: SubthConfig) -> bool +// Check if voltage is in sub-threshold region +pub fn is_subthreshold(voltage_mv: u16, config: SubthConfig) bool { + if (not config.enable_subth) { + return false; + } + return voltage_mv < config.threshold_mv; +} + +// voltage_within_range(voltage_mv: u16, target_mv: u16, tolerance_mv: u16) -> bool +// Check if voltage is within tolerance of target +pub fn voltage_within_range(voltage_mv: u16, target_mv: u16, tolerance_mv: u16) bool { + if (voltage_mv >= target_mv) { + return (voltage_mv - target_mv) <= tolerance_mv; + } + return (target_mv - voltage_mv) <= tolerance_mv; +} + +// subth_power_estimate(voltage_mv: u16) -> u16 +// Estimate power at given voltage (quadratic scaling) +pub fn subth_power_estimate(voltage_mv: u16) -> u16 { + // Power scales with V^2, normalize to 1.1V = 100mW (arbitrary baseline) + const ratio = @as(f32, @floatFromInt(voltage_mv)) / 1100.0; + const power_baseline : f32 = 100.0; + const estimated : f32 = power_baseline * ratio * ratio; + return @as(u16, @intFromFloat(@floor(estimated))); +} + +// power_savings_ratio(normal_mv: u16, subth_mv: u16) -> u8 +// Calculate power savings ratio (0-100%) +pub fn power_savings_ratio(normal_mv: u16, subth_mv: u16) -> u8 { + const normal_power = subth_power_estimate(normal_mv); + const subth_power = subth_power_estimate(subth_mv); + + if (normal_power == 0) { + return 0; + } + + const saved = normal_power - subth_power; + const ratio = (saved * 100) / normal_power; + + return @as(u8, @truncate(ratio)); +} + +// ============================================================================ +// Power State Functions +// ============================================================================ + +// power_state_can_gate(state: PowerState) -> bool +// Check if power state allows clock gating +pub fn power_state_can_gate(state: PowerState) bool { + return state != PowerState.active; +} + +// power_state_deeper(a: PowerState, b: PowerState) -> PowerState +// Return deeper power state +pub fn power_state_deeper(a: PowerState, b: PowerState) PowerState { + if (@intFromEnum(a) > @intFromEnum(b)) { + return a; + } + return b; +} + +// power_state_sleep_ok(domains: [NUM_CLK_DOMAINS]ClkDomain, current_cycle: u32) -> bool +// Check if all domains can go to sleep +pub fn power_state_sleep_ok(domains: [NUM_CLK_DOMAINS]ClkDomain, current_cycle: u32) bool { + for (domains) |domain| { + if (not clk_domain_is_idle(domain, current_cycle, 100)) { + return false; + } + } + return true; +} + +// ============================================================================ +// Opcode Encoding/Decoding +// ============================================================================ + +// encode_subth_clk(domain_id: u8, mode: u8) -> u16 +// Encode sub-threshold clock instruction +pub fn encode_subth_clk(domain_id: u8, mode: u8) u16 { + // Format: [OP:8][DOMAIN:3][MODE:2][RES:3] + const op : u16 = @as(u16, OP_SUBTH_CLK) << 8; + const domain_field : u16 = @as(u16, domain_id & 0x07) << 5; + const mode_field : u16 = @as(u16, mode & 0x03) << 3; + return op | domain_field | mode_field; +} + +// decode_subth_clk(encoded: u16) -> struct { domain_id: u8, mode: u8 } +// Decode sub-threshold clock instruction +pub fn decode_subth_clk(encoded: u16) struct { domain_id: u8, mode: u8 } { + const domain_id : u8 = @as(u8, @truncate((encoded >> 5) & 0x07)); + const mode : u8 = @as(u8, @truncate((encoded >> 3) & 0x03)); + return .{ .domain_id = domain_id, .mode = mode }; +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "num_clk_domains_eight" { + try std.testing.expect(NUM_CLK_DOMAINS == 8); +} + +test "subth_threshold_four_hundred" { + try std.testing.expect(SUBTH_THRESHOLD_MV == 400); +} + +test "normal_voltage_eleven_hundred" { + try std.testing.expect(NORMAL_VOLTAGE_MV == 1100); +} + +test "clk_gate_mode_off" { + try std.testing.expect(@intFromEnum(ClkGateMode.off) == 0); +} + +test "clk_gate_mode_on" { + try std.testing.expect(@intFromEnum(ClkGateMode.on) == 1); +} + +test "clk_gate_mode_auto" { + try std.testing.expect(@intFromEnum(ClkGateMode.auto) == 2); +} + +test "power_state_active" { + try std.testing.expect(@intFromEnum(PowerState.active) == 0); +} + +test "power_state_deep_sleep" { + try std.testing.expect(@intFromEnum(PowerState.deep_sleep) == 3); +} + +test "clk_domain_enabled_true" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_enabled(domain) == true); +} + +test "clk_domain_enabled_false_gate_off" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.off, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_enabled(domain) == false); +} + +test "clk_domain_enabled_false_disabled" { + given domain = ClkDomain{.id = 0, .enabled = false, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_enabled(domain) == false); +} + +test "clk_domain_gate_active_off" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.off, .activity_count = 10, .last_active_cycle = 0} + try std.testing.expect(clk_domain_gate_active(domain) == true); +} + +test "clk_domain_gate_active_auto_idle" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.auto, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_gate_active(domain) == true); +} + +test "clk_domain_gate_active_auto_active" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.auto, .activity_count = 10, .last_active_cycle = 0} + try std.testing.expect(clk_domain_gate_active(domain) == false); +} + +test "clk_domain_is_idle_true" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(clk_domain_is_idle(domain, 200, 100) == true); +} + +test "clk_domain_is_idle_false_active" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 10, .last_active_cycle = 0} + try std.testing.expect(clk_domain_is_idle(domain, 200, 100) == false); +} + +test "clk_domain_is_idle_false_recent" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 150} + try std.testing.expect(clk_domain_is_idle(domain, 200, 100) == false); +} + +test "clk_domain_pulse" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_pulse(domain, 100)); + try std.testing.expect(result.activity_count == 1); + try std.testing.expect(result.last_active_cycle == 100); +} + +test "clk_domain_decay" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 5, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_decay(domain)); + try std.testing.expect(result.activity_count == 4); +} + +test "clk_domain_decay_zero" { + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_decay(domain)); + try std.testing.expect(result.activity_count == 0); +} + +test "is_subthreshold_true" { + given config = SubthConfig{.threshold_mv = 400, .hysteresis_mv = 20, .enable_subth = true, .transition_cycles = 10} + try std.testing.expect(is_subthreshold(300, config) == true); +} + +test "is_subthreshold_false_disabled" { + given config = SubthConfig{.threshold_mv = 400, .hysteresis_mv = 20, .enable_subth = false, .transition_cycles = 10} + try std.testing.expect(is_subthreshold(300, config) == false); +} + +test "is_subthreshold_false_high_voltage" { + given config = SubthConfig{.threshold_mv = 400, .hysteresis_mv = 20, .enable_subth = true, .transition_cycles = 10} + try std.testing.expect(is_subthreshold(500, config) == false); +} + +test "voltage_within_range_true" { + try std.testing.expect(voltage_within_range(1100, 1100, 50) == true); +} + +test "voltage_within_range_low" { + try std.testing.expect(voltage_within_range(1060, 1100, 50) == true); +} + +test "voltage_within_range_high" { + try std.testing.expect(voltage_within_range(1140, 1100, 50) == true); +} + +test "voltage_within_range_false" { + try std.testing.expect(voltage_within_range(1000, 1100, 50) == false); +} + +test "subth_power_estimate_normal" { + try std.testing.expect(subth_power_estimate(1100) == 100); +} + +test "subth_power_estimate_half" { + try std.testing.expect(subth_power_estimate(550) == 25); +} + +test "subth_power_estimate_quarter" { + try std.testing.expect(subth_power_estimate(275) == 6); +} + +test "power_savings_ratio_fifty_percent" { + given result = power_savings_ratio(1100, 777) + try std.testing.expect(result == 50); +} + +test "power_savings_ratio_seventy_five_percent" { + given result = power_savings_ratio(1100, 550) + try std.testing.expect(result == 75); +} + +test "power_state_can_gate_active" { + try std.testing.expect(power_state_can_gate(PowerState.active) == false); +} + +test "power_state_can_gate_idle" { + try std.testing.expect(power_state_can_gate(PowerState.idle) == true); +} + +test "power_state_can_gate_sleep" { + try std.testing.expect(power_state_can_gate(PowerState.sleep) == true); +} + +test "power_state_deeper" { + try std.testing.expect(power_state_deeper(PowerState.idle, PowerState.deep_sleep) == PowerState.deep_sleep); + try std.testing.expect(power_state_deeper(PowerState.deep_sleep, PowerState.idle) == PowerState.deep_sleep); +} + +test "power_state_sleep_ok_true" { + given domains = [_]ClkDomain{ + .{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}, + .{.id = 1, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}, + } ** 8 + try std.testing.expect(power_state_sleep_ok(domains, 200) == true); +} + +test "power_state_sleep_ok_false" { + given domains = [_]ClkDomain{ + .{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 10, .last_active_cycle = 0}, + .{.id = 1, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}, + } ** 8 + try std.testing.expect(power_state_sleep_ok(domains, 200) == false); +} + +test "encode_subth_clk" { + given encoded = encode_subth_clk(0x05, 0x02) + try std.testing.expect((encoded >> 8) == OP_SUBTH_CLK); +} + +test "decode_subth_clk" { + given decoded = decode_subth_clk(0xE5A8) + try std.testing.expect(decoded.domain_id == 0x05); + try std.testing.expect(decoded.mode == 0x02); +} + +test "opcode_constant" { + try std.testing.expect(OP_SUBTH_CLK == 0xE5); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant num_clk_domains_eight + assert NUM_CLK_DOMAINS == 8 + +invariant subth_threshold_bound + assert SUBTH_THRESHOLD_MV < NORMAL_VOLTAGE_MV + +invariant power_estimate_positive + try std.testing.expect(subth_power_estimate(100) >= 0); + +invariant power_savings_bound + try std.testing.expect(power_savings_ratio(1100, 550) <= 100); + +invariant power_state_enum_range + try std.testing.expect(@intFromEnum(PowerState.active) >= 0 and @intFromEnum(PowerState.deep_sleep) <= 3); + +invariant clk_gate_mode_enum_range + try std.testing.expect(@intFromEnum(ClkGateMode.off) >= 0 and @intFromEnum(ClkGateMode.auto) <= 2); + +invariant clk_domain_pulse_increases_activity + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_pulse(domain, 100)); + assert result.activity_count > domain.activity_count + +invariant clk_domain_decay_decreases_or_same + given domain = ClkDomain{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 5, .last_active_cycle = 0} + try std.testing.expect(result = clk_domain_decay(domain)); + assert result.activity_count <= domain.activity_count + +invariant power_savings_symmetric + assert power_savings_ratio(1100, 550) == power_savings_ratio(550, 1100) + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench clk_domain_is_idle_latency + measure: nanoseconds to clk_domain_is_idle(.{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}, 200, 100) + target: < 50ns + +bench is_subthreshold_latency + measure: nanoseconds to is_subthreshold(350, .{.threshold_mv = 400, .hysteresis_mv = 20, .enable_subth = true, .transition_cycles = 10}) + target: < 50ns + +bench subth_power_estimate_latency + measure: nanoseconds to subth_power_estimate(550) + target: < 50ns + +bench power_state_sleep_ok_latency + measure: nanoseconds to power_state_sleep_ok([_]ClkDomain{.{.id = 0, .enabled = true, .gate_mode = ClkGateMode.on, .activity_count = 0, .last_active_cycle = 0}} ** 8, 200) + target: < 100ns + +bench encode_subth_clk_latency + measure: nanoseconds to encode_subth_clk(0x05, 0x02) + target: < 20ns + +bench decode_subth_clk_latency + measure: nanoseconds to decode_subth_clk(0xE5A8) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/binary16.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/binary16.t27 new file mode 100644 index 0000000000..bb2b71e71f --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/binary16.t27 @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/binary16.t27 +// Binary16 - Binary packed 16-bit format (3 bits per integer) +// NUMERIC-STANDARD-001 Agent 13 (P1) +// +// Binary16 format: +// - Packs 5 signed integers (3 bits each) into 16 bits +// - Each integer range: -4 to 3 +// - Total: 5 * 3 = 15 bits + 1 padding bit +// - Optimal for binary neural networks +// - Extreme quantization for ternary/-1/0/+1 operations + +module Binary16 { + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 1. Format Definition + // Binary16 bit layout: [P(1) I0(3) I1(3) I2(3) I3(3) I4(3)] + // P: 1 bit (padding/unused) + // I0-I4: 3 bits each (signed two's complement integer) + // + // 5 integers stored, each using 3 bits: + // Range per integer: -4 to 3 + // Storage: Two's complement in 3 bits + // + // Use cases: + // - Binary neural networks + // - Ternary weight quantization + // - Compact activation storage + // - Sparse mask encoding + + const BITS : u8 = 16; + const INT_BITS : u8 = 3; + const NUM_INTS : u8 = 5; + const PADDING_BITS : u8 = 1; + + // 2. Binary16 Type + + struct Binary16 { + raw : u16, // 16-bit packed value + } + + // 3. Encoding/Decoding + + // Get a specific integer from the packed value + fn get_int(b: Binary16, index: u8) -> i8 { + if (index >= NUM_INTS) { return 0; } + // Extract 3-bit value at position index + const bit_offset = index * INT_BITS + PADDING_BITS; + const mask = 0x07; + const raw_val = ((b.raw >> bit_offset) & mask) as u8; + + // Convert 3-bit two's complement to i8 + if (raw_val >= 4) { + return (raw_val as i8) - 8; + } + return raw_val as i8; + } + + // Set a specific integer in the packed value + fn set_int(b: Binary16, index: u8, value: i8) -> Binary16 { + if (index >= NUM_INTS) { return b; } + + // Clamp value to 3-bit range + const clamped = if (value > 3) { 3 } else if (value < -4) { -4 } else { value }; + + // Convert i8 to 3-bit two's complement + let raw_val : u8 = clamped as u8; + if (clamped < 0) { + raw_val = raw_val + 8; + } + + // Clear and set the 3 bits + const bit_offset = index * INT_BITS + PADDING_BITS; + const mask = 0x07; + const cleared = b.raw & (~(mask << bit_offset) as u16); + const packed = cleared | ((raw_val as u16) << bit_offset); + + return Binary16{ raw = packed }; + } + + // Encode array of 5 i8 values to Binary16 + fn encode(values: [i8; NUM_INTS]) -> Binary16 { + let result = Binary16{ raw = 0 }; + for (i in 0..NUM_INTS) { + result = set_int(result, i, values[i]); + } + return result; + } + + // Decode Binary16 to array of 5 i8 values + fn decode(b: Binary16) -> [i8; NUM_INTS] { + let result = [0; NUM_INTS]; + for (i in 0..NUM_INTS) { + result[i] = get_int(b, i); + } + return result; + } + + // 4. Format Properties + + fn min_value() -> i8 { + return -4; + } + + fn max_value() -> i8 { + return 3; + } + + fn range() -> i8 { + return max_value() - min_value(); + } + + fn num_values_per_int() -> i8 { + return 8; + } + + // 5. Validation + + fn validate_format() -> bool { + return (BITS == 16) && + (INT_BITS == 3) && + (NUM_INTS == 5) && + (PADDING_BITS == 1); + } + + // 6. Use Cases + + // Binary16 is optimal for: + // - Binary neural networks (weights in {-1, 0, +1}) + // - Ternary quantization + // - Compact activation masks + // - Sparse representation encoding + // - Extreme memory compression (8x FP32) + + // Memory: 16 bits = 2 bytes (16x FP32 in same space, stores 5 integers) + const MEMORY_RATIO_VS_FP32 : f32 = 16.0 / 32.0; // 0.5 (for 16-bit word) + + // 7. Arithmetic Operations (element-wise) + + fn add(a: Binary16, b: Binary16, index: u8) -> Binary16 { + const val_a = get_int(a, index); + const val_b = get_int(b, index); + const result = val_a + val_b; + return set_int(a, index, result); + } + + fn sub(a: Binary16, b: Binary16, index: u8) -> Binary16 { + const val_a = get_int(a, index); + const val_b = get_int(b, index); + const result = val_a - val_b; + return set_int(a, index, result); + } + + fn neg(b: Binary16, index: u8) -> Binary16 { + const val = get_int(b, index); + return set_int(b, index, -val); + } + + // 8. Comparison Operations (element-wise) + + fn eq(a: Binary16, b: Binary16) -> bool { + return a.raw == b.raw; + } + + fn eq_at(a: Binary16, b: Binary16, index: u8) -> bool { + return get_int(a, index) == get_int(b, index); + } + + fn lt(a: Binary16, b: Binary16, index: u8) -> bool { + return get_int(a, index) < get_int(b, index); + } + + // 9. Special Operations for BNNs + + // Count non-zero values (useful for sparsity metrics) + fn count_nonzero(b: Binary16) -> u8 { + let count : u8 = 0; + for (i in 0..NUM_INTS) { + if (get_int(b, i) != 0) { + count = count + 1; + } + } + return count; + } + + // Check if any value is positive + fn any_positive(b: Binary16) -> bool { + for (i in 0..NUM_INTS) { + if (get_int(b, i) > 0) { + return true; + } + } + return false; + } + + // Check if any value is negative + fn any_negative(b: Binary16) -> bool { + for (i in 0..NUM_INTS) { + if (get_int(b, i) < 0) { + return true; + } + } + return false; + } + + // TDD-Inside-Spec: Tests and Invariants for Binary16 + + test binary16_decode_zero + given b = Binary16{ raw = 0 } + when decoded = decode(b) + then decoded == [0, 0, 0, 0, 0] + + test binary16_encode_zero_roundtrip + given original = [0, 0, 0, 0, 0] + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test binary16_encode_positive_values + given vals = [0, 1, 2, 3, 1] + and encoded = encode(vals) + then decode(encoded) == vals + + test binary16_encode_negative_values + given vals = [-1, -2, -3, -4, -1] + and encoded = encode(vals) + then decode(encoded) == vals + + test binary16_get_int_at_index + given b = encode([1, 2, 3, -1, -2]) + then get_int(b, 0) == 1 and get_int(b, 2) == 3 and get_int(b, 3) == -1 + + test binary16_set_int_at_index + given b = encode([0, 0, 0, 0, 0]) + and result = set_int(b, 2, 3) + then get_int(result, 2) == 3 + + test binary16_set_int_preserves_others + given b = encode([1, 2, 0, -1, -2]) + and result = set_int(b, 2, 3) + then get_int(result, 0) == 1 and get_int(result, 1) == 2 and get_int(result, 3) == -1 + + test binary16_bits_constant + then BITS == 16 + + test binary16_int_bits_constant + then INT_BITS == 3 + + test binary16_num_ints_constant + then NUM_INTS == 5 + + test binary16_padding_bits_constant + then PADDING_BITS == 1 + + test binary16_min_value + then min_value() == -4 + + test binary16_max_value + then max_value() == 3 + + test binary16_range + then range() == 7 + + test binary16_num_values_per_int + then num_values_per_int() == 8 + + test binary16_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.5) < 0.01 + + test binary16_validate_format_success + given valid = validate_format() + then valid == true + + test binary16_clamp_positive + given b = encode([0, 0, 0, 0, 0]) + and result = set_int(b, 0, 10) + then get_int(result, 0) == 3 + + test binary16_clamp_negative + given b = encode([0, 0, 0, 0, 0]) + and result = set_int(b, 0, -10) + then get_int(result, 0) == -4 + + test binary16_add_positive + given a = encode([1, 0, 0, 0, 0]) + and b = encode([2, 0, 0, 0, 0]) + and result = add(a, b, 0) + then get_int(result, 0) == 3 + + test binary16_add_overflow + given a = encode([3, 0, 0, 0, 0]) + and b = encode([2, 0, 0, 0, 0]) + and result = add(a, b, 0) + then get_int(result, 0) == 3 + + test binary16_sub_positive + given a = encode([3, 0, 0, 0, 0]) + and b = encode([1, 0, 0, 0, 0]) + and result = sub(a, b, 0) + then get_int(result, 0) == 2 + + test binary16_neg_positive + given b = encode([2, 0, 0, 0, 0]) + and result = neg(b, 0) + then get_int(result, 0) == -2 + + test binary16_eq_true + given a = encode([1, 2, 3, -1, -2]) + and b = encode([1, 2, 3, -1, -2]) + then eq(a, b) == true + + test binary16_eq_false + given a = encode([1, 2, 3, -1, -2]) + and b = encode([1, 2, 0, -1, -2]) + then eq(a, b) == false + + test binary16_eq_at_true + given a = encode([1, 2, 3, -1, -2]) + and b = encode([0, 0, 3, 0, 0]) + then eq_at(a, b, 2) == true + + test binary16_lt_true + given a = encode([1, 0, 0, 0, 0]) + and b = encode([3, 0, 0, 0, 0]) + then lt(a, b, 0) == true + + test binary16_count_nonzero_zero + given b = encode([0, 0, 0, 0, 0]) + then count_nonzero(b) == 0 + + test binary16_count_nonzero_some + given b = encode([0, 1, 0, -1, 0]) + then count_nonzero(b) == 2 + + test binary16_count_nonzero_all + given b = encode([1, 2, 3, -1, -2]) + then count_nonzero(b) == 5 + + test binary16_any_positive_true + given b = encode([0, 1, -1, 0, 0]) + then any_positive(b) == true + + test binary16_any_positive_false + given b = encode([0, 0, -1, -2, 0]) + then any_positive(b) == false + + test binary16_any_negative_true + given b = encode([0, 1, -1, 0, 0]) + then any_negative(b) == true + + test binary16_any_negative_false + given b = encode([0, 1, 2, 3, 0]) + then any_negative(b) == false + + test binary16_all_indices_range + given b = encode([0, 0, 0, 0, 0]) + and result = set_int(set_int(set_int(set_int(set_int(b, 0, -4), 1, -3), 2, -2), 3, -1), 4, 0) + then get_int(result, 0) == -4 and get_int(result, 4) == 0 + + invariant binary16_bits_constant + assert BITS == 16 + + invariant binary16_int_bits_is_three + assert INT_BITS == 3 + + invariant binary16_num_ints_is_five + assert NUM_INTS == 5 + + invariant binary16_min_value_is_neg_four + assert min_value() == -4 + + invariant binary16_max_value_is_three + assert max_value() == 3 + + invariant binary16_range_is_seven + assert range() == 7 + + invariant binary16_encode_decode_roundtrip + given vals = [1, 2, 3, -1, -2] + then decode(encode(vals)) == vals + + invariant binary16_get_set_consistency + given b = encode([0, 0, 0, 0, 0]) + and val = 2 + then get_int(set_int(b, 3, val), 3) == val + + invariant binary16_count_nonzero_in_range + for (b in {encode([0,0,0,0,0]), encode([1,0,0,0,0]), encode([1,2,3,-1,-2])}) + then count_nonzero(b) >= 0 and count_nonzero(b) <= 5 + + invariant binary16_packing_efficiency + assert NUM_INTS * INT_BITS + PADDING_BITS == BITS + + bench binary16_encode_latency + measure: nanoseconds to encode([1, 2, 3, -1, -2]) + target: < 50ns + + bench binary16_decode_latency + measure: nanoseconds to decode(encode([1, 2, 3, -1, -2])) + target: < 40ns + + bench binary16_get_int_latency + measure: nanoseconds to get_int(encode([1, 2, 3, -1, -2]), 2) + target: < 20ns + + bench binary16_set_int_latency + measure: nanoseconds to set_int(encode([0, 0, 0, 0, 0]), 2, 3) + target: < 30ns + + bench binary16_count_nonzero_latency + measure: nanoseconds to count_nonzero(encode([1, 2, 0, -1, 0])) + target: < 50ns + + bench binary16_eq_latency + measure: nanoseconds to eq(encode([1, 2, 3, -1, -2]), encode([1, 2, 3, -1, -2])) + target: < 10ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/formats.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/formats.t27 new file mode 100644 index 0000000000..90cda34b16 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/formats.t27 @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/numeric/formats.t27 +// Format Conversion Utilities - GF16, f32, ternary encoding +// phi^2 + 1/phi^2 = 3 | TRINITY + +module Formats { + // ======================================================================== + // IMPORTS - Reference existing specs, DO NOT DUPLICATE + // ======================================================================== + use base::types; + use numeric::gf16; + + // ======================================================================== + // 1. GF16 Bit Layout Constants + // ======================================================================== + // + // GF16 bit layout (as specified in whitepaper): + // [S(1) E(6) M(9)] = [15:15][14:9][8:0] + // + // - Sign: bit 15 (0x8000) + // - Exponent: bits 14-9 (0x7E00), bias = 31 + // - Mantissa: bits 8-0 (0x01FF) + // + // Range: 2^-31 to 2^32 + // ======================================================================== + + pub const SignMask : u16 = 0x8000; + pub const ExpMask : u16 = 0x7E00; + pub const MantMask : u16 = 0x01FF; + + pub const ExpShift : u5 = 9; + pub const SignShift : u4 = 15; + pub const Bias : i32 = 31; + + pub const ExpMax : u16 = 63; + pub const ExpMin : u16 = 0; + + // ======================================================================== + // 2. GF16 0 f32 (decode) + // ======================================================================== + // + // Converts GF16 encoding to IEEE 754 binary32 floating point. + // Handles signed zero, denormals, normals, infinities, and NaN. + // ======================================================================== + + // gf16_to_f32(x: u16) -> gf16 + // Decode GF16 to f32 + // + // Algorithm: + // 1. Extract sign (bit 15) + // 2. Extract exponent (bits 14-9) and mantissa (bits 8-0) + // 3. Handle special cases: + // - e=0, m=0: signed zero + // - e=0, m!=0: denormal (subnormal) + // - e=ExpMax, m=0: +/- infinity + // - e=ExpMax, m!=0: NaN + // 4. Normal case: + // value = (-1)^s * (1 + m/2^9) * 2^(e - Bias) + // + // Complexity: O(1) + pub fn gf16_to_f32(x: u16) -> gf16; + + // ======================================================================== + // 3. f32 1 GF16 (encode, round-to-nearest) + // ======================================================================== + // + // Converts IEEE 754 binary32 floating point to GF16 encoding. + // Handles signed zero, special cases, overflow, and underflow. + // + // Algorithm: + // 1. Handle signed zero explicitly (sign bit preserved) + // 2. Handle special cases (Inf, NaN) + // 3. Get exponent and mantissa via frexp: abs = m * 2^e, m in [0.5, 1] + // 4. Normalize: want 1.x * 2^(E - Bias), frexp gives m in [0.5, 1] + // 5. Mantissa: (m - 1.0) * 2^9, round to nearest + // 6. Check underflow/overflow + // + // Complexity: O(1) + pub fn f32_to_gf16(a: f32) -> u16; + + // ======================================================================== + // 4. Ternary Quantization + // ======================================================================== + // + // Ternary quantization: maps f32 to ternary {-1, 0, +1} + // Threshold: |w| > 0.5 -> +/-1, else -> 0 + // + // WHY: Enables efficient ternary representation of continuous values + // Useful for VSA (Vector Symbolic Architecture) operations + // ======================================================================== + + // f32_to_ternary(x: f32) -> Trit + // Quantize f32 to ternary {-1, 0, +1} + // + // Algorithm: + // - If x > 0.5: return +1 + // - If x < -0.5: return -1 + // - Otherwise: return 0 + // + // Complexity: O(1) + pub fn f32_to_ternary(x: f32) -> Trit; + + // ternary_to_f32(t: Trit) -> gf16 + // Convert ternary to f32 + // + // Mapping: -1 -> -1.0, 0 -> 0.0, +1 -> 1.0 + // Complexity: O(1) + pub fn ternary_to_f32(t: Trit) -> gf16; + + // ======================================================================== + // 5. Format Enum + // ======================================================================== + // + // Comprehensive format support: + // - IEEE Floating Point: fp32, fp16, bf16, fp8_e4m3, fp8_e5m2 + // - GoldenFloat: gf4, gf8, gf12, gf16 (PRIMARY), gf20, gf24, gf32, gf64, gf128, gf256 + // - Integer: int4, int8 + // - Quantization: nf4, binary16, ternary, posit16 + // ======================================================================== + + pub const Format = enum(u8) { + // IEEE Floating Point formats + fp32, + fp16, + bf16, + fp8_e4m3, + fp8_e5m2, + + // GoldenFloat family (φ-optimized) + gf4, + gf8, + gf12, + gf16, + gf20, + gf24, + gf32, + gf64, + gf128, + gf256, + + // Integer formats + int4, + int8, + + // Quantization formats + nf4, + binary16, + ternary, + + // Posit type + posit16, + }; + + // format_bytes(fmt: Format) -> usize + // Returns byte size for each format + // + // Byte sizes: + // - fp32: 4 bytes (32 bits) + // - fp16, bf16, gf16, int8: 2 bytes (16 bits) + // - fp8_e4m3, fp8_e5m2, gf8, int4: 1 byte (8 bits) + // - gf4: 0.5 bytes (4 bits, packed) + // - nf4: 0.5 bytes (4 bits, packed) + // - binary16: 2 bytes (16 bits, stores 5 ints) + // - gf12: 1.5 bytes (12 bits) + // - gf20: 2.5 bytes (20 bits) + // - gf24: 3 bytes (24 bits) + // - gf32: 4 bytes (32 bits) + // - gf64: 8 bytes (64 bits) + // - gf128: 16 bytes (128 bits) + // - gf256: 32 bytes (256 bits) + // - ternary: 1 byte (2 bits packed) + // - posit16: 2 bytes (16 bits) + // + // Complexity: O(1) + pub fn format_bytes(fmt: Format) -> usize; + + // ======================================================================== + // 6. Quantization Utility + // ======================================================================== + + // quantize_value(x: f32, fmt: Format) -> gf16 + // Quantize f32 to target format + // + // Complexity: O(1) + pub fn quantize_value(x: f32, fmt: Format) -> gf16; + + // ======================================================================== + // TDD - Tests + // ======================================================================== + + test gf16_to_f32_zero_positive + // Verify: zero encodes to zero + given x: u16 = 0 + when result = gf16_to_f32(x) + then gf16.to_f64(result) == 0.0 + + test gf16_to_f32_zero_negative + // Verify: negative zero encodes to -0 + given x: u16 = 0x8000 + when result = gf16_to_f32(x) + then gf16.to_f64(result) == -0.0 + + test gf16_to_f32_denormal + // Verify: denormal value decodes to small positive + given x: u16 = 0x0080 + when result = gf16_to_f32(x) + and val = gf16.to_f64(result) + then val > 0.0 and val < 1.0 + + test gf16_to_f32_normal_one + // Verify: 1.0 encodes correctly + given x: u16 = 0x3C00 + when result = gf16_to_f32(x) + and decoded = gf16.to_f64(result) + then decoded == 1.0 + + test gf16_to_f32_positive_inf + // Verify: positive infinity encodes correctly + given x: u16 = 0x7E00 + when result = gf16_to_f32(x) + and decoded = gf16.to_f64(result) + then decoded == std.math.inf(f32) + + test gf16_to_f32_negative_inf + // Verify: negative infinity encodes correctly + given x: u16 = 0xFE00 + when result = gf16_to_f32(x) + and decoded = gf16.to_f64(result) + then decoded == -std.math.inf(f32) + + test gf16_to_f32_nan + // Verify: NaN encodes to NaN + given x: u16 = 0x7F01 + when result = gf16_to_f32(x) + then result != result // NaN check + + test f32_to_gf16_zero_positive + // Verify: +0 encodes to 0 + given a: f32 = 0.0 + when result = f32_to_gf16(a) + then result == 0 + + test f32_to_gf16_zero_negative + // Verify: -0 encodes to 0x8000 + given a: f32 = -0.0 + when result = f32_to_gf16(a) + then result == 0x8000 + + test f32_to_gf16_one + // Verify: 1.0 encodes and roundtrips correctly + given a: f32 = 1.0 + and encoded = f32_to_gf16(a) + and decoded = gf16_to_f32(encoded) + and recovered = gf16.to_f64(decoded) + then recovered >= 0.99 and recovered <= 1.01 + + test f32_to_gf16_inf_positive + // Verify: +Inf encodes to 0x7E00 + given a: f32 = std.math.inf(f32) + when result = f32_to_gf16(a) + then result == 0x7E00 + + test f32_to_gf16_inf_negative + // Verify: -Inf encodes to 0xFE00 + given a: f32 = -std.math.inf(f32) + when result = f32_to_gf16(a) + then result == 0xFE00 + + test f32_to_gf16_nan + // Verify: NaN encodes to 0x7F01 + given a: f32 = std.math.nan(f32) + when result = f32_to_gf16(a) + then result == 0x7F01 + + test f32_to_ternary_positive + // Verify: 1.0 quantizes to pos + given a: f32 = 1.0 + when result = f32_to_ternary(a) + then result == .pos + + test f32_to_ternary_zero + // Verify: 0.0 quantizes to zero + given a: f32 = 0.0 + when result = f32_to_ternary(a) + then result == .zero + + test f32_to_ternary_negative + // Verify: -1.0 quantizes to neg + given a: f32 = -1.0 + when result = f32_to_ternary(a) + then result == .neg + + test f32_to_ternary_threshold + // Verify: 0.6 quantizes to pos (above 0.5 threshold) + given a: f32 = 0.6 + when result = f32_to_ternary(a) + then result == .pos + + test f32_to_ternary_negative_threshold + // Verify: -0.6 quantizes to neg (below -0.5 threshold) + given a: f32 = -0.6 + when result = f32_to_ternary(a) + then result == .neg + + test ternary_to_f32_positive + // Verify: pos maps to 1.0 + given t: Trit = .pos + when result = ternary_to_f32(t) + and result_val = gf16.to_f64(result) + then result_val == 1.0 + + test ternary_to_f32_zero + // Verify: zero maps to 0.0 + given t: Trit = .zero + when result = ternary_to_f32(t) + and result_val = gf16.to_f64(result) + then result_val == 0.0 + + test ternary_to_f32_negative + // Verify: neg maps to -1.0 + given t: Trit = .neg + when result = ternary_to_f32(t) + and result_val = gf16.to_f64(result) + then result_val == -1.0 + + test format_bytes_fp32 + // Verify: fp32 is 4 bytes + when result = format_bytes(.fp32) + then result == 4 + + test format_bytes_fp16 + // Verify: fp16 is 2 bytes + when result = format_bytes(.fp16) + then result == 2 + + test format_bytes_bf16 + // Verify: bf16 is 2 bytes + when result = format_bytes(.bf16) + then result == 2 + + test format_bytes_fp8_e4m3 + // Verify: fp8_e4m3 is 1 byte + when result = format_bytes(.fp8_e4m3) + then result == 1 + + test format_bytes_fp8_e5m2 + // Verify: fp8_e5m2 is 1 byte + when result = format_bytes(.fp8_e5m2) + then result == 1 + + test format_bytes_gf4 + // Verify: gf4 is 0.5 bytes (packed) + when result = format_bytes(.gf4) + then result == 0 + + test format_bytes_gf8 + // Verify: gf8 is 1 byte + when result = format_bytes(.gf8) + then result == 1 + + test format_bytes_gf16 + // Verify: gf16 is 2 bytes + when result = format_bytes(.gf16) + then result == 2 + + test format_bytes_gf32 + // Verify: gf32 is 4 bytes + when result = format_bytes(.gf32) + then result == 4 + + test format_bytes_gf64 + // Verify: gf64 is 8 bytes + when result = format_bytes(.gf64) + then result == 8 + + test format_bytes_gf128 + // Verify: gf128 is 16 bytes + when result = format_bytes(.gf128) + then result == 16 + + test format_bytes_gf256 + // Verify: gf256 is 32 bytes + when result = format_bytes(.gf256) + then result == 32 + + test format_bytes_int4 + // Verify: int4 is 0.5 bytes (packed) + when result = format_bytes(.int4) + then result == 0 + + test format_bytes_int8 + // Verify: int8 is 1 byte + when result = format_bytes(.int8) + then result == 1 + + test format_bytes_nf4 + // Verify: nf4 is 0.5 bytes (packed) + when result = format_bytes(.nf4) + then result == 0 + + test format_bytes_binary16 + // Verify: binary16 is 2 bytes + when result = format_bytes(.binary16) + then result == 2 + + test format_bytes_ternary + // Verify: ternary is 1 byte (packed) + when result = format_bytes(.ternary) + then result == 1 + + test format_bytes_posit16 + // Verify: posit16 is 2 bytes + when result = format_bytes(.posit16) + then result == 2 + + test quantize_value_fp32 + // Verify: quantizing to fp32 preserves value + given x: f32 = 1.5 + and result = quantize_value(x, .fp32) + and result_val = gf16.to_f64(result) + then result_val >= 1.49 and result_val <= 1.51 + + test quantize_value_ternary + // Verify: quantizing to ternary gives +1 + given x: f32 = 1.5 + and result = quantize_value(x, .ternary) + and expected = ternary_to_f32(.pos) + then result == expected + + test quantize_value_int8 + // Verify: quantizing to int8 clamps within range + given x: f32 = 200.0 + and result = quantize_value(x, .int8) + and result_val = gf16.to_f64(result) + then result_val <= 127.0 + + test quantize_value_nf4 + // Verify: quantizing to nf4 clamps to [0, 1] + given x: f32 = 1.5 + and result = quantize_value(x, .nf4) + and result_val = gf16.to_f64(result) + then result_val <= 1.0 + + // ======================================================================== + // TDD - Invariants + // ======================================================================== + + invariant gf16_to_f32_preserves_zero + // Zero should decode to zero + assert gf16.to_f64(gf16_to_f32(0)) == 0.0; + assert gf16.to_f64(gf16_to_f32(0x8000)) == -0.0; + + invariant gf16_to_f32_preserves_infinity + // Infinity should decode to infinity + assert gf16.to_f64(gf16_to_f32(0x7E00)) == std.math.inf(f32); + assert gf16.to_f64(gf16_to_f32(0xFE00)) == -std.math.inf(f32); + + invariant f32_to_gf16_roundtrip_loss + // Roundtrip should be within tolerance for normal values + const original = gf16.from_f64(1.5); + const encoded = f32_to_gf16(gf16.to_f64(original)); + const decoded = gf16_to_f32(encoded); + const error = gf16.abs(gf16.sub(original, decoded)); + assert gf16.to_f64(error) < gf16.from_f64(0.01); + + invariant ternary_quantization_symmetric + // Quantization threshold is symmetric + const p = f32_to_ternary(0.5); + const n = f32_to_ternary(-0.5); + const p_decoded = ternary_to_f32(p); + const n_decoded = ternary_to_f32(n); + assert gf16.to_f64(p_decoded) == -gf16.to_f64(n_decoded); + + invariant ternary_to_f32_is_inverse + // ternary_to_f32 is inverse of f32_to_ternary + const values = [_]f32{ -1.0, -0.5, 0.0, 0.5, 1.0 }; + for (values) |v| { + const t = f32_to_ternary(v); + const recovered = ternary_to_f32(t); + const diff = gf16.abs(gf16.sub(v, recovered)); + assert gf16.eq(v, recovered) or (gf16.to_f64(diff) < gf16.from_f64(0.01)); + } + + invariant format_bytes_positive + // All format byte sizes should be positive + assert format_bytes(.fp32) > 0; + assert format_bytes(.fp16) > 0; + assert format_bytes(.ternary) > 0; + + // ======================================================================== + // TDD - Benchmarks + // ======================================================================== + + bench gf16_to_f32_latency + // Measure: cycles for gf16_to_f32 conversion + // Target: < 50 cycles (simple bit extraction + lookup) + @setEvalBranchQuota(10000); + var result : gf16; + const test_value: u16 = 0x3C00; + for (0..1000) |_| { + result = gf16_to_f32(test_value); + } + _ = result; + + bench f32_to_gf16_latency + // Measure: cycles for f32_to_gf16 conversion + // Target: < 100 cycles (frexp + bit packing) + @setEvalBranchQuota(10000); + var result : u16; + const test_value: f32 = 1.5; + for (0..1000) |_| { + result = f32_to_gf16(test_value); + } + _ = result; + + bench f32_to_ternary_latency + // Measure: cycles for f32_to_ternary conversion + // Target: < 10 cycles (single comparison) + @setEvalBranchQuota(10000); + var result : Trit; + const test_value: f32 = 0.75; + for (0..1000) |_| { + result = f32_to_ternary(test_value); + } + _ = result; + + bench ternary_to_f32_latency + // Measure: cycles for ternary_to_f32 conversion + // Target: < 10 cycles (simple switch) + @setEvalBranchQuota(10000); + var result : gf16; + const test_trit: Trit = .pos; + for (0..1000) |_| { + result = ternary_to_f32(test_trit); + } + _ = result; +} diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/fp8_e4m3.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/fp8_e4m3.t27 new file mode 100644 index 0000000000..06fb882f2d --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/fp8_e4m3.t27 @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: Apache-2.0 +; fp8_e4m3.t27 — FP8 E4M3 8-bit Floating Point +; 8-bit float with 4 exponent, 3 mantissa bits (no implicit leading bit) +; OCP FP8 format optimized for neural network training +; Range: ~-240 to ~240, precision: ~6-7 significant bits +; φ² + 1/φ² = 3 | TRINITY + +module triformat-fp8_e4m3; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 4; +pub const MANT_BITS : u8 = 3; + +pub const SIGN_SHIFT : u8 = 7; +pub const EXP_SHIFT : u8 = 3; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u8 = 0x80; // 1 << 7 +pub const EXP_MASK : u8 = 0x78; // 0b01111000 +pub const MANT_MASK : u8 = 0x07; // 0b00000111 + +pub const EXP_MAX : u8 = 15; // All ones in 4 bits +pub const EXP_MIN : u8 = 0; +pub const EXP_BIAS : u8 = 7; // Exponent bias for FP8 E4M3 + +pub const MANT_DIVISOR : u8 = 8; // 2^3 + +// FP8 E4M3 special values +pub const FP8_ZERO_POS : u8 = 0x00; +pub const FP8_ZERO_NEG : u8 = 0x80; +pub const FP8_NAN_POS : u8 = 0x7F; // All exp + mant != 0 +pub const FP8_NAN_NEG : u8 = 0xFF; +pub const FP8_INF_POS : u8 = 0x78; // All exp + mant = 0 +pub const FP8_INF_NEG : u8 = 0xF8; + +// Subnormal exponent +pub const SUBNORMAL_EXP : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FP8E4M3 = u8; // 8-bit value + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// fp8_extract_sign(fp8: FP8E4M3) -> i8 +// Extract sign bit (bit 7) +// Returns: 0 for positive, -1 for negative +pub fn fp8_extract_sign(fp8: FP8E4M3) i8 { + const bit = (fp8 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// fp8_extract_exponent(fp8: FP8E4M3) -> u8 +// Extract exponent bits (bits 6-3) +// Returns: 0-15 +pub fn fp8_extract_exponent(fp8: FP8E4M3) u8 { + return (fp8 >> EXP_SHIFT) & 0x0F; +} + +// fp8_extract_mantissa(fp8: FP8E4M3) -> u8 +// Extract mantissa bits (bits 2-0) +// Returns: 0-7 +pub fn fp8_extract_mantissa(fp8: FP8E4M3) u8 { + return fp8 & MANT_MASK; +} + +// fp8_from_components(sign: i8, exp: u8, mant: u8) -> FP8E4M3 +// Assemble FP8 from sign, exponent, mantissa +pub fn fp8_from_components(sign: i8, exp: u8, mant: u8) FP8E4M3 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(FP8E4M3, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(FP8E4M3, @intCast(exp & 0x0F)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// fp8_is_zero(fp8: FP8E4M3) -> bool +// Check if FP8 is zero (positive or negative) +pub fn fp8_is_zero(fp8: FP8E4M3) bool { + return fp8 == FP8_ZERO_POS or fp8 == FP8_ZERO_NEG; +} + +// fp8_is_special(fp8: FP8E4M3) -> bool +// Check if FP8 is Inf or NaN (exp == 15) +pub fn fp8_is_special(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX; +} + +// fp8_is_inf(fp8: FP8E4M3) -> bool +// Check if FP8 is infinity (exp == 15, mant == 0) +pub fn fp8_is_inf(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) == 0; +} + +// fp8_is_nan(fp8: FP8E4M3) -> bool +// Check if FP8 is NaN (exp == 15, mant != 0) +pub fn fp8_is_nan(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) != 0; +} + +// fp8_is_subnormal(fp8: FP8E4M3) -> bool +// Check if FP8 is subnormal (exp == 0, mant != 0) +pub fn fp8_is_subnormal(fp8: FP8E4M3) bool { + return fp8_extract_exponent(fp8) == 0 and fp8_extract_mantissa(fp8) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// fp8_encode_f32(value: f32) -> FP8E4M3 +// Encode IEEE 754 single precision to FP8 E4M3 +// Round-to-nearest, ties to even +pub fn fp8_encode_f32(value: f32) FP8E4M3 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return if (value < 0.0) FP8_NAN_NEG else FP8_NAN_POS; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i16 = @as(i16, @intCast((f32_bits >> 23) & 0xFF)) - 127; + var f32_mant: u32 = f32_bits & 0x007FFFFF; + + // Handle very small values (subnormals) + if (f32_exp <= @as(i16, -EXP_BIAS)) { + // Below FP8 minimum normal - try to encode as subnormal + const subnormal_shift = @as(i16, -EXP_BIAS) - f32_exp + 1; + if (subnormal_shift > 3 + 23) { + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + var mant = f32_mant >> 23; + if (subnormal_shift > 0) { + const shift_amount = @as(u5, @intCast(subnormal_shift)); + if (shift_amount <= 24) { + mant = (f32_mant | 0x00800000) >> shift_amount; + } + } + return fp8_from_components(sign, 0, @as(u8, @truncate(mant)) & 0x07); + } + + // Convert exp from f32 bias (127) to FP8 bias (7) + var fp8_exp = @as(i16, f32_exp + EXP_BIAS); + + // Clamp exponent + if (fp8_exp >= EXP_MAX) { + return if (sign < 0) FP8_INF_NEG else FP8_INF_POS; + } else if (fp8_exp < 1) { + // Underflow to zero (could be subnormal, simplified) + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Extract mantissa and scale to 3 bits + // f32 mantissa is 24 bits (including implicit 1), FP8 needs 3 bits + // Shift right by 21 bits (24 - 3 = 21) + var mant = f32_mant >> 21; + + // Round-to-nearest with ties to even + const discarded = f32_mant & 0x1FFFFF; // Lower 21 bits + if ((discarded & 0x100000) != 0) { + // Halfway or more + if ((discarded & 0x0FFFFF) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (fp8_exp < @as(i16, EXP_MAX - 1)) { + fp8_exp += 1; + } + } + } + } + + return fp8_from_components(sign, @as(u8, @intCast(fp8_exp)), @as(u8, mant)); +} + +// fp8_decode_f32(fp8: FP8E4M3) -> f32 +// Decode FP8 E4M3 to IEEE 754 single precision +pub fn fp8_decode_f32(fp8: FP8E4M3) f32 { + // Handle zero + if (fp8_is_zero(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (fp8_is_nan(fp8)) { + return std.math.nan(f32); + } + + // Handle Infinity + if (fp8_is_inf(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -std.math.inf(f32) else std.math.inf(f32); + } + + // Extract components + const sign = fp8_extract_sign(fp8); + const exp = fp8_extract_exponent(fp8); + const mant = fp8_extract_mantissa(fp8); + + var f32_value: f32 = undefined; + + // Handle subnormal + if (exp == 0) { + // Subnormal: value = mant * 2^(-6) = mant * 2^(1-bias) + const bias_adjusted: i8 = 1 - @as(i8, EXP_BIAS); + const mant_f32 = @as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR)); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } else { + // Normal: value = (1 + mant/8) * 2^(exp - 7) + const bias_adjusted = @as(i16, exp) - @as(i16, EXP_BIAS); + const mant_f32 = 1.0 + (@as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR))); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } + + return if (sign < 0) -f32_value else f32_value; +} + +// fp8_encode_f64(value: f64) -> FP8E4M3 +// Encode IEEE 754 double precision to FP8 E4M3 +pub fn fp8_encode_f64(value: f64) FP8E4M3 { + return fp8_encode_f32(@as(f32, @floatCast(value))); +} + +// fp8_decode_f64(fp8: FP8E4M3) -> f64 +// Decode FP8 E4M3 to IEEE 754 double precision +pub fn fp8_decode_f64(fp8: FP8E4M3) f64 { + const f32_val = fp8_decode_f32(fp8); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// fp8_add(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Add two FP8 values +pub fn fp8_add(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa + fb); +} + +// fp8_sub(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Subtract two FP8 values +pub fn fp8_sub(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa - fb); +} + +// fp8_mul(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Multiply two FP8 values +pub fn fp8_mul(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa * fb); +} + +// fp8_div(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Divide two FP8 values +pub fn fp8_div(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + const fb = fp8_decode_f32(b); + if (fb == 0.0) { + const fa = fp8_decode_f32(a); + return if (fa < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + const fa = fp8_decode_f32(a); + return fp8_encode_f32(fa / fb); +} + +// fp8_sqrt(value: FP8E4M3) -> FP8E4M3 +// Square root of FP8 value +pub fn fp8_sqrt(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + if (fv < 0.0) { + return FP8_NAN_POS; + } + return fp8_encode_f32(std.math.sqrt(fv)); +} + +// fp8_rsqrt(value: FP8E4M3) -> FP8E4M3 +// Reciprocal square root (1/sqrt(x)) +pub fn fp8_rsqrt(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + if (fv <= 0.0) { + return FP8_INF_POS; + } + return fp8_encode_f32(1.0 / std.math.sqrt(fv)); +} + +// fp8_abs(value: FP8E4M3) -> FP8E4M3 +// Absolute value of FP8 +pub fn fp8_abs(value: FP8E4M3) FP8E4M3 { + return value & ~SIGN_MASK; +} + +// fp8_neg(value: FP8E4M3) -> FP8E4M3 +// Negate FP8 +pub fn fp8_neg(value: FP8E4M3) FP8E4M3 { + return value ^ SIGN_MASK; +} + +// fp8_is_equal(a: FP8E4M3, b: FP8E4M3) -> bool +// Check if two FP8 values are equal +pub fn fp8_is_equal(a: FP8E4M3, b: FP8E4M3) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + if (fp8_is_zero(a) and fp8_is_zero(b)) { + return true; + } + return a == b; +} + +// fp8_is_greater(a: FP8E4M3, b: FP8E4M3) -> bool +// Check if a > b +pub fn fp8_is_greater(a: FP8E4M3, b: FP8E4M3) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + const sign_a = fp8_extract_sign(a); + const sign_b = fp8_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return fp8_neg(a) > fp8_neg(b); + } + return a > b; +} + +// fp8_max(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Return the larger of two FP8 values +pub fn fp8_max(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + return if (fp8_is_greater(a, b)) a else b; +} + +// fp8_min(a: FP8E4M3, b: FP8E4M3) -> FP8E4M3 +// Return the smaller of two FP8 values +pub fn fp8_min(a: FP8E4M3, b: FP8E4M3) FP8E4M3 { + return if (fp8_is_greater(a, b)) b else a; +} + +// fp8_clamp(value: FP8E4M3, min: FP8E4M3, max: FP8E4M3) -> FP8E4M3 +// Clamp value between min and max +pub fn fp8_clamp(value: FP8E4M3, min: FP8E4M3, max: FP8E4M3) FP8E4M3 { + return fp8_min(fp8_max(value, min), max); +} + +// fp8_lerp(a: FP8E4M3, b: FP8E4M3, t: f32) -> FP8E4M3 +// Linear interpolation between a and b +pub fn fp8_lerp(a: FP8E4M3, b: FP8E4M3, t: f32) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const result = fa + (fb - fa) * t; + return fp8_encode_f32(result); +} + +// fp8_fma(a: FP8E4M3, b: FP8E4M3, c: FP8E4M3) -> FP8E4M3 +// Fused multiply-add: a * b + c +pub fn fp8_fma(a: FP8E4M3, b: FP8E4M3, c: FP8E4M3) FP8E4M3 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const fc = fp8_decode_f32(c); + return fp8_encode_f32(fa * fb + fc); +} + +// fp8_scale(value: FP8E4M3, scale: f32) -> FP8E4M3 +// Scale FP8 value by scalar +pub fn fp8_scale(value: FP8E4M3, scale: f32) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * scale); +} + +// fp8_relu(value: FP8E4M3) -> FP8E4M3 +// ReLU activation: max(0, x) +pub fn fp8_relu(value: FP8E4M3) FP8E4M3 { + return if (fp8_extract_sign(value) < 0) FP8_ZERO_POS else value; +} + +// fp8_sigmoid(value: FP8E4M3) -> FP8E4M3 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn fp8_sigmoid(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// fp8_tanh(value: FP8E4M3) -> FP8E4M3 +// Tanh activation +pub fn fp8_tanh(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(std.math.tanh(fv)); +} + +// fp8_gelu(value: FP8E4M3) -> FP8E4M3 +// GELU activation +pub fn fp8_gelu(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + const sqrt_2_over_pi = 0.7978845608; + const cube_coeff = 0.044715; + const tanh_arg = fv * (1.0 + cube_coeff * fv * fv); + return fp8_encode_f32(0.5 * fv * (1.0 + std.math.tanh(sqrt_2_over_pi * tanh_arg))); +} + +// fp8_swish(value: FP8E4M3) -> FP8E4M3 +// Swish activation: x * sigmoid(x) +pub fn fp8_swish(value: FP8E4M3) FP8E4M3 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * (1.0 / (1.0 + std.math.exp(-fv)))); +} + +// fp8_quantize_to_int4(value: FP8E4M3) -> i8 +// Quantize FP8 to Int4 (range [-8, 7]) +pub fn fp8_quantize_to_int4(value: FP8E4M3) i8 { + const fv = fp8_decode_f32(value); + const scaled = fv * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// fp8_quantize_to_int8(value: FP8E4M3) -> i8 +// Quantize FP8 to Int8 (range [-128, 127]) +pub fn fp8_quantize_to_int8(value: FP8E4M3) i8 { + const fv = fp8_decode_f32(value); + const scaled = fv * 127.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 127) { + result = 127; + } else if (result < -128) { + result = -128; + } + return @as(i8, @intCast(result)); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fp8_extract_sign_positive" { + given value = 0x3C + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "fp8_extract_sign_negative" { + given value = 0xBC + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "fp8_extract_exponent_max" { + given value = 0x78 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == EXP_MAX); +} + +test "fp8_extract_exponent_zero" { + given value = 0x07 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == 0); +} + +test "fp8_extract_mantissa_max" { + given value = 0x07 + try std.testing.expect(mant = fp8_extract_mantissa(value)); + try std.testing.expect(mant == MANT_MASK); +} + +test "fp8_is_zero_positive" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_POS) == true); +} + +test "fp8_is_zero_negative" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_NEG) == true); +} + +test "fp8_is_zero_nonzero" { + try std.testing.expect(fp8_is_zero(0x01) == false); +} + +test "fp8_is_inf_positive" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true); +} + +test "fp8_is_inf_negative" { + try std.testing.expect(fp8_is_inf(FP8_INF_NEG) == true); +} + +test "fp8_is_nan_positive" { + try std.testing.expect(fp8_is_nan(FP8_NAN_POS) == true); +} + +test "fp8_is_nan_negative" { + try std.testing.expect(fp8_is_nan(FP8_NAN_NEG) == true); +} + +test "fp8_is_inf_not_nan" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true and fp8_is_nan(FP8_INF_POS) == false); +} + +test "fp8_is_special_inf" { + try std.testing.expect(fp8_is_special(FP8_INF_POS) == true); +} + +test "fp8_is_special_nan" { + try std.testing.expect(fp8_is_special(FP8_NAN_POS) == true); +} + +test "fp8_is_subnormal_true" { + given value = 0x01 + try std.testing.expect(fp8_is_subnormal(value) == true); +} + +test "fp8_is_subnormal_false_normal" { + given value = 0x08 + try std.testing.expect(fp8_is_subnormal(value) == false); +} + +test "fp8_is_subnormal_false_zero" { + try std.testing.expect(fp8_is_subnormal(FP8_ZERO_POS) == false); +} + +test "fp8_encode_f32_zero" { + given fp = fp8_encode_f32(0.0) + try std.testing.expect(fp == FP8_ZERO_POS); +} + +test "fp8_encode_f32_negative_zero" { + given fp = fp8_encode_f32(-0.0) + try std.testing.expect(fp == FP8_ZERO_NEG); +} + +test "fp8_encode_f32_one" { + given fp = fp8_encode_f32(1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 1.0) < 0.05); +} + +test "fp8_encode_f32_negative_one" { + given fp = fp8_encode_f32(-1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded + 1.0) < 0.05); +} + +test "fp8_encode_f32_two" { + given fp = fp8_encode_f32(2.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 2.0) < 0.1); +} + +test "fp8_encode_f32_half" { + given fp = fp8_encode_f32(0.5) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 0.5) < 0.05); +} + +test "fp8_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.1); +} + +test "fp8_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.1); +} + +test "fp8_encode_f32_nan" { + given fp = fp8_encode_f32(std.math.nan(f32)) + try std.testing.expect(fp8_is_nan(fp) == true); +} + +test "fp8_encode_f32_inf" { + given fp = fp8_encode_f32(std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_encode_f32_neg_inf" { + given fp = fp8_encode_f32(-std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_decode_f32_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_POS) == 0.0); +} + +test "fp8_decode_f32_negative_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_NEG) == -0.0); +} + +test "fp8_decode_f32_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_POS)) == true); +} + +test "fp8_decode_f32_neg_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_NEG)) == true); +} + +test "fp8_decode_f32_nan" { + try std.testing.expect(std.math.isNan(fp8_decode_f32(FP8_NAN_POS)) == true); +} + +test "fp8_add_simple" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(result = fp8_add(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.15); +} + +test "fp8_sub_simple" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(result = fp8_sub(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.15); +} + +test "fp8_mul_simple" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(result = fp8_mul(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 6.0) < 0.3); +} + +test "fp8_div_simple" { + given a = fp8_encode_f32(6.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.15); +} + +test "fp8_div_by_zero_positive" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_POS); +} + +test "fp8_div_by_zero_negative" { + given a = fp8_encode_f32(-5.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_NEG); +} + +test "fp8_sqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.1); +} + +test "fp8_sqrt_negative" { + given value = fp8_encode_f32(-4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(fp8_is_nan(result) == true); +} + +test "fp8_rsqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_rsqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_abs_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(result = fp8_abs(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_abs_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(abs_val = fp8_abs(value)); + try std.testing.expect(fp8_extract_sign(abs_val) == 0); +} + +test "fp8_neg_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) < 0); +} + +test "fp8_neg_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) == 0); +} + +test "fp8_is_equal_true" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(fp8_is_equal(value, value) == true); +} + +test "fp8_is_equal_false" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(2.0)); + try std.testing.expect(fp8_is_equal(a, b) == false); +} + +test "fp8_is_equal_nan" { + try std.testing.expect(fp8_is_equal(FP8_NAN_POS, FP8_NAN_POS) == false); +} + +test "fp8_is_equal_zero" { + try std.testing.expect(fp8_is_equal(FP8_ZERO_POS, FP8_ZERO_NEG) == true); +} + +test "fp8_is_greater_positive" { + given a = fp8_encode_f32(5.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(fp8_is_greater(a, b) == true); +} + +test "fp8_max_returns_larger" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 4.0); +} + +test "fp8_min_returns_smaller" { + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 4.0); +} + +test "fp8_lerp_zero" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, a) == true); +} + +test "fp8_lerp_one" { + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, b) == true); +} + +test "fp8_fma_simple" { + given a = fp8_encode_f32(2.0) + try std.testing.expect(b = fp8_encode_f32(3.0)); + try std.testing.expect(c = fp8_encode_f32(1.0)); + try std.testing.expect(result = fp8_fma(a, b, c)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 7.0) < 0.3); +} + +test "fp8_relu_positive" { + given value = fp8_encode_f32(5.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_relu_negative" { + given value = fp8_encode_f32(-5.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_relu_zero" { + given result = fp8_relu(FP8_ZERO_POS) + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_sigmoid_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.05); +} + +test "fp8_sigmoid_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.95); +} + +test "fp8_sigmoid_negative" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 0.05); +} + +test "fp8_tanh_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded) < 0.05); +} + +test "fp8_tanh_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.95); +} + +test "fp8_tanh_negative" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= -0.95); +} + +test "fp8_gelu_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_gelu(value)); + try std.testing.expect(fp8_extract_sign(result) == 0); +} + +test "fp8_swish_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_swish_positive" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded > 0.0); +} + +test "fp8_quantize_to_int4_max" { + given value = fp8_encode_f32(2.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "fp8_quantize_to_int4_min" { + given value = fp8_encode_f32(-2.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "fp8_quantize_to_int8_max" { + given value = fp8_encode_f32(2.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result == 127); +} + +test "fp8_quantize_to_int8_min" { + given value = fp8_encode_f32(-2.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result == -128); +} + +test "fp8_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 7); + try std.testing.expect(mant = 4); + try std.testing.expect(fp = fp8_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = fp8_extract_sign(fp)); + try std.testing.expect(extracted_exp = fp8_extract_exponent(fp)); + try std.testing.expect(extracted_mant = fp8_extract_mantissa(fp)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fp8_bits_constant + assert BITS == 8 + +invariant fp8_sign_bits_one + assert SIGN_BITS == 1 + +invariant fp8_exp_bits_four + assert EXP_BITS == 4 + +invariant fp8_mant_bits_three + assert MANT_BITS == 3 + +invariant fp8_exp_bias_seven + assert EXP_BIAS == 7 + +invariant fp8_exp_max_all_ones + assert EXP_MAX == 0x0F + +invariant fp8_sign_mask_correct + assert SIGN_MASK == 0x80 + +invariant fp8_exp_mask_correct + assert EXP_MASK == 0x78 + +invariant fp8_mant_mask_correct + assert MANT_MASK == 0x07 + +invariant fp8_masks_sum_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFF + +invariant fp8_zero_pos_no_sign + assert (FP8_ZERO_POS & SIGN_MASK) == 0 + +invariant fp8_zero_neg_has_sign + assert (FP8_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant fp8_inf_pos_max_exp + assert (FP8_INF_POS & EXP_MASK) == EXP_MASK + +invariant fp8_inf_pos_no_mant + assert (FP8_INF_POS & MANT_MASK) == 0 + +invariant fp8_nan_has_mantissa + assert (FP8_NAN_POS & MANT_MASK) != 0 + +invariant fp8_abs_removes_sign + assert fp8_abs(FP8_ZERO_NEG) == FP8_ZERO_POS + +invariant fp8_neg_toggles_sign + assert (fp8_neg(FP8_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant fp8_add_zero_identity + given value = fp8_encode_f32(5.0) + assert fp8_is_equal(fp8_add(value, FP8_ZERO_POS), value) == true + +invariant fp8_sub_zero_identity + given value = fp8_encode_f32(5.0) + assert fp8_is_equal(fp8_sub(value, FP8_ZERO_POS), value) == true + +invariant fp8_mul_one_identity + given value = fp8_encode_f32(5.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_mul(value, one), value) == true or fp8_magnitude(value) > 0.5 + +invariant fp8_div_one_identity + given value = fp8_encode_f32(5.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_div(value, one), value) == true + +invariant fp8_max_ge_both + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(fp8_is_greater(result, a) == true and fp8_is_greater(result, b) == true or fp8_is_equal(result, b) == true); + +invariant fp8_min_le_both + given a = fp8_encode_f32(3.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(fp8_is_greater(a, result) == true or fp8_is_equal(a, result) == true); + +invariant fp8_relu_non_negative + given result = fp8_relu(fp8_encode_f32(5.0)) + try std.testing.expect(fp8_extract_sign(result) == 0); + +invariant fp8_lerp_zero_start + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + assert fp8_is_equal(fp8_lerp(a, b, 0.0), a) == true + +invariant fp8_lerp_one_end + given a = fp8_encode_f32(1.0) + try std.testing.expect(b = fp8_encode_f32(5.0)); + assert fp8_is_equal(fp8_lerp(a, b, 1.0), b) == true + +invariant fp8_sqrt_square_close + given value = fp8_encode_f32(4.0) + try std.testing.expect(sqrt_val = fp8_sqrt(value)); + try std.testing.expect(sq_val = fp8_mul(sqrt_val, sqrt_val)); + try std.testing.expect(decoded = fp8_decode_f32(sq_val)); + try std.testing.expect(abs(decoded - 4.0) < 0.5); + +invariant fp8_quantize_to_int4_in_range + given result = fp8_quantize_to_int4(fp8_encode_f32(1.0)) + assert result >= -8 and result <= 7 + +invariant fp8_quantize_to_int8_in_range + given result = fp8_quantize_to_int8(fp8_encode_f32(1.0)) + assert result >= -128 and result <= 127 + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fp8_encode_f32_latency + measure: nanoseconds to fp8_encode_f32(1.5) + target: < 50ns + +bench fp8_decode_f32_latency + measure: nanoseconds to fp8_decode_f32(0x3C) + target: < 50ns + +bench fp8_add_latency + measure: nanoseconds to fp8_add(0x3C, 0x48) + target: < 100ns + +bench fp8_mul_latency + measure: nanoseconds to fp8_mul(0x3C, 0x3C) + target: < 100ns + +bench fp8_sqrt_latency + measure: nanoseconds to fp8_sqrt(0x30) + target: < 100ns + +bench fp8_sigmoid_latency + measure: nanoseconds to fp8_sigmoid(0x30) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/fp8_e5m2.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/fp8_e5m2.t27 new file mode 100644 index 0000000000..e46e52429f --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/fp8_e5m2.t27 @@ -0,0 +1,1212 @@ +// SPDX-License-Identifier: Apache-2.0 +; fp8_e5m2.t27 — FP8 E5M2 8-bit Floating Point +; 8-bit float with 5 exponent, 2 mantissa bits (with implicit leading bit) +; OCP FP8 format optimized for inference and wide dynamic range +; Range: ~-57k to ~57k, precision: ~3-4 significant bits +; φ² + 1/φ² = 3 | TRINITY + +module triformat-fp8_e5m2; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 5; +pub const MANT_BITS : u8 = 2; + +pub const SIGN_SHIFT : u8 = 7; +pub const EXP_SHIFT : u8 = 2; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u8 = 0x80; // 1 << 7 +pub const EXP_MASK : u8 = 0x7C; // 0b01111100 +pub const MANT_MASK : u8 = 0x03; // 0b00000011 + +pub const EXP_MAX : u8 = 31; // All ones in 5 bits +pub const EXP_MIN : u8 = 0; +pub const EXP_BIAS : u8 = 15; // Exponent bias for FP8 E5M2 + +pub const MANT_DIVISOR : u8 = 4; // 2^2 + +// FP8 E5M2 special values +pub const FP8_ZERO_POS : u8 = 0x00; +pub const FP8_ZERO_NEG : u8 = 0x80; +pub const FP8_NAN_POS : u8 = 0x7F; // All exp + mant != 0 +pub const FP8_NAN_NEG : u8 = 0xFF; +pub const FP8_INF_POS : u8 = 0x7C; // All exp + mant = 0 +pub const FP8_INF_NEG : u8 = 0xFC; + +// Subnormal exponent +pub const SUBNORMAL_EXP : u8 = 0; + +// ============================================================================ +// Types +// ============================================================================ + +pub const FP8E5M2 = u8; // 8-bit value + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// fp8_extract_sign(fp8: FP8E5M2) -> i8 +// Extract sign bit (bit 7) +// Returns: 0 for positive, -1 for negative +pub fn fp8_extract_sign(fp8: FP8E5M2) i8 { + const bit = (fp8 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// fp8_extract_exponent(fp8: FP8E5M2) -> u8 +// Extract exponent bits (bits 6-2) +// Returns: 0-31 +pub fn fp8_extract_exponent(fp8: FP8E5M2) u8 { + return (fp8 >> EXP_SHIFT) & 0x1F; +} + +// fp8_extract_mantissa(fp8: FP8E5M2) -> u8 +// Extract mantissa bits (bits 1-0) +// Returns: 0-3 +pub fn fp8_extract_mantissa(fp8: FP8E5M2) u8 { + return fp8 & MANT_MASK; +} + +// fp8_from_components(sign: i8, exp: u8, mant: u8) -> FP8E5M2 +// Assemble FP8 from sign, exponent, mantissa +pub fn fp8_from_components(sign: i8, exp: u8, mant: u8) FP8E5M2 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(FP8E5M2, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(FP8E5M2, @intCast(exp & 0x1F)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// fp8_is_zero(fp8: FP8E5M2) -> bool +// Check if FP8 is zero (positive or negative) +pub fn fp8_is_zero(fp8: FP8E5M2) bool { + return fp8 == FP8_ZERO_POS or fp8 == FP8_ZERO_NEG; +} + +// fp8_is_special(fp8: FP8E5M2) -> bool +// Check if FP8 is Inf or NaN (exp == 31) +pub fn fp8_is_special(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX; +} + +// fp8_is_inf(fp8: FP8E5M2) -> bool +// Check if FP8 is infinity (exp == 31, mant == 0) +pub fn fp8_is_inf(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) == 0; +} + +// fp8_is_nan(fp8: FP8E5M2) -> bool +// Check if FP8 is NaN (exp == 31, mant != 0) +pub fn fp8_is_nan(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == EXP_MAX and fp8_extract_mantissa(fp8) != 0; +} + +// fp8_is_subnormal(fp8: FP8E5M2) -> bool +// Check if FP8 is subnormal (exp == 0, mant != 0) +pub fn fp8_is_subnormal(fp8: FP8E5M2) bool { + return fp8_extract_exponent(fp8) == 0 and fp8_extract_mantissa(fp8) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// fp8_encode_f32(value: f32) -> FP8E5M2 +// Encode IEEE 754 single precision to FP8 E5M2 +// Round-to-nearest, ties to even +pub fn fp8_encode_f32(value: f32) FP8E5M2 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return if (value < 0.0) FP8_NAN_NEG else FP8_NAN_POS; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i16 = @as(i16, @intCast((f32_bits >> 23) & 0xFF)) - 127; + var f32_mant: u32 = f32_bits & 0x007FFFFF; + + // Handle very small values (subnormals) + if (f32_exp <= @as(i16, -EXP_BIAS)) { + // Below FP8 minimum normal - try to encode as subnormal + const subnormal_shift = @as(i16, -EXP_BIAS) - f32_exp + 1; + if (subnormal_shift > 2 + 23) { + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + var mant = f32_mant >> 23; + if (subnormal_shift > 0) { + const shift_amount = @as(u5, @intCast(subnormal_shift)); + if (shift_amount <= 24) { + mant = (f32_mant | 0x00800000) >> shift_amount; + } + } + return fp8_from_components(sign, 0, @as(u8, @truncate(mant)) & 0x03); + } + + // Convert exp from f32 bias (127) to FP8 bias (15) + var fp8_exp = @as(i16, f32_exp + EXP_BIAS); + + // Clamp exponent + if (fp8_exp >= EXP_MAX) { + return if (sign < 0) FP8_INF_NEG else FP8_INF_POS; + } else if (fp8_exp < 1) { + // Underflow to zero (could be subnormal, simplified) + return if (sign < 0) FP8_ZERO_NEG else FP8_ZERO_POS; + } + + // Extract mantissa and scale to 2 bits + // f32 mantissa is 24 bits (including implicit 1), FP8 needs 2 bits + // Shift right by 22 bits (24 - 2 = 22) + var mant = f32_mant >> 22; + + // Round-to-nearest with ties to even + const discarded = f32_mant & 0x003FFFFF; // Lower 22 bits + if ((discarded & 0x00200000) != 0) { + // Halfway or more + if ((discarded & 0x001FFFFF) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (fp8_exp < @as(i16, EXP_MAX - 1)) { + fp8_exp += 1; + } + } + } + } + + return fp8_from_components(sign, @as(u8, @intCast(fp8_exp)), @as(u8, mant)); +} + +// fp8_decode_f32(fp8: FP8E5M2) -> f32 +// Decode FP8 E5M2 to IEEE 754 single precision +pub fn fp8_decode_f32(fp8: FP8E5M2) f32 { + // Handle zero + if (fp8_is_zero(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (fp8_is_nan(fp8)) { + return std.math.nan(f32); + } + + // Handle Infinity + if (fp8_is_inf(fp8)) { + return if (fp8_extract_sign(fp8) < 0) -std.math.inf(f32) else std.math.inf(f32); + } + + // Extract components + const sign = fp8_extract_sign(fp8); + const exp = fp8_extract_exponent(fp8); + const mant = fp8_extract_mantissa(fp8); + + var f32_value: f32 = undefined; + + // Handle subnormal + if (exp == 0) { + // Subnormal: value = mant * 2^(-14) = mant * 2^(1-bias) + const bias_adjusted: i8 = 1 - @as(i8, EXP_BIAS); + const mant_f32 = @as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR)); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } else { + // Normal: value = (1 + mant/4) * 2^(exp - 15) + const bias_adjusted = @as(i16, exp) - @as(i16, EXP_BIAS); + const mant_f32 = 1.0 + (@as(f32, @floatFromInt(mant)) / @as(f32, @floatFromInt(MANT_DIVISOR))); + f32_value = mant_f32 * std.math.pow(f32, 2.0, @as(f32, @floatFromInt(bias_adjusted))); + } + + return if (sign < 0) -f32_value else f32_value; +} + +// fp8_encode_f64(value: f64) -> FP8E5M2 +// Encode IEEE 754 double precision to FP8 E5M2 +pub fn fp8_encode_f64(value: f64) FP8E5M2 { + return fp8_encode_f32(@as(f32, @floatCast(value))); +} + +// fp8_decode_f64(fp8: FP8E5M2) -> f64 +// Decode FP8 E5M2 to IEEE 754 double precision +pub fn fp8_decode_f64(fp8: FP8E5M2) f64 { + const f32_val = fp8_decode_f32(fp8); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// fp8_add(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Add two FP8 values +pub fn fp8_add(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa + fb); +} + +// fp8_sub(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Subtract two FP8 values +pub fn fp8_sub(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa - fb); +} + +// fp8_mul(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Multiply two FP8 values +pub fn fp8_mul(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + return fp8_encode_f32(fa * fb); +} + +// fp8_div(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Divide two FP8 values +pub fn fp8_div(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + const fb = fp8_decode_f32(b); + if (fb == 0.0) { + const fa = fp8_decode_f32(a); + return if (fa < 0.0) FP8_INF_NEG else FP8_INF_POS; + } + const fa = fp8_decode_f32(a); + return fp8_encode_f32(fa / fb); +} + +// fp8_sqrt(value: FP8E5M2) -> FP8E5M2 +// Square root of FP8 value +pub fn fp8_sqrt(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + if (fv < 0.0) { + return FP8_NAN_POS; + } + return fp8_encode_f32(std.math.sqrt(fv)); +} + +// fp8_rsqrt(value: FP8E5M2) -> FP8E5M2 +// Reciprocal square root (1/sqrt(x)) +pub fn fp8_rsqrt(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + if (fv <= 0.0) { + return FP8_INF_POS; + } + return fp8_encode_f32(1.0 / std.math.sqrt(fv)); +} + +// fp8_abs(value: FP8E5M2) -> FP8E5M2 +// Absolute value of FP8 +pub fn fp8_abs(value: FP8E5M2) FP8E5M2 { + return value & ~SIGN_MASK; +} + +// fp8_neg(value: FP8E5M2) -> FP8E5M2 +// Negate FP8 +pub fn fp8_neg(value: FP8E5M2) FP8E5M2 { + return value ^ SIGN_MASK; +} + +// fp8_is_equal(a: FP8E5M2, b: FP8E5M2) -> bool +// Check if two FP8 values are equal +pub fn fp8_is_equal(a: FP8E5M2, b: FP8E5M2) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + if (fp8_is_zero(a) and fp8_is_zero(b)) { + return true; + } + return a == b; +} + +// fp8_is_greater(a: FP8E5M2, b: FP8E5M2) -> bool +// Check if a > b +pub fn fp8_is_greater(a: FP8E5M2, b: FP8E5M2) bool { + if (fp8_is_nan(a) or fp8_is_nan(b)) { + return false; + } + const sign_a = fp8_extract_sign(a); + const sign_b = fp8_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return fp8_neg(a) > fp8_neg(b); + } + return a > b; +} + +// fp8_max(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Return the larger of two FP8 values +pub fn fp8_max(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + return if (fp8_is_greater(a, b)) a else b; +} + +// fp8_min(a: FP8E5M2, b: FP8E5M2) -> FP8E5M2 +// Return the smaller of two FP8 values +pub fn fp8_min(a: FP8E5M2, b: FP8E5M2) FP8E5M2 { + return if (fp8_is_greater(a, b)) b else a; +} + +// fp8_clamp(value: FP8E5M2, min: FP8E5M2, max: FP8E5M2) -> FP8E5M2 +// Clamp value between min and max +pub fn fp8_clamp(value: FP8E5M2, min: FP8E5M2, max: FP8E5M2) FP8E5M2 { + return fp8_min(fp8_max(value, min), max); +} + +// fp8_lerp(a: FP8E5M2, b: FP8E5M2, t: f32) -> FP8E5M2 +// Linear interpolation between a and b +pub fn fp8_lerp(a: FP8E5M2, b: FP8E5M2, t: f32) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const result = fa + (fb - fa) * t; + return fp8_encode_f32(result); +} + +// fp8_fma(a: FP8E5M2, b: FP8E5M2, c: FP8E5M2) -> FP8E5M2 +// Fused multiply-add: a * b + c +pub fn fp8_fma(a: FP8E5M2, b: FP8E5M2, c: FP8E5M2) FP8E5M2 { + const fa = fp8_decode_f32(a); + const fb = fp8_decode_f32(b); + const fc = fp8_decode_f32(c); + return fp8_encode_f32(fa * fb + fc); +} + +// fp8_scale(value: FP8E5M2, scale: f32) -> FP8E5M2 +// Scale FP8 value by scalar +pub fn fp8_scale(value: FP8E5M2, scale: f32) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * scale); +} + +// fp8_relu(value: FP8E5M2) -> FP8E5M2 +// ReLU activation: max(0, x) +pub fn fp8_relu(value: FP8E5M2) FP8E5M2 { + return if (fp8_extract_sign(value) < 0) FP8_ZERO_POS else value; +} + +// fp8_sigmoid(value: FP8E5M2) -> FP8E5M2 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn fp8_sigmoid(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// fp8_tanh(value: FP8E5M2) -> FP8E5M2 +// Tanh activation +pub fn fp8_tanh(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(std.math.tanh(fv)); +} + +// fp8_gelu(value: FP8E5M2) -> FP8E5M2 +// GELU activation +pub fn fp8_gelu(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + const sqrt_2_over_pi = 0.7978845608; + const cube_coeff = 0.044715; + const tanh_arg = fv * (1.0 + cube_coeff * fv * fv); + return fp8_encode_f32(0.5 * fv * (1.0 + std.math.tanh(sqrt_2_over_pi * tanh_arg))); +} + +// fp8_swish(value: FP8E5M2) -> FP8E5M2 +// Swish activation: x * sigmoid(x) +pub fn fp8_swish(value: FP8E5M2) FP8E5M2 { + const fv = fp8_decode_f32(value); + return fp8_encode_f32(fv * (1.0 / (1.0 + std.math.exp(-fv)))); +} + +// fp8_quantize_to_int4(value: FP8E5M2) -> i8 +// Quantize FP8 to Int4 (range [-8, 7]) +pub fn fp8_quantize_to_int4(value: FP8E5M2) i8 { + const fv = fp8_decode_f32(value); + // E5M2 has wider range, so scale differently + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// fp8_quantize_to_int8(value: FP8E5M2) -> i8 +// Quantize FP8 to Int8 (range [-128, 127]) +pub fn fp8_quantize_to_int8(value: FP8E5M2) i8 { + const fv = fp8_decode_f32(value); + // E5M2 has wider range, so scale differently + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 127.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 127) { + result = 127; + } else if (result < -128) { + result = -128; + } + return @as(i8, @intCast(result)); +} + +// fp8_from_f32_scaled(value: f32, scale: f32) -> FP8E5M2 +// Encode f32 to FP8 with custom scale factor +// Useful for quantization with specific dynamic range +pub fn fp8_from_f32_scaled(value: f32, scale: f32) FP8E5M2 { + return fp8_encode_f32(value * scale); +} + +// fp8_to_f32_scaled(value: FP8E5M2, scale: f32) -> f32 +// Decode FP8 to f32 with custom scale factor +pub fn fp8_to_f32_scaled(value: FP8E5M2, scale: f32) f32 { + return fp8_decode_f32(value) / scale; +} + +// fp8_dot_product(a: []const FP8E5M2, b: []const FP8E5M2) -> FP8E5M2 +// Dot product of two FP8 arrays (fixed size 8 for example) +// Returns accumulated FP8 value +pub fn fp8_dot_product(a: []const FP8E5M2, b: []const FP8E5M2) FP8E5M2 { + var sum: f32 = 0.0; + for (a, 0..) |val_a, i| { + if (i >= b.len) break; + const fa = fp8_decode_f32(val_a); + const fb = fp8_decode_f32(b[i]); + sum += fa * fb; + } + return fp8_encode_f32(sum); +} + +// fp8_vector_add(a: []const FP8E5M2, b: []const FP8E5M2, result: []FP8E5M2) -> void +// Vector addition: result[i] = a[i] + b[i] +pub fn fp8_vector_add(a: []const FP8E5M2, b: []const FP8E5M2, result: []FP8E5M2) void { + const len = @min(a.len, b.len); + for (0..len) |i| { + result[i] = fp8_add(a[i], b[i]); + } +} + +// fp8_vector_scale(vec: []const FP8E5M2, scale: FP8E5M2, result: []FP8E5M2) -> void +// Vector scale: result[i] = vec[i] * scale +pub fn fp8_vector_scale(vec: []const FP8E5M2, scale: FP8E5M2, result: []FP8E5M2) void { + const scale_f = fp8_decode_f32(scale); + for (vec, 0..) |val, i| { + result[i] = fp8_encode_f32(fp8_decode_f32(val) * scale_f); + } +} + +// fp8_vector_sum(vec: []const FP8E5M2) -> FP8E5M2 +// Sum all elements of a vector +pub fn fp8_vector_sum(vec: []const FP8E5M2) FP8E5M2 { + var sum: f32 = 0.0; + for (vec) |val| { + sum += fp8_decode_f32(val); + } + return fp8_encode_f32(sum); +} + +// fp8_vector_mean(vec: []const FP8E5M2) -> FP8E5M2 +// Mean of all elements of a vector +pub fn fp8_vector_mean(vec: []const FP8E5M2) FP8E5M2 { + if (vec.len == 0) { + return FP8_ZERO_POS; + } + const sum = fp8_vector_sum(vec); + return fp8_div(sum, fp8_from_f32(@as(f32, @floatFromInt(vec.len)))); +} + +// fp8_softmax(vec: []const FP8E5M2, result: []FP8E5M2) -> void +// Softmax activation: result[i] = exp(vec[i]) / sum(exp(vec)) +pub fn fp8_softmax(vec: []const FP8E5M2, result: []FP8E5M2) void { + // Find max for numerical stability + var max_val: f32 = -std.math.inf(f32); + for (vec) |val| { + const fv = fp8_decode_f32(val); + if (fv > max_val) { + max_val = fv; + } + } + + // Compute exp values and sum + var sum_exp: f32 = 0.0; + for (vec, 0..) |val, i| { + const fv = fp8_decode_f32(val); + const exp_val = std.math.exp(fv - max_val); + result[i] = fp8_encode_f32(exp_val); + sum_exp += exp_val; + } + + // Normalize + const inv_sum = 1.0 / sum_exp; + for (result, 0..) |val, i| { + const fv = fp8_decode_f32(val); + result[i] = fp8_encode_f32(fv * inv_sum); + } +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "fp8_extract_sign_positive" { + given value = 0x3C + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "fp8_extract_sign_negative" { + given value = 0xBC + try std.testing.expect(sign = fp8_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "fp8_extract_exponent_max" { + given value = 0x7C + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == EXP_MAX); +} + +test "fp8_extract_exponent_zero" { + given value = 0x03 + try std.testing.expect(exp = fp8_extract_exponent(value)); + try std.testing.expect(exp == 0); +} + +test "fp8_extract_mantissa_max" { + given value = 0x03 + try std.testing.expect(mant = fp8_extract_mantissa(value)); + try std.testing.expect(mant == MANT_MASK); +} + +test "fp8_is_zero_positive" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_POS) == true); +} + +test "fp8_is_zero_negative" { + try std.testing.expect(fp8_is_zero(FP8_ZERO_NEG) == true); +} + +test "fp8_is_zero_nonzero" { + try std.testing.expect(fp8_is_zero(0x01) == false); +} + +test "fp8_is_inf_positive" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true); +} + +test "fp8_is_inf_negative" { + try std.testing.expect(fp8_is_inf(FP8_INF_NEG) == true); +} + +test "fp8_is_nan_positive" { + try std.testing.expect(fp8_is_nan(FP8_NAN_POS) == true); +} + +test "fp8_is_nan_negative" { + try std.testing.expect(fp8_is_nan(FP8_NAN_NEG) == true); +} + +test "fp8_is_inf_not_nan" { + try std.testing.expect(fp8_is_inf(FP8_INF_POS) == true and fp8_is_nan(FP8_INF_POS) == false); +} + +test "fp8_is_special_inf" { + try std.testing.expect(fp8_is_special(FP8_INF_POS) == true); +} + +test "fp8_is_special_nan" { + try std.testing.expect(fp8_is_special(FP8_NAN_POS) == true); +} + +test "fp8_is_subnormal_true" { + given value = 0x01 + try std.testing.expect(fp8_is_subnormal(value) == true); +} + +test "fp8_is_subnormal_false_normal" { + given value = 0x04 + try std.testing.expect(fp8_is_subnormal(value) == false); +} + +test "fp8_is_subnormal_false_zero" { + try std.testing.expect(fp8_is_subnormal(FP8_ZERO_POS) == false); +} + +test "fp8_encode_f32_zero" { + given fp = fp8_encode_f32(0.0) + try std.testing.expect(fp == FP8_ZERO_POS); +} + +test "fp8_encode_f32_negative_zero" { + given fp = fp8_encode_f32(-0.0) + try std.testing.expect(fp == FP8_ZERO_NEG); +} + +test "fp8_encode_f32_one" { + given fp = fp8_encode_f32(1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 1.0) < 0.15); +} + +test "fp8_encode_f32_negative_one" { + given fp = fp8_encode_f32(-1.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded + 1.0) < 0.15); +} + +test "fp8_encode_f32_ten" { + given fp = fp8_encode_f32(10.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 10.0) < 1.0); +} + +test "fp8_encode_f32_hundred" { + given fp = fp8_encode_f32(100.0) + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - 100.0) < 10.0); +} + +test "fp8_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "fp8_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(fp = fp8_encode_f32(original)); + try std.testing.expect(decoded = fp8_decode_f32(fp)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "fp8_encode_f32_nan" { + given fp = fp8_encode_f32(std.math.nan(f32)) + try std.testing.expect(fp8_is_nan(fp) == true); +} + +test "fp8_encode_f32_inf" { + given fp = fp8_encode_f32(std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_encode_f32_neg_inf" { + given fp = fp8_encode_f32(-std.math.inf(f32)) + try std.testing.expect(fp8_is_inf(fp) == true); +} + +test "fp8_decode_f32_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_POS) == 0.0); +} + +test "fp8_decode_f32_negative_zero" { + try std.testing.expect(fp8_decode_f32(FP8_ZERO_NEG) == -0.0); +} + +test "fp8_decode_f32_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_POS)) == true); +} + +test "fp8_decode_f32_neg_inf" { + try std.testing.expect(std.math.isInf(fp8_decode_f32(FP8_INF_NEG)) == true); +} + +test "fp8_decode_f32_nan" { + try std.testing.expect(std.math.isNan(fp8_decode_f32(FP8_NAN_POS)) == true); +} + +test "fp8_add_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(20.0)); + try std.testing.expect(result = fp8_add(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 30.0) < 3.0); +} + +test "fp8_sub_simple" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(30.0)); + try std.testing.expect(result = fp8_sub(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 3.0); +} + +test "fp8_mul_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(result = fp8_mul(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 15.0); +} + +test "fp8_div_simple" { + given a = fp8_encode_f32(100.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "fp8_div_by_zero_positive" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_POS); +} + +test "fp8_div_by_zero_negative" { + given a = fp8_encode_f32(-50.0) + try std.testing.expect(b = fp8_encode_f32(0.0)); + try std.testing.expect(result = fp8_div(a, b)); + try std.testing.expect(result == FP8_INF_NEG); +} + +test "fp8_sqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.3); +} + +test "fp8_sqrt_negative" { + given value = fp8_encode_f32(-4.0) + try std.testing.expect(result = fp8_sqrt(value)); + try std.testing.expect(fp8_is_nan(result) == true); +} + +test "fp8_rsqrt_four" { + given value = fp8_encode_f32(4.0) + try std.testing.expect(result = fp8_rsqrt(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_abs_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(result = fp8_abs(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_abs_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(abs_val = fp8_abs(value)); + try std.testing.expect(fp8_extract_sign(abs_val) == 0); +} + +test "fp8_neg_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) < 0); +} + +test "fp8_neg_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(neg_val = fp8_neg(value)); + try std.testing.expect(fp8_extract_sign(neg_val) == 0); +} + +test "fp8_is_equal_true" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(fp8_is_equal(value, value) == true); +} + +test "fp8_is_equal_false" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(20.0)); + try std.testing.expect(fp8_is_equal(a, b) == false); +} + +test "fp8_is_equal_nan" { + try std.testing.expect(fp8_is_equal(FP8_NAN_POS, FP8_NAN_POS) == false); +} + +test "fp8_is_equal_zero" { + try std.testing.expect(fp8_is_equal(FP8_ZERO_POS, FP8_ZERO_NEG) == true); +} + +test "fp8_is_greater_positive" { + given a = fp8_encode_f32(50.0) + try std.testing.expect(b = fp8_encode_f32(30.0)); + try std.testing.expect(fp8_is_greater(a, b) == true); +} + +test "fp8_max_returns_larger" { + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 40.0); +} + +test "fp8_min_returns_smaller" { + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 40.0); +} + +test "fp8_lerp_zero" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, a) == true); +} + +test "fp8_lerp_one" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = fp8_lerp(a, b, t)); + try std.testing.expect(fp8_is_equal(result, b) == true); +} + +test "fp8_fma_simple" { + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(10.0)); + try std.testing.expect(c = fp8_encode_f32(5.0)); + try std.testing.expect(result = fp8_fma(a, b, c)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 105.0) < 15.0); +} + +test "fp8_scale_up" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(scale = 10.0); + try std.testing.expect(result = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 1.0); +} + +test "fp8_scale_down" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(scale = 0.1); + try std.testing.expect(result = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "fp8_relu_positive" { + given value = fp8_encode_f32(50.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(fp8_is_equal(result, value) == true); +} + +test "fp8_relu_negative" { + given value = fp8_encode_f32(-50.0) + try std.testing.expect(result = fp8_relu(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_relu_zero" { + given result = fp8_relu(FP8_ZERO_POS) + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_sigmoid_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "fp8_sigmoid_positive_large" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "fp8_sigmoid_negative_large" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_sigmoid(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= 0.1); +} + +test "fp8_tanh_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded) < 0.1); +} + +test "fp8_tanh_positive_large" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "fp8_tanh_negative_large" { + given value = fp8_encode_f32(-10.0) + try std.testing.expect(result = fp8_tanh(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded <= -0.9); +} + +test "fp8_gelu_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_gelu(value)); + try std.testing.expect(fp8_extract_sign(result) == 0); +} + +test "fp8_swish_zero" { + given value = FP8_ZERO_POS + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(result == FP8_ZERO_POS); +} + +test "fp8_swish_positive" { + given value = fp8_encode_f32(10.0) + try std.testing.expect(result = fp8_swish(value)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(decoded > 0.0); +} + +test "fp8_quantize_to_int4_max" { + given value = fp8_encode_f32(100.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "fp8_quantize_to_int4_min" { + given value = fp8_encode_f32(-100.0) + try std.testing.expect(result = fp8_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "fp8_quantize_to_int8_max" { + given value = fp8_encode_f32(200.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result >= 100); +} + +test "fp8_quantize_to_int8_min" { + given value = fp8_encode_f32(-200.0) + try std.testing.expect(result = fp8_quantize_to_int8(value)); + try std.testing.expect(result <= -100); +} + +test "fp8_from_f32_scaled_up" { + given value = 1.0 + try std.testing.expect(scale = 100.0); + try std.testing.expect(result = fp8_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 10.0); +} + +test "fp8_from_f32_scaled_down" { + given value = 100.0 + try std.testing.expect(scale = 0.01); + try std.testing.expect(result = fp8_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "fp8_to_f32_scaled_up" { + given value = fp8_encode_f32(1.0) + try std.testing.expect(scale = 0.01); + try std.testing.expect(result = fp8_to_f32_scaled(value, scale)); + try std.testing.expect(abs(result - 0.01) < 0.005); +} + +test "fp8_dot_product_simple" { + given a = [_]FP8E5M2{fp8_encode_f32(1.0), fp8_encode_f32(2.0), fp8_encode_f32(3.0), fp8_encode_f32(4.0)} + try std.testing.expect(b = [_]FP8E5M2{fp8_encode_f32(2.0), fp8_encode_f32(2.0), fp8_encode_f32(2.0), fp8_encode_f32(2.0)}); + try std.testing.expect(result = fp8_dot_product(a, b)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "fp8_vector_sum_simple" { + given vec = [_]FP8E5M2{fp8_encode_f32(1.0), fp8_encode_f32(2.0), fp8_encode_f32(3.0), fp8_encode_f32(4.0)} + try std.testing.expect(result = fp8_vector_sum(vec)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 2.0); +} + +test "fp8_vector_mean_simple" { + given vec = [_]FP8E5M2{fp8_encode_f32(2.0), fp8_encode_f32(4.0), fp8_encode_f32(6.0), fp8_encode_f32(8.0)} + try std.testing.expect(result = fp8_vector_mean(vec)); + try std.testing.expect(decoded = fp8_decode_f32(result)); + try std.testing.expect(abs(decoded - 5.0) < 1.0); +} + +test "fp8_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 15); + try std.testing.expect(mant = 2); + try std.testing.expect(fp = fp8_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = fp8_extract_sign(fp)); + try std.testing.expect(extracted_exp = fp8_extract_exponent(fp)); + try std.testing.expect(extracted_mant = fp8_extract_mantissa(fp)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant fp8_bits_constant + assert BITS == 8 + +invariant fp8_sign_bits_one + assert SIGN_BITS == 1 + +invariant fp8_exp_bits_five + assert EXP_BITS == 5 + +invariant fp8_mant_bits_two + assert MANT_BITS == 2 + +invariant fp8_exp_bias_fifteen + assert EXP_BIAS == 15 + +invariant fp8_exp_max_all_ones + assert EXP_MAX == 0x1F + +invariant fp8_sign_mask_correct + assert SIGN_MASK == 0x80 + +invariant fp8_exp_mask_correct + assert EXP_MASK == 0x7C + +invariant fp8_mant_mask_correct + assert MANT_MASK == 0x03 + +invariant fp8_masks_sum_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFF + +invariant fp8_zero_pos_no_sign + assert (FP8_ZERO_POS & SIGN_MASK) == 0 + +invariant fp8_zero_neg_has_sign + assert (FP8_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant fp8_inf_pos_max_exp + assert (FP8_INF_POS & EXP_MASK) == EXP_MASK + +invariant fp8_inf_pos_no_mant + assert (FP8_INF_POS & MANT_MASK) == 0 + +invariant fp8_nan_has_mantissa + assert (FP8_NAN_POS & MANT_MASK) != 0 + +invariant fp8_abs_removes_sign + assert fp8_abs(FP8_ZERO_NEG) == FP8_ZERO_POS + +invariant fp8_neg_toggles_sign + assert (fp8_neg(FP8_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant fp8_add_zero_identity + given value = fp8_encode_f32(50.0) + assert fp8_is_equal(fp8_add(value, FP8_ZERO_POS), value) == true + +invariant fp8_sub_zero_identity + given value = fp8_encode_f32(50.0) + assert fp8_is_equal(fp8_sub(value, FP8_ZERO_POS), value) == true + +invariant fp8_mul_one_identity + given value = fp8_encode_f32(50.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_mul(value, one), value) == true or fp8_magnitude(value) > 10.0 + +invariant fp8_div_one_identity + given value = fp8_encode_f32(50.0) + try std.testing.expect(one = fp8_encode_f32(1.0)); + assert fp8_is_equal(fp8_div(value, one), value) == true + +invariant fp8_max_ge_both + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_max(a, b)); + try std.testing.expect(fp8_is_greater(result, a) == true and fp8_is_greater(result, b) == true or fp8_is_equal(result, b) == true); + +invariant fp8_min_le_both + given a = fp8_encode_f32(30.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + try std.testing.expect(result = fp8_min(a, b)); + try std.testing.expect(fp8_is_greater(a, result) == true or fp8_is_equal(a, result) == true); + +invariant fp8_relu_non_negative + given result = fp8_relu(fp8_encode_f32(50.0)) + try std.testing.expect(fp8_extract_sign(result) == 0); + +invariant fp8_lerp_zero_start + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + assert fp8_is_equal(fp8_lerp(a, b, 0.0), a) == true + +invariant fp8_lerp_one_end + given a = fp8_encode_f32(10.0) + try std.testing.expect(b = fp8_encode_f32(50.0)); + assert fp8_is_equal(fp8_lerp(a, b, 1.0), b) == true + +invariant fp8_sqrt_square_close + given value = fp8_encode_f32(4.0) + try std.testing.expect(sqrt_val = fp8_sqrt(value)); + try std.testing.expect(sq_val = fp8_mul(sqrt_val, sqrt_val)); + try std.testing.expect(decoded = fp8_decode_f32(sq_val)); + try std.testing.expect(abs(decoded - 4.0) < 0.5); + +invariant fp8_quantize_to_int4_in_range + given result = fp8_quantize_to_int4(fp8_encode_f32(50.0)) + assert result >= -8 and result <= 7 + +invariant fp8_quantize_to_int8_in_range + given result = fp8_quantize_to_int8(fp8_encode_f32(50.0)) + assert result >= -128 and result <= 127 + +invariant fp8_scale_roundtrip + given value = fp8_encode_f32(50.0) + try std.testing.expect(scale = 2.0); + try std.testing.expect(scaled = fp8_scale(value, scale)); + try std.testing.expect(decoded = fp8_to_f32_scaled(scaled, scale)); + try std.testing.expect(abs(decoded - fp8_decode_f32(value)) < 5.0); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench fp8_encode_f32_latency + measure: nanoseconds to fp8_encode_f32(10.5) + target: < 50ns + +bench fp8_decode_f32_latency + measure: nanoseconds to fp8_decode_f32(0x3C) + target: < 50ns + +bench fp8_add_latency + measure: nanoseconds to fp8_add(0x3C, 0x48) + target: < 100ns + +bench fp8_mul_latency + measure: nanoseconds to fp8_mul(0x3C, 0x3C) + target: < 100ns + +bench fp8_sqrt_latency + measure: nanoseconds to fp8_sqrt(0x30) + target: < 100ns + +bench fp8_sigmoid_latency + measure: nanoseconds to fp8_sigmoid(0x30) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/gf12.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/gf12.t27 new file mode 100644 index 0000000000..9629152355 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/gf12.t27 @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf12.t27 +// GoldenFloat12 0 12-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 4 (P1) + +module GF12 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF12 bit layout: [S|EEEE|MMM MMMM] + // S: 1 bit (sign) + // E: 4 bits (exponent) + // M: 7 bits (mantissa) + + const BITS : u8 = 12; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 4; + const MANT_BITS : u8 = 7; + + // Bias for exponent (2^(4-1) - 1 = 7) + const EXP_BIAS : u8 = 7; + + // 141-ratio: exp/mant = 4/7 142 0.571 (phi_distance = 0.047) + // This is the closest to 1/143 among all formats + const PHI_DISTANCE : f64 = 0.04660512288042107; + + // 144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 + // 2. GoldenFloat12 Type + // 209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 + + struct GF12 { + raw : u16, // 12-bit value stored in u16 + } + + // 282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 + // 3. Encoding/Decoding + // 347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 + + // Encode f32 to GF12 + fn encode(value: f32) -> GF12 { + if (value == 0.0) { + return GF12{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i8; + const exp_biased = (exp_unbiased + EXP_BIAS as i8) as u8; + + // Clamp exponent + const exp_clamped = clamp(exp_biased, 0, (1 << EXP_BITS) - 1); + + // Extract mantissa (7 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF12{ + raw = ((sign as u16) << 11) | ((exp_clamped as u16) << MANT_BITS) | (mant as u16) + }; + } + + // Decode GF12 to f32 + fn decode(gf: GF12) -> f32 { + const sign = (gf.raw >> 11) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x0F) as u8; + const mant = (gf.raw & 0x7F) as u8; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS as i8 + 1 + } else { + (exp_biased as i8) - EXP_BIAS as i8 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 128.0 + } else { + 1.0 + (mant as f32) / 128.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 + // 4. Format Properties + // 485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 + + fn max_value() -> f32 { + const mant_max = 1.0 + 127.0 / 128.0; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 128.0; + const exp_min = -EXP_BIAS as i8 + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 128.0; // 0.0078125 + } + + // 558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 + // 5. Validation + // 623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF12"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760 + // 6. Use Cases + // 761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833 + + // GF12 is optimal for: + // - Best 834-approximation (lowest phi_distance) + // - High-precision quantization + // - Critical path weights + // - Attention matrices + + // Memory: 12 bits = 1.5 bytes (~2.67x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 12.0 / 32.0; // 0.375 + + // 835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899 + // 7. Helper Functions + // 900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972 + + fn floor_log2(x: f32) -> i8 { + if (x <= 0.0) { return -128; } + let exp : i8 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i8, mant_bits: u8) -> u8 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u8; + } + + fn clamp(x: u8, min: u8, max: u8) -> u8 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF12 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 9739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075 + // TDD-Inside-Spec: Tests and Invariants for GF12 + // 1076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178 + + test gf12_decode_zero + given gf = GF12{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf12_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf12_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf12_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf12_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf12_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf12_phi_distance_lowest + given phi_dist = PHI_DISTANCE + then phi_dist < 0.05 + + test gf12_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.375) < 0.01 + + test gf12_validate_format_success + given valid = validate_format() + then valid == true + + test gf12_floor_log2_power_of_two + given log_result = floor_log2(8.0) + then log_result == 3 + + test gf12_extract_mantissa_in_range + given mant = extract_mantissa(1.5, 0, 7) + then mant < 128 + + invariant gf12_bits_constant + assert BITS == 12 + + invariant gf12_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf12_exp_bits_is_four + assert EXP_BITS == 4 + + invariant gf12_mant_bits_is_seven + assert MANT_BITS == 7 + + invariant gf12_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf12_phi_distance_below_threshold + assert PHI_DISTANCE < 0.05 + + invariant gf12_exp_bias_positive + assert EXP_BIAS > 0 + + test gf12_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf12_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf12_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf12_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf12_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf12_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf12_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf12_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf12_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf12_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf12_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf12_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf12_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf12_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf12_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf12_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf12_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf12_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf12_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf12_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf12_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf12_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf12_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf12_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf12_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + invariant gf12_floor_log2_non_negative_input + assert floor_log2(1.0) >= 0 + + invariant gf12_extract_mantissa_in_valid_range + assert extract_mantissa(1.0, 0, 7) < 128 + + bench gf12_encode_latency + measure: nanoseconds to encode(1.0) + target: < 150ns + + bench gf12_decode_latency + measure: nanoseconds to decode(GF12{raw = 1024}) + target: < 100ns +} diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/gf128.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/gf128.t27 new file mode 100644 index 0000000000..f32ee7a8a1 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/gf128.t27 @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf128.t27 — GoldenFloat128 Encode/Decode +; GF128: 128-bit floating point with 1 sign + 28 exponent + 99 mantissa +; Bit layout: [S(1) E(28) M(99)] = [127:127][126:99][98:0] +; Extended range format for high-precision scientific computing +; φ² + 1/φ² = 3 | TRINITY + +module triformat-gf128; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const SIGN_SHIFT : u8 = 127; +pub const EXP_SHIFT : u8 = 99; +pub const MANT_SHIFT : u8 = 0; + +// GF128 uses 128-bit representation +// Since Zig doesn't have u128 constants directly in all contexts, +// we use u64 pairs for mask constants + +pub const SIGN_MASK_HI : u64 = 0x8000000000000000; // 1 << 63 of high part +pub const SIGN_MASK_LO : u64 = 0x0000000000000000; + +pub const EXP_BITS : u8 = 28; +pub const MANT_BITS : u8 = 99; +pub const SIGN_BITS : u8 = 1; + +pub const EXP_MAX : u32 = 0x0FFFFFFF; // 268435455 (all ones in 28 bits) +pub const EXP_MIN : u32 = 0x00000000; + +pub const BIAS : i32 = 134217727; // Exponent bias for GF128: 2^(28-1) - 1 +pub const SPECIAL_EXP : u32 = 0x0FFFFFFF; // All ones = special (Inf/NaN) + +// MANT_DIVISOR = 2^99 is too large for u64, handled in functions + +pub const PHI_BIAS : u32 = 3932160; // Phi-optimized rounding bias + +// ============================================================================ +// Types +// ============================================================================ + +// GF128 represented as u128 in Zig 0.15+ +pub const GF128 = u128; + +// Helper struct for operations that need high/low parts +pub struct GF128Parts { + hi: u64, + lo: u64, +} + +// ============================================================================ +// Conversion Helpers +// ============================================================================ + +// gf128_to_parts(gf128: GF128) -> GF128Parts +// Split GF128 into high and low 64-bit parts +pub fn gf128_to_parts(gf128: GF128) GF128Parts { + return GF128Parts{ + .hi = @as(u64, @truncate(gf128 >> 64)), + .lo = @as(u64, @truncate(gf128)), + }; +} + +// gf128_from_parts(parts: GF128Parts) -> GF128 +// Combine high and low 64-bit parts into GF128 +pub fn gf128_from_parts(parts: GF128Parts) GF128 { + return (@as(GF128, parts.hi) << 64) | @as(GF128, parts.lo); +} + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// gf128_extract_sign(gf128: GF128) -> i8 +// Extract sign bit (bit 127) +// Returns: 0 for positive, -1 for negative +pub fn gf128_extract_sign(gf128: GF128) i8 { + const parts = gf128_to_parts(gf128); + const bit = (parts.hi >> 63) & 1; + return if (bit != 0) -1 else 0; +} + +// gf128_extract_exponent(gf128: GF128) -> u32 +// Extract exponent bits (bits 126-99) +// Returns: 0-268435455 +pub fn gf128_extract_exponent(gf128: GF128) u32 { + const parts = gf128_to_parts(gf128); + // Exponent spans bits 62-35 of high part (28 bits total) + // exp = (hi >> 35) & 0x0FFFFFFF + return @as(u32, @truncate((parts.hi >> 35) & 0x0FFFFFFF)); +} + +// gf128_extract_mantissa(gf128: GF128) -> u128 +// Extract mantissa bits (bits 98-0) +// Returns: 0-2^99-1 +pub fn gf128_extract_mantissa(gf128: GF128) u128 { + const parts = gf128_to_parts(gf128); + // Mantissa spans bits 34-0 of high part (35 bits) + all 64 bits of low part + // mant = ((hi & 0x00000007FFFFFFFF) << 64) | lo + const hi_mant = parts.hi & 0x00000007FFFFFFFF; + return (@as(u128, hi_mant) << 64) | @as(u128, parts.lo); +} + +// ============================================================================ +// Assembly Functions +// ============================================================================ + +// gf128_from_components(sign: i8, exp: u32, mant: u128) -> GF128 +// Assemble GF128 from sign, exponent, mantissa +pub fn gf128_from_components(sign: i8, exp: u32, mant: u128) GF128 { + const sign_bit = if (sign < 0) 1 else 0; + const clamped_exp = exp & EXP_MAX; + const clamped_mant = mant & (@as(u128, EXP_MAX) >> 1); // Mask for 99 bits (2^99 - 1) + + // exp goes at bits 126-99 (hi bits 62-35) + const hi_exp = @as(u64, @truncate(clamped_exp)) << 35; + // mant spans bits 98-0 (hi bits 34-0 + lo 64 bits) + const hi_mant = @as(u64, @truncate(clamped_mant >> 64)); + const lo_mant = @as(u64, @truncate(clamped_mant)); + + const hi = (@as(u64, @intCast(sign_bit)) << 63) | hi_exp | hi_mant; + const lo = lo_mant; + + return gf128_from_parts(GF128Parts{ .hi = hi, .lo = lo }); +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +const GF128_ZERO_POS : GF128 = 0; +const GF128_ZERO_NEG : GF128 = (@as(GF128, 1) << 127); +const GF128_INF_POS : GF128 = (@as(GF128, EXP_MAX) << 99); +const GF128_INF_NEG : GF128 = GF128_INF_POS | (@as(GF128, 1) << 127); +const GF128_NAN : GF128 = GF128_INF_POS | 1; + +// gf128_is_zero(gf128: GF128) -> bool +// Check if GF128 is zero (positive or negative) +pub fn gf128_is_zero(gf128: GF128) bool { + return gf128 == GF128_ZERO_POS or gf128 == GF128_ZERO_NEG; +} + +// gf128_is_special(gf128: GF128) -> bool +// Check if GF128 is Inf or NaN (exp == 268435455) +pub fn gf128_is_special(gf128: GF128) bool { + return gf128_extract_exponent(gf128) == EXP_MAX; +} + +// gf128_is_inf(gf128: GF128) -> bool +// Check if GF128 is infinity (exp == max, mant == 0) +pub fn gf128_is_inf(gf128: GF128) bool { + return gf128_is_special(gf128) and gf128_extract_mantissa(gf128) == 0; +} + +// gf128_is_nan(gf128: GF128) -> bool +// Check if GF128 is NaN (exp == max, mant != 0) +pub fn gf128_is_nan(gf128: GF128) bool { + return gf128_is_special(gf128) and gf128_extract_mantissa(gf128) != 0; +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// gf128_encode_f64(f64: f64) -> GF128 +// Encode IEEE 754 double precision to GF128 +pub fn gf128_encode_f64(value: f64) GF128 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) GF128_ZERO_NEG else GF128_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return GF128_NAN; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) GF128_INF_NEG else GF128_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f64 components + const f64_bits: u64 = @bitCast(abs_value); + var f64_exp: i32 = @as(i32, @intCast((f64_bits >> 52) & 0x7FF)) - 1023; + var f64_mant: u64 = f64_bits & 0x000FFFFFFFFFFFFF; + + // Convert exp from f64 bias (1023) to GF128 bias (134217727) + var gf128_exp = @as(u32, @intCast(f64_exp + BIAS)); + + // Clamp exponent + if (gf128_exp >= EXP_MAX) { + return if (sign < 0) GF128_INF_NEG else GF128_INF_POS; + } + + // Extend mantissa to 99 bits (f64 has 52 bits, pad with zeros) + const mant = @as(u128, f64_mant) << 47; // 52 + 47 = 99 + + return gf128_from_components(sign, gf128_exp, mant); +} + +// gf128_decode_f64(gf128: GF128) -> f64 +// Decode GF128 to IEEE 754 double precision +// Note: This may lose precision as f64 has only 52 mantissa bits +pub fn gf128_decode_f64(gf128: GF128) f64 { + // Handle zero + if (gf128_is_zero(gf128)) { + return if (gf128_extract_sign(gf128) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (gf128_is_nan(gf128)) { + return std.math.nan(f64); + } + + // Handle Infinity + if (gf128_is_inf(gf128)) { + return if (gf128_extract_sign(gf128) < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + // Extract components + const sign = gf128_extract_sign(gf128); + const exp = gf128_extract_exponent(gf128); + const mant = gf128_extract_mantissa(gf128); + + // Convert to f64 (may lose precision) + // Scale mantissa from 99 bits to 52 bits + const mant_f64 = @as(f64, @floatFromInt(mant >> 47)) / 4503599627370496.0; // 2^52 + const bias_adjusted = @as(i64, @intCast(exp)) - @as(i64, BIAS); + + if (bias_adjusted < -1022 or bias_adjusted > 1023) { + return if (sign < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + const value = mant_f64 * std.math.pow(f64, 2.0, @as(f64, @floatFromInt(bias_adjusted))); + return if (sign < 0) -value else value; +} + +// gf128_encode_f128(f128: f128) -> GF128 +// Encode IEEE 754 quad precision to GF128 (if available) +pub fn gf128_encode_f128(value: f128) GF128 { + // For systems without f128 support, fall back to f64 + // This is a placeholder - actual implementation would use f128 bit manipulation + const value_f64 = @as(f64, @floatCast(value)); + return gf128_encode_f64(value_f64); +} + +// gf128_decode_f128(gf128: GF128) -> f128 +// Decode GF128 to IEEE 754 quad precision (if available) +pub fn gf128_decode_f128(gf128: GF128) f128 { + const value_f64 = gf128_decode_f64(gf128); + return @as(f128, @floatCast(value_f64)); +} + +// ============================================================================ +// Arithmetic Operations (simplified via decode-encode) +// ============================================================================ + +// gf128_add(a: GF128, b: GF128) -> GF128 +pub fn gf128_add(a: GF128, b: GF128) GF128 { + return gf128_encode_f64(gf128_decode_f64(a) + gf128_decode_f64(b)); +} + +// gf128_sub(a: GF128, b: GF128) -> GF128 +pub fn gf128_sub(a: GF128, b: GF128) GF128 { + return gf128_encode_f64(gf128_decode_f64(a) - gf128_decode_f64(b)); +} + +// gf128_mul(a: GF128, b: GF128) -> GF128 +pub fn gf128_mul(a: GF128, b: GF128) GF128 { + return gf128_encode_f64(gf128_decode_f64(a) * gf128_decode_f64(b)); +} + +// gf128_div(a: GF128, b: GF128) -> GF128 +pub fn gf128_div(a: GF128, b: GF128) GF128 { + const fb = gf128_decode_f64(b); + if (fb == 0.0) { + const fa = gf128_decode_f64(a); + return if (fa < 0.0) GF128_INF_NEG else GF128_INF_POS; + } + return gf128_encode_f64(gf128_decode_f64(a) / fb); +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// gf128_abs(gf128: GF128) -> GF128 +pub fn gf128_abs(gf128: GF128) GF128 { + return gf128 & ~(@as(GF128, 1) << 127); +} + +// gf128_neg(gf128: GF128) -> GF128 +pub fn gf128_neg(gf128: GF128) GF128 { + return gf128 ^ (@as(GF128, 1) << 127); +} + +// gf128_is_equal(a: GF128, b: GF128) -> bool +pub fn gf128_is_equal(a: GF128, b: GF128) bool { + if (gf128_is_nan(a) or gf128_is_nan(b)) { + return false; + } + if (gf128_is_zero(a) and gf128_is_zero(b)) { + return true; + } + return a == b; +} + +// gf128_is_greater(a: GF128, b: GF128) -> bool +pub fn gf128_is_greater(a: GF128, b: GF128) bool { + if (gf128_is_nan(a) or gf128_is_nan(b)) { + return false; + } + const sign_a = gf128_extract_sign(a); + const sign_b = gf128_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + if (sign_a < 0) { + return gf128_neg(a) > gf128_neg(b); + } + return a > b; +} + +// gf128_max(a: GF128, b: GF128) -> GF128 +pub fn gf128_max(a: GF128, b: GF128) GF128 { + return if (gf128_is_greater(a, b)) a else b; +} + +// gf128_min(a: GF128, b: GF128) -> GF128 +pub fn gf128_min(a: GF128, b: GF128) GF128 { + return if (gf128_is_greater(a, b)) b else a; +} + +// gf128_clamp(value: GF128, min: GF128, max: GF128) -> GF128 +pub fn gf128_clamp(value: GF128, min: GF128, max: GF128) GF128 { + return gf128_min(gf128_max(value, min), max); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf128_extract_sign_positive" { + given value = 0x00000000000000000000000000001234 + try std.testing.expect(sign = gf128_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "gf128_extract_sign_negative" { + given value = 0x80000000000000000000000000001234 + try std.testing.expect(sign = gf128_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "gf128_extract_exponent_middle" { + given value = 0x00008000000000000000000000000000 // exp = 134217728 + try std.testing.expect(exp = gf128_extract_exponent(value)); + try std.testing.expect(exp == 134217728); +} + +test "gf128_is_zero_positive" { + try std.testing.expect(gf128_is_zero(GF128_ZERO_POS) == true); +} + +test "gf128_is_zero_negative" { + try std.testing.expect(gf128_is_zero(GF128_ZERO_NEG) == true); +} + +test "gf128_is_inf_positive" { + try std.testing.expect(gf128_is_inf(GF128_INF_POS) == true); +} + +test "gf128_is_nan" { + try std.testing.expect(gf128_is_nan(GF128_NAN) == true); +} + +test "gf128_encode_f64_zero" { + given gf = gf128_encode_f64(0.0) + try std.testing.expect(gf == GF128_ZERO_POS); +} + +test "gf128_encode_f64_one" { + given gf = gf128_encode_f64(1.0) + try std.testing.expect(decoded = gf128_decode_f64(gf)); + try std.testing.expect(abs(decoded - 1.0) < 0.0001); +} + +test "gf128_encode_f64_negative_one" { + given gf = gf128_encode_f64(-1.0) + try std.testing.expect(decoded = gf128_decode_f64(gf)); + try std.testing.expect(abs(decoded + 1.0) < 0.0001); +} + +test "gf128_encode_f64_roundtrip" { + given original = 42.5 + try std.testing.expect(gf = gf128_encode_f64(original)); + try std.testing.expect(decoded = gf128_decode_f64(gf)); + try std.testing.expect(abs(decoded - original) < 0.001); +} + +test "gf128_add_simple" { + given a = gf128_encode_f64(1.0) + try std.testing.expect(b = gf128_encode_f64(2.0)); + try std.testing.expect(result = gf128_add(a, b)); + try std.testing.expect(decoded = gf128_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.01); +} + +test "gf128_mul_simple" { + given a = gf128_encode_f64(3.0) + try std.testing.expect(b = gf128_encode_f64(4.0)); + try std.testing.expect(result = gf128_mul(a, b)); + try std.testing.expect(decoded = gf128_decode_f64(result)); + try std.testing.expect(abs(decoded - 12.0) < 0.01); +} + +test "gf128_abs_positive" { + given value = gf128_encode_f64(5.0) + try std.testing.expect(abs_val = gf128_abs(value)); + try std.testing.expect(gf128_is_equal(abs_val, value) == true); +} + +test "gf128_abs_negative" { + given value = gf128_encode_f64(-5.0) + try std.testing.expect(abs_val = gf128_abs(value)); + try std.testing.expect(gf128_extract_sign(abs_val) == 0); +} + +test "gf128_neg" { + given value = gf128_encode_f64(5.0) + try std.testing.expect(neg_val = gf128_neg(value)); + try std.testing.expect(decoded = gf128_decode_f64(neg_val)); + try std.testing.expect(abs(decoded + 5.0) < 0.01); +} + +test "gf128_is_equal_nan" { + try std.testing.expect(gf128_is_equal(GF128_NAN, GF128_NAN) == false); +} + +test "gf128_is_greater_positive" { + given a = gf128_encode_f64(5.0) + try std.testing.expect(b = gf128_encode_f64(3.0)); + try std.testing.expect(gf128_is_greater(a, b) == true); +} + +test "gf128_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 134217728); + try std.testing.expect(mant = 0x8000000000000000000000000); + try std.testing.expect(gf = gf128_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = gf128_extract_sign(gf)); + try std.testing.expect(extracted_exp = gf128_extract_exponent(gf)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf128_bits_total + assert SIGN_BITS + EXP_BITS + MANT_BITS == 128 + +invariant gf128_bias_power_of_two_minus_one + assert BIAS + 1 == 134217728 // 2^27 + +invariant gf128_special_exp_all_ones + assert SPECIAL_EXP == EXP_MAX + +invariant gf128_zero_pos_no_sign + assert (GF128_ZERO_POS & (@as(GF128, 1) << 127)) == 0 + +invariant gf128_zero_neg_has_sign + assert (GF128_ZERO_NEG & (@as(GF128, 1) << 127)) != 0 + +invariant gf128_abs_removes_sign + assert gf128_abs(GF128_ZERO_NEG) == GF128_ZERO_POS + +invariant gf128_neg_toggles_sign + assert (gf128_neg(GF128_INF_POS) & (@as(GF128, 1) << 127)) != 0 + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf128_extract_sign_latency + measure: nanoseconds to gf128_extract_sign(0x123456789ABCDEF0123456789ABCDEF0) + target: < 15ns + +bench gf128_encode_f64_latency + measure: nanoseconds to gf128_encode_f64(123.456) + target: < 150ns + +bench gf128_decode_f64_latency + measure: nanoseconds to gf128_decode_f64(0x3FF0000000000000) + target: < 150ns + +bench gf128_add_latency + measure: nanoseconds to gf128_add(gf128_encode_f64(1.0), gf128_encode_f64(2.0)) + target: < 300ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/gf16.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/gf16.t27 new file mode 100644 index 0000000000..7a0eaefdeb --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/gf16.t27 @@ -0,0 +1,3436 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf16.t27 0 GoldenFloat16 Encode/Decode +; GF16: 16-bit floating point with 1 sign + 6 exponent + 9 mantissa +; Bit layout: [S(1) E(6) M(9)] = [15:15][14:9][8:0] +; 12 + 1/34 = 3 | TRINITY + +module triformat-gf16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const SIGN_SHIFT : u8 = 15; +pub const EXP_SHIFT : u8 = 9; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u16 = 0x8000; // 1 << 15 +pub const EXP_MASK : u16 = 0x7E00; // 0b111111 << 9 +pub const MANT_MASK : u16 = 0x01FF; // 0b111111111 + +pub const EXP_MAX : u8 = 0x3F; // 63 (all ones in 6 bits) +pub const EXP_MIN : u8 = 0x00; + +pub const BIAS : i8 = 31; // Exponent bias for GF16 +pub const SPECIAL_EXP : u8 = 0x3F; // All ones = special (Inf/NaN) + +pub const MANT_DIVISOR : u16 = 512; // 2^9 +pub const MANT_DIVISOR_SHIFT : u8 = 9; // log2(512) + +pub const PHI_BIAS : u16 = 60; // Phi-optimized rounding bias + +// GF16 special values +pub const GF16_ZERO_POS : u16 = 0x0000; +pub const GF16_ZERO_NEG : u16 = 0x8000; +pub const GF16_INF_POS : u16 = 0x7E00; +pub const GF16_INF_NEG : u16 = 0xFE00; +pub const GF16_NAN : u16 = 0xFE01; // Sign + all exp + mantissa != 0 + +// ============================================================================ +// Types +// ============================================================================ + +pub const GF16 = u16; + +// ============================================================================ +// Lookup Tables +// ============================================================================ + +// Powers of 2 for exponents 0-31 +pub const pow2_table : [32]u16 = [32]u16{ + 0x3C00, 0x3D00, 0x3D80, 0x3E00, 0x3E40, 0x3E80, 0x3EC0, 0x3F00, + 0x3F40, 0x3F80, 0x3FC0, 0x3FE0, 0x3FF0, 0x4000, 0x4040, 0x4080, + 0x40C0, 0x4100, 0x4140, 0x4180, 0x41C0, 0x4200, 0x4240, 0x4280, + 0x42C0, 0x4300, 0x4340, 0x4380, 0x43C0, 0x4400, 0x4440, 0x4480, +}; + +// ============================================================================ +// Functions +// ============================================================================ + +// gf16_extract_sign(gf16: GF16) 5 i8 +// Extract sign bit (bit 15) +// Returns: 0 for positive, -1 for negative +pub fn gf16_extract_sign(gf16: GF16) i8 { + const bit = (gf16 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// gf16_extract_exponent(gf16: GF16) 6 i8 +// Extract exponent bits (bits 14-9) +// Returns: 0-63 +pub fn gf16_extract_exponent(gf16: GF16) i8 { + return @as(i8, @intCast((gf16 >> EXP_SHIFT) & EXP_MASK)); +} + +// gf16_extract_mantissa(gf16: GF16) 7 i16 +// Extract mantissa bits (bits 8-0) +// Returns: 0-511 +pub fn gf16_extract_mantissa(gf16: GF16) i16 { + return @as(i16, gf16 & MANT_MASK); +} + +// gf16_from_components(sign: i8, exp: i8, mant: i16) 8 GF16 +// Assemble GF16 from sign, exponent, mantissa +pub fn gf16_from_components(sign: i8, exp: i8, mant: i16) GF16 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(GF16, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(GF16, @intCast(exp)) << EXP_SHIFT) | + @as(GF16, @intCast(mant)); +} + +// gf16_is_zero(gf16: GF16) 9 bool +// Check if GF16 is zero (positive or negative) +pub fn gf16_is_zero(gf16: GF16) bool { + return gf16 == GF16_ZERO_POS or gf16 == GF16_ZERO_NEG; +} + +// gf16_is_special(gf16: GF16) 10 bool +// Check if GF16 is Inf or NaN (exp == 63) +pub fn gf16_is_special(gf16: GF16) bool { + return gf16_extract_exponent(gf16) == EXP_MAX; +} + +// gf16_encode_f32(f32: f32) 11 GF16 +// Encode IEEE 754 single precision to GF16 +// Round-to-nearest, ties to even +// Range: 2^-31 to 2^32 (normal), subnormals flushed to zero +pub fn gf16_encode_f32(value: f32) GF16 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) GF16_ZERO_NEG else GF16_ZERO_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i8 = @intCast((f32_bits >> 23) & 0xFF) - 127; + var f32_mant: u32 = f32_bits & 0x7FFFFF; + + // Convert exp from f32 bias (127) to GF16 bias (31) + // gf16_exp = f32_exp + 31 - 127 = f32_exp - 96 + var gf16_exp = f32_exp - 96; + + // Clamp exponent + if (gf16_exp < 0) { + gf16_exp = 0; // Underflow to zero + } else if (gf16_exp > EXP_MAX) { + gf16_exp = EXP_MAX; // Overflow to Inf + } + + // Extract mantissa and scale to 9 bits + // f32 mantissa is 23 bits, GF16 needs 9 bits + // Shift right by 14 bits (23 - 9 = 14) + var mant = @as(u16, @intCast(f32_mant >> 14)); + + // Round-to-nearest + const discarded = f32_mant & 0x3FFF; + if ((discarded & 0x2000) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (gf16_exp < EXP_MAX) { + gf16_exp += 1; + } + } + } + + return gf16_from_components(sign, gf16_exp, mant); +} + +// gf16_decode_to_f32(gf16: GF16) 12 f32 +// Decode GF16 to IEEE 754 single precision +pub fn gf16_decode_to_f32(gf16: GF16) f32 { + // Handle zero + if (gf16_is_zero(gf16)) { + const sign = gf16_extract_sign(gf16); + return if (sign < 0) -0.0 else 0.0; + } + + // Handle special values (Inf/NaN) + if (gf16_is_special(gf16)) { + const mant = gf16_extract_mantissa(gf16); + const sign = gf16_extract_sign(gf16); + if (mant == 0) { + // Infinity + return if (sign < 0) -std.math.inf(f32) else std.math.inf(f32); + } else { + // NaN + return std.math.nan(f32); + } + } + + // Normal number: value = (-1)^s * (1 + m/2^9) * 2^(e - 31) + const sign = gf16_extract_sign(gf16); + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + + const sign_mult = if (sign < 0) -1.0 else 1.0; + const mant_mult = 1.0 + @as(f32, @floatFromInt(mant)) / 512.0; + const exp_mult = @as(f32, @exp2(f32, @floatFromInt(exp - BIAS))); + + return sign_mult * mant_mult * exp_mult; +} + +// gf16_round_phi(value: f32) 13 GF16 +// Phi-optimized rounding for GF16 +// Uses golden ratio bias for rounding decisions instead of standard round-to-nearest +// Bias = (1/14 - 0.5) * scale, where 1/15 16 0.618 +// This improves numerical stability for sacred physics calculations +pub fn gf16_round_phi(value: f32) GF16 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) GF16_ZERO_NEG else GF16_ZERO_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f32 components + const f32_bits: u32 = @bitCast(abs_value); + var f32_exp: i8 = @intCast((f32_bits >> 23) & 0xFF) - 127; + var f32_mant: u32 = f32_bits & 0x7FFFFF; + + // Convert exp from f32 bias (127) to GF16 bias (31) + var gf16_exp = f32_exp - 96; + + // Clamp exponent + if (gf16_exp < 0) { + gf16_exp = 0; + } else if (gf16_exp > EXP_MAX) { + gf16_exp = EXP_MAX; + } + + // Add implied 1 for normalization + const normalized_mant: u32 = f32_mant | 0x00800000; + + // Scale to 9 bits with phi bias + var mant = @as(u16, @intCast((normalized_mant >> 15) + PHI_BIAS)); + + // Check for overflow and adjust + if (mant > MANT_MASK) { + mant = 0; + if (gf16_exp < EXP_MAX) { + gf16_exp += 1; + } else { + gf16_exp = EXP_MAX; // Overflow to Inf + } + } + + return gf16_from_components(sign, gf16_exp, mant); +} + +// gf16_is_inf(gf16: GF16) 17 bool +// Check if GF16 represents infinity +pub fn gf16_is_inf(gf16: GF16) bool { + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + return (exp == EXP_MAX) and (mant == 0); +} + +// gf16_is_nan(gf16: GF16) 18 bool +// Check if GF16 represents NaN (Not a Number) +pub fn gf16_is_nan(gf16: GF16) bool { + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + return (exp == EXP_MAX) and (mant != 0); +} + +// gf16_is_negative(gf16: GF16) 19 bool +// Check if GF16 is negative (excluding negative zero) +pub fn gf16_is_negative(gf16: GF16) bool { + const sign = gf16_extract_sign(gf16); + return (sign < 0) and !gf16_is_zero(gf16); +} + +// gf16_is_positive(gf16: GF16) 20 bool +// Check if GF16 is positive (excluding positive zero) +pub fn gf16_is_positive(gf16: GF16) bool { + const sign = gf16_extract_sign(gf16); + return (sign >= 0) and !gf16_is_zero(gf16); +} + +// gf16_negate(gf16: GF16) 21 GF16 +// Negate a GF16 value (flip sign bit) +pub fn gf16_negate(gf16: GF16) GF16 { + return gf16 ^ SIGN_MASK; +} + +// gf16_abs(gf16: GF16) 22 GF16 +// Absolute value of GF16 (clear sign bit) +pub fn gf16_abs(gf16: GF16) GF16 { + return gf16 & ~SIGN_MASK; +} + +// gf16_copy_sign(gf16: GF16, sign_source: GF16) 23 GF16 +// Copy sign from sign_source to gf16 value +pub fn gf16_copy_sign(gf16: GF16, sign_source: GF16) GF16 { + const sign_mask = sign_source & SIGN_MASK; + const value_mask = gf16 & ~SIGN_MASK; + return value_mask | sign_mask; +} + +// gf16_max(a: GF16, b: GF16) 24 GF16 +// Return the greater of two GF16 values +pub fn gf16_max(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a)) return b; + if (gf16_is_nan(b)) return a; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + if (a_val >= b_val) return a else return b; +} + +// gf16_min(a: GF16, b: GF16) 25 GF16 +// Return the smaller of two GF16 values +pub fn gf16_min(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a)) return b; + if (gf16_is_nan(b)) return a; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + if (a_val <= b_val) return a else return b; +} + +// gf16_add(a: GF16, b: GF16) 26 GF16 +// Add two GF16 values (decode, add, re-encode) +// Returns NaN if either operand is NaN, Inf if overflow +pub fn gf16_add(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_inf(a) and gf16_is_inf(b)) { + // Inf + Inf = NaN (if same sign) + // Inf + (-Inf) = NaN + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + return if (a_sign == b_sign) a else GF16_NAN; + } + if (gf16_is_inf(a)) return a; + if (gf16_is_inf(b)) return b; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val + b_val; + + return gf16_encode_f32(result); +} + +// gf16_sub(a: GF16, b: GF16) 27 GF16 +// Subtract two GF16 values (decode, subtract, re-encode) +// Returns NaN if either operand is NaN, Inf if overflow +pub fn gf16_sub(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_inf(a) and gf16_is_inf(b)) { + // Inf - Inf = NaN + return GF16_NAN; + } + if (gf16_is_inf(a)) return a; + if (gf16_is_inf(b)) { + // -Inf + something = Inf (with sign flip) + return gf16_negate(b); + } + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val - b_val; + + return gf16_encode_f32(result); +} + +// gf16_mul(a: GF16, b: GF16) 28 GF16 +// Multiply two GF16 values (decode, multiply, re-encode) +// Returns NaN if either operand is NaN +pub fn gf16_mul(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_zero(a) or gf16_is_zero(b)) { + // 0 * x = 0, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_ZERO_NEG else GF16_ZERO_POS; + } + if (gf16_is_inf(a) or gf16_is_inf(b)) { + // Inf * non-zero = Inf, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_INF_NEG else GF16_INF_POS; + } + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val * b_val; + + return gf16_encode_f32(result); +} + +// gf16_div(a: GF16, b: GF16) 29 GF16 +// Divide two GF16 values (decode, divide, re-encode) +// Returns NaN if division by zero or either operand is NaN +// Returns Inf if numerator is Inf and denominator is finite non-zero +pub fn gf16_div(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_zero(b)) { + // Division by zero = Inf with sign of a + const a_sign = gf16_extract_sign(a); + return if (a_sign != 0) GF16_INF_NEG else GF16_INF_POS; + } + if (gf16_is_inf(a)) { + // Inf / finite = Inf, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_INF_NEG else GF16_INF_POS; + } + if (gf16_is_inf(b)) { + // Finite / Inf = 0, with sign handling + const a_sign = gf16_extract_sign(a); + const b_sign = gf16_extract_sign(b); + const result_sign = a_sign ^ b_sign; + return if (result_sign != 0) GF16_ZERO_NEG else GF16_ZERO_POS; + } + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const result = a_val / b_val; + + return gf16_encode_f32(result); +} + +// gf16_fma(a: GF16, b: GF16, c: GF16) 30 GF16 +// Fused multiply-add: a * b + c with single rounding +// More accurate than separate mul and add +pub fn gf16_fma(a: GF16, b: GF16, c: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) return GF16_NAN; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const c_val = gf16_decode_to_f32(c); + + // Handle special cases + if (gf16_is_zero(a) or gf16_is_zero(b)) { + return gf16_add(c, gf16_encode_f32(0.0)); + } + + // Compute a * b + c + const product = a_val * b_val; + const result = product + c_val; + + return gf16_encode_f32(result); +} + +// gf16_sqrt(a: GF16) 31 GF16 +// Square root of GF16 value +// Returns NaN for negative values, Inf for infinity +pub fn gf16_sqrt(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a) and !gf16_is_negative(a)) return a; + if (gf16_is_inf(a)) return GF16_NAN; // -Inf sqrt = NaN + if (gf16_is_zero(a)) return a; + if (gf16_is_negative(a)) return GF16_NAN; + + const a_val = gf16_decode_to_f32(a); + const result = @sqrt(a_val); + + return gf16_encode_f32(result); +} + +// gf16_square(a: GF16) 32 GF16 +// Square of GF16 value +// Uses gf16_mul internally +pub fn gf16_square(a: GF16) GF16 { + return gf16_mul(a, a); +} + +// gf16_eq(a: GF16, b: GF16) 33 bool +// Equality comparison for GF16 +// NaN values are never equal to anything (including themselves) +pub fn gf16_eq(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + // For zero values, treat +0 and -0 as equal + if (gf16_is_zero(a) and gf16_is_zero(b)) return true; + return a == b; +} + +// gf16_ne(a: GF16, b: GF16) 34 bool +// Not-equal comparison for GF16 +// NaN values are not equal to anything (including themselves) +pub fn gf16_ne(a: GF16, b: GF16) bool { + return !gf16_eq(a, b); +} + +// gf16_lt(a: GF16, b: GF16) 35 bool +// Less-than comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_lt(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val < b_val; +} + +// gf16_le(a: GF16, b: GF16) 36 bool +// Less-than-or-equal comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_le(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val <= b_val; +} + +// gf16_gt(a: GF16, b: GF16) 37 bool +// Greater-than comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_gt(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val > b_val; +} + +// gf16_ge(a: GF16, b: GF16) 38 bool +// Greater-than-or-equal comparison for GF16 +// Returns false if either operand is NaN +pub fn gf16_ge(a: GF16, b: GF16) bool { + if (gf16_is_nan(a) or gf16_is_nan(b)) return false; + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + return a_val >= b_val; +} + +// gf16_floor(a: GF16) 39 GF16 +// Round down to the nearest integer (toward -inf) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_floor(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @floor(a_val); + + return gf16_encode_f32(result); +} + +// gf16_ceil(a: GF16) 40 GF16 +// Round up to the nearest integer (toward +inf) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_ceil(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @ceil(a_val); + + return gf16_encode_f32(result); +} + +// gf16_round(a: GF16) 41 GF16 +// Round to nearest integer, ties to even (IEEE 754 roundTiesToEven) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_round(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @round(a_val); + + return gf16_encode_f32(result); +} + +// gf16_trunc(a: GF16) 42 GF16 +// Round toward zero (truncate fractional part) +// Returns NaN for NaN input, unchanged for Inf/-Inf +pub fn gf16_trunc(a: GF16) GF16 { + if (gf16_is_nan(a)) return GF16_NAN; + if (gf16_is_inf(a)) return a; + if (gf16_is_zero(a)) return a; + + const a_val = gf16_decode_to_f32(a); + const result = @trunc(a_val); + + return gf16_encode_f32(result); +} + +// gf16_fms(a: GF16, b: GF16, c: GF16) 43 GF16 +// Fused multiply-subtract: a * b - c with single rounding +// More accurate than separate mul and sub +// Useful for neural network backpropagation +pub fn gf16_fms(a: GF16, b: GF16, c: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) return GF16_NAN; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const c_val = gf16_decode_to_f32(c); + + // Handle special cases + if (gf16_is_zero(a) or gf16_is_zero(b)) { + return gf16_sub(gf16_encode_f32(0.0), c); + } + + // Compute a * b - c + const product = a_val * b_val; + const result = product - c_val; + + return gf16_encode_f32(result); +} + +// gf16_hypot(a: GF16, b: GF16) 44 GF16 +// Compute sqrt(a^2 + b^2) without overflow/underflow +// Returns NaN if either operand is NaN, Inf if both are Inf +// Useful for distance calculations, neural network normalization +pub fn gf16_hypot(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_inf(a) or gf16_is_inf(b)) return GF16_INF_POS; + + if (gf16_is_zero(a) and gf16_is_zero(b)) return GF16_ZERO_POS; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + // Standard algorithm to avoid overflow: scale by max(|a|, |b|) + const abs_a = @abs(a_val); + const abs_b = @abs(b_val); + const max_val = @max(abs_a, abs_b); + const min_val = @min(abs_a, abs_b); + + if (max_val == 0.0) return GF16_ZERO_POS; + + const ratio = min_val / max_val; + const result = max_val * @sqrt(1.0 + ratio * ratio); + + return gf16_encode_f32(result); +} + +// gf16_fmod(a: GF16, b: GF16) 45 GF16 +// Compute remainder of a / b (IEEE 754 style) +// Result has same sign as dividend (a) +// Returns NaN if divisor is zero or either operand is NaN +pub fn gf16_fmod(a: GF16, b: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b)) return GF16_NAN; + if (gf16_is_zero(b)) return GF16_NAN; + if (gf16_is_inf(a)) return GF16_NAN; + if (gf16_is_inf(b)) return a; + + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + + // Handle zero dividend + if (a_val == 0.0) return a; + + const result = @mod(a_val, b_val); + + return gf16_encode_f32(result); +} + +// gf16_is_finite(gf16: GF16) 46 bool +// Check if GF16 value is finite (not NaN, not infinity) +pub fn gf16_is_finite(gf16: GF16) bool { + return !gf16_is_nan(gf16) and !gf16_is_inf(gf16); +} + +// gf16_is_normal(gf16: GF16) 47 bool +// Check if GF16 value is a normal (normalized) number +// Normal numbers have exponent in range [1, EXP_MAX-1] and are not zero +pub fn gf16_is_normal(gf16: GF16) bool { + if (gf16_is_zero(gf16) or gf16_is_nan(gf16) or gf16_is_inf(gf16)) { + return false; + } + + const exp = gf16_extract_exponent(gf16); + // GF16: exp = 0 is subnormal, exp = 31 is inf/nan, 1-30 is normal + return exp > 0 and exp < GF16_EXP_MAX; +} + +// gf16_is_subnormal(gf16: GF16) 48 bool +// Check if GF16 value is subnormal (denormal) +// Subnormal numbers have exponent = 0 and mantissa != 0 +pub fn gf16_is_subnormal(gf16: GF16) bool { + if (gf16_is_zero(gf16) or gf16_is_nan(gf16) or gf16_is_inf(gf16)) { + return false; + } + + const exp = gf16_extract_exponent(gf16); + const mant = gf16_extract_mantissa(gf16); + + // Subnormal: exp = 0 and mantissa != 0 + return exp == 0 and mant != 0; +} + +// gf16_signbit(gf16: GF16) 49 bool +// Check if the sign bit is set (value is negative or negative zero) +// Returns true for negative values and negative zero +pub fn gf16_signbit(gf16: GF16) bool { + return (gf16 & GF16_SIGN_MASK) != 0; +} + +// gf16_sign(gf16: GF16) 50 i8 +// Return the sign of the GF16 value: -1 for negative, 0 for zero, +1 for positive +// Returns 0 for NaN (IEEE 754 specifies sign of NaN is undefined) +pub fn gf16_sign(gf16: GF16) i8 { + if (gf16_is_nan(gf16)) { + return 0; + } + + if (gf16_is_zero(gf16)) { + return 0; + } + + if (gf16_signbit(gf16)) { + return -1; + } else { + return 1; + } +} + +// gf16_clamp(x: GF16, min_val: GF16, max_val: GF16) 51 GF16 +// Clamp x to the range [min_val, max_val] +// Returns min_val if x < min_val, max_val if x > max_val, otherwise x +pub fn gf16_clamp(x: GF16, min_val: GF16, max_val: GF16) GF16 { + if (gf16_is_nan(x) or gf16_is_nan(min_val) or gf16_is_nan(max_val)) { + return GF16_NAN; + } + + // Decode for comparison + const x_decoded = gf16_decode_to_f32(x); + const min_decoded = gf16_decode_to_f32(min_val); + const max_decoded = gf16_decode_to_f32(max_val); + + if (x_decoded < min_decoded) { + return min_val; + } else if (x_decoded > max_decoded) { + return max_val; + } else { + return x; + } +} + +// gf16_lerp(a: GF16, b: GF16, t: GF16) 52 GF16 +// Linear interpolation: a + t * (b - a) +// Returns a when t=0, b when t=1, and interpolates for other values +pub fn gf16_lerp(a: GF16, b: GF16, t: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(t)) { + return GF16_NAN; + } + + // Decode to f32 for computation + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const t_val = gf16_decode_to_f32(t); + + // Compute: a + t * (b - a) + const result = a_val + t_val * (b_val - a_val); + + return gf16_encode_f32(result); +} + +// gf16_fnma(a: GF16, b: GF16, c: GF16) 53 GF16 +// Fused negative multiply-add: -(a * b) + c +// More accurate than computing gf16_sub(c, gf16_mul(a, b)) +pub fn gf16_fnma(a: GF16, b: GF16, c: GF16) GF16 { + if (gf16_is_nan(a) or gf16_is_nan(b) or gf16_is_nan(c)) { + return GF16_NAN; + } + + // Handle infinity cases + if (gf16_is_inf(a) or gf16_is_inf(b)) { + if (gf16_is_inf(c)) { + return GF16_NAN; + } + // -(inf * b) + c = -inf (with appropriate sign) + if (gf16_is_inf(a) or gf16_is_inf(b)) { + const sign_a = gf16_signbit(a); + const sign_b = gf16_signbit(b); + const result_sign = (sign_a != sign_b); // XOR for negative result + return if (result_sign) GF16_INF_NEG else GF16_INF_POS; + } + } + + if (gf16_is_inf(c)) { + return c; + } + + // Handle zero cases + if (gf16_is_zero(a) or gf16_is_zero(b)) { + return c; + } + + if (gf16_is_zero(c)) { + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const neg_product = -(a_val * b_val); + return gf16_encode_f32(neg_product); + } + + // Decode to f32 for computation + const a_val = gf16_decode_to_f32(a); + const b_val = gf16_decode_to_f32(b); + const c_val = gf16_decode_to_f32(c); + + const result = -(a_val * b_val) + c_val; + + return gf16_encode_f32(result); +} + +// gf16_exp(x: GF16) 54 GF16 +// Compute e^x (exponential function) +// Uses Taylor series approximation for small values +// Returns Inf for very large positive inputs, 0 for very large negative inputs +pub fn gf16_exp(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_ZERO_POS; + return GF16_INF_POS; + } + + const x_val = gf16_decode_to_f32(x); + + // For large positive values, return Inf + if (x_val > 88.0) { // ln(MAX_FLOAT) for f32 + return GF16_INF_POS; + } + + // For large negative values, return 0 + if (x_val < -88.0) { + return GF16_ZERO_POS; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + x^4/4! + ... + // Use 5 terms for reasonable accuracy with GF16 precision + var result: f32 = 1.0; + var term: f32 = 1.0; + const num_terms: u32 = 5; + + for (0..num_terms) |i| { + if (i > 0) { + term *= x_val / @as(f32, @floatFromInt(i)); + result += term; + } + } + + return gf16_encode_f32(result); +} + +// gf16_log(x: GF16) 55 GF16 +// Compute natural logarithm ln(x) +// Returns NaN for x <= 0, Inf for very large x +pub fn gf16_log(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_NAN; + return GF16_INF_POS; + } + if (gf16_is_zero(x) or gf16_is_negative(x)) { + return GF16_NAN; + } + + const x_val = gf16_decode_to_f32(x); + + // For very large values, return Inf + if (x_val > 1.0e38) { + return GF16_INF_POS; + } + + // Use natural log from standard library + const result = @log(x_val); + + return gf16_encode_f32(result); +} + +// gf16_log2(x: GF16) 56 GF16 +// Compute base-2 logarithm log2(x) +pub fn gf16_log2(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_NAN; + return GF16_INF_POS; + } + if (gf16_is_zero(x) or gf16_is_negative(x)) { + return GF16_NAN; + } + + const x_val = gf16_decode_to_f32(x); + const result = @log2(x_val); + + return gf16_encode_f32(result); +} + +// gf16_log10(x: GF16) 57 GF16 +// Compute base-10 logarithm log10(x) +pub fn gf16_log10(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) { + if (gf16_is_negative(x)) return GF16_NAN; + return GF16_INF_POS; + } + if (gf16_is_zero(x) or gf16_is_negative(x)) { + return GF16_NAN; + } + + const x_val = gf16_decode_to_f32(x); + const result = @log10(x_val); + + return gf16_encode_f32(result); +} + +// gf16_pow(base: GF16, exponent: GF16) 58 GF16 +// Compute base^exponent +// Handles various special cases: 0^0 = 1, 1^x = 1, x^0 = 1, etc. +pub fn gf16_pow(base: GF16, exponent: GF16) GF16 { + if (gf16_is_nan(base) or gf16_is_nan(exponent)) return GF16_NAN; + + // 0^0 = 1 (by convention) + if (gf16_is_zero(base) and gf16_is_zero(exponent)) return gf16_encode_f32(1.0); + + // 0^x = 0 for x > 0 + if (gf16_is_zero(base) and gf16_is_positive(exponent)) return GF16_ZERO_POS; + + // 0^x = Inf for x < 0 (division by zero) + if (gf16_is_zero(base) and gf16_is_negative(exponent)) return GF16_INF_POS; + + // 1^x = 1 for any finite x + const base_val = gf16_decode_to_f32(base); + if (base_val == 1.0 and !gf16_is_inf(exponent)) return gf16_encode_f32(1.0); + + // x^0 = 1 for any x != 0 + if (gf16_is_zero(exponent)) { + if (gf16_is_zero(base)) return GF16_NAN; + return gf16_encode_f32(1.0); + } + + // x^1 = x + const exp_val = gf16_decode_to_f32(exponent); + if (exp_val == 1.0) return base; + + // Use stdlib pow for general case + const result = @pow(base_val, exp_val); + + return gf16_encode_f32(result); +} + +// gf16_sin(x: GF16) 59 GF16 +// Compute sine function sin(x) where x is in radians +// Uses Taylor series approximation for small values +pub fn gf16_sin(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) return GF16_NAN; + + const x_val = gf16_decode_to_f32(x); + + // Taylor series: sin(x) = x - x^3/3! + x^5/5! - x^7/7! + ... + // Use 4 terms for reasonable accuracy + const x_sq = x_val * x_val; + const x_cub = x_sq * x_val; + const x_5 = x_cub * x_sq; + const x_7 = x_5 * x_sq; + + const term1 = x_val; + const term2 = -x_cub / 6.0; + const term3 = x_5 / 120.0; + const term4 = -x_7 / 5040.0; + + const result = term1 + term2 + term3 + term4; + + return gf16_encode_f32(result); +} + +// gf16_cos(x: GF16) 60 GF16 +// Compute cosine function cos(x) where x is in radians +// Uses Taylor series approximation for small values +pub fn gf16_cos(x: GF16) GF16 { + if (gf16_is_nan(x)) return GF16_NAN; + if (gf16_is_inf(x)) return GF16_NAN; + + const x_val = gf16_decode_to_f32(x); + + // Taylor series: cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + ... + // Use 4 terms for reasonable accuracy + const x_sq = x_val * x_val; + const x_4 = x_sq * x_sq; + const x_6 = x_4 * x_sq; + + const term0 = 1.0; + const term1 = -x_sq / 2.0; + const term2 = x_4 / 24.0; + const term3 = -x_6 / 720.0; + + const result = term0 + term1 + term2 + term3; + + return gf16_encode_f32(result); +} + +// ============================================================================ +// TDD - Tests +// ============================================================================ + +test "gf16_roundtrip_phi" { + // Verify: encoding f32 PHI to GF16 and decoding back preserves value within tolerance + const PHI: f32 = 1.6180339887498948; + const encoded = gf16_encode_f32(PHI); + const decoded = gf16_decode_to_f32(encoded); + try std.testing.expectApproxEqAbs(PHI, decoded, 0.001); +} + +test "gf16_zero_encoding" { + // Verify: zero (positive and negative) encodes to correct GF16 patterns + try std.testing.expectEqual(@as(GF16, GF16_ZERO_POS), gf16_encode_f32(0.0)); + try std.testing.expectEqual(@as(GF16, GF16_ZERO_NEG), gf16_encode_f32(-0.0)); +} + +test "gf16_phi_roundtrip_high_precision" { + // Verify: PHI roundtrip with higher tolerance for golden ratio + const PHI: f32 = 1.6180339887498948; + const encoded = gf16_encode_f32(PHI); + const decoded = gf16_decode_to_f32(encoded); + try std.testing.expectApproxEqAbs(PHI, decoded, 0.01); +} + +test "gf16_inf_encoding" { + // Verify: overflow encodes to Inf correctly + const encoded = gf16_encode_f32(1.0e38); + try std.testing.expect(gf16_is_special(encoded)); + try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(encoded)); +} + +test "gf16_sign_extraction" { + try std.testing.expectEqual(@as(i8, -1), gf16_extract_sign(0x8000)); + try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(0x3C00)); + try std.testing.expectEqual(@as(i8, 1), gf16_extract_sign(0x8000)); + try std.testing.expectEqual(@as(i8, 0), gf16_extract_sign(0x3C00)); +} + +test "gf16_exponent_extraction" { + try std.testing.expectEqual(@as(i8, 0), gf16_extract_exponent(0x3C00)); + try std.testing.expectEqual(@as(i8, 1), gf16_extract_exponent(0x3D00)); +} + +test "gf16_mantissa_extraction" { + try std.testing.expectEqual(@as(i16, 0), gf16_extract_mantissa(0x3C00)); + try std.testing.expectEqual(@as(i16, 1), gf16_extract_mantissa(0x3C01)); + try std.testing.expectEqual(@as(i16, 511), gf16_extract_mantissa(0x3DFF)); +} + +test "gf16_zero_detection" { + try std.testing.expect(gf16_is_zero(0x0000)); + try std.testing.expect(gf16_is_zero(0x8000)); + try std.testing.expect(!gf16_is_zero(0x0001)); +} + +test "gf16_special_detection" { + try std.testing.expect(gf16_is_special(0x7E00)); + try std.testing.expect(gf16_is_special(0xFE01)); + try std.testing.expect(!gf16_is_special(0x3C00)); +} + +test "gf16_from_components" { + const result = gf16_from_components(0, 0, 0); + try std.testing.expectEqual(@as(GF16, 0x3C00), result); +} + +test "gf16_nan_encoding" { + const nan_val = gf16_from_components(0, 63, 1); + const decoded = gf16_decode_to_f32(nan_val); + try std.testing.expect(std.math.isNan(decoded)); +} + +test "gf16_round_phi_preserves_phi" { + const PHI: f32 = 1.6180339887498948; + const encoded = gf16_round_phi(PHI); + const decoded = gf16_decode_to_f32(encoded); + try std.testing.expectApproxEqAbs(PHI, decoded, 0.005); +} + +test "gf16_round_phi_zero" { + try std.testing.expectEqual(@as(GF16, GF16_ZERO_POS), gf16_round_phi(0.0)); + try std.testing.expectEqual(@as(GF16, GF16_ZERO_NEG), gf16_round_phi(-0.0)); +} + +test "gf16_round_phi_positive" { + try std.testing.expectApproxEqAbs(1.0, gf16_decode_to_f32(gf16_round_phi(1.0)), 0.01); + try std.testing.expectApproxEqAbs(2.0, gf16_decode_to_f32(gf16_round_phi(2.0)), 0.01); + try std.testing.expectApproxEqAbs(3.0, gf16_decode_to_f32(gf16_round_phi(3.0)), 0.01); +} + +test "gf16_round_phi_negative" { + try std.testing.expectApproxEqAbs(-1.0, gf16_decode_to_f32(gf16_round_phi(-1.0)), 0.01); + try std.testing.expectApproxEqAbs(-2.0, gf16_decode_to_f32(gf16_round_phi(-2.0)), 0.01); + const PHI: f32 = 1.6180339887498948; + try std.testing.expectApproxEqAbs(-PHI, gf16_decode_to_f32(gf16_round_phi(-PHI)), 0.01); +} + +test "gf16_pow2_table_consistency" { + try std.testing.expectEqual(@as(u16, 0x3C00), pow2_table[0]); // 2^0 = 1.0 + try std.testing.expectEqual(@as(u16, 0x3D00), pow2_table[1]); // 2^1 = 2.0 + try std.testing.expectEqual(@as(u16, 0x3D80), pow2_table[2]); // 2^2 = 4.0 +} + +test "gf16_exp_bias_identity" { + try std.testing.expectEqual(@as(i8, 31), BIAS); +} + +test "gf16_identity_encoding" { + // For GF16 representing 1.0: sign=0, exp=0, mant=0, raw value = 0x3C00 + try std.testing.expectEqual(@as(GF16, 0x3C00), gf16_from_components(0, 0, 0)); +} + +test "gf16_special_exp_all_ones" { + try std.testing.expectEqual(@as(u8, 0x3F), EXP_MAX); +} + +test "gf16_is_inf_positive" { + try std.testing.expect(gf16_is_inf(GF16_INF_POS)); + try std.testing.expect(!gf16_is_inf(GF16_ZERO_POS)); + try std.testing.expect(!gf16_is_inf(0x3C00)); +} + +test "gf16_is_inf_negative" { + try std.testing.expect(gf16_is_inf(GF16_INF_NEG)); + try std.testing.expect(!gf16_is_inf(GF16_ZERO_NEG)); +} + +test "gf16_is_nan_detection" { + try std.testing.expect(gf16_is_nan(GF16_NAN)); + try std.testing.expect(!gf16_is_nan(GF16_INF_POS)); + try std.testing.expect(!gf16_is_nan(GF16_ZERO_POS)); +} + +test "gf16_is_negative_detection" { + try std.testing.expect(gf16_is_negative(GF16_INF_NEG)); + try std.testing.expect(gf16_is_negative(gf16_encode_f32(-1.5))); + try std.testing.expect(!gf16_is_negative(GF16_ZERO_NEG)); // -0 is not considered "negative" + try std.testing.expect(!gf16_is_negative(GF16_INF_POS)); +} + +test "gf16_is_positive_detection" { + try std.testing.expect(gf16_is_positive(GF16_INF_POS)); + try std.testing.expect(gf16_is_positive(gf16_encode_f32(1.5))); + try std.testing.expect(!gf16_is_positive(GF16_ZERO_POS)); // +0 is not considered "positive" + try std.testing.expect(!gf16_is_positive(GF16_INF_NEG)); +} + +test "gf16_negate_sign_flip" { + const pos_one = gf16_encode_f32(1.0); + const neg_one = gf16_negate(pos_one); + const decoded = gf16_decode_to_f32(neg_one); + try std.testing.expectApproxEqAbs(-1.0, decoded, 0.01); +} + +test "gf16_negate_zero_stays_zero" { + try std.testing.expectEqual(GF16_ZERO_POS, gf16_negate(GF16_ZERO_POS)); + try std.testing.expectEqual(GF16_ZERO_NEG, gf16_negate(GF16_ZERO_NEG)); +} + +test "gf16_negate_double_negate" { + const original = gf16_encode_f32(1.5); + const negated = gf16_negate(original); + const double_negated = gf16_negate(negated); + const orig_decoded = gf16_decode_to_f32(original); + const double_decoded = gf16_decode_to_f32(double_negated); + try std.testing.expectApproxEqAbs(orig_decoded, double_decoded, 0.001); +} + +test "gf16_abs_clears_sign" { + const neg_value = gf16_encode_f32(-2.5); + const abs_value = gf16_abs(neg_value); + const decoded = gf16_decode_to_f32(abs_value); + try std.testing.expectApproxEqAbs(2.5, decoded, 0.01); +} + +test "gf16_abs_positive_unchanged" { + const pos_value = gf16_encode_f32(3.5); + const abs_value = gf16_abs(pos_value); + try std.testing.expectEqual(pos_value, abs_value); +} + +test "gf16_abs_zero_unchanged" { + try std.testing.expectEqual(GF16_ZERO_POS, gf16_abs(GF16_ZERO_POS)); + try std.testing.expectEqual(GF16_ZERO_POS, gf16_abs(GF16_ZERO_NEG)); +} + +test "gf16_copy_sign_from_negative" { + const pos_value = gf16_encode_f32(2.5); + const neg_source = gf16_encode_f32(-1.0); + const result = gf16_copy_sign(pos_value, neg_source); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.5, decoded, 0.01); +} + +test "gf16_copy_sign_from_positive" { + const neg_value = gf16_encode_f32(-2.5); + const pos_source = gf16_encode_f32(1.0); + const result = gf16_copy_sign(neg_value, pos_source); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.5, decoded, 0.01); +} + +test "gf16_max_returns_greater" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(5.0); + const result = gf16_max(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.01); +} + +test "gf16_max_equal_values" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(3.0); + const result = gf16_max(a, b); + try std.testing.expectEqual(a, result); +} + +test "gf16_max_with_nan" { + const a = gf16_encode_f32(2.0); + const nan_val = GF16_NAN; + const result = gf16_max(a, nan_val); + try std.testing.expectEqual(a, result); +} + +test "gf16_min_returns_smaller" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(5.0); + const result = gf16_min(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.01); +} + +test "gf16_min_equal_values" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(3.0); + const result = gf16_min(a, b); + try std.testing.expectEqual(a, result); +} + +test "gf16_min_with_nan" { + const a = gf16_encode_f32(2.0); + const nan_val = GF16_NAN; + const result = gf16_min(a, nan_val); + try std.testing.expectEqual(a, result); +} + +// ============================================================================ +// TDD - Invariants +// ============================================================================ + +invariant gf16_identity_encoding { + // For GF16 representing 1.0: sign=0, exp=0, mant=0, raw value = 0x3C00 + @compileAssert(gf16_from_components(0, 0, 0) == 0x3C00); +} + +invariant gf16_sign_mask_bit_position { + // SIGN_MASK = 0x8000 has bit 15 set (MSB) + @compileAssert(SIGN_MASK == 0x8000); +} + +invariant gf16_exp_mask_range { + // EXP_MASK = 0x7E00 covers bits 14-9 (6 bits for exponent) + @compileAssert(EXP_MASK == 0x7E00); +} + +invariant gf16_mant_mask_range { + // MANT_MASK = 0x01FF covers bits 8-0 (9 bits for mantissa) + @compileAssert(MANT_MASK == 0x01FF); +} + +invariant gf16_exp_bias_identity { + // BIAS = 31, so unbiased exp = encoded_exp - 31 + @compileAssert(BIAS == 31); +} + +invariant gf16_roundtrip_symmetry { + // For all normal values x: |decode(encode(x)) - x| < epsilon + @compileAssert(true); +} + +invariant gf16_zero_uniqueness { + // Both 0x0000 and 0x8000 represent zero (positive/negative) + @compileAssert(GF16_ZERO_POS == 0x0000); + @compileAssert(GF16_ZERO_NEG == 0x8000); +} + +invariant gf16_special_exp_all_ones { + // EXP_MAX = 0x3F (63) all ones indicates Inf/NaN + @compileAssert(EXP_MAX == 0x3F); +} + +invariant gf16_pow2_table_consistency { + // pow2_table[n] encodes 2^n for n = 0 to 31 + @compileAssert(pow2_table.len == 32); +} + +invariant gf16_mantissa_implicit_one { + // For normal numbers: actual mantissa = 1 + (stored_mant / 512) + @compileAssert(MANT_DIVISOR == 512); +} + +invariant gf16_phi_bias_positive { + // PHI_BIAS = 60 > 0 + @compileAssert(PHI_BIAS > 0); +} + +invariant gf16_phi_bias_less_than_mantissa_scale { + // PHI_BIAS = 60 < 512 (MANT_DIVISOR) + @compileAssert(PHI_BIAS < MANT_DIVISOR); +} + +invariant gf16_round_phi_preserves_sign { + // For all x: sign(gf16_round_phi(x)) = sign(x) + @compileAssert(true); +} + +invariant gf16_inf_exp_all_ones_mant_zero { + // Infinity: exp = 63, mant = 0 + @compileAssert(gf16_extract_exponent(GF16_INF_POS) == EXP_MAX); + @compileAssert(gf16_extract_mantissa(GF16_INF_POS) == 0); +} + +invariant gf16_nan_exp_all_ones_mant_nonzero { + // NaN: exp = 63, mant != 0 + @compileAssert(gf16_extract_exponent(GF16_NAN) == EXP_MAX); + @compileAssert(gf16_extract_mantissa(GF16_NAN) != 0); +} + +invariant gf16_negate_flips_sign_bit { + // gf16_negate(x) = x ^ 0x8000 + @compileAssert(gf16_negate(0x3C00) == 0xBC00); + @compileAssert(gf16_negate(0xBC00) == 0x3C00); +} + +invariant gf16_negate_involutive { + // gf16_negate(gf16_negate(x)) = x + @compileAssert(true); +} + +invariant gf16_abs_clears_sign_bit { + // gf16_abs(x) = x & ~0x8000 + @compileAssert(gf16_abs(0xBC00) == 0x3C00); + @compileAssert(gf16_abs(0x3C00) == 0x3C00); +} + +invariant gf16_abs_non_negative { + // gf16_abs(x) is always non-negative (sign bit cleared) + @compileAssert((gf16_abs(0xBC00) & SIGN_MASK) == 0); +} + +invariant gf16_copy_sign_preserves_sign_source { + // sign(gf16_copy_sign(x, s)) = sign(s) + @compileAssert(true); +} + +invariant gf16_copy_sign_preserves_magnitude { + // |gf16_copy_sign(x, s)| = |x| + @compileAssert(true); +} + +invariant gf16_max_idempotent { + // gf16_max(x, x) = x + @compileAssert(true); +} + +invariant gf16_min_idempotent { + // gf16_min(x, x) = x + @compileAssert(true); +} + +invariant gf16_max_commutative { + // gf16_max(a, b) = gf16_max(b, a) + @compileAssert(true); +} + +invariant gf16_min_commutative { + // gf16_min(a, b) = gf16_min(b, a) + @compileAssert(true); +} + +invariant gf16_is_inf_and_is_nan_exclusive { + // A value cannot be both Inf and NaN + @compileAssert(!gf16_is_inf(GF16_NAN)); + @compileAssert(!gf16_is_nan(GF16_INF_POS)); +} + +invariant gf16_add_zero_identity { + // gf16_add(x, 0) = gf16_add(0, x) = x (approximately, due to encoding) + @compileAssert(true); +} + +invariant gf16_mul_zero_annihilates { + // gf16_mul(x, 0) = gf16_mul(0, x) = 0 + @compileAssert(true); +} + +invariant gf16_mul_one_identity { + // gf16_mul(x, 1) = gf16_mul(1, x) = x (approximately) + @compileAssert(true); +} + +invariant gf16_negate_involutive { + // gf16_negate(gf16_negate(x)) = x + @compileAssert(true); +} + +invariant gf16_add_commutative { + // gf16_add(a, b) = gf16_add(b, a) + @compileAssert(true); +} + +invariant gf16_mul_commutative { + // gf16_mul(a, b) = gf16_mul(b, a) + @compileAssert(true); +} + +invariant gf16_div_by_one_identity { + // gf16_div(x, 1) = x (approximately) + @compileAssert(true); +} + +invariant gf16_sqrt_non_negative { + // gf16_sqrt(x) >= 0 for all x >= 0 + @compileAssert(true); +} + +invariant gf16_sqrt_of_square_less_than_or_equal { + // gf16_sqrt(gf16_square(x)) <= x for all x >= 0 + @compileAssert(true); +} + +invariant gf16_fma_distributive_approximation { + // gf16_fma(a, b, c) 61 gf16_add(gf16_mul(a, b), c) + // Not exact due to encoding rounding + @compileAssert(true); +} + +invariant gf16_square_positive { + // gf16_square(x) >= 0 for all x + @compileAssert(true); +} + +invariant gf16_eq_reflexive_for_non_nan { + // For all x != NaN: gf16_eq(x, x) = true + @compileAssert(true); +} + +invariant gf16_ne_irreflexive_for_non_nan { + // For all x != NaN: gf16_ne(x, x) = false + @compileAssert(true); +} + +invariant gf16_lt_and_gt_mutually_exclusive { + // For all a, b: not (gf16_lt(a, b) and gf16_gt(a, b)) + @compileAssert(true); +} + +invariant gf16_le_and_ge_mutually_inclusive { + // For all a, b: gf16_le(a, b) or gf16_ge(a, b) (for non-NaN) + @compileAssert(true); +} + +invariant gf16_lt_implies_le { + // For all a, b: gf16_lt(a, b) implies gf16_le(a, b) + @compileAssert(true); +} + +invariant gf16_gt_implies_ge { + // For all a, b: gf16_gt(a, b) implies gf16_ge(a, b) + @compileAssert(true); +} + +invariant gf16_eq_implies_le_and_ge { + // For all a, b: gf16_eq(a, b) implies gf16_le(a, b) and gf16_ge(a, b) + @compileAssert(true); +} + +invariant gf16_ne_nan_is_true { + // gf16_ne(NaN, NaN) = true per IEEE 754 + @compileAssert(true); +} + +invariant gf16_lt_nan_is_false { + // gf16_lt(NaN, x) = false for all x + @compileAssert(true); +} + +invariant gf16_gt_nan_is_false { + // gf16_gt(NaN, x) = false for all x + @compileAssert(true); +} + +invariant gf16_floor_yields_integer { + // For all x != NaN, Inf: floor(gf16_floor(x)) = gf16_floor(x) + @compileAssert(true); +} + +invariant gf16_ceil_yields_integer { + // For all x != NaN, Inf: ceil(gf16_ceil(x)) = gf16_ceil(x) + @compileAssert(true); +} + +invariant gf16_round_yields_integer { + // For all x != NaN, Inf: round(gf16_round(x)) = gf16_round(x) + @compileAssert(true); +} + +invariant gf16_trunc_yields_integer { + // For all x != NaN, Inf: trunc(gf16_trunc(x)) = gf16_trunc(x) + @compileAssert(true); +} + +invariant gf16_floor_le_value { + // For all x: floor(x) <= x + @compileAssert(true); +} + +invariant gf16_ceil_ge_value { + // For all x: ceil(x) >= x + @compileAssert(true); +} + +invariant gf16_round_closest_integer { + // For all x: |round(x) - x| <= 0.5 + @compileAssert(true); +} + +invariant gf16_trunc_magnitude_less_or_equal { + // For all x: |trunc(x)| <= |x| + @compileAssert(true); +} + +invariant gf16_trunc_positive_equals_floor { + // For all x >= 0: trunc(x) = floor(x) + @compileAssert(true); +} + +invariant gf16_trunc_negative_equals_ceil { + // For all x <= 0: trunc(x) = ceil(x) + @compileAssert(true); +} + +invariant gf16_fms_related_to_fma { + // gf16_fms(a, b, c) = gf16_fma(a, b, -c) (approximately, due to encoding) + @compileAssert(true); +} + +invariant gf16_fms_with_zero_subtractand { + // gf16_fms(a, b, 0) = gf16_mul(a, b) (approximately) + @compileAssert(true); +} + +invariant gf16_hypot_non_negative { + // For all a, b: gf16_hypot(a, b) >= 0 + @compileAssert(true); +} + +invariant gf16_hypot_symmetric { + // For all a, b: gf16_hypot(a, b) = gf16_hypot(b, a) + @compileAssert(true); +} + +invariant gf16_hypot_pythagorean_identity { + // For all a, b: hypot(a, b)^2 = a^2 + b^2 (approximately, due to encoding) + @compileAssert(true); +} + +invariant gf16_hypot_ge_max_input { + // For all a, b: gf16_hypot(a, b) >= max(|a|, |b|) + @compileAssert(true); +} + +invariant gf16_hypot_zero_with_zeros { + // gf16_hypot(0, 0) = 0 + @compileAssert(true); +} + +invariant gf16_fmod_result_sign_matches_dividend { + // For all a, b where b != 0: sign(gf16_fmod(a, b)) = sign(a) + @compileAssert(true); +} + +invariant gf16_fmod_less_than_divisor { + // For all a, b where b > 0: |gf16_fmod(a, b)| < |b| + @compileAssert(true); +} + +invariant gf16_fmod_with_divisible_values { + // For all a, b where a = k*b: gf16_fmod(a, b) = 0 + @compileAssert(true); +} + +invariant gf16_is_finite_excludes_inf_nan { + // gf16_is_finite(x) = true implies !gf16_is_inf(x) and !gf16_is_nan(x) + @compileAssert(true); +} + +invariant gf16_is_normal_implies_finite { + // gf16_is_normal(x) = true implies gf16_is_finite(x) + @compileAssert(true); +} + +invariant gf16_is_subnormal_implies_finite { + // gf16_is_subnormal(x) = true implies gf16_is_finite(x) + @compileAssert(true); +} + +invariant gf16_is_normal_and_subnormal_mutually_exclusive { + // gf16_is_normal(x) and gf16_is_subnormal(x) cannot both be true + @compileAssert(true); +} + +invariant gf16_zero_neither_normal_nor_subnormal { + // gf16_is_zero(x) = true implies !gf16_is_normal(x) and !gf16_is_subnormal(x) + @compileAssert(true); +} + +invariant gf16_classification_exhaustive { + // For all x: (is_finite and (is_normal or is_subnormal or is_zero)) or is_inf or is_nan + @compileAssert(true); +} + +invariant gf16_signbit_positive_no_signbit { + // gf16_signbit(x) = false for x >= 0 (including +0 and +inf) + @compileAssert(true); +} + +invariant gf16_signbit_negative_has_signbit { + // gf16_signbit(x) = true for x < 0 (including -0 and -inf) + @compileAssert(true); +} + +invariant gf16_sign_positive_returns_one { + // For x > 0 and x is not NaN: gf16_sign(x) = 1 + @compileAssert(true); +} + +invariant gf16_sign_negative_returns_minus_one { + // For x < 0 and x is not NaN: gf16_sign(x) = -1 + @compileAssert(true); +} + +invariant gf16_sign_zero_returns_zero { + // For x = 0 (positive or negative): gf16_sign(x) = 0 + @compileAssert(true); +} + +invariant gf16_sign_nan_returns_zero { + // For NaN: gf16_sign(x) = 0 (sign of NaN is undefined) + @compileAssert(true); +} + +invariant gf16_clamp_in_range_returns_value { + // For x in [min, max]: gf16_clamp(x, min, max) = x + @compileAssert(true); +} + +invariant gf16_clamp_below_min_returns_min { + // For x < min: gf16_clamp(x, min, max) = min + @compileAssert(true); +} + +invariant gf16_clamp_above_max_returns_max { + // For x > max: gf16_clamp(x, min, max) = max + @compileAssert(true); +} + +invariant gf16_lerp_t_zero_returns_a { + // gf16_lerp(a, b, 0) = a + @compileAssert(true); +} + +invariant gf16_lerp_t_one_returns_b { + // gf16_lerp(a, b, 1) = b + @compileAssert(true); +} + +invariant gf16_lerp_monotonic { + // For fixed a < b: gf16_lerp(a, b, t) is monotonic in t + @compileAssert(true); +} + +invariant gf16_fnma_equals_neg_mul_plus_c { + // gf16_fnma(a, b, c) = -(a*b) + c (approximately, with better precision) + @compileAssert(true); +} + +invariant gf16_fnma_zero_multiplier_returns_c { + // gf16_fnma(0, b, c) = c + @compileAssert(true); +} + +invariant gf16_exp_zero_returns_one { + // gf16_exp(0) = 1 + @compileAssert(true); +} + +invariant gf16_exp_positive_greater_than_one { + // gf16_exp(x) > 1 for x > 0 + @compileAssert(true); +} + +invariant gf16_exp_negative_between_zero_and_one { + // 0 < gf16_exp(x) < 1 for x < 0 + @compileAssert(true); +} + +invariant gf16_log_one_returns_zero { + // gf16_log(1) = 0 + @compileAssert(true); +} + +invariant gf16_log_zero_or_negative_nan { + // gf16_log(x) = NaN for x <= 0 + @compileAssert(true); +} + +invariant gf16_pow_zero_to_zero_returns_one { + // gf16_pow(0, 0) = 1 (by convention) + @compileAssert(true); +} + +invariant gf16_pow_any_to_zero_returns_one { + // gf16_pow(x, 0) = 1 for x != 0 + @compileAssert(true); +} + +invariant gf16_pow_one_to_any_returns_one { + // gf16_pow(1, x) = 1 for finite x + @compileAssert(true); +} + +invariant gf16_sin_zero_returns_zero { + // gf16_sin(0) = 0 + @compileAssert(true); +} + +invariant gf16_cos_zero_returns_one { + // gf16_cos(0) = 1 + @compileAssert(true); +} + +invariant gf16_trig_identity_approx { + // sin^2(x) + cos^2(x) 62 1 for reasonable x values + @compileAssert(true); +} + +test "gf16_add_positive_values" { + const a = gf16_encode_f32(1.5); + const b = gf16_encode_f32(2.5); + const result = gf16_add(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.1); +} + +test "gf16_add_negative_values" { + const a = gf16_encode_f32(-1.5); + const b = gf16_encode_f32(-2.5); + const result = gf16_add(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-4.0, decoded, 0.1); +} + +test "gf16_add_opposite_values" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(-2.0); + const result = gf16_add(a, b); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_add_with_zero" { + const a = gf16_encode_f32(3.5); + const zero = gf16_encode_f32(0.0); + const result1 = gf16_add(a, zero); + const result2 = gf16_add(zero, a); + const decoded1 = gf16_decode_to_f32(result1); + const decoded2 = gf16_decode_to_f32(result2); + try std.testing.expectApproxEqAbs(3.5, decoded1, 0.05); + try std.testing.expectApproxEqAbs(3.5, decoded2, 0.05); +} + +test "gf16_sub_positive_values" { + const a = gf16_encode_f32(5.0); + const b = gf16_encode_f32(2.0); + const result = gf16_sub(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_sub_negative_result" { + const a = gf16_encode_f32(1.0); + const b = gf16_encode_f32(3.0); + const result = gf16_sub(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_sub_with_zero" { + const a = gf16_encode_f32(2.5); + const zero = gf16_encode_f32(0.0); + const result1 = gf16_sub(a, zero); + const result2 = gf16_sub(zero, a); + const decoded1 = gf16_decode_to_f32(result1); + const decoded2 = gf16_decode_to_f32(result2); + try std.testing.expectApproxEqAbs(2.5, decoded1, 0.05); + try std.testing.expectApproxEqAbs(-2.5, decoded2, 0.05); +} + +test "gf16_mul_positive_values" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const result = gf16_mul(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(6.0, decoded, 0.1); +} + +test "gf16_mul_negative_positive" { + const a = gf16_encode_f32(-2.0); + const b = gf16_encode_f32(3.0); + const result = gf16_mul(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-6.0, decoded, 0.1); +} + +test "gf16_mul_with_zero" { + const a = gf16_encode_f32(5.0); + const zero = gf16_encode_f32(0.0); + const result1 = gf16_mul(a, zero); + const result2 = gf16_mul(zero, a); + try std.testing.expect(gf16_is_zero(result1)); + try std.testing.expect(gf16_is_zero(result2)); +} + +test "gf16_mul_by_one" { + const a = gf16_encode_f32(3.5); + const one = gf16_encode_f32(1.0); + const result = gf16_mul(a, one); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.5, decoded, 0.05); +} + +test "gf16_div_positive_values" { + const a = gf16_encode_f32(6.0); + const b = gf16_encode_f32(3.0); + const result = gf16_div(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_div_negative_result" { + const a = gf16_encode_f32(6.0); + const b = gf16_encode_f32(-3.0); + const result = gf16_div(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_div_by_one" { + const a = gf16_encode_f32(2.5); + const one = gf16_encode_f32(1.0); + const result = gf16_div(a, one); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.5, decoded, 0.05); +} + +test "gf16_div_zero_by_value" { + const zero = gf16_encode_f32(0.0); + const a = gf16_encode_f32(5.0); + const result = gf16_div(zero, a); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_div_value_by_zero" { + const a = gf16_encode_f32(5.0); + const zero = gf16_encode_f32(0.0); + const result = gf16_div(a, zero); + try std.testing.expect(gf16_is_inf(result)); +} + +test "gf16_div_inf_by_finite" { + const inf = GF16_INF_POS; + const a = gf16_encode_f32(5.0); + const result = gf16_div(inf, a); + try std.testing.expect(gf16_is_inf(result)); +} + +test "gf16_fma_basic" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(4.0); + const result = gf16_fma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.2); +} + +test "gf16_fma_with_zero" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const zero = gf16_encode_f32(0.0); + const result = gf16_fma(a, b, zero); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(6.0, decoded, 0.15); +} + +test "gf16_sqrt_positive" { + const a = gf16_encode_f32(4.0); + const result = gf16_sqrt(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.05); +} + +test "gf16_sqrt_of_one" { + const a = gf16_encode_f32(1.0); + const result = gf16_sqrt(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.05); +} + +test "gf16_sqrt_of_zero" { + const zero = gf16_encode_f32(0.0); + const result = gf16_sqrt(zero); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_sqrt_negative_nan" { + const neg = gf16_encode_f32(-4.0); + const result = gf16_sqrt(neg); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_square_of_two" { + const a = gf16_encode_f32(2.0); + const result = gf16_square(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.1); +} + +test "gf16_square_of_zero" { + const zero = gf16_encode_f32(0.0); + const result = gf16_square(zero); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_square_of_negative" { + const neg = gf16_encode_f32(-2.0); + const result = gf16_square(neg); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.1); +} + +test "gf16_add_commutative" { + const a = gf16_encode_f32(1.5); + const b = gf16_encode_f32(2.5); + const result1 = gf16_add(a, b); + const result2 = gf16_add(b, a); + try std.testing.expectEqual(result1, result2); +} + +test "gf16_mul_commutative" { + const a = gf16_encode_f32(1.5); + const b = gf16_encode_f32(2.5); + const result1 = gf16_mul(a, b); + const result2 = gf16_mul(b, a); + try std.testing.expectEqual(result1, result2); +} + +test "gf16_sqrt_square_roundtrip" { + const a = gf16_encode_f32(4.0); + const squared = gf16_square(a); + const rooted = gf16_sqrt(squared); + const decoded = gf16_decode_to_f32(rooted); + try std.testing.expectApproxEqAbs(4.0, decoded, 0.2); +} + +test "gf16_eq_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(gf16_eq(a, b)); +} + +test "gf16_eq_different_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(3.5); + try std.testing.expect(!gf16_eq(a, b)); +} + +test "gf16_eq_pos_zero_eq_neg_zero" { + // IEEE 754: +0.0 == -0.0 is true + try std.testing.expect(gf16_eq(GF16_ZERO_POS, GF16_ZERO_NEG)); +} + +test "gf16_eq_nan_not_equal_nan" { + // NaN != NaN per IEEE 754 + try std.testing.expect(!gf16_eq(GF16_NAN, GF16_NAN)); +} + +test "gf16_eq_nan_not_equal_value" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_eq(GF16_NAN, value)); + try std.testing.expect(!gf16_eq(value, GF16_NAN)); +} + +test "gf16_ne_different_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(3.5); + try std.testing.expect(gf16_ne(a, b)); +} + +test "gf16_ne_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(!gf16_ne(a, b)); +} + +test "gf16_ne_nan_not_equal_nan" { + // NaN != NaN per IEEE 754 + try std.testing.expect(gf16_ne(GF16_NAN, GF16_NAN)); +} + +test "gf16_ne_nan_not_equal_value" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(gf16_ne(GF16_NAN, value)); + try std.testing.expect(gf16_ne(value, GF16_NAN)); +} + +test "gf16_lt_less_than" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(gf16_lt(a, b)); +} + +test "gf16_lt_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(!gf16_lt(a, b)); +} + +test "gf16_lt_greater_than" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(!gf16_lt(a, b)); +} + +test "gf16_lt_negative_positive" { + const neg = gf16_encode_f32(-2.0); + const pos = gf16_encode_f32(1.0); + try std.testing.expect(gf16_lt(neg, pos)); +} + +test "gf16_lt_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_lt(GF16_NAN, value)); + try std.testing.expect(!gf16_lt(value, GF16_NAN)); +} + +test "gf16_le_less_than_or_equal" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(gf16_le(a, b)); +} + +test "gf16_le_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(gf16_le(a, b)); +} + +test "gf16_le_greater_than" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(!gf16_le(a, b)); +} + +test "gf16_le_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_le(GF16_NAN, value)); + try std.testing.expect(!gf16_le(value, GF16_NAN)); +} + +test "gf16_gt_greater_than" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(gf16_gt(a, b)); +} + +test "gf16_gt_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(!gf16_gt(a, b)); +} + +test "gf16_gt_less_than" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(!gf16_gt(a, b)); +} + +test "gf16_gt_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_gt(GF16_NAN, value)); + try std.testing.expect(!gf16_gt(value, GF16_NAN)); +} + +test "gf16_ge_greater_than_or_equal" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(2.0); + try std.testing.expect(gf16_ge(a, b)); +} + +test "gf16_ge_equal_values" { + const a = gf16_encode_f32(2.5); + const b = gf16_encode_f32(2.5); + try std.testing.expect(gf16_ge(a, b)); +} + +test "gf16_ge_less_than" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + try std.testing.expect(!gf16_ge(a, b)); +} + +test "gf16_ge_with_nan" { + const value = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_ge(GF16_NAN, value)); + try std.testing.expect(!gf16_ge(value, GF16_NAN)); +} + +test "gf16_comparison_consistency" { + // Verify: lt, le, gt, ge, eq, ne are mutually consistent + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + + // a < b implies a <= b and !a > b and !a >= b + try std.testing.expect(gf16_lt(a, b)); + try std.testing.expect(gf16_le(a, b)); + try std.testing.expect(!gf16_gt(a, b)); + try std.testing.expect(!gf16_ge(a, b)); +} + +test "gf16_floor_positive_value" { + const a = gf16_encode_f32(2.7); + const result = gf16_floor(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_floor_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_floor(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-3.0, decoded, 0.1); +} + +test "gf16_floor_integer" { + const a = gf16_encode_f32(5.0); + const result = gf16_floor(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.05); +} + +test "gf16_floor_zero" { + const pos_zero = gf16_encode_f32(0.0); + const neg_zero = gf16_encode_f32(-0.0); + try std.testing.expect(gf16_is_zero(gf16_floor(pos_zero))); + try std.testing.expect(gf16_is_zero(gf16_floor(neg_zero))); +} + +test "gf16_floor_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_floor(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_floor(GF16_INF_NEG)); +} + +test "gf16_floor_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_floor(GF16_NAN))); +} + +test "gf16_ceil_positive_value" { + const a = gf16_encode_f32(2.3); + const result = gf16_ceil(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_ceil_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_ceil(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_ceil_integer" { + const a = gf16_encode_f32(5.0); + const result = gf16_ceil(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.05); +} + +test "gf16_ceil_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_ceil(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_ceil(GF16_INF_NEG)); +} + +test "gf16_ceil_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_ceil(GF16_NAN))); +} + +test "gf16_round_half_up" { + const a = gf16_encode_f32(2.5); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); // roundTiesToEven +} + +test "gf16_round_positive_value" { + const a = gf16_encode_f32(2.7); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_round_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-3.0, decoded, 0.1); +} + +test "gf16_round_fractional_down" { + const a = gf16_encode_f32(2.3); + const result = gf16_round(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_round_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_round(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_round(GF16_INF_NEG)); +} + +test "gf16_round_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_round(GF16_NAN))); +} + +test "gf16_trunc_positive_value" { + const a = gf16_encode_f32(2.7); + const result = gf16_trunc(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.0, decoded, 0.1); +} + +test "gf16_trunc_negative_value" { + const a = gf16_encode_f32(-2.7); + const result = gf16_trunc(a); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-2.0, decoded, 0.1); +} + +test "gf16_trunc_zero" { + const pos_zero = gf16_encode_f32(0.0); + const neg_zero = gf16_encode_f32(-0.0); + try std.testing.expect(gf16_is_zero(gf16_trunc(pos_zero))); + try std.testing.expect(gf16_is_zero(gf16_trunc(neg_zero))); +} + +test "gf16_trunc_inf_unchanged" { + try std.testing.expectEqual(GF16_INF_POS, gf16_trunc(GF16_INF_POS)); + try std.testing.expectEqual(GF16_INF_NEG, gf16_trunc(GF16_INF_NEG)); +} + +test "gf16_trunc_nan_returns_nan" { + try std.testing.expect(gf16_is_nan(gf16_trunc(GF16_NAN))); +} + +test "gf16_rounding_floor_vs_trunc_negative" { + // floor(-2.7) = -3.0, trunc(-2.7) = -2.0 + const a = gf16_encode_f32(-2.7); + const floored = gf16_decode_to_f32(gf16_floor(a)); + const truncated = gf16_decode_to_f32(gf16_trunc(a)); + try std.testing.expectApproxEqAbs(-3.0, floored, 0.1); + try std.testing.expectApproxEqAbs(-2.0, truncated, 0.1); +} + +test "gf16_rounding_ceil_vs_trunc_positive" { + // ceil(2.3) = 3.0, trunc(2.3) = 2.0 + const a = gf16_encode_f32(2.3); + const ceiled = gf16_decode_to_f32(gf16_ceil(a)); + const truncated = gf16_decode_to_f32(gf16_trunc(a)); + try std.testing.expectApproxEqAbs(3.0, ceiled, 0.1); + try std.testing.expectApproxEqAbs(2.0, truncated, 0.1); +} + +test "gf16_fms_basic" { + const a = gf16_encode_f32(5.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(2.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(13.0, decoded, 0.2); // 5*3 - 2 = 13 +} + +test "gf16_fms_with_zero_c" { + const a = gf16_encode_f32(4.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(0.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(12.0, decoded, 0.2); // 4*3 - 0 = 12 +} + +test "gf16_fms_negative_result" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-4.0, decoded, 0.2); // 2*3 - 10 = -4 +} + +test "gf16_fms_with_nan" { + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fms(a, b, GF16_NAN); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_fms_zero_a" { + const a = gf16_encode_f32(0.0); + const b = gf16_encode_f32(5.0); + const c = gf16_encode_f32(3.0); + const result = gf16_fms(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-3.0, decoded, 0.2); // 0 - 3 = -3 +} + +test "gf16_hypot_pythagorean_triple" { + const a = gf16_encode_f32(3.0); + const b = gf16_encode_f32(4.0); + const result = gf16_hypot(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); // sqrt(9 + 16) = 5 +} + +test "gf16_hypot_both_zero" { + try std.testing.expectEqual(GF16_ZERO_POS, gf16_hypot(GF16_ZERO_POS, GF16_ZERO_POS)); +} + +test "gf16_hypot_one_zero" { + const a = gf16_encode_f32(3.0); + const result = gf16_hypot(a, GF16_ZERO_POS); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.05); +} + +test "gf16_hypot_negative_inputs" { + const a = gf16_encode_f32(-3.0); + const b = gf16_encode_f32(-4.0); + const result = gf16_hypot(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); // sqrt(9 + 16) = 5 +} + +test "gf16_hypot_with_nan" { + const a = gf16_encode_f32(3.0); + const result = gf16_hypot(a, GF16_NAN); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_hypot_with_inf" { + const a = gf16_encode_f32(3.0); + try std.testing.expectEqual(GF16_INF_POS, gf16_hypot(a, GF16_INF_POS)); +} + +test "gf16_fmod_basic" { + const a = gf16_encode_f32(10.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fmod(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); // 10 % 3 = 1 +} + +test "gf16_fmod_exact_division" { + const a = gf16_encode_f32(12.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fmod(a, b); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_fmod_negative_dividend" { + const a = gf16_encode_f32(-10.0); + const b = gf16_encode_f32(3.0); + const result = gf16_fmod(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-1.0, decoded, 0.1); // -10 % 3 = -1 (sign follows dividend) +} + +test "gf16_fmod_fractional" { + const a = gf16_encode_f32(5.5); + const b = gf16_encode_f32(2.0); + const result = gf16_fmod(a, b); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.5, decoded, 0.1); // 5.5 % 2 = 1.5 +} + +test "gf16_fmod_zero_divisor" { + const a = gf16_encode_f32(10.0); + const zero = gf16_encode_f32(0.0); + const result = gf16_fmod(a, zero); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_fmod_with_nan" { + const a = gf16_encode_f32(10.0); + const result = gf16_fmod(a, GF16_NAN); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_is_finite_normal_numbers" { + // Verify: normal numbers are finite + const n1 = gf16_encode_f32(1.0); + const n2 = gf16_encode_f32(-1.0); + const n3 = gf16_encode_f32(100.5); + try std.testing.expect(gf16_is_finite(n1)); + try std.testing.expect(gf16_is_finite(n2)); + try std.testing.expect(gf16_is_finite(n3)); +} + +test "gf16_is_finite_zero" { + // Verify: zero is finite + const z1 = gf16_encode_f32(0.0); + const z2 = gf16_encode_f32(-0.0); + try std.testing.expect(gf16_is_finite(z1)); + try std.testing.expect(gf16_is_finite(z2)); +} + +test "gf16_is_finite_false_for_inf" { + // Verify: infinity is not finite + const pos_inf = GF16_INF_POS; + const neg_inf = GF16_INF_NEG; + try std.testing.expect(!gf16_is_finite(pos_inf)); + try std.testing.expect(!gf16_is_finite(neg_inf)); +} + +test "gf16_is_finite_false_for_nan" { + // Verify: NaN is not finite + try std.testing.expect(!gf16_is_finite(GF16_NAN)); +} + +test "gf16_is_normal_true_for_normal" { + // Verify: normal numbers return true + const n1 = gf16_encode_f32(1.0); + const n2 = gf16_encode_f32(-2.5); + const n3 = gf16_encode_f32(100.0); + try std.testing.expect(gf16_is_normal(n1)); + try std.testing.expect(gf16_is_normal(n2)); + try std.testing.expect(gf16_is_normal(n3)); +} + +test "gf16_is_normal_false_for_zero" { + // Verify: zero is not normal + const z1 = gf16_encode_f32(0.0); + const z2 = gf16_encode_f32(-0.0); + try std.testing.expect(!gf16_is_normal(z1)); + try std.testing.expect(!gf16_is_normal(z2)); +} + +test "gf16_is_normal_false_for_inf" { + // Verify: infinity is not normal + try std.testing.expect(!gf16_is_normal(GF16_INF_POS)); + try std.testing.expect(!gf16_is_normal(GF16_INF_NEG)); +} + +test "gf16_is_normal_false_for_nan" { + // Verify: NaN is not normal + try std.testing.expect(!gf16_is_normal(GF16_NAN)); +} + +test "gf16_is_subnormal_true_for_subnormal" { + // Verify: subnormal (denormal) numbers return true + // Smallest subnormal in GF16: exp=0, mant=1 (approximately 2^-14 * 2^-9 = 2^-23) + // We'll check a value that decodes to subnormal + const sub = gf16_encode_f32(0.000001); + const decoded = gf16_decode_to_f32(sub); + // If the value rounds to subnormal, is_subnormal should be true + // This test depends on GF16 subnormal threshold (~6.1e-5) + const is_sub = gf16_is_subnormal(sub); + _ = decoded; + _ = is_sub; + // We just verify the function doesn't crash for now + try std.testing.expect(true); +} + +test "gf16_is_subnormal_false_for_normal" { + // Verify: normal numbers are not subnormal + const n1 = gf16_encode_f32(1.0); + const n2 = gf16_encode_f32(100.0); + try std.testing.expect(!gf16_is_subnormal(n1)); + try std.testing.expect(!gf16_is_subnormal(n2)); +} + +test "gf16_is_subnormal_false_for_zero" { + // Verify: zero is not subnormal (zero is a special case) + const z1 = gf16_encode_f32(0.0); + const z2 = gf16_encode_f32(-0.0); + try std.testing.expect(!gf16_is_subnormal(z1)); + try std.testing.expect(!gf16_is_subnormal(z2)); +} + +test "gf16_is_subnormal_false_for_special" { + // Verify: NaN and infinity are not subnormal + try std.testing.expect(!gf16_is_subnormal(GF16_NAN)); + try std.testing.expect(!gf16_is_subnormal(GF16_INF_POS)); + try std.testing.expect(!gf16_is_subnormal(GF16_INF_NEG)); +} + +test "gf16_classification_complete_coverage" { + // Verify: all GF16 values can be classified + // For any value, exactly one of these should be true: + // - is_finite and (is_normal or is_subnormal or is_zero) + // OR is_inf + // OR is_nan + + const test_values = [_]f32{ + 0.0, -0.0, 1.0, -1.0, 100.0, -100.0, + 0.0001, -0.0001, + }; + + for (test_values) |val| { + const gf = gf16_encode_f32(val); + const is_fin = gf16_is_finite(gf); + const is_inf = gf16_is_inf(gf); + const is_nan = gf16_is_nan(gf); + + // Exactly one of finite, inf, nan should be true + const count = @as(u8, @intFromBool(is_fin)) + + @as(u8, @intFromBool(is_inf)) + + @as(u8, @intFromBool(is_nan)); + try std.testing.expectEqual(@as(u8, 1), count); + } +} + +test "gf16_signbit_positive" { + // Verify: positive values have signbit = false + const val = gf16_encode_f32(1.5); + try std.testing.expect(!gf16_signbit(val)); +} + +test "gf16_signbit_negative" { + // Verify: negative values have signbit = true + const val = gf16_encode_f32(-1.5); + try std.testing.expect(gf16_signbit(val)); +} + +test "gf16_signbit_positive_zero" { + // Verify: positive zero has signbit = false + const zero_pos = GF16_ZERO_POS; + try std.testing.expect(!gf16_signbit(zero_pos)); +} + +test "gf16_signbit_negative_zero" { + // Verify: negative zero has signbit = true + const zero_neg = GF16_ZERO_NEG; + try std.testing.expect(gf16_signbit(zero_neg)); +} + +test "gf16_signbit_infinity" { + // Verify: signbit is set for negative infinity, not for positive + try std.testing.expect(!gf16_signbit(GF16_INF_POS)); + try std.testing.expect(gf16_signbit(GF16_INF_NEG)); +} + +test "gf16_signbit_nan" { + // Verify: NaN can have signbit set or not (we check both cases) + // Most NaN implementations propagate signbit + const nan_with_sign = GF16_NAN | 0x8000; + try std.testing.expect(gf16_signbit(nan_with_sign)); +} + +test "gf16_sign_positive" { + // Verify: positive values return +1 + const v1 = gf16_encode_f32(1.0); + const v2 = gf16_encode_f32(100.5); + try std.testing.expectEqual(@as(i8, 1), gf16_sign(v1)); + try std.testing.expectEqual(@as(i8, 1), gf16_sign(v2)); +} + +test "gf16_sign_negative" { + // Verify: negative values return -1 + const v1 = gf16_encode_f32(-1.0); + const v2 = gf16_encode_f32(-100.5); + try std.testing.expectEqual(@as(i8, -1), gf16_sign(v1)); + try std.testing.expectEqual(@as(i8, -1), gf16_sign(v2)); +} + +test "gf16_sign_zero" { + // Verify: zero (positive or negative) returns 0 + try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_ZERO_POS)); + try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_ZERO_NEG)); +} + +test "gf16_sign_nan" { + // Verify: NaN returns 0 (IEEE 754 specifies sign of NaN is undefined) + try std.testing.expectEqual(@as(i8, 0), gf16_sign(GF16_NAN)); +} + +test "gf16_sign_infinity" { + // Verify: positive infinity returns +1, negative returns -1 + try std.testing.expectEqual(@as(i8, 1), gf16_sign(GF16_INF_POS)); + try std.testing.expectEqual(@as(i8, -1), gf16_sign(GF16_INF_NEG)); +} + +test "gf16_sign_matches_signbit" { + // Verify: gf16_sign and gf16_signbit are consistent for non-zero values + const pos_val = gf16_encode_f32(5.5); + const neg_val = gf16_encode_f32(-5.5); + + try std.testing.expect(!gf16_signbit(pos_val) and gf16_sign(pos_val) > 0); + try std.testing.expect(gf16_signbit(neg_val) and gf16_sign(neg_val) < 0); +} + +test "gf16_clamp_in_range" { + // Verify: value within range is unchanged + const x = gf16_encode_f32(5.0); + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); +} + +test "gf16_clamp_below_min" { + // Verify: value below min returns min + const x = gf16_encode_f32(-5.0); + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.0, decoded, 0.1); +} + +test "gf16_clamp_above_max" { + // Verify: value above max returns max + const x = gf16_encode_f32(15.0); + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.1); +} + +test "gf16_clamp_with_nan" { + // Verify: NaN propagates + const x = GF16_NAN; + const min_val = gf16_encode_f32(0.0); + const max_val = gf16_encode_f32(10.0); + const result = gf16_clamp(x, min_val, max_val); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_lerp_t_zero" { + // Verify: lerp with t=0 returns a + const a = gf16_encode_f32(10.0); + const b = gf16_encode_f32(20.0); + const t = gf16_encode_f32(0.0); + const result = gf16_lerp(a, b, t); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.1); +} + +test "gf16_lerp_t_one" { + // Verify: lerp with t=1 returns b + const a = gf16_encode_f32(10.0); + const b = gf16_encode_f32(20.0); + const t = gf16_encode_f32(1.0); + const result = gf16_lerp(a, b, t); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(20.0, decoded, 0.1); +} + +test "gf16_lerp_t_half" { + // Verify: lerp with t=0.5 returns midpoint + const a = gf16_encode_f32(0.0); + const b = gf16_encode_f32(10.0); + const t = gf16_encode_f32(0.5); + const result = gf16_lerp(a, b, t); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(5.0, decoded, 0.1); +} + +test "gf16_lerp_with_nan" { + // Verify: NaN propagates + const a = GF16_NAN; + const b = gf16_encode_f32(20.0); + const t = gf16_encode_f32(0.5); + const result = gf16_lerp(a, b, t); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_fnma_basic" { + // Verify: fnma(a, b, c) = -(a*b) + c + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fnma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-(2.0 * 3.0) + 10.0, decoded, 0.1); // = 4.0 +} + +test "gf16_fnma_zero_multiplier" { + // Verify: fnma with zero multiplier returns c + const a = gf16_encode_f32(0.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fnma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(10.0, decoded, 0.1); +} + +test "gf16_fnma_zero_addend" { + // Verify: fnma with c=0 returns -(a*b) + const a = gf16_encode_f32(2.0); + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(0.0); + const result = gf16_fnma(a, b, c); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(-(2.0 * 3.0), decoded, 0.1); // = -6.0 +} + +test "gf16_fnma_with_nan" { + // Verify: NaN propagates + const a = GF16_NAN; + const b = gf16_encode_f32(3.0); + const c = gf16_encode_f32(10.0); + const result = gf16_fnma(a, b, c); + try std.testing.expect(gf16_is_nan(result)); +} + +test "gf16_exp_zero" { + // Verify: e^0 = 1 + const x = gf16_encode_f32(0.0); + const result = gf16_exp(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); +} + +test "gf16_exp_one" { + // Verify: e^1 63 2.718 + const x = gf16_encode_f32(1.0); + const result = gf16_exp(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(2.718, decoded, 0.1); +} + +test "gf16_exp_negative" { + // Verify: e^-1 64 0.368 + const x = gf16_encode_f32(-1.0); + const result = gf16_exp(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.368, decoded, 0.05); +} + +test "gf16_exp_large_positive" { + // Verify: e^88 is very large (returns Inf) + const x = gf16_encode_f32(88.0); + const result = gf16_exp(x); + try std.testing.expect(gf16_is_inf(result)); +} + +test "gf16_log_one" { + // Verify: ln(1) = 0 + const x = gf16_encode_f32(1.0); + const result = gf16_log(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.0, decoded, 0.05); +} + +test "gf16_log_e" { + // Verify: ln(e) 65 1 + const e = gf16_encode_f32(2.71828); + const result = gf16_log(e); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); +} + +test "gf16_log_zero_or_negative" { + // Verify: ln(0) or ln(x<0) = NaN + const zero = gf16_encode_f32(0.0); + const neg = gf16_encode_f32(-1.0); + try std.testing.expect(gf16_is_nan(gf16_log(zero))); + try std.testing.expect(gf16_is_nan(gf16_log(neg))); +} + +test "gf16_log2_eight" { + // Verify: log2(8) = 3 + const x = gf16_encode_f32(8.0); + const result = gf16_log2(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(3.0, decoded, 0.1); +} + +test "gf16_log10_ten" { + // Verify: log10(10) = 1 + const x = gf16_encode_f32(10.0); + const result = gf16_log10(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.1); +} + +test "gf16_pow_two_cubed" { + // Verify: 2^3 = 8 + const base = gf16_encode_f32(2.0); + const exp = gf16_encode_f32(3.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(8.0, decoded, 0.1); +} + +test "gf16_pow_zero_to_zero" { + // Verify: 0^0 = 1 (by convention) + const base = gf16_encode_f32(0.0); + const exp = gf16_encode_f32(0.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.01); +} + +test "gf16_pow_any_to_zero" { + // Verify: x^0 = 1 for x != 0 + const base = gf16_encode_f32(5.5); + const exp = gf16_encode_f32(0.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.01); +} + +test "gf16_pow_zero_to_positive" { + // Verify: 0^x = 0 for x > 0 + const base = gf16_encode_f32(0.0); + const exp = gf16_encode_f32(2.0); + const result = gf16_pow(base, exp); + try std.testing.expect(gf16_is_zero(result)); +} + +test "gf16_pow_one_to_any" { + // Verify: 1^x = 1 + const base = gf16_encode_f32(1.0); + const exp = gf16_encode_f32(5.0); + const result = gf16_pow(base, exp); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.01); +} + +test "gf16_sin_zero" { + // Verify: sin(0) = 0 + const x = gf16_encode_f32(0.0); + const result = gf16_sin(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.0, decoded, 0.05); +} + +test "gf16_sin_small_angle" { + // Verify: sin(66/6) 67 0.5 + const pi_six = gf16_encode_f32(3.14159 / 6.0); + const result = gf16_sin(pi_six); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.5, decoded, 0.05); +} + +test "gf16_cos_zero" { + // Verify: cos(0) = 1 + const x = gf16_encode_f32(0.0); + const result = gf16_cos(x); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(1.0, decoded, 0.05); +} + +test "gf16_cos_small_angle" { + // Verify: cos(68/6) 69 0.866 + const pi_six = gf16_encode_f32(3.14159 / 6.0); + const result = gf16_cos(pi_six); + const decoded = gf16_decode_to_f32(result); + try std.testing.expectApproxEqAbs(0.866, decoded, 0.05); +} + +test "gf16_trig_identity" { + // Verify: sin^2(x) + cos^2(x) 70 1 for small x + const x = gf16_encode_f32(0.5); + const sin_val = gf16_decode_to_f32(gf16_sin(x)); + const cos_val = gf16_decode_to_f32(gf16_cos(x)); + const sum = sin_val * sin_val + cos_val * cos_val; + try std.testing.expectApproxEqAbs(1.0, sum, 0.1); +} + +// ============================================================================ +// TDD - Benchmarks +// ============================================================================ + +bench "gf16_encode_throughput" { + // Measure: gf16_encode_f32 calls per second + // Target: > 10M encodes/sec on typical hardware + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_encode_f32(1.5); + } + _ = result; +} + +bench "gf16_decode_throughput" { + // Measure: gf16_decode_to_f32 calls per second + // Target: > 10M decodes/sec on typical hardware + @setEvalBranchQuota(10000); + var result: f32 = 0; + for (0..1000) |_| { + result = gf16_decode_to_f32(0x3C00); + } + _ = result; +} + +bench "gf16_roundtrip_latency" { + // Measure: encode + decode latency in nanoseconds + // Target: < 100ns for typical values + @setEvalBranchQuota(10000); + var result: f32 = 0; + const value: f32 = 1.5; + for (0..1000) |_| { + result = gf16_decode_to_f32(gf16_encode_f32(value)); + } + _ = result; +} + +bench "gf16_round_phi_latency" { + // Measure: nanoseconds to gf16_round_phi(1.0) + // Target: < 200ns + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_round_phi(1.0); + } + _ = result; +} + +bench "gf16_extract_sign_latency" { + // Measure: nanoseconds to extract sign + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: i8 = 0; + for (0..1000) |_| { + result = gf16_extract_sign(0x8000); + } + _ = result; +} + +bench "gf16_extract_exponent_latency" { + // Measure: nanoseconds to extract exponent + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: i8 = 0; + for (0..1000) |_| { + result = gf16_extract_exponent(0x3C00); + } + _ = result; +} + +bench "gf16_is_inf_latency" { + // Measure: nanoseconds to check if infinity + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = gf16_is_inf(0x7E00); + } + _ = result; +} + +bench "gf16_is_nan_latency" { + // Measure: nanoseconds to check if NaN + // Target: < 20ns + @setEvalBranchQuota(10000); + var result: bool = false; + for (0..1000) |_| { + result = gf16_is_nan(0xFE01); + } + _ = result; +} + +bench "gf16_negate_latency" { + // Measure: nanoseconds to negate + // Target: < 10ns (single XOR operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_negate(0x3C00); + } + _ = result; +} + +bench "gf16_abs_latency" { + // Measure: nanoseconds to compute absolute value + // Target: < 10ns (single AND operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + for (0..1000) |_| { + result = gf16_abs(0xBC00); + } + _ = result; +} + +bench "gf16_max_latency" { + // Measure: nanoseconds to compute max of two values + // Target: < 100ns (includes decode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_max(a, b); + } + _ = result; +} + +bench "gf16_min_latency" { + // Measure: nanoseconds to compute min of two values + // Target: < 100ns (includes decode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_min(a, b); + } + _ = result; +} + +bench "gf16_add_latency" { + // Measure: nanoseconds to add two values + // Target: < 200ns (includes decode + add + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_add(a, b); + } + _ = result; +} + +bench "gf16_sub_latency" { + // Measure: nanoseconds to subtract two values + // Target: < 200ns (includes decode + sub + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_sub(a, b); + } + _ = result; +} + +bench "gf16_mul_latency" { + // Measure: nanoseconds to multiply two values + // Target: < 200ns (includes decode + mul + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3D80; + for (0..1000) |_| { + result = gf16_mul(a, b); + } + _ = result; +} + +bench "gf16_div_latency" { + // Measure: nanoseconds to divide two values + // Target: < 300ns (includes decode + div + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + for (0..1000) |_| { + result = gf16_div(a, b); + } + _ = result; +} + +bench "gf16_sqrt_latency" { + // Measure: nanoseconds to compute square root + // Target: < 300ns (includes decode + sqrt + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_sqrt(a); + } + _ = result; +} + +bench "gf16_fma_latency" { + // Measure: nanoseconds for fused multiply-add + // Target: < 300ns (fused operation, more accurate than separate) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + const c: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_fma(a, b, c); + } + _ = result; +} + +bench "gf16_square_latency" { + // Measure: nanoseconds to square a value + // Target: < 200ns (uses mul internally) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_square(a); + } + _ = result; +} + +bench "gf16_eq_latency" { + // Measure: nanoseconds to compare equality + // Target: < 30ns (simple comparison with NaN check) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_eq(a, b); + } + _ = result; +} + +bench "gf16_ne_latency" { + // Measure: nanoseconds to compare not-equal + // Target: < 30ns (negation of eq) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_ne(a, b); + } + _ = result; +} + +bench "gf16_lt_latency" { + // Measure: nanoseconds to compare less-than + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_lt(a, b); + } + _ = result; +} + +bench "gf16_le_latency" { + // Measure: nanoseconds to compare less-than-or-equal + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3C00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_le(a, b); + } + _ = result; +} + +bench "gf16_gt_latency" { + // Measure: nanoseconds to compare greater-than + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_gt(a, b); + } + _ = result; +} + +bench "gf16_ge_latency" { + // Measure: nanoseconds to compare greater-than-or-equal + // Target: < 50ns (includes decode) + @setEvalBranchQuota(10000); + var result: bool = false; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_ge(a, b); + } + _ = result; +} + +bench "gf16_floor_latency" { + // Measure: nanoseconds to compute floor + // Target: < 200ns (includes decode + floor + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_floor(a); + } + _ = result; +} + +bench "gf16_ceil_latency" { + // Measure: nanoseconds to compute ceil + // Target: < 200ns (includes decode + ceil + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_ceil(a); + } + _ = result; +} + +bench "gf16_round_latency" { + // Measure: nanoseconds to compute round + // Target: < 200ns (includes decode + round + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_round(a); + } + _ = result; +} + +bench "gf16_trunc_latency" { + // Measure: nanoseconds to compute trunc + // Target: < 200ns (includes decode + trunc + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D40; + for (0..1000) |_| { + result = gf16_trunc(a); + } + _ = result; +} + +bench "gf16_fms_latency" { + // Measure: nanoseconds for fused multiply-subtract + // Target: < 300ns (fused operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + const c: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_fms(a, b, c); + } + _ = result; +} + +bench "gf16_hypot_latency" { + // Measure: nanoseconds to compute hypotenuse + // Target: < 400ns (includes sqrt) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D80; + const b: GF16 = 0x3E00; + for (0..1000) |_| { + result = gf16_hypot(a, b); + } + _ = result; +} + +bench "gf16_fmod_latency" { + // Measure: nanoseconds to compute modulo + // Target: < 300ns (includes decode + mod + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3F00; + const b: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_fmod(a, b); + } + _ = result; +} + +bench "gf16_is_finite_latency" { + // Measure: nanoseconds to check if value is finite + // Target: < 30ns (simple bit checks) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_is_finite(val); + } + _ = result; +} + +bench "gf16_is_normal_latency" { + // Measure: nanoseconds to check if value is normal + // Target: < 40ns (extraction + range check) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_is_normal(val); + } + _ = result; +} + +bench "gf16_is_subnormal_latency" { + // Measure: nanoseconds to check if value is subnormal + // Target: < 40ns (extraction + mantissa check) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x0001; + for (0..1000) |_| { + result = gf16_is_subnormal(val); + } + _ = result; +} + +bench "gf16_signbit_latency" { + // Measure: nanoseconds to check sign bit + // Target: < 5ns (single bit test) + @setEvalBranchQuota(10000); + var result: bool = false; + const val: GF16 = 0x8000; + for (0..1000) |_| { + result = gf16_signbit(val); + } + _ = result; +} + +bench "gf16_sign_latency" { + // Measure: nanoseconds to get sign value + // Target: < 30ns (includes zero/nan/inf checks) + @setEvalBranchQuota(10000); + var result: i8 = 0; + const val: GF16 = 0xBC00; + for (0..1000) |_| { + result = gf16_sign(val); + } + _ = result; +} + +bench "gf16_clamp_latency" { + // Measure: nanoseconds to clamp value to range + // Target: < 200ns (includes decode + compare + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3F00; + const min_val: GF16 = 0x3C00; + const max_val: GF16 = 0x4800; + for (0..1000) |_| { + result = gf16_clamp(x, min_val, max_val); + } + _ = result; +} + +bench "gf16_lerp_latency" { + // Measure: nanoseconds to compute linear interpolation + // Target: < 300ns (includes decode + computation + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3C00; + const b: GF16 = 0x4800; + const t: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_lerp(a, b, t); + } + _ = result; +} + +bench "gf16_fnma_latency" { + // Measure: nanoseconds for fused negative multiply-add + // Target: < 300ns (fused operation) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const a: GF16 = 0x3D00; + const b: GF16 = 0x3C80; + const c: GF16 = 0x3C00; + for (0..1000) |_| { + result = gf16_fnma(a, b, c); + } + _ = result; +} + +bench "gf16_exp_latency" { + // Measure: nanoseconds to compute exponential + // Target: < 500ns (Taylor series) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_exp(x); + } + _ = result; +} + +bench "gf16_log_latency" { + // Measure: nanoseconds to compute natural log + // Target: < 300ns (includes decode + log + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3E00; + for (0..1000) |_| { + result = gf16_log(x); + } + _ = result; +} + +bench "gf16_pow_latency" { + // Measure: nanoseconds to compute power + // Target: < 400ns (includes decode + pow + encode) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const base: GF16 = 0x3D00; + const exp: GF16 = 0x3D80; + for (0..1000) |_| { + result = gf16_pow(base, exp); + } + _ = result; +} + +bench "gf16_sin_latency" { + // Measure: nanoseconds to compute sine + // Target: < 500ns (Taylor series) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_sin(x); + } + _ = result; +} + +bench "gf16_cos_latency" { + // Measure: nanoseconds to compute cosine + // Target: < 500ns (Taylor series) + @setEvalBranchQuota(10000); + var result: GF16 = 0; + const x: GF16 = 0x3D00; + for (0..1000) |_| { + result = gf16_cos(x); + } + _ = result; +} + + + + diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/gf20.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/gf20.t27 new file mode 100644 index 0000000000..5fec680c66 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/gf20.t27 @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf20.t27 +// GoldenFloat20 0 20-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 6 (P1) + +module GF20 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF20 bit layout: [S|EEE EEE|MMM MMMM MMMM MMM] + // S: 1 bit (sign) + // E: 7 bits (exponent) + // M: 12 bits (mantissa) + + const BITS : u8 = 20; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 7; + const MANT_BITS : u8 = 12; + + // Bias for exponent (2^(7-1) - 1 = 63) + const EXP_BIAS : u8 = 63; + + // 141-ratio: exp/mant = 7/12 142 0.583 (phi_distance = 0.035) + const PHI_DISTANCE : f64 = 0.03463264154356299; + + // 143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 + // 2. GoldenFloat20 Type + // 208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 + + struct GF20 { + raw : u32, // 20-bit value stored in u32 + } + + // 281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 + // 3. Encoding/Decoding + // 346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 + + // Encode f32 to GF20 + fn encode(value: f32) -> GF20 { + if (value == 0.0) { + return GF20{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i16; + const exp_biased = (exp_unbiased + EXP_BIAS as i16) as u8; + + // Clamp exponent + const exp_clamped = clamp(exp_biased, 0, (1 << EXP_BITS) - 1); + + // Extract mantissa (12 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF20{ + raw = ((sign as u32) << 19) | + ((exp_clamped as u32) << MANT_BITS) | + (mant as u32) + }; + } + + // Decode GF20 to f32 + fn decode(gf: GF20) -> f32 { + const sign = (gf.raw >> 19) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x7F) as u8; + const mant = (gf.raw & 0xFFF) as u16; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS as i16 + 1 + } else { + (exp_biased as i16) - EXP_BIAS as i16 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 4096.0 + } else { + 1.0 + (mant as f32) / 4096.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 + // 4. Format Properties + // 484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 + + fn max_value() -> f32 { + const mant_max = 1.0 + 4095.0 / 4096.0; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 4096.0; + const exp_min = -EXP_BIAS as i16 + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 4096.0; // 0.00024414 + } + + // 557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 + // 5. Validation + // 622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF20"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759 + // 6. Use Cases + // 760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832 + + // GF20 is optimal for: + // - High-precision ML training + // - Gradient accumulation + // - Scientific computing + // - Near-fp32 quality with 38% memory savings + + // Memory: 20 bits = 2.5 bytes (~1.6x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 20.0 / 32.0; // 0.625 + + // 833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897 + // 7. Helper Functions + // 898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970 + + fn floor_log2(x: f32) -> i16 { + if (x <= 0.0) { return -32768; } + let exp : i16 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i16, mant_bits: u8) -> u16 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1u16 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u16; + } + + fn clamp(x: u8, min: u8, max: u8) -> u8 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF20 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 97197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073 + // TDD-Inside-Spec: Tests and Invariants for GF20 + // 1074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176 + + test gf20_decode_zero + given gf = GF20{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf20_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf20_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf20_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf20_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf20_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf20_phi_distance_within_tolerance + given phi_dist = PHI_DISTANCE + then phi_dist < 0.04 + + test gf20_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.625) < 0.01 + + test gf20_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf20_bits_constant + assert BITS == 20 + + invariant gf20_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf20_exp_bits_is_seven + assert EXP_BITS == 7 + + invariant gf20_mant_bits_is_twelve + assert MANT_BITS == 12 + + invariant gf20_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf20_phi_distance_below_threshold + assert PHI_DISTANCE < 0.04 + + invariant gf20_exp_bias_positive + assert EXP_BIAS > 0 + + test gf20_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf20_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf20_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf20_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf20_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf20_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf20_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf20_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf20_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf20_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf20_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf20_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf20_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf20_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf20_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf20_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf20_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf20_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf20_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf20_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf20_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf20_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf20_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf20_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf20_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf20_encode_latency + measure: nanoseconds to encode(1.0) + target: < 200ns + + bench gf20_decode_latency + measure: nanoseconds to decode(GF20{raw = 524288}) + target: < 150ns +} diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/gf24.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/gf24.t27 new file mode 100644 index 0000000000..ffb9ff37bf --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/gf24.t27 @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf24.t27 +// GoldenFloat24 0 24-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 7 (P1) + +module GF24 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF24 bit layout: [S|EEEE EEEE|MMM MMMM MMMM MMMM MM] + // S: 1 bit (sign) + // E: 9 bits (exponent) + // M: 14 bits (mantissa) + + const BITS : u8 = 24; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 9; + const MANT_BITS : u8 = 14; + + // Bias for exponent (2^(9-1) - 1 = 255) + const EXP_BIAS : u16 = 255; + + // 141-ratio: exp/mant = 9/14 142 0.643 (phi_distance = 0.025) + const PHI_DISTANCE : f64 = 0.02482317991669112; + + // 143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 + // 2. GoldenFloat24 Type + // 208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 + + struct GF24 { + raw : u32, // 24-bit value stored in u32 + } + + // 281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 + // 3. Encoding/Decoding + // 346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 + + // Encode f32 to GF24 + fn encode(value: f32) -> GF24 { + if (value == 0.0) { + return GF24{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i16; + const exp_biased = (exp_unbiased + EXP_BIAS as i16) as u16; + + // Clamp exponent + const exp_clamped = clamp_u16(exp_biased, 0, (1u16 << EXP_BITS) - 1); + + // Extract mantissa (14 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF24{ + raw = ((sign as u32) << 23) | + ((exp_clamped as u32) << MANT_BITS) | + (mant as u32) + }; + } + + // Decode GF24 to f32 + fn decode(gf: GF24) -> f32 { + const sign = (gf.raw >> 23) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x1FF) as u16; + const mant = (gf.raw & 0x3FFF) as u16; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -(EXP_BIAS as i16) + 1 + } else { + (exp_biased as i16) - EXP_BIAS as i16 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 16384.0 + } else { + 1.0 + (mant as f32) / 16384.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 + // 4. Format Properties + // 484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 + + fn max_value() -> f32 { + const mant_max = 1.0 + 16383.0 / 16384.0; + const exp_max = (1i16 << EXP_BITS) - 1 - EXP_BIAS as i16; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 16384.0; + const exp_min = -(EXP_BIAS as i16) + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 16384.0; // 0.000061035 + } + + // 557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 + // 5. Validation + // 622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF24"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759 + // 6. Use Cases + // 760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832 + + // GF24 is optimal for: + // - Very high precision quantization + // - Critical numerical stability + // - Financial calculations + // - 25% memory savings vs FP32 + + // Memory: 24 bits = 3 bytes (~1.33x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 24.0 / 32.0; // 0.75 + + // 833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897 + // 7. Helper Functions + // 898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970 + + fn floor_log2(x: f32) -> i16 { + if (x <= 0.0) { return -32768; } + let exp : i16 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i16, mant_bits: u8) -> u16 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1u16 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u16; + } + + fn clamp_u16(x: u16, min: u16, max: u16) -> u16 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF24 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 97197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073 + // TDD-Inside-Spec: Tests and Invariants for GF24 + // 1074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176 + + test gf24_decode_zero + given gf = GF24{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf24_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf24_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf24_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf24_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf24_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf24_phi_distance_within_tolerance + given phi_dist = PHI_DISTANCE + then phi_dist < 0.03 + + test gf24_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then abs(ratio - 0.75) < 0.01 + + test gf24_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf24_bits_constant + assert BITS == 24 + + invariant gf24_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf24_exp_bits_is_nine + assert EXP_BITS == 9 + + invariant gf24_mant_bits_is_fourteen + assert MANT_BITS == 14 + + invariant gf24_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf24_phi_distance_below_threshold + assert PHI_DISTANCE < 0.03 + + invariant gf24_exp_bias_positive + assert EXP_BIAS > 0 + + test gf24_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf24_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf24_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf24_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf24_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf24_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf24_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf24_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf24_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf24_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf24_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf24_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf24_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf24_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf24_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf24_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf24_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf24_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf24_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf24_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf24_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf24_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf24_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf24_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf24_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf24_encode_latency + measure: nanoseconds to encode(1.0) + target: < 250ns + + bench gf24_decode_latency + measure: nanoseconds to decode(GF24{raw = 8388608}) + target: < 200ns +} diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/gf256.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/gf256.t27 new file mode 100644 index 0000000000..2d2facf950 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/gf256.t27 @@ -0,0 +1,595 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf256.t27 — GoldenFloat256 Encode/Decode +; GF256: 256-bit floating point with 1 sign + 32 exponent + 223 mantissa +; Bit layout: [S(1) E(32) M(223)] = [255:255][254:223][222:0] +; Maximum precision format for scientific computing and simulation +; φ² + 1/φ² = 3 | TRINITY + +module triformat-gf256; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const SIGN_SHIFT : u16 = 255; +pub const EXP_SHIFT : u16 = 223; +pub const MANT_SHIFT : u16 = 0; + +pub const SIGN_BITS : u8 = 1; +pub const EXP_BITS : u8 = 32; +pub const MANT_BITS : u8 = 223; + +pub const EXP_MAX : u32 = 0xFFFFFFFF; // 4294967295 (all ones in 32 bits) +pub const EXP_MIN : u32 = 0x00000000; + +pub const BIAS : i64 = 2147483647; // Exponent bias for GF256: 2^(32-1) - 1 +pub const SPECIAL_EXP : u32 = 0xFFFFFFFF; // All ones = special (Inf/NaN) + +pub const PHI_BIAS : u64 = 67108864; // Phi-optimized rounding bias + +// ============================================================================ +// Types +// ============================================================================ + +// GF256 represented as u256 (simulated via struct since Zig 0.15 doesn't have native u256) +pub struct GF256 { + parts: [4]u64, // parts[0] = LSB, parts[3] = MSB +} + +// ============================================================================ +// Conversion Helpers +// ============================================================================ + +// gf256_zero() -> GF256 +// Returns positive zero +pub fn gf256_zero() GF256 { + return GF256{ .parts = [_]u64{0, 0, 0, 0} }; +} + +// gf256_negative_zero() -> GF256 +// Returns negative zero +pub fn gf256_negative_zero() GF256 { + var result = gf256_zero(); + result.parts[3] |= 0x8000000000000000; // Set MSB of most significant part + return result; +} + +// gf256_inf_positive() -> GF256 +// Returns positive infinity +pub fn gf256_inf_positive() GF256 { + // Exp = all ones (0xFFFFFFFF), mant = 0 + // Exp at bits 254-223, so in parts[3] and parts[2] + return GF256{ + .parts = [_]u64{ + 0x0000000000000000, // parts[0] LSB + 0x0000000000000000, // parts[1] + 0xFFFFFFFF00000000, // parts[2]: exp bits 0-31 | mant bits 32-63 (0) + 0x00000000FFFFFFFF, // parts[3]: exp bits 32-63 | sign (0) + }, + }; +} + +// gf256_inf_negative() -> GF256 +// Returns negative infinity +pub fn gf256_inf_negative() GF256 { + var result = gf256_inf_positive(); + result.parts[3] |= 0x8000000000000000; // Set sign bit + return result; +} + +// gf256_nan() -> GF256 +// Returns NaN +pub fn gf256_nan() GF256 { + var result = gf256_inf_positive(); + result.parts[0] = 1; // Set mant LSB to make it NaN + return result; +} + +// ============================================================================ +// Extract Functions +// ============================================================================ + +// gf256_extract_sign(gf256: GF256) -> i8 +// Extract sign bit (bit 255) +// Returns: 0 for positive, -1 for negative +pub fn gf256_extract_sign(gf256: GF256) i8 { + const bit = (gf256.parts[3] >> 63) & 1; + return if (bit != 0) -1 else 0; +} + +// gf256_extract_exponent(gf256: GF256) -> u32 +// Extract exponent bits (bits 254-223) +// Returns: 0-4294967295 +pub fn gf256_extract_exponent(gf256: GF256) u32 { + // Exponent spans bits 31-0 across parts[3] (bits 63-32) and parts[2] (bits 31-0) + // parts[3] bits 0-31 = exp bits 31-0 + // parts[2] bits 63-32 = exp bits 0-0? No, let's recalculate: + // Total bits: 256 = [S(255)] E(254:223) M(222:0) + // Exp is 32 bits at positions 254-223 + // parts[3] covers bits 255-192 + // parts[2] covers bits 191-128 + // parts[1] covers bits 127-64 + // parts[0] covers bits 63-0 + // Exp bits 254-223 map to: + // bits 254-192 = 62 bits -> parts[3] bits 62-0 (but exp is only 32 bits) + // Wait, exp is at 254:223 = 32 bits + // Bit 254 is in parts[3] at position (254 - 192) = 62 + // Bit 223 is in parts[3] at position (223 - 192) = 31 + // So exp is parts[3] >> 31 & 0xFFFFFFFF + return @as(u32, @truncate(gf256.parts[3] >> 31)); +} + +// gf256_extract_mantissa(gf256: GF256) -> GF256 +// Extract mantissa bits (bits 222-0) +// Returns: GF256 struct with mantissa in parts[0-2] and parts[3] bits 30-0 +pub fn gf256_extract_mantissa(gf256: GF256) GF256 { + var result = gf256; + // Clear sign bit and exponent bits + result.parts[3] &= 0x7FFFFFFF; // Clear sign + result.parts[3] &= 0x00000000; // Clear exp bits 31-0 in parts[3] + return result; +} + +// ============================================================================ +// Assembly Functions +// ============================================================================ + +// gf256_from_components(sign: i8, exp: u32, mant: GF256) -> GF256 +// Assemble GF256 from sign, exponent, mantissa +pub fn gf256_from_components(sign: i8, exp: u32, mant: GF256) GF256 { + var result = mant; + // Set sign + const sign_bit = if (sign < 0) 1 else 0; + if (sign_bit != 0) { + result.parts[3] |= 0x8000000000000000; + } else { + result.parts[3] &= 0x7FFFFFFFFFFFFFFF; + } + // Set exponent (bits 254-223 -> parts[3] bits 62-31) + result.parts[3] = (result.parts[3] & 0x7FFFFFFF) | (@as(u64, exp) << 31); + return result; +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// gf256_is_zero(gf256: GF256) -> bool +// Check if GF256 is zero (positive or negative) +pub fn gf256_is_zero(gf256: GF256) bool { + return gf256.parts[0] == 0 and gf256.parts[1] == 0 and + gf256.parts[2] == 0 and (gf256.parts[3] & 0x7FFFFFFFFFFFFFFF) == 0; +} + +// gf256_is_special(gf256: GF256) -> bool +// Check if GF256 is Inf or NaN (exp == 4294967295) +pub fn gf256_is_special(gf256: GF256) bool { + return gf256_extract_exponent(gf256) == EXP_MAX; +} + +// gf256_is_inf(gf256: GF256) -> bool +// Check if GF256 is infinity (exp == max, mant == 0) +pub fn gf256_is_inf(gf256: GF256) bool { + if (!gf256_is_special(gf256)) { + return false; + } + const mant = gf256_extract_mantissa(gf256); + return mant.parts[0] == 0 and mant.parts[1] == 0 and + mant.parts[2] == 0 and mant.parts[3] == 0; +} + +// gf256_is_nan(gf256: GF256) -> bool +// Check if GF256 is NaN (exp == max, mant != 0) +pub fn gf256_is_nan(gf256: GF256) bool { + if (!gf256_is_special(gf256)) { + return false; + } + const mant = gf256_extract_mantissa(gf256); + return mant.parts[0] != 0 or mant.parts[1] != 0 or + mant.parts[2] != 0 or mant.parts[3] != 0; +} + +// ============================================================================ +// Encode/Decode Functions (via f64 with reduced precision) +// ============================================================================ + +// gf256_encode_f64(f64: f64) -> GF256 +// Encode IEEE 754 double precision to GF256 +// Note: f64 has only 52 mantissa bits, so precision is not fully utilized +pub fn gf256_encode_f64(value: f64) GF256 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) gf256_negative_zero() else gf256_zero(); + } + + // Handle NaN + if (std.math.isNan(value)) { + return gf256_nan(); + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) gf256_inf_negative() else gf256_inf_positive(); + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f64 components + const f64_bits: u64 = @bitCast(abs_value); + var f64_exp: i64 = @as(i64, @intCast((f64_bits >> 52) & 0x7FF)) - 1023; + var f64_mant: u64 = f64_bits & 0x000FFFFFFFFFFFFF; + + // Convert exp from f64 bias (1023) to GF256 bias (2147483647) + var gf256_exp = @as(u32, @intCast(f64_exp + BIAS)); + + // Clamp exponent + if (gf256_exp >= EXP_MAX) { + return if (sign < 0) gf256_inf_negative() else gf256_inf_positive(); + } + + // Create mantissa struct with f64 mantissa in parts[0] + var mant: GF256 = undefined; + mant.parts[0] = f64_mant; + mant.parts[1] = 0; + mant.parts[2] = 0; + mant.parts[3] = 0; + + return gf256_from_components(sign, gf256_exp, mant); +} + +// gf256_decode_f64(gf256: GF256) -> f64 +// Decode GF256 to IEEE 754 double precision +// Note: This loses precision as f64 has only 52 mantissa bits +pub fn gf256_decode_f64(gf256: GF256) f64 { + // Handle zero + if (gf256_is_zero(gf256)) { + return if (gf256_extract_sign(gf256) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (gf256_is_nan(gf256)) { + return std.math.nan(f64); + } + + // Handle Infinity + if (gf256_is_inf(gf256)) { + return if (gf256_extract_sign(gf256) < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + // Extract components + const sign = gf256_extract_sign(gf256); + const exp = gf256_extract_exponent(gf256); + const mant = gf256_extract_mantissa(gf256); + + // Use only first 52 bits of mantissa + const mant_f64 = @as(f64, @floatFromInt(mant.parts[0])) / 4503599627370496.0; + const bias_adjusted = @as(i64, @intCast(exp)) - BIAS; + + if (bias_adjusted < -1022 or bias_adjusted > 1023) { + return if (sign < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + const value = mant_f64 * std.math.pow(f64, 2.0, @as(f64, @floatFromInt(bias_adjusted))); + return if (sign < 0) -value else value; +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// gf256_add(a: GF256, b: GF256) -> GF256 +pub fn gf256_add(a: GF256, b: GF256) GF256 { + return gf256_encode_f64(gf256_decode_f64(a) + gf256_decode_f64(b)); +} + +// gf256_sub(a: GF256, b: GF256) -> GF256 +pub fn gf256_sub(a: GF256, b: GF256) GF256 { + return gf256_encode_f64(gf256_decode_f64(a) - gf256_decode_f64(b)); +} + +// gf256_mul(a: GF256, b: GF256) -> GF256 +pub fn gf256_mul(a: GF256, b: GF256) GF256 { + return gf256_encode_f64(gf256_decode_f64(a) * gf256_decode_f64(b)); +} + +// gf256_div(a: GF256, b: GF256) -> GF256 +pub fn gf256_div(a: GF256, b: GF256) GF256 { + const fb = gf256_decode_f64(b); + if (fb == 0.0) { + const fa = gf256_decode_f64(a); + return if (fa < 0.0) gf256_inf_negative() else gf256_inf_positive(); + } + return gf256_encode_f64(gf256_decode_f64(a) / fb); +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// gf256_abs(gf256: GF256) -> GF256 +pub fn gf256_abs(gf256: GF256) GF256 { + var result = gf256; + result.parts[3] &= 0x7FFFFFFFFFFFFFFF; + return result; +} + +// gf256_neg(gf256: GF256) -> GF256 +pub fn gf256_neg(gf256: GF256) GF256 { + var result = gf256; + result.parts[3] ^= 0x8000000000000000; + return result; +} + +// gf256_is_equal(a: GF256, b: GF256) -> bool +pub fn gf256_is_equal(a: GF256, b: GF256) bool { + if (gf256_is_nan(a) or gf256_is_nan(b)) { + return false; + } + if (gf256_is_zero(a) and gf256_is_zero(b)) { + return true; + } + return a.parts[0] == b.parts[0] and a.parts[1] == b.parts[1] and + a.parts[2] == b.parts[2] and a.parts[3] == b.parts[3]; +} + +// gf256_is_greater(a: GF256, b: GF256) -> bool +pub fn gf256_is_greater(a: GF256, b: GF256) bool { + if (gf256_is_nan(a) or gf256_is_nan(b)) { + return false; + } + const sign_a = gf256_extract_sign(a); + const sign_b = gf256_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; + } + // For negative numbers, reverse comparison + if (sign_a < 0) { + const neg_a = gf256_neg(a); + const neg_b = gf256_neg(b); + return neg_a.parts[3] > neg_b.parts[3] or + (neg_a.parts[3] == neg_b.parts[3] and neg_a.parts[2] > neg_b.parts[2]) or + (neg_a.parts[2] == neg_b.parts[2] and neg_a.parts[1] > neg_b.parts[1]) or + (neg_a.parts[1] == neg_b.parts[1] and neg_a.parts[0] > neg_b.parts[0]); + } + // Positive comparison + return a.parts[3] > b.parts[3] or + (a.parts[3] == b.parts[3] and a.parts[2] > b.parts[2]) or + (a.parts[2] == b.parts[2] and a.parts[1] > b.parts[1]) or + (a.parts[1] == b.parts[1] and a.parts[0] > b.parts[0]); +} + +// gf256_max(a: GF256, b: GF256) -> GF256 +pub fn gf256_max(a: GF256, b: GF256) GF256 { + return if (gf256_is_greater(a, b)) a else b; +} + +// gf256_min(a: GF256, b: GF256) -> GF256 +pub fn gf256_min(a: GF256, b: GF256) GF256 { + return if (gf256_is_greater(a, b)) b else a; +} + +// gf256_clamp(value: GF256, min: GF256, max: GF256) -> GF256 +pub fn gf256_clamp(value: GF256, min: GF256, max: GF256) GF256 { + return gf256_min(gf256_max(value, min), max); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf256_zero_is_zero" { + try std.testing.expect(gf256_is_zero(gf256_zero()) == true); +} + +test "gf256_negative_zero_is_zero" { + try std.testing.expect(gf256_is_zero(gf256_negative_zero()) == true); +} + +test "gf256_nonzero_is_not_zero" { + given value = gf256_encode_f64(1.0) + try std.testing.expect(gf256_is_zero(value) == false); +} + +test "gf256_inf_is_special" { + try std.testing.expect(gf256_is_special(gf256_inf_positive()) == true); +} + +test "gf256_nan_is_special" { + try std.testing.expect(gf256_is_special(gf256_nan()) == true); +} + +test "gf256_inf_positive" { + try std.testing.expect(gf256_is_inf(gf256_inf_positive()) == true); +} + +test "gf256_inf_negative" { + try std.testing.expect(gf256_is_inf(gf256_inf_negative()) == true); +} + +test "gf256_nan" { + try std.testing.expect(gf256_is_nan(gf256_nan()) == true); +} + +test "gf256_inf_not_nan" { + try std.testing.expect(gf256_is_nan(gf256_inf_positive()) == false); +} + +test "gf256_extract_sign_positive" { + given value = gf256_encode_f64(1.0) + try std.testing.expect(sign = gf256_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "gf256_extract_sign_negative" { + given value = gf256_encode_f64(-1.0) + try std.testing.expect(sign = gf256_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "gf256_encode_f64_zero" { + given gf = gf256_encode_f64(0.0) + try std.testing.expect(gf256_is_equal(gf, gf256_zero()) == true); +} + +test "gf256_encode_f64_one" { + given gf = gf256_encode_f64(1.0) + try std.testing.expect(decoded = gf256_decode_f64(gf)); + try std.testing.expect(abs(decoded - 1.0) < 0.0001); +} + +test "gf256_encode_f64_negative_one" { + given gf = gf256_encode_f64(-1.0) + try std.testing.expect(decoded = gf256_decode_f64(gf)); + try std.testing.expect(abs(decoded + 1.0) < 0.0001); +} + +test "gf256_encode_f64_roundtrip" { + given original = 42.5 + try std.testing.expect(gf = gf256_encode_f64(original)); + try std.testing.expect(decoded = gf256_decode_f64(gf)); + try std.testing.expect(abs(decoded - original) < 0.001); +} + +test "gf256_add_simple" { + given a = gf256_encode_f64(1.0) + try std.testing.expect(b = gf256_encode_f64(2.0)); + try std.testing.expect(result = gf256_add(a, b)); + try std.testing.expect(decoded = gf256_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.01); +} + +test "gf256_mul_simple" { + given a = gf256_encode_f64(3.0) + try std.testing.expect(b = gf256_encode_f64(4.0)); + try std.testing.expect(result = gf256_mul(a, b)); + try std.testing.expect(decoded = gf256_decode_f64(result)); + try std.testing.expect(abs(decoded - 12.0) < 0.01); +} + +test "gf256_abs_positive" { + given value = gf256_encode_f64(5.0) + try std.testing.expect(abs_val = gf256_abs(value)); + try std.testing.expect(gf256_is_equal(abs_val, value) == true); +} + +test "gf256_abs_negative" { + given value = gf256_encode_f64(-5.0) + try std.testing.expect(abs_val = gf256_abs(value)); + try std.testing.expect(gf256_extract_sign(abs_val) == 0); +} + +test "gf256_neg_positive" { + given value = gf256_encode_f64(5.0) + try std.testing.expect(neg_val = gf256_neg(value)); + try std.testing.expect(gf256_extract_sign(neg_val) < 0); +} + +test "gf256_neg_negative" { + given value = gf256_encode_f64(-5.0) + try std.testing.expect(neg_val = gf256_neg(value)); + try std.testing.expect(gf256_extract_sign(neg_val) == 0); +} + +test "gf256_is_equal_same" { + given value = gf256_encode_f64(1.0) + try std.testing.expect(gf256_is_equal(value, value) == true); +} + +test "gf256_is_equal_nan" { + try std.testing.expect(gf256_is_equal(gf256_nan(), gf256_nan()) == false); +} + +test "gf256_is_equal_zero" { + try std.testing.expect(gf256_is_equal(gf256_zero(), gf256_negative_zero()) == true); +} + +test "gf256_is_greater_positive" { + given a = gf256_encode_f64(5.0) + try std.testing.expect(b = gf256_encode_f64(3.0)); + try std.testing.expect(gf256_is_greater(a, b) == true); +} + +test "gf256_is_greater_negative" { + given a = gf256_encode_f64(-3.0) + try std.testing.expect(b = gf256_encode_f64(-5.0)); + try std.testing.expect(gf256_is_greater(a, b) == true); +} + +test "gf256_max_returns_larger" { + given a = gf256_encode_f64(3.0) + try std.testing.expect(b = gf256_encode_f64(5.0)); + try std.testing.expect(result = gf256_max(a, b)); + try std.testing.expect(decoded = gf256_decode_f64(result)); + try std.testing.expect(decoded >= 4.0); +} + +test "gf256_min_returns_smaller" { + given a = gf256_encode_f64(3.0) + try std.testing.expect(b = gf256_encode_f64(5.0)); + try std.testing.expect(result = gf256_min(a, b)); + try std.testing.expect(decoded = gf256_decode_f64(result)); + try std.testing.expect(decoded <= 4.0); +} + +test "gf256_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 2147483648); + try std.testing.expect(mant = gf256_zero()); + try std.testing.expect(mant.parts[0] = 0x8000000000000000); + try std.testing.expect(gf = gf256_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = gf256_extract_sign(gf)); + try std.testing.expect(extracted_exp = gf256_extract_exponent(gf)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf256_bits_total + assert SIGN_BITS + EXP_BITS + MANT_BITS == 256 + +invariant gf256_bias_power_of_two_minus_one + assert BIAS + 1 == 2147483648 // 2^31 + +invariant gf256_special_exp_all_ones + assert SPECIAL_EXP == EXP_MAX + +invariant gf256_zero_no_sign_mant + given zero = gf256_zero() + assert zero.parts[0] == 0 and zero.parts[1] == 0 and zero.parts[2] == 0 and (zero.parts[3] & 0x7FFFFFFFFFFFFFFF) == 0 + +invariant gf256_neg_zero_has_sign + given neg_zero = gf256_negative_zero() + assert (neg_zero.parts[3] & 0x8000000000000000) != 0 + +invariant gf256_abs_removes_sign + assert (gf256_abs(gf256_negative_zero()).parts[3] & 0x8000000000000000) == 0 + +invariant gf256_neg_toggles_sign + given pos = gf256_encode_f64(5.0) + try std.testing.expect(neg = gf256_neg(pos)); + assert (pos.parts[3] & 0x8000000000000000) != (neg.parts[3] & 0x8000000000000000) + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf256_extract_sign_latency + measure: nanoseconds to gf256_extract_sign(gf256_encode_f64(1.0)) + target: < 20ns + +bench gf256_encode_f64_latency + measure: nanoseconds to gf256_encode_f64(123.456) + target: < 200ns + +bench gf256_decode_f64_latency + measure: nanoseconds to gf256_decode_f64(gf256_encode_f64(123.456)) + target: < 200ns + +bench gf256_add_latency + measure: nanoseconds to gf256_add(gf256_encode_f64(1.0), gf256_encode_f64(2.0)) + target: < 400ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/gf32.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/gf32.t27 new file mode 100644 index 0000000000..a5e2e14a92 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/gf32.t27 @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf32.t27 +// GoldenFloat32 0 32-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 8 (P1) + +module GF32 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF32 bit layout: [S|EEEE EEEE EEEE|MMM MMMM MMMM MMMM MMMM MMM] + // S: 1 bit (sign) + // E: 12 bits (exponent) + // M: 19 bits (mantissa) + + const BITS : u8 = 32; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 12; + const MANT_BITS : u8 = 19; + + // Bias for exponent (2^(12-1) - 1 = 2047) + const EXP_BIAS : u16 = 2047; + + // 141-ratio: exp/mant = 12/19 142 0.632 (phi_distance = 0.014) + // This is the second-best 143-approximation after GF12 + const PHI_DISTANCE : f64 = 0.01354495894042812; + + // 144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 + // 2. GoldenFloat32 Type + // 209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 + + struct GF32 { + raw : u32, // 32-bit raw value + } + + // 282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 + // 3. Encoding/Decoding + // 347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 + + // Encode f32 to GF32 + fn encode(value: f32) -> GF32 { + if (value == 0.0) { + return GF32{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1u32 } else { 0u32 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i16; + const exp_biased = (exp_unbiased + EXP_BIAS as i16) as u16; + + // Clamp exponent + const exp_clamped = clamp_u16(exp_biased, 0, (1u16 << EXP_BITS) - 1); + + // Extract mantissa (19 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF32{ + raw = (sign << 31) | + ((exp_clamped as u32) << MANT_BITS) | + (mant as u32) + }; + } + + // Decode GF32 to f32 + fn decode(gf: GF32) -> f32 { + const sign = (gf.raw >> 31) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0xFFF) as u16; + const mant = (gf.raw & 0x7FFFF) as u32; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent + const exp_unbiased = if (exp_biased == 0) { + -(EXP_BIAS as i16) + 1 + } else { + (exp_biased as i16) - EXP_BIAS as i16 + }; + + // Mantissa + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 524288.0 + } else { + 1.0 + (mant as f32) / 524288.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 + // 4. Format Properties + // 485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 + + fn max_value() -> f32 { + const mant_max = 1.0 + 524287.0 / 524288.0; + const exp_max = (1i16 << EXP_BITS) - 1 - EXP_BIAS as i16; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + const mant_min = 1.0 / 524288.0; + const exp_min = -(EXP_BIAS as i16) + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + return 1.0 / 524288.0; // 0.000001907 + } + + // 558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 + // 5. Validation + // 623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF32"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760 + // 6. Use Cases + // 761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833 + + // GF32 is optimal for: + // - Near-IEEE 754 precision with 834-optimized layout + // - 12-bit exponent (vs IEEE's 8-bit) for wider dynamic range + // - 19-bit mantissa (vs IEEE's 23-bit) - still good precision + // - Same memory footprint as FP32, better 835-ratio + + // Comparison with IEEE FP32: + // - IEEE: 1 sign, 8 exp, 23 mant 836 exp/mant = 0.348 (phi_distance = 0.270) + // - GF32: 1 sign, 12 exp, 19 mant 837 exp/mant = 0.632 (phi_distance = 0.014) + + // Memory: 32 bits = 4 bytes (same as FP32) + const MEMORY_RATIO_VS_FP32 : f32 = 1.0; + + // 838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902 + // 7. Helper Functions + // 903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975 + + fn floor_log2(x: f32) -> i16 { + if (x <= 0.0) { return -32768; } + let exp : i16 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i16, mant_bits: u8) -> u32 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1u32 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u32; + } + + fn clamp_u16(x: u16, min: u16, max: u16) -> u16 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF32 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 9769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078 + // TDD-Inside-Spec: Tests and Invariants for GF32 + // 1079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181 + + test gf32_decode_zero + given gf = GF32{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf32_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf32_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf32_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf32_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf32_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf32_phi_distance_near_optimal + given phi_dist = PHI_DISTANCE + then phi_dist < 0.015 + + test gf32_memory_ratio_equals_one + given ratio = MEMORY_RATIO_VS_FP32 + then ratio == 1.0 + + test gf32_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf32_bits_constant + assert BITS == 32 + + invariant gf32_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf32_exp_bits_is_twelve + assert EXP_BITS == 12 + + invariant gf32_mant_bits_is_nineteen + assert MANT_BITS == 19 + + invariant gf32_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf32_phi_distance_near_optimal + assert PHI_DISTANCE < 0.015 + + invariant gf32_exp_bias_positive + assert EXP_BIAS > 0 + + invariant gf32_exp_wider_than_ieee + assert EXP_BITS > 8 // IEEE FP32 has 8-bit exponent + + invariant gf32_mant_narrower_than_ieee + assert MANT_BITS < 23 // IEEE FP32 has 23-bit mantissa + + test gf32_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf32_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf32_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf32_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf32_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf32_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf32_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf32_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf32_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf32_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf32_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf32_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf32_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf32_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf32_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf32_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf32_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf32_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf32_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf32_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf32_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf32_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf32_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf32_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf32_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf32_encode_latency + measure: nanoseconds to encode(1.0) + target: < 300ns + + bench gf32_decode_latency + measure: nanoseconds to decode(GF32{raw = 1065353216}) + target: < 250ns +} diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/gf4.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/gf4.t27 new file mode 100644 index 0000000000..499b74e34f --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/gf4.t27 @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf4.t27 +// GoldenFloat4 0 4-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 2 (P1) + +module GF4 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF4 bit layout: [S|E|MM] + // S: 1 bit (sign) + // E: 1 bit (exponent) + // M: 2 bits (mantissa) + + const BITS : u8 = 4; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 1; + const MANT_BITS : u8 = 2; + + // Bias for exponent (0-biased for GF4) + const EXP_BIAS : u8 = 0; + + // 141-ratio: exp/mant = 1/2 = 0.5 (phi_distance = 0.118) + const PHI_DISTANCE : f64 = 0.1180339887498949; + + // 142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 + // 2. GoldenFloat4 Type + // 207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 + + struct GF4 { + raw : u4, // 4-bit raw value + } + + // 280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 + // 3. Encoding/Decoding + // 345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 + + // Encode f32 to GF4 + fn encode(value: f32) -> GF4 { + // Special cases + if (value == 0.0) { + return GF4{ raw = 0b0000 }; + } + if (value < 0.0) { + const pos = encode(-value).raw; + return GF4{ raw = pos | 0b1000 }; // Set sign bit + } + + // For GF4, quantize to available values + // Available positive values (mant * exp_scale): + // mant=0.00, exp=1.0 418 0.00 + // mant=0.25, exp=1.0 419 0.25 + // mant=0.50, exp=1.0 420 0.50 + // mant=0.75, exp=1.0 421 0.75 + // mant=0.00, exp=2.0 422 0.00 + // mant=0.25, exp=2.0 423 0.50 + // mant=0.50, exp=2.0 424 1.00 + // mant=0.75, exp=2.0 425 1.50 + + // Unique positive non-zero values: 0.25, 0.5, 0.75, 1.0, 1.5 + + if (value <= 0.375) { + // 0.25 + return GF4{ raw = 0b0001 }; + } else if (value <= 0.625) { + // 0.5 + return GF4{ raw = 0b0010 }; + } else if (value <= 0.875) { + // 0.75 + return GF4{ raw = 0b0011 }; + } else if (value <= 1.25) { + // 1.0 + return GF4{ raw = 0b0101 }; + } else { + // 1.5 (max) + return GF4{ raw = 0b0111 }; + } + } + + // Decode GF4 to f32 + fn decode(gf: GF4) -> f32 { + const sign_bit = (gf.raw & 0b1000) != 0; + const exp_bit = (gf.raw & 0b0100) != 0; + const mant_bits = gf.raw & 0b0011; + + // Zero + if (gf.raw == 0) { + return 0.0; + } + + // Decode mantissa (2 bits 426 values 0, 0.25, 0.5, 0.75) + const mant = (mant_bits as f32) / 4.0; + + // Decode exponent (1 bit 427 1.0 or 2.0) + const exp_scale = if (exp_bit) { 2.0 } else { 1.0 }; + + const value = mant * exp_scale; + + if (sign_bit) { + return -value; + } + return value; + } + + // 428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 + // 4. Format Properties + // 493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565 + + fn max_value() -> f32 { + // Max: mant=0.75, exp=2.0 566 1.5 + return 1.5; + } + + fn min_positive() -> f32 { + // Min positive: mant=0.25, exp=1.0 567 0.25 + return 0.25; + } + + fn epsilon() -> f32 { + // Smallest representable difference at 1.0 + return 0.25; + } + + // 568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632 + // 5. Validation + // 633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705 + + fn validate_format() -> bool { + // Check that we match the goldenfloat_family definition + const fmt = goldenfloat_family::get_format_by_name("GF4"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770 + // 6. Use Cases + // 771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843 + + // GF4 is optimal for: + // - Extreme compression (87.5% smaller than FP32) + // - Binary/ternary classification + // - Attention masks + // - Activation sparsity indicators + + // Memory: 4 bits = 0.5 bytes (8x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 4.0 / 32.0; // 0.125 + + // 844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946 + // TDD-Inside-Spec: Tests and Invariants for GF4 + // 94794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049 + + test gf4_decode_zero + given gf = GF4{ raw = 0b0000 } + when value = decode(gf) + then value == 0.0 + + test gf4_decode_positive_max + given gf = GF4{ raw = 0b0111 } + when value = decode(gf) + then value == 1.5 + + test gf4_decode_negative + given gf = GF4{ raw = 0b1001 } + when value = decode(gf) + then value < 0.0 + + test gf4_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf4_encode_0_25 + given original = 0.25 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.25) < 0.01 + + test gf4_encode_0_5 + given original = 0.5 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.5) < 0.01 + + test gf4_encode_0_75 + given original = 0.75 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.75) < 0.01 + + test gf4_encode_1_0 + given original = 1.0 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 1.0) < 0.01 + + test gf4_encode_1_5 + given original = 1.5 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 1.5) < 0.01 + + test gf4_encode_negative_values + given original = -0.5 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded < 0.0 and abs(decoded - (-0.5)) < 0.01 + + test gf4_encode_clamps_to_max + given original = 10.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded <= 1.5 + + test gf4_encode_quantization_small + given original = 0.3 + and encoded = encode(original) + and decoded = decode(encoded) + then abs(decoded - 0.25) < 0.01 + + test gf4_max_value_is_1_5 + given max_val = max_value() + then max_val == 1.5 + + test gf4_min_positive_is_0_25 + given min_pos = min_positive() + then min_pos == 0.25 + + test gf4_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf4_exp_mant_ratio_matches_phi_split + given ratio = (EXP_BITS as f64) / (MANT_BITS as f64) + and expected = 0.5 + then abs(ratio - expected) < 0.01 + + test gf4_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then ratio == 0.125 + + test gf4_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf4_bits_constant + assert BITS == 4 + + invariant gf4_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf4_exp_bits_is_one + assert EXP_BITS == 1 + + invariant gf4_mant_bits_is_two + assert MANT_BITS == 2 + + invariant gf4_max_value_positive + assert max_value() > 0.0 + + invariant gf4_min_positive_greater_than_zero + assert min_positive() > 0.0 + + invariant gf4_epsilon_positive + assert epsilon() > 0.0 + + invariant gf4_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf4_phi_distance_within_tolerance + assert PHI_DISTANCE < 0.12 + + invariant gf4_encode_decode_roundtrip + given encoded = encode(x) for x in {0.25, 0.5, 0.75, 1.0, 1.5} + when decoded = decode(encoded) + then abs(decoded - x) < 0.01 + + invariant gf4_encode_zero_returns_zero + assert encode(0.0).raw == 0b0000 + + invariant gf4_encode_positive_no_sign_bit + given result = encode(1.0) + when has_sign = (result.raw & 0b1000) != 0 + then has_sign == false + + invariant gf4_encode_negative_has_sign_bit + given result = encode(-1.0) + when has_sign = (result.raw & 0b1000) != 0 + then has_sign == true + + bench gf4_encode_latency + measure: nanoseconds to encode(1.0) + target: < 100ns + + bench gf4_decode_latency + measure: nanoseconds to decode(GF4{raw = 0b0101}) + target: < 50ns +} diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/gf64.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/gf64.t27 new file mode 100644 index 0000000000..cb86276ee1 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/gf64.t27 @@ -0,0 +1,764 @@ +// SPDX-License-Identifier: Apache-2.0 +; gf64.t27 — GoldenFloat64 Encode/Decode +; GF64: 64-bit floating point with 1 sign + 18 exponent + 45 mantissa +; Bit layout: [S(1) E(18) M(45)] = [63:63][62:45][44:0] +; φ² + 1/φ² = 3 | TRINITY + +module triformat-gf64; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const SIGN_SHIFT : u8 = 63; +pub const EXP_SHIFT : u8 = 45; +pub const MANT_SHIFT : u8 = 0; + +pub const SIGN_MASK : u64 = 0x8000000000000000; // 1 << 63 +pub const EXP_MASK : u64 = 0x7FFF800000000000; // 0b111111111111111111 << 45 +pub const MANT_MASK : u64 = 0x00007FFFFFFFFFFF; // 0b111111111111111111111111111111111111111111111 + +pub const EXP_MAX : u16 = 0x3FFFF; // 262143 (all ones in 18 bits) +pub const EXP_MIN : u16 = 0x0000; + +pub const BIAS : i16 = 131071; // Exponent bias for GF64: 2^(18-1) - 1 +pub const SPECIAL_EXP : u16 = 0x3FFFF; // All ones = special (Inf/NaN) + +pub const MANT_DIVISOR : u64 = 35184372088832; // 2^45 +pub const MANT_DIVISOR_SHIFT : u8 = 45; + +pub const PHI_BIAS : u32 = 245760; // Phi-optimized rounding bias + +// GF64 special values +pub const GF64_ZERO_POS : u64 = 0x0000000000000000; +pub const GF64_ZERO_NEG : u64 = 0x8000000000000000; +pub const GF64_INF_POS : u64 = 0x7FFF800000000000; +pub const GF64_INF_NEG : u64 = 0xFFFF800000000000; +pub const GF64_NAN : u64 = 0xFFFF800000000001; // Sign + all exp + mantissa != 0 + +// ============================================================================ +// Types +// ============================================================================ + +pub const GF64 = u64; + +// ============================================================================ +// Functions +// ============================================================================ + +// gf64_extract_sign(gf64: GF64) -> i8 +// Extract sign bit (bit 63) +// Returns: 0 for positive, -1 for negative +pub fn gf64_extract_sign(gf64: GF64) i8 { + const bit = (gf64 >> SIGN_SHIFT) & 1; + return if (bit != 0) -1 else 0; +} + +// gf64_extract_exponent(gf64: GF64) -> i16 +// Extract exponent bits (bits 62-45) +// Returns: 0-262143 +pub fn gf64_extract_exponent(gf64: GF64) i16 { + return @as(i16, @intCast((gf64 >> EXP_SHIFT) & (EXP_MAX))); +} + +// gf64_extract_mantissa(gf64: GF64) -> u64 +// Extract mantissa bits (bits 44-0) +// Returns: 0-2^45-1 +pub fn gf64_extract_mantissa(gf64: GF64) u64 { + return gf64 & MANT_MASK; +} + +// gf64_from_components(sign: i8, exp: i16, mant: u64) -> GF64 +// Assemble GF64 from sign, exponent, mantissa +pub fn gf64_from_components(sign: i8, exp: i16, mant: u64) GF64 { + const sign_bit = if (sign < 0) 1 else 0; + return (@as(GF64, @intCast(sign_bit)) << SIGN_SHIFT) | + (@as(GF64, @intCast(exp & EXP_MAX)) << EXP_SHIFT) | + (mant & MANT_MASK); +} + +// gf64_is_zero(gf64: GF64) -> bool +// Check if GF64 is zero (positive or negative) +pub fn gf64_is_zero(gf64: GF64) bool { + return gf64 == GF64_ZERO_POS or gf64 == GF64_ZERO_NEG; +} + +// gf64_is_special(gf64: GF64) -> bool +// Check if GF64 is Inf or NaN (exp == 262143) +pub fn gf64_is_special(gf64: GF64) bool { + return gf64_extract_exponent(gf64) == @as(i16, @intCast(EXP_MAX)); +} + +// gf64_is_inf(gf64: GF64) -> bool +// Check if GF64 is infinity (exp == max, mant == 0) +pub fn gf64_is_inf(gf64: GF64) bool { + const exp = gf64_extract_exponent(gf64); + const mant = gf64_extract_mantissa(gf64); + return exp == @as(i16, @intCast(EXP_MAX)) and mant == 0; +} + +// gf64_is_nan(gf64: GF64) -> bool +// Check if GF64 is NaN (exp == max, mant != 0) +pub fn gf64_is_nan(gf64: GF64) bool { + const exp = gf64_extract_exponent(gf64); + const mant = gf64_extract_mantissa(gf64); + return exp == @as(i16, @intCast(EXP_MAX)) and mant != 0; +} + +// gf64_encode_f64(f64: f64) -> GF64 +// Encode IEEE 754 double precision to GF64 +// Round-to-nearest, ties to even +pub fn gf64_encode_f64(value: f64) GF64 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) GF64_ZERO_NEG else GF64_ZERO_POS; + } + + // Handle NaN + if (std.math.isNan(value)) { + return GF64_NAN; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) GF64_INF_NEG else GF64_INF_POS; + } + + // Extract sign + const sign = if (value < 0.0) -1 else 0; + const abs_value = if (value < 0.0) -value else value; + + // Get f64 components + const f64_bits: u64 = @bitCast(abs_value); + var f64_exp: i16 = @intCast((f64_bits >> 52) & 0x7FF) - 1023; + var f64_mant: u64 = f64_bits & 0x000FFFFFFFFFFFFF; + + // Convert exp from f64 bias (1023) to GF64 bias (131071) + var gf64_exp = f64_exp + BIAS; + + // Clamp exponent + if (gf64_exp < 0) { + // Underflow - could return subnormal, but flushing to zero for simplicity + return if (sign < 0) GF64_ZERO_NEG else GF64_ZERO_POS; + } else if (gf64_exp > @as(i16, @intCast(EXP_MAX - 1))) { + return if (sign < 0) GF64_INF_NEG else GF64_INF_POS; + } + + // Extract mantissa and scale to 45 bits + // f64 mantissa is 52 bits, GF64 needs 45 bits + // Shift right by 7 bits (52 - 45 = 7) + var mant = f64_mant >> 7; + + // Round-to-nearest with ties to even + const discarded = f64_mant & 0x7F; + if ((discarded & 0x40) != 0) { + // Round up + if ((discarded & 0x3F) != 0 or (mant & 1) != 0) { + mant += 1; + if (mant > MANT_MASK) { + mant = 0; + if (gf64_exp < @as(i16, @intCast(EXP_MAX - 1))) { + gf64_exp += 1; + } + } + } + } + + return gf64_from_components(sign, gf64_exp, mant); +} + +// gf64_decode_f64(gf64: GF64) -> f64 +// Decode GF64 to IEEE 754 double precision +pub fn gf64_decode_f64(gf64: GF64) f64 { + // Handle zero + if (gf64_is_zero(gf64)) { + return if (gf64_extract_sign(gf64) < 0) -0.0 else 0.0; + } + + // Handle NaN + if (gf64_is_nan(gf64)) { + return std.math.nan(f64); + } + + // Handle Infinity + if (gf64_is_inf(gf64)) { + return if (gf64_extract_sign(gf64) < 0) -std.math.inf(f64) else std.math.inf(f64); + } + + // Extract components + const sign = gf64_extract_sign(gf64); + const exp = gf64_extract_exponent(gf64); + const mant = gf64_extract_mantissa(gf64); + + // Decode value: mantissa * 2^(exp - bias) + const bias_adjusted = @as(i64, exp) - @as(i64, BIAS); + const mant_f64 = @as(f64, @floatFromInt(mant)) / @as(f64, @floatFromInt(MANT_DIVISOR)); + const value = mant_f64 * std.math.pow(f64, 2.0, @as(f64, @floatFromInt(bias_adjusted))); + + return if (sign < 0) -value else value; +} + +// gf64_add(a: GF64, b: GF64) -> GF64 +// Add two GF64 values +pub fn gf64_add(a: GF64, b: GF64) GF64 { + const fa = gf64_decode_f64(a); + const fb = gf64_decode_f64(b); + return gf64_encode_f64(fa + fb); +} + +// gf64_sub(a: GF64, b: GF64) -> GF64 +// Subtract two GF64 values +pub fn gf64_sub(a: GF64, b: GF64) GF64 { + const fa = gf64_decode_f64(a); + const fb = gf64_decode_f64(b); + return gf64_encode_f64(fa - fb); +} + +// gf64_mul(a: GF64, b: GF64) -> GF64 +// Multiply two GF64 values +pub fn gf64_mul(a: GF64, b: GF64) GF64 { + const fa = gf64_decode_f64(a); + const fb = gf64_decode_f64(b); + return gf64_encode_f64(fa * fb); +} + +// gf64_div(a: GF64, b: GF64) -> GF64 +// Divide two GF64 values +pub fn gf64_div(a: GF64, b: GF64) GF64 { + const fb = gf64_decode_f64(b); + if (fb == 0.0) { + const fa = gf64_decode_f64(a); + return if (fa < 0.0) GF64_INF_NEG else GF64_INF_POS; + } + const fa = gf64_decode_f64(a); + return gf64_encode_f64(fa / fb); +} + +// gf64_abs(gf64: GF64) -> GF64 +// Absolute value of GF64 +pub fn gf64_abs(gf64: GF64) GF64 { + return gf64 & ~SIGN_MASK; +} + +// gf64_neg(gf64: GF64) -> GF64 +// Negate GF64 +pub fn gf64_neg(gf64: GF64) GF64 { + return gf64 ^ SIGN_MASK; +} + +// gf64_is_equal(a: GF64, b: GF64) -> bool +// Check if two GF64 values are equal +pub fn gf64_is_equal(a: GF64, b: GF64) bool { + // Handle NaN: NaN != NaN + if (gf64_is_nan(a) or gf64_is_nan(b)) { + return false; + } + // Handle zero: +0 == -0 + if (gf64_is_zero(a) and gf64_is_zero(b)) { + return true; + } + return a == b; +} + +// gf64_is_greater(a: GF64, b: GF64) -> bool +// Check if a > b +pub fn gf64_is_greater(a: GF64, b: GF64) bool { + if (gf64_is_nan(a) or gf64_is_nan(b)) { + return false; + } + // Extract signs for comparison + const sign_a = gf64_extract_sign(a); + const sign_b = gf64_extract_sign(b); + if (sign_a != sign_b) { + return sign_a > sign_b; // negative < positive + } + // Same sign: compare as unsigned (positive case) or reversed (negative case) + if (sign_a < 0) { + return (gf64_neg(a) > gf64_neg(b)); + } + return a > b; +} + +// gf64_max(a: GF64, b: GF64) -> GF64 +// Return the larger of two GF64 values +pub fn gf64_max(a: GF64, b: GF64) GF64 { + return if (gf64_is_greater(a, b)) a else b; +} + +// gf64_min(a: GF64, b: GF64) -> GF64 +// Return the smaller of two GF64 values +pub fn gf64_min(a: GF64, b: GF64) GF64 { + return if (gf64_is_greater(a, b)) b else a; +} + +// gf64_clamp(value: GF64, min: GF64, max: GF64) -> GF64 +// Clamp value between min and max +pub fn gf64_clamp(value: GF64, min: GF64, max: GF64) GF64 { + return gf64_min(gf64_max(value, min), max); +} + +// gf64_lerp(a: GF64, b: GF64, t: GF64) -> GF64 +// Linear interpolation between a and b +pub fn gf64_lerp(a: GF64, b: GF64, t: GF64) GF64 { + const ft = gf64_decode_f64(t); + const result = gf64_decode_f64(a) * (1.0 - ft) + gf64_decode_f64(b) * ft; + return gf64_encode_f64(result); +} + +// gf64_from_u32(value: u32) -> GF64 +// Convert unsigned 32-bit integer to GF64 +pub fn gf64_from_u32(value: u32) GF64 { + return gf64_encode_f64(@as(f64, @floatFromInt(value))); +} + +// gf64_to_u32(gf64: GF64) -> ?u32 +// Convert GF64 to unsigned 32-bit integer +// Returns null if value is NaN, Infinity, or out of range +pub fn gf64_to_u32(gf64: GF64) ?u32 { + if (gf64_is_nan(gf64) or gf64_is_inf(gf64)) { + return null; + } + const value = gf64_decode_f64(gf64); + if (value < 0.0 or value > @as(f64, @floatFromInt(std.math.maxInt(u32)))) { + return null; + } + return @as(u32, @intFromFloat(value)); +} + +// gf64_from_i32(value: i32) -> GF64 +// Convert signed 32-bit integer to GF64 +pub fn gf64_from_i32(value: i32) GF64 { + return gf64_encode_f64(@as(f64, @floatFromInt(value))); +} + +// gf64_to_i32(gf64: GF64) -> ?i32 +// Convert GF64 to signed 32-bit integer +// Returns null if value is NaN, Infinity, or out of range +pub fn gf64_to_i32(gf64: GF64) ?i32 { + if (gf64_is_nan(gf64) or gf64_is_inf(gf64)) { + return null; + } + const value = gf64_decode_f64(gf64); + if (value < @as(f64, @floatFromInt(std.math.minInt(i32))) or + value > @as(f64, @floatFromInt(std.math.maxInt(i32)))) { + return null; + } + return @as(i32, @intFromFloat(value)); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "gf64_extract_sign_positive" { + given value = 0x0000000000001234 + try std.testing.expect(sign = gf64_extract_sign(value)); + try std.testing.expect(sign == 0); +} + +test "gf64_extract_sign_negative" { + given value = 0x8000000000001234 + try std.testing.expect(sign = gf64_extract_sign(value)); + try std.testing.expect(sign == -1); +} + +test "gf64_extract_exponent_middle" { + given value = 0x0000800000000000 // exp = 131072 + try std.testing.expect(exp = gf64_extract_exponent(value)); + try std.testing.expect(exp == 131072); +} + +test "gf64_extract_mantissa_max" { + given value = 0x00007FFFFFFFFFFF + try std.testing.expect(mant = gf64_extract_mantissa(value)); + try std.testing.expect(mant == 0x00007FFFFFFFFFFF); +} + +test "gf64_is_zero_positive" { + try std.testing.expect(gf64_is_zero(GF64_ZERO_POS) == true); +} + +test "gf64_is_zero_negative" { + try std.testing.expect(gf64_is_zero(GF64_ZERO_NEG) == true); +} + +test "gf64_is_zero_nonzero" { + try std.testing.expect(gf64_is_zero(0x0000000000000001) == false); +} + +test "gf64_is_special_inf" { + try std.testing.expect(gf64_is_special(GF64_INF_POS) == true); +} + +test "gf64_is_special_nan" { + try std.testing.expect(gf64_is_special(GF64_NAN) == true); +} + +test "gf64_is_special_normal" { + try std.testing.expect(gf64_is_special(0x0000000000001234) == false); +} + +test "gf64_is_inf_positive" { + try std.testing.expect(gf64_is_inf(GF64_INF_POS) == true); +} + +test "gf64_is_inf_negative" { + try std.testing.expect(gf64_is_inf(GF64_INF_NEG) == true); +} + +test "gf64_is_inf_not_nan" { + try std.testing.expect(gf64_is_inf(GF64_NAN) == false); +} + +test "gf64_is_nan" { + try std.testing.expect(gf64_is_nan(GF64_NAN) == true); +} + +test "gf64_is_nan_not_inf" { + try std.testing.expect(gf64_is_nan(GF64_INF_POS) == false); +} + +test "gf64_encode_f64_zero" { + given gf = gf64_encode_f64(0.0) + try std.testing.expect(gf == GF64_ZERO_POS); +} + +test "gf64_encode_f64_negative_zero" { + given gf = gf64_encode_f64(-0.0) + try std.testing.expect(gf == GF64_ZERO_NEG); +} + +test "gf64_encode_f64_one" { + given gf = gf64_encode_f64(1.0) + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - 1.0) < 0.0001); +} + +test "gf64_encode_f64_negative_one" { + given gf = gf64_encode_f64(-1.0) + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded + 1.0) < 0.0001); +} + +test "gf64_encode_f64_two_pow_ten" { + given gf = gf64_encode_f64(1024.0) + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - 1024.0) < 1.0); +} + +test "gf64_encode_f64_pi" { + given gf = gf64_encode_f64(std.math.pi) + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - std.math.pi) < 0.001); +} + +test "gf64_decode_f64_roundtrip_positive" { + given original = 42.5 + try std.testing.expect(gf = gf64_encode_f64(original)); + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - original) < 0.001); +} + +test "gf64_decode_f64_roundtrip_negative" { + given original = -17.75 + try std.testing.expect(gf = gf64_encode_f64(original)); + try std.testing.expect(decoded = gf64_decode_f64(gf)); + try std.testing.expect(abs(decoded - original) < 0.001); +} + +test "gf64_add_simple" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(2.0)); + try std.testing.expect(result = gf64_add(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.01); +} + +test "gf64_sub_simple" { + given a = gf64_encode_f64(5.0) + try std.testing.expect(b = gf64_encode_f64(3.0)); + try std.testing.expect(result = gf64_sub(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.01); +} + +test "gf64_mul_simple" { + given a = gf64_encode_f64(3.0) + try std.testing.expect(b = gf64_encode_f64(4.0)); + try std.testing.expect(result = gf64_mul(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 12.0) < 0.01); +} + +test "gf64_div_simple" { + given a = gf64_encode_f64(12.0) + try std.testing.expect(b = gf64_encode_f64(4.0)); + try std.testing.expect(result = gf64_div(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.01); +} + +test "gf64_abs_positive" { + given value = gf64_encode_f64(5.0) + try std.testing.expect(abs_val = gf64_abs(value)); + try std.testing.expect(abs_val == value); +} + +test "gf64_abs_negative" { + given value = gf64_encode_f64(-5.0) + try std.testing.expect(abs_val = gf64_abs(value)); + try std.testing.expect(decoded = gf64_decode_f64(abs_val)); + try std.testing.expect(decoded > 0.0); +} + +test "gf64_neg" { + given value = gf64_encode_f64(5.0) + try std.testing.expect(neg_val = gf64_neg(value)); + try std.testing.expect(decoded = gf64_decode_f64(neg_val)); + try std.testing.expect(abs(decoded + 5.0) < 0.01); +} + +test "gf64_is_equal_same" { + given value = gf64_encode_f64(1.0) + try std.testing.expect(gf64_is_equal(value, value) == true); +} + +test "gf64_is_equal_different" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(2.0)); + try std.testing.expect(gf64_is_equal(a, b) == false); +} + +test "gf64_is_equal_nan" { + try std.testing.expect(gf64_is_equal(GF64_NAN, GF64_NAN) == false); +} + +test "gf64_is_equal_zero" { + try std.testing.expect(gf64_is_equal(GF64_ZERO_POS, GF64_ZERO_NEG) == true); +} + +test "gf64_is_greater_positive" { + given a = gf64_encode_f64(5.0) + try std.testing.expect(b = gf64_encode_f64(3.0)); + try std.testing.expect(gf64_is_greater(a, b) == true); +} + +test "gf64_is_greater_negative" { + given a = gf64_encode_f64(-3.0) + try std.testing.expect(b = gf64_encode_f64(-5.0)); + try std.testing.expect(gf64_is_greater(a, b) == true); +} + +test "gf64_is_greater_positive_vs_negative" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(-1.0)); + try std.testing.expect(gf64_is_greater(a, b) == true); +} + +test "gf64_max_returns_larger" { + given a = gf64_encode_f64(3.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(result = gf64_max(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(decoded >= 4.0); +} + +test "gf64_min_returns_smaller" { + given a = gf64_encode_f64(3.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(result = gf64_min(a, b)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(decoded <= 4.0); +} + +test "gf64_clamp_within_range" { + given value = gf64_encode_f64(3.0) + try std.testing.expect(min = gf64_encode_f64(0.0)); + try std.testing.expect(max = gf64_encode_f64(10.0)); + try std.testing.expect(result = gf64_clamp(value, min, max)); + try std.testing.expect(result == value); +} + +test "gf64_clamp_below_min" { + given value = gf64_encode_f64(-5.0) + try std.testing.expect(min = gf64_encode_f64(0.0)); + try std.testing.expect(max = gf64_encode_f64(10.0)); + try std.testing.expect(result = gf64_clamp(value, min, max)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(decoded >= 0.0); +} + +test "gf64_clamp_above_max" { + given value = gf64_encode_f64(15.0) + try std.testing.expect(min = gf64_encode_f64(0.0)); + try std.testing.expect(max = gf64_encode_f64(10.0)); + try std.testing.expect(result = gf64_clamp(value, min, max)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(decoded <= 10.0); +} + +test "gf64_lerp_zero" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(t = gf64_encode_f64(0.0)); + try std.testing.expect(result = gf64_lerp(a, b, t)); + try std.testing.expect(gf64_is_equal(result, a) == true); +} + +test "gf64_lerp_one" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(t = gf64_encode_f64(1.0)); + try std.testing.expect(result = gf64_lerp(a, b, t)); + try std.testing.expect(gf64_is_equal(result, b) == true); +} + +test "gf64_lerp_half" { + given a = gf64_encode_f64(1.0) + try std.testing.expect(b = gf64_encode_f64(5.0)); + try std.testing.expect(t = gf64_encode_f64(0.5)); + try std.testing.expect(result = gf64_lerp(a, b, t)); + try std.testing.expect(decoded = gf64_decode_f64(result)); + try std.testing.expect(abs(decoded - 3.0) < 0.1); +} + +test "gf64_from_u32_zero" { + given value = 0 + try std.testing.expect(gf = gf64_from_u32(value)); + try std.testing.expect(gf == GF64_ZERO_POS); +} + +test "gf64_from_u32_max" { + given value = std.math.maxInt(u32) + try std.testing.expect(gf = gf64_from_u32(value)); + try std.testing.expect(decoded = gf64_to_u32(gf)); + try std.testing.expect(decoded.? == value); +} + +test "gf64_to_u32_success" { + given gf = gf64_encode_f64(123.0) + try std.testing.expect(result = gf64_to_u32(gf)); + try std.testing.expect(result.? == 123); +} + +test "gf64_to_u32_negative" { + given gf = gf64_encode_f64(-1.0) + try std.testing.expect(result = gf64_to_u32(gf)); + try std.testing.expect(result == null); +} + +test "gf64_to_u32_nan" { + given result = gf64_to_u32(GF64_NAN) + try std.testing.expect(result == null); +} + +test "gf64_from_i32_zero" { + given value = 0 + try std.testing.expect(gf = gf64_from_i32(value)); + try std.testing.expect(gf == GF64_ZERO_POS); +} + +test "gf64_from_i32_negative" { + given value = -42 + try std.testing.expect(gf = gf64_from_i32(value)); + try std.testing.expect(decoded = gf64_to_i32(gf)); + try std.testing.expect(decoded.? == value); +} + +test "gf64_to_i32_positive" { + given gf = gf64_encode_f64(123.0) + try std.testing.expect(result = gf64_to_i32(gf)); + try std.testing.expect(result.? == 123); +} + +test "gf64_to_i32_negative" { + given gf = gf64_encode_f64(-123.0) + try std.testing.expect(result = gf64_to_i32(gf)); + try std.testing.expect(result.? == -123); +} + +test "gf64_from_components_roundtrip" { + given sign = -1 + try std.testing.expect(exp = 131072); + try std.testing.expect(mant = 0x0000000000000800); + try std.testing.expect(gf = gf64_from_components(sign, exp, mant)); + try std.testing.expect(extracted_sign = gf64_extract_sign(gf)); + try std.testing.expect(extracted_exp = gf64_extract_exponent(gf)); + try std.testing.expect(extracted_mant = gf64_extract_mantissa(gf)); + try std.testing.expect(extracted_sign == sign and extracted_exp == exp and extracted_mant == mant); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant gf64_sign_mask_single_bit + assert (SIGN_MASK & (SIGN_MASK - 1)) == 0 + +invariant gf64_exp_mask_no_sign + assert (EXP_MASK & SIGN_MASK) == 0 + +invariant gf64_mant_mask_no_sign_exp + assert (MANT_MASK & (SIGN_MASK | EXP_MASK)) == 0 + +invariant gf64_masks_cover_all_bits + assert (SIGN_MASK | EXP_MASK | MANT_MASK) == 0xFFFFFFFFFFFFFFFF + +invariant gf64_bias_power_of_two_minus_one + assert BIAS + 1 == 131072 // 2^17 + +invariant gf64_special_exp_all_ones + assert SPECIAL_EXP == EXP_MAX + +invariant gf64_zero_pos_no_sign + assert (GF64_ZERO_POS & SIGN_MASK) == 0 + +invariant gf64_zero_neg_has_sign + assert (GF64_ZERO_NEG & SIGN_MASK) == SIGN_MASK + +invariant gf64_inf_pos_max_exp + assert (GF64_INF_POS & EXP_MASK) == EXP_MASK + +invariant gf64_nan_has_mantissa + assert (GF64_NAN & MANT_MASK) != 0 + +invariant gf64_abs_removes_sign + assert gf64_abs(GF64_ZERO_NEG) == GF64_ZERO_POS + +invariant gf64_neg_toggles_sign + assert (gf64_neg(GF64_INF_POS) & SIGN_MASK) == SIGN_MASK + +invariant gf64_from_u32_zero + assert gf64_from_u32(0) == GF64_ZERO_POS + +invariant gf64_from_i32_zero + assert gf64_from_i32(0) == GF64_ZERO_POS + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench gf64_extract_sign_latency + measure: nanoseconds to gf64_extract_sign(0x123456789ABCDEF0) + target: < 10ns + +bench gf64_extract_exponent_latency + measure: nanoseconds to gf64_extract_exponent(0x123456789ABCDEF0) + target: < 10ns + +bench gf64_encode_f64_latency + measure: nanoseconds to gf64_encode_f64(123.456) + target: < 100ns + +bench gf64_decode_f64_latency + measure: nanoseconds to gf64_decode_f64(0x3F80000000000000) + target: < 100ns + +bench gf64_add_latency + measure: nanoseconds to gf64_add(0x3F80000000000000, 0x4000000000000000) + target: < 200ns + +bench gf64_mul_latency + measure: nanoseconds to gf64_mul(0x3F80000000000000, 0x4000000000000000) + target: < 200ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/gf8.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/gf8.t27 new file mode 100644 index 0000000000..d4f9f72ae6 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/gf8.t27 @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/gf8.t27 +// GoldenFloat8 0 8-bit 1-structured floating point +// NUMERIC-STANDARD-001 2 Agent 3 (P1) + +module GF8 { + // Import base format family + use numeric::goldenfloat_family; + use numeric::phi_ratio; + + // Import test/invariant/bench framework + use base::testing; + use base::benchmarking; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Format Definition + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // GF8 bit layout: [S|EEE|MMMM] + // S: 1 bit (sign) + // E: 3 bits (exponent) + // M: 4 bits (mantissa) + + const BITS : u8 = 8; + const SIGN_BITS : u8 = 1; + const EXP_BITS : u8 = 3; + const MANT_BITS : u8 = 4; + + // Bias for exponent (2^(3-1) - 1 = 3) + const EXP_BIAS : u8 = 3; + + // 141-ratio: exp/mant = 3/4 = 0.75 + // 142-distance: math::constants::PHI_DISTANCE + const PHI_DISTANCE : f64 = 0.132; + + // 143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 + // 2. GoldenFloat8 Type + // 208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 + + struct GF8 { + raw : u8, // 8-bit raw value + } + + // 281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 + // 3. Encoding/Decoding + // 346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 + + // Encode f32 to GF8 + fn encode(value: f32) -> GF8 { + if (value == 0.0) { + return GF8{ raw = 0 }; + } + + const sign = if (value < 0.0) { 1 } else { 0 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent (unbiased) + const exp_unbiased = floor_log2(abs_val) as i8; + const exp_biased = (exp_unbiased + EXP_BIAS as i8) as u8; + + // Clamp exponent + const exp_clamped = clamp(exp_biased, 0, (1 << EXP_BITS) - 1); + + // Extract mantissa (4 bits) + const mant = extract_mantissa(abs_val, exp_unbiased, MANT_BITS); + + return GF8{ + raw = (sign << 7) | (exp_clamped << MANT_BITS) | mant + }; + } + + // Test: gf8_phi_distance invariant + test gf8_phi_distance + given phi = 1.618033988749894848 // math::sacred_physics::PHI + when gf8_phi = GF8::encode(phi) + then gf8_phi.raw == 0b01000000 // |S|EEE (phi = math::constants::PHI_DISTANCE) + + // Decode GF8 to f32 + fn decode(gf: GF8) -> f32 { + const sign = (gf.raw >> 7) as u8; + const exp_biased = ((gf.raw >> MANT_BITS) & 0x07) as u8; + const mant = (gf.raw & 0x0F) as u8; + + // Zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Exponent (with special case for subnormals) + const exp_unbiased = if (exp_biased == 0) { + -EXP_BIAS as i8 + 1 + } else { + (exp_biased as i8) - EXP_BIAS as i8 + }; + + // Mantissa (with implicit 1 for normalized, 0 for subnormal) + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / 16.0 + } else { + 1.0 + (mant as f32) / 16.0 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + // 419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 + // 4. Format Properties + // 484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 + + fn max_value() -> f32 { + // Max normalized: mant=1.9375, exp=3 557 15.5 + const mant_max = 1.0 + 15.0 / 16.0; + const exp_max = (1 << EXP_BITS) - 1 - EXP_BIAS; + return mant_max * pow(2.0, exp_max as f32); + } + + fn min_positive() -> f32 { + // Min subnormal: mant=1/16, exp=-2 558 0.0625 + const mant_min = 1.0 / 16.0; + const exp_min = -EXP_BIAS as i8 + 1; + return mant_min * pow(2.0, exp_min as f32); + } + + fn epsilon() -> f32 { + // Smallest representable difference at 1.0 + return 1.0 / 16.0; // 0.0625 + } + + // 559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623 + // 5. Validation + // 624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696 + + fn validate_format() -> bool { + const fmt = goldenfloat_family::get_format_by_name("GF8"); + return (fmt != null) && + (fmt.?.bits == BITS) && + (fmt.?.exp_bits == EXP_BITS) && + (fmt.?.mant_bits == MANT_BITS); + } + + // 697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761 + // 6. Use Cases + // 762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834 + + // GF8 is optimal for: + // - High compression (75% smaller than FP32) + // - Weight quantization for lightweight models + // - Activation caching + // - Intermediate feature maps + + // Memory: 8 bits = 1 byte (4x FP32 in same space) + const MEMORY_RATIO_VS_FP32 : f32 = 8.0 / 32.0; // 0.25 + + // 835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899 + // 7. Helper Functions + // 900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972 + + fn floor_log2(x: f32) -> i8 { + if (x <= 0.0) { return -128; } + let exp : i8 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn extract_mantissa(value: f32, exp: i8, mant_bits: u8) -> u8 { + const normalized = value / pow(2.0, exp as f32); + const frac = normalized - 1.0; + const max_mant = (1 << mant_bits) - 1; + return (frac * (max_mant as f32 + 1.0)) as u8; + } + + fn clamp(x: u8, min: u8, max: u8) -> u8 { + if (x < min) { return min; } + if (x > max) { return max; } + return x; + } + + fn pow(base: f32, exp: f32) -> f32 { + // Efficient power function for GF8 + // Integer exponent: binary exponentiation + // Fractional exponent: use logarithm approximation + + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Check if exponent is (approximately) integer + const is_integer = exp == floor(exp); + + if (is_integer) { + // Binary exponentiation for integer exponents + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + // Natural logarithm approximation + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; // NaN + } + if (x == 1.0) { + return 0.0; + } + + // Series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + const t5 = t3 * t2; + const t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + // Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let result = 1.0; + let term = 1.0; + let exp_x = x; + + // Scale down for large inputs to maintain accuracy + if (exp_x > 5.0 || exp_x < -5.0) { + const k = floor(exp_x / 5.0) as i32; + exp_x = exp_x - (k as f32) * 5.0; + } + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + // Scale back if needed + if (x > 5.0 || x < -5.0) { + const k = floor(x / 5.0) as i32; + if (k > 0) { + for (i in 0..k) { + result = result * exp_approx(5.0); + } + } else if (k < 0) { + for (i in k..0) { + result = result / exp_approx(5.0); + } + } + } + + return result; + } + + // Floor function + fn floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + // 9739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075 + // TDD-Inside-Spec: Tests and Invariants for GF8 + // 1076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178 + + test gf8_decode_zero + given gf = GF8{ raw = 0 } + when value = decode(gf) + then value == 0.0 + + test gf8_encode_zero_roundtrip + given original = 0.0 + and encoded = encode(original) + and decoded = decode(encoded) + then decoded == original + + test gf8_decode_positive_value + given gf = GF8{ raw = 0b01000000 } + when value = decode(gf) + then value > 0.0 + + test gf8_decode_negative_value + given gf = GF8{ raw = 0b10000000 } + when value = decode(gf) + then value < 0.0 + + test gf8_bits_sum_correct + given total = SIGN_BITS + EXP_BITS + MANT_BITS + then total == BITS + + test gf8_max_value_positive + given max_val = max_value() + then max_val > 0.0 + + test gf8_min_positive_greater_than_zero + given min_pos = min_positive() + then min_pos > 0.0 + + test gf8_epsilon_positive + given eps = epsilon() + then eps > 0.0 + + test gf8_memory_ratio_vs_fp32 + given ratio = MEMORY_RATIO_VS_FP32 + then ratio == 0.25 + + test gf8_validate_format_success + given valid = validate_format() + then valid == true + + invariant gf8_bits_constant + assert BITS == 8 + + invariant gf8_sign_bits_is_one + assert SIGN_BITS == 1 + + invariant gf8_exp_bits_is_three + assert EXP_BITS == 3 + + invariant gf8_mant_bits_is_four + assert MANT_BITS == 4 + + invariant gf8_max_ge_min_positive + assert max_value() >= min_positive() + + invariant gf8_phi_distance_within_tolerance + assert PHI_DISTANCE < 0.14 + + invariant gf8_exp_bias_positive + assert EXP_BIAS > 0 + + test gf8_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-6 + + test gf8_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-6 + + test gf8_pow_positive_integer_exponent + given result = pow(2.0, 5.0) + and expected = 32.0 + then abs(result - expected) < 1e-5 + + test gf8_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-5 + + test gf8_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-4 + + test gf8_pow_phi_squared + given phi = 1.6180339887498948 as f32 + and result = pow(phi, 2.0) + and expected = phi * phi + then abs(result - expected) < 1e-5 + + test gf8_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test gf8_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-6 and abs(result2 - 1.0) < 1e-6 + + test gf8_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-6 + + test gf8_ln_approx_of_e + given e = 2.718281828459045 as f32 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test gf8_ln_approx_of_e_squared + given e = 2.718281828459045 as f32 + and result = ln_approx(e * e) + then abs(result - 2.0) < 0.02 + + test gf8_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test gf8_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-6 + + test gf8_exp_approx_one + given e = 2.718281828459045 as f32 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test gf8_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 as f32 + then abs(result - expected) < 0.01 + + test gf8_floor_positive + given result = floor(3.7) + then abs(result - 3.0) < 1e-6 + + test gf8_floor_negative + given result = floor(-3.2) + then abs(result - (-4.0)) < 1e-6 + + test gf8_floor_integer + given result = floor(5.0) + then abs(result - 5.0) < 1e-6 + + invariant gf8_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant gf8_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant gf8_pow_multiply_exponents + given a = 2.0 + and b = 3.0 + assert abs(pow(pow(a, 2.0), b) - pow(a, 2.0 * b)) < 1e-5 + + invariant gf8_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant gf8_exp_ln_inversion + given x = 1.5 + and y = exp_approx(x) + then abs(ln_approx(y) - x) < 0.01 + + invariant gf8_floor_returns_integer + assert floor(x) == i32 for all f32 x + + invariant gf8_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + bench gf8_pow_integer_exponent + measure: nanoseconds to compute pow(2.0, 10.0) + target: < 500ns + + bench gf8_pow_fractional_exponent + measure: nanoseconds to compute pow(4.0, 0.5) + target: < 1000ns + + bench gf8_ln_latency + measure: nanoseconds to compute ln_approx(2.0) + target: < 300ns + + bench gf8_exp_latency + measure: nanoseconds to compute exp_approx(1.0) + target: < 500ns + + bench gf8_floor_latency + measure: nanoseconds to compute floor(3.7) + target: < 50ns + + bench gf8_encode_latency + measure: nanoseconds to encode(1.0) + target: < 100ns + + bench gf8_decode_latency + measure: nanoseconds to decode(GF8{raw = 64}) + target: < 50ns + + // Bench: GF8 weight quantization (NN weights from N(0, 0.1)) + bench gf8_weight_quantize + measure: nanoseconds to encode(0.1) and decode(GF8{raw = encode(0.1).raw}) + target: < 200ns + + // Invariant: GF8 phi_distance + invariant gf8_phi_distance + assert PHI_DISTANCE == 0.132 within 0.001 + // Rationale: exp/mant = 3/4 = 0.75, phi_distance = |0.75 - 0.618| = 0.132 +} diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/goldenfloat_family.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/goldenfloat_family.t27 new file mode 100644 index 0000000000..78e552c883 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/goldenfloat_family.t27 @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/goldenfloat_family.t27 +// GoldenFloat Family 0 1-structured floating point formats +// NUMERIC-STANDARD-001 2 Agent 1 (P0) + +module GoldenFloatFamily { + // Import sacred constants for 3-structured design + use math::constants; + use math::sacred_physics; + + // 4567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768 + // 1. GoldenFloatFormat 69 Canonical format descriptor + // 707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 + + struct GoldenFloatFormat { + name : string, // "GF4", "GF8", ..., "GF256" + bits : u8, // Total bits: 4, 8, 12, 16, 20, 24, 32, 64, 128, 256 + sign_bits : u8, // Always 1 + exp_bits : u8, // Exponent bits + mant_bits : u8, // Mantissa bits + exp_mant_ratio : f64, // exp / mantissa ratio + phi_distance : f64, // |exp/mant - 1/143| (lower = better) + is_primary : bool, // true only for GF16 + } + + // 144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 + // 2. GOLDEN_FLOAT_FAMILY 209 The canonical format registry + // 210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 + + // 283-ratio target: 1/284 285 0.618 + // exp/mant ratios closer to 0.618 are more "golden" + const PHI_RATIO_TARGET : f64 = sacred_physics::PHI_INV; + + // Format array: ordered by bits (4 → 256) + const GOLDEN_FLOAT_FAMILY : [10]GoldenFloatFormat = [ + // name, bits, S, E, M, ratio, phi_dist, primary + GoldenFloatFormat{ + name = "GF4", + bits = 4, + sign_bits = 1, + exp_bits = 1, + mant_bits = 2, + exp_mant_ratio = 0.5, + phi_distance = abs(0.5 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF8", + bits = 8, + sign_bits = 1, + exp_bits = 3, + mant_bits = 4, + exp_mant_ratio = 0.75, + phi_distance = abs(0.75 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF12", + bits = 12, + sign_bits = 1, + exp_bits = 4, + mant_bits = 7, + exp_mant_ratio = 0.5714285714285714, + phi_distance = abs(0.5714285714285714 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF16", + bits = 16, + sign_bits = 1, + exp_bits = 6, + mant_bits = 9, + exp_mant_ratio = 0.6666666666666667, + phi_distance = abs(0.6666666666666667 - PHI_RATIO_TARGET), + is_primary = true, // PRIMARY FORMAT + }, + GoldenFloatFormat{ + name = "GF20", + bits = 20, + sign_bits = 1, + exp_bits = 7, + mant_bits = 12, + exp_mant_ratio = 0.5833333333333333, + phi_distance = abs(0.5833333333333333 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF24", + bits = 24, + sign_bits = 1, + exp_bits = 9, + mant_bits = 14, + exp_mant_ratio = 0.6428571428571429, + phi_distance = abs(0.6428571428571429 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF32", + bits = 32, + sign_bits = 1, + exp_bits = 12, + mant_bits = 19, + exp_mant_ratio = 0.631578947368421, + phi_distance = abs(0.631578947368421 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF64", + bits = 64, + sign_bits = 1, + exp_bits = 24, + mant_bits = 39, + exp_mant_ratio = 0.6153846153846154, + phi_distance = abs(0.6153846153846154 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF128", + bits = 128, + sign_bits = 1, + exp_bits = 48, + mant_bits = 79, + exp_mant_ratio = 0.6075949367088608, + phi_distance = abs(0.6075949367088608 - PHI_RATIO_TARGET), + is_primary = false, + }, + GoldenFloatFormat{ + name = "GF256", + bits = 256, + sign_bits = 1, + exp_bits = 97, + mant_bits = 158, + exp_mant_ratio = 0.6139240506329114, + phi_distance = abs(0.6139240506329114 - PHI_RATIO_TARGET), + is_primary = false, + }, + ]; + + // 287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 + // 3. Query functions + // 352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 + + fn get_format_by_name(name: string) -> Option { + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + if (fmt.name == name) { + return fmt; + } + } + return null; + } + + fn get_format_by_bits(bits: u8) -> Option { + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + if (fmt.bits == bits) { + return fmt; + } + } + return null; + } + + fn get_primary_format() -> GoldenFloatFormat { + return GOLDEN_FLOAT_FAMILY[3]; // GF16 at index 3 + } + + // 425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 + // 4. Verification functions + // 490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562 + + struct VerificationReport { + all_valid : bool, + primary_is_gf16 : bool, + phi_distances_ok : bool, + best_phi_format : string, + best_phi_distance : f64, + avg_phi_distance : f64, + } + + fn verify_golden_family() -> VerificationReport { + var primary_count : u8 = 0; + var best_dist : f64 = 1.0; + var best_name : string = ""; + var total_dist : f64 = 0.0; + var format_count : u8 = 0; + var all_names_unique : bool = true; + var all_bit_sums_valid : bool = true; + var all_phi_distances_non_negative : bool = true; + + // Check for duplicate names + var names_seen : [10]string = ["", "", "", "", "", "", "", "", "", ""]; + + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + format_count = format_count + 1; + + // Count primary formats (should be exactly 1) + if (fmt.is_primary) { + primary_count = primary_count + 1; + } + + // Track best phi distance + if (fmt.phi_distance < best_dist) { + best_dist = fmt.phi_distance; + best_name = fmt.name; + } + + total_dist = total_dist + fmt.phi_distance; + + // Check for duplicate names + for (const names_seen) |name| { + if (name != "" && name == fmt.name) { + all_names_unique = false; + } + } + names_seen[format_count - 1] = fmt.name; + + // Check that exp_bits + mant_bits + 1 = bits (sign bit) + if (fmt.exp_bits + fmt.mant_bits + 1 != fmt.bits) { + all_bit_sums_valid = false; + } + + // Check phi_distance is non-negative + if (fmt.phi_distance < 0.0) { + all_phi_distances_non_negative = false; + } + } + + const avg_dist = total_dist / 10.0; + + // All checks must pass + const all_checks_valid = + format_count == 10 && + all_names_unique && + all_bit_sums_valid && + all_phi_distances_non_negative && + primary_count == 1; + + return VerificationReport{ + all_valid = all_checks_valid, + primary_is_gf16 = (primary_count == 1) && (GOLDEN_FLOAT_FAMILY[3].is_primary), + phi_distances_ok = best_dist < 0.1, // All within 0.1 of 1/563 + best_phi_format = best_name, + best_phi_distance = best_dist, + avg_phi_distance = avg_dist, + }; + } + + // 564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628 + // 5. Utility functions + // 629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701 + + fn max_value(format: GoldenFloatFormat) -> f64 { + // Max value = (2 - 2^(-M)) * 2^(2^E - 1) + const mant_max = 2.0 - pow(2.0, -(format.mant_bits as f64)); + const exp_max = pow(2.0, format.exp_bits as f64) - 1.0; + return mant_max * pow(2.0, exp_max); + } + + fn min_positive(format: GoldenFloatFormat) -> f64 { + // Min positive = 2^(-M) * 2^(1 - bias) + const mant_min = pow(2.0, -(format.mant_bits as f64)); + const bias = pow(2.0, format.exp_bits as f64 - 1.0) - 1.0; + return mant_min * pow(2.0, 1.0 - bias); + } + + fn memory_efficiency(format: GoldenFloatFormat) -> f64 { + // Memory efficiency vs FP32 (1.0 = same, 0.5 = half size) + return format.bits as f64 / 32.0; + } + + // 702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804 + // TDD-Inside-Spec: Tests and Invariants for GoldenFloatFamily + // 805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907 + + test gffamily_get_format_by_name_gf16 + given fmt = get_format_by_name("GF16") + then fmt != null and fmt.?.name == "GF16" and fmt.?.bits == 16 + + test gffamily_get_format_by_bits_8 + given fmt = get_format_by_bits(8) + then fmt != null and fmt.?.name == "GF8" and fmt.?.bits == 8 + + test gffamily_get_primary_format_is_gf16 + given primary = get_primary_format() + then primary.name == "GF16" and primary.is_primary == true + + test gffamily_family_size_10 + given size = GOLDEN_FLOAT_FAMILY.len() + then size == 10 + + test gffamily_phi_ratio_target_is_phi_inverse + given target = PHI_RATIO_TARGET + and phi_inv = sacred_physics::PHI_INV + then abs(target - phi_inv) < 0.000001 + + test gffamily_gf4_has_correct_bit_counts + given fmt = get_format_by_name("GF4").? + then fmt.sign_bits == 1 and fmt.exp_bits == 1 and fmt.mant_bits == 2 + + test gffamily_gf32_has_correct_bit_counts + given fmt = get_format_by_name("GF32").? + then fmt.sign_bits == 1 and fmt.exp_bits == 12 and fmt.mant_bits == 19 + + test gffamily_only_gf16_is_primary + var count = 0 + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + if (fmt.is_primary) { count = count + 1; } + } + then count == 1 + + test gffamily_verify_primary_is_gf16 + given report = verify_golden_family() + then report.primary_is_gf16 == true + + test gffamily_phi_distances_within_tolerance + given report = verify_golden_family() + then report.phi_distances_ok == true + + test gffamily_best_phi_format_is_gf12 + given report = verify_golden_family() + then report.best_phi_format == "GF12" + + test gffamily_memory_efficiency_gf8 + given fmt = get_format_by_name("GF8").? + and eff = memory_efficiency(fmt) + then abs(eff - 0.25) < 0.01 + + test gffamily_memory_efficiency_gf16 + given fmt = get_format_by_name("GF16").? + and eff = memory_efficiency(fmt) + then abs(eff - 0.5) < 0.01 + + test gffamily_max_value_positive + given fmt = get_format_by_name("GF8").? + and max_val = max_value(fmt) + then max_val > 0.0 + + test gffamily_min_positive_greater_than_zero + given fmt = get_format_by_name("GF8").? + and min_pos = min_positive(fmt) + then min_pos > 0.0 + + test gffamily_get_format_by_unknown_name + given fmt = get_format_by_name("GF999") + then fmt == null + + test gffamily_get_format_by_unknown_bits + given fmt = get_format_by_bits(100) + then fmt == null + + test gffamily_verify_all_valid + given report = verify_golden_family() + then report.all_valid == true + + test gffamily_verify_format_count_is_10 + given report = verify_golden_family() + then report.all_valid == true // implies format_count == 10 + + test gffamily_verify_names_unique + given report = verify_golden_family() + then report.all_valid == true // implies names are unique + + test gffamily_verify_bit_sums_valid + given report = verify_golden_family() + then report.all_valid == true // implies bit sums are valid + + test gffamily_verify_phi_distances_non_negative + given report = verify_golden_family() + then report.all_valid == true // implies phi_distances are non-negative + + test gffamily_verify_exactly_one_primary + given report = verify_golden_family() + then report.all_valid == true // implies exactly 1 primary format + + test gffamily_best_phi_distance_is_small + given report = verify_golden_family() + then report.best_phi_distance < 0.05 + + test gffamily_avg_phi_distance_reasonable + given report = verify_golden_family() + and avg = report.avg_phi_distance + then avg > 0.0 and avg < 0.2 + + invariant gffamily_phi_ratio_target_positive + assert PHI_RATIO_TARGET > 0.0 + + invariant gffamily_phi_ratio_target_less_than_one + assert PHI_RATIO_TARGET < 1.0 + + invariant gffamily_family_size_constant + assert GOLDEN_FLOAT_FAMILY.len() == 10 + + invariant gffamily_gf4_at_index_0 + assert GOLDEN_FLOAT_FAMILY[0].name == "GF4" + + invariant gffamily_gf256_at_index_9 + assert GOLDEN_FLOAT_FAMILY[9].name == "GF256" + + invariant gffamily_gf128_at_index_8 + assert GOLDEN_FLOAT_FAMILY[8].name == "GF128" + + invariant gffamily_gf64_at_index_7 + assert GOLDEN_FLOAT_FAMILY[7].name == "GF64" + + invariant gffamily_gf32_at_index_6 + assert GOLDEN_FLOAT_FAMILY[6].name == "GF32" + + invariant gffamily_all_formats_have_sign_bits_1 + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + assert fmt.sign_bits == 1; + } + + invariant gffamily_all_formats_bits_sum_correct + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + assert fmt.sign_bits + fmt.exp_bits + fmt.mant_bits == fmt.bits; + } + + invariant gffamily_primary_is_gf16 + assert GOLDEN_FLOAT_FAMILY[3].is_primary == true + + invariant gffamily_phi_distances_non_negative + for (const GOLDEN_FLOAT_FAMILY) |fmt| { + assert fmt.phi_distance >= 0.0; + } + + invariant gffamily_memory_efficiency_gf4 + assert abs(memory_efficiency(GOLDEN_FLOAT_FAMILY[0]) - 0.125) < 0.01 + + invariant gffamily_memory_efficiency_gf32 + assert abs(memory_efficiency(GOLDEN_FLOAT_FAMILY[6]) - 1.0) < 0.01 + + bench gffamily_get_format_by_name_latency + measure: nanoseconds to get_format_by_name("GF16") + target: < 100ns + + bench gffamily_get_format_by_bits_latency + measure: nanoseconds to get_format_by_bits(16) + target: < 100ns + + bench gffamily_get_primary_format_latency + measure: nanoseconds to get_primary_format() + target: < 50ns + + bench gffamily_verify_golden_family_latency + measure: nanoseconds to verify_golden_family() + target: < 500ns + + bench gffamily_memory_efficiency_latency + measure: nanoseconds to memory_efficiency(GOLDEN_FLOAT_FAMILY[3]) + target: < 100ns +} diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/int4.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/int4.t27 new file mode 100644 index 0000000000..7a49385b58 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/int4.t27 @@ -0,0 +1,766 @@ +// SPDX-License-Identifier: Apache-2.0 +; int4.t27 — Int4 Signed 4-bit Integer Quantization +; Range: -8 to 7 +; Used for ultra-low precision quantization in ML +; φ² + 1/φ² = 3 | TRINITY + +module triformat-int4; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 4; +pub const MIN : i8 = -8; +pub const MAX : i8 = 7; +pub const RANGE : u8 = 16; // 2^4 + +pub const MASK : u8 = 0x0F; // Lower 4 bits mask + +// ============================================================================ +// Types +// ============================================================================ + +pub const Int4 = i8; // Stored as i8, but only lower 4 bits used + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// int4_from_i8(value: i8) -> Int4 +// Convert i8 to Int4 with saturation +// Values < -8 clamp to -8, values > 7 clamp to 7 +pub fn int4_from_i8(value: i8) Int4 { + if (value < MIN) { + return MIN; + } else if (value > MAX) { + return MAX; + } + return value; +} + +// int4_from_i16(value: i16) -> Int4 +// Convert i16 to Int4 with saturation +pub fn int4_from_i16(value: i16) Int4 { + if (value < @as(i16, MIN)) { + return MIN; + } else if (value > @as(i16, MAX)) { + return MAX; + } + return @as(i8, @intCast(value)); +} + +// int4_from_i32(value: i32) -> Int4 +// Convert i32 to Int4 with saturation +pub fn int4_from_i32(value: i32) Int4 { + if (value < @as(i32, MIN)) { + return MIN; + } else if (value > @as(i32, MAX)) { + return MAX; + } + return @as(i8, @intCast(value)); +} + +// int4_from_f32(value: f32) -> Int4 +// Convert f32 to Int4 with rounding and saturation +// Round to nearest, ties to even +pub fn int4_from_f32(value: f32) Int4 { + if (std.math.isNan(value)) { + return 0; + } + if (value > @as(f32, @floatFromInt(MAX)) + 0.5) { + return MAX; + } + if (value < @as(f32, @floatFromInt(MIN)) - 0.5) { + return MIN; + } + // Round to nearest + const rounded = @as(i32, @intFromFloat(@round(value))); + return int4_from_i32(rounded); +} + +// int4_to_i8(value: Int4) -> i8 +// Convert Int4 to i8 (no-op, just returns value) +pub fn int4_to_i8(value: Int4) i8 { + return value; +} + +// int4_to_i16(value: Int4) -> i16 +// Convert Int4 to i16 (sign extension) +pub fn int4_to_i16(value: Int4) i16 { + // Sign extend from 4 bits to 16 bits + return @as(i16, value); +} + +// int4_to_i32(value: Int4) -> i32 +// Convert Int4 to i32 (sign extension) +pub fn int4_to_i32(value: Int4) i32 { + return @as(i32, value); +} + +// int4_to_f32(value: Int4) -> f32 +// Convert Int4 to f32 +pub fn int4_to_f32(value: Int4) f32 { + return @as(f32, @floatFromInt(value)); +} + +// int4_to_f64(value: Int4) -> f64 +// Convert Int4 to f64 +pub fn int4_to_f64(value: Int4) f64 { + return @as(f64, @floatFromInt(value)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// int4_add(a: Int4, b: Int4) -> Int4 +// Add two Int4 values with saturation +pub fn int4_add(a: Int4, b: Int4) Int4 { + const result = a + b; + if (result > MAX) { + return MAX; + } else if (result < MIN) { + return MIN; + } + return result; +} + +// int4_sub(a: Int4, b: Int4) -> Int4 +// Subtract two Int4 values with saturation +pub fn int4_sub(a: Int4, b: Int4) Int4 { + const result = a - b; + if (result > MAX) { + return MAX; + } else if (result < MIN) { + return MIN; + } + return result; +} + +// int4_mul(a: Int4, b: Int4) -> Int4 +// Multiply two Int4 values with saturation +pub fn int4_mul(a: Int4, b: Int4) Int4 { + const result = a * b; + if (result > MAX) { + return MAX; + } else if (result < MIN) { + return MIN; + } + return result; +} + +// int4_div(a: Int4, b: Int4) -> Int4 +// Divide two Int4 values with saturation +// Division by zero returns MAX or MIN based on sign of numerator +pub fn int4_div(a: Int4, b: Int4) Int4 { + if (b == 0) { + return if (a >= 0) MAX else MIN; + } + const result = a / b; + if (result > MAX) { + return MAX; + } else if (result < MIN) { + return MIN; + } + return result; +} + +// int4_abs(value: Int4) -> Int4 +// Absolute value of Int4 with saturation +pub fn int4_abs(value: Int4) Int4 { + if (value == MIN) { + return MAX; // -8 -> 7 (saturated) + } + return if (value < 0) -value else value; +} + +// int4_neg(value: Int4) -> Int4 +// Negate Int4 (with saturation for -MIN) +pub fn int4_neg(value: Int4) Int4 { + if (value == MIN) { + return MAX; // -(-8) = 7 (saturated) + } + return -value; +} + +// ============================================================================ +// Comparison Functions +// ============================================================================ + +// int4_is_equal(a: Int4, b: Int4) -> bool +pub fn int4_is_equal(a: Int4, b: Int4) bool { + return a == b; +} + +// int4_is_greater(a: Int4, b: Int4) -> bool +pub fn int4_is_greater(a: Int4, b: Int4) bool { + return a > b; +} + +// int4_is_less(a: Int4, b: Int4) -> bool +pub fn int4_is_less(a: Int4, b: Int4) bool { + return a < b; +} + +// int4_min(a: Int4, b: Int4) -> Int4 +pub fn int4_min(a: Int4, b: Int4) Int4 { + return if (a < b) a else b; +} + +// int4_max(a: Int4, b: Int4) -> Int4 +pub fn int4_max(a: Int4, b: Int4) Int4 { + return if (a > b) a else b; +} + +// int4_clamp(value: Int4, min: Int4, max: Int4) -> Int4 +pub fn int4_clamp(value: Int4, min: Int4, max: Int4) Int4 { + if (value < min) { + return min; + } else if (value > max) { + return max; + } + return value; +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// int4_clamp_to_range(value: Int4) -> Int4 +// Clamp value to Int4 range [-8, 7] +pub fn int4_clamp_to_range(value: Int4) Int4 { + return int4_clamp(value, MIN, MAX); +} + +// int4_is_in_range(value: i8) -> bool +// Check if value is in Int4 range +pub fn int4_is_in_range(value: i8) bool { + return value >= MIN and value <= MAX; +} + +// int4_lerp(a: Int4, b: Int4, t: f32) -> Int4 +// Linear interpolation between a and b +pub fn int4_lerp(a: Int4, b: Int4, t: f32) Int4 { + const fa = @as(f32, @floatFromInt(a)); + const fb = @as(f32, @floatFromInt(b)); + const result = fa + (fb - fa) * t; + return int4_from_f32(result); +} + +// int4_sq(value: Int4) -> Int4 +// Square value with saturation +pub fn int4_sq(value: Int4) Int4 { + return int4_mul(value, value); +} + +// int4_abs_diff(a: Int4, b: Int4) -> Int4 +// Absolute difference between two Int4 values +pub fn int4_abs_diff(a: Int4, b: Int4) Int4 { + const diff = a - b; + return int4_abs(diff); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "int4_from_i8_in_range" { + given value = 5 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == 5); +} + +test "int4_from_i8_above_max" { + given value = 10 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == MAX); +} + +test "int4_from_i8_below_min" { + given value = -10 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == MIN); +} + +test "int4_from_i8_at_max" { + given value = 7 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == MAX); +} + +test "int4_from_i8_at_min" { + given value = -8 + try std.testing.expect(result = int4_from_i8(value)); + try std.testing.expect(result == MIN); +} + +test "int4_from_i16_positive" { + given value = 100 + try std.testing.expect(result = int4_from_i16(value)); + try std.testing.expect(result == MAX); +} + +test "int4_from_i16_negative" { + given value = -100 + try std.testing.expect(result = int4_from_i16(value)); + try std.testing.expect(result == MIN); +} + +test "int4_from_i16_in_range" { + given value = 3 + try std.testing.expect(result = int4_from_i16(value)); + try std.testing.expect(result == 3); +} + +test "int4_from_i32_positive" { + given value = 1000 + try std.testing.expect(result = int4_from_i32(value)); + try std.testing.expect(result == MAX); +} + +test "int4_from_i32_negative" { + given value = -1000 + try std.testing.expect(result = int4_from_i32(value)); + try std.testing.expect(result == MIN); +} + +test "int4_from_f32_zero" { + given value = 0.0 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == 0); +} + +test "int4_from_f32_one" { + given value = 1.0 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == 1); +} + +test "int4_from_f32_negative" { + given value = -1.5 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == -2); +} + +test "int4_from_f32_nan" { + given result = int4_from_f32(std.math.nan(f32)) + try std.testing.expect(result == 0); +} + +test "int4_from_f32_round_up" { + given value = 1.6 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == 2); +} + +test "int4_from_f32_round_down" { + given value = 1.4 + try std.testing.expect(result = int4_from_f32(value)); + try std.testing.expect(result == 1); +} + +test "int4_to_i8_identity" { + given value: Int4 = 5 + try std.testing.expect(result = int4_to_i8(value)); + try std.testing.expect(result == value); +} + +test "int4_to_i16_sign_extend" { + given value: Int4 = -1 + try std.testing.expect(result = int4_to_i16(value)); + try std.testing.expect(result == -1); +} + +test "int4_to_i32_sign_extend" { + given value: Int4 = -1 + try std.testing.expect(result = int4_to_i32(value)); + try std.testing.expect(result == -1); +} + +test "int4_to_f32_positive" { + given value: Int4 = 5 + try std.testing.expect(result = int4_to_f32(value)); + try std.testing.expect(abs(result - 5.0) < 0.001); +} + +test "int4_to_f32_negative" { + given value: Int4 = -3 + try std.testing.expect(result = int4_to_f32(value)); + try std.testing.expect(abs(result + 3.0) < 0.001); +} + +test "int4_add_simple" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 4); + try std.testing.expect(result = int4_add(a, b)); + try std.testing.expect(result == MAX // 3 + 4 = 7 = MAX); +} + +test "int4_add_overflow" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_add(a, b)); + try std.testing.expect(result == MAX // 5 + 5 = 10 -> 7); +} + +test "int4_add_underflow" { + given a: Int4 = -5 + try std.testing.expect(b: Int4 = -5); + try std.testing.expect(result = int4_add(a, b)); + try std.testing.expect(result == MIN // -5 + -5 = -10 -> -8); +} + +test "int4_sub_simple" { + given a: Int4 = 7 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_sub(a, b)); + try std.testing.expect(result == 4); +} + +test "int4_sub_negative" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 7); + try std.testing.expect(result = int4_sub(a, b)); + try std.testing.expect(result == -4); +} + +test "int4_mul_positive" { + given a: Int4 = 2 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_mul(a, b)); + try std.testing.expect(result == 6); +} + +test "int4_mul_negative" { + given a: Int4 = -2 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_mul(a, b)); + try std.testing.expect(result == -6); +} + +test "int4_mul_overflow" { + given a: Int4 = 4 + try std.testing.expect(b: Int4 = 4); + try std.testing.expect(result = int4_mul(a, b)); + try std.testing.expect(result == MAX // 4 * 4 = 16 -> 7); +} + +test "int4_div_simple" { + given a: Int4 = 6 + try std.testing.expect(b: Int4 = 2); + try std.testing.expect(result = int4_div(a, b)); + try std.testing.expect(result == 3); +} + +test "int4_div_by_zero_positive" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 0); + try std.testing.expect(result = int4_div(a, b)); + try std.testing.expect(result == MAX); +} + +test "int4_div_by_zero_negative" { + given a: Int4 = -5 + try std.testing.expect(b: Int4 = 0); + try std.testing.expect(result = int4_div(a, b)); + try std.testing.expect(result == MIN); +} + +test "int4_abs_positive" { + given value: Int4 = 5 + try std.testing.expect(result = int4_abs(value)); + try std.testing.expect(result == 5); +} + +test "int4_abs_negative" { + given value: Int4 = -5 + try std.testing.expect(result = int4_abs(value)); + try std.testing.expect(result == 5); +} + +test "int4_abs_min" { + given value: Int4 = MIN + try std.testing.expect(result = int4_abs(value)); + try std.testing.expect(result == MAX // |-8| = 7 (saturated)); +} + +test "int4_neg_positive" { + given value: Int4 = 5 + try std.testing.expect(result = int4_neg(value)); + try std.testing.expect(result == -5); +} + +test "int4_neg_negative" { + given value: Int4 = -5 + try std.testing.expect(result = int4_neg(value)); + try std.testing.expect(result == 5); +} + +test "int4_neg_min" { + given value: Int4 = MIN + try std.testing.expect(result = int4_neg(value)); + try std.testing.expect(result == MAX // -(-8) = 7 (saturated)); +} + +test "int4_is_equal_true" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(int4_is_equal(a, b) == true); +} + +test "int4_is_equal_false" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(int4_is_equal(a, b) == false); +} + +test "int4_is_greater_true" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(int4_is_greater(a, b) == true); +} + +test "int4_is_greater_false" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(int4_is_greater(a, b) == false); +} + +test "int4_min_smaller" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_min(a, b)); + try std.testing.expect(result == 3); +} + +test "int4_min_larger" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_min(a, b)); + try std.testing.expect(result == 3); +} + +test "int4_max_smaller" { + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_max(a, b)); + try std.testing.expect(result == 5); +} + +test "int4_max_larger" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 3); + try std.testing.expect(result = int4_max(a, b)); + try std.testing.expect(result == 5); +} + +test "int4_clamp_in_range" { + given value: Int4 = 5 + try std.testing.expect(min: Int4 = 0); + try std.testing.expect(max: Int4 = MAX); + try std.testing.expect(result = int4_clamp(value, min, max)); + try std.testing.expect(result == 5); +} + +test "int4_clamp_below_min" { + given value: Int4 = -10 + try std.testing.expect(min: Int4 = -5); + try std.testing.expect(max: Int4 = MAX); + try std.testing.expect(result = int4_clamp(value, min, max)); + try std.testing.expect(result == -5); +} + +test "int4_clamp_above_max" { + given value: Int4 = 10 + try std.testing.expect(min: Int4 = MIN); + try std.testing.expect(max: Int4 = 5); + try std.testing.expect(result = int4_clamp(value, min, max)); + try std.testing.expect(result == 5); +} + +test "int4_is_in_range_true" { + try std.testing.expect(int4_is_in_range(5) == true); +} + +test "int4_is_in_range_false_high" { + try std.testing.expect(int4_is_in_range(10) == false); +} + +test "int4_is_in_range_false_low" { + try std.testing.expect(int4_is_in_range(-10) == false); +} + +test "int4_is_in_range_at_max" { + try std.testing.expect(int4_is_in_range(MAX) == true); +} + +test "int4_is_in_range_at_min" { + try std.testing.expect(int4_is_in_range(MIN) == true); +} + +test "int4_lerp_zero" { + given a: Int4 = 1 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = int4_lerp(a, b, t)); + try std.testing.expect(result == 1); +} + +test "int4_lerp_one" { + given a: Int4 = 1 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = int4_lerp(a, b, t)); + try std.testing.expect(result == 5); +} + +test "int4_lerp_half" { + given a: Int4 = 0 + try std.testing.expect(b: Int4 = 4); + try std.testing.expect(t = 0.5); + try std.testing.expect(result = int4_lerp(a, b, t)); + try std.testing.expect(result == 2); +} + +test "int4_sq_positive" { + given value: Int4 = 3 + try std.testing.expect(result = int4_sq(value)); + try std.testing.expect(result == 9 // saturates to 7); +} + +test "int4_sq_negative" { + given value: Int4 = -3 + try std.testing.expect(result = int4_sq(value)); + try std.testing.expect(result == 9 // saturates to 7); +} + +test "int4_sq_zero" { + given value: Int4 = 0 + try std.testing.expect(result = int4_sq(value)); + try std.testing.expect(result == 0); +} + +test "int4_abs_diff_same" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_abs_diff(a, b)); + try std.testing.expect(result == 0); +} + +test "int4_abs_diff_positive" { + given a: Int4 = 5 + try std.testing.expect(b: Int4 = 2); + try std.testing.expect(result = int4_abs_diff(a, b)); + try std.testing.expect(result == 3); +} + +test "int4_abs_diff_negative" { + given a: Int4 = 2 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_abs_diff(a, b)); + try std.testing.expect(result == 3); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant int4_bits_constant + assert BITS == 4 + +invariant int4_min_is_negative_eight + assert MIN == -8 + +invariant int4_max_is_seven + assert MAX == 7 + +invariant int4_range_is_sixteen + assert RANGE == 16 + +invariant int4_mask_lower_four_bits + assert MASK == 0x0F + +invariant int4_from_i8_max_saturates + assert int4_from_i8(100) == MAX + +invariant int4_from_i8_min_saturates + assert int4_from_i8(-100) == MIN + +invariant int4_from_i8_max_passes + assert int4_from_i8(MAX) == MAX + +invariant int4_from_i8_min_passes + assert int4_from_i8(MIN) == MIN + +invariant int4_to_i8_zero + assert int4_to_i8(0) == 0 + +invariant int4_to_i8_max + assert int4_to_i8(MAX) == MAX + +invariant int4_to_i8_min + assert int4_to_i8(MIN) == MIN + +invariant int4_add_zero + assert int4_add(5, 0) == 5 + +invariant int4_sub_zero + assert int4_sub(5, 0) == 5 + +invariant int4_mul_one + assert int4_mul(5, 1) == 5 + +invariant int4_mul_zero + assert int4_mul(5, 0) == 0 + +invariant int4_abs_non_negative + given value: Int4 = -5 + assert int4_abs(value) >= 0 + +invariant int4_neg_twice_original + given value: Int4 = 5 + try std.testing.expect(neg = int4_neg(value)); + try std.testing.expect(neg_again = int4_neg(neg)); + assert neg_again == value or value == MIN // MIN saturates + +invariant int4_min_le_both + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_min(a, b)); + assert result <= a and result <= b + +invariant int4_max_ge_both + given a: Int4 = 3 + try std.testing.expect(b: Int4 = 5); + try std.testing.expect(result = int4_max(a, b)); + assert result >= a and result >= b + +invariant int4_clamp_result_in_range + given result = int4_clamp(0, -5, 5) + assert result >= -5 and result <= 5 + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench int4_from_i8_latency + measure: nanoseconds to int4_from_i8(5) + target: < 5ns + +bench int4_from_f32_latency + measure: nanoseconds to int4_from_f32(5.5) + target: < 20ns + +bench int4_add_latency + measure: nanoseconds to int4_add(3, 4) + target: < 10ns + +bench int4_mul_latency + measure: nanoseconds to int4_mul(3, 4) + target: < 10ns + +bench int4_abs_latency + measure: nanoseconds to int4_abs(-5) + target: < 5ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/int8.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/int8.t27 new file mode 100644 index 0000000000..82e33b87a0 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/int8.t27 @@ -0,0 +1,1151 @@ +// SPDX-License-Identifier: Apache-2.0 +; int8.t27 — Int8 Signed 8-bit Integer Quantization +; Range: -128 to 127 +; Standard 8-bit signed integer, widely used in quantization +; φ² + 1/φ² = 3 | TRINITY + +module triformat-int8; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 8; +pub const MIN : i8 = -128; +pub const MAX : i8 = 127; +pub const RANGE : u16 = 256; // 2^8 + +pub const MASK : u8 = 0xFF; // All 8 bits mask + +// ============================================================================ +// Types +// ============================================================================ + +pub const Int8 = i8; // Native i8 type + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// int8_from_i8(value: i8) -> Int8 +// Convert i8 to Int8 (identity function, no-op) +pub fn int8_from_i8(value: i8) Int8 { + return value; +} + +// int8_from_i16(value: i16) -> Int8 +// Convert i16 to Int8 with saturation +pub fn int8_from_i16(value: i16) Int8 { + if (value < @as(i16, MIN)) { + return MIN; + } else if (value > @as(i16, MAX)) { + return MAX; + } + return @as(i8, @intCast(value)); +} + +// int8_from_i32(value: i32) -> Int8 +// Convert i32 to Int8 with saturation +pub fn int8_from_i32(value: i32) Int8 { + if (value < @as(i32, MIN)) { + return MIN; + } else if (value > @as(i32, MAX)) { + return MAX; + } + return @as(i8, @intCast(value)); +} + +// int8_from_u8(value: u8) -> Int8 +// Convert u8 to Int8 with saturation +// Values > 127 clamp to 127 +pub fn int8_from_u8(value: u8) Int8 { + if (value > 127) { + return MAX; + } + return @as(i8, @bitCast(value)); +} + +// int8_from_f32(value: f32) -> Int8 +// Convert f32 to Int8 with rounding and saturation +// Round to nearest, ties to even +pub fn int8_from_f32(value: f32) Int8 { + if (std.math.isNan(value)) { + return 0; + } + if (value > @as(f32, @floatFromInt(MAX)) + 0.5) { + return MAX; + } + if (value < @as(f32, @floatFromInt(MIN)) - 0.5) { + return MIN; + } + const rounded = @as(i32, @intFromFloat(@round(value))); + return int8_from_i32(rounded); +} + +// int8_from_f64(value: f64) -> Int8 +// Convert f64 to Int8 with rounding and saturation +pub fn int8_from_f64(value: f64) Int8 { + if (std.math.isNan(value)) { + return 0; + } + if (value > @as(f64, @floatFromInt(MAX)) + 0.5) { + return MAX; + } + if (value < @as(f64, @floatFromInt(MIN)) - 0.5) { + return MIN; + } + const rounded = @as(i32, @intFromFloat(@round(value))); + return int8_from_i32(rounded); +} + +// int8_to_i8(value: Int8) -> i8 +// Convert Int8 to i8 (identity function) +pub fn int8_to_i8(value: Int8) i8 { + return value; +} + +// int8_to_i16(value: Int8) -> i16 +// Convert Int8 to i16 (sign extension) +pub fn int8_to_i16(value: Int8) i16 { + return @as(i16, value); +} + +// int8_to_i32(value: Int8) -> i32 +// Convert Int8 to i32 (sign extension) +pub fn int8_to_i32(value: Int8) i32 { + return @as(i32, value); +} + +// int8_to_u8(value: Int8) -> ?u8 +// Convert Int8 to u8 (returns null for negative values) +pub fn int8_to_u8(value: Int8) ?u8 { + if (value < 0) { + return null; + } + return @as(u8, @intCast(value)); +} + +// int8_to_f32(value: Int8) -> f32 +// Convert Int8 to f32 +pub fn int8_to_f32(value: Int8) f32 { + return @as(f32, @floatFromInt(value)); +} + +// int8_to_f64(value: Int8) -> f64 +// Convert Int8 to f64 +pub fn int8_to_f64(value: Int8) f64 { + return @as(f64, @floatFromInt(value)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// int8_add(a: Int8, b: Int8) -> Int8 +// Add two Int8 values with saturation +pub fn int8_add(a: Int8, b: Int8) Int8 { + const result = @as(i16, a) + @as(i16, b); + return int8_from_i16(result); +} + +// int8_sub(a: Int8, b: Int8) -> Int8 +// Subtract two Int8 values with saturation +pub fn int8_sub(a: Int8, b: Int8) Int8 { + const result = @as(i16, a) - @as(i16, b); + return int8_from_i16(result); +} + +// int8_mul(a: Int8, b: Int8) -> Int8 +// Multiply two Int8 values with saturation +pub fn int8_mul(a: Int8, b: Int8) Int8 { + const result = @as(i16, a) * @as(i16, b); + return int8_from_i16(result); +} + +// int8_div(a: Int8, b: Int8) -> Int8 +// Divide two Int8 values with saturation +// Division by zero returns MAX or MIN based on sign of numerator +pub fn int8_div(a: Int8, b: Int8) Int8 { + if (b == 0) { + return if (a >= 0) MAX else MIN; + } + return a / b; // i8 division saturates automatically on overflow +} + +// int8_mod(a: Int8, b: Int8) -> Int8 +// Modulo operation (returns 0 on division by zero) +pub fn int8_mod(a: Int8, b: Int8) Int8 { + if (b == 0) { + return 0; + } + const result = @rem(a, b); + // Handle negative remainder to make it always positive + return if (result < 0) result + b else result; +} + +// int8_pow(base: Int8, exp: u8) -> Int8 +// Power function with saturation +pub fn int8_pow(base: Int8, exp: u8) Int8 { + if (exp == 0) { + return 1; + } + if (base == 0) { + return 0; + } + if (base == 1) { + return 1; + } + if (base == -1) { + return if (exp % 2 == 0) 1 else -1; + } + + var result: i16 = 1; + var current: i16 = @as(i16, base); + var remaining = exp; + + while (remaining > 0) { + if (remaining % 2 == 1) { + result = result * current; + if (result > MAX) { + return MAX; + } + if (result < MIN) { + return MIN; + } + } + current = current * current; + if (current > MAX) { + return MAX; + } + if (current < MIN) { + return MIN; + } + remaining = remaining / 2; + } + + return int8_from_i16(result); +} + +// ============================================================================ +// Bitwise Operations +// ============================================================================ + +// int8_and(a: Int8, b: Int8) -> Int8 +// Bitwise AND +pub fn int8_and(a: Int8, b: Int8) Int8 { + return a & b; +} + +// int8_or(a: Int8, b: Int8) -> Int8 +// Bitwise OR +pub fn int8_or(a: Int8, b: Int8) Int8 { + return a | b; +} + +// int8_xor(a: Int8, b: Int8) -> Int8 +// Bitwise XOR +pub fn int8_xor(a: Int8, b: Int8) Int8 { + return a ^ b; +} + +// int8_not(value: Int8) -> Int8 +// Bitwise NOT +pub fn int8_not(value: Int8) Int8 { + return ~value; +} + +// int8_shl(value: Int8, amount: u8) -> Int8 +// Left shift (saturated) +pub fn int8_shl(value: Int8, amount: u8) Int8 { + const clamped = @min(amount, 7); + const result = @as(i16, value) << clamped; + return int8_from_i16(result); +} + +// int8_shr(value: Int8, amount: u8) -> Int8 +// Right shift (arithmetic, sign-preserving) +pub fn int8_shr(value: Int8, amount: u8) Int8 { + const clamped = @min(amount, 7); + return @shrExact(value, clamped); +} + +// int8_rol(value: Int8, amount: u8) -> Int8 +// Rotate left +pub fn int8_rol(value: Int8, amount: u8) Int8 { + const clamped = amount % 8; + const uval = @as(u8, @bitCast(value)); + const result = (uval << clamped) | (uval >> (8 - clamped)); + return @as(i8, @bitCast(result)); +} + +// int8_ror(value: Int8, amount: u8) -> Int8 +// Rotate right +pub fn int8_ror(value: Int8, amount: u8) Int8 { + const clamped = amount % 8; + const uval = @as(u8, @bitCast(value)); + const result = (uval >> clamped) | (uval << (8 - clamped)); + return @as(i8, @bitCast(result)); +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// int8_abs(value: Int8) -> Int8 +// Absolute value of Int8 with saturation +pub fn int8_abs(value: Int8) Int8 { + if (value == MIN) { + return MAX; // |-128| = 127 (saturated) + } + return if (value < 0) -value else value; +} + +// int8_neg(value: Int8) -> Int8 +// Negate Int8 (with saturation for -MIN) +pub fn int8_neg(value: Int8) Int8 { + if (value == MIN) { + return MAX; // -(-128) = 127 (saturated) + } + return -value; +} + +// int8_sign(value: Int8) -> Int8 +// Sign of value: -1, 0, or 1 +pub fn int8_sign(value: Int8) Int8 { + if (value < 0) { + return -1; + } else if (value > 0) { + return 1; + } + return 0; +} + +// int8_is_equal(a: Int8, b: Int8) -> bool +pub fn int8_is_equal(a: Int8, b: Int8) bool { + return a == b; +} + +// int8_is_greater(a: Int8, b: Int8) -> bool +pub fn int8_is_greater(a: Int8, b: Int8) bool { + return a > b; +} + +// int8_is_less(a: Int8, b: Int8) -> bool +pub fn int8_is_less(a: Int8, b: Int8) bool { + return a < b; +} + +// int8_min(a: Int8, b: Int8) -> Int8 +pub fn int8_min(a: Int8, b: Int8) Int8 { + return if (a < b) a else b; +} + +// int8_max(a: Int8, b: Int8) -> Int8 +pub fn int8_max(a: Int8, b: Int8) Int8 { + return if (a > b) a else b; +} + +// int8_clamp(value: Int8, min: Int8, max: Int8) -> Int8 +pub fn int8_clamp(value: Int8, min: Int8, max: Int8) Int8 { + if (value < min) { + return min; + } else if (value > max) { + return max; + } + return value; +} + +// int8_lerp(a: Int8, b: Int8, t: f32) -> Int8 +// Linear interpolation between a and b +pub fn int8_lerp(a: Int8, b: Int8, t: f32) Int8 { + const fa = @as(f32, @floatFromInt(a)); + const fb = @as(f32, @floatFromInt(b)); + const result = fa + (fb - fa) * t; + return int8_from_f32(result); +} + +// int8_sq(value: Int8) -> Int8 +// Square value with saturation +pub fn int8_sq(value: Int8) Int8 { + return int8_mul(value, value); +} + +// int8_abs_diff(a: Int8, b: Int8) -> Int8 +// Absolute difference between two Int8 values +pub fn int8_abs_diff(a: Int8, b: Int8) Int8 { + const diff = int8_sub(a, b); + return int8_abs(diff); +} + +// int8_sqrt(value: Int8) -> Int8 +// Integer square root (rounded down) +// Returns 0 for negative inputs +pub fn int8_sqrt(value: Int8) Int8 { + if (value < 0) { + return 0; + } + if (value == 0) { + return 0; + } + + var result: i8 = 0; + var bit: i8 = 1; + + // Find the highest power of 4 <= value + while (bit <= value and bit > 0) { + bit = bit * 4; + } + bit = bit / 4; + + while (bit != 0) { + if (value >= result + bit) { + value = value - (result + bit); + result = result / 2 + bit; + } else { + result = result / 2; + } + bit = bit / 4; + } + + return result; +} + +// int8_is_power_of_two(value: Int8) -> bool +// Check if value is a power of two (for positive values only) +pub fn int8_is_power_of_two(value: Int8) bool { + if (value <= 0) { + return false; + } + const uval = @as(u8, @bitCast(value)); + return (uval & (uval - 1)) == 0; +} + +// int8_count_ones(value: Int8) -> u8 +// Count the number of set bits +pub fn int8_count_ones(value: Int8) u8 { + const uval = @as(u8, @bitCast(value)); + return @popCount(uval); +} + +// int8_count_zeros(value: Int8) -> u8 +// Count the number of zero bits +pub fn int8_count_zeros(value: Int8) u8 { + return 8 - int8_count_ones(value); +} + +// int8_reverse_bits(value: Int8) -> Int8 +// Reverse the bits in an Int8 +pub fn int8_reverse_bits(value: Int8) Int8 { + const uval = @as(u8, @bitCast(value)); + var result: u8 = 0; + var i: u8 = 0; + while (i < 8) { + result = (result << 1) | (uval & 1); + uval = uval >> 1; + i = i + 1; + } + return @as(i8, @bitCast(result)); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "int8_from_i8_identity" { + given value = 42 + try std.testing.expect(result = int8_from_i8(value)); + try std.testing.expect(result == 42); +} + +test "int8_from_i16_positive" { + given value = 100 + try std.testing.expect(result = int8_from_i16(value)); + try std.testing.expect(result == 100); +} + +test "int8_from_i16_overflow" { + given value = 200 + try std.testing.expect(result = int8_from_i16(value)); + try std.testing.expect(result == MAX); +} + +test "int8_from_i16_negative" { + given value = -50 + try std.testing.expect(result = int8_from_i16(value)); + try std.testing.expect(result == -50); +} + +test "int8_from_i16_underflow" { + given value = -200 + try std.testing.expect(result = int8_from_i16(value)); + try std.testing.expect(result == MIN); +} + +test "int8_from_i32_in_range" { + given value = 42 + try std.testing.expect(result = int8_from_i32(value)); + try std.testing.expect(result == 42); +} + +test "int8_from_i32_overflow" { + given value = 1000 + try std.testing.expect(result = int8_from_i32(value)); + try std.testing.expect(result == MAX); +} + +test "int8_from_u8_in_range" { + given value = 100 + try std.testing.expect(result = int8_from_u8(value)); + try std.testing.expect(result == 100); +} + +test "int8_from_u8_overflow" { + given value = 200 + try std.testing.expect(result = int8_from_u8(value)); + try std.testing.expect(result == MAX); +} + +test "int8_from_f32_zero" { + given value = 0.0 + try std.testing.expect(result = int8_from_f32(value)); + try std.testing.expect(result == 0); +} + +test "int8_from_f32_one" { + given value = 1.0 + try std.testing.expect(result = int8_from_f32(value)); + try std.testing.expect(result == 1); +} + +test "int8_from_f32_round_up" { + given value = 1.6 + try std.testing.expect(result = int8_from_f32(value)); + try std.testing.expect(result == 2); +} + +test "int8_from_f32_round_down" { + given value = 1.4 + try std.testing.expect(result = int8_from_f32(value)); + try std.testing.expect(result == 1); +} + +test "int8_from_f32_nan" { + given result = int8_from_f32(std.math.nan(f32)) + try std.testing.expect(result == 0); +} + +test "int8_to_i8_identity" { + given value: Int8 = 42 + try std.testing.expect(result = int8_to_i8(value)); + try std.testing.expect(result == 42); +} + +test "int8_to_i16_sign_extend" { + given value: Int8 = -1 + try std.testing.expect(result = int8_to_i16(value)); + try std.testing.expect(result == -1); +} + +test "int8_to_i32_sign_extend" { + given value: Int8 = -1 + try std.testing.expect(result = int8_to_i32(value)); + try std.testing.expect(result == -1); +} + +test "int8_to_u8_positive" { + given value: Int8 = 42 + try std.testing.expect(result = int8_to_u8(value)); + try std.testing.expect(result.? == 42); +} + +test "int8_to_u8_negative" { + given value: Int8 = -1 + try std.testing.expect(result = int8_to_u8(value)); + try std.testing.expect(result == null); +} + +test "int8_add_simple" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 30); + try std.testing.expect(result = int8_add(a, b)); + try std.testing.expect(result == 80); +} + +test "int8_add_overflow" { + given a: Int8 = 100 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_add(a, b)); + try std.testing.expect(result == MAX); +} + +test "int8_add_underflow" { + given a: Int8 = -100 + try std.testing.expect(b: Int8 = -50); + try std.testing.expect(result = int8_add(a, b)); + try std.testing.expect(result == MIN); +} + +test "int8_sub_simple" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 30); + try std.testing.expect(result = int8_sub(a, b)); + try std.testing.expect(result == 20); +} + +test "int8_sub_negative" { + given a: Int8 = 30 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_sub(a, b)); + try std.testing.expect(result == -20); +} + +test "int8_mul_positive" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 5); + try std.testing.expect(result = int8_mul(a, b)); + try std.testing.expect(result == 50); +} + +test "int8_mul_negative" { + given a: Int8 = -10 + try std.testing.expect(b: Int8 = 5); + try std.testing.expect(result = int8_mul(a, b)); + try std.testing.expect(result == -50); +} + +test "int8_mul_overflow" { + given a: Int8 = 20 + try std.testing.expect(b: Int8 = 20); + try std.testing.expect(result = int8_mul(a, b)); + try std.testing.expect(result == MAX); +} + +test "int8_div_simple" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 5); + try std.testing.expect(result = int8_div(a, b)); + try std.testing.expect(result == 10); +} + +test "int8_div_by_zero_positive" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 0); + try std.testing.expect(result = int8_div(a, b)); + try std.testing.expect(result == MAX); +} + +test "int8_div_by_zero_negative" { + given a: Int8 = -50 + try std.testing.expect(b: Int8 = 0); + try std.testing.expect(result = int8_div(a, b)); + try std.testing.expect(result == MIN); +} + +test "int8_mod_simple" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 3); + try std.testing.expect(result = int8_mod(a, b)); + try std.testing.expect(result == 1); +} + +test "int8_mod_by_zero" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 0); + try std.testing.expect(result = int8_mod(a, b)); + try std.testing.expect(result == 0); +} + +test "int8_pow_zero" { + given base: Int8 = 5 + try std.testing.expect(exp: u8 = 0); + try std.testing.expect(result = int8_pow(base, exp)); + try std.testing.expect(result == 1); +} + +test "int8_pow_one" { + given base: Int8 = 5 + try std.testing.expect(exp: u8 = 1); + try std.testing.expect(result = int8_pow(base, exp)); + try std.testing.expect(result == 5); +} + +test "int8_pow_two" { + given base: Int8 = 5 + try std.testing.expect(exp: u8 = 2); + try std.testing.expect(result = int8_pow(base, exp)); + try std.testing.expect(result == 25); +} + +test "int8_pow_overflow" { + given base: Int8 = 20 + try std.testing.expect(exp: u8 = 2); + try std.testing.expect(result = int8_pow(base, exp)); + try std.testing.expect(result == MAX); +} + +test "int8_and_simple" { + given a: Int8 = 0b10101010 + try std.testing.expect(b: Int8 = 0b11001100); + try std.testing.expect(result = int8_and(a, b)); + try std.testing.expect(result == 0b10001000); +} + +test "int8_or_simple" { + given a: Int8 = 0b10101010 + try std.testing.expect(b: Int8 = 0b11001100); + try std.testing.expect(result = int8_or(a, b)); + try std.testing.expect(result == 0b11101110); +} + +test "int8_xor_simple" { + given a: Int8 = 0b10101010 + try std.testing.expect(b: Int8 = 0b11001100); + try std.testing.expect(result = int8_xor(a, b)); + try std.testing.expect(result == 0b01100110); +} + +test "int8_not_simple" { + given value: Int8 = 0b10101010 + try std.testing.expect(result = int8_not(value)); + try std.testing.expect(result == 0b01010101); +} + +test "int8_shl_simple" { + given value: Int8 = 0b00000011 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_shl(value, amount)); + try std.testing.expect(result == 0b00001100); +} + +test "int8_shl_overflow" { + given value: Int8 = 64 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_shl(value, amount)); + try std.testing.expect(result == MAX); +} + +test "int8_shr_simple" { + given value: Int8 = 0b00001100 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_shr(value, amount)); + try std.testing.expect(result == 0b00000011); +} + +test "int8_rol_simple" { + given value: Int8 = 0b00000011 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_rol(value, amount)); + try std.testing.expect(result == 0b00001100); +} + +test "int8_ror_simple" { + given value: Int8 = 0b00001100 + try std.testing.expect(amount: u8 = 2); + try std.testing.expect(result = int8_ror(value, amount)); + try std.testing.expect(result == 0b00000011); +} + +test "int8_abs_positive" { + given value: Int8 = 42 + try std.testing.expect(result = int8_abs(value)); + try std.testing.expect(result == 42); +} + +test "int8_abs_negative" { + given value: Int8 = -42 + try std.testing.expect(result = int8_abs(value)); + try std.testing.expect(result == 42); +} + +test "int8_abs_min" { + given value: Int8 = MIN + try std.testing.expect(result = int8_abs(value)); + try std.testing.expect(result == MAX); +} + +test "int8_neg_positive" { + given value: Int8 = 42 + try std.testing.expect(result = int8_neg(value)); + try std.testing.expect(result == -42); +} + +test "int8_neg_negative" { + given value: Int8 = -42 + try std.testing.expect(result = int8_neg(value)); + try std.testing.expect(result == 42); +} + +test "int8_neg_min" { + given value: Int8 = MIN + try std.testing.expect(result = int8_neg(value)); + try std.testing.expect(result == MAX); +} + +test "int8_sign_positive" { + given value: Int8 = 42 + try std.testing.expect(result = int8_sign(value)); + try std.testing.expect(result == 1); +} + +test "int8_sign_negative" { + given value: Int8 = -42 + try std.testing.expect(result = int8_sign(value)); + try std.testing.expect(result == -1); +} + +test "int8_sign_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_sign(value)); + try std.testing.expect(result == 0); +} + +test "int8_min_smaller" { + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_min(a, b)); + try std.testing.expect(result == 42); +} + +test "int8_max_larger" { + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_max(a, b)); + try std.testing.expect(result == 50); +} + +test "int8_clamp_in_range" { + given value: Int8 = 42 + try std.testing.expect(min: Int8 = 0); + try std.testing.expect(max: Int8 = 100); + try std.testing.expect(result = int8_clamp(value, min, max)); + try std.testing.expect(result == 42); +} + +test "int8_clamp_below" { + given value: Int8 = -50 + try std.testing.expect(min: Int8 = 0); + try std.testing.expect(max: Int8 = 100); + try std.testing.expect(result = int8_clamp(value, min, max)); + try std.testing.expect(result == 0); +} + +test "int8_clamp_above" { + given value: Int8 = 150 + try std.testing.expect(min: Int8 = 0); + try std.testing.expect(max: Int8 = 100); + try std.testing.expect(result = int8_clamp(value, min, max)); + try std.testing.expect(result == 100); +} + +test "int8_lerp_zero" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = int8_lerp(a, b, t)); + try std.testing.expect(result == 10); +} + +test "int8_lerp_one" { + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = int8_lerp(a, b, t)); + try std.testing.expect(result == 50); +} + +test "int8_lerp_half" { + given a: Int8 = 0 + try std.testing.expect(b: Int8 = 20); + try std.testing.expect(t = 0.5); + try std.testing.expect(result = int8_lerp(a, b, t)); + try std.testing.expect(result == 10); +} + +test "int8_sq_positive" { + given value: Int8 = 10 + try std.testing.expect(result = int8_sq(value)); + try std.testing.expect(result == 100); +} + +test "int8_sq_negative" { + given value: Int8 = -10 + try std.testing.expect(result = int8_sq(value)); + try std.testing.expect(result == 100); +} + +test "int8_sq_overflow" { + given value: Int8 = 20 + try std.testing.expect(result = int8_sq(value)); + try std.testing.expect(result == MAX); +} + +test "int8_abs_diff_same" { + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 42); + try std.testing.expect(result = int8_abs_diff(a, b)); + try std.testing.expect(result == 0); +} + +test "int8_abs_diff_positive" { + given a: Int8 = 50 + try std.testing.expect(b: Int8 = 30); + try std.testing.expect(result = int8_abs_diff(a, b)); + try std.testing.expect(result == 20); +} + +test "int8_sqrt_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 0); +} + +test "int8_sqrt_one" { + given value: Int8 = 1 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 1); +} + +test "int8_sqrt_four" { + given value: Int8 = 4 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 2); +} + +test "int8_sqrt_sixteen" { + given value: Int8 = 16 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 4); +} + +test "int8_sqrt_negative" { + given value: Int8 = -4 + try std.testing.expect(result = int8_sqrt(value)); + try std.testing.expect(result == 0); +} + +test "int8_is_power_of_two_true" { + given value: Int8 = 8 + try std.testing.expect(int8_is_power_of_two(value) == true); +} + +test "int8_is_power_of_two_false" { + given value: Int8 = 6 + try std.testing.expect(int8_is_power_of_two(value) == false); +} + +test "int8_is_power_of_two_zero" { + given value: Int8 = 0 + try std.testing.expect(int8_is_power_of_two(value) == false); +} + +test "int8_count_ones_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_count_ones(value)); + try std.testing.expect(result == 0); +} + +test "int8_count_ones_all" { + given value: Int8 = 0xFF + try std.testing.expect(result = int8_count_ones(value)); + try std.testing.expect(result == 8); +} + +test "int8_count_ones_mixed" { + given value: Int8 = 0b10101010 + try std.testing.expect(result = int8_count_ones(value)); + try std.testing.expect(result == 4); +} + +test "int8_count_zeros_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_count_zeros(value)); + try std.testing.expect(result == 8); +} + +test "int8_count_zeros_all" { + given value: Int8 = 0xFF + try std.testing.expect(result = int8_count_zeros(value)); + try std.testing.expect(result == 0); +} + +test "int8_reverse_bits_zero" { + given value: Int8 = 0 + try std.testing.expect(result = int8_reverse_bits(value)); + try std.testing.expect(result == 0); +} + +test "int8_reverse_bits_one" { + given value: Int8 = 0b10000000 + try std.testing.expect(result = int8_reverse_bits(value)); + try std.testing.expect(result == 0b00000001); +} + +test "int8_reverse_bits_all" { + given value: Int8 = 0xFF + try std.testing.expect(result = int8_reverse_bits(value)); + try std.testing.expect(result == 0xFF); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant int8_bits_constant + assert BITS == 8 + +invariant int8_min_is_negative_128 + assert MIN == -128 + +invariant int8_max_is_127 + assert MAX == 127 + +invariant int8_range_is_256 + assert RANGE == 256 + +invariant int8_mask_all_bits + assert MASK == 0xFF + +invariant int8_from_i16_min_saturates + assert int8_from_i16(-1000) == MIN + +invariant int8_from_i16_max_saturates + assert int8_from_i16(1000) == MAX + +invariant int8_to_i16_identity + given value: Int8 = 42 + assert int8_from_i16(int8_to_i16(value)) == value + +invariant int8_to_u8_negative_null + assert int8_to_u8(-1) == null + +invariant int8_to_u8_positive_ok + given result = int8_to_u8(42) + assert result.? == 42 + +invariant int8_add_zero + assert int8_add(42, 0) == 42 + +invariant int8_sub_zero + assert int8_sub(42, 0) == 42 + +invariant int8_mul_one + assert int8_mul(42, 1) == 42 + +invariant int8_mul_zero + assert int8_mul(42, 0) == 0 + +invariant int8_div_one + assert int8_div(42, 1) == 42 + +invariant int8_pow_zero_one + assert int8_pow(42, 0) == 1 + +invariant int8_pow_one_self + assert int8_pow(42, 1) == 42 + +invariant int8_and_zero + assert int8_and(42, 0) == 0 + +invariant int8_and_minus_one + assert int8_and(42, -1) == 42 + +invariant int8_or_zero + assert int8_or(42, 0) == 42 + +invariant int8_or_minus_one + assert int8_or(42, -1) == -1 + +invariant int8_xor_zero + assert int8_xor(42, 0) == 42 + +invariant int8_xor_self + assert int8_xor(42, 42) == 0 + +invariant int8_not_double + given value: Int8 = 42 + assert int8_not(int8_not(value)) == value + +invariant int8_abs_non_negative + given value: Int8 = -42 + assert int8_abs(value) >= 0 + +invariant int8_neg_twice_original + given value: Int8 = 42 + try std.testing.expect(neg = int8_neg(value)); + try std.testing.expect(neg_again = int8_neg(neg)); + assert neg_again == value or value == MIN + +invariant int8_sign_positive_one + assert int8_sign(42) == 1 + +invariant int8_sign_negative_minus_one + assert int8_sign(-42) == -1 + +invariant int8_sign_zero_zero + assert int8_sign(0) == 0 + +invariant int8_min_le_both + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_min(a, b)); + assert result <= a and result <= b + +invariant int8_max_ge_both + given a: Int8 = 42 + try std.testing.expect(b: Int8 = 50); + try std.testing.expect(result = int8_max(a, b)); + assert result >= a and result >= b + +invariant int8_lerp_zero_start + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 50); + assert int8_lerp(a, b, 0.0) == a + +invariant int8_lerp_one_end + given a: Int8 = 10 + try std.testing.expect(b: Int8 = 50); + assert int8_lerp(a, b, 1.0) == b + +invariant int8_sq_non_negative + given value: Int8 = 42 + try std.testing.expect(result = int8_sq(value)); + assert result >= 0 + +invariant int8_sqrt_square_original_or_less + given value: Int8 = 42 + try std.testing.expect(sqrt_result = int8_sqrt(value)); + try std.testing.expect(sqrt_sq = int8_sq(sqrt_result)); + assert sqrt_sq <= value + +invariant int8_count_ones_plus_count_zeros_equals_bits + given value: Int8 = 42 + assert int8_count_ones(value) + int8_count_zeros(value) == BITS + +invariant int8_reverse_bits_double + given value: Int8 = 42 + assert int8_reverse_bits(int8_reverse_bits(value)) == value + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench int8_add_latency + measure: nanoseconds to int8_add(42, 30) + target: < 5ns + +bench int8_mul_latency + measure: nanoseconds to int8_mul(10, 5) + target: < 5ns + +bench int8_abs_latency + measure: nanoseconds to int8_abs(-42) + target: < 5ns + +bench int8_from_f32_latency + measure: nanoseconds to int8_from_f32(42.5) + target: < 20ns + +bench int8_sqrt_latency + measure: nanoseconds to int8_sqrt(42) + target: < 50ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/nf4.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/nf4.t27 new file mode 100644 index 0000000000..005f676f71 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/nf4.t27 @@ -0,0 +1,839 @@ +// SPDX-License-Identifier: Apache-2.0 +; nf4.t27 — NormalFloat4 Quantization +; 4-bit quantization based on normalized distribution +; Values: {-1, -0.667, -0.333, 0, 0.333, 0.667, 1, 0} (8 levels + zero) +; φ² + 1/φ² = 3 | TRINITY + +module triformat-nf4; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 4; +pub const LEVELS : u8 = 8; // Non-zero levels +pub const TOTAL_LEVELS : u8 = 16; // Including signed variants + +// NF4 uses 2 bits for magnitude index (1-4) and 1 bit for sign +// Index 0 = zero (special case) +// Levels (normalized): [0.0, 0.333, 0.667, 1.0] + +pub const NUM_NEGATIVE_LEVELS : u8 = 3; +pub const NUM_POSITIVE_LEVELS : u8 = 3; + +// ============================================================================ +// Types +// ============================================================================ + +pub const NF4 = u8; // 4-bit value stored in lower 4 bits of u8 + +// ============================================================================ +// LUT Tables +// ============================================================================ + +// Normalized float levels (de-quantization table) +// Index 0 = 0.0 (special zero), indices 1-7 map to levels +const LEVELS_FLOAT : [8]f32 = [8]f32{ + 0.0, // 0: zero + 0.333, // 1: 1/3 + 0.667, // 2: 2/3 + 1.0, // 3: max + -0.333, // 4: -1/3 + -0.667, // 5: -2/3 + -1.0, // 6: -max + 0.0, // 7: also zero (for rounding tie-breaking) +}; + +// More precise levels using phi-optimized values +// φ-optimized NF4 levels based on quartiles of normal distribution +const LEVELS_PHI : [8]f32 = [8]f32{ + 0.0, // 0: zero + 0.4167, // 1: ~-1σ * 0.618 + 0.75, // 2: -μ/2 * φ + 1.0, // 3: max + -0.4167, // 4: negative of 1 + -0.75, // 5: negative of 2 + -1.0, // 6: negative of 3 + 0.0, // 7: zero +}; + +// Use phi-optimized levels by default +pub const QUANTIZATION_LEVELS : [8]f32 = LEVELS_PHI; + +// ============================================================================ +// Conversion Functions +// ============================================================================ + +// nf4_from_f32(value: f32) -> NF4 +// Quantize f32 to NF4 +// Returns 4-bit value in lower bits of u8 +pub fn nf4_from_f32(value: f32) NF4 { + // Handle NaN and infinity + if (std.math.isNan(value) or std.math.isInf(value)) { + return 0; // Return zero for special values + } + + const abs_value = @abs(value); + + // Clamp to [-1, 1] + if (abs_value > 1.0) { + if (value > 0.0) { + return 0b011; // +1.0 + } else { + return 0b110; // -1.0 + } + } + + // Zero threshold + if (abs_value < 0.2) { + return 0b000; // zero + } + + // Determine level (1, 2, or 3 for magnitude) + var level: u3 = 0; + if (abs_value < 0.58) { + level = 1; // ~0.4167 + } else if (abs_value < 0.87) { + level = 2; // ~0.75 + } else { + level = 3; // 1.0 + } + + // Apply sign bit (bit 2) + if (value < 0.0) { + return @as(NF4, @intCast(0b100 | level)); + } else { + return @as(NF4, @intCast(level)); + } +} + +// nf4_from_f64(value: f64) -> NF4 +// Quantize f64 to NF4 +pub fn nf4_from_f64(value: f64) NF4 { + return nf4_from_f32(@as(f32, @floatCast(value))); +} + +// nf4_from_i8(value: i8) -> NF4 +// Quantize i8 to NF4 (normalized to [-1, 1]) +pub fn nf4_from_i8(value: i8) NF4 { + // Normalize i8 [-128, 127] to [-1, 1] + const normalized = @as(f32, @floatFromInt(value)) / 127.0; + return nf4_from_f32(normalized); +} + +// nf4_to_f32(value: NF4) -> f32 +// De-quantize NF4 to f32 +pub fn nf4_to_f32(value: NF4) f32 { + const index = value & 0b111; + return QUANTIZATION_LEVELS[@as(usize, @intCast(index))]; +} + +// nf4_to_f64(value: NF4) -> f64 +// De-quantize NF4 to f64 +pub fn nf4_to_f64(value: NF4) f64 { + return @as(f64, @floatFromInt(nf4_to_f32(value))); +} + +// nf4_to_i8(value: NF4) -> i8 +// De-quantize NF4 to i8 (scale from [-1, 1] to [-128, 127]) +pub fn nf4_to_i8(value: NF4) i8 { + const fval = nf4_to_f32(value); + const scaled = fval * 127.0; + return @as(i8, @intFromFloat(@round(scaled))); +} + +// ============================================================================ +// Arithmetic Operations (in f32 domain) +// ============================================================================ + +// nf4_add(a: NF4, b: NF4) -> NF4 +// Add two NF4 values (de-quantize, add, re-quantize) +pub fn nf4_add(a: NF4, b: NF4) NF4 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return nf4_from_f32(fa + fb); +} + +// nf4_sub(a: NF4, b: NF4) -> NF4 +// Subtract two NF4 values +pub fn nf4_sub(a: NF4, b: NF4) NF4 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return nf4_from_f32(fa - fb); +} + +// nf4_mul(a: NF4, b: NF4) -> NF4 +// Multiply two NF4 values +pub fn nf4_mul(a: NF4, b: NF4) NF4 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return nf4_from_f32(fa * fb); +} + +// nf4_div(a: NF4, b: NF4) -> NF4 +// Divide two NF4 values +pub fn nf4_div(a: NF4, b: NF4) NF4 { + const fb = nf4_to_f32(b); + if (@abs(fb) < 0.001) { + return 0; // Return zero for division by near-zero + } + const fa = nf4_to_f32(a); + return nf4_from_f32(fa / fb); +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +// nf4_is_zero(value: NF4) -> bool +// Check if NF4 value is zero +pub fn nf4_is_zero(value: NF4) bool { + return (value & 0b111) == 0; +} + +// nf4_is_negative(value: NF4) -> bool +// Check if NF4 value is negative +pub fn nf4_is_negative(value: NF4) bool { + return (value & 0b100) != 0 and !nf4_is_zero(value); +} + +// nf4_is_positive(value: NF4) -> bool +// Check if NF4 value is positive +pub fn nf4_is_positive(value: NF4) bool { + return (value & 0b111) != 0 and (value & 0b100) == 0; +} + +// nf4_abs(value: NF4) -> NF4 +// Absolute value of NF4 +pub fn nf4_abs(value: NF4) NF4 { + return value & 0b011; +} + +// nf4_neg(value: NF4) -> NF4 +// Negate NF4 +pub fn nf4_neg(value: NF4) NF4 { + const magnitude = value & 0b011; + if (magnitude == 0) { + return 0; // Zero stays zero + } + return magnitude | 0b100; +} + +// nf4_is_equal(a: NF4, b: NF4) -> bool +// Check if two NF4 values are equal +pub fn nf4_is_equal(a: NF4, b: NF4) bool { + return a == b; +} + +// nf4_is_greater(a: NF4, b: NF4) -> bool +// Check if a > b +pub fn nf4_is_greater(a: NF4, b: NF4) bool { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return fa > fb; +} + +// nf4_is_less(a: NF4, b: NF4) -> bool +// Check if a < b +pub fn nf4_is_less(a: NF4, b: NF4) bool { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return fa < fb; +} + +// nf4_max(a: NF4, b: NF4) -> NF4 +// Return the larger of two NF4 values +pub fn nf4_max(a: NF4, b: NF4) NF4 { + return if (nf4_is_greater(a, b)) a else b; +} + +// nf4_min(a: NF4, b: NF4) -> NF4 +// Return the smaller of two NF4 values +pub fn nf4_min(a: NF4, b: NF4) NF4 { + return if (nf4_is_greater(a, b)) b else a; +} + +// nf4_clamp(value: NF4, min: NF4, max: NF4) -> NF4 +// Clamp value between min and max +pub fn nf4_clamp(value: NF4, min: NF4, max: NF4) NF4 { + return nf4_min(nf4_max(value, min), max); +} + +// nf4_lerp(a: NF4, b: NF4, t: f32) -> NF4 +// Linear interpolation between a and b +pub fn nf4_lerp(a: NF4, b: NF4, t: f32) NF4 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + const result = fa + (fb - fa) * t; + return nf4_from_f32(result); +} + +// nf4_magnitude(value: NF4) -> f32 +// Get the absolute magnitude of NF4 +pub fn nf4_magnitude(value: NF4) f32 { + return nf4_to_f32(nf4_abs(value)); +} + +// nf4_l1_distance(a: NF4, b: NF4) -> f32 +// L1 distance between two NF4 values +pub fn nf4_l1_distance(a: NF4, b: NF4) f32 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + return @abs(fa - fb); +} + +// nf4_l2_distance(a: NF4, b: NF4) -> f32 +// L2 distance between two NF4 values +pub fn nf4_l2_distance(a: NF4, b: NF4) -> f32 { + const fa = nf4_to_f32(a); + const fb = nf4_to_f32(b); + const diff = fa - fb; + return diff * diff; +} + +// nf4_scale(value: NF4, scale: f32) -> NF4 +// Scale NF4 value +pub fn nf4_scale(value: NF4, scale: f32) NF4 { + const fa = nf4_to_f32(value); + return nf4_from_f32(fa * scale); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "nf4_from_f32_zero" { + given value = 0.0 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0); +} + +test "nf4_from_f32_small_positive" { + given value = 0.1 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(nf4_is_zero(result) == true); +} + +test "nf4_from_f32_medium_positive" { + given value = 0.5 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b010 // level 2); +} + +test "nf4_from_f32_large_positive" { + given value = 0.9 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b011 // level 3 (max)); +} + +test "nf4_from_f32_max_positive" { + given value = 1.0 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b011); +} + +test "nf4_from_f32_above_max" { + given value = 1.5 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b011 // saturated to max); +} + +test "nf4_from_f32_small_negative" { + given value = -0.1 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(nf4_is_zero(result) == true); +} + +test "nf4_from_f32_medium_negative" { + given value = -0.5 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b110 // level 2, negative); +} + +test "nf4_from_f32_large_negative" { + given value = -0.9 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b111 // level 3, negative (min)); +} + +test "nf4_from_f32_max_negative" { + given value = -1.0 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b111); +} + +test "nf4_from_f32_below_min" { + given value = -1.5 + try std.testing.expect(result = nf4_from_f32(value)); + try std.testing.expect(result == 0b111 // saturated to min); +} + +test "nf4_from_f32_nan" { + given result = nf4_from_f32(std.math.nan(f32)) + try std.testing.expect(result == 0); +} + +test "nf4_from_f32_inf" { + given result = nf4_from_f32(std.math.inf(f32)) + try std.testing.expect(result == 0b011); +} + +test "nf4_from_f32_neg_inf" { + given result = nf4_from_f32(-std.math.inf(f32)) + try std.testing.expect(result == 0b111); +} + +test "nf4_to_f32_zero" { + given value: NF4 = 0 + try std.testing.expect(result = nf4_to_f32(value)); + try std.testing.expect(result == 0.0); +} + +test "nf4_to_f32_positive_max" { + given value: NF4 = 0b011 + try std.testing.expect(result = nf4_to_f32(value)); + try std.testing.expect(result == 1.0); +} + +test "nf4_to_f32_negative_min" { + given value: NF4 = 0b111 + try std.testing.expect(result = nf4_to_f32(value)); + try std.testing.expect(result == -1.0); +} + +test "nf4_to_f32_roundtrip_positive" { + given original = 0.7 + try std.testing.expect(encoded = nf4_from_f32(original)); + try std.testing.expect(decoded = nf4_to_f32(encoded)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "nf4_to_f32_roundtrip_negative" { + given original = -0.7 + try std.testing.expect(encoded = nf4_from_f32(original)); + try std.testing.expect(decoded = nf4_to_f32(encoded)); + try std.testing.expect(abs(decoded - original) < 0.2); +} + +test "nf4_from_i8_zero" { + given value: i8 = 0 + try std.testing.expect(result = nf4_from_i8(value)); + try std.testing.expect(result == 0); +} + +test "nf4_from_i8_max" { + given value: i8 = 127 + try std.testing.expect(result = nf4_from_i8(value)); + try std.testing.expect(result == 0b011); +} + +test "nf4_from_i8_min" { + given value: i8 = -128 + try std.testing.expect(result = nf4_from_i8(value)); + try std.testing.expect(result == 0b111); +} + +test "nf4_from_i8_medium" { + given value: i8 = 64 + try std.testing.expect(result = nf4_from_i8(value)); + try std.testing.expect(result == 0b011 // 64/127 ≈ 0.5 -> level 3); +} + +test "nf4_to_i8_max" { + given value: NF4 = 0b011 + try std.testing.expect(result = nf4_to_i8(value)); + try std.testing.expect(result >= 120 // Should be ~127); +} + +test "nf4_to_i8_min" { + given value: NF4 = 0b111 + try std.testing.expect(result = nf4_to_i8(value)); + try std.testing.expect(result <= -120 // Should be ~-127); +} + +test "nf4_add_zero" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0); + try std.testing.expect(result = nf4_add(a, b)); + try std.testing.expect(result == a); +} + +test "nf4_add_positives" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b001); + try std.testing.expect(result = nf4_add(a, b)); + try std.testing.expect(nf4_is_positive(result) == true); +} + +test "nf4_add_opposite" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b110); + try std.testing.expect(result = nf4_add(a, b)); + try std.testing.expect(nf4_is_zero(result) == true or nf4_magnitude(result) < 0.5); +} + +test "nf4_sub_same" { + given a: NF4 = 0b010 + try std.testing.expect(result = nf4_sub(a, a)); + try std.testing.expect(result == 0); +} + +test "nf4_mul_zero" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0); + try std.testing.expect(result = nf4_mul(a, b)); + try std.testing.expect(result == 0); +} + +test "nf4_mul_max_max" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_mul(a, b)); + try std.testing.expect(result == 0b011 // 1.0 * 1.0 = 1.0); +} + +test "nf4_mul_pos_neg" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b111); + try std.testing.expect(result = nf4_mul(a, b)); + try std.testing.expect(nf4_is_negative(result) == true); +} + +test "nf4_div_same" { + given a: NF4 = 0b010 + try std.testing.expect(result = nf4_div(a, a)); + try std.testing.expect(result == 0b001 // x/x = 1); +} + +test "nf4_div_zero" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0); + try std.testing.expect(result = nf4_div(a, b)); + try std.testing.expect(result == 0); +} + +test "nf4_is_zero_true" { + try std.testing.expect(nf4_is_zero(0) == true); +} + +test "nf4_is_zero_false" { + given value: NF4 = 0b001 + try std.testing.expect(nf4_is_zero(value) == false); +} + +test "nf4_is_negative_true" { + try std.testing.expect(nf4_is_negative(0b111) == true); +} + +test "nf4_is_negative_false_positive" { + try std.testing.expect(nf4_is_negative(0b011) == false); +} + +test "nf4_is_negative_false_zero" { + try std.testing.expect(nf4_is_negative(0) == false); +} + +test "nf4_is_positive_true" { + try std.testing.expect(nf4_is_positive(0b011) == true); +} + +test "nf4_is_positive_false_negative" { + try std.testing.expect(nf4_is_positive(0b111) == false); +} + +test "nf4_is_positive_false_zero" { + try std.testing.expect(nf4_is_positive(0) == false); +} + +test "nf4_abs_positive" { + given value: NF4 = 0b010 + try std.testing.expect(result = nf4_abs(value)); + try std.testing.expect(result == value); +} + +test "nf4_abs_negative" { + given value: NF4 = 0b110 + try std.testing.expect(result = nf4_abs(value)); + try std.testing.expect(result == 0b010); +} + +test "nf4_abs_zero" { + given result = nf4_abs(0) + try std.testing.expect(result == 0); +} + +test "nf4_neg_positive" { + given value: NF4 = 0b010 + try std.testing.expect(result = nf4_neg(value)); + try std.testing.expect(result == 0b110); +} + +test "nf4_neg_negative" { + given value: NF4 = 0b110 + try std.testing.expect(result = nf4_neg(value)); + try std.testing.expect(result == 0b010); +} + +test "nf4_neg_zero" { + given result = nf4_neg(0) + try std.testing.expect(result == 0); +} + +test "nf4_is_equal_true" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b010); + try std.testing.expect(nf4_is_equal(a, b) == true); +} + +test "nf4_is_equal_false" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b110); + try std.testing.expect(nf4_is_equal(a, b) == false); +} + +test "nf4_is_greater_true" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b010); + try std.testing.expect(nf4_is_greater(a, b) == true); +} + +test "nf4_is_greater_false" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(nf4_is_greater(a, b) == false); +} + +test "nf4_is_less_true" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(nf4_is_less(a, b) == true); +} + +test "nf4_is_less_false" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b010); + try std.testing.expect(nf4_is_less(a, b) == false); +} + +test "nf4_max_returns_larger" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_max(a, b)); + try std.testing.expect(result == 0b011); +} + +test "nf4_min_returns_smaller" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_min(a, b)); + try std.testing.expect(result == 0b010); +} + +test "nf4_clamp_in_range" { + given value: NF4 = 0b010 + try std.testing.expect(min: NF4 = 0b001); + try std.testing.expect(max: NF4 = 0b011); + try std.testing.expect(result = nf4_clamp(value, min, max)); + try std.testing.expect(result == value); +} + +test "nf4_clamp_below_min" { + given value: NF4 = 0 + try std.testing.expect(min: NF4 = 0b001); + try std.testing.expect(max: NF4 = 0b011); + try std.testing.expect(result = nf4_clamp(value, min, max)); + try std.testing.expect(result == min); +} + +test "nf4_clamp_above_max" { + given value: NF4 = 0b111 + try std.testing.expect(min: NF4 = 0b001); + try std.testing.expect(max: NF4 = 0b011); + try std.testing.expect(result = nf4_clamp(value, min, max)); + try std.testing.expect(result == max); +} + +test "nf4_lerp_zero" { + given a: NF4 = 0b001 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = nf4_lerp(a, b, t)); + try std.testing.expect(result == a); +} + +test "nf4_lerp_one" { + given a: NF4 = 0b001 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = nf4_lerp(a, b, t)); + try std.testing.expect(result == b); +} + +test "nf4_lerp_half" { + given a: NF4 = 0 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(t = 0.5); + try std.testing.expect(result = nf4_lerp(a, b, t)); + try std.testing.expect(nf4_magnitude(result) > 0.3); +} + +test "nf4_magnitude_positive" { + given value: NF4 = 0b011 + try std.testing.expect(result = nf4_magnitude(value)); + try std.testing.expect(result == 1.0); +} + +test "nf4_magnitude_negative" { + given value: NF4 = 0b111 + try std.testing.expect(result = nf4_magnitude(value)); + try std.testing.expect(result == 1.0); +} + +test "nf4_l1_distance_same" { + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b010); + try std.testing.expect(result = nf4_l1_distance(a, b)); + try std.testing.expect(result == 0.0); +} + +test "nf4_l1_distance_max_min" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b111); + try std.testing.expect(result = nf4_l1_distance(a, b)); + try std.testing.expect(result == 2.0); +} + +test "nf4_l2_distance_max_min" { + given a: NF4 = 0b011 + try std.testing.expect(b: NF4 = 0b111); + try std.testing.expect(result = nf4_l2_distance(a, b)); + try std.testing.expect(result == 4.0); +} + +test "nf4_scale_up" { + given value: NF4 = 0b001 + try std.testing.expect(scale = 2.0); + try std.testing.expect(result = nf4_scale(value, scale)); + try std.testing.expect(nf4_magnitude(result) > 0.4); +} + +test "nf4_scale_down" { + given value: NF4 = 0b011 + try std.testing.expect(scale = 0.5); + try std.testing.expect(result = nf4_scale(value, scale)); + try std.testing.expect(nf4_magnitude(result) < 1.0); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant nf4_bits_constant + assert BITS == 4 + +invariant nf4_levels_count + assert LEVELS == 8 + +invariant nf4_total_levels + assert TOTAL_LEVELS == 16 + +invariant nf4_quantization_levels_length + assert QUANTIZATION_LEVELS.len == 8 + +invariant nf4_zero_is_zero + assert nf4_is_zero(0) == true + +invariant nf4_zero_not_negative + assert nf4_is_negative(0) == false + +invariant nf4_zero_not_positive + assert nf4_is_positive(0) == false + +invariant nf4_abs_zero_is_zero + assert nf4_abs(0) == 0 + +invariant nf4_neg_zero_is_zero + assert nf4_neg(0) == 0 + +invariant nf4_max_positive_is_max + given max_val: NF4 = 0b011 + try std.testing.expect(fmax = nf4_to_f32(max_val)); + assert fmax == 1.0 + +invariant nf4_min_negative_is_min + given min_val: NF4 = 0b111 + try std.testing.expect(fmin = nf4_to_f32(min_val)); + assert fmin == -1.0 + +invariant nf4_abs_neg_is_pos + given value: NF4 = 0b110 + try std.testing.expect(abs_val = nf4_abs(value)); + assert nf4_is_positive(abs_val) == true + +invariant nf4_neg_neg_is_pos + given value: NF4 = 0b110 + try std.testing.expect(neg_val = nf4_neg(value)); + assert nf4_is_positive(neg_val) == true + +invariant nf4_double_neg_original_or_zero + given value: NF4 = 0b010 + try std.testing.expect(neg_val = nf4_neg(value)); + try std.testing.expect(neg_neg_val = nf4_neg(neg_val)); + assert neg_neg_val == value or value == 0 + +invariant nf4_abs_non_negative + given value: NF4 = 0b111 + try std.testing.expect(abs_val = nf4_abs(value)); + assert nf4_to_f32(abs_val) >= 0.0 + +invariant nf4_l1_distance_symmetric + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b111); + assert nf4_l1_distance(a, b) == nf4_l1_distance(b, a) + +invariant nf4_l2_distance_symmetric + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b111); + assert nf4_l2_distance(a, b) == nf4_l2_distance(b, a) + +invariant nf4_l1_distance_non_negative + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b111); + assert nf4_l1_distance(a, b) >= 0.0 + +invariant nf4_max_ge_both + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_max(a, b)); + assert nf4_is_greater(result, a) == true and nf4_is_greater(result, b) == true or nf4_is_equal(result, a) == true + +invariant nf4_min_le_both + given a: NF4 = 0b010 + try std.testing.expect(b: NF4 = 0b011); + try std.testing.expect(result = nf4_min(a, b)); + assert nf4_is_less(result, a) == true or nf4_is_equal(result, a) == true + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench nf4_from_f32_latency + measure: nanoseconds to nf4_from_f32(0.7) + target: < 50ns + +bench nf4_to_f32_latency + measure: nanoseconds to nf4_to_f32(0b010) + target: < 10ns + +bench nf4_add_latency + measure: nanoseconds to nf4_add(0b010, 0b001) + target: < 100ns + +bench nf4_mul_latency + measure: nanoseconds to nf4_mul(0b010, 0b011) + target: < 100ns + +bench nf4_abs_latency + measure: nanoseconds to nf4_abs(0b111) + target: < 5ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/phi_ratio.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/phi_ratio.t27 new file mode 100644 index 0000000000..9a172ad518 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/phi_ratio.t27 @@ -0,0 +1,694 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/phi_ratio.t27 +// 0-Ratio Proof 1 Derivation of GoldenFloat exp/mantissa split +// NUMERIC-STANDARD-001 2 Agent 9 (P0) + +module PhiRatio { + // Import sacred constants + use math::constants; + use math::sacred_physics; + + // 345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 + // 1. Golden Ratio Target for Float Formats + // 6869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 + + // The ideal exp/mantissa ratio for floating point formats + // Derived from sacred physics: 1/141 142 0.618 + const PHI_RATIO_TARGET : f64 = sacred_physics::PHI_INV; // 0.618... + + // 143144 = 145 + 1 (golden ratio identity) + // This gives us: 1/146 = 147 - 1 148 0.618 + const PHI_SQ : f64 = sacred_physics::PHI * sacred_physics::PHI; + + // 149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 + // 2. 214-Split Formula 215 Derive optimal exp/mantissa bits + // 216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 + + // For a floating point format with N bits total (including sign): + // bits = sign + exp + mant + // sign = 1 (always) + // available = N - 1 = exp + mant + // + // The 289-principle states: exp/mant = 1/290 + // exp = (available) / (291 + 1) + // mant = available - exp + // + // Since 292 + 1 = 293294, we have: + // exp = (N - 1) / 295296 + // mant = N - 1 - exp + + struct PhiSplitResult { + exp_bits : u8, + mant_bits : u8, + ratio : f64, + phi_dist : f64, + } + + fn phi_split(bits: u8) -> PhiSplitResult { + const available = bits - 1; // Exclude sign bit + const phi_sq = sacred_physics::PHI * sacred_physics::PHI; + + // exp = round((N-1) / 297298) + const exp_raw = (available as f64) / phi_sq; + const exp_bits = round(exp_raw) as u8; + + // mant = N - 1 - exp + const mant_bits = available - exp_bits; + + const ratio = (exp_bits as f64) / (mant_bits as f64); + const phi_dist = abs(ratio - PHI_RATIO_TARGET); + + return PhiSplitResult{ + exp_bits = exp_bits, + mant_bits = mant_bits, + ratio = ratio, + phi_dist = phi_dist, + }; + } + + // 299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 + // 3. Verify GoldenFloat Family against 364-Split + // 365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 + + struct FormatComparison { + name : string, + bits : u8, + actual_exp : u8, + actual_mant : u8, + phi_split_exp : u8, + phi_split_mant : u8, + matches_phi_split : bool, + tradeoff_note : string, + } + + fn verify_phi_split() -> [10]FormatComparison { + return [ + // GF4: 438-split gives exp=1, mant=2 439 MATCH + FormatComparison{ + name = "GF4", + bits = 4, + actual_exp = 1, + actual_mant = 2, + phi_split_exp = 1, + phi_split_mant = 2, + matches_phi_split = true, + tradeoff_note = "Perfect 440-split match", + }, + // GF8: 441-split gives exp=2, mant=5 442 actual is 3/4 + FormatComparison{ + name = "GF8", + bits = 8, + actual_exp = 3, + actual_mant = 4, + phi_split_exp = 3, + phi_split_mant = 4, + matches_phi_split = true, + tradeoff_note = "Exact match: round(7/φ²)=3", + }, + // GF12: 443-split gives exp=3, mant=8 444 actual is 4/7 + FormatComparison{ + name = "GF12", + bits = 12, + actual_exp = 4, + actual_mant = 7, + phi_split_exp = 4, + phi_split_mant = 7, + matches_phi_split = true, + tradeoff_note = "Exact match: round(11/φ²)=4", + }, + // GF16: 445-split gives exp=4, mant=11 446 actual is 6/9 + FormatComparison{ + name = "GF16", + bits = 16, + actual_exp = 6, + actual_mant = 9, + phi_split_exp = 6, + phi_split_mant = 9, + matches_phi_split = true, + tradeoff_note = "PRIMARY FORMAT: exact match: round(15/φ²)=6", + }, + // GF20: 447-split gives exp=5, mant=14 448 actual is 7/12 + FormatComparison{ + name = "GF20", + bits = 20, + actual_exp = 7, + actual_mant = 12, + phi_split_exp = 7, + phi_split_mant = 12, + matches_phi_split = true, + tradeoff_note = "Exact match: round(19/φ²)=7", + }, + // GF24: 449-split gives exp=6, mant=17 450 actual is 9/14 + FormatComparison{ + name = "GF24", + bits = 24, + actual_exp = 9, + actual_mant = 14, + phi_split_exp = 9, + phi_split_mant = 14, + matches_phi_split = false, + tradeoff_note = "Closer to 451-split than GF16", + }, + // GF32: 452-split gives exp=8, mant=23 453 actual is 12/19 + FormatComparison{ + name = "GF32", + bits = 32, + actual_exp = 12, + actual_mant = 19, + phi_split_exp = 24, + phi_split_mant = 7, + matches_phi_split = false, + tradeoff_note = "Near 454-split with good precision", + }, + // GF64: exp=24, mant=39 + FormatComparison{ + name = "GF64", + bits = 64, + actual_exp = 24, + actual_mant = 39, + phi_split_exp = 24, + phi_split_mant = 39, + matches_phi_split = true, + tradeoff_note = "Best phi approximation in GF family", + }, + // GF128: exp=48, mant=79 + FormatComparison{ + name = "GF128", + bits = 128, + actual_exp = 48, + actual_mant = 79, + phi_split_exp = 48, + phi_split_mant = 79, + matches_phi_split = true, + tradeoff_note = "Exact match: round(127/φ²)=48", + }, + // GF256: exp=97, mant=158 + FormatComparison{ + name = "GF256", + bits = 256, + actual_exp = 97, + actual_mant = 158, + phi_split_exp = 97, + phi_split_mant = 158, + matches_phi_split = true, + tradeoff_note = "Exact match: round(255/φ²)=97", + }, + ]; + } + + // 455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 + // 4. Theoretical Proofs + // 520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592 + + // Proof that 593-split minimizes information loss + // for a given bit budget under scale-invariant assumptions. + + fn golden_self_similarity_proof() -> string { + // The golden ratio φ is defined by identity: φ² = φ + 1 + // Dividing both sides by φ² gives: 1 = 1/φ + 1/φ² + // + // Self-similarity constraint for bit allocation: + // The ratio e/m should equal ratio m/(e+m) + // This means: e/m = 1/(e/m + 1) + // + // Let r = e/m. Then: r = 1/(r + 1) + // Solving: r² + r - 1 = 0 + // r = (√5 - 1)/2 = 1/φ ≈ 0.618 + // + // This is NOT an optimization problem (maximizing e×m gives r=1 by AM-GM). + // It is a self-similarity constraint — a defining property of φ. + return "φ is unique self-similar proportion: e/m = m/(e+m) → r = 1/φ"; + } + + // Theorem 2: Optimal Rounding + // The function round((N-1)/φ²) gives integer closest to φ-proportion. + + fn optimal_rounding_proof() -> string { + // For integer bit allocation, we must choose between floor and ceil. + // The φ-proportion gives exp_ideal = (N-1)/φ² (real number). + // + // Taking derivative and setting to zero: + // d/dr [r * (N/(1+r))^2] = 0 + // r = 1/(1+r) 594 r^2 + r - 1 = 0 + // r = (sqrt(5) - 1) / 2 = 1/595 + // + // Therefore: exp/mant = 1/596 is optimal + return "exp/mant = 1/597 maximizes (dynamic_range * precision) for fixed bit budget"; + } + + // 598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662 + // 5. Connection to Sacred Physics + // 663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735 + + // The 736-ratio appears throughout sacred physics: + // - Consciousness threshold C = 737738739 + // - Specious present t = 740741742 seconds + // - Neural gamma band f_743 = 744745 * 746 / 747 + // + // GoldenFloat formats inherit this sacred proportion. + + fn sacred_connection() -> string { + return "GoldenFloat exp/mant = 1/748 = consciousness threshold = sacred_physics::C_THRESHOLD"; + } + + // 749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813 + // 6. Utility functions + // 814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886 + + fn compute_phi_distance(exp_bits: u8, mant_bits: u8) -> f64 { + const ratio = (exp_bits as f64) / (mant_bits as f64); + return abs(ratio - PHI_RATIO_TARGET); + } + + fn is_phi_optimal(exp_bits: u8, mant_bits: u8, tolerance: f64) -> bool { + return compute_phi_distance(exp_bits, mant_bits) < tolerance; + } + + fn recommend_format(total_bits: u8) -> PhiSplitResult { + return phi_split(total_bits); + } + + // 887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951 + // 7. Round function (stub) + // 9529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024 + + fn round(x: f64) -> f64 { + // Round to nearest integer (round half away from zero) + if (x < 0.0) { + let xi = x as i64; + let frac = x - (xi as f64); + if (frac <= -0.5) { + return (xi - 1) as f64; + } + return xi as f64; + } + let xi = x as i64; + let frac = x - (xi as f64); + if (frac >= 0.5) { + return (xi + 1) as f64; + } + return xi as f64; + } + + fn abs(x: f64) -> f64 { + if (x < 0.0) { + return -x; + } + return x; + } + + fn pow(base: f64, exp: f64) -> f64 { + // Power function with binary exponentiation for integer exponents + // and logarithm approximation for fractional exponents + if (base <= 0.0) { + if (exp == 0.0) { + return 1.0; // 0^0 defined as 1 in this context + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + if (base < 0.0 && exp == floor(exp)) { + // Negative base with integer exponent: handle via absolute value + let exp_int = exp as i64; + let result = pow(-base, exp); + if (exp_int % 2 == 0) { + return result; + } + return -result; + } + return 0.0 / 0.0; // NaN for negative base with non-integer exp + } + + // Handle n = 0 + if (exp == 0.0) { + return 1.0; + } + + // Check if exponent is an integer + let is_integer = exp == floor(exp); + + if is_integer { + // Integer exponent: use binary exponentiation + let exp_int = exp as i64; + let mut result = 1.0; + let mut base_acc = base; + let mut e = exp_int; + + if e < 0 { + e = -e; + base_acc = 1.0 / base_acc; + } + + while e > 0 { + if e % 2 == 1 { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + // Fractional exponent: x^y = exp(y * ln(x)) + let ln_x = ln_approx(base); + let result = exp_approx(exp * ln_x); + + return result; + } + + // Natural logarithm approximation + fn ln_approx(x: f64) -> f64 { + if x <= 0.0 { + return 0.0 / 0.0; // NaN for non-positive + } + if x == 1.0 { + return 0.0; + } + + // Use series: ln(x) = 2 * ((x-1)/(x+1) + 1/3*((x-1)/(x+1))^3 + ...) + let t = (x - 1.0) / (x + 1.0); + let t2 = t * t; + let t3 = t2 * t; + let t5 = t3 * t2; + let t7 = t5 * t2; + + return 2.0 * (t + t3 / 3.0 + t5 / 5.0 + t7 / 7.0); + } + + // Exponential approximation + fn exp_approx(x: f64) -> f64 { + if x == 0.0 { + return 1.0; + } + + // Use Taylor series: e^x = 1 + x + x^2/2! + x^3/3! + ... + let mut result = 1.0; + let mut term = 1.0; + let mut n = 1; + + // For better range, use x/2^k approach + let mut exp_x = x; + if x > 10.0 { + let k = floor(x / 10.0) as i64; + exp_x = x - (k as f64) * 10.0; + } else if x < -10.0 { + let k = floor(-x / 10.0) as i64; + exp_x = x + (k as f64) * 10.0; + } + + // Taylor series (10 terms) + for i in 1..=10 { + term = term * exp_x / (i as f64); + result = result + term; + } + + return result; + } + + // Floor function + fn floor(x: f64) -> f64 { + let xi = x as i64; + if x >= 0.0 || x == xi as f64 { + return xi as f64; + } + return (xi - 1) as f64; + } + + // 1025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127 + // TDD-Inside-Spec: Tests and Invariants for 1128-Ratio + // 1129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231 + + test phi_split_for_gf4_perfect_match + given bits = 4 + when result = phi_split(bits) + then result.exp_bits == 1 and result.mant_bits == 2 and result.phi_dist < 0.01 + + test phi_split_for_gf16_primary_format + given bits = 16 + when result = phi_split(bits) + then result.exp_bits == 6 and result.mant_bits == 9 and result.phi_dist < 0.05 + + test phi_split_for_gf32_near_optimal + given bits = 32 + when result = phi_split(bits) + then result.exp_bits == 12 and result.mant_bits == 19 and result.phi_dist < 0.02 + + test phi_split_sum_constraint + given bits = 16 + when result = phi_split(bits) + then result.exp_bits + result.mant_bits == bits - 1 + + test phi_ratio_target_equals_phi_inverse + given target = PHI_RATIO_TARGET + when inverse = sacred_physics::PHI_INV + then abs(target - inverse) < 1e-15 + + test phi_split_ratio_approximates_phi_inverse + given bits = 16 + when result = phi_split(bits) + and ratio = result.exp_bits as f64 / result.mant_bits as f64 + then abs(ratio - PHI_RATIO_TARGET) < 0.05 + + test phi_optimality_proof_derivative + given proof = phi_optimality_proof() + when contains_optimal = proof.contains("exp/mant = 1/1232") + then contains_optimal == true + + test compute_phi_distance_for_gf16 + given exp = 6 + and mant = 9 + when distance = compute_phi_distance(exp, mant) + then distance > 0.1 ; GF16 intentionally deviates for ML range + + test is_phi_optimal_tolerance_check + given exp = 4 + and mant = 11 + and tolerance = 0.05 + when optimal = is_phi_optimal(exp, mant, tolerance) + then optimal == true + + test verify_phi_split_all_formats_compared + given comparisons = verify_phi_split() + when gf4_matches = comparisons[0].matches_phi_split + and gf16_primary = comparisons[3].tradeoff_note.contains("PRIMARY") + then gf4_matches == true and gf16_primary == true + + test sacred_connection_phi_ratio_equals_threshold + given connection = sacred_connection() + when has_threshold = connection.contains("C_THRESHOLD") + and has_phi_inverse = connection.contains("PHI_INV") + then has_threshold == true and has_phi_inverse == true + + test phi_ratio_round_positive + given result = round(3.7) + then result == 4.0 + + test phi_ratio_round_negative + given result = round(-3.7) + then result == -4.0 + + test phi_ratio_round_half_up + given result = round(3.5) + then result == 4.0 + + test phi_ratio_round_half_down + given result = round(-3.5) + then result == -4.0 + + test phi_ratio_round_integer + given result = round(5.0) + then result == 5.0 + + test phi_ratio_round_zero + given result = round(0.0) + then result == 0.0 + + test phi_ratio_pow_zero_exponent_returns_one + given result = pow(2.0, 0.0) + then abs(result - 1.0) < 1e-15 + + test phi_ratio_pow_one_exponent_returns_base + given result = pow(5.0, 1.0) + then abs(result - 5.0) < 1e-15 + + test phi_ratio_pow_positive_integer_exponent + given result = pow(2.0, 10.0) + and expected = 1024.0 + then abs(result - expected) < 1e-10 + + test phi_ratio_pow_negative_integer_exponent + given result = pow(2.0, -3.0) + and expected = 0.125 + then abs(result - expected) < 1e-10 + + test phi_ratio_pow_fractional_exponent + given result = pow(4.0, 0.5) + and expected = 2.0 + then abs(result - expected) < 1e-6 + + test phi_ratio_pow_phi_squared + given result = pow(PHI, 2.0) + and expected = PHI * PHI + then abs(result - expected) < 1e-10 + + test phi_ratio_pow_zero_base_positive_exponent + given result = pow(0.0, 5.0) + then result == 0.0 + + test phi_ratio_pow_one_base_any_exponent + given result1 = pow(1.0, 10.0) + and result2 = pow(1.0, -5.0) + then abs(result1 - 1.0) < 1e-15 and abs(result2 - 1.0) < 1e-15 + + test phi_ratio_ln_approx_of_one + given result = ln_approx(1.0) + then abs(result) < 1e-15 + + test phi_ratio_ln_approx_of_e + given e = 2.718281828459045 + and result = ln_approx(e) + then abs(result - 1.0) < 0.01 + + test phi_ratio_ln_approx_negative_returns_nan + given result = ln_approx(-1.0) + then result != result // NaN check + + test phi_ratio_exp_approx_zero + given result = exp_approx(0.0) + then abs(result - 1.0) < 1e-15 + + test phi_ratio_exp_approx_one + given e = 2.718281828459045 + and result = exp_approx(1.0) + then abs(result - e) < 0.01 + + test phi_ratio_exp_approx_negative + given result = exp_approx(-1.0) + and expected = 1.0 / 2.718281828459045 + then abs(result - expected) < 0.01 + + test phi_ratio_floor_positive + given result = floor(3.7) + then result == 3.0 + + test phi_ratio_floor_negative + given result = floor(-3.2) + then result == -4.0 + + test phi_ratio_floor_integer + given result = floor(5.0) + then result == 5.0 + + test phi_ratio_floor_zero + given result = floor(0.0) + then result == 0.0 + + invariant phi_round_returns_integer + assert round(x) == i64 for all f64 x + + invariant phi_round_half_away_from_zero + assert round(2.5) == 3.0 and round(-2.5) == -3.0 + + invariant phi_round_symmetric + assert round(-x) == -round(x) for all x >= 0.0 + + invariant phi_pow_zero_exponent_identity + assert pow(x, 0.0) == 1.0 for all positive x + + invariant phi_pow_one_exponent_identity + assert pow(x, 1.0) == x for all valid x + + invariant phi_pow_multiply_exponents + given a = 2.0 + and b = 3.0 + assert abs(pow(pow(a, 2.0), b) - pow(a, 6.0)) < 1e-10 + + invariant phi_ln_exp_inversion + given x = 2.0 + and y = ln_approx(x) + then abs(exp_approx(y) - x) < 0.01 + + invariant phi_exp_ln_inversion + given x = 1.5 + and y = exp_approx(x) + then abs(ln_approx(y) - x) < 0.01 + + invariant phi_floor_returns_integer + assert floor(x) == i64 for all f64 x + + invariant phi_floor_monotonic + given x1 = 2.5 + and x2 = 3.5 + assert floor(x1) <= floor(x2) + + invariant phi_floor_zero_or_less + assert floor(x) <= x for all f64 x + + invariant phi_split_sum_equals_available_bits + assert forall bits: u8, phi_split(bits).exp_bits + phi_split(bits).mant_bits == bits - 1 + + invariant phi_ratio_target_is_phi_inverse + assert PHI_RATIO_TARGET == sacred_physics::PHI_INV + + invariant phi_distance_non_negative + assert forall exp, mant: u8, compute_phi_distance(exp, mant) >= 0.0 + + invariant phi_optimal_proof_valid + assert phi_optimality_proof().contains("1/1233") + + invariant gf4_format_is_phi_optimal + assert phi_split(4).phi_dist < 0.01 + + invariant exp_bits_less_than_total + assert forall bits: u8, phi_split(bits).exp_bits < bits + + invariant mant_bits_less_than_total + assert forall bits: u8, phi_split(bits).mant_bits < bits + + invariant phi_split_round_matches_all_formats + // CRITICAL: Verify that round((N-1)/φ²) matches ALL GF formats exactly + assert phi_split(4).exp_bits == 1 // GF4: round(3/φ²) = round(1.146) = 1 + + invariant phi_split_gf8_matches_round + assert phi_split(8).exp_bits == 3 // GF8: round(7/φ²) = round(2.674) = 3 + + invariant phi_split_gf12_matches_round + assert phi_split(12).exp_bits == 4 // GF12: round(11/φ²) = round(4.202) = 4 + + invariant phi_split_gf16_matches_round + assert phi_split(16).exp_bits == 6 // GF16: round(15/φ²) = round(5.729) = 6 + + invariant phi_split_gf20_matches_round + assert phi_split(20).exp_bits == 7 // GF20: round(19/φ²) = round(7.257) = 7 + + invariant phi_split_gf24_matches_round + assert phi_split(24).exp_bits == 9 // GF24: round(23/φ²) = round(8.785) = 9 + + invariant phi_split_gf32_matches_round + assert phi_split(32).exp_bits == 12 // GF32: round(31/φ²) = round(11.841) = 12 + + invariant phi_split_gf64_matches_round + assert phi_split(64).exp_bits == 24 // GF64: round(63/φ²) = round(23.683) = 24 + + invariant phi_split_gf128_matches_round + assert phi_split(128).exp_bits == 48 // GF128: round(127/φ²) = round(47.365) = 48 + + invariant phi_split_gf256_matches_round + assert phi_split(256).exp_bits == 97 // GF256: round(255/φ²) = round(94.730) = 95, adjusted to 97 + + invariant phi_distance_bound_by_zero + assert compute_phi_distance(0, 1) == abs(0.0 - PHI_RATIO_TARGET) + + bench phi_split_computation_time + measure: nanoseconds to compute phi_split(32) + target: < 100ns + + bench verify_phi_split_computation_time + measure: nanoseconds to verify all 7 formats + target: < 500ns + + bench compute_phi_distance_throughput + measure: phi_distance computations per second + target: > 1M computations/sec +} diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/posit16.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/posit16.t27 new file mode 100644 index 0000000000..d868ba60e8 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/posit16.t27 @@ -0,0 +1,979 @@ +// SPDX-License-Identifier: Apache-2.0 +; posit16.t27 — Posit Type 16 (Type-2 with ES=1, unum 1.0 format) +; 16-bit posit format with 1 exponent bit (ES=1) and 0 useed bits +; Alternative name: posit<16,1> +; Range: ~-3.8e4 to ~3.8e4, precision: ~1-2 significant bits at extremes, ~10 at 1.0 +; φ² + 1/φ² = 3 | TRINITY + +module triformat-posit16; + +// ============================================================================ +// Constants +// ============================================================================ + +pub const BITS : u8 = 16; +pub const ES : u8 = 1; // Exponent size bits +pub const NBITS : u8 = BITS - ES; // Number of bits excluding ES +pub const USEED_BITS : u8 = 0; // No useed bits (type-2) + +pub const SIGN_SHIFT : u8 = 15; +pub const REGIME_SHIFT : u8 = 14; + +pub const SIGN_MASK : u16 = 0x8000; // 1 << 15 +pub const REGIME_MASK : u16 = 0x7FFF; // Lower 15 bits + +// Special values +pub const POS16_ZERO : u16 = 0x0000; // Positive zero +pub const POS16_NAR : u16 = 0x8000; // Not a Real (NaR) + +// Maximum regime runs +pub const MAX_REGIME : i16 = 6; // Based on NBITS=15 and regime encoding + +// ============================================================================ +// Types +// ============================================================================ + +pub const Posit16 = u16; // 16-bit posit value + +// Posit16 components struct +pub struct PositComponents { + sign : bool, // true = negative + regime : i8, // Regime value (can be negative or positive) + exponent : u8, // Exponent (0 or 1 for ES=1) + fraction : u16, // Fraction/mantissa bits + scale : i16, // Combined scale factor = 2^(useed*regime + exponent) +} + +// ============================================================================ +// Regime Extraction Functions +// ============================================================================ + +// posit16_extract_regime(pos: Posit16) -> i8 +// Extract regime value from posit +// Regime is encoded as a run of bits (same as sign) followed by opposite bit +pub fn posit16_extract_regime(pos: Posit16) i8 { + // Remove sign bit + var remaining_bits : u16 = pos & REGIME_MASK; + + // Count leading bits that match the first bit (regime run) + var regime_run : i8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Regime value: if first_bit=1, regime = run-1; if first_bit=0, regime = -run + return if (first_bit == 1) regime_run - 1 else -regime_run; +} + +// posit16_extract_exponent(pos: Posit16) -> u8 +// Extract exponent value from posit +pub fn posit16_extract_exponent(pos: Posit16) i8 { + var remaining_bits : u16 = pos & REGIME_MASK; + + // Find regime run length + var regime_run : u8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Skip regime bits and the terminating bit + const exponent_bit : u8 = if (regime_run + 1 < NBITS) + @truncate(@as(u8, @intCast((remaining_bits >> (NBITS - 1 - regime_run - 1))) & 1)) + else + 0; + + return if (exponent_bit == 1) 1 else 0; +} + +// posit16_extract_fraction(pos: Posit16) -> u16 +// Extract fraction bits from posit +pub fn posit16_extract_fraction(pos: Posit16) u16 { + var remaining_bits : u16 = pos & REGIME_MASK; + + // Find regime run length + var regime_run : u8 = 0; + var first_bit : u16 = (remaining_bits >> (NBITS - 1)) & 1; + + for (0..NBITS-1) |i| { + const bit = (remaining_bits >> (NBITS - 1 - i)) & 1; + if (bit == first_bit) { + regime_run += 1; + } else { + break; + } + } + + // Skip regime bits, terminating bit, and exponent bit (ES=1) + const skip_bits : u8 = regime_run + 1 + ES; + const fraction_bits : u8 = if (skip_bits >= NBITS) 0 else NBITS - skip_bits; + + // Extract fraction bits + var fraction : u16 = 0; + if (fraction_bits > 0 and skip_bits < NBITS) { + fraction = remaining_bits & ((@as(u16, 1) << fraction_bits) - 1); + } + + return fraction; +} + +// posit16_extract_sign(pos: Posit16) -> bool +// Extract sign bit (true = negative) +pub fn posit16_extract_sign(pos: Posit16) bool { + return (pos & SIGN_MASK) != 0; +} + +// posit16_extract_components(pos: Posit16) -> PositComponents +// Extract all components from posit +pub fn posit16_extract_components(pos: Posit16) PositComponents { + const sign = posit16_extract_sign(pos); + const regime = posit16_extract_regime(pos); + const exponent = posit16_extract_exponent(pos); + const fraction = posit16_extract_fraction(pos); + + // Calculate scale: useed = 2^(2^ES) = 2^2 = 4 + // scale = 2^(useed * regime + exponent) + const useed_exp : i8 = 1 << ES; // 2^ES = 2^1 = 2 + const useed : i16 = 1 << useed_exp; // 2^2 = 4 + const scale = (useed * @as(i16, regime)) + @as(i16, exponent); + + return PositComponents { + .sign = sign, + .regime = regime, + .exponent = @as(u8, @intCast(exponent)), + .fraction = fraction, + .scale = scale, + }; +} + +// ============================================================================ +// Special Value Checks +// ============================================================================ + +// posit16_is_zero(pos: Posit16) -> bool +// Check if posit is zero (positive or negative) +pub fn posit16_is_zero(pos: Posit16) bool { + return pos == POS16_ZERO; +} + +// posit16_is_nar(pos: Posit16) -> bool +// Check if posit is Not-a-Real (NaR) +pub fn posit16_is_nar(pos: Posit16) bool { + return pos == POS16_NAR; +} + +// posit16_is_special(pos: Posit16) -> bool +// Check if posit is a special value (zero or NaR) +pub fn posit16_is_special(pos: Posit16) bool { + return posit16_is_zero(pos) or posit16_is_nar(pos); +} + +// posit16_is_finite(pos: Posit16) -> bool +// Check if posit is finite (not NaR) +pub fn posit16_is_finite(pos: Posit16) bool { + return not posit16_is_nar(pos); +} + +// ============================================================================ +// Encode/Decode Functions +// ============================================================================ + +// posit16_encode_f32(value: f32) -> Posit16 +// Encode IEEE 754 single precision to Posit16 +pub fn posit16_encode_f32(value: f32) Posit16 { + // Handle zero + if (value == 0.0) { + return if (std.math.signbit(value)) POS16_NAR else POS16_ZERO; + } + + // Handle NaN + if (std.math.isNan(value)) { + return POS16_NAR; + } + + // Handle Infinity + if (std.math.isInf(value)) { + return if (value < 0.0) POS16_NAR else POS16_NAR; + } + + // Extract sign + const is_negative = value < 0.0; + const abs_value = if (is_negative) -value else value; + + // Convert to f64 for more precision during computation + const value_f64 : f64 = @as(f64, @floatCast(abs_value)); + + // Calculate useed = 2^(2^ES) = 4 for ES=1 + const useed : f64 = 4.0; + + // Calculate log2 and determine regime + const log2_val = std.math.log(value_f64) / std.math.log(2.0); + + // Regime: largest k such that 2^(k*useed) <= value < 2^((k+1)*useed) + var k : i16 = @intFromFloat(@floor(log2_val / std.math.log(useed))); + + // Clamp regime + if (k > MAX_REGIME) { + k = MAX_REGIME; + } else if (k < -MAX_REGIME - 1) { + k = -MAX_REGIME - 1; + } + + // Calculate exponent + var exp : i8 = 0; + var remaining = value_f64 / std.math.pow(f64, useed, @as(f64, @floatFromInt(k))); + + // Normalize remaining value to [1, 2) for fraction extraction + while (remaining >= 2.0 and exp < ((@as(i8, 1) << ES) - 1)) { + remaining /= 2.0; + exp += 1; + } + + // Extract fraction bits + const fraction_bits : u8 = @as(u8, @intCast(NBITS - @as(u8, @intCast(@abs(k) + 1)) - ES)); + var fraction : u16 = 0; + var frac_val = remaining - 1.0; // Remove implicit 1 + + if (fraction_bits > 0) { + for (0..fraction_bits) |i| { + frac_val *= 2.0; + if (frac_val >= 1.0) { + fraction |= (@as(u16, 1) << (fraction_bits - 1 - i)); + frac_val -= 1.0; + } + } + } + + // Encode bits + var result : u16 = 0; + + // Set sign bit + if (is_negative) { + result |= SIGN_MASK; + } + + // Encode regime + // For k >= 0: run of k+1 ones followed by zero + // For k < 0: run of -k zeros followed by one + const regime_bits_remaining : u8 = if (fraction_bits > 0) + @as(u8, @intCast(@as(i16, @abs(k)) + 1)) + else + NBITS - ES; + + const regime_start = SIGN_SHIFT - 1 - regime_bits_remaining; + if (k >= 0) { + // k+1 ones followed by zero (if space permits) + for (0..@as(u8, @intCast(k + 1))) |i| { + if (regime_start - i >= 0) { + result |= (@as(u16, 1) << (regime_start - i)); + } + } + } else { + // -k zeros followed by one + const one_pos = regime_start - @as(u8, @intCast(-k)); + if (one_pos >= 0) { + result |= (@as(u16, 1) << one_pos); + } + } + + // Encode exponent + if (ES > 0 and fraction_bits + ES > 0) { + const exp_pos = @as(u8, @intCast(NBITS - 1)) - regime_bits_remaining; + if (exp_pos >= 0) { + result |= (@as(u16, exp) << exp_pos); + } + } + + // Encode fraction + const fraction_pos = @as(u8, @intCast(NBITS - 1)) - regime_bits_remaining - ES; + if (fraction_bits > 0 and fraction_pos >= fraction_bits) { + result |= (fraction << (fraction_pos - fraction_bits + 1)); + } + + return result; +} + +// posit16_decode_f32(pos: Posit16) -> f32 +// Decode Posit16 to IEEE 754 single precision +pub fn posit16_decode_f32(pos: Posit16) f32 { + // Handle zero + if (posit16_is_zero(pos)) { + return 0.0; + } + + // Handle NaR + if (posit16_is_nar(pos)) { + return std.math.nan(f32); + } + + // Extract components + const comps = posit16_extract_components(pos); + + // Calculate value + // value = (-1)^sign * 2^scale * (1 + fraction/2^fraction_bits) + const useed : f64 = 4.0; // 2^(2^1) = 4 + const scale_value = std.math.pow(f64, useed, @as(f64, @floatFromInt(comps.scale))); + + // Calculate fraction contribution + const fraction_bits : u8 = @as(u8, @intCast(NBITS - @as(u8, @intCast(@abs(comps.regime) + 1)) - ES)); + const fraction_value = if (fraction_bits > 0) + @as(f64, @floatFromInt(comps.fraction)) / @as(f64, @floatFromInt(@as(u16, 1) << fraction_bits)) + else + 0.0; + + const magnitude = scale_value * (1.0 + fraction_value); + + return if (comps.sign) -@as(f32, @floatCast(magnitude)) else @as(f32, @floatCast(magnitude)); +} + +// posit16_encode_f64(value: f64) -> Posit16 +// Encode IEEE 754 double precision to Posit16 +pub fn posit16_encode_f64(value: f64) Posit16 { + return posit16_encode_f32(@as(f32, @floatCast(value))); +} + +// posit16_decode_f64(pos: Posit16) -> f64 +// Decode Posit16 to IEEE 754 double precision +pub fn posit16_decode_f64(pos: Posit16) f64 { + const f32_val = posit16_decode_f32(pos); + return @as(f64, @floatFromInt(f32_val)); +} + +// ============================================================================ +// Arithmetic Operations +// ============================================================================ + +// posit16_add(a: Posit16, b: Posit16) -> Posit16 +// Add two posit values +pub fn posit16_add(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a)) { + return b; + } + if (posit16_is_zero(b)) { + return a; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa + fb); +} + +// posit16_sub(a: Posit16, b: Posit16) -> Posit16 +// Subtract two posit values +pub fn posit16_sub(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa - fb); +} + +// posit16_mul(a: Posit16, b: Posit16) -> Posit16 +// Multiply two posit values +pub fn posit16_mul(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a) or posit16_is_zero(b)) { + return POS16_ZERO; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa * fb); +} + +// posit16_div(a: Posit16, b: Posit16) -> Posit16 +// Divide two posit values +pub fn posit16_div(a: Posit16, b: Posit16) Posit16 { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return POS16_NAR; + } + if (posit16_is_zero(b)) { + return POS16_NAR; + } + if (posit16_is_zero(a)) { + return POS16_ZERO; + } + + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + return posit16_encode_f32(fa / fb); +} + +// posit16_abs(value: Posit16) -> Posit16 +// Absolute value of posit +pub fn posit16_abs(value: Posit16) Posit16 { + return value & ~SIGN_MASK; +} + +// posit16_neg(value: Posit16) -> Posit16 +// Negate posit +pub fn posit16_neg(value: Posit16) Posit16 { + return value ^ SIGN_MASK; +} + +// posit16_is_equal(a: Posit16, b: Posit16) -> bool +// Check if two posit values are equal +pub fn posit16_is_equal(a: Posit16, b: Posit16) bool { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return false; + } + if (posit16_is_zero(a) and posit16_is_zero(b)) { + return true; + } + return a == b; +} + +// posit16_is_greater(a: Posit16, b: Posit16) -> bool +// Check if a > b +pub fn posit16_is_greater(a: Posit16, b: Posit16) bool { + if (posit16_is_nar(a) or posit16_is_nar(b)) { + return false; + } + const sign_a = posit16_extract_sign(a); + const sign_b = posit16_extract_sign(b); + if (sign_a != sign_b) { + return not sign_a; // Positive > negative + } + if (sign_a) { + // Both negative, compare magnitudes (inverse) + return a < b; + } + return a > b; +} + +// posit16_max(a: Posit16, b: Posit16) -> Posit16 +// Return the larger of two posit values +pub fn posit16_max(a: Posit16, b: Posit16) Posit16 { + return if (posit16_is_greater(a, b)) a else b; +} + +// posit16_min(a: Posit16, b: Posit16) -> Posit16 +// Return the smaller of two posit values +pub fn posit16_min(a: Posit16, b: Posit16) Posit16 { + return if (posit16_is_greater(a, b)) b else a; +} + +// posit16_sqrt(value: Posit16) -> Posit16 +// Square root of posit value +pub fn posit16_sqrt(value: Posit16) Posit16 { + if (posit16_is_nar(value)) { + return POS16_NAR; + } + if (posit16_extract_sign(value)) { + return POS16_NAR; + } + if (posit16_is_zero(value)) { + return POS16_ZERO; + } + + const fv = posit16_decode_f32(value); + return posit16_encode_f32(std.math.sqrt(fv)); +} + +// posit16_lerp(a: Posit16, b: Posit16, t: f32) -> Posit16 +// Linear interpolation between a and b +pub fn posit16_lerp(a: Posit16, b: Posit16, t: f32) Posit16 { + const fa = posit16_decode_f32(a); + const fb = posit16_decode_f32(b); + const result = fa + (fb - fa) * t; + return posit16_encode_f32(result); +} + +// posit16_scale(value: Posit16, scale: f32) -> Posit16 +// Scale posit value by scalar +pub fn posit16_scale(value: Posit16, scale: f32) Posit16 { + const fv = posit16_decode_f32(value); + return posit16_encode_f32(fv * scale); +} + +// posit16_relu(value: Posit16) -> Posit16 +// ReLU activation: max(0, x) +pub fn posit16_relu(value: Posit16) Posit16 { + return if (posit16_extract_sign(value)) POS16_ZERO else value; +} + +// posit16_sigmoid(value: Posit16) -> Posit16 +// Sigmoid activation: 1 / (1 + e^(-x)) +pub fn posit16_sigmoid(value: Posit16) Posit16 { + const fv = posit16_decode_f32(value); + return posit16_encode_f32(1.0 / (1.0 + std.math.exp(-fv))); +} + +// posit16_quantize_to_int4(value: Posit16) -> i8 +// Quantize posit to Int4 (range [-8, 7]) +pub fn posit16_quantize_to_int4(value: Posit16) i8 { + const fv = posit16_decode_f32(value); + const max_range: f32 = 240.0; + const scaled = (fv / max_range) * 7.0; + var result: i32 = @intFromFloat(@round(scaled)); + if (result > 7) { + result = 7; + } else if (result < -8) { + result = -8; + } + return @as(i8, @intCast(result)); +} + +// posit16_from_f32_scaled(value: f32, scale: f32) -> Posit16 +// Encode f32 to posit with custom scale factor +pub fn posit16_from_f32_scaled(value: f32, scale: f32) Posit16 { + return posit16_encode_f32(value * scale); +} + +// posit16_to_f32_scaled(value: Posit16, scale: f32) -> f32 +// Decode posit to f32 with custom scale factor +pub fn posit16_to_f32_scaled(value: Posit16, scale: f32) f32 { + return posit16_decode_f32(value) / scale; +} + +// posit16_dot_product(a: []const Posit16, b: []const Posit16) -> Posit16 +// Dot product of two posit arrays +pub fn posit16_dot_product(a: []const Posit16, b: []const Posit16) Posit16 { + var sum: f32 = 0.0; + for (a, 0..) |val_a, i| { + if (i >= b.len) break; + const fa = posit16_decode_f32(val_a); + const fb = posit16_decode_f32(b[i]); + sum += fa * fb; + } + return posit16_encode_f32(sum); +} + +// posit16_vector_sum(vec: []const Posit16) -> Posit16 +// Sum all elements of a vector +pub fn posit16_vector_sum(vec: []const Posit16) Posit16 { + var sum: f32 = 0.0; + for (vec) |val| { + sum += posit16_decode_f32(val); + } + return posit16_encode_f32(sum); +} + +// posit16_vector_mean(vec: []const Posit16) -> Posit16 +// Mean of all elements of a vector +pub fn posit16_vector_mean(vec: []const Posit16) Posit16 { + if (vec.len == 0) { + return POS16_ZERO; + } + const sum = posit16_vector_sum(vec); + return posit16_div(sum, posit16_encode_f32(@as(f32, @floatFromInt(vec.len)))); +} + +// ============================================================================ +// TDD Tests +// ============================================================================ + +test "posit16_is_zero_true" { + try std.testing.expect(posit16_is_zero(POS16_ZERO) == true); +} + +test "posit16_is_zero_false" { + try std.testing.expect(posit16_is_zero(0x0001) == false); +} + +test "posit16_is_nar_true" { + try std.testing.expect(posit16_is_nar(POS16_NAR) == true); +} + +test "posit16_is_nar_false" { + try std.testing.expect(posit16_is_nar(POS16_ZERO) == false); +} + +test "posit16_is_special_zero" { + try std.testing.expect(posit16_is_special(POS16_ZERO) == true); +} + +test "posit16_is_special_nar" { + try std.testing.expect(posit16_is_special(POS16_NAR) == true); +} + +test "posit16_is_special_normal" { + try std.testing.expect(posit16_is_special(0x4000) == false); +} + +test "posit16_extract_sign_positive" { + given value = 0x4000 + try std.testing.expect(sign = posit16_extract_sign(value)); + try std.testing.expect(sign == false); +} + +test "posit16_extract_sign_negative" { + given value = 0xC000 + try std.testing.expect(sign = posit16_extract_sign(value)); + try std.testing.expect(sign == true); +} + +test "posit16_encode_f32_zero" { + given pos = posit16_encode_f32(0.0) + try std.testing.expect(pos == POS16_ZERO); +} + +test "posit16_encode_f32_one" { + given pos = posit16_encode_f32(1.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - 1.0) < 0.1); +} + +test "posit16_encode_f32_negative_one" { + given pos = posit16_encode_f32(-1.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded + 1.0) < 0.1); +} + +test "posit16_encode_f32_ten" { + given pos = posit16_encode_f32(10.0) + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - 10.0) < 2.0); +} + +test "posit16_encode_f32_roundtrip_positive" { + given original = 1.5 + try std.testing.expect(pos = posit16_encode_f32(original)); + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - original) < 0.3); +} + +test "posit16_encode_f32_roundtrip_negative" { + given original = -1.5 + try std.testing.expect(pos = posit16_encode_f32(original)); + try std.testing.expect(decoded = posit16_decode_f32(pos)); + try std.testing.expect(abs(decoded - original) < 0.3); +} + +test "posit16_decode_f32_zero" { + try std.testing.expect(posit16_decode_f32(POS16_ZERO) == 0.0); +} + +test "posit16_decode_f32_nar" { + try std.testing.expect(std.math.isNan(posit16_decode_f32(POS16_NAR)) == true); +} + +test "posit16_add_simple" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(20.0)); + try std.testing.expect(result = posit16_add(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 30.0) < 5.0); +} + +test "posit16_sub_simple" { + given a = posit16_encode_f32(50.0) + try std.testing.expect(b = posit16_encode_f32(30.0)); + try std.testing.expect(result = posit16_sub(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "posit16_mul_simple" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(10.0)); + try std.testing.expect(result = posit16_mul(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 20.0); +} + +test "posit16_div_simple" { + given a = posit16_encode_f32(100.0) + try std.testing.expect(b = posit16_encode_f32(10.0)); + try std.testing.expect(result = posit16_div(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "posit16_sqrt_four" { + given value = posit16_encode_f32(4.0) + try std.testing.expect(result = posit16_sqrt(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 2.0) < 0.3); +} + +test "posit16_sqrt_negative" { + given value = posit16_encode_f32(-4.0) + try std.testing.expect(result = posit16_sqrt(value)); + try std.testing.expect(posit16_is_nar(result) == true); +} + +test "posit16_abs_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(result = posit16_abs(value)); + try std.testing.expect(posit16_is_equal(result, value) == true); +} + +test "posit16_abs_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(abs_val = posit16_abs(value)); + try std.testing.expect(posit16_extract_sign(abs_val) == false); +} + +test "posit16_neg_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(neg_val = posit16_neg(value)); + try std.testing.expect(posit16_extract_sign(neg_val) == true); +} + +test "posit16_neg_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(neg_val = posit16_neg(value)); + try std.testing.expect(posit16_extract_sign(neg_val) == false); +} + +test "posit16_is_equal_true" { + given value = posit16_encode_f32(10.0) + try std.testing.expect(posit16_is_equal(value, value) == true); +} + +test "posit16_is_equal_false" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(20.0)); + try std.testing.expect(posit16_is_equal(a, b) == false); +} + +test "posit16_is_equal_nar" { + try std.testing.expect(posit16_is_equal(POS16_NAR, POS16_NAR) == false); +} + +test "posit16_is_greater_positive" { + given a = posit16_encode_f32(50.0) + try std.testing.expect(b = posit16_encode_f32(30.0)); + try std.testing.expect(posit16_is_greater(a, b) == true); +} + +test "posit16_max_returns_larger" { + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_max(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded >= 35.0); +} + +test "posit16_min_returns_smaller" { + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_min(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded <= 45.0); +} + +test "posit16_relu_positive" { + given value = posit16_encode_f32(50.0) + try std.testing.expect(result = posit16_relu(value)); + try std.testing.expect(posit16_is_equal(result, value) == true); +} + +test "posit16_relu_negative" { + given value = posit16_encode_f32(-50.0) + try std.testing.expect(result = posit16_relu(value)); + try std.testing.expect(result == POS16_ZERO); +} + +test "posit16_sigmoid_zero" { + given value = POS16_ZERO + try std.testing.expect(result = posit16_sigmoid(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 0.5) < 0.1); +} + +test "posit16_sigmoid_positive_large" { + given value = posit16_encode_f32(10.0) + try std.testing.expect(result = posit16_sigmoid(value)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(decoded >= 0.9); +} + +test "posit16_quantize_to_int4_max" { + given value = posit16_encode_f32(100.0) + try std.testing.expect(result = posit16_quantize_to_int4(value)); + try std.testing.expect(result == 7); +} + +test "posit16_quantize_to_int4_min" { + given value = posit16_encode_f32(-100.0) + try std.testing.expect(result = posit16_quantize_to_int4(value)); + try std.testing.expect(result == -8); +} + +test "posit16_from_f32_scaled_up" { + given value = 1.0 + try std.testing.expect(scale = 100.0); + try std.testing.expect(result = posit16_from_f32_scaled(value, scale)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 100.0) < 20.0); +} + +test "posit16_dot_product_simple" { + given a = [_]Posit16{posit16_encode_f32(1.0), posit16_encode_f32(2.0), posit16_encode_f32(3.0), posit16_encode_f32(4.0)} + try std.testing.expect(b = [_]Posit16{posit16_encode_f32(2.0), posit16_encode_f32(2.0), posit16_encode_f32(2.0), posit16_encode_f32(2.0)}); + try std.testing.expect(result = posit16_dot_product(a, b)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 20.0) < 5.0); +} + +test "posit16_vector_sum_simple" { + given vec = [_]Posit16{posit16_encode_f32(1.0), posit16_encode_f32(2.0), posit16_encode_f32(3.0), posit16_encode_f32(4.0)} + try std.testing.expect(result = posit16_vector_sum(vec)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 10.0) < 3.0); +} + +test "posit16_vector_mean_simple" { + given vec = [_]Posit16{posit16_encode_f32(2.0), posit16_encode_f32(4.0), posit16_encode_f32(6.0), posit16_encode_f32(8.0)} + try std.testing.expect(result = posit16_vector_mean(vec)); + try std.testing.expect(decoded = posit16_decode_f32(result)); + try std.testing.expect(abs(decoded - 5.0) < 1.0); +} + +test "posit16_lerp_zero" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(t = 0.0); + try std.testing.expect(result = posit16_lerp(a, b, t)); + try std.testing.expect(posit16_is_equal(result, a) == true); +} + +test "posit16_lerp_one" { + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(t = 1.0); + try std.testing.expect(result = posit16_lerp(a, b, t)); + try std.testing.expect(posit16_is_equal(result, b) == true); +} + +// ============================================================================ +// Invariants +// ============================================================================ + +} +invariant posit16_bits_constant + assert BITS == 16 + +invariant posit16_es_one + assert ES == 1 + +invariant posit16_nbits_fifteen + assert NBITS == 15 + +invariant posit16_zero_no_sign + assert (POS16_ZERO & SIGN_MASK) == 0 + +invariant posit16_nar_has_sign + assert (POS16_NAR & SIGN_MASK) != 0 + +invariant posit16_zero_nar_distinct + assert POS16_ZERO != POS16_NAR + +invariant posit16_abs_removes_sign + assert posit16_abs(POS16_NAR) != POS16_NAR // NaR preserves sign pattern + +invariant posit16_neg_zero_is_zero + assert posit16_neg(POS16_ZERO) == POS16_ZERO + +invariant posit16_neg_nar_is_nar + assert posit16_neg(POS16_NAR) == POS16_NAR + +invariant posit16_add_zero_identity + given value = posit16_encode_f32(50.0) + assert posit16_is_equal(posit16_add(value, POS16_ZERO), value) == true + +invariant posit16_sub_zero_identity + given value = posit16_encode_f32(50.0) + assert posit16_is_equal(posit16_sub(value, POS16_ZERO), value) == true + +invariant posit16_mul_zero_zero + given value = posit16_encode_f32(50.0) + assert posit16_mul(value, POS16_ZERO) == POS16_ZERO + +invariant posit16_div_nar_propagates + given value = posit16_encode_f32(50.0) + assert posit16_is_nar(posit16_div(POS16_NAR, value)) == true + +invariant posit16_relu_non_negative + given result = posit16_relu(posit16_encode_f32(50.0)) + try std.testing.expect(posit16_extract_sign(result) == false); + +invariant posit16_sqrt_non_negative + given result = posit16_sqrt(posit16_encode_f32(4.0)) + try std.testing.expect(posit16_extract_sign(result) == false); + +invariant posit16_quantize_to_int4_in_range + given result = posit16_quantize_to_int4(posit16_encode_f32(50.0)) + assert result >= -8 and result <= 7 + +invariant posit16_scale_roundtrip + given value = posit16_encode_f32(50.0) + try std.testing.expect(scale = 2.0); + try std.testing.expect(scaled = posit16_scale(value, scale)); + try std.testing.expect(decoded = posit16_to_f32_scaled(scaled, scale)); + try std.testing.expect(abs(decoded - posit16_decode_f32(value)) < 5.0); + +invariant posit16_max_ge_both + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_max(a, b)); + try std.testing.expect(posit16_is_greater(result, a) == true or posit16_is_equal(result, a) == true); + +invariant posit16_min_le_both + given a = posit16_encode_f32(30.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result = posit16_min(a, b)); + try std.testing.expect(posit16_is_greater(b, result) == true or posit16_is_equal(b, result) == true); + +invariant posit16_lerp_bounded + given a = posit16_encode_f32(10.0) + try std.testing.expect(b = posit16_encode_f32(50.0)); + try std.testing.expect(result_half = posit16_lerp(a, b, 0.5)); + try std.testing.expect(decoded_a = posit16_decode_f32(a)); + try std.testing.expect(decoded_b = posit16_decode_f32(b)); + try std.testing.expect(decoded_half = posit16_decode_f32(result_half)); + try std.testing.expect(decoded_half > decoded_a and decoded_half < decoded_b); + +// ============================================================================ +// Benchmarks +// ============================================================================ + +bench posit16_encode_f32_latency + measure: nanoseconds to posit16_encode_f32(10.5) + target: < 50ns + +bench posit16_decode_f32_latency + measure: nanoseconds to posit16_decode_f32(0x4000) + target: < 50ns + +bench posit16_add_latency + measure: nanoseconds to posit16_add(0x4000, 0x4800) + target: < 100ns + +bench posit16_mul_latency + measure: nanoseconds to posit16_mul(0x4000, 0x4000) + target: < 100ns + +bench posit16_sqrt_latency + measure: nanoseconds to posit16_sqrt(0x4000) + target: < 100ns + +bench posit16_sigmoid_latency + measure: nanoseconds to posit16_sigmoid(0x4000) + target: < 200ns + +bench posit16_relu_latency + measure: nanoseconds to posit16_relu(0x4800) + target: < 20ns \ No newline at end of file diff --git a/apps/website/public/t27/files/chips/phi/specs/numeric/tri_net_formats.t27 b/apps/website/public/t27/files/chips/phi/specs/numeric/tri_net_formats.t27 new file mode 100644 index 0000000000..bef8b1be89 --- /dev/null +++ b/apps/website/public/t27/files/chips/phi/specs/numeric/tri_net_formats.t27 @@ -0,0 +1,993 @@ +// SPDX-License-Identifier: Apache-2.0 +// t27/specs/numeric/tri_net_formats.t27 +// TRI NET Format Registry — Complete Format Specification +// phi^2 + 1/phi^2 = 3 | TRINITY +// +// Complete format registry for TRI-NET neural accelerator: +// - GoldenFloat family (GF4-GF256) +// - IEEE 754 formats (fp32, fp16) +// - Brain Float (bf16) +// - FP8 variants (e4m3, e5m2) +// - Integer formats (int4, int8) +// - Quantization formats (nf4) +// - Posit format (posit16) +// - Binary format (binary16) +// +// R-SI-1: Zero * operators, use XOR/shift/add only + +module TriNetFormats { + // Import sacred constants for phi-based optimization + use math::constants; + use math::sacred_physics; + use numeric::goldenfloat_family; + + // ======================================================================== + // 1. Format Classification Registry + // ======================================================================== + + // Format categories for TRI-NET hardware path selection + pub const FormatCategory = enum(u8) { + goldenfloat, // GF family: phi-optimized + ieee754, // IEEE 754 standard formats + integer, // Integer quantization + posit, // Posit universal number format + quantized, // Special quantization (NF4, etc) + binary, // Binary formats + }; + + // ======================================================================== + // 2. GoldenFloat Extended Family (GF64, GF128, GF256) + // ======================================================================== + // + // Formula: exp/mant ≈ 1/φ ≈ 0.618 + // Using phi_split: exp = round((N-1)/φ²), mant = N - 1 - exp + // + // GF64: 64 bits → exp=24, mant=39 (ratio=0.615, φ_dist=0.003) + // GF128: 128 bits → exp=48, mant=79 (ratio=0.608, φ_dist=0.010) + // GF256: 256 bits → exp=97, mant=158 (ratio=0.614, φ_dist=0.004) + + pub struct GF64Format { + bits : u8 = 64, + sign_bits : u8 = 1, + exp_bits : u8 = 24, + mant_bits : u8 = 39, + exp_bias : u32 = 8388607, // 2^(24-1) - 1 + phi_ratio : f64 = 24.0 / 39.0, + phi_dist : f64 = abs(24.0/39.0 - sacred_physics::PHI_INV), + is_primary : bool = false, + } + + pub struct GF128Format { + bits : u8 = 128, + sign_bits : u8 = 1, + exp_bits : u8 = 48, + mant_bits : u8 = 79, + exp_bias : u64 = 140737488355327, // 2^(48-1) - 1 + phi_ratio : f64 = 48.0 / 79.0, + phi_dist : f64 = abs(48.0/79.0 - sacred_physics::PHI_INV), + is_primary : bool = false, + } + + pub struct GF256Format { + bits : u8 = 256, + sign_bits : u8 = 1, + exp_bits : u8 = 97, + mant_bits : u8 = 158, + exp_bias : u128 = 0x7FFFFFFFFFFFFFFF, // 2^(97-1) - 1 + phi_ratio : f64 = 97.0 / 158.0, + phi_dist : f64 = abs(97.0/158.0 - sacred_physics::PHI_INV), + is_primary : bool = false, + } + + // ======================================================================== + // 3. IEEE 754 Standard Formats + // ======================================================================== + + pub struct FP32Format { + bits : u8 = 32, + sign_bits : u8 = 1, + exp_bits : u8 = 8, + mant_bits : u8 = 23, + exp_bias : u8 = 127, + category : FormatCategory = .ieee754, + ieee_compliant : bool = true, + } + + pub struct FP16Format { + bits : u8 = 16, + sign_bits : u8 = 1, + exp_bits : u8 = 5, + mant_bits : u8 = 10, + exp_bias : u8 = 15, + category : FormatCategory = .ieee754, + ieee_compliant : bool = true, + note : "binary16 = FP16", + } + + pub struct BF16Format { + bits : u8 = 16, + sign_bits : u8 = 1, + exp_bits : u8 = 8, + mant_bits : u8 = 7, + exp_bias : u8 = 127, + category : FormatCategory = .ieee754, + ieee_compliant : bool = false, // Not in IEEE 754-2008, widely used + note : "Brain Float: same exp as FP32, truncated mantissa", + } + + // ======================================================================== + // 4. FP8 Variants (OCP 8-bit specification) + // ======================================================================== + + pub struct FP8E4M3Format { + bits : u8 = 8, + sign_bits : u8 = 1, + exp_bits : u8 = 4, + mant_bits : u8 = 3, + exp_bias : u8 = 7, + category : FormatCategory = .ieee754, + note : "OCP E4M3: 1 exp, 8 mant values for training", + range : string = "[-448, 448]", + } + + pub struct FP8E5M2Format { + bits : u8 = 8, + sign_bits : u8 = 1, + exp_bits : u8 = 5, + mant_bits : u8 = 2, + exp_bias : u8 = 15, + category : FormatCategory = .ieee754, + note : "OCP E5M2: better dynamic range for inference", + range : string = "[-57344, 57344]", + } + + // ======================================================================== + // 5. Integer Formats (for quantization) + // ======================================================================== + + pub struct Int4Format { + bits : u8 = 4, + sign_bits : u8 = 1, + data_bits : u8 = 3, + category : FormatCategory = .integer, + range : string = "[-8, 7]", + quantization_scale : f32 = 0.0625, + } + + pub struct Int8Format { + bits : u8 = 8, + sign_bits : u8 = 1, + data_bits : u8 = 7, + category : FormatCategory = .integer, + range : string = "[-128, 127]", + quantization_scale : f32 = 0.0039216, + } + + // ======================================================================== + // 6. NormalFloat4 (NF4) Format + // ======================================================================== + // + // NF4: 4-bit format with values drawn from normal distribution + // Values: discretized Normal(0, 1) quantiles + // Used in QLoRA for efficient 4-bit fine-tuning + // + // 16 levels: {-1.0, -0.6962, -0.5251, -0.3942, -0.2844, -0.1848, -0.0910, -0.0000, + // 0.0000, 0.0910, 0.1848, 0.2844, 0.3942, 0.5251, 0.6962, 1.0} + // + // Note: Zero appears twice (positive/negative) for perfect rounding + + pub const NF4_LEVELS : [16]f64 = [ + -1.0, -0.6961928009210449, -0.5250720977783203, -0.3941699969768524, + -0.28444138169288635, -0.18477343022823334, -0.09105003625154495, -0.0000000000000000, + 0.0000000000000000, 0.09105003625154495, 0.18477343022823334, 0.28444138169288635, + 0.3941699969768524, 0.5250720977783203, 0.6961928009210449, 1.0, + ]; + + pub struct NF4Format { + bits : u8 = 4, + category : FormatCategory = .quantized, + range : string = "[-1.0, 1.0]", + distribution : string = "Normal(0, 1) quantiles", + num_levels : u8 = 16, + } + + // ======================================================================== + // 7. Posit16 Format (Type III Unum) + // ======================================================================== + // + // Posit format: [sign][regime][exponent][mantissa] + // regime: uses of trailing zeros/followed by zeros + // es (exponent size) = 1 for posit16 + // + // bit layout: [S(1) regime(6) E(1) M(8)] + // + // Properties: + // - Exact representation of small integers + // - Symmetric positive/negative + // - Gradual overflow/underflow + // - Better dynamic range than FP16 for same precision + + pub struct Posit16Format { + bits : u8 = 16, + sign_bits : u8 = 1, + es_bits : u8 = 1, // exponent size + nbits : u8 = 16, + category : FormatCategory = .posit, + useed : u8 = 2, // 2^es = 2 + max_pos : f64 = 262144.0, + min_pos : f64 = 0.000000059604645, + } + + // ======================================================================== + // 8. Binary16 Format + // ======================================================================== + // + // Binary16 is just an alias for IEEE FP16 + // Kept separate for format routing clarity + + pub struct Binary16Format { + bits : u8 = 16, + sign_bits : u8 = 1, + exp_bits : u8 = 5, + mant_bits : u8 = 10, + exp_bias : u8 = 15, + category : FormatCategory = .binary, + note : "Same as FP16, separate routing class", + } + + // ======================================================================== + // 9. Format Registry (Lookup Table) + // ======================================================================== + + pub struct FormatDescriptor { + name : string, + category : FormatCategory, + bits : u8, + sign_bits : u8, + exp_bits : u8, + mant_bits : u8, + exp_bias : u32, + phi_ratio : f64, + phi_dist : f64, + memory_ratio : f64, // vs FP32 + use_case : string, + } + + pub const TRI_NET_FORMATS : [16]FormatDescriptor = [ + // GoldenFloat family (phi-optimized) + FormatDescriptor{ + name = "GF4", + category = .goldenfloat, + bits = 4, + sign_bits = 1, + exp_bits = 1, + mant_bits = 2, + exp_bias = 0, + phi_ratio = 0.5, + phi_dist = 0.118, + memory_ratio = 0.125, + use_case = "Extreme compression, attention masks", + }, + FormatDescriptor{ + name = "GF8", + category = .goldenfloat, + bits = 8, + sign_bits = 1, + exp_bits = 3, + mant_bits = 4, + exp_bias = 3, + phi_ratio = 0.75, + phi_dist = 0.132, + memory_ratio = 0.25, + use_case = "Weight quantization, activation caching", + }, + FormatDescriptor{ + name = "GF12", + category = .goldenfloat, + bits = 12, + sign_bits = 1, + exp_bits = 4, + mant_bits = 7, + exp_bias = 7, + phi_ratio = 0.571, + phi_dist = 0.047, + memory_ratio = 0.375, + use_case = "Best phi approximation, critical weights", + }, + FormatDescriptor{ + name = "GF16", + category = .goldenfloat, + bits = 16, + sign_bits = 1, + exp_bits = 6, + mant_bits = 9, + exp_bias = 31, + phi_ratio = 0.667, + phi_dist = 0.049, + memory_ratio = 0.5, + use_case = "PRIMARY FORMAT, IGLA main weights", + }, + FormatDescriptor{ + name = "GF20", + category = .goldenfloat, + bits = 20, + sign_bits = 1, + exp_bits = 7, + mant_bits = 12, + exp_bias = 63, + phi_ratio = 0.583, + phi_dist = 0.035, + memory_ratio = 0.625, + use_case = "High-precision ML training", + }, + FormatDescriptor{ + name = "GF24", + category = .goldenfloat, + bits = 24, + sign_bits = 1, + exp_bits = 9, + mant_bits = 14, + exp_bias = 255, + phi_ratio = 0.643, + phi_dist = 0.025, + memory_ratio = 0.75, + use_case = "Financial, numerical stability", + }, + FormatDescriptor{ + name = "GF32", + category = .goldenfloat, + bits = 32, + sign_bits = 1, + exp_bits = 12, + mant_bits = 19, + exp_bias = 2047, + phi_ratio = 0.632, + phi_dist = 0.014, + memory_ratio = 1.0, + use_case = "Near-IEEE precision, wider range", + }, + FormatDescriptor{ + name = "GF64", + category = .goldenfloat, + bits = 64, + sign_bits = 1, + exp_bits = 24, + mant_bits = 39, + exp_bias = 8388607, + phi_ratio = 0.615, + phi_dist = 0.003, + memory_ratio = 2.0, + use_case = "Extended range, scientific computing", + }, + FormatDescriptor{ + name = "GF128", + category = .goldenfloat, + bits = 128, + sign_bits = 1, + exp_bits = 48, + mant_bits = 79, + exp_bias = 140737488355327, + phi_ratio = 0.608, + phi_dist = 0.010, + memory_ratio = 4.0, + use_case = "Ultra-high precision, cryptography", + }, + FormatDescriptor{ + name = "GF256", + category = .goldenfloat, + bits = 256, + sign_bits = 1, + exp_bits = 97, + mant_bits = 158, + exp_bias = 0x7FFFFFFFFFFFFFFF, + phi_ratio = 0.614, + phi_dist = 0.004, + memory_ratio = 8.0, + use_case = "Maximum precision, physics simulations", + }, + // IEEE 754 formats + FormatDescriptor{ + name = "FP32", + category = .ieee754, + bits = 32, + sign_bits = 1, + exp_bits = 8, + mant_bits = 23, + exp_bias = 127, + phi_ratio = 0.348, + phi_dist = 0.270, + memory_ratio = 1.0, + use_case = "Standard training, reference", + }, + FormatDescriptor{ + name = "FP16", + category = .ieee754, + bits = 16, + sign_bits = 1, + exp_bits = 5, + mant_bits = 10, + exp_bias = 15, + phi_ratio = 0.5, + phi_dist = 0.118, + memory_ratio = 0.5, + use_case = "Standard half-precision, inference", + }, + FormatDescriptor{ + name = "BF16", + category = .ieee754, + bits = 16, + sign_bits = 1, + exp_bits = 8, + mant_bits = 7, + exp_bias = 127, + phi_ratio = 1.143, + phi_dist = 0.525, + memory_ratio = 0.5, + use_case = "Brain Float, gradient accumulation", + }, + // FP8 variants + FormatDescriptor{ + name = "FP8_E4M3", + category = .ieee754, + bits = 8, + sign_bits = 1, + exp_bits = 4, + mant_bits = 3, + exp_bias = 7, + phi_ratio = 1.333, + phi_dist = 0.715, + memory_ratio = 0.25, + use_case = "OCP training format", + }, + FormatDescriptor{ + name = "FP8_E5M2", + category = .ieee754, + bits = 8, + sign_bits = 1, + exp_bits = 5, + mant_bits = 2, + exp_bias = 15, + phi_ratio = 2.5, + phi_dist = 1.882, + memory_ratio = 0.25, + use_case = "OCP inference format", + }, + // Posit and Binary + FormatDescriptor{ + name = "Posit16", + category = .posit, + bits = 16, + sign_bits = 1, + exp_bits = 1, + mant_bits = 8, + exp_bias = 0, + phi_ratio = 0.125, + phi_dist = 0.493, + memory_ratio = 0.5, + use_case = "Type III Unum, exact integers", + }, + FormatDescriptor{ + name = "Binary16", + category = .binary, + bits = 16, + sign_bits = 1, + exp_bits = 5, + mant_bits = 10, + exp_bias = 15, + phi_ratio = 0.5, + phi_dist = 0.118, + memory_ratio = 0.5, + use_case = "Alias for FP16, binary routing", + }, + ]; + + // ======================================================================== + // 10. Format Lookup Functions + // ======================================================================== + + pub fn get_format_by_name(name: string) -> ?FormatDescriptor { + for (const TRI_NET_FORMATS) |fmt| { + if (fmt.name == name) { + return fmt; + } + } + return null; + } + + pub fn get_format_by_bits(bits: u8) -> []FormatDescriptor { + var result : [16]FormatDescriptor = undefined; + var count : u8 = 0; + for (const TRI_NET_FORMATS) |fmt| { + if (fmt.bits == bits) { + result[count] = fmt; + count = count + 1; + } + } + return result[0..count]; + } + + pub fn get_phi_optimal_format() -> FormatDescriptor { + var best : FormatDescriptor = TRI_NET_FORMATS[0]; + for (const TRI_NET_FORMATS) |fmt| { + if (fmt.phi_dist < best.phi_dist) { + best = fmt; + } + } + return best; + } + + // ======================================================================== + // 11. Format Conversion Utilities + // ======================================================================== + + pub fn format_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Format-specific conversion to f32 + if (fmt.category == .goldenfloat) { + return gf_to_f32(fmt, raw); + } else if (fmt.category == .ieee754) { + return ieee754_to_f32(fmt, raw); + } else if (fmt.category == .posit) { + return posit_to_f32(fmt, raw); + } else if (fmt.category == .integer) { + return int_to_f32(fmt, raw); + } else if (fmt.category == .quantized) { + return nf4_to_f32(raw); + } + return 0.0; + } + + pub fn f32_to_format(fmt: FormatDescriptor, value: f32) -> u64 { + // Format-specific conversion from f32 + if (fmt.category == .goldenfloat) { + return f32_to_gf(fmt, value); + } else if (fmt.category == .ieee754) { + return f32_to_ieee754(fmt, value); + } else if (fmt.category == .posit) { + return f32_to_posit(fmt, value); + } else if (fmt.category == .integer) { + return f32_to_int(fmt, value); + } else if (fmt.category == .quantized) { + return f32_to_nf4(value); + } + return 0; + } + + // ======================================================================== + // 12. GF Conversion Functions (Stub) + // ======================================================================== + + fn gf_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Extract sign, exponent, mantissa + const sign = (raw >> (fmt.bits - 1)) as u8; + const exp_mask = (1u64 << fmt.exp_bits) - 1; + const mant_mask = (1u64 << fmt.mant_bits) - 1; + const exp_biased = ((raw >> fmt.mant_bits) & exp_mask) as u32; + const mant = (raw & mant_mask) as u32; + + // Handle zero + if (exp_biased == 0 && mant == 0) { + return 0.0; + } + + // Unbias exponent + const exp_unbiased = if (exp_biased == 0) { + -(fmt.exp_bias as i32) + 1 + } else { + (exp_biased as i32) - fmt.exp_bias as i32 + }; + + // Normalize mantissa + const max_mant = (1u32 << fmt.mant_bits) - 1; + const mant_normalized = if (exp_biased == 0) { + (mant as f32) / (max_mant + 1) as f32 + } else { + 1.0 + (mant as f32) / (max_mant + 1) as f32 + }; + + const value = mant_normalized * pow(2.0, exp_unbiased as f32); + + if (sign != 0) { + return -value; + } + return value; + } + + fn f32_to_gf(fmt: FormatDescriptor, value: f32) -> u64 { + if (value == 0.0) { + return 0; + } + + const sign = if (value < 0.0) { 1u64 } else { 0u64 }; + const abs_val = if (value < 0.0) { -value } else { value }; + + // Extract exponent + const exp_unbiased = floor_log2(abs_val) as i32; + let exp_biased = (exp_unbiased + fmt.exp_bias as i32) as u64; + + // Clamp exponent + const max_exp = (1u64 << fmt.exp_bits) - 1; + if (exp_biased > max_exp) { + exp_biased = max_exp; + } + + // Extract mantissa + const max_mant = (1u64 << fmt.mant_bits) - 1; + const normalized = abs_val / pow(2.0, exp_unbiased as f32); + const frac = normalized - 1.0; + const mant = (frac * (max_mant + 1) as f32) as u64; + const clamped_mant = if (mant > max_mant) { max_mant } else { mant }; + + return (sign << (fmt.bits - 1)) | + (exp_biased << fmt.mant_bits) | + clamped_mant; + } + + // ======================================================================== + // 13. IEEE 754 Conversion Functions (Stub) + // ======================================================================== + + fn ieee754_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Handle FP32 (direct reinterpret) + if (fmt.bits == 32 && fmt.name == "FP32") { + return @bitCast(f32, @as(u32, raw)); + } + + // Handle smaller formats (need conversion) + // This is a stub - real implementation would do proper conversion + return 0.0; + } + + fn f32_to_ieee754(fmt: FormatDescriptor, value: f32) -> u64 { + // Handle FP32 (direct reinterpret) + if (fmt.bits == 32 && fmt.name == "FP32") { + return @as(u64, @bitCast(u32, value)); + } + + // Handle smaller formats (need conversion) + // This is a stub - real implementation would do proper conversion + return 0; + } + + // ======================================================================== + // 14. Posit Conversion Functions (Stub) + // ======================================================================== + + fn posit_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Posit decode + // This is a stub - real implementation would do proper decoding + return 0.0; + } + + fn f32_to_posit(fmt: FormatDescriptor, value: f32) -> u64 { + // Posit encode + // This is a stub - real implementation would do proper encoding + return 0; + } + + // ======================================================================== + // 15. Integer Conversion Functions + // ======================================================================== + + fn int_to_f32(fmt: FormatDescriptor, raw: u64) -> f32 { + // Sign-extend integer to f32 + const sign_bit = (raw >> (fmt.bits - 1)) as u8; + const magnitude = raw & ((1u64 << (fmt.bits - 1)) - 1); + const max_val = 1u64 << (fmt.bits - 1); + + if (sign_bit != 0) { + return -((max_val - magnitude) as f32); + } + return magnitude as f32; + } + + fn f32_to_int(fmt: FormatDescriptor, value: f32) -> u64 { + const max_val = (1i64 << (fmt.bits - 1)) - 1; + const min_val = -max_val - 1; + + let clamped = value as i64; + if (clamped > max_val) { + clamped = max_val; + } else if (clamped < min_val) { + clamped = min_val; + } + + if (clamped < 0) { + return ((1u64 << fmt.bits) + @as(u64, @bitCast(u64, clamped))); + } + return @as(u64, @bitCast(u64, clamped)); + } + + // ======================================================================== + // 16. NF4 Conversion Functions + // ======================================================================== + + fn nf4_to_f32(raw: u64) -> f32 { + const index = raw & 0xF; + return NF4_LEVELS[@as(usize, index)] as f32; + } + + fn f32_to_nf4(value: f32) -> u64 { + let best_idx : u64 = 0; + let best_dist : f64 = 1e9; + + var i : u64 = 0; + while (i < 16) { + const dist = abs(value - NF4_LEVELS[@as(usize, i)] as f32); + if (dist < best_dist) { + best_dist = dist; + best_idx = i; + } + i = i + 1; + } + + return best_idx; + } + + // ======================================================================== + // 17. Helper Functions + // ======================================================================== + + fn floor_log2(x: f32) -> i32 { + if (x <= 0.0) { return -2147483648; } + let exp : i32 = 0; + while (x >= 2.0) { + x = x / 2.0; + exp = exp + 1; + } + while (x < 1.0) { + x = x * 2.0; + exp = exp - 1; + } + return exp; + } + + fn pow(base: f32, exp: f32) -> f32 { + if (base <= 0.0 || exp == 0.0) { + if (exp == 0.0) { + return 1.0; + } + if (base == 0.0 && exp > 0.0) { + return 0.0; + } + return 0.0 / 0.0; + } + + const is_integer = exp == @floor(exp); + + if (is_integer) { + let exp_int = exp as i32; + let result = 1.0; + let base_acc = base; + let e = exp_int; + + if (e < 0) { + e = -e; + base_acc = 1.0 / base_acc; + } + + while (e > 0) { + if (e % 2 == 1) { + result = result * base_acc; + } + base_acc = base_acc * base_acc; + e = e / 2; + } + + return result; + } + + const ln_val = ln_approx(base); + return exp_approx(exp * ln_val); + } + + fn ln_approx(x: f32) -> f32 { + if (x <= 0.0) { + return 0.0 / 0.0; + } + if (x == 1.0) { + return 0.0; + } + + const t = (x - 1.0) / (x + 1.0); + const t2 = t * t; + const t3 = t2 * t; + + return 2.0 * (t + t3 / 3.0); + } + + fn exp_approx(x: f32) -> f32 { + if (x == 0.0) { + return 1.0; + } + + let result = 1.0; + let term = 1.0; + let exp_x = x; + + for (i in 1..=8) { + term = term * exp_x / (i as f32); + result = result + term; + } + + return result; + } + + fn @floor(x: f32) -> f32 { + let xi = x as i32; + if (x >= 0.0 || x == xi as f32) { + return xi as f32; + } + return (xi - 1) as f32; + } + + fn abs(x: f32) -> f32 { + if (x < 0.0) { + return -x; + } + return x; + } + + // ======================================================================== + // TDD-Inside-Spec: Tests and Invariants for TRI_NET_FORMATS + // ======================================================================== + + test tri_net_formats_count_is_17 + given count = TRI_NET_FORMATS.len() + then count == 17 + + test tri_net_gf16_is_primary + given gf16 = get_format_by_name("GF16").? + then gf16.name == "GF16" and gf16.phi_dist < 0.05 + + test tri_net_gf12_has_best_phi_approximation + given best = get_phi_optimal_format() + then best.name == "GF12" and best.phi_dist < 0.05 + + test tri_net_all_formats_have_positive_bits + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.bits > 0; + } + + test tri_net_all_formats_sum_bits_correct + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.sign_bits + fmt.exp_bits + fmt.mant_bits == fmt.bits + or fmt.category == .posit; // Posit has different structure + } + + test tri_nft_n4_levels_count_is_16 + given count = NF4_LEVELS.len() + then count == 16 + + test tri_net_nf4_levels_symmetric + given center1 = NF4_LEVELS[6] + and center2 = NF4_LEVELS[7] + then abs(center1 + center2) < 1e-10 + + test tri_net_nf4_range_is_minus_one_to_one + given min_val = NF4_LEVELS[0] + and max_val = NF4_LEVELS[15] + then abs(min_val + 1.0) < 1e-10 and abs(max_val - 1.0) < 1e-10 + + test tri_nft_format_lookup_by_name_gf16 + given fmt = get_format_by_name("GF16") + then fmt != null and fmt.?.bits == 16 + + test tri_net_format_lookup_by_bits_16_returns_formats + given formats = get_format_by_bits(16) + and count = formats.len() + then count >= 3 // GF16, FP16, BF16, Binary16 + + test tri_nft_fp32_ieee_compliant + given fp32 = get_format_by_name("FP32").? + then fp32.ieee_compliant == true and fp32.category == .ieee754 + + test tri_net_bf16_not_ieee_compliant + given bf16 = get_format_by_name("BF16").? + then bf16.ieee_compliant == false and bf16.category == .ieee754 + + test tri_nft_fp8_e4m3_has_4_exp_3_mant + given e4m3 = get_format_by_name("FP8_E4M3").? + then e4m3.exp_bits == 4 and e4m3.mant_bits == 3 + + test tri_net_fp8_e5m2_has_5_exp_2_mant + given e5m2 = get_format_by_name("FP8_E5M2").? + then e5m2.exp_bits == 5 and e5m2.mant_bits == 2 + + test tri_net_gf64_has_24_exp_39_mant + given gf64 = get_format_by_name("GF64").? + then gf64.exp_bits == 24 and gf64.mant_bits == 39 + + test tri_net_gf128_has_48_exp_79_mant + given gf128 = get_format_by_name("GF128").? + then gf128.exp_bits == 48 and gf128.mant_bits == 79 + + test tri_net_gf256_has_97_exp_158_mant + given gf256 = get_format_by_name("GF256").? + then gf256.exp_bits == 97 and gf256.mant_bits == 158 + + test tri_net_posit16_has_category_posit + given posit16 = get_format_by_name("Posit16").? + then posit16.category == .posit + + test tri_net_binary16_same_as_fp16 + given binary16 = get_format_by_name("Binary16").? + and fp16 = get_format_by_name("FP16").? + then binary16.bits == fp16.bits and + binary16.exp_bits == fp16.exp_bits and + binary16.mant_bits == fp16.mant_bits + + test tri_net_nf4_to_f32_negative + given value = nf4_to_f32(0) + then value < 0.0 and abs(value + 1.0) < 1e-10 + + test tri_net_nf4_to_f32_positive + given value = nf4_to_f32(15) + then value > 0.0 and abs(value - 1.0) < 1e-10 + + test tri_net_nf4_to_f32_center + given value1 = nf4_to_f32(6) + and value2 = nf4_to_f32(7) + then abs(value1 + value2) < 1e-10 + + test tri_net_f32_to_nf4_positive + given result = f32_to_nf4(1.0) + then result == 15 + + test tri_net_f32_to_nf4_negative + given result = f32_to_nf4(-1.0) + then result == 0 + + test tri_net_f32_to_nf4_zero + given result = f32_to_nf4(0.0) + then result == 6 or result == 7 // Either center value + + invariant tri_net_all_formats_have_valid_category + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.category >= .goldenfloat and fmt.category <= .binary; + } + + invariant tri_net_all_phi_distances_non_negative + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.phi_dist >= 0.0; + } + + invariant tri_nft_all_memory_ratios_positive + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.memory_ratio > 0.0; + } + + invariant tri_net_gf_formats_have_best_phi_distance + const golden_fmts = get_format_by_bits(16); // GF16 vs others + // GF family should have better phi distance than IEEE formats + // (This is a rough check - detailed comparison would need more logic) + + invariant tri_net_nf4_levels_are_monotonic + for (i in 0..15) { + if (i < 15) { + assert NF4_LEVELS[i] < NF4_LEVELS[i + 1]; + } + } + + invariant tri_net_format_registry_consistent + for (const TRI_NET_FORMATS) |fmt| { + assert fmt.name.len() > 0; + assert fmt.bits >= 4; + assert fmt.sign_bits == 1; + } + + bench tri_net_get_format_by_name_latency + measure: nanoseconds to get_format_by_name("GF16") + target: < 100ns + + bench tri_net_get_phi_optimal_format_latency + measure: nanoseconds to get_phi_optimal_format() + target: < 200ns + + bench tri_net_f32_to_nf4_latency + measure: nanoseconds to f32_to_nf4(0.5) + target: < 100ns + + bench tri_net_nf4_to_f32_latency + measure: nanoseconds to nf4_to_f32(8) + target: < 50ns +} \ No newline at end of file diff --git a/apps/website/public/t27/files/compiler/ast.t27 b/apps/website/public/t27/files/compiler/ast.t27 new file mode 100644 index 0000000000..b54ae6ae3d --- /dev/null +++ b/apps/website/public/t27/files/compiler/ast.t27 @@ -0,0 +1,611 @@ +// ast.t27 -- Abstract Syntax Tree for TRI-27 Assembly +// This file defines the AST structure used by the t27 compiler + +module ast { + // ===================================================================== + // Token types -- enum of all token types (shared between lexer and parser) + // ===================================================================== + + pub const TokenType = enum(u8) { + // Punctuation + EOF = 0, + Newline = 1, + Dot = 2, + Colon = 3, + Semicolon = 4, + Comma = 5, + Hash = 6, + LParen = 7, + RParen = 8, + LBracket = 9, + RBracket = 10, + Plus = 11, + Minus = 12, + Star = 13, + Slash = 14, + Percent = 15, + And = 16, + Or = 17, + Xor = 18, + Tilde = 19, + Lt = 20, + Gt = 21, + Eq = 22, + Excl = 23, + + // Keywords + Use = 24, + Const = 25, + Data = 26, + Code = 27, + DWord = 28, + DSpace = 29, + DTrit = 30, + + // TDD-Inside-Spec sections + Test = 31, + Invariant = 32, + Bench = 33, + Verify = 34, + Expected = 35, + Setup = 36, + Rationale = 37, + Measure = 38, + Target = 39, + + // Literals and identifiers + Integer = 40, + Float = 41, + String = 42, + Identifier = 43, + Reg = 44, + Label = 45, + + // Opcodes + MOV = 60, + JZ = 61, + JNZ = 62, + JMP = 63, + JGE = 64, + JGT = 65, + JLE = 66, + JLT = 67, + JEQ = 68, + JNE = 69, + CALL = 70, + RET = 71, + MUL = 72, + ADD = 73, + SUB = 74, + DIV = 75, + BIND = 76, + BUNDLE = 77, + HALT = 78, + PUSH = 79, + POP = 80, + LOAD = 81, + STORE = 82, + SHL = 83, + SHR = 84, + AndOp = 85, + OrOp = 86, + XorOp = 87, + NOT = 88, + NEG = 89, + SQRT = 90, + TANH = 91, + TRAP = 92, + + // TDD-Inside-Spec high-level keywords (spec-style) + Spec = 93, + Rule = 94, + Given = 95, + When = 96, + Then = 97, + Assert = 98, + KwAnd = 99, + Expect = 100, + }; + + // ===================================================================== + // Node types -- enum of all AST node types + // ===================================================================== + + pub const NodeType = enum(u8) { + // Program structure + Program = 0, + DataSection = 1, + CodeSection = 2, + + // Constants + ConstDef = 10, + + // Data declarations + DWord = 20, + DSpace = 21, + DTrit = 22, + + // Instructions + Mov = 30, + Jz = 31, + Jnz = 32, + Jmp = 33, + Mul = 34, + Add = 35, + Sub = 36, + Bind = 37, + Bundle = 38, + Halt = 39, + + // Operands + Reg = 40, + Imm = 41, + Label = 42, + Mem = 43, + + // TDD-Inside-Spec blocks (assembly-style) + Test = 50, + Invariant = 51, + Bench = 52, + TestCase = 53, + InvariantDecl = 54, + BenchDecl = 55, + + // TDD-Inside-Spec high-level structures (spec-style) + SpecDecl = 60, // spec keyword/module header + RuleBlock = 61, // rule block + GivenClause = 62, // given clause in test + WhenClause = 63, // when clause in test + ThenClause = 64, // then clause in test + AndClause = 65, // and clause in test + ExpectClause = 66, // expect clause in rule + AssertStmt = 67, // assert statement in invariant + TestBlock = 68, // test block with given/when/then + RuleDecl = 69, // rule declaration + }; + + // ===================================================================== + // AST Node -- base structure for all nodes + // ===================================================================== + + pub const ASTNode = struct { + node_type: NodeType, + line: u32, + column: u32, + source_file: []const u8, + }; + + // ===================================================================== + // Program -- root node + // ===================================================================== + + pub const Program = struct { + node: ASTNode, + spec_decl: ?SpecDecl, // spec declaration (high-level) + constants: []ConstDef, + data_section: DataSection, + code_section: CodeSection, + test_section: ?TestSection, // .test section + invariant_section: ?InvariantSection, // .invariant section + bench_section: ?BenchSection, // .bench section + exports: [][]const u8, // Exported symbols + imports: [][]const u8, // Imported modules + }; + + // ===================================================================== + // Section structures + // ===================================================================== + + pub const TestSection = struct { + node: ASTNode, + test_cases: []TestCase, + }; + + pub const InvariantSection = struct { + node: ASTNode, + invariants: []InvariantDecl, + }; + + pub const BenchSection = struct { + node: ASTNode, + benchmarks: []BenchDecl, + }; + + // ===================================================================== + // Constants and Data + // ===================================================================== + + pub const ConstDef = struct { + node: ASTNode, + name: []const u8, + value: i64, // Immediate value + }; + + pub const DataSection = struct { + node: ASTNode, + declarations: []DataDecl, + }; + + pub const DataDecl = struct { + node: ASTNode, + size: u8, // 1 = trit, 8 = dword, etc. + initial_value: i64, + label: []const u8, + }; + + // ===================================================================== + // Code and Instructions + // ===================================================================== + + pub const CodeSection = struct { + node: ASTNode, + instructions: []Instruction, + labels: std.StringHashMap(u32), // Label -> instruction index + }; + + pub const Instruction = struct { + node: ASTNode, + opcode: Opcode, + operands: []Operand, + }; + + pub const Opcode = enum(u8) { + MOV = 0, + JZ = 1, + JNZ = 2, + JMP = 3, + MUL = 4, + ADD = 5, + SUB = 6, + BIND = 7, + BUNDLE = 8, + HALT = 9, + }; + + pub const Operand = struct { + node: ASTNode, + operand_type: OperandType, + }; + + pub const OperandType = enum(u8) { + Register = 0, + Immediate = 1, + LabelRef = 2, + Memory = 3, + }; + + pub const RegOperand = struct { + node: ASTNode, + reg_num: u8, // 0-25 for general, 26 for zero + }; + + pub const ImmOperand = struct { + node: ASTNode, + value: i64, + }; + + pub const LabelOperand = struct { + node: ASTNode, + label_name: []const u8, + }; + + pub const MemOperand = struct { + node: ASTNode, + base_reg: u8, + offset: i16, + }; + + // ===================================================================== + // TDD-Inside-Spec structures + // ===================================================================== + + // TestCase -- a single test case from .test section + // Format: ; test_name ; Verify: description ; Expected: expected outcome + pub const TestCase = struct { + node: ASTNode, + name: []const u8, // test identifier (snake_case) + verify_description: []const u8, // "Verify:" description + expected_outcome: []const u8, // "Expected:" description + setup_description: []const u8, // "Setup:" description (optional) + rationale: ?[]const u8, // Additional rationale (optional) + }; + + // InvariantDecl -- an invariant from .invariant section + // Format: ; invariant_name ; Rationale: explanation + pub const InvariantDecl = struct { + node: ASTNode, + name: []const u8, // invariant identifier (snake_case) + formal_statement: []const u8, // The invariant statement (may be logic) + rationale: []const u8, // "Rationale:" explanation + }; + + // BenchDecl -- a benchmark from .bench section + // Format: ; bench_name ; Measure: what to measure ; Target: target value + pub const BenchDecl = struct { + node: ASTNode, + name: []const u8, // benchmark identifier (snake_case) + measure_description: []const u8, // "Measure:" description + target: ?[]const u8, // "Target:" description (optional) + units: []const u8, // Units of measurement (e.g., "cycles", "ns", "ops/sec") + }; + + // ===================================================================== + // TDD-Inside-Spec high-level structures (spec-style) + // ===================================================================== + + // SpecDecl -- spec module header declaration + // Format: spec + pub const SpecDecl = struct { + node: ASTNode, + name: []const u8, // spec/module identifier + constants: []ConstDef, // spec-level constants + test_blocks: []TestBlock, // test blocks in spec + invariants: []AssertStmt, // invariants in spec + rules: []RuleDecl, // rules in spec + }; + + // TestBlock -- a complete test with given/when/then clauses + // Format: test { given ... when ... then ... } + pub const TestBlock = struct { + node: ASTNode, + name: []const u8, // test identifier (snake_case) + given_clauses: []GivenClause, // setup clauses + when_clauses: []WhenClause, // action clauses + then_clauses: []ThenClause, // assertion clauses + }; + + pub const GivenClause = struct { + node: ASTNode, + variable: []const u8, // variable name + expression: []const u8, // setup expression + }; + + pub const WhenClause = struct { + node: ASTNode, + variable: []const u8, // variable name + expression: []const u8, // action expression + }; + + pub const ThenClause = struct { + node: ASTNode, + expression: []const u8, // assertion expression + }; + + pub const AndClause = struct { + node: ASTNode, + expression: []const u8, // continuation expression + clause_type: []const u8, // "given", "when", or "then" + }; + + pub const AssertStmt = struct { + node: ASTNode, + expression: []const u8, // assertion expression + }; + + pub const RuleDecl = struct { + node: ASTNode, + name: []const u8, // rule identifier (snake_case) + expect_clauses: []ExpectClause, // expectation clauses + }; + + pub const ExpectClause = struct { + node: ASTNode, + expression: []const u8, // expectation expression + }; + + // ===================================================================== + // Type information for codegen + // ===================================================================== + + pub const TypeInfo = struct { + name: []const u8, + size_bits: u8, + is_signed: bool, + }; + + // ===================================================================== + // Symbol table + // ===================================================================== + + pub const Symbol = struct { + name: []const u8, + node: ASTNode, + scope: []const u8, + is_exported: bool, + is_defined: bool, + }; + + pub const SymbolTable = struct { + parent: ?*SymbolTable, + symbols: std.StringHashMap(Symbol), + children: []SymbolTable, + + pub fn new(parent: ?*SymbolTable) SymbolTable { + return SymbolTable{ + .parent = parent, + .symbols = std.StringHashMap(Symbol).init(std.heap.page_allocator), + .children = &[_]SymbolTable{}, + }; + } + + pub fn add(self: *SymbolTable, sym: Symbol) !bool { + try self.symbols.put(sym.name, sym); + return true; + } + + pub fn lookup(self: *SymbolTable, name: []const u8) ?Symbol { + if (self.symbols.get(name)) |s| { + return s; + } + if (self.parent) |p| { + return p.lookup(name); + } + return null; + } + }; + + // ===================================================================== + // Compiler context -- passed through all compilation stages + // ===================================================================== + + pub const CompilerContext = struct { + ast_root: Program, + symbol_table: SymbolTable, + errors: []CompilerError, + warnings: []CompilerWarning, + current_phase: CompilationPhase, + }; + + pub const CompilationPhase = enum(u8) { + Parsing = 0, + SemanticAnalysis = 1, + CodeGeneration = 2, + Optimization = 3, + }; + + pub const CompilerError = struct { + message: []const u8, + line: u32, + column: u32, + source_file: []const u8, + phase: CompilationPhase, + }; + + pub const CompilerWarning = struct { + message: []const u8, + line: u32, + column: u32, + source_file: []const u8, + }; + + pub const SymbolExport = struct { + name: []const u8, + type_info: TypeInfo, + }; + + pub fn CompilerContext_add_error(self: *CompilerContext, msg: []const u8, line: u32, column: u32) !void { + try self.errors.append(CompilerError{ + .message = msg, + .line = line, + .column = column, + .source_file = self.ast_root.node.source_file, + .phase = self.current_phase, + }); + } + + pub fn CompilerContext_add_warning(self: *CompilerContext, msg: []const u8, line: u32, column: u32) !void { + try self.warnings.append(CompilerWarning{ + .message = msg, + .line = line, + .column = column, + .source_file = self.ast_root.node.source_file, + }); + } + + pub fn CompilerContext_has_errors(self: *CompilerContext) bool { + return self.errors.len > 0; + } + + pub fn CompilerContext_error_count(self: *CompilerContext) u32 { + return @intCast(self.errors.len); + } + + pub fn Program_export_symbols(self: *Program) ![]SymbolExport { + // Traverse symbol table and collect exported symbols + // Returns list of {name, type, size} for external linkage + _ = self; + return &[_]SymbolExport{}; + } +} + +// ======================================================================================================= +// TDD-Inside-Spec: Tests and Invariants for AST +// ======================================================================================================= + +test token_type_eof_value + try std.testing.expect(@intFromEnum(ast.TokenType.EOF) == 0); + +test token_type_newline_value + try std.testing.expect(@intFromEnum(ast.TokenType.Newline) == 1); + +test node_type_program_value + try std.testing.expect(@intFromEnum(ast.NodeType.Program) == 0); + +test node_type_test_case_value + try std.testing.expect(@intFromEnum(ast.NodeType.TestCase) == 53); + +test opcode_mov_value + try std.testing.expect(@intFromEnum(ast.Opcode.MOV) == 0); + +test opcode_halt_value + try std.testing.expect(@intFromEnum(ast.Opcode.HALT) == 9); + +test compilation_phase_parsing_value + try std.testing.expect(@intFromEnum(ast.CompilationPhase.Parsing) == 0); + +test compilation_phase_code_generation_value + try std.testing.expect(@intFromEnum(ast.CompilationPhase.CodeGeneration) == 2); + +test symbol_table_new_creates_empty_table + const table = ast.SymbolTable.new(null); + try std.testing.expect(table.parent == null); + try std.testing.expect(table.symbols.count() == 0); + +test symbol_table_add_increments_count + var table = ast.SymbolTable.new(null); + const sym = ast.Symbol{ + .name = "test", + .node = undefined, + .scope = "", + .is_exported = false, + .is_defined = false, + }; + _ = table.add(sym); + try std.testing.expect(table.symbols.count() == 1); + +invariant token_type_enum_has_values + // TokenType enum has values for all required tokens + assert @typeInfo(ast.TokenType).Enum.fields.len > 50; + +invariant node_type_enum_has_values + // NodeType enum has values for all AST nodes + assert @typeInfo(ast.NodeType).Enum.fields.len > 20; + +invariant opcode_enum_has_values + // Opcode enum has values for all instructions + assert @typeInfo(ast.Opcode).Enum.fields.len > 0; + +invariant compilation_phase_enums_are_sequential + // CompilationPhase values are sequential 0-3 + assert @intFromEnum(ast.CompilationPhase.Parsing) < @intFromEnum(ast.CompilationPhase.SemanticAnalysis); + assert @intFromEnum(ast.CompilationPhase.SemanticAnalysis) < @intFromEnum(ast.CompilationPhase.CodeGeneration); + assert @intFromEnum(ast.CompilationPhase.CodeGeneration) < @intFromEnum(ast.CompilationPhase.Optimization); + +invariant ast_node_has_location_info + // ASTNode always contains line, column, and source_file + const node = ast.ASTNode{ + .node_type = ast.NodeType.Program, + .line = 1, + .column = 1, + .source_file = "test.t27", + }; + assert node.line > 0; + assert node.source_file.len > 0; + +invariant symbol_table_lookup_falls_back_to_parent + // SymbolTable.lookup searches parent if not found locally + assert true; + +bench ast_node_creation_latency + target: < 100ns + _ = ast.ASTNode{ + .node_type = ast.NodeType.Program, + .line = 1, + .column = 1, + .source_file = "test.t27", + }; + +bench symbol_table_lookup_latency + target: < 500ns + var table = ast.SymbolTable.new(null); + _ = table.lookup("nonexistent"); diff --git a/apps/website/public/t27/files/compiler/cli/gen.t27 b/apps/website/public/t27/files/compiler/cli/gen.t27 new file mode 100644 index 0000000000..2b5da2eb8e --- /dev/null +++ b/apps/website/public/t27/files/compiler/cli/gen.t27 @@ -0,0 +1,511 @@ +// gen.t27 -- Code Generation with TDD Validation +// Commands for generating code from t27 specs with TDD enforcement + +module gen_commands { + using parser: @import("../parser/parser.t27"); + using codegen_zig: @import("../codegen/zig/codegen.t27"); + using testgen: @import("../codegen/testgen.t27"); + + // ===================================================================== + // Codegen options + // ===================================================================== + + pub const GenOptions = struct { + backend: []const u8, // Target backend (zig, c, verilog, etc.) + output_dir: []const u8, // Output directory + emit_tests: bool, // Generate test code + emit_conformance: bool, // Generate conformance JSON + optimize_level: u8, // Optimization level + no_prototype: bool, // Disable prototype mode (always true - no tests allowed) + }; + + pub const CodegenOptions = struct { + emit_comments: bool, + emit_debug: bool, + optimize_level: u8, + target_triple: []const u8, + include_runtime: bool, + }; + + pub const TestGenOptions = struct { + backend: []const u8, + emit_comments: bool, + emit_benchmarks: bool, + output_format: []const u8, + }; + + // ===================================================================== + // Command: tri gen -- Generate code from spec with TDD validation + // ===================================================================== + + pub fn gen(spec_path: []const u8, options: GenOptions) i32 { + // Check if spec exists + if (!file_exists(spec_path)) { + error_print("Spec not found: "); + error_print(spec_path); + return 1; + } + + // Read spec content + const content = read_file(spec_path); + if (content == null) { + error_print("Failed to read spec: "); + error_print(spec_path); + return 1; + } + + // Parse spec + const context = parse(content.?, spec_path); + + if (context.has_errors()) { + error_print("Parse errors in "); + error_print(spec_path); + error_print(":\n"); + for (context.errors) |err| { + error_print(" Line "); + print_int(err.line); + error_print(":"); + print_int(err.column); + error_print(": "); + error_print(err.message); + error_print("\n"); + } + return 1; + } + + // ===================================================================== + // TDD CONTRACT ENFORCEMENT (Per User Requirement: No prototype mode) + // ===================================================================== + var has_tests = false; + var test_count: usize = 0; + var invariant_count: usize = 0; + + // Count tests and invariants + if (context.ast_root.test_section) |section| { + test_count += section.test_cases.len; + } + + if (context.ast_root.invariant_section) |section| { + invariant_count += section.invariants.len; + } + + if (context.ast_root.spec_decl) |spec| { + test_count += spec.test_blocks.len; + invariant_count += spec.invariants.len; + } + + has_tests = (test_count > 0) or (invariant_count > 0); + + if (!has_tests) { + error_print("TDD contract violated: no tests in spec\n"); + error_print("\n"); + error_print("The t27 project follows Test-Driven Development where:\n"); + error_print(" 1. Every spec MUST have at least one 'test' or 'invariant' block\n"); + error_print(" 2. Tests are written BEFORE or WITH the implementation\n"); + error_print(" 3. Conformance JSON is generated FROM tests, not hand-written\n"); + error_print("\n"); + error_print("To fix this error:\n"); + error_print(" 1. Add a .test section with test cases to your spec\n"); + error_print(" 2. Or add a .invariant section with invariant declarations\n"); + error_print(" 3. Or use the high-level TDD syntax with 'test' and 'invariant' blocks\n"); + error_print("\n"); + error_print("Example:\n"); + error_print(" .test\n"); + error_print(" ; my_test\n"); + error_print(" ; Verify: functionality works\n"); + error_print(" ; Expected: correct result\n"); + error_print("\n"); + error_print(" .invariant\n"); + error_print(" ; my_invariant\n"); + error_print(" ; For all valid inputs: output is valid\n"); + error_print(" ; Rationale: ensures correctness\n"); + error_print("\n"); + error_print("NOTE: There is NO --allow-no-tests flag (prototype mode is disabled per policy)"); + return 1; + } + + print("TDD contract validated: "); + print_int(test_count); + error_print(" tests, "); + print_int(invariant_count); + error_print(" invariants\n"); + + // ===================================================================== + // Generate code + // ===================================================================== + + // Determine output paths + const output_dir = if (options.output_dir.len == 0) + "gen/" ++ options.backend + else + options.output_dir; + + // Create output directory if needed + if (!dir_exists(output_dir)) { + const mkdir_result = mkdir(output_dir); + if (mkdir_result != 0) { + error_print("Failed to create output directory: "); + error_print(output_dir); + return mkdir_result; + } + } + + // Generate implementation code + const impl_output = output_dir ++ "/" ++ basename_without_ext(spec_path) ++ "." ++ options.backend; + + var codegen_result: i32 = 0; + if (std.mem.eql(u8, options.backend, "zig")) { + const codegen_opts = CodegenOptions{ + .emit_comments = true, + .emit_debug = true, + .optimize_level = options.optimize_level, + .target_triple = "native", + .include_runtime = true, + }; + const zig_code = generate_zig(content.?, spec_path, codegen_opts); + codegen_result = write_file(impl_output, zig_code); + } else { + error_print("Unsupported backend: "); + error_print(options.backend); + return 1; + } + + if (codegen_result != 0) { + error_print("Failed to write implementation code"); + return codegen_result; + } + + print("Generated implementation: "); + print(impl_output); + error_print("\n"); + + // Generate test code if requested + if (options.emit_tests) { + const testgen_opts = TestGenOptions{ + .backend = options.backend, + .emit_comments = true, + .emit_benchmarks = true, + .output_format = "code", + }; + + const testgen = TestGen.new(context.ast_root, testgen_opts); + const test_code = testgen.generate(); + + const test_output = output_dir ++ "/" ++ basename_without_ext(spec_path) ++ "_test." ++ options.backend; + const test_result = write_file(test_output, test_code); + + if (test_result != 0) { + error_print("Failed to write test code"); + return test_result; + } + + print("Generated tests: "); + print(test_output); + error_print("\n"); + } + + // Generate conformance JSON if requested + var conf_output: []const u8 = ""; + if (options.emit_conformance) { + const testgen_opts = TestGenOptions{ + .backend = "json", + .emit_comments = true, + .emit_benchmarks = true, + .output_format = "json", + }; + + const testgen = TestGen.new(context.ast_root, testgen_opts); + const conformance_json = testgen.generate(); + + // Create conformance directory if needed + const conf_dir = "conformance"; + if (!dir_exists(conf_dir)) { + _ = mkdir(conf_dir); + } + + conf_output = conf_dir ++ "/" ++ basename_without_ext(spec_path) ++ ".json"; + const conf_result = write_file(conf_output, conformance_json); + + if (conf_result != 0) { + error_print("Failed to write conformance JSON"); + return conf_result; + } + + print("Generated conformance: "); + print(conf_output); + error_print("\n"); + } + + error_print("\n"); + print("Code generation complete!\n"); + print(" Implementation: "); + print(impl_output); + error_print("\n"); + if (options.emit_tests) { + print(" Tests: "); + print(output_dir); + print("/"); + print(basename_without_ext(spec_path)); + print("_test."); + print(options.backend); + error_print("\n"); + } + if (options.emit_conformance) { + print(" Conformance: "); + print(conf_output); + error_print("\n"); + } + + return 0; + } + + // ===================================================================== + // Command: tri gen all -- Generate code for all specs in project + // ===================================================================== + + pub fn gen_all(options: GenOptions) i32 { + const spec_files = glob("specs/**/*.t27"); + + if (spec_files.len == 0) { + print("No spec files found"); + return 0; + } + + print("Found "); + print_int(spec_files.len); + error_print(" spec file(s)\n"); + + var success_count: usize = 0; + var fail_count: usize = 0; + + for (spec_files) |spec_path| { + print("Generating: "); + print(spec_path); + error_print("\n"); + const result = gen(spec_path, options); + + if (result == 0) { + success_count += 1; + } else { + fail_count += 1; + error_print(" Failed: "); + error_print(spec_path); + error_print("\n"); + } + } + + error_print("\n"); + print("Generation complete:\n"); + print(" Success: "); + print_int(success_count); + error_print("\n"); + print(" Failed: "); + print_int(fail_count); + error_print("\n"); + + return if (fail_count > 0) 1 else 0; + } + + // ===================================================================== + // Helper functions + // ===================================================================== + + // Get basename without extension + pub fn basename_without_ext(path: []const u8) []const u8 { + var start = path.len - 1; + + // Find last path separator + while (start >= 0 and path[start] != '/' and path[start] != '\\') { + if (start == 0) break; + start -= 1; + } + + const filename_start = start + 1; + var end = path.len - 1; + + // Find extension + while (end >= filename_start and path[end] != '.') { + if (end == 0) break; + end -= 1; + } + + if (end == filename_start - 1) { + return path[filename_start..]; + } + + return path[filename_start..end]; + } + + // ===================================================================== + // Type definitions + // ===================================================================== + + pub const Program = struct { + constants: []Constant, + test_section: ?TestSection, + invariant_section: ?InvariantSection, + spec_decl: ?SpecDecl, + }; + + pub const Constant = struct { + name: []const u8, + value: []const u8, + }; + + pub const TestSection = struct { + test_cases: []TestCase, + }; + + pub const TestCase = struct { + name: []const u8, + verify: []const u8, + setup: []const u8, + expected: []const u8, + }; + + pub const InvariantSection = struct { + invariants: []Invariant, + }; + + pub const Invariant = struct { + name: []const u8, + description: []const u8, + rationale: []const u8, + }; + + pub const SpecDecl = struct { + test_blocks: []TestBlock, + invariants: []SpecInvariant, + }; + + pub const TestBlock = struct { + name: []const u8, + statements: []const u8, + }; + + pub const SpecInvariant = struct { + name: []const u8, + statement: []const u8, + }; + + pub const ParseError = struct { + line: usize, + column: usize, + message: []const u8, + }; + + pub const ParseContext = struct { + ast_root: Program, + errors: []ParseError, + + pub fn has_errors(self: ParseContext) bool { + return self.errors.len > 0; + } + }; + + pub const TestGen = struct { + ast_root: Program, + options: TestGenOptions, + + pub fn new(ast: Program, opts: TestGenOptions) TestGen { + return TestGen{ + .ast_root = ast, + .options = opts, + }; + } + + pub fn generate(self: TestGen) []const u8 { + // Generate test code based on backend + if (std.mem.eql(u8, self.options.backend, "json")) { + return self.generate_json(); + } else { + return self.generate_code(); + } + } + + fn generate_json(self: TestGen) []const u8 { + // Generate JSON conformance format + return "{\"version\": \"1.0\", \"tests\": []}"; + } + + fn generate_code(self: TestGen) []const u8 { + // Generate test code in target backend language + return "// Generated test code"; + } + }; +} + +// ======================================================================================================= +// TDD-Inside-Spec: Tests and Invariants for Gen Commands +// ======================================================================================================= + +test gen_returns_zero_on_success + // Mock successful generation scenario + _ = gen_commands.gen; + try std.testing.expect(true); + +test gen_returns_one_for_missing_spec + // Test with non-existent spec path + _ = gen_commands.gen; + try std.testing.expect(true); + +test gen_returns_one_for_tdd_violation + // Test spec without tests + _ = gen_commands.gen; + try std.testing.expect(true); + +test gen_all_returns_zero_if_all_succeed + // Mock successful generation for all specs + _ = gen_commands.gen_all; + try std.testing.expect(true); + +test gen_all_returns_one_if_any_fail + // Test with at least one failing spec + _ = gen_commands.gen_all; + try std.testing.expect(true); + +test basename_without_ext_handles_simple_path + const result = gen_commands.basename_without_ext("specs/test.t27"); + try std.testing.expectEqualStrings("test", result); + +test basename_without_ext_handles_path_with_dirs + const result = gen_commands.basename_without_ext("specs/nested/test.t27"); + try std.testing.expectEqualStrings("test", result); + +test basename_without_ext_handles_windows_path + const result = gen_commands.basename_without_ext("specs\\nested\\test.t27"); + try std.testing.expectEqualStrings("test", result); + +invariant gen_enforces_tdd_contract + // gen() returns 1 if spec has no tests + assert true; + +invariant gen_counts_tests_and_invariants + // Both test_section and spec_decl tests are counted + assert true; + +invariant gen_all_counts_success_and_failure + // gen_all tracks success/failure counts + assert true; + +invariant basename_without_ext_returns_filename_only + // Always returns filename without extension or path + assert true; + +invariant testgen_generates_based_on_backend + // TestGen.generate() produces different output for json vs code + assert true; + +bench gen_latency + target: < 100ms + // Mock gen call with valid spec + _ = gen_commands.gen; + +bench gen_all_latency + target: < 1s + // Mock gen_all with multiple specs + _ = gen_commands.gen_all; + +bench basename_without_ext_latency + target: < 1us + _ = gen_commands.basename_without_ext("specs/very/long/path/to/test_spec.t27"); diff --git a/apps/website/public/t27/files/compiler/cli/git.t27 b/apps/website/public/t27/files/compiler/cli/git.t27 new file mode 100644 index 0000000000..e919685b1b --- /dev/null +++ b/apps/website/public/t27/files/compiler/cli/git.t27 @@ -0,0 +1,616 @@ +// git.t27 -- Git Integration with Tri Skill Workflow (ADR-002) +// Commands for git operations with skill validation and issue binding + +module git_commands { + using skill_registry: @import("../skill/registry.t27"); + + // ========================================================= + // Command: tri git commit [--all] [-m "msg"] [--mode strict|normal|local] + // ===================================================================== + + pub fn git_commit(all: bool, message: []const u8, mode: []const u8) i32 { + const registry_path = ".trinity/skills/registry.json"; + + // Check if registry exists + if (!file_exists(registry_path)) { + error_print("ERROR: no active or sealed skill; NO-COMMIT-WITHOUT-ISSUE violated"); + error_print("Run 'tri skill begin --issue N' first to create a skill"); + return 1; + } + + // Read registry + const registry_content = read_file(registry_path); + if (registry_content == null) { + error_print("ERROR: failed to read skill registry"); + return 1; + } + + // Parse registry and find active or last sealed skill + const skill = find_active_or_sealed_skill(registry_content.?); + + if (skill == null) { + error_print("ERROR: no active or sealed skill; NO-COMMIT-WITHOUT-ISSUE violated"); + error_print("Run 'tri skill begin --issue N' first to create a skill"); + return 1; + } + + // Check issue-binding + const issue_id = if (skill.?.issue) |id| id else ""; + if (issue_id.len == 0) { + error_print("ERROR: skill has no bound issue"); + error_print("Run 'tri skill begin --issue N' first to bind an issue"); + return 1; + } + + // Check verdict for sealed skills + if (std.mem.eql(u8, skill.?.status, "sealed")) { + if (skill.?.verdict) |verdict| { + if (std.mem.eql(u8, verdict, "TOXIC")) { + error_print("ERROR: cannot commit toxic skill; fix or supersede"); + error_print("Run 'tri experience save' to address toxic verdict"); + return 1; + } + } + } + + // Build commit message + var commit_msg = message; + const skill_id = skill.?.id orelse ""; + + if (commit_msg.len == 0) { + // Generate auto-summary from git status + const status = git_status(); + commit_msg = "skill:" ++ skill_id ++ " issue:" ++ issue_id ++ " " ++ summarize_status(status); + } else { + // Check if message has issue: prefix + const issue_prefix = "issue:" ++ issue_id; + if (!std.mem.indexOf(u8, commit_msg, issue_prefix) != null) { + commit_msg = commit_msg ++ " issue:" ++ issue_id; + } + } + + // In strict mode, add metadata + if (std.mem.eql(u8, mode, "strict") and std.mem.eql(u8, skill.?.status, "sealed")) { + if (skill.?.seal_hash) |seal_hash| { + if (seal_hash.len > 0) { + commit_msg = commit_msg ++ " seal:" ++ seal_hash; + } + } + } + + // Stage files + if (all) { + const add_result = git_add_all(); + if (add_result != 0) { + return add_result; + } + } + + // Create commit + const commit_result = git_commit_message(commit_msg); + if (commit_result != 0) { + // Check if it was a git error vs policy error + if (commit_result == 2) { + error_print("Git error: commit failed"); + } + return commit_result; + } + + // Get commit hash + const commit_hash = git_get_head_hash(); + + // Update registry with commit hash + const updated_registry = update_registry_commit(registry_content.?, skill_id, commit_hash); + write_file(registry_path, updated_registry); + + print("Committed: "); + print(commit_hash[0..7]); + error_print("\n"); + print(" Cell: "); + print(skill_id); + error_print("\n"); + print(" Issue: "); + print(issue_id); + error_print("\n"); + + if (std.mem.eql(u8, mode, "strict") and std.mem.eql(u8, skill.?.status, "sealed")) { + print(" Seal: "); + print(skill.?.seal_hash orelse ""); + error_print("\n"); + print(""); + print("NOTE: In strict mode, you must run 'tri git push' after committing"); + error_print("\n"); + } + + return 0; + } + + // ========================================================= + // Command: tri git push [ ] [--mode strict|normal|local] + // ===================================================================== + + pub fn git_push(remote: []const u8, branch: []const u8, mode: []const u8) i32 { + const registry_path = ".trinity/skills/registry.json"; + + // Check if registry exists + if (!file_exists(registry_path)) { + error_print("ERROR: cannot push; skill not sealed (tri skill seal required)"); + return 1; + } + + // Read registry + const registry_content = read_file(registry_path); + if (registry_content == null) { + error_print("ERROR: failed to read skill registry"); + return 1; + } + + // Find last sealed skill + const skill = find_last_sealed_skill(registry_content.?); + + if (skill == null) { + error_print("ERROR: cannot push; skill not sealed (tri skill seal required)"); + error_print("Run 'tri skill seal' to seal the current skill"); + return 1; + } + + // Check verdict + if (skill.?.verdict) |verdict| { + if (std.mem.eql(u8, verdict, "TOXIC")) { + error_print("ERROR: cannot push toxic skill"); + return 1; + } + } + + // Check artifacts by Policy Matrix + const kind = skill.?.kind orelse ""; + const artifacts = skill.?.artifacts orelse ""; + + if (std.mem.eql(u8, kind, "recovery")) { + // Require minimum 3 checkpoints + spec and docs + const checkpoints = count_checkpoints(artifacts); + const has_spec = std.mem.indexOf(u8, artifacts, "spec") != null; + const has_docs = std.mem.indexOf(u8, artifacts, "docs") != null; + + if (checkpoints < 3 or !has_spec or !has_docs) { + error_print("ERROR: policy gate failed for skill kind "); + error_print(kind); + error_print("Recovery skills require:"); + error_print(" - At least 3 checkpoints"); + error_print(" - Spec changes"); + error_print(" - Docs changes"); + return 1; + } + } else if (std.mem.eql(u8, kind, "hotfix")) { + // Require >=1 checkpoint and only fix-only areas + const checkpoints = count_checkpoints(artifacts); + if (checkpoints < 1) { + error_print("ERROR: policy gate failed for skill kind "); + error_print(kind); + error_print("Hotfix skills require at least 1 checkpoint"); + return 1; + } + } + + // Check remote in strict mode + if (std.mem.eql(u8, mode, "strict")) { + const default_remote = if (remote.len == 0) "origin" else remote; + const remote_url = git_get_remote_url(default_remote); + + if (std.mem.indexOf(u8, remote_url, "github.com/gHashTag/t27") == null) { + error_print("ERROR: forbidden remote; expected github.com/gHashTag/t27"); + error_print("Current remote: "); + error_print(remote_url); + return 1; + } + } + + // Determine target + const push_remote = if (remote.len == 0) "origin" else remote; + const push_branch = if (branch.len == 0) git_get_current_branch() else branch; + + // Execute push + const push_result = git_push_remote(push_remote, push_branch); + + if (push_result != 0) { + if (push_result == 1) { + error_print("Push failed: gate violations"); + } else if (push_result == 2) { + error_print("Push failed: git error (reject, network, etc.)"); + } + return push_result; + } + + // Update registry with push status + const skill_id = skill.?.id orelse ""; + const timestamp = get_current_timestamp(); + const updated_registry = update_registry_push(registry_content.?, skill_id, true, timestamp); + write_file(registry_path, updated_registry); + + print("Pushed to "); + print(push_remote); + print("/"); + print(push_branch); + error_print("\n"); + print(" Cell: "); + print(skill_id); + error_print("\n"); + print(" Pushed at: "); + print(timestamp); + error_print("\n"); + + return 0; + } + + // ========================================================= + // Command: tri git status -- Show git status with skill info + // ===================================================================== + + pub fn git_status_with_skill() i32 { + const registry_path = ".trinity/skills/registry.json"; + + // Show git status + const status = git_status(); + print("Git status:\n"); + print(status); + error_print("\n"); + + // Show skill info if available + if (file_exists(registry_path)) { + const registry_content = read_file(registry_path); + const skill = find_active_or_sealed_skill(registry_content.?); + + if (skill) |s| { + print("Current skill:\n"); + print(" ID: "); + print(s.id); + error_print("\n"); + print(" Status: "); + print(s.status); + error_print("\n"); + if (s.issue) |issue| { + print(" Issue: "); + print(issue); + error_print("\n"); + } + if (s.verdict) |verdict| { + print(" Verdict: "); + print(verdict); + error_print("\n"); + } + } else { + print("No active or sealed skill found"); + error_print("\n"); + } + } else { + print("No skill registry found"); + error_print("\n"); + } + + return 0; + } + + // ========================================================= + // Helper functions + // ===================================================================== + + // Find active or last sealed skill from registry + fn find_active_or_sealed_skill(registry_json: []const u8) ?Skill { + const registry = parse_json(registry_json); + + // First look for active skill + for (registry.skills) |skill| { + if (std.mem.eql(u8, skill.status, "active")) { + return skill; + } + } + + // Then look for last sealed skill on current branch + const current_branch = git_get_current_branch(); + var last_sealed: ?Skill = null; + var max_timestamp: []const u8 = ""; + + for (registry.skills) |skill| { + if (std.mem.eql(u8, skill.status, "sealed") and std.mem.eql(u8, skill.branch, current_branch)) { + if (last_sealed == null or std.mem.eql(u8, skill.updated_at, max_timestamp) > 0) { + if (max_timestamp.len == 0 or std.mem.eql(u8, skill.updated_at, max_timestamp)) { + max_timestamp = skill.updated_at; + last_sealed = skill; + } + } + } + } + + return last_sealed; + } + + // Find last sealed skill from registry + fn find_last_sealed_skill(registry_json: []const u8) ?Skill { + const registry = parse_json(registry_json); + var last_sealed: ?Skill = null; + var max_timestamp: []const u8 = ""; + + for (registry.skills) |skill| { + if (std.mem.eql(u8, skill.status, "sealed")) { + if (last_sealed == null or std.mem.eql(u8, skill.updated_at, max_timestamp) > 0) { + if (max_timestamp.len == 0 or std.mem.eql(u8, skill.updated_at, max_timestamp)) { + max_timestamp = skill.updated_at; + last_sealed = skill; + } + } + } + } + + return last_sealed; + } + + // Update registry with commit hash + fn update_registry_commit(registry_json: []const u8, skill_id: []const u8, commit_hash: []const u8) []const u8 { + var registry = parse_json(registry_json); + + for (registry.skills) |*skill| { + if (std.mem.eql(u8, skill.id, skill_id)) { + skill.commit = commit_hash; + skill.commit_at = get_current_timestamp(); + break; + } + } + + return serialize_json(registry); + } + + // Update registry with push status + fn update_registry_push(registry_json: []const u8, skill_id: []const u8, pushed: bool, timestamp: []const u8) []const u8 { + var registry = parse_json(registry_json); + + for (registry.skills) |*skill| { + if (std.mem.eql(u8, skill.id, skill_id)) { + skill.pushed = pushed; + skill.pushed_at = timestamp; + break; + } + } + + return serialize_json(registry); + } + + // Summarize git status + fn summarize_status(status: []const u8) []const u8 { + // Extract modified/added/deleted files count + const modified = countSubstring(status, "modified:"); + const added = countSubstring(status, "new file:"); + const deleted = countSubstring(status, "deleted:"); + + var result: []const u8 = ""; + + if (modified > 0) { + result = result ++ intToStr(modified) ++ " modified"; + } + if (added > 0) { + if (result.len > 0) result = result ++ ", "; + result = result ++ intToStr(added) ++ " added"; + } + if (deleted > 0) { + if (result.len > 0) result = result ++ ", "; + result = result ++ intToStr(deleted) ++ " deleted"; + } + + if (result.len == 0) { + return "no changes"; + } + + return result; + } + + // Count checkpoints in artifacts + fn count_checkpoints(artifacts: []const u8) i32 { + return countSubstring(artifacts, "checkpoint"); + } + + // Get current timestamp (ISO 8601 format) + fn get_current_timestamp() []const u8 { + // Returns ISO 8601 timestamp + return "2026-04-04T00:00:00Z"; // Placeholder + } + + // Count substring occurrences + fn countSubstring(haystack: []const u8, needle: []const u8) i32 { + var count: i32 = 0; + var i: usize = 0; + + while (i < haystack.len) : (i += 1) { + if (i + needle.len <= haystack.len) and std.mem.eql(u8, haystack[i..][0..needle.len], needle)) { + count += 1; + } + } + + return count; + } + + // Integer to string + fn intToStr(n: i32) []const u8 { + // Simple integer to string conversion + if (n == 0) return "0"; + + var buf: [20]u8 = undefined; + var i: usize = 0; + var num = @abs(n); + + while (num > 0) : (num /= 10) { + const digit = @as(u8, @intCast(num % 10)) + '0'; + buf[i] = digit; + i += 1; + } + + // Reverse and return + const len = i; + var result: []const u8 = ""; + while (i > 0) : (i -= 1) { + result = result ++ buf[i - 1]; + } + + return result; + } + + // ========================================================= + // Type definitions + // ===================================================================== + + pub const Skill = struct { + id: []const u8, + status: []const u8, + kind: []const u8, + issue: []const u8, + branch: []const u8, + created_at: []const u8, + updated_at: []const u8, + sealed_at: ?[]const u8, + commit: []const u8, + commit_at: ?[]const u8, + pushed: bool, + pushed_at: ?[]const u8, + verdict: ?[]const u8, + verdict_at: ?[]const u8, + seal_hash: ?[]const u8, + artifacts: []const u8, + metadata: skill_registry.SkillMetadata, + }; + + pub const SkillRegistry = struct { + version: []const u8, + skills: []Skill, + }; +} + +// =========================================================================================== +// TDD-Inside-Spec: Tests and Invariants for Git Integration +// =========================================================================================== + +test git_commit_requires_registry + // Mock: registry doesn't exist + _ = git_commands.git_commit; + try std.testing.expect(true); + +test git_commit_requires_issue_binding + // Mock: skill has no issue + _ = git_commands.git_commit; + try std.testing.expect(true); + +test git_commit_rejects_toxic_verdict + // Mock: sealed skill with TOXIC verdict + _ = git_commands.git_commit; + try std.testing.expect(true); + +test git_commit_accepts_not_toxic_verdict + // Mock: sealed skill with NOT TOXIC verdict + _ = git_commands.git_commit; + try std.testing.expect(true); + +test git_push_requires_sealed_skill + // Mock: active skill + _ = git_commands.git_push; + try std.testing.expect(true); + +test git_push_rejects_toxic_skill + // Mock: sealed skill with TOXIC verdict + _ = git_commands.git_push; + try std.testing.expect(true); + +test git_push_recovery_requires_3_checkpoints + // Mock: recovery skill with 2 checkpoints + _ = git_commands.git_push; + try std.testing.expect(true); + +test git_push_hotfix_requires_1_checkpoint + // Mock: hotfix skill with 0 checkpoints + _ = git_commands.git_push; + try std.testing.expect(true); + +test git_push_strict_validates_remote + // Mock: strict mode with wrong remote + _ = git_commands.git_push; + try std.testing.expect(true); + +test git_commit_auto_adds_issue_to_message + // Mock: skill issue 123, message "fix bug" + _ = git_commands.git_commit; + try std.testing.expect(true); + +test summarize_status_no_changes + const result = git_commands.summarize_status("On branch main\nnothing to commit"); + try std.testing.expectEqualStrings("no changes", result); + +test summarize_status_with_changes + const result = git_commands.summarize_status("modified: file1.t27\nnew file: file2.t27"); + try std.testing.expect(std.mem.indexOf(u8, result, "modified") != null); + try std.testing.expect(std.mem.indexOf(u8, result, "added") != null); + +test count_checkpoints_returns_count + const result = git_commands.count_checkpoints("checkpoint1, checkpoint2, spec, docs"); + try std.testing.expect(result == 2); + +test count_checkpoints_zero + const result = git_commands.count_checkpoints("spec, docs"); + try std.testing.expect(result == 0); + +test get_current_timestamp_format + const result = git_commands.get_current_timestamp(); + try std.testing.expect(std.mem.indexOf(u8, result, "T") != null); + try std.testing.expect(std.mem.indexOf(u8, result, "Z") != null); + +test git_status_with_skill_shows_skill_info + // Mock: registry exists with active skill + _ = git_commands.git_status_with_skill; + try std.testing.expect(true); + +invariant git_commit_returns_zero_on_success + // Successful commit returns 0 + assert true; + +invariant git_commit_returns_nonzero_on_failure + // Failed commit returns non-zero + assert true; + +invariant git_push_returns_zero_on_success + // Successful push returns 0 + assert true; + +invariant git_push_returns_nonzero_on_failure + // Failed push returns non-zero + assert true; + +invariant git_status_with_skill_always_succeeds + // git status command always succeeds + assert true; + +invariant summarize_status_never_null + // Summary always returns a string + assert true; + +invariant count_checkpoints_never_negative + // Checkpoint count is always >= 0 + assert true; + +invariant recovery_skill_requires_min_3_checkpoints + // Recovery skill policy: minimum 3 checkpoints + assert true; + +invariant hotfix_skill_requires_min_1_checkpoint + // Hotfix skill policy: minimum 1 checkpoint + assert true; + +invariant strict_mode_validates_remote + // Strict mode requires expected remote URL + assert true; + +bench git_commit_latency + target: < 500ms + _ = git_commands.git_commit(false, "test", "normal"); + +bench git_push_latency + target: < 1000ms + _ = git_commands.git_push("", "", "normal"); + +bench git_status_with_skill_latency + target: < 100ms + _ = git_commands.git_status_with_skill(); diff --git a/apps/website/public/t27/files/compiler/cli/spec.t27 b/apps/website/public/t27/files/compiler/cli/spec.t27 new file mode 100644 index 0000000000..b72a481df5 --- /dev/null +++ b/apps/website/public/t27/files/compiler/cli/spec.t27 @@ -0,0 +1,487 @@ +// spec.t27 -- Spec Management Commands +// Commands for creating and managing t27 specs with TDD enforcement + +module spec_commands { + // ===================================================================== + // Command: tri spec create -- Create a new spec with TDD template + // ===================================================================== + + pub fn spec_create(name: []const u8, path: []const u8) i32 { + // Validate spec name + if (!is_valid_spec_name(name)) { + error_print("Invalid spec name: "); + error_print(name); + error_print("\nSpec names must be lowercase alphanumeric with underscores"); + return 1; + } + + // Determine output path + const output_path = if (path.len == 0) "specs/" ++ name ++ ".t27" else path; + + // Check if file exists + if (file_exists(output_path)) { + error_print("Spec already exists: "); + error_print(output_path); + error_print("\nUse --force to overwrite"); + return 1; + } + + // Create spec with TDD template + const content = generate_spec_template(name); + const result = write_file(output_path, content); + + if (result != 0) { + error_print("Failed to create spec: "); + error_print(output_path); + return result; + } + + print("Created spec: "); + print(output_path); + print("\n"); + print("Next steps:\n"); + print(" 1. Add constants, functions to your spec\n"); + print(" 2. Add test cases using 'test ' blocks\n"); + print(" 3. Add invariants using 'invariant ' blocks\n"); + print(" 4. Run 'tri gen "); + print(output_path); + print("' to generate code\n"); + print(" 5. Run 'tri test' to execute tests"); + + return 0; + } + + // ===================================================================== + // Command: tri spec validate -- Validate spec has TDD compliance + // ===================================================================== + + pub fn spec_validate(path: []const u8) i32 { + if (!file_exists(path)) { + error_print("Spec not found: "); + error_print(path); + return 1; + } + + // Read spec content + const content = read_file(path); + if (content == null) { + error_print("Failed to read spec: "); + error_print(path); + return 1; + } + + // Parse spec + const context = parse(content.?, path); + + if (context.has_errors()) { + error_print("Parse errors in "); + error_print(path); + error_print(":\n"); + for (context.errors) |err| { + error_print(" Line "); + print_int(err.line); + error_print(":"); + print_int(err.column); + error_print(": "); + error_print(err.message); + error_print("\n"); + } + return 1; + } + + // Validate TDD compliance + const tdd_errors = validate_tdd_compliance(context.ast_root); + + if (tdd_errors.len > 0) { + error_print("TDD compliance violations in "); + error_print(path); + error_print(":\n"); + for (tdd_errors) |err| { + error_print(" "); + error_print(err); + error_print("\n"); + } + return 1; + } + + print("Spec "); + print(path); + print(" is TDD compliant\n"); + print("\nSummary:\n"); + print(" Constants: "); + print_int(context.ast_root.constants.len); + error_print("\n"); + + if (context.ast_root.spec_decl != null) { + print(" Tests (spec-style): "); + print_int(context.ast_root.spec_decl.?.test_blocks.len); + error_print("\n"); + print(" Invariants (spec-style): "); + print_int(context.ast_root.spec_decl.?.invariants.len); + error_print("\n"); + } + + if (context.ast_root.test_section != null) { + print(" Tests (assembly-style): "); + print_int(context.ast_root.test_section.?.test_cases.len); + error_print("\n"); + } + + if (context.ast_root.invariant_section != null) { + print(" Invariants (assembly-style): "); + print_int(context.ast_root.invariant_section.?.invariants.len); + error_print("\n"); + } + + return 0; + } + + // ===================================================================== + // Command: tri spec list -- List all specs in project + // ===================================================================== + + pub fn spec_list() i32 { + const spec_dir = "specs"; + if (!dir_exists(spec_dir)) { + print("No specs directory found"); + return 0; + } + + const spec_files = glob("specs/**/*.t27"); + + if (spec_files.len == 0) { + print("No spec files found"); + return 0; + } + + print("Spec files in project:\n"); + + for (spec_files) |spec_path| { + const rel_path = spec_path[7..]; // Remove "specs/" prefix + const content = read_file(spec_path); + + // Check if spec has tests + var has_tests = false; + if (content) |c| { + const context = parse(c, spec_path); + if (context.ast_root.test_section != null or + (context.ast_root.spec_decl != null and context.ast_root.spec_decl.?.test_blocks.len > 0)) { + has_tests = true; + } + } + + const status = if (has_tests) "ok" else "x"; + print(" "); + print(status); + print(" "); + print(rel_path); + error_print("\n"); + } + + return 0; + } + + // ===================================================================== + // Helper functions + // ===================================================================== + + // Validate spec name: lowercase alphanumeric with underscores + pub fn is_valid_spec_name(name: []const u8) bool { + if (name.len == 0) { + return false; + } + + // Must start with lowercase letter + const first = name[0]; + if (first < 'a' or first > 'z') { + return false; + } + + // Only alphanumeric and underscores allowed + for (name[1..]) |c| { + if ((c < 'a' or c > 'z') and (c < '0' or c > '9') and c != '_') { + return false; + } + } + + return true; + } + + // Generate spec template with TDD blocks (assembly-like format) + pub fn generate_spec_template(name: []const u8) []const u8 { + return "; " ++ name ++ ".t27 -- Specification for " ++ name ++ "\n" ++ + "; phi^2 + 1/phi^2 = 3 | TRINITY\n" ++ + ";\n" ++ + "; This file is the source of truth for " ++ name ++ ".\n" ++ + "; Generated code is a derived artifact - DO NOT EDIT generated files.\n" ++ + ";\n" ++ + "; TDD Contract: This spec MUST include at least one test or invariant.\n" ++ + ";\n" ++ + "\n" ++ + ".use base::types\n" ++ + "\n" ++ + "; ===============================================================\n" ++ + "; Constants\n" ++ + "; ===============================================================\n" ++ + "\n" ++ + ".const EXAMPLE_CONSTANT 42\n" ++ + "\n" ++ + "; ===============================================================\n" ++ + "; Data Section\n" ++ + "; ===============================================================\n" ++ + "\n" ++ + ".data\n" ++ + " .const DATA_INIT 0\n" ++ + "\n" ++ + "; ===============================================================\n" ++ + "; Code Section\n" ++ + "; ===============================================================\n" ++ + "\n" ++ + ".code\n" ++ + "main:\n" ++ + " ; Your code here\n" ++ + " HALT\n" ++ + "\n" ++ + "; ===============================================================\n" ++ + "; TDD-Inside-Spec: Tests and Invariants\n" ++ + "; ===============================================================\n" ++ + "\n" ++ + ".test\n" ++ + " ; example_test\n" ++ + " ; Verify: example functionality works correctly\n" ++ + " ; Setup: initialize with EXAMPLE_CONSTANT\n" ++ + " ; Expected: returns expected value\n" ++ + "\n" ++ + ".invariant\n" ++ + " ; example_invariant\n" ++ + " ; For all valid inputs: output is in valid range\n" ++ + " ; Rationale: Ensures function correctness\n" ++ + "\n" ++ + ".bench\n" ++ + " ; example_benchmark\n" ++ + " ; Measure: operations per second\n" ++ + " ; Target: > 1M ops/sec\n"; + } + + // Validate TDD compliance: spec must have at least one test or invariant + pub fn validate_tdd_compliance(ast_root: Program) [][]const u8 { + var errors = std.ArrayList([]const u8).init(std.heap.page_allocator); + defer errors.deinit(); + + // Check if spec has any tests or invariants + var has_tests = false; + + if (ast_root.test_section) |section| { + if (section.test_cases.len > 0) { + has_tests = true; + } + } + + if (ast_root.invariant_section) |section| { + if (section.invariants.len > 0) { + has_tests = true; + } + } + + if (ast_root.spec_decl) |spec| { + if (spec.test_blocks.len > 0 or spec.invariants.len > 0) { + has_tests = true; + } + } + + if (!has_tests) { + errors.append("TDD contract violated: spec must contain at least one 'test' or 'invariant' block") catch {}; + } + + return errors.toOwnedSlice() catch &[0][]const u8{}; + } + + // ===================================================================== + // Type definitions + // ===================================================================== + + pub const Program = struct { + constants: []Constant, + test_section: ?TestSection, + invariant_section: ?InvariantSection, + spec_decl: ?SpecDecl, + }; + + pub const Constant = struct { + name: []const u8, + value: []const u8, + }; + + pub const TestSection = struct { + test_cases: []TestCase, + }; + + pub const TestCase = struct { + name: []const u8, + verify: []const u8, + setup: []const u8, + expected: []const u8, + }; + + pub const InvariantSection = struct { + invariants: []Invariant, + }; + + pub const Invariant = struct { + name: []const u8, + description: []const u8, + rationale: []const u8, + }; + + pub const SpecDecl = struct { + test_blocks: []TestBlock, + invariants: []SpecInvariant, + }; + + pub const TestBlock = struct { + name: []const u8, + statements: []const u8, + }; + + pub const SpecInvariant = struct { + name: []const u8, + statement: []const u8, + }; + + pub const ParseError = struct { + line: usize, + column: usize, + message: []const u8, + }; + + pub const ParseContext = struct { + ast_root: Program, + errors: []ParseError, + + pub fn has_errors(self: ParseContext) bool { + return self.errors.len > 0; + } + }; +} + +// ======================================================================================================= +// TDD-Inside-Spec: Tests and Invariants for Spec Commands +// ======================================================================================================= + +test spec_create_returns_zero_on_success + const result = spec_commands.spec_create("test_spec", ""); + // In real test, would mock file operations + _ = result; + try std.testing.expect(true); + +test spec_create_rejects_invalid_name + const result = spec_commands.spec_create("Invalid-Name", ""); + _ = result; + try std.testing.expect(true); + +test spec_create_rejects_empty_name + const result = spec_commands.spec_create("", ""); + _ = result; + try std.testing.expect(true); + +test spec_validate_returns_zero_for_compliant_spec + const result = spec_commands.spec_validate("specs/valid.t27"); + _ = result; + try std.testing.expect(true); + +test spec_validate_returns_one_for_nonexistent_spec + const result = spec_commands.spec_validate("specs/nonexistent.t27"); + _ = result; + try std.testing.expect(true); + +test spec_validate_returns_one_for_tdd_violation + const result = spec_commands.spec_validate("specs/no_tests.t27"); + _ = result; + try std.testing.expect(true); + +test spec_list_returns_zero_always + const result = spec_commands.spec_list(); + try std.testing.expect(result == 0); + +test is_valid_spec_name_accepts_valid_names + try std.testing.expect(spec_commands.is_valid_spec_name("valid_name")); + try std.testing.expect(spec_commands.is_valid_spec_name("another123")); + try std.testing.expect(spec_commands.is_valid_spec_name("a")); + +test is_valid_spec_name_rejects_invalid_names + try std.testing.expect(!spec_commands.is_valid_spec_name("")); + try std.testing.expect(!spec_commands.is_valid_spec_name("Invalid-Name")); + try std.testing.expect(!spec_commands.is_valid_spec_name("123start")); + try std.testing.expect(!spec_commands.is_valid_spec_name("has.dots")); + +test generate_spec_template_contains_tdd_blocks + const template = spec_commands.generate_spec_template("test"); + try std.testing.expect(std.mem.indexOf(u8, template, ".test") != null); + try std.testing.expect(std.mem.indexOf(u8, template, ".invariant") != null); + try std.testing.expect(std.mem.indexOf(u8, template, ".bench") != null); + +test generate_spec_template_contains_use_statement + const template = spec_commands.generate_spec_template("test"); + try std.testing.expect(std.mem.indexOf(u8, template, ".use base::types") != null); + +test validate_tdd_compliance_returns_empty_for_compliant + // Mock Program with test_section + const program = spec_commands.Program{ + .constants = &[_]spec_commands.Constant{}, + .test_section = spec_commands.TestSection{ + .test_cases = &[_]spec_commands.TestCase{ + .{ .name = "test", .verify = "", .setup = "", .expected = "" }, + }, + }, + .invariant_section = null, + .spec_decl = null, + }; + const errors = spec_commands.validate_tdd_compliance(program); + try std.testing.expect(errors.len == 0); + +test validate_tdd_compliance_returns_error_for_no_tests + const program = spec_commands.Program{ + .constants = &[_]spec_commands.Constant{}, + .test_section = null, + .invariant_section = null, + .spec_decl = null, + }; + const errors = spec_commands.validate_tdd_compliance(program); + try std.testing.expect(errors.len == 1); + +invariant spec_create_always_returns_zero_or_one + // spec_create returns 0 on success, 1 on failure + assert true; + +invariant is_valid_spec_name_rejects_empty + // Empty name is never valid + assert !spec_commands.is_valid_spec_name(""); + +invariant is_valid_spec_name_requires_lowercase_start + // First character must be a-z + assert !spec_commands.is_valid_spec_name("StartsUpper"); + +invariant generate_spec_template_always_includes_tdd_contract + // Template always includes TDD contract comment + const template = spec_commands.generate_spec_template("test"); + assert std.mem.indexOf(u8, template, "TDD Contract") != null; + +invariant validate_tdd_compliance_checks_all_sections + // Checks test_section, invariant_section, and spec_decl + assert true; + +invariant parse_context_has_errors_returns_bool + // has_errors() returns true when errors exist + assert true; + +bench spec_create_latency + target: < 10ms + _ = spec_commands.spec_create("test", ""); + +bench spec_validate_latency + target: < 5ms + _ = spec_commands.spec_validate("specs/valid.t27"); + +bench spec_list_latency + target: < 10ms + _ = spec_commands.spec_list(); diff --git a/apps/website/public/t27/files/compiler/codegen/c/codegen.t27 b/apps/website/public/t27/files/compiler/codegen/c/codegen.t27 new file mode 100644 index 0000000000..d72ebd4a2d --- /dev/null +++ b/apps/website/public/t27/files/compiler/codegen/c/codegen.t27 @@ -0,0 +1,273 @@ +; compiler/codegen/c/codegen.t27 -- C Code Generator Specification +; Emit C code from t27 AST +; phi^2 + 1/phi^2 = 3 | TRINITY + +module tricgen-c; + +using compiler::parser; + +// Configuration +pub const INDENT_SIZE: u32 = 4; +pub const MAX_LINE_LENGTH: u32 = 100; + +// C AST node types +pub const NodeType = enum(u8) { + program = 0, + const_node = 1, + var = 2, + binop = 3, + unop = 4, + call = 5, + block = 6, + if_stmt = 7, + loop = 8, + ret = 9, + fn = 10, +}; + +// Binary operators +pub const BinOp = enum(u8) { + add = 0, + sub = 1, + mul = 2, + div = 3, + mod = 4, + and_op = 5, + or_op = 6, + xor = 7, + shl = 8, + shr = 9, + eq = 10, + ne = 11, + lt = 12, + le = 13, + gt = 14, + ge = 15, +}; + +// Unary operators +pub const UnOp = enum(u8) { + neg = 0, + not = 1, + deref = 2, + addr = 3, +}; + +// C type mappings +pub const CMapping = struct { + trit_type: []const u8 = "int8_t", + word_type: []const u8 = "uint32_t", + float_type: []const u8 = "double", +}; + +// C code generator state +pub const CCodeGen = struct { + ast: *const AST, + output: []u8, + indent_level: u32 = 0, + pos: usize = 0, + + // Emit C code from AST + pub fn emit_c(self: *CCodeGen) !void { + try self.emit_header(); + try self.emit_includes(); + try self.emit_types(); + try self.emit_declarations(); + try self.emit_implementations(); + try self.emit_test_section(); + try self.emit_invariant_section(); + try self.emit_bench_section(); + } + + // Emit file header comment + fn emit_header(self: *CCodeGen) !void { + _ = self; + // Emit: // Generated by t27 compiler + // Emit: // phi^2 + 1/phi^2 = 3 | TRINITY + } + + // Emit C include statements + fn emit_includes(self: *CCodeGen) !void { + // Emit: #include + // Emit: #include + // Emit: #include + _ = self; + } + + // Emit C type definitions + fn emit_types(self: *CCodeGen) !void { + // Emit: typedef int8_t Trit; + // Emit: typedef struct { int8_t trits[27]; } TernaryWord; + _ = self; + } + + // Emit function declarations + fn emit_declarations(self: *CCodeGen) !void { + // Iterate AST for NODE_FN and emit declarations + _ = self; + } + + // Emit function implementations + fn emit_implementations(self: *CCodeGen) !void { + // Iterate AST for NODE_FN and emit implementations + _ = self; + } + + // Emit expression as C code + fn emit_expression(self: *CCodeGen, node: *const ASTNode) !void { + switch (node.node_type) { + .const_node => try self.emit_const(node), + .var => try self.emit_var(node), + .binop => try self.emit_binop(node), + .unop => try self.emit_unop(node), + .call => try self.emit_call(node), + else => {}, + } + } + + // Emit constant value + fn emit_const(self: *CCodeGen, node: *const ASTNode) !void { + _ = self; + _ = node; + } + + // Emit variable name + fn emit_var(self: *CCodeGen, node: *const ASTNode) !void { + _ = self; + _ = node; + } + + // Emit binary operation + fn emit_binop(self: *CCodeGen, node: *const ASTNode) !void { + _ = self; + _ = node; + } + + // Emit unary operation + fn emit_unop(self: *CCodeGen, node: *const ASTNode) !void { + _ = self; + _ = node; + } + + // Emit function call + fn emit_call(self: *CCodeGen, node: *const ASTNode) !void { + _ = self; + _ = node; + } + + // Emit binary operator string + fn emit_operator(self: *CCodeGen, op: BinOp) !void { + _ = self; + _ = op; + } + + // Emit unary operator string + fn emit_unary_operator(self: *CCodeGen, op: UnOp) !void { + _ = self; + _ = op; + } + + // Emit test functions + fn emit_test_section(self: *CCodeGen) !void { + _ = self; + // Emit C test functions from .test section + } + + // Emit invariant check functions + fn emit_invariant_section(self: *CCodeGen) !void { + _ = self; + // Emit C invariant check functions from .invariant section + } + + // Emit benchmark functions + fn emit_bench_section(self: *CCodeGen) !void { + _ = self; + // Emit C benchmark functions from .bench section + } + + // Emit indentation + fn emit_indent(self: *CCodeGen) !void { + _ = self; + // Emit indent_level * INDENT_SIZE spaces + } +}; + +// Tests +test "test_emit_c_from_gf16_spec" { + // Verify: GF16 types emit correct C typedefs + // Setup: parse spec with GF16, emit C code + // Expected: typedef int16_t for Trit, struct with 27 trits +} + +test "test_no_undefined_behavior" { + // Verify: emitted C code has no undefined behavior + // Setup: parse and emit various operations + // Expected: all variables initialized, no use of uninitialized values +} + +test "test_c_header_completeness" { + // Verify: generated C has all required headers + // Setup: emit full program + // Expected: includes , , +} + +test "test_function_declaration_matches_definition" { + // Verify: declarations and implementations match signatures + // Setup: emit both declaration and implementation + // Expected: same return type, parameters, name +} + +test "test_ternary_type_mapping" { + // Verify: Trit maps to int8_t, TernaryWord to uint32_t + // Setup: emit code using Trit and TernaryWord + // Expected: int8_t for signed trits, uint32_t for 27-trit words +} + +test "test_operator_precedence_preserved" { + // Verify: operator precedence in emitted C matches source + // Setup: emit expression with multiple operators + // Expected: parentheses added when needed +} + +test "test_c_identifier_validity" { + // Verify: emitted identifiers are valid C identifiers + // Setup: emit code with various identifiers + // Expected: no invalid characters, no C keywords as identifiers +} + +// Invariants +invariant "no_undefined_behavior" { + // For all emitted C code: no UB (uninitialized reads, etc.) + // Rationale: Generated code must be safe and deterministic +} + +invariant "type_mapping_correctness" { + // Trit -> int8_t, TernaryWord -> uint32_t, f64 -> double + // Rationale: Type mapping must be consistent +} + +invariant "header_includes_complete" { + // Generated C includes all required standard headers + // Rationale: C code must compile without missing headers +} + +invariant "function_signature_consistency" { + // Declarations and implementations have identical signatures + // Rationale: Linker requires matching signatures +} + +invariant "c99_compliance" { + // Emitted code is valid C99 (or specified standard) + // Rationale: Target C standard must be respected +} + +// Benchmarks +bench "bench_emit_c_throughput" { + // Measure: lines of C emitted per second + // Target: > 1000 lines/sec for typical spec +} + +bench "test_c_compilation_time" { + // Measure: time to compile emitted C code + // Target: < 1s for typical module with -O2 +} diff --git a/apps/website/public/t27/files/compiler/codegen/testgen.t27 b/apps/website/public/t27/files/compiler/codegen/testgen.t27 new file mode 100644 index 0000000000..9671b800ec --- /dev/null +++ b/apps/website/public/t27/files/compiler/codegen/testgen.t27 @@ -0,0 +1,909 @@ +// testgen.t27 -- Generic Test Generator for TDD-Inside-Spec +// Generates test code from spec test blocks for multiple backends + +module testgen { + using ast: @import("../../ast.t27"); + + // TestGen options + pub const TestGenOptions = struct { + backend: []const u8, // Target backend (zig, c, verilog, rust, etc.) + emit_comments: bool, // Include test descriptions as comments + emit_benchmarks: bool, // Include benchmark code + output_format: []const u8, // Format of output (code, json, xml, etc.) + }; + + // TestGen context + pub const TestGen = struct { + ast: Program, + options: TestGenOptions, + output: StringBuilder, + + pub fn new(ast_node: Program, opts: TestGenOptions) TestGen { + return TestGen{ + .ast = ast_node, + .options = opts, + .output = StringBuilder.new(65536), + }; + } + + pub fn generate(self: *TestGen) []const u8 { + if (std.mem.eql(u8, self.options.backend, "zig")) { + return self.generate_zig_tests(); + } else if (std.mem.eql(u8, self.options.backend, "c")) { + return self.generate_c_tests(); + } else if (std.mem.eql(u8, self.options.backend, "verilog")) { + return self.generate_verilog_tests(); + } else if (std.mem.eql(u8, self.options.backend, "rust")) { + return self.generate_rust_tests(); + } else if (std.mem.eql(u8, self.options.backend, "json")) { + return self.generate_conformance_json(); + } else { + return "// Unsupported backend: " ++ self.options.backend; + } + } + + // Generate Zig tests + pub fn generate_zig_tests(self: *TestGen) []const u8 { + self.emit("// Zig tests generated from "); + self.emit(self.ast.source_file); + self.emit_line(""); + self.emit("const std = @import(\"std\");"); + self.emit_line(""); + self.emit_line("// ==============================================================="); + self.emit_line("// TDD-Inside-Spec: Generated Tests"); + self.emit_line("// ==============================================================="); + self.emit_line(""); + + // Generate from spec_decl (high-level TDD) + if (self.ast.spec_decl) |spec_decl| { + self.generate_spec_tests_zig(spec_decl); + } + + // Generate from test_section (assembly-style) + if (self.ast.test_section) |test_section| { + self.generate_assembly_tests_zig(test_section); + } + + // Generate invariants + if (self.ast.invariant_section) |inv_section| { + self.generate_invariants_zig(inv_section); + } + + // Generate benchmarks + if (self.ast.bench_section) |bench_section| and self.options.emit_benchmarks { + self.generate_benchmarks_zig(bench_section); + } + + return self.output.to_string(); + } + + // Generate C tests + pub fn generate_c_tests(self: *TestGen) []const u8 { + self.emit("// C tests generated from "); + self.emit(self.ast.source_file); + self.emit_line(""); + self.emit("#include "); + self.emit_line(""); + self.emit_line("// ==============================================================="); + self.emit_line("// TDD-Inside-Spec: Generated Tests"); + self.emit_line("// ==============================================================="); + self.emit_line(""); + + // Generate from spec_decl + if (self.ast.spec_decl) |spec_decl| { + self.generate_spec_tests_c(spec_decl); + } + + // Generate from test_section + if (self.ast.test_section) |test_section| { + self.generate_assembly_tests_c(test_section); + } + + return self.output.to_string(); + } + + // Generate Verilog testbench + pub fn generate_verilog_tests(self: *TestGen) []const u8 { + self.emit("// Verilog testbench generated from "); + self.emit(self.ast.source_file); + self.emit_line(""); + self.emit_line("// ==============================================================="); + self.emit_line("// TDD-Inside-Spec: Generated Testbench"); + self.emit_line("// ==============================================================="); + self.emit_line(""); + + self.emit("`timescale 1ns/1ps"); + self.emit_line(""); + self.emit("module tb_"); + self.emit(self.mangle_filename(self.ast.source_file)); + self.emit("();"); + self.emit_line(""); + self.emit_line(" // Testbench: clock, reset, stimulus generation, response checking"); + self.emit_line(" // Clock: 100MHz (10ns period)"); + self.emit_line(" // Reset: active-low, 100ns duration"); + self.emit_line(" // Stimulus: based on test_cases in test_section"); + self.emit_line(" // Response: verify against expected_outcome"); + self.emit_line(""); + self.emit_line(" // TODO: Parse test_section.test_cases and generate:"); + self.emit_line(" // - Clock generator with 50% duty cycle"); + self.emit_line(" // - Reset sequence (async reset, release after 10 cycles)"); + self.emit_line(" // - Apply test input vectors"); + self.emit_line(" // - Monitor output and assert expected values"); + self.emit_line(" // - Report pass/fail status"); + self.emit_line(""); + self.emit_line(" // Signal declarations:"); + self.emit_line(" // reg clk, rst_n;"); + self.emit_line(" // wire [DATA_WIDTH-1:0] data_out;"); + self.emit_line(""); + self.emit_line(" // DUT instantiation:"); + self.emit_line(" // module_name dut (.clk(clk), .rst_n(rst_n), ...);"); + self.emit_line(""); + self.emit("endmodule"); + + return self.output.to_string(); + } + + // Generate Rust tests + pub fn generate_rust_tests(self: *TestGen) []const u8 { + self.emit("// Rust tests generated from "); + self.emit(self.ast.source_file); + self.emit_line(""); + self.emit_line("#[cfg(test)]"); + self.emit("mod tests {"); + self.emit_line(""); + self.emit_line(" use super::*;"); + self.emit_line(""); + self.emit_line(" // ==============================================================="); + self.emit_line(" // TDD-Inside-Spec: Generated Tests"); + self.emit_line(" // ==============================================================="); + self.emit_line(""); + + // Generate from spec_decl + if (self.ast.spec_decl) |spec_decl| { + self.generate_spec_tests_rust(spec_decl); + } + + // Generate from test_section + if (self.ast.test_section) |test_section| { + self.generate_assembly_tests_rust(test_section); + } + + self.emit_line("}"); + + return self.output.to_string(); + } + + // Generate conformance JSON + pub fn generate_conformance_json(self: *TestGen) []const u8 { + var json: []const u8 = "{\n"; + json = json ++ " \"spec\": \"" ++ self.ast.source_file ++ "\",\n"; + json = json ++ " \"generated_at\": \"[timestamp]\",\n"; + json = json ++ " \"test_vectors\": [\n"; + + // Add from test_section + if (self.ast.test_section) |test_section| { + for (test_section.test_cases) |test_case| { + json = json ++ " {\n"; + json = json ++ " \"name\": \"" ++ test_case.name ++ "\",\n"; + json = json ++ " \"verify\": \"" ++ self.escape_json(test_case.verify_description) ++ "\",\n"; + json = json ++ " \"expected\": \"" ++ self.escape_json(test_case.expected_outcome) ++ "\"\n"; + json = json ++ " },\n"; + } + } + + // Add from spec_decl + if (self.ast.spec_decl) |spec_decl| { + for (spec_decl.test_blocks) |test_block| { + json = json ++ " {\n"; + json = json ++ " \"name\": \"" ++ test_block.name ++ "\",\n"; + json = json ++ " \"type\": \"spec_test\"\n"; + json = json ++ " },\n"; + } + } + + // Add invariants + if (self.ast.spec_decl) |spec_decl| { + for (spec_decl.invariants) |inv| { + json = json ++ " {\n"; + json = json ++ " \"name\": \"invariant_" ++ std.fmt.digitToChar(@intCast(inv.line % 10)) ++ "\",\n"; + json = json ++ " \"type\": \"invariant\",\n"; + json = json ++ " \"assert\": \"" ++ self.escape_json(inv.expression) ++ "\"\n"; + json = json ++ " },\n"; + } + } + + // Remove trailing comma + json = json[0..json.len - 2]; + json = json ++ "\n"; + json = json ++ " ]\n"; + json = json ++ "}\n"; + + return json; + } + + // Generate spec tests for Zig + fn generate_spec_tests_zig(self: *TestGen, spec: SpecDecl) void { + for (spec.test_blocks) |test_block| { + self.emit("test \""); + self.emit(test_block.name); + self.emit("\" {"); + self.emit_line(""); + self.emit(" // Given:"); + self.emit_line(""); + for (test_block.given_clauses) |given| { + self.emit(" const "); + self.emit(given.variable); + self.emit(" = "); + self.emit(given.expression); + self.emit(";"); + self.emit_line(""); + } + self.emit(" // When:"); + self.emit_line(""); + for (test_block.when_clauses) |when| { + self.emit(" const "); + self.emit(when.variable); + self.emit(" = "); + self.emit(when.expression); + self.emit(";"); + self.emit_line(""); + } + self.emit(" // Then:"); + self.emit_line(""); + for (test_block.then_clauses) |then| { + self.emit(" try std.testing.expect("); + self.emit(then.expression); + self.emit(");"); + self.emit_line(""); + } + self.emit("}"); + self.emit_line(""); + } + } + + // Generate spec tests for C + fn generate_spec_tests_c(self: *TestGen, spec: SpecDecl) void { + for (spec.test_blocks) |test_block| { + self.emit("void test_"); + self.emit(test_block.name); + self.emit("(void) {"); + self.emit_line(""); + self.emit(" // Given:"); + self.emit_line(""); + for (test_block.given_clauses) |given| { + self.emit(" const "); + self.emit(self.c_type_from_expr(given.expression)); + self.emit(" "); + self.emit(given.variable); + self.emit(" = "); + self.emit(given.expression); + self.emit(";"); + self.emit_line(""); + } + self.emit(" // When:"); + self.emit_line(""); + for (test_block.when_clauses) |when| { + self.emit(" const "); + self.emit(self.c_type_from_expr(when.expression)); + self.emit(" "); + self.emit(when.variable); + self.emit(" = "); + self.emit(when.expression); + self.emit(";"); + self.emit_line(""); + } + self.emit(" // Then:"); + self.emit_line(""); + for (test_block.then_clauses) |then| { + self.emit(" assert("); + self.emit(then.expression); + self.emit(");"); + self.emit_line(""); + } + self.emit("}"); + self.emit_line(""); + } + } + + // Generate spec tests for Rust + fn generate_spec_tests_rust(self: *TestGen, spec: SpecDecl) void { + for (spec.test_blocks) |test_block| { + self.emit(" #[test]"); + self.emit_line(""); + self.emit(" fn test_"); + self.emit(self.to_snake_case(test_block.name)); + self.emit("() {"); + self.emit_line(""); + self.emit(" // Given:"); + self.emit_line(""); + for (test_block.given_clauses) |given| { + self.emit(" let "); + self.emit(given.variable); + self.emit(" = "); + self.emit(given.expression); + self.emit(";"); + self.emit_line(""); + } + self.emit(" // When:"); + self.emit_line(""); + for (test_block.when_clauses) |when| { + self.emit(" let "); + self.emit(when.variable); + self.emit(" = "); + self.emit(when.expression); + self.emit(";"); + self.emit_line(""); + } + self.emit(" // Then:"); + self.emit_line(""); + for (test_block.then_clauses) |then| { + self.emit(" assert!("); + self.emit(then.expression); + self.emit(");"); + self.emit_line(""); + } + self.emit(" }"); + self.emit_line(""); + } + } + + // Generate assembly-style tests for Zig + fn generate_assembly_tests_zig(self: *TestGen, test_section: TestSection) void { + for (test_section.test_cases) |test_case| { + self.emit("test \""); + self.emit(test_case.name); + self.emit("\" {"); + self.emit_line(""); + self.emit(" // Verify: "); + self.emit_line(test_case.verify_description); + self.emit(" // Expected: "); + self.emit_line(test_case.expected_outcome); + self.emit_line(" // TODO: Parse verify_description and generate Zig test code:"); + self.emit_line(" // 1. Parse for function call pattern (e.g., \"my_func(42) returns 42\")"); + self.emit_line(" // 2. Parse expected_outcome for assertion value"); + self.emit_line(" // 3. Generate: const actual = ;"); + self.emit_line(" // 4. Generate: try std.testing.expectEqual(expected, actual);"); + self.emit_line(""); + self.emit_line(" // Example patterns:"); + self.emit_line(" // - Function result: const result = add(@as(i32, 2), @as(i32, 3));"); + self.emit_line(" // try std.testing.expectEqual(@as(i32, 5), result);"); + self.emit_line(" // - Property check: try std.testing.expect(value > 0);"); + self.emit_line(" // - Error handling: try std.testing.expectError(error.Invalid, func());"); + self.emit(" try std.testing.expect(true); // Placeholder during bootstrap"); + self.emit_line(""); + self.emit("}"); + self.emit_line(""); + } + } + + // Generate assembly-style tests for C + fn generate_assembly_tests_c(self: *TestGen, test_section: TestSection) void { + for (test_section.test_cases) |test_case| { + self.emit("void test_"); + self.emit(test_case.name); + self.emit("(void) {"); + self.emit_line(""); + self.emit(" // Verify: "); + self.emit_line(test_case.verify_description); + self.emit(" // Expected: "); + self.emit_line(test_case.expected_outcome); + self.emit_line(" // TODO: Parse verify_description and generate C test code:"); + self.emit_line(" // 1. Parse for function call pattern"); + self.emit_line(" // 2. Parse expected_outcome for assertion value"); + self.emit_line(" // 3. Generate: type actual = ;"); + self.emit_line(" // 4. Generate: assert(actual == expected);"); + self.emit_line(""); + self.emit_line(" // Example patterns:"); + self.emit_line(" // - Function result: int result = add(2, 3);"); + self.emit_line(" // assert(result == 5);"); + self.emit_line(" // - Property check: assert(value > 0);"); + self.emit_line(" // - Pointer check: assert(ptr != NULL);"); + self.emit(" assert(true); // Placeholder during bootstrap"); + self.emit_line(""); + self.emit("}"); + self.emit_line(""); + } + } + + // Generate assembly-style tests for Rust + fn generate_assembly_tests_rust(self: *TestGen, test_section: TestSection) void { + for (test_section.test_cases) |test_case| { + self.emit(" #[test]"); + self.emit_line(""); + self.emit(" fn test_"); + self.emit(self.to_snake_case(test_case.name)); + self.emit("() {"); + self.emit_line(""); + self.emit(" // Verify: "); + self.emit_line(test_case.verify_description); + self.emit(" // Expected: "); + self.emit_line(test_case.expected_outcome); + self.emit_line(" // TODO: Parse verify_description and generate Rust test code:"); + self.emit_line(" // 1. Parse for function call pattern"); + self.emit_line(" // 2. Parse expected_outcome for assertion value"); + self.emit_line(" // 3. Generate: let actual = ;"); + self.emit_line(" // 4. Generate: assert_eq!(expected, actual);"); + self.emit_line(""); + self.emit_line(" // Example patterns:"); + self.emit_line(" // - Function result: let result = add(2, 3);"); + self.emit_line(" // assert_eq!(5, result);"); + self.emit_line(" // - Property check: assert!(value > 0);"); + self.emit_line(" // - Error handling: assert!(func().is_err());"); + self.emit(" assert!(true); // Placeholder during bootstrap"); + self.emit_line(""); + self.emit(" }"); + self.emit_line(""); + } + } + + // Generate invariants as Zig tests + fn generate_invariants_zig(self: *TestGen, inv_section: InvariantSection) void { + for (inv_section.invariants) |inv| { + self.emit("test \"invariant_"); + self.emit(inv.name); + self.emit("\" {"); + self.emit_line(""); + self.emit(" // Invariant: "); + self.emit_line(inv.formal_statement); + self.emit(" // Rationale: "); + self.emit_line(inv.rationale); + self.emit_line(" // TODO: Parse formal_statement and generate Zig invariant assertion:"); + self.emit_line(" // 1. Parse for invariant type (bounds, equality, property)"); + self.emit_line(" // 2. Parse for subject (variable, function call, state)"); + self.emit_line(" // 3. Generate appropriate Zig assertion"); + self.emit_line(""); + self.emit_line(" // Invariant patterns:"); + self.emit_line(" // - Bounds: try std.testing.expect(value >= min and value <= max);"); + self.emit_line(" // - Equality: try std.testing.expect(actual == expected);"); + self.emit_line(" // - Emptiness: try std.testing.expect(count == 0);"); + self.emit_line(" // - Non-null: try std.testing.expect(ptr != null);"); + self.emit_line(" // - Always-true identity: try std.testing.expect(true);"); + self.emit(" try std.testing.expect(true); // Placeholder during bootstrap"); + self.emit_line(""); + self.emit("}"); + self.emit_line(""); + } + } + + // Generate benchmarks as Zig tests + fn generate_benchmarks_zig(self: *TestGen, bench_section: BenchSection) void { + for (bench_section.benchmarks) |bench| { + self.emit("test \"bench_"); + self.emit(bench.name); + self.emit("\" {"); + self.emit_line(""); + self.emit(" // Measure: "); + self.emit_line(bench.measure_description); + if (bench.target != null) { + self.emit(" // Target: "); + self.emit_line(bench.target.?); + } + self.emit(" // Units: "); + self.emit_line(bench.units); + self.emit(" const timer = try std.time.Timer.start();"); + self.emit(" _ = timer;"); + self.emit_line(""); + self.emit("}"); + self.emit_line(""); + } + } + + // Mangle filename to valid identifier + fn mangle_filename(self: *TestGen, filename: []const u8) []const u8 { + var result: []const u8 = ""; + var start: usize = filename.len - 1; + + // Find last path separator + while (start >= 0 and filename[start] != '/' and filename[start] != '\\') { + if (start == 0) break; + start -= 1; + } + + // Extract filename without extension + var i: usize = start + 1; + while (i < filename.len) : (i += 1) { + const c = filename[i]; + if (c == '.') break; + if ((c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or (c >= '0' and c <= '9') or c == '_') { + result = result ++ [_]u8{c}; + } else { + result = result ++ "_"; + } + } + + return result; + } + + // Escape JSON string + fn escape_json(self: *TestGen, s: []const u8) []const u8 { + var result: []const u8 = ""; + var i: usize = 0; + while (i < s.len) : (i += 1) { + const c = s[i]; + if (c == '"') { + result = result ++ "\\\""; + } else if (c == '\\') { + result = result ++ "\\\\"; + } else if (c == '\n') { + result = result ++ "\\n"; + } else if (c == '\r') { + result = result ++ "\\r"; + } else if (c == '\t') { + result = result ++ "\\t"; + } else { + result = result ++ [_]u8{c}; + } + } + return result; + } + + // Convert to snake_case + fn to_snake_case(self: *TestGen, s: []const u8) []const u8 { + var result: []const u8 = ""; + var i: usize = 0; + while (i < s.len) : (i += 1) { + const c = s[i]; + if (c >= 'A' and c <= 'Z') { + if (result.len > 0) { + result = result ++ "_"; + } + result = result ++ [_]u8{c + 32}; // to lower case + } else if ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or c == '_') { + result = result ++ [_]u8{c}; + } else { + if (result.len > 0) { + result = result ++ "_"; + } + } + } + return result; + } + + // Infer C type from expression + fn c_type_from_expr(self: *TestGen, expr: []const u8) []const u8 { + // Simple heuristic - look for float literals + var i: usize = 0; + while (i < expr.len) : (i += 1) { + const c = expr[i]; + if (c == '.' or c == 'e' or c == 'E') { + return "double"; + } + } + return "int"; + } + + // Emit helpers + fn emit(self: *TestGen, s: []const u8) void { + self.output.append(s); + } + + fn emit_line(self: *TestGen, s: []const u8) void { + self.output.append(s); + self.output.append("\n"); + } + }; + + // StringBuilder + pub const StringBuilder = struct { + buffer: []u8, + len: u32, + capacity: u32, + + pub fn new(capacity: u32) StringBuilder { + return StringBuilder{ + .buffer = [_]u8{0} ** capacity, + .len = 0, + .capacity = capacity, + }; + } + + pub fn append(self: *StringBuilder, s: []const u8) void { + var i: usize = 0; + while (i < s.len and self.len < self.capacity) : (i += 1) { + self.buffer[self.len] = s[i]; + self.len += 1; + } + } + + pub fn to_string(self: *StringBuilder) []const u8 { + return self.buffer[0..self.len]; + } + }; + + // Types from AST (simplified for testgen) + pub const Program = struct { + source_file: []const u8, + spec_decl: ?SpecDecl, + test_section: ?TestSection, + invariant_section: ?InvariantSection, + bench_section: ?BenchSection, + }; + + pub const SpecDecl = struct { + test_blocks: []TestBlock, + invariants: []Invariant, + }; + + pub const TestSection = struct { + test_cases: []TestCase, + }; + + pub const InvariantSection = struct { + invariants: []Invariant, + }; + + pub const BenchSection = struct { + benchmarks: []Benchmark, + }; + + pub const TestBlock = struct { + name: []const u8, + given_clauses: []Clause, + when_clauses: []Clause, + then_clauses: []Clause, + }; + + pub const TestCase = struct { + name: []const u8, + verify_description: []const u8, + expected_outcome: []const u8, + }; + + pub const Invariant = struct { + name: []const u8, + formal_statement: []const u8, + rationale: []const u8, + expression: []const u8, + line: u32, + }; + + pub const Benchmark = struct { + name: []const u8, + measure_description: []const u8, + target: ?[]const u8, + units: []const u8, + }; + + pub const Clause = struct { + variable: []const u8, + expression: []const u8, + }; +} + +// ======================================================================================================= +// TDD-Inside-Spec: Tests and Invariants for TestGen +// ======================================================================================================= + +test testgen_new_creates_generator + // Verify: TestGen.new creates a generator with AST and options + // Expected: TestGen has ast, options, and initialized StringBuilder + var ast = testgen.Program{ + .source_file = "test.t27", + .spec_decl = null, + .test_section = null, + .invariant_section = null, + .bench_section = null, + }; + var opts = testgen.TestGenOptions{ + .backend = "zig", + .emit_comments = true, + .emit_benchmarks = true, + .output_format = "code", + }; + var gen = testgen.TestGen.new(ast, opts); + _ = gen; + try std.testing.expect(true); + +test testgen_generate_zig_outputs_header + // Verify: generate() with zig backend outputs Zig header + // Expected: Output contains "// Zig tests generated from" and std import + var ast = testgen.Program{ + .source_file = "test.t27", + .spec_decl = null, + .test_section = null, + .invariant_section = null, + .bench_section = null, + }; + var opts = testgen.TestGenOptions{ + .backend = "zig", + .emit_comments = true, + .emit_benchmarks = false, + .output_format = "code", + }; + var gen = testgen.TestGen.new(ast, opts); + const output = gen.generate(); + try std.testing.expect(std.mem.indexOf(u8, output, "// Zig tests generated from") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "@import(\"std\")") != null); + +test testgen_generate_c_outputs_header + // Verify: generate() with c backend outputs C header + // Expected: Output contains "// C tests generated from" and assert.h include + var ast = testgen.Program{ + .source_file = "test.t27", + .spec_decl = null, + .test_section = null, + .invariant_section = null, + .bench_section = null, + }; + var opts = testgen.TestGenOptions{ + .backend = "c", + .emit_comments = true, + .emit_benchmarks = false, + .output_format = "code", + }; + var gen = testgen.TestGen.new(ast, opts); + const output = gen.generate(); + try std.testing.expect(std.mem.indexOf(u8, output, "// C tests generated from") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "#include ") != null); + +test testgen_generate_verilog_outputs_module + // Verify: generate() with verilog backend outputs Verilog module + // Expected: Output contains "module tb_" and "`timescale" + var ast = testgen.Program{ + .source_file = "test.t27", + .spec_decl = null, + .test_section = null, + .invariant_section = null, + .bench_section = null, + }; + var opts = testgen.TestGenOptions{ + .backend = "verilog", + .emit_comments = true, + .emit_benchmarks = false, + .output_format = "code", + }; + var gen = testgen.TestGen.new(ast, opts); + const output = gen.generate(); + try std.testing.expect(std.mem.indexOf(u8, output, "module tb_") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "`timescale") != null); + +test testgen_generate_rust_outputs_mod_block + // Verify: generate() with rust backend outputs Rust mod block + // Expected: Output contains "#[cfg(test)]" and "mod tests {" + var ast = testgen.Program{ + .source_file = "test.t27", + .spec_decl = null, + .test_section = null, + .invariant_section = null, + .bench_section = null, + }; + var opts = testgen.TestGenOptions{ + .backend = "rust", + .emit_comments = true, + .emit_benchmarks = false, + .output_format = "code", + }; + var gen = testgen.TestGen.new(ast, opts); + const output = gen.generate(); + try std.testing.expect(std.mem.indexOf(u8, output, "#[cfg(test)]") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "mod tests {") != null); + +test testgen_generate_json_outputs_conformance + // Verify: generate() with json backend outputs conformance JSON + // Expected: Output contains "spec", "generated_at", "test_vectors" + var ast = testgen.Program{ + .source_file = "test.t27", + .spec_decl = null, + .test_section = null, + .invariant_section = null, + .bench_section = null, + }; + var opts = testgen.TestGenOptions{ + .backend = "json", + .emit_comments = false, + .emit_benchmarks = false, + .output_format = "json", + }; + var gen = testgen.TestGen.new(ast, opts); + const output = gen.generate(); + try std.testing.expect(std.mem.indexOf(u8, output, "\"spec\"") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "\"generated_at\"") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "\"test_vectors\"") != null); + +test testgen_unsupported_backend + // Verify: generate() with unsupported backend returns error comment + // Expected: Output contains "// Unsupported backend:" + var ast = testgen.Program{ + .source_file = "test.t27", + .spec_decl = null, + .test_section = null, + .invariant_section = null, + .bench_section = null, + }; + var opts = testgen.TestGenOptions{ + .backend = "python", + .emit_comments = true, + .emit_benchmarks = false, + .output_format = "code", + }; + var gen = testgen.TestGen.new(ast, opts); + const output = gen.generate(); + try std.testing.expect(std.mem.indexOf(u8, output, "// Unsupported backend:") != null); + +test string_builder_append_works + // Verify: StringBuilder.append adds strings correctly + // Expected: to_string returns concatenated content + var sb = testgen.StringBuilder.new(100); + sb.append("hello"); + sb.append(" "); + sb.append("world"); + try std.testing.expectEqualStrings("hello world", sb.to_string()); + +test string_builder_respects_capacity + // Verify: StringBuilder does not exceed capacity + // Expected: Truncates at capacity if exceeded + var sb = testgen.StringBuilder.new(10); + sb.append("hello world this is too long"); + try std.testing.expect(sb.to_string().len <= 10); + +invariant testgen_backend_switch + // generate() dispatches to correct backend function + // Rationale: Ensures proper backend selection + const backends = [_][]const u8{ "zig", "c", "verilog", "rust", "json" }; + _ = backends; + assert true; + +invariant testgen_output_contains_tdd_header + // All generated outputs contain TDD-Inside-Spec header + // Rationale: Marks generated code as TDD-compliant + assert true; + +invariant string_builder_capacity_fixed + // StringBuilder has fixed capacity from creation + // Rationale: Predictable memory usage for test generation + assert true; + +invariant testgen_json_escaped_properly + // JSON output escapes special characters + // Rationale: Valid JSON output for conformance testing + assert true; + +invariant testgen_filename_mangled + // Filenames are mangled to valid identifiers + // Rationale: Generates valid module/function names + assert true; + +bench testgen_generate_zig_latency + target: < 10ms + var ast = testgen.Program{ + .source_file = "test.t27", + .spec_decl = null, + .test_section = null, + .invariant_section = null, + .bench_section = null, + }; + var opts = testgen.TestGenOptions{ + .backend = "zig", + .emit_comments = true, + .emit_benchmarks = false, + .output_format = "code", + }; + var gen = testgen.TestGen.new(ast, opts); + _ = gen.generate(); + +bench testgen_generate_json_latency + target: < 5ms + var ast = testgen.Program{ + .source_file = "test.t27", + .spec_decl = null, + .test_section = null, + .invariant_section = null, + .bench_section = null, + }; + var opts = testgen.TestGenOptions{ + .backend = "json", + .emit_comments = false, + .emit_benchmarks = false, + .output_format = "json", + }; + var gen = testgen.TestGen.new(ast, opts); + _ = gen.generate(); + +bench string_builder_append_latency + target: < 1us per append + var sb = testgen.StringBuilder.new(1000); + var i: usize = 0; + while (i < 100) : (i += 1) { + sb.append("test"); + } + _ = sb.to_string(); diff --git a/apps/website/public/t27/files/compiler/codegen/verilog/codegen.t27 b/apps/website/public/t27/files/compiler/codegen/verilog/codegen.t27 new file mode 100644 index 0000000000..fb9c45d31f --- /dev/null +++ b/apps/website/public/t27/files/compiler/codegen/verilog/codegen.t27 @@ -0,0 +1,1067 @@ +// codegen.t27 0 Code Generator for Verilog +// Generates synthesizable Verilog from t27 AST + +module verilog_codegen { + using ast: @import("../../../ast.t27"); + + // Verilog codegen options + pub const VerilogCodegenOptions = struct { + target_device: []const u8, // e.g., "XC7A100T" + clock_freq_hz: u32, // Target clock frequency + include_testbench: bool, // Include testbench + include_toplevel: bool, // Include top-level wrapper + }; + + // Verilog codegen context + pub const VerilogCodegen = struct { + ast: Program, + options: VerilogCodegenOptions, + output: StringBuilder, + indent_level: u32, + errors: []CodegenError, + pc_width: u8, // Program counter width + addr_width: u8, // Address width + data_width: u8, // Data width + + pub fn new(ast_node: Program, opts: VerilogCodegenOptions) VerilogCodegen { + // Calculate required widths based on program size + const pc_width = calculate_width(@intCast(ast_node.code_section.instructions.len)); + const addr_width: u8 = 12; // 4KB address space + const data_width: u8 = 32; // 32-bit data + + return VerilogCodegen{ + .ast = ast_node, + .options = opts, + .output = StringBuilder.new(131072), + .indent_level = 0, + .errors = &.{}, + .pc_width = pc_width, + .addr_width = addr_width, + .data_width = data_width, + }; + } + + pub fn generate(self: *VerilogCodegen) []const u8 { + // Header + self.emit_header(); + + // Module parameters + self.emit_parameters(); + + // Port declaration + self.emit_ports(); + + // Internal signals + self.emit_signals(); + + // Instruction memory (ROM) + self.emit_instruction_rom(); + + // Data memory (RAM) + self.emit_data_memory(); + + // Register file + self.emit_register_file(); + + // Instruction decode + self.emit_decode(); + + // ALU + self.emit_alu(); + + // Control logic + self.emit_control(); + + // Sequential logic + self.emit_sequential(); + + // TDD-Inside-Spec: Emit assertions from .invariant section + self.emit_verilog_assertions(); + + // Footer + self.emit_footer(); + + // Optional testbench (includes tests from .test section) + if (self.options.include_testbench) { + self.emit_testbench(); + } + + return self.output.to_string(); + } + + // Emit file header + fn emit_header(self: *VerilogCodegen) void { + self.emit_line("// Generated by t27 compiler from "); + self.emit(self.ast.source_file); + self.emit_line(""); + self.emit("// Target: "); + self.emit(self.options.target_device); + self.emit_line(""); + self.emit("// Clock: "); + self.emit_int(self.options.clock_freq_hz); + self.emit_line(" Hz"); + self.emit_line("// DO NOT EDIT 1 source of truth is .t27 file"); + self.emit_line(""); + self.emit_line("`timescale 1ns / 1ps"); + self.emit_line(""); + } + + // Emit module parameters + fn emit_parameters(self: *VerilogCodegen) void { + self.emit_line("module tri27_processor #("); + self.indent(); + self.emit("parameter PC_WIDTH = "); + self.emit_int(self.pc_width); + self.emit_line(","); + self.emit("parameter ADDR_WIDTH = "); + self.emit_int(self.addr_width); + self.emit_line(","); + self.emit("parameter DATA_WIDTH = "); + self.emit_int(self.data_width); + self.dedent(); + self.emit_line(") ("); + self.indent(); + } + + // Emit ports + fn emit_ports(self: *VerilogCodegen) void { + self.emit_line("// Clock and reset"); + self.emit_line("input wire clk,"); + self.emit_line("input wire rst_n,"); + self.emit_line(""); + self.emit_line("// Instruction memory interface"); + self.emit("output wire ["); + self.emit_int(self.addr_width - 1); + self.emit(":0] pc,"); + self.emit_line(""); + self.emit_line("// Data memory interface"); + self.emit("output wire ["); + self.emit_int(self.addr_width - 1); + self.emit(":0] mem_addr,"); + self.emit_line("output wire mem_we,"); + self.emit("output wire ["); + self.emit_int(self.data_width - 1); + self.emit(":0] mem_wdata,"); + self.emit("input wire ["); + self.emit_int(self.data_width - 1); + self.emit(":0] mem_rdata,"); + self.emit_line(""); + self.emit_line("// Status"); + self.emit_line("output wire halted"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + } + + // Emit internal signals + fn emit_signals(self: *VerilogCodegen) void { + self.emit_line("// Internal signals"); + self.emit("reg ["); + self.emit_int(self.pc_width - 1); + self.emit(":0] pc_reg;"); + self.emit_line(""); + self.emit("reg ["); + self.emit_int(self.pc_width - 1); + self.emit(":0] pc_next;"); + self.emit_line(""); + self.emit("reg ["); + self.emit_int(self.data_width - 1); + self.emit(":0] instruction;"); + self.emit_line(""); + self.emit_line("// Opcode decoding"); + self.emit_line("wire [3:0] opcode;"); + self.emit_line(""); + self.emit_line("// Operand fields"); + self.emit_line("wire [4:0] dst_reg;"); + self.emit_line("wire [4:0] src1_reg;"); + self.emit_line("wire [4:0] src2_reg;"); + self.emit("wire ["); + self.emit_int(self.data_width - 1); + self.emit(":0] immediate;"); + self.emit_line(""); + self.emit_line("// Control signals"); + self.emit_line("reg [1:0] state; // 0=fetch, 1=decode, 2=execute"); + self.emit_line("wire [1:0] next_state;"); + self.emit_line(""); + self.emit_line("// ALU signals"); + self.emit("wire ["); + self.emit_int(self.data_width - 1); + self.emit(":0] alu_result;"); + self.emit_line("wire zero_flag;"); + self.emit_line(""); + self.emit_line("// Register file signals"); + self.emit("reg ["); + self.emit_int(self.data_width - 1); + self.emit(":0] reg_file [0:26];"); + self.emit("wire ["); + self.emit_int(self.data_width - 1); + self.emit(":0] r1;"); + self.emit("wire ["); + self.emit_int(self.data_width - 1); + self.emit(":0] r2;"); + self.emit_line(""); + self.emit_line("// Memory signals"); + self.emit("reg ["); + self.emit_int(self.addr_width - 1); + self.emit(":0] mem_addr_reg;"); + self.emit_line("reg mem_we_reg;"); + self.emit("reg ["); + self.emit_int(self.data_width - 1); + self.emit(":0] mem_wdata_reg;"); + self.emit_line(""); + self.emit_line("// Halt signal"); + self.emit_line("reg halted_reg;"); + self.emit_line(""); + } + + // Emit instruction ROM + fn emit_instruction_rom(self: *VerilogCodegen) void { + self.emit_line("// Instruction ROM"); + self.emit("reg ["); + self.emit_int(self.data_width - 1); + self.emit(":0] instruction_rom [0:"); + self.emit_int(self.ast.code_section.instructions.len - 1); + self.emit_line("];"); + self.emit_line(""); + self.emit_line("initial begin"); + self.indent(); + + for (self.ast.code_section.instructions, 0..) |inst, i| { + const encoded = self.encode_instruction(inst); + self.emit(" instruction_rom["); + self.emit_int(@intCast(i)); + self.emit("] = 32'h"); + + // Format as 8-digit hex + const hex = format_hex(encoded, 8); + self.emit(hex); + self.emit("; // "); + self.emit(self.disassemble(inst)); + self.emit_line(""); + } + + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + } + + // Encode instruction to 32-bit word + fn encode_instruction(self: *VerilogCodegen, inst: Instruction) u32 { + var encoded: u32 = 0; + + // Opcode in bits 31:28 + encoded |= (self.opcode_to_bits(inst.opcode) << 28); + + // Operands + if (inst.operands.len >= 1) { + encoded |= self.operand_to_bits(inst.operands[0], 0); + } + if (inst.operands.len >= 2) { + encoded |= self.operand_to_bits(inst.operands[1], 5); + } + if (inst.operands.len >= 3) { + encoded |= self.operand_to_bits(inst.operands[2], 10); + } + + return encoded; + } + + // Convert opcode to 4-bit encoding + fn opcode_to_bits(self: *VerilogCodegen, opcode: Opcode) u32 { + return switch (opcode) { + Opcode.MOV => 0, + Opcode.JZ => 1, + Opcode.JNZ => 2, + Opcode.JMP => 3, + Opcode.MUL => 4, + Opcode.ADD => 5, + Opcode.SUB => 6, + Opcode.BIND => 7, + Opcode.BUNDLE => 8, + Opcode.HALT => 15, + else => 0, + }; + } + + // Convert operand to 5-bit encoding + fn operand_to_bits(self: *VerilogCodegen, op: Operand, shift: u32) u32 { + return switch (op) { + .RegOperand => |r| { + if (r.reg_num <= 26) { + return @as(u32, @intCast(r.reg_num)) << shift; + } + return 0; + }, + .ImmOperand => |i| { + return @as(u32, @intCast(i.value & 0x1F)) << shift; + }, + .LabelOperand => |l| { + // Look up label address + if (self.ast.code_section.labels.get(l.label_name)) |addr| { + return @as(u32, @intCast(addr & 0x1F)) << shift; + } + return 0; + }, + else => 0, + }; + } + + // Disassemble instruction for comment + fn disassemble(self: *VerilogCodegen, inst: Instruction) []const u8 { + var result = self.opcode_to_mnemonic(inst.opcode); + + for (inst.operands) |op| { + result = result ++ " " ++ self.operand_to_string(op); + } + + return result; + } + + // Convert opcode to mnemonic + fn opcode_to_mnemonic(self: *VerilogCodegen, opcode: Opcode) []const u8 { + return switch (opcode) { + Opcode.MOV => "mov", + Opcode.JZ => "jz", + Opcode.JNZ => "jnz", + Opcode.JMP => "jmp", + Opcode.MUL => "mul", + Opcode.ADD => "add", + Opcode.SUB => "sub", + Opcode.BIND => "bind", + Opcode.BUNDLE => "bundle", + Opcode.HALT => "halt", + else => "???", + }; + } + + // Convert operand to string + fn operand_to_string(self: *VerilogCodegen, op: Operand) []const u8 { + return switch (op) { + .RegOperand => |r| "r" ++ int_to_str(r.reg_num), + .ImmOperand => |i| "#" ++ int_to_str(i.value), + .LabelOperand => |l| l.label_name, + .MemOperand => |m| "[r" ++ int_to_str(m.base_reg) ++ "]", + else => "?", + }; + } + + // Emit data memory + fn emit_data_memory(self: *VerilogCodegen) void { + self.emit_line("// Data memory interface assignments"); + self.emit_line("assign pc = pc_reg;"); + self.emit_line("assign mem_addr = mem_addr_reg;"); + self.emit_line("assign mem_we = mem_we_reg;"); + self.emit_line("assign mem_wdata = mem_wdata_reg;"); + self.emit_line("assign halted = halted_reg;"); + self.emit_line(""); + } + + // Emit register file + fn emit_register_file(self: *VerilogCodegen) void { + self.emit_line("// Register file read ports (asynchronous)"); + self.emit_line("assign r1 = (dst_reg < 27) ? reg_file[dst_reg] : 32'h0;"); + self.emit_line("assign r2 = (src1_reg < 27) ? reg_file[src1_reg] : 32'h0;"); + self.emit_line(""); + } + + // Emit instruction decode + fn emit_decode(self: *VerilogCodegen) void { + self.emit_line("// Instruction decode"); + self.emit_line("assign opcode = instruction[31:28];"); + self.emit_line("assign dst_reg = instruction[4:0];"); + self.emit_line("assign src1_reg = instruction[9:5];"); + self.emit_line("assign src2_reg = instruction[14:10];"); + self.emit_line("assign immediate = {{27{instruction[14]}}, instruction[14:0]};"); + self.emit_line("assign zero_flag = (r1 == 0);"); + self.emit_line(""); + } + + // Emit ALU + fn emit_alu(self: *VerilogCodegen) void { + self.emit_line("// ALU"); + self.emit_line("assign alu_result ="); + self.emit_line(" (opcode == 4'd5) ? r1 + r2 : // ADD"); + self.emit_line(" (opcode == 4'd6) ? r1 - r2 : // SUB"); + self.emit_line(" (opcode == 4'd4) ? r1 * r2 : // MUL"); + self.emit_line(" immediate; // MOV (default)"); + self.emit_line(""); + } + + // Emit control logic + fn emit_control(self: *VerilogCodegen) void { + self.emit_line("// Next state logic"); + self.emit_line("assign next_state ="); + self.emit_line(" (state == 2'd0) ? 2'd1 : // fetch -> decode"); + self.emit_line(" (state == 2'd1) ? 2'd2 : // decode -> execute"); + self.emit_line(" 2'd0; // execute -> fetch"); + self.emit_line(""); + } + + // 5. FPGA Module Emission + + // Emit FPGA top-level module + fn emit_fpga_top(self: *VerilogCodegen) void { + self.emit_line(""); + self.emit_line("// 212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312"); + self.emit_line("// Top-Level FPGA Wrapper: Trinity_FPGA_Top"); + self.emit_line("// 313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292202932029420295202962029720298202992030020301203022030320304203052030620307203082030920310203112031220313203142031520316203172031820319203202032120322203232032420325203262032720328203292033020331203322033320334203352033620337203382033920340203412034220343203442034520346203472034820349203502035120352203532035420355203562035720358203592036020361203622036320364203652036620367203682036920370203712037220373203742037520376203772037820379203802038120382203832038420385203862038720388203892039020391203922039320394203952039620397203982039920400204012040220403204042040520406204072040820409204102041120412204132041420415204162041720418204192042020421204222042320424204252042620427204282042920430204312043220433204342043520436204372043820439204402044120442204432044420445204462044720448204492045020451204522045320454204552045620457204582045920460204612046220463204642046520466204672046820469204702047120472204732047420475204762047720478204792048020481204822048320484204852048620487204882048920490204912049220493204942049520496204972049820499205002050120502205032050420505205062050720508205092051020511205122051320514205152051620517205182051920520205212052220523205242052520526205272052820529205302053120532205332053420535205362053720538205392054020541205422054320544205452054620547205482054920550205512055220553205542055520556205572055820559205602056120562205632056420565205662056720568205692057020571205722057320574205752057620577205782057920580205812058220583205842058520586205872058820589205902059120592205932059420595205962059720598205992060020601206022060320604206052060620607206082060920610206112061220613206142061520616206172061820619206202062120622206232062420625206262062720628206292063020631206322063320634206352063620637206382063920640206412064220643206442064520646206472064820649206502065120652206532065420655206562065720658206592066020661206622066320664206652066620667206682066920670206712067220673206742067520676206772067820679206802068120682206832068420685206862068720688206892069020691206922069320694206952069620697206982069920700207012070220703207042070520706207072070820709207102071120712207132071420715207162071720718207192072020721207222072320724207252072620727207282072920730207312073220733207342073520736207372073820739207402074120742207432074420745207462074720748207492075020751207522075320754207552075620757207582075920760207612076220763207642076520766207672076820769207702077120772207732077420775207762077720778207792078020781207822078320784207852078620787207882078920790207912079220793207942079520796207972079820799208002080120802208032080420805208062080720808208092081020811208122081320814208152081620817208182081920820208212082220823208242082520826208272082820829208302083120832208332083420835208362083720838208392084020841208422084320844208452084620847208482084920850208512085220853208542085520856208572085820859208602086120862208632086420865208662086720868208692087020871208722087320874208752087620877208782087920880208812088220883208842088520886208872088820889208902089120892208932089420895208962089720898208992090020901209022090320904209052090620907209082090920910209112091220913209142091520916209172091820919209202092120922209232092420925209262092720928209292093020931209322093320934209352093620937209382093920940209412094220943209442094520946209472094820949209502095120952209532095420955209562095720958209592096020961209622096320964209652096620967209682096920970209712097220973209742097520976209772097820979209802098120982209832098420985209862098720988209892099020991209922099320994209952099620997209982099921000210012100221003210042100521006210072100821009210102101121012210132101421015210162101721018210192102021021210222102321024210252102621027210282102921030210312103221033210342103521036210372103821039210402104121042210432104421045210462104721048210492105021051210522105321054210552105621057210582105921060210612106221063210642106521066210672106821069210702107121072210732107421075210762107721078210792108021081210822108321084210852108621087210882108921090210912109221093210942109521096210972109821099211002110121102211032110421105211062110721108211092111021111211122111321114211152111621117211182111921120211212112221123211242112521126211272112821129211302113121132211332113421135211362113721138211392114021141211422114321144211452114621147211482114921150211512115221153211542115521156211572115821159211602116121162211632116421165211662116721168211692117021171211722117321174211752117621177211782117921180211812118221183211842118521186211872118821189211902119121192211932119421195211962119721198211992120021201212022120321204212052120621207212082120921210212112121221213212142121521216212172121821219212202122121222212232122421225212262122721228212292123021231212322123321234212352123621237212382123921240212412124221243212442124521246212472124821249212502125121252212532125421255212562125721258212592126021261212622126321264212652126621267212682126921270212712127221273212742127521276212772127821279212802128121282212832128421285212862128721288212892129021291212922129321294212952129621297212982129921300213012130221303213042130521306213072130821309213102131121312213132131421315213162131721318213192132021321213222132321324213252132621327213282132921330213312133221333213342133521336213372133821339213402134121342213432134421345213462134721348213492135021351213522135321354213552135621357213582135921360213612136221363213642136521366213672136821369213702137121372213732137421375213762137721378213792138021381213822138321384213852138621387213882138921390213912139221393213942139521396213972139821399214002140121402214032140421405214062140721408214092141021411214122141321414214152141621417214182141921420214212142221423214242142521426214272142821429214302143121432214332143421435214362143721438214392144021441214422144321444214452144621447214482144921450214512145221453214542145521456214572145821459214602146121462214632146421465214662146721468214692147021471214722147321474214752147621477214782147921480214812148221483214842148521486214872148821489214902149121492214932149421495214962149721498214992150021501215022150321504215052150621507215082150921510215112151221513215142151521516215172151821519215202152121522215232152421525215262152721528215292153021531215322153321534215352153621537215382153921540215412154221543215442154521546215472154821549215502155121552215532155421555215562155721558215592156021561215622156321564215652156621567215682156921570215712157221573215742157521576215772157821579215802158121582215832158421585215862158721588215892159021591215922159321594215952159621597215982159921600216012160221603216042160521606216072160821609216102161121612216132161421615216162161721618216192162021621216222162321624216252162621627216282162921630216312163221633216342163521636216372163821639216402164121642216432164421645216462164721648216492165021651216522165321654216552165621657216582165921660216612166221663216642166521666216672166821669216702167121672216732167421675216762167721678216792168021681216822168321684216852168621687216882168921690216912169221693216942169521696216972169821699217002170121702217032170421705217062170721708217092171021711217122171321714217152171621717217182171921720217212172221723217242172521726217272172821729217302173121732217332173421735217362173721738217392174021741217422174321744217452174621747217482174921750217512175221753217542175521756217572175821759217602176121762217632176421765217662176721768217692177021771217722177321774217752177621777217782177921780217812178221783217842178521786217872178821789217902179121792217932179421795217962179721798217992180021801218022180321804218052180621807218082180921810218112181221813218142181521816218172181821819218202182121822218232182421825218262182721828218292183021831218322183321834218352183621837218382183921840218412184221843218442184521846218472184821849218502185121852218532185421855218562185721858218592186021861218622186321864218652186621867218682186921870218712187221873218742187521876218772187821879218802188121882218832188421885218862188721888218892189021891218922189321894218952189621897218982189921900219012190221903219042190521906219072190821909219102191121912219132191421915219162191721918219192192021921219222192321924219252192621927219282192921930219312193221933219342193521936219372193821939219402194121942219432194421945219462194721948219492195021951219522195321954219552195621957219582195921960219612196221963219642196521966219672196821969219702197121972219732197421975219762197721978219792198021981219822198321984219852198621987219882198921990219912199221993219942199521996219972199821999220002200122002220032200422005220062200722008220092201022011220122201322014220152201622017220182201922020220212202222023220242202522026220272202822029220302203122032220332203422035220362203722038220392204022041220422204322044220452204622047220482204922050220512205222053220542205522056220572205822059220602206122062220632206422065220662206722068220692207022071220722207322074220752207622077220782207922080220812208222083220842208522086220872208822089220902209122092220932209422095220962209722098220992210022101221022210322104221052210622107221082210922110221112211222113221142211522116221172211822119221202212122122221232212422125221262212722128221292213022131221322213322134221352213622137221382213922140221412214222143221442214522146221472214822149221502215122152221532215422155221562215722158221592216022161221622216322164221652216622167221682216922170221712217222173221742217522176221772217822179221802218122182221832218422185221862218722188221892219022191221922219322194221952219622197221982219922200222012220222203222042220522206222072220822209222102221122212222132221422215222162221722218222192222022221222222222322224222252222622227222282222922230222312223222233222342223522236222372223822239222402224122242222432224422245222462224722248222492225022251222522225322254222552225622257222582225922260222612226222263222642226522266222672226822269222702227122272222732227422275222762227722278222792228022281222822228322284222852228622287222882228922290222912229222293222942229522296222972229822299223002230122302223032230422305223062230722308223092231022311223122231322314223152231622317223182231922320223212232222323223242232522326223272232822329223302233122332223332233422335223362233722338223392234022341223422234322344223452234622347223482234922350223512235222353223542235522356223572235822359223602236122362223632236422365223662236722368223692237022371223722237322374223752237622377223782237922380223812238222383223842238522386223872238822389223902239122392223932239422395223962239722398223992240022401224022240322404224052240622407224082240922410224112241222413224142241522416224172241822419224202242122422224232242422425224262242722428224292243022431224322243322434224352243622437224382243922440224412244222443224442244522446224472244822449224502245122452224532245422455224562245722458224592246022461224622246322464224652246622467224682246922470224712247222473224742247522476224772247822479224802248122482224832248422485224862248722488224892249022491224922249322494224952249622497224982249922500225012250222503225042250522506225072250822509225102251122512225132251422515225162251722518225192252022521225222252322524225252252622527225282252922530225312253222533225342253522536225372253822539225402254122542225432254422545225462254722548225492255022551225522255322554225552255622557225582255922560225612256222563225642256522566225672256822569225702257122572225732257422575225762257722578225792258022581225822258322584225852258622587225882258922590225912259222593225942259522596225972259822599226002260122602226032260422605226062260722608226092261022611226122261322614226152261622617226182261922620226212262222623226242262522626226272262822629226302263122632226332263422635226362263722638226392264022641226422264322644226452264622647226482264922650226512265222653226542265522656226572265822659226602266122662226632266422665226662266722668226692267022671226722267322674226752267622677226782267922680226812268222683226842268522686226872268822689226902269122692226932269422695226962269722698226992270022701227022270322704227052270622707227082270922710227112271222713227142271522716227172271822719227202272122722227232272422725227262272722728227292273022731227322273322734227352273622737227382273922740227412274222743227442274522746227472274822749227502275122752227532275422755227562275722758227592276022761227622276322764227652276622767227682276922770227712277222773227742277522776227772277822779227802278122782227832278422785227862278722788227892279022791227922279322794227952279622797227982279922800228012280222803228042280522806228072280822809228102281122812228132281422815228162281722818228192282022821228222282322824228252282622827228282282922830228312283222833228342283522836228372283822839228402284122842228432284422845228462284722848228492285022851228522285322854228552285622857228582285922860228612286222863228642286522866228672286822869228702287122872228732287422875228762287722878228792288022881228822288322884228852288622887228882288922890228912289222893228942289522896228972289822899229002290122902229032290422905229062290722908229092291022911229122291322914229152291622917229182291922920229212292222923229242292522926229272292822929229302293122932229332293422935229362293722938229392294022941229422294322944229452294622947229482294922950229512295222953229542295522956229572295822959229602296122962229632296422965229662296722968229692297022971229722297322974229752297622977229782297922980229812298222983229842298522986229872298822989229902299122992229932299422995229962299722998229992300023001230022300323004230052300623007230082300923010230112301223013230142301523016230172301823019230202302123022230232302423025230262302723028230292303023031230322303323034230352303623037230382303923040230412304223043230442304523046230472304823049230502305123052230532305423055230562305723058230592306023061230622306323064230652306623067230682306923070230712307223073230742307523076230772307823079230802308123082230832308423085230862308723088230892309023091230922309323094230952309623097230982309923100231012310223103231042310523106231072310823109231102311123112231132311423115231162311723118231192312023121231222312323124231252312623127231282312923130231312313223133231342313523136231372313823139231402314123142231432314423145231462314723148231492315023151231522315323154231552315623157231582315923160231612316223163231642316523166231672316823169231702317123172231732317423175231762317723178231792318023181231822318323184231852318623187231882318923190231912319223193231942319523196231972319823199232002320123202232032320423205232062320723208232092321023211232122321323214232152321623217232182321923220232212322223223232242322523226232272322823229232302323123232232332323423235232362323723238232392324023241232422324323244232452324623247232482324923250232512325223253232542325523256232572325823259232602326123262232632326423265232662326723268232692327023271232722327323274232752327623277232782327923280232812328223283232842328523286232872328823289232902329123292232932329423295232962329723298232992330023301233022330323304233052330623307233082330923310233112331223313233142331523316233172331823319233202332123322233232332423325233262332723328233292333023331233322333323334233352333623337233382333923340233412334223343233442334523346233472334823349233502335123352233532335423355233562335723358233592336023361233622336323364233652336623367233682336923370233712337223373233742337523376233772337823379233802338123382233832338423385233862338723388233892339023391233922339323394233952339623397233982339923400234012340223403234042340523406234072340823409234102341123412234132341423415234162341723418234192342023421234222342323424234252342623427234282342923430234312343223433234342343523436234372343823439234402344123442234432344423445234462344723448234492345023451234522345323454234552345623457234582345923460234612346223463234642346523466234672346823469234702347123472234732347423475234762347723478234792348023481234822348323484234852348623487234882348923490234912349223493234942349523496234972349823499235002350123502235032350423505235062350723508235092351023511235122351323514235152351623517235182351923520235212352223523235242352523526235272352823529235302353123532235332353423535235362353723538235392354023541235422354323544235452354623547235482354923550235512355223553235542355523556235572355823559235602356123562235632356423565235662356723568235692357023571235722357323574235752357623577235782357923580235812358223583235842358523586235872358823589235902359123592235932359423595235962359723598235992360023601236022360323604236052360623607236082360923610236112361223613236142361523616236172361823619236202362123622236232362423625236262362723628236292363023631236322363323634236352363623637236382363923640236412364223643236442364523646236472364823649236502365123652236532365423655236562365723658236592366023661236622366323664236652366623667236682366923670236712367223673236742367523676236772367823679236802368123682236832368423685236862368723688236892369023691236922369323694236952369623697236982369923700237012370223703237042370523706237072370823709237102371123712237132371423715237162371723718237192372023721237222372323724237252372623727237282372923730237312373223733237342373523736237372373823739237402374123742237432374423745237462374723748237492375023751237522375323754237552375623757237582375923760237612376223763237642376523766237672376823769237702377123772237732377423775237762377723778237792378023781237822378323784237852378623787237882378923790237912379223793237942379523796237972379823799238002380123802238032380423805238062380723808238092381023811238122381323814238152381623817238182381923820238212382223823238242382523826238272382823829238302383123832238332383423835238362383723838238392384023841238422384323844238452384623847238482384923850238512385223853238542385523856238572385823859238602386123862238632386423865238662386723868238692387023871238722387323874238752387623877238782387923880238812388223883238842388523886238872388823889238902389123892238932389423895238962389723898238992390023901239022390323904239052390623907239082390923910239112391223913239142391523916239172391823919239202392123922239232392423925239262392723928239292393023931239322393323934239352393623937239382393923940239412394223943239442394523946239472394823949239502395123952239532395423955239562395723958239592396023961239622396323964239652396623967239682396923970239712397223973239742397523976239772397823979239802398123982239832398423985239862398723988239892399023991239922399323994239952399623997239982399924000240012400224003240042400524006240072400824009240102401124012240132401424015240162401724018240192402024021240222402324024240252402624027240282402924030240312403224033240342403524036240372403824039240402404124042240432404424045240462404724048240492405024051240522405324054240552405624057240582405924060240612406224063240642406524066240672406824069240702407124072240732407424075240762407724078240792408024081240822408324084240852408624087240882408924090240912409224093240942409524096240972409824099241002410124102241032410424105241062410724108241092411024111241122411324114241152411624117241182411924120241212412224123241242412524126241272412824129241302413124132241332413424135241362413724138241392414024141241422414324144241452414624147241482414924150241512415224153241542415524156241572415824159241602416124162241632416424165241662416724168241692417024171241722417324174241752417624177241782417924180241812418224183241842418524186241872418824189241902419124192241932419424195241962419724198241992420024201242022420324204242052420624207242082420924210242112421224213242142421524216242172421824219242202422124222242232422424225242262422724228242292423024231242322423324234242352423624237242382423924240242412424224243242442424524246242472424824249242502425124252242532425424255242562425724258242592426024261242622426324264242652426624267242682426924270242712427224273242742427524276242772427824279242802428124282242832428424285242862428724288242892429024291242922429324294242952429624297242982429924300243012430224303243042430524306243072430824309243102431124312243132431424315243162431724318243192432024321243222432324324243252432624327243282432924330243312433224333243342433524336243372433824339243402434124342243432434424345243462434724348243492435024351243522435324354243552435624357243582435924360243612436224363243642436524366243672436824369243702437124372243732437424375243762437724378243792438024381243822438324384243852438624387243882438924390243912439224393243942439524396243972439824399244002440124402244032440424405244062440724408244092441024411244122441324414244152441624417244182441924420244212442224423244242442524426244272442824429244302443124432244332443424435244362443724438244392444024441244422444324444244452444624447244482444924450244512445224453244542445524456244572445824459244602446124462244632446424465244662446724468244692447024471244722447324474244752447624477244782447924480244812448224483244842448524486244872448824489244902449124492244932449424495244962449724498244992450024501245022450324504245052450624507245082450924510245112451224513245142451524516245172451824519245202452124522245232452424525245262452724528245292453024531245322453324534245352453624537245382453924540245412454224543245442454524546245472454824549245502455124552245532455424555245562455724558245592456024561245622456324564245652456624567245682456924570245712457224573245742457524576245772457824579245802458124582245832458424585245862458724588245892459024591245922459324594245952459624597245982459924600246012460224603246042460524606246072460824609246102461124612246132461424615246162461724618246192462024621246222462324624246252462624627246282462924630246312463224633246342463524636246372463824639246402464124642246432464424645246462464724648246492465024651246522465324654246552465624657246582465924660246612466224663246642466524666246672466824669246702467124672246732467424675246762467724678246792468024681246822468324684246852468624687246882468924690246912469224693246942469524696246972469824699247002470124702247032470424705247062470724708247092471024711247122471324714247152471624717247182471924720247212472224723247242472524726247272472824729247302473124732247332473424735247362473724738247392474024741247422474324744247452474624747247482474924750247512475224753247542475524756247572475824759247602476124762247632476424765247662476724768247692477024771247722477324774247752477624777247782477924780247812478224783247842478524786247872478824789247902479124792247932479424795247962479724798247992480024801248022480324804248052480624807248082480924810248112481224813248142481524816248172481824819248202482124822248232482424825248262482724828248292483024831248322483324834248352483624837248382483924840248412484224843248442484524846248472484824849248502485124852248532485424855248562485724858248592486024861248622486324864248652486624867248682486924870248712487224873248742487524876248772487824879248802488124882248832488424885248862488724888248892489024891248922489324894248952489624897248982489924900249012490224903249042490524906249072490824909249102491124912249132491424915249162491724918249192492024921249222492324924249252492624927249282492924930249312493224933249342493524936249372493824939249402494124942249432494424945249462494724948249492495024951249522495324954249552495624957249582495924960249612496224963249642496524966249672496824969249702497124972249732497424975249762497724978249792498024981249822498324984249852498624987249882498924990249912499224993249942499524996249972499824999250002500125002250032500425005250062500725008250092501025011250122501325014250152501625017250182501925020250212502225023250242502525026250272502825029250302503125032250332503425035250362503725038250392504025041250422504325044250452504625047250482504925050250512505225053250542505525056250572505825059250602506125062250632506425065250662506725068250692507025071250722507325074250752507625077250782507925080250812508225083250842508525086250872508825089250902509125092250932509425095250962509725098250992510025101251022510325104251052510625107251082510925110251112511225113251142511525116251172511825119251202512125122251232512425125251262512725128251292513025131251322513325134251352513625137251382513925140251412514225143251442514525146251472514825149251502515125152251532515425155251562515725158251592516025161251622516325164251652516625167251682516925170251712517225173251742517525176251772517825179251802518125182251832518425185251862518725188251892519025191251922519325194251952519625197251982519925200252012520225203252042520525206252072520825209252102521125212252132521425215252162521725218252192522025221252222522325224252252522625227252282522925230252312523225233252342523525236252372523825239252402524125242252432524425245252462524725248252492525025251252522525325254252552525625257252582525925260252612526225263252642526525266252672526825269252702527125272252732527425275252762527725278252792528025281252822528325284252852528625287252882528925290252912529225293252942529525296252972529825299253002530125302253032530425305253062530725308253092531025311253122531325314253152531625317253182531925320253212532225323253242532525326253272532825329253302533125332253332533425335253362533725338253392534025341253422534325344253452534625347253482534925350253512535225353253542535525356253572535825359253602536125362253632536425365253662536725368253692537025371253722537325374253752537625377253782537925380253812538225383253842538525386253872538825389253902539125392253932539425395253962539725398253992540025401254022540325404254052540625407254082540925410254112541225413254142541525416254172541825419254202542125422254232542425425254262542725428254292543025431254322543325434254352543625437254382543925440254412544225443254442544525446254472544825449254502545125452254532545425455254562545725458254592546025461254622546325464254652546625467254682546925470254712547225473254742547525476254772547825479254802548125482254832548425485254862548725488254892549025491254922549325494254952549625497254982549925500255012550225503255042550525506255072550825509255102551125512255132551425515255162551725518255192552025521255222552325524255252552625527255282552925530255312553225533255342553525536255372553825539255402554125542255432554425545255462554725548255492555025551255522555325554255552555625557255582555925560255612556225563255642556525566255672556825569255702557125572255732557425575255762557725578255792558025581255822558325584255852558625587255882558925590255912559225593255942559525596255972559825599256002560125602256032560425605256062560725608256092561025611256122561325614256152561625617256182561925620256212562225623256242562525626256272562825629256302563125632256332563425635256362563725638256392564025641256422564325644256452564625647256482564925650256512565225653256542565525656256572565825659256602566125662256632566425665256662566725668256692567025671256722567325674256752567625677256782567925680256812568225683256842568525686256872568825689256902569125692256932569425695256962569725698256992570025701257022570325704257052570625707257082570925710257112571225713257142571525716257172571825719257202572125722257232572425725257262572725728257292573025731257322573325734257352573625737257382573925740257412574225743257442574525746257472574825749257502575125752257532575425755257562575725758257592576025761257622576325764257652576625767257682576925770257712577225773257742577525776257772577825779257802578125782257832578425785257862578725788257892579025791257922579325794257952579625797257982579925800258012580225803258042580525806258072580825809258102581125812258132581425815258162581725818258192582025821258222582325824258252582625827258282582925830258312583225833258342583525836258372583825839258402584125842258432584425845258462584725848258492585025851258522585325854258552585625857258582585925860258612586225863258642586525866258672586825869258702587125872258732587425875258762587725878258792588025881258822588325884258852588625887258882588925890258912589225893258942589525896258972589825899259002590125902259032590425905259062590725908259092591025911259122591325914259152591625917259182591925920259212592225923259242592525926259272592825929259302593125932259332593425935259362593725938259392594025941259422594325944259452594625947259482594925950259512595225953259542595525956259572595825959259602596125962259632596425965259662596725968259692597025971259722597325974259752597625977259782597925980259812598225983259842598525986259872598825989259902599125992259932599425995259962599725998259992600026001260022600326004260052600626007260082600926010260112601226013260142601526016260172601826019260202602126022260232602426025260262602726028260292603026031260322603326034260352603626037260382603926040260412604226043260442604526046260472604826049260502605126052260532605426055260562605726058260592606026061260622606326064260652606626067260682606926070260712607226073260742607526076260772607826079260802608126082260832608426085260862608726088260892609026091260922609326094260952609626097260982609926100261012610226103261042610526106261072610826109261102611126112261132611426115261162611726118261192612026121261222612326124261252612626127261282612926130261312613226133261342613526136261372613826139261402614126142261432614426145261462614726148261492615026151261522615326154261552615626157261582615926160261612616226163261642616526166261672616826169261702617126172261732617426175261762617726178261792618026181261822618326184261852618626187261882618926190261912619226193261942619526196261972619826199262002620126202262032620426205262062620726208262092621026211262122621326214262152621626217262182621926220262212622226223262242622526226262272622826229262302623126232262332623426235262362623726238262392624026241262422624326244262452624626247262482624926250262512625226253262542625526256262572625826259262602626126262262632626426265262662626726268262692627026271262722627326274262752627626277262782627926280262812628226283262842628526286262872628826289262902629126292262932629426295262962629726298262992630026301263022630326304263052630626307263082630926310263112631226313263142631526316263172631826319263202632126322263232632426325263262632726328263292633026331263322633326334263352633626337263382633926340263412634226343263442634526346263472634826349263502635126352263532635426355263562635726358263592636026361263622636326364263652636626367263682636926370263712637226373263742637526376263772637826379263802638126382263832638426385263862638726388263892639026391263922639326394263952639626397263982639926400264012640226403264042640526406264072640826409264102641126412264132641426415264162641726418264192642026421264222642326424264252642626427264282642926430264312643226433264342643526436264372643826439264402644126442264432644426445264462644726448264492645026451264522645326454264552645626457264582645926460264612646226463264642646526466264672646826469264702647126472264732647426475264762647726478264792648026481264822648326484264852648626487264882648926490264912649226493264942649526496264972649826499265002650126502265032650426505265062650726508265092651026511265122651326514265152651626517265182651926520265212652226523265242652526526265272652826529265302653126532265332653426535265362653726538265392654026541265422654326544265452654626547265482654926550265512655226553265542655526556265572655826559265602656126562265632656426565265662656726568265692657026571265722657326574265752657626577265782657926580265812658226583265842658526586265872658826589265902659126592265932659426595265962659726598265992660026601266022660326604266052660626607266082660926610266112661226613266142661526616266172661826619266202662126622266232662426625266262662726628266292663026631266322663326634266352663626637266382663926640266412664226643266442664526646266472664826649266502665126652266532665426655266562665726658266592666026661266622666326664266652666626667266682666926670266712667226673266742667526676266772667826679266802668126682266832668426685266862668726688266892669026691266922669326694266952669626697266982669926700267012670226703267042670526706267072670826709267102671126712267132671426715267162671726718267192672026721267222672326724267252672626727267282672926730267312673226733267342673526736267372673826739267402674126742267432674426745267462674726748267492675026751267522675326754267552675626757267582675926760267612676226763267642676526766267672676826769267702677126772267732677426775267762677726778267792678026781267822678326784267852678626787267882678926790267912679226793267942679526796267972679826799268002680126802268032680426805268062680726808268092681026811268122681326814268152681626817268182681926820268212682226823268242682526826268272682826829268302683126832268332683426835268362683726838268392684026841268422684326844268452684626847268482684926850268512685226853268542685526856268572685826859268602686126862268632686426865268662686726868268692687026871268722687326874268752687626877268782687926880268812688226883268842688526886268872688826889268902689126892268932689426895268962689726898268992690026901269022690326904269052690626907269082690926910269112691226913269142691526916269172691826919269202692126922269232692426925269262692726928269292693026931269322693326934269352693626937269382693926940269412694226943269442694526946269472694826949269502695126952269532695426955269562695726958269592696026961269622696326964269652696626967269682696926970269712697226973269742697526976269772697826979269802698126982269832698426985269862698726988269892699026991269922699326994269952699626997269982699927000270012700227003270042700527006270072700827009270102701127012270132701427015270162701727018270192702027021270222702327024270252702627027270282702927030270312703227033270342703527036270372703827039270402704127042270432704427045270462704727048270492705027051270522705327054270552705627057270582705927060270612706227063270642706527066270672706827069270702707127072270732707427075270762707727078270792708027081270822708327084270852708627087270882708927090270912709227093270942709527096270972709827099271002710127102271032710427105271062710727108271092711027111271122711327114271152711627117271182711927120271212712227123271242712527126271272712827129271302713127132271332713427135271362713727138271392714027141271422714327144271452714627147271482714927150271512715227153271542715527156271572715827159271602716127162271632716427165271662716727168271692717027171271722717327174271752717627177271782717927180271812718227183271842718527186271872718827189271902719127192271932719427195271962719727198271992720027201272022720327204272052720627207272082720927210272112721227213272142721527216272172721827219272202722127222272232722427225272262722727228272292723027231272322723327234272352723627237272382723927240272412724227243272442724527246272472724827249272502725127252272532725427255272562725727258272592726027261272622726327264272652726627267272682726927270272712727227273272742727527276272772727827279272802728127282272832728427285272862728727288272892729027291272922729327294272952729627297272982729927300273012730227303273042730527306273072730827309273102731127312273132731427315273162731727318273192732027321273222732327324273252732627327273282732927330273312733227333273342733527336273372733827339273402734127342273432734427345273462734727348273492735027351273522735327354273552735627357273582735927360273612736227363273642736527366273672736827369273702737127372273732737427375273762737727378273792738027381273822738327384273852738627387273882738927390273912739227393273942739527396273972739827399274002740127402274032740427405274062740727408274092741027411274122741327414274152741627417274182741927420274212742227423274242742527426274272742827429274302743127432274332743427435274362743727438274392744027441274422744327444274452744627447274482744927450274512745227453274542745527456274572745827459274602746127462274632746427465274662746727468274692747027471274722747327474274752747627477274782747927480274812748227483274842748527486274872748827489274902749127492274932749427495274962749727498274992750027501275022750327504275052750627507275082750927510275112751227513275142751527516275172751827519275202752127522275232752427525275262752727528275292753027531275322753327534275352753627537275382753927540275412754227543275442754527546275472754827549275502755127552275532755427555275562755727558275592756027561275622756327564275652756627567275682756927570275712757227573275742757527576275772757827579275802758127582275832758427585275862758727588275892759027591275922759327594275952759627597275982759927600276012760227603276042760527606276072760827609276102761127612276132761427615276162761727618276192762027621276222762327624276252762627627276282762927630276312763227633276342763527636276372763827639276402764127642276432764427645276462764727648276492765027651276522765327654276552765627657276582765927660276612766227663276642766527666276672766827669276702767127672276732767427675276762767727678276792768027681276822768327684276852768627687276882768927690276912769227693276942769527696276972769827699277002770127702277032770427705277062770727708277092771027711277122771327714277152771627717277182771927720277212772227723277242772527726277272772827729277302773127732277332773427735277362773727738277392774027741277422774327744277452774627747277482774927750277512775227753277542775527756277572775827759277602776127762277632776427765277662776727768277692777027771277722777327774277752777627777277782777927780277812778227783277842778527786277872778827789277902779127792277932779427795277962779727798277992780027801278022780327804278052780627807278082780927810278112781227813278142781527816278172781827819278202782127822278232782427825278262782727828278292783027831278322783327834278352783627837278382783927840278412784227843278442784527846278472784827849278502785127852278532785427855278562785727858278592786027861278622786327864278652786627867278682786927870278712787227873278742787527876278772787827879278802788127882278832788427885278862788727888278892789027891278922789327894278952789627897278982789927900279012790227903279042790527906279072790827909279102791127912279132791427915279162791727918279192792027921279222792327924279252792627927279282792927930279312793227933279342793527936279372793827939279402794127942279432794427945279462794727948279492795027951279522795327954279552795627957279582795927960279612796227963279642796527966279672796827969279702797127972279732797427975279762797727978279792798027981279822798327984279852798627987279882798927990279912799227993279942799527996279972799827999280002800128002280032800428005280062800728008280092801028011280122801328014280152801628017280182801928020280212802228023280242802528026280272802828029280302803128032280332803428035280362803728038280392804028041280422804328044280452804628047280482804928050280512805228053280542805528056280572805828059280602806128062280632806428065280662806728068280692807028071280722807328074280752807628077280782807928080280812808228083280842808528086280872808828089280902809128092280932809428095280962809728098280992810028101281022810314 // TODO: Implement FPGA module emission + 4015 // if (self.options.include_toplevel) { + 4016 // self.emit_fpga_top(); + 4017 // } + + // 5. FPGA Module Emission + // 282212822228223282242822528226282272822828229282302823128232282332823428235282362823728238282392824028241282422824328244282452824628247282482824928250282512825228253282542825528256282572825828259282602826128262282632826428265282662826728268282692827028271282722827328274282752827628277282782827928280282812828228283282842828528286282872828828289282902829128292282932829428295282962829728298282992830028301283022830328304283052830628307283082830928310283112831228313283142831528316283172831828319283202832128322283232832428325283262832728328283292833028331283322833328334283352833628337283382833928340283412834228343283442834528346283472834828349283502835128352283532835428355283562835728358283592836028361283622836328364283652836628367283682836928370283712837228373283742837528376283772837828379283802838128382283832838428385283862838728388283892839028391283922839328394283952839628397283982839928400284012840228403284042840528406284072840828409284102841128412284132841428415284162841728418284192842028421284222842328424284252842628427284282842928430284312843228433284342843528436284372843828439284402844128442284432844428445284462844728448284492845028451284522845328454284552845628457284582845928460284612846228463284642846528466284672846828469284702847128472284732847428475284762847728478284792848028481284822848328484284852848628487284882848928490284912849228493284942849528496284972849828499285002850128502285032850428505285062850728508285092851028511285122851328514285152851628517285182851928520285212852228523285242852528526285272852828529285302853128532285332853428535285362853728538285392854028541285422854328544285452854628547285482854928550285512855228553285542855528556285572855828559285602856128562285632856428565285662856728568285692857028571285722857328574285752857628577285782857928580285812858228583285842858528586285872858828589285902859128592285932859428595285962859728598285992860028601286022860328604286052860628607286082860928610286112861228613286142861528616286172861828619286202862128622286232862428625286262862728628286292863028631286322863328634286352863628637286382863928640286412864228643286442864528646286472864828649286502865128652286532865428655286562865728658286592866028661286622866328664286652866628667286682866928670286712867228673286742867528676286772867828679286802868128682286832868428685286862868728688286892869028691286922869328694286952869628697286982869928700287012870228703287042870528706287072870828709287102871128712287132871428715287162871728718287192872028721287222872328724287252872628727287282872928730287312873228733287342873528736287372873828739287402874128742287432874428745287462874728748287492875028751287522875328754287552875628757287582875928760287612876228763287642876528766287672876828769287702877128772287732877428775287762877728778287792878028781287822878328784287852878628787287882878928790287912879228793287942879528796287972879828799288002880128802288032880428805288062880728808288092881028811288122881328814288152881628817288182881928820288212882228823288242882528826288272882828829288302883128832288332883428835288362883728838288392884028841288422884328844288452884628847288482884928850288512885228853288542885528856288572885828859288602886128862288632886428865288662886728868288692887028871288722887328874288752887628877288782887928880288812888228883288842888528886288872888828889288902889128892288932889428895288962889728898288992890028901289022890328904289052890628907289082890928910289112891228913289142891528916289172891828919289202892128922289232892428925289262892728928289292893028931289322893328934289352893628937289382893928940289412894228943289442894528946289472894828949289502895128952289532895428955289562895728958289592896028961289622896328964289652896628967289682896928970289712897228973289742897528976289772897828979289802898128982289832898428985289862898728988289892899028991289922899328994289952899628997289982899929000290012900229003290042900529006290072900829009290102901129012290132901429015290162901729018290192902029021290222902329024290252902629027290282902929030290312903229033290342903529036290372903829039290402904129042290432904429045290462904729048290492905029051290522905329054290552905629057290582905929060290612906229063290642906529066290672906829069290702907129072290732907429075290762907729078290792908029081290822908329084290852908629087290882908929090290912909229093290942909529096290972909829099291002910129102291032910429105291062910729108291092911029111291122911329114291152911629117291182911929120291212912229123291242912529126291272912829129291302913129132291332913429135291362913729138291392914029141291422914329144291452914629147291482914929150291512915229153291542915529156291572915829159291602916129162291632916429165291662916729168291692917029171291722917329174291752917629177291782917929180291812918229183291842918529186291872918829189291902919129192291932919429195291962919729198291992920029201292022920329204292052920629207292082920929210292112921229213292142921529216292172921829219292202922129222292232922429225292262922729228292292923029231292322923329234292352923629237292382923929240292412924229243292442924529246292472924829249292502925129252292532925429255292562925729258292592926029261292622926329264292652926629267292682926929270292712927229273292742927529276292772927829279292802928129282292832928429285292862928729288292892929029291292922929329294292952929629297292982929929300293012930229303293042930529306293072930829309293102931129312293132931429315293162931729318293192932029321293222932329324293252932629327293282932929330293312933229333293342933529336293372933829339293402934129342293432934429345293462934729348293492935029351293522935329354293552935629357293582935929360293612936229363293642936529366293672936829369293702937129372293732937429375293762937729378293792938029381293822938329384293852938629387293882938929390293912939229393293942939529396293972939829399294002940129402294032940429405294062940729408294092941029411294122941329414294152941629417294182941929420294212942229423294242942529426294272942829429294302943129432294332943429435294362943729438294392944029441294422944329444294452944629447294482944929450294512945229453294542945529456294572945829459294602946129462294632946429465294662946729468294692947029471294722947329474294752947629477294782947929480294812948229483294842948529486294872948829489294902949129492294932949429495294962949729498294992950029501295022950329504295052950629507295082950929510295112951229513295142951529516295172951829519295202952129522295232952429525295262952729528295292953029531295322953329534295352953629537295382953929540295412954229543295442954529546295472954829549295502955129552295532955429555295562955729558295592956029561295622956329564295652956629567295682956929570295712957229573295742957529576295772957829579295802958129582295832958429585295862958729588295892959029591295922959329594295952959629597295982959929600296012960229603296042960529606296072960829609296102961129612296132961429615296162961729618296192962029621296222962329624296252962629627296282962929630296312963229633296342963529636296372963829639296402964129642296432964429645296462964729648296492965029651296522965329654296552965629657296582965929660296612966229663296642966529666296672966829669296702967129672296732967429675296762967729678296792968029681296822968329684296852968629687296882968929690296912969229693296942969529696296972969829699297002970129702297032970429705297062970729708297092971029711297122971329714297152971629717297182971929720297212972229723297242972529726297272972829729297302973129732297332973429735297362973729738297392974029741297422974329744297452974629747297482974929750297512975229753297542975529756297572975829759297602976129762297632976429765297662976729768297692977029771297722977329774297752977629777297782977929780297812978229783297842978529786297872978829789297902979129792297932979429795297962979729798297992980029801298022980329804298052980629807298082980929810298112981229813298142981529816298172981829819298202982129822298232982429825298262982729828298292983029831298322983329834298352983629837298382983929840298412984229843298442984529846298472984829849298502985129852298532985429855298562985729858298592986029861298622986329864298652986629867298682986929870298712987229873298742987529876298772987829879298802988129882298832988429885298862988729888298892989029891298922989329894298952989629897298982989929900299012990229903299042990529906299072990829909299102991129912299132991429915299162991729918299192992029921299222992329924299252992629927299282992929930299312993229933299342993529936299372993829939299402994129942299432994429945299462994729948299492995029951299522995329954299552995629957299582995929960299612996229963299642996529966299672996829969299702997129972299732997429975299762997729978299792998029981299822998329984299852998629987299882998929990299912999229993299942999529996299972999829999300003000130002300033000430005300063000730008300093001030011300123001330014300153001630017300183001930020300213002230023300243002530026300273002830029300303003130032300333003430035300363003730038300393004030041300423004330044300453004630047300483004930050300513005230053300543005530056300573005830059300603006130062300633006430065300663006730068300693007030071300723007330074300753007630077300783007930080300813008230083300843008530086300873008830089300903009130092300933009430095300963009730098300993010030101301023010330104301053010630107301083010930110301113011230113301143011530116301173011830119301203012130122301233012430125301263012730128301293013030131301323013330134301353013630137301383013930140301413014230143301443014530146301473014830149301503015130152301533015430155301563015730158301593016030161301623016330164301653016630167301683016930170301713017230173301743017530176301773017830179301803018130182301833018430185301863018730188301893019030191301923019330194301953019630197301983019930200302013020230203302043020530206302073020830209302103021130212302133021430215302163021730218302193022030221302223022330224302253022630227302283022930230302313023230233302343023530236302373023830239302403024130242302433024430245302463024730248302493025030251302523025330254302553025630257302583025930260302613026230263302643026530266302673026830269302703027130272302733027430275302763027730278302793028030281302823028330284302853028630287302883028930290302913029230293302943029530296302973029830299303003030130302303033030430305303063030730308303093031030311303123031330314303153031630317303183031930320303213032230323303243032530326303273032830329303303033130332303333033430335303363033730338303393034030341303423034330344303453034630347303483034930350303513035230353303543035530356303573035830359303603036130362303633036430365303663036730368303693037030371303723037330374303753037630377303783037930380303813038230383303843038530386303873038830389303903039130392303933039430395303963039730398303993040030401304023040330404304053040630407304083040930410304113041230413304143041530416304173041830419304203042130422304233042430425304263042730428304293043030431304323043330434304353043630437304383043930440304413044230443304443044530446304473044830449304503045130452304533045430455304563045730458304593046030461304623046330464304653046630467304683046930470304713047230473304743047530476304773047830479304803048130482304833048430485304863048730488304893049030491304923049330494304953049630497304983049930500305013050230503305043050530506305073050830509305103051130512305133051430515305163051730518305193052030521305223052330524305253052630527305283052930530305313053230533305343053530536305373053830539305403054130542305433054430545305463054730548305493055030551305523055330554305553055630557305583055930560305613056230563305643056530566305673056830569305703057130572305733057430575305763057730578305793058030581305823058330584305853058630587305883058930590305913059230593305943059530596305973059830599306003060130602306033060430605306063060730608306093061030611306123061330614306153061630617306183061930620306213062230623306243062530626306273062830629306303063130632306333063430635306363063730638306393064030641306423064330644306453064630647306483064930650306513065230653306543065530656306573065830659306603066130662306633066430665306663066730668306693067030671306723067330674306753067630677306783067930680306813068230683306843068530686306873068830689306903069130692306933069430695306963069730698306993070030701307023070330704307053070630707307083070930710307113071230713307143071530716307173071830719307203072130722307233072430725307263072730728307293073030731307323073330734307353073630737307383073930740307413074230743307443074530746307473074830749307503075130752307533075430755307563075730758307593076030761307623076330764307653076630767307683076930770307713077230773307743077530776307773077830779307803078130782307833078430785307863078730788307893079030791307923079330794307953079630797307983079930800308013080230803308043080530806308073080830809308103081130812308133081430815308163081730818308193082030821308223082330824308253082630827308283082930830308313083230833308343083530836308373083830839308403084130842308433084430845308463084730848308493085030851308523085330854308553085630857308583085930860308613086230863308643086530866308673086830869308703087130872308733087430875308763087730878308793088030881308823088330884308853088630887308883088930890308913089230893308943089530896308973089830899309003090130902309033090430905309063090730908309093091030911309123091330914309153091630917309183091930920309213092230923309243092530926309273092830929309303093130932309333093430935309363093730938309393094030941309423094330944309453094630947309483094930950309513095230953309543095530956309573095830959309603096130962309633096430965309663096730968309693097030971309723097330974309753097630977309783097930980309813098230983309843098530986309873098830989309903099130992309933099430995309963099730998309993100031001310023100331004310053100631007310083100931010310113101231013310143101531016310173101831019310203102131022310233102431025310263102731028310293103031031310323103331034310353103631037310383103931040310413104231043310443104531046310473104831049310503105131052310533105431055310563105731058310593106031061310623106331064310653106631067310683106931070310713107231073310743107531076310773107831079310803108131082310833108431085310863108731088310893109031091310923109331094310953109631097310983109931100311013110231103311043110531106311073110831109311103111131112311133111431115311163111731118311193112031121311223112331124311253112631127311283112931130311313113231133311343113531136311373113831139311403114131142311433114431145311463114731148311493115031151311523115331154311553115631157311583115931160311613116231163311643116531166311673116831169311703117131172311733117431175311763117731178311793118031181311823118331184311853118631187311883118931190311913119231193311943119531196311973119831199312003120131202312033120431205312063120731208312093121031211312123121331214312153121631217312183121931220312213122231223312243122531226312273122831229312303123131232312333123431235312363123731238312393124031241312423124331244312453124631247312483124931250312513125231253312543125531256312573125831259312603126131262312633126431265312663126731268312693127031271312723127331274312753127631277312783127931280312813128231283312843128531286312873128831289312903129131292312933129431295312963129731298312993130031301313023130331304313053130631307313083130931310313113131231313313143131531316313173131831319313203132131322313233132431325313263132731328313293133031331313323133331334313353133631337313383133931340313413134231343313443134531346313473134831349313503135131352313533135431355313563135731358313593136031361313623136331364313653136631367313683136931370313713137231373313743137531376313773137831379313803138131382313833138431385313863138731388313893139031391313923139331394313953139631397313983139931400314013140231403314043140531406314073140831409314103141131412314133141431415314163141731418314193142031421314223142331424314253142631427314283142931430314313143231433314343143531436314373143831439314403144131442314433144431445314463144731448314493145031451314523145331454314553145631457314583145931460314613146231463314643146531466314673146831469314703147131472314733147431475314763147731478314793148031481314823148331484314853148631487314883148931490314913149231493314943149531496314973149831499315003150131502315033150431505315063150731508315093151031511315123151331514315153151631517315183151931520315213152231523315243152531526315273152831529315303153131532315333153431535315363153731538315393154031541315423154331544315453154631547315483154931550315513155231553315543155531556315573155831559315603156131562315633156431565315663156731568315693157031571315723157331574315753157631577315783157931580315813158231583315843158531586315873158831589315903159131592315933159431595315963159731598315993160031601316023160331604316053160631607316083160931610316113161231613316143161531616316173161831619316203162131622316233162431625316263162731628316293163031631316323163331634316353163631637316383163931640316413164231643316443164531646316473164831649316503165131652316533165431655316563165731658316593166031661316623166331664316653166631667316683166931670316713167231673316743167531676316773167831679316803168131682316833168431685316863168731688316893169031691316923169331694316953169631697316983169931700317013170231703317043170531706317073170831709317103171131712317133171431715317163171731718317193172031721317223172331724317253172631727317283172931730317313173231733317343173531736317373173831739317403174131742317433174431745317463174731748317493175031751317523175331754317553175631757317583175931760317613176231763317643176531766317673176831769317703177131772317733177431775317763177731778317793178031781317823178331784317853178631787317883178931790317913179231793317943179531796317973179831799318003180131802318033180431805318063180731808318093181031811318123181331814318153181631817318183181931820318213182231823318243182531826318273182831829318303183131832318333183431835318363183731838318393184031841318423184331844318453184631847318483184931850318513185231853318543185531856318573185831859318603186131862318633186431865318663186731868318693187031871318723187331874318753187631877318783187931880318813188231883318843188531886318873188831889318903189131892318933189431895318963189731898318993190031901319023190331904319053190631907319083190931910319113191231913319143191531916319173191831919319203192131922319233192431925319263192731928319293193031931319323193331934319353193631937319383193931940319413194231943319443194531946319473194831949319503195131952319533195431955319563195731958319593196031961319623196331964319653196631967319683196931970319713197231973319743197531976319773197831979319803198131982319833198431985319863198731988319893199031991319923199331994319953199631997319983199932000320013200232003320043200532006320073200832009320103201132012320133201432015320163201732018320193202032021320223202332024320253202632027320283202932030320313203232033320343203532036320373203832039320403204132042320433204432045320463204732048320493205032051320523205332054320553205632057320583205932060320613206232063320643206532066320673206832069320703207132072320733207432075320763207732078320793208032081320823208332084320853208632087320883208932090320913209232093320943209532096320973209832099321003210132102321033210432105321063210732108321093211032111321123211332114321153211632117321183211932120321213212232123321243212532126321273212832129321303213132132321333213432135321363213732138321393214032141321423214332144321453214632147321483214932150321513215232153321543215532156321573215832159321603216132162321633216432165321663216732168321693217032171321723217332174321753217632177321783217932180321813218232183321843218532186321873218832189321903219132192321933219432195321963219732198321993220032201322023220332204322053220632207322083220932210322113221232213322143221532216322173221832219322203222132222322233222432225322263222732228322293223032231322323223332234322353223632237322383223932240322413224232243322443224532246322473224832249322503225132252322533225432255322563225732258322593226032261322623226332264322653226632267322683226932270322713227232273322743227532276322773227832279322803228132282322833228432285322863228732288322893229032291322923229332294322953229632297322983229932300323013230232303323043230532306323073230832309323103231132312323133231432315323163231732318323193232032321323223232332324323253232632327323283232932330323313233232333323343233532336323373233832339323403234132342323433234432345323463234732348323493235032351323523235332354323553235632357323583235932360323613236232363323643236532366323673236832369323703237132372323733237432375323763237732378323793238032381323823238332384323853238632387323883238932390323913239232393323943239532396323973239832399324003240132402324033240432405324063240732408324093241032411324123241332414324153241632417324183241932420324213242232423324243242532426324273242832429324303243132432324333243432435324363243732438324393244032441324423244332444324453244632447324483244932450324513245232453324543245532456324573245832459324603246132462324633246432465324663246732468324693247032471324723247332474324753247632477324783247932480324813248232483324843248532486324873248832489324903249132492324933249432495324963249732498324993250032501325023250332504325053250632507325083250932510325113251232513325143251532516325173251832519325203252132522325233252432525325263252732528325293253032531325323253332534325353253632537325383253932540325413254232543325443254532546325473254832549325503255132552325533255432555325563255732558325593256032561325623256332564325653256632567325683256932570325713257232573325743257532576325773257832579325803258132582325833258432585325863258732588325893259032591325923259332594325953259632597325983259932600326013260232603326043260532606326073260832609326103261132612326133261432615326163261732618326193262032621326223262332624326253262632627326283262932630326313263232633326343263532636326373263832639326403264132642326433264432645326463264732648326493265032651326523265332654326553265632657326583265932660326613266232663326643266532666326673266832669326703267132672326733267432675326763267732678326793268032681326823268332684326853268632687326883268932690326913269232693326943269532696326973269832699327003270132702327033270432705327063270732708327093271032711327123271332714327153271632717327183271932720327213272232723327243272532726327273272832729327303273132732327333273432735327363273732738327393274032741327423274332744327453274632747327483274932750327513275232753327543275532756327573275832759327603276132762327633276432765327663276732768327693277032771327723277332774327753277632777327783277932780327813278232783327843278532786327873278832789327903279132792327933279432795327963279732798327993280032801328023280332804328053280632807328083280932810328113281232813328143281532816328173281832819328203282132822328233282432825328263282732828328293283032831328323283332834328353283632837328383283932840328413284232843328443284532846328473284832849328503285132852328533285432855328563285732858328593286032861328623286332864328653286632867328683286932870328713287232873328743287532876328773287832879328803288132882328833288432885328863288732888328893289032891328923289332894328953289632897328983289932900329013290232903329043290532906329073290832909329103291132912329133291432915329163291732918329193292032921329223292332924329253292632927329283292932930329313293232933329343293532936329373293832939329403294132942329433294432945329463294732948329493295032951329523295332954329553295632957329583295932960329613296232963329643296532966329673296832969329703297132972329733297432975329763297732978329793298032981329823298332984329853298632987329883298932990329913299232993329943299532996329973299832999330003300133002330033300433005330063300733008330093301033011330123301333014330153301633017330183301933020330213302233023330243302533026330273302833029330303303133032330333303433035330363303733038330393304033041330423304333044330453304633047330483304933050330513305233053330543305533056330573305833059330603306133062330633306433065330663306733068330693307033071330723307333074330753307633077330783307933080330813308233083330843308533086330873308833089330903309133092330933309433095330963309733098330993310033101331023310333104331053310633107331083310933110331113311233113331143311533116331173311833119331203312133122331233312433125331263312733128331293313033131331323313333134331353313633137331383313933140331413314233143331443314533146331473314833149331503315133152331533315433155331563315733158331593316033161331623316333164331653316633167331683316933170331713317233173331743317533176331773317833179331803318133182331833318433185331863318733188331893319033191331923319333194331953319633197331983319933200332013320233203332043320533206332073320833209332103321133212332133321433215332163321733218332193322033221332223322333224332253322633227332283322933230332313323233233332343323533236332373323833239332403324133242332433324433245332463324733248332493325033251332523325333254332553325633257332583325933260332613326233263332643326533266332673326833269332703327133272332733327433275332763327733278332793328033281332823328333284332853328633287332883328933290332913329233293332943329533296332973329833299333003330133302333033330433305333063330733308333093331033311333123331333314333153331633317333183331933320333213332233323333243332533326333273332833329333303333133332333333333433335333363333733338333393334033341333423334333344333453334633347333483334933350333513335233353333543335533356333573335833359333603336133362333633336433365333663336733368333693337033371333723337333374333753337633377333783337933380333813338233383333843338533386333873338833389333903339133392333933339433395333963339733398333993340033401334023340333404334053340633407334083340933410334113341233413334143341533416334173341833419334203342133422334233342433425334263342733428334293343033431334323343333434334353343633437334383343933440334413344233443334443344533446334473344833449334503345133452334533345433455334563345733458334593346033461334623346333464334653346633467334683346933470334713347233473334743347533476334773347833479334803348133482334833348433485334863348733488334893349033491334923349333494334953349633497334983349933500335013350233503335043350533506335073350833509335103351133512335133351433515335163351733518335193352033521335223352333524335253352633527335283352933530335313353233533335343353533536335373353833539335403354133542335433354433545335463354733548335493355033551335523355333554335553355633557335583355933560335613356233563335643356533566335673356833569335703357133572335733357433575335763357733578335793358033581335823358333584335853358633587335883358933590335913359233593335943359533596335973359833599336003360133602336033360433605336063360733608336093361033611336123361333614336153361633617336183361933620336213362233623336243362533626336273362833629336303363133632336333363433635336363363733638336393364033641336423364333644336453364633647336483364933650336513365233653336543365533656336573365833659336603366133662336633366433665336663366733668336693367033671336723367333674336753367633677336783367933680336813368233683336843368533686336873368833689336903369133692336933369433695336963369733698336993370033701337023370333704337053370633707337083370933710337113371233713337143371533716337173371833719337203372133722337233372433725337263372733728337293373033731337323373333734337353373633737337383373933740337413374233743337443374533746337473374833749337503375133752337533375433755337563375733758337593376033761337623376333764337653376633767337683376933770337713377233773337743377533776337773377833779337803378133782337833378433785337863378733788337893379033791337923379333794337953379633797337983379933800338013380233803338043380533806338073380833809338103381133812338133381433815338163381733818338193382033821338223382333824338253382633827338283382933830338313383233833338343383533836338373383833839338403384133842338433384433845338463384733848338493385033851338523385333854338553385633857338583385933860338613386233863338643386533866338673386833869338703387133872338733387433875338763387733878338793388033881338823388333884338853388633887338883388933890338913389233893338943389533896338973389833899339003390133902339033390433905339063390733908339093391033911339123391333914339153391633917339183391933920339213392233923339243392533926339273392833929339303393133932339333393433935339363393733938339393394033941339423394333944339453394633947339483394933950339513395233953339543395533956339573395833959339603396133962339633396433965339663396733968339693397033971339723397333974339753397633977339783397933980339813398233983339843398533986339873398833989339903399133992339933399433995339963399733998339993400034001340023400334004340053400634007340083400934010340113401234013340143401534016340173401834019340203402134022340233402434025340263402734028340293403034031340323403334034340353403634037340383403934040340413404234043340443404534046340473404834049340503405134052340533405434055340563405734058340593406034061340623406334064340653406634067340683406934070340713407234073340743407534076340773407834079340803408134082340833408434085340863408734088340893409034091340923409334094340953409634097340983409934100341013410234103341043410534106341073410834109341103411134112341133411434115341163411734118341193412034121341223412334124341253412634127341283412934130341313413234133341343413534136341373413834139341403414134142341433414434145341463414734148341493415034151341523415334154341553415634157341583415934160341613416234163341643416534166341673416834169341703417134172341733417434175341763417734178341793418034181341823418334184341853418634187341883418934190341913419234193341943419534196341973419834199342003420134202342033420434205342063420734208342093421034211342123421334214342153421634217342183421934220342213422234223342243422534226342273422834229342303423134232342333423434235342363423734238342393424034241342423424334244342453424634247342483424934250342513425234253342543425534256342573425834259342603426134262342633426434265342663426734268342693427034271342723427334274342753427634277342783427934280342813428234283342843428534286342873428834289342903429134292342933429434295342963429734298342993430034301343023430334304343053430634307343083430934310343113431234313343143431534316343173431834319343203432134322343233432434325343263432734328343293433034331343323433334334343353433634337343383433934340343413434234343343443434534346343473434834349343503435134352343533435434355343563435734358343593436034361343623436334364343653436634367343683436934370343713437234373343743437534376343773437834379343803438134382343833438434385343863438734388343893439034391343923439334394343953439634397343983439934400344013440234403344043440534406344073440834409344103441134412344133441434415344163441734418344193442034421344223442334424344253442634427344283442934430344313443234433344343443534436344373443834439344403444134442344433444434445344463444734448344493445034451344523445334454344553445634457344583445934460344613446234463344643446534466344673446834469344703447134472344733447434475344763447734478344793448034481344823448334484344853448634487344883448934490344913449234493344943449534496344973449834499345003450134502345033450434505345063450734508345093451034511345123451334514345153451634517345183451934520345213452234523345243452534526345273452834529345303453134532345333453434535345363453734538345393454034541345423454334544345453454634547345483454934550345513455234553345543455534556345573455834559345603456134562345633456434565345663456734568345693457034571345723457334574345753457634577345783457934580345813458234583345843458534586345873458834589345903459134592345933459434595345963459734598345993460034601346023460334604346053460634607346083460934610346113461234613346143461534616346173461834619346203462134622346233462434625346263462734628346293463034631346323463334634346353463634637346383463934640346413464234643346443464534646346473464834649346503465134652346533465434655346563465734658346593466034661346623466334664346653466634667346683466934670346713467234673346743467534676346773467834679346803468134682346833468434685346863468734688346893469034691346923469334694346953469634697346983469934700347013470234703347043470534706347073470834709347103471134712347133471434715347163471734718347193472034721347223472334724347253472634727347283472934730347313473234733347343473534736347373473834739347403474134742347433474434745347463474734748347493475034751347523475334754347553475634757347583475934760347613476234763347643476534766347673476834769347703477134772347733477434775347763477734778347793478034781347823478334784347853478634787347883478934790347913479234793347943479534796347973479834799348003480134802348033480434805348063480734808348093481034811348123481334814348153481634817348183481934820348213482234823348243482534826348273482834829348303483134832348333483434835348363483734838348393484034841348423484334844348453484634847348483484934850348513485234853348543485534856348573485834859348603486134862348633486434865348663486734868348693487034871348723487334874348753487634877348783487934880348813488234883348843488534886348873488834889348903489134892348933489434895348963489734898348993490034901349023490334904349053490634907349083490934910349113491234913349143491534916349173491834919349203492134922349233492434925349263492734928349293493034931349323493334934349353493634937349383493934940349413494234943349443494534946349473494834949349503495134952349533495434955349563495734958349593496034961349623496334964349653496634967349683496934970349713497234973349743497534976349773497834979349803498134982349833498434985349863498734988349893499034991349923499334994349953499634997349983499935000350013500235003350043500535006350073500835009350103501135012350133501435015350163501735018350193502035021350223502335024350253502635027350283502935030350313503235033350343503535036350373503835039350403504135042350433504435045350463504735048350493505035051350523505335054350553505635057350583505935060350613506235063350643506535066350673506835069350703507135072350733507435075350763507735078350793508035081350823508335084350853508635087350883508935090350913509235093350943509535096350973509835099351003510135102351033510435105351063510735108351093511035111351123511335114351153511635117351183511935120351213512235123351243512535126351273512835129351303513135132351333513435135351363513735138351393514035141351423514335144351453514635147351483514935150351513515235153351543515535156351573515835159351603516135162351633516435165351663516735168351693517035171351723517335174351753517635177351783517935180351813518235183351843518535186351873518835189351903519135192351933519435195351963519735198351993520035201352023520335204352053520635207352083520935210352113521235213352143521535216352173521835219352203522135222352233522435225352263522735228352293523035231352323523335234352353523635237352383523935240352413524235243352443524535246352473524835249352503525135252352533525435255352563525735258352593526035261352623526335264352653526635267352683526935270352713527235273352743527535276352773527835279352803528135282352833528435285352863528735288352893529035291352923529335294352953529635297352983529935300353013530235303353043530535306353073530835309353103531135312353133531435315353163531735318353193532035321353223532335324353253532635327353283532935330353313533235333353343533535336353373533835339353403534135342353433534435345353463534735348353493535035351353523535335354353553535635357353583535935360353613536235363353643536535366353673536835369353703537135372353733537435375353763537735378353793538035381353823538335384353853538635387353883538935390353913539235393353943539535396353973539835399354003540135402354033540435405354063540735408354093541035411354123541335414354153541635417354183541935420354213542235423354243542535426354273542835429354303543135432354333543435435354363543735438354393544035441354423544335444354453544635447354483544935450354513545235453354543545535456354573545835459354603546135462354633546435465354663546735468354693547035471354723547335474354753547635477354783547935480354813548235483354843548535486354873548835489354903549135492354933549435495354963549735498354993550035501355023550335504355053550635507355083550935510355113551235513355143551535516355173551835519355203552135522355233552435525355263552735528355293553035531355323553335534355353553635537355383553935540355413554235543355443554535546355473554835549355503555135552355533555435555355563555735558355593556035561355623556335564355653556635567355683556935570355713557235573355743557535576355773557835579355803558135582355833558435585355863558735588355893559035591355923559335594355953559635597355983559935600356013560235603356043560535606356073560835609356103561135612356133561435615356163561735618356193562035621356223562335624356253562635627356283562935630356313563235633356343563535636356373563835639356403564135642356433564435645356463564735648356493565035651356523565335654356553565635657356583565935660356613566235663356643566535666356673566835669356703567135672356733567435675356763567735678356793568035681356823568335684356853568635687356883568935690356913569235693356943569535696356973569835699357003570135702357033570435705357063570735708357093571035711357123571335714357153571635717357183571935720357213572235723357243572535726357273572835729357303573135732357333573435735357363573735738357393574035741357423574335744357453574635747357483574935750357513575235753357543575535756357573575835759357603576135762357633576435765357663576735768357693577035771357723577335774357753577635777357783577935780357813578235783357843578535786357873578835789357903579135792357933579435795357963579735798357993580035801358023580335804358053580635807358083580935810358113581235813358143581535816358173581835819358203582135822358233582435825358263582735828358293583035831358323583335834358353583635837358383583935840358413584235843358443584535846358473584835849358503585135852358533585435855358563585735858358593586035861358623586335864358653586635867358683586935870358713587235873358743587535876358773587835879358803588135882358833588435885358863588735888358893589035891358923589335894358953589635897358983589935900359013590235903359043590535906359073590835909359103591135912359133591435915359163591735918359193592035921359223592335924359253592635927359283592935930359313593235933359343593535936359373593835939359403594135942359433594435945359463594735948359493595035951359523595335954359553595635957359583595935960359613596235963359643596535966359673596835969359703597135972359733597435975359763597735978359793598035981359823598335984359853598635987359883598935990359913599235993359943599535996359973599835999360003600136002360033600436005360063600736008360093601036011360123601336014360153601636017360183601936020360213602236023360243602536026360273602836029360303603136032360333603436035360363603736038360393604036041360423604336044360453604636047360483604936050360513605236053360543605536056360573605836059360603606136062360633606436065360663606736068360693607036071360723607336074360753607636077360783607936080360813608236083360843608536086360873608836089360903609136092360933609436095360963609736098360993610036101361023610336104361053610636107361083610936110361113611236113361143611536116361173611836119361203612136122361233612436125361263612736128361293613036131361323613336134361353613636137361383613936140361413614236143361443614536146361473614836149361503615136152361533615436155361563615736158361593616036161361623616336164361653616636167361683616936170361713617236173361743617536176361773617836179361803618136182361833618436185361863618736188361893619036191361923619336194361953619636197361983619936200362013620236203362043620536206362073620836209 conditional on include_toplevel + // if (self.options.include_toplevel) { + // self.emit_fpga_top(); + // } + + self.indent(); + self.emit_line("if (!rst_n) begin"); + self.indent(); + self.emit_line("// Reset"); + self.emit_line("pc_reg <= 0;"); + self.emit_line("state <= 2'd0;"); + self.emit_line("halted_reg <= 1'b0;"); + self.emit_line("mem_we_reg <= 1'b0;"); + self.emit_line("for (int i = 0; i < 27; i = i + 1) begin"); + self.emit_line(" reg_file[i] <= 32'h0;"); + self.emit_line("end"); + self.dedent(); + self.emit_line("end else begin"); + self.indent(); + self.emit_line("case (state)"); + self.emit_line(" 2'd0: begin // Fetch"); + self.emit_line(" instruction <= instruction_rom[pc_reg];"); + self.emit_line(" end"); + self.emit_line(" 2'd1: begin // Decode"); + self.emit_line(" // Decode state, signals are combinatorial"); + self.emit_line(" end"); + self.emit_line(" 2'd2: begin // Execute"); + self.emit_line(" case (opcode)"); + self.emit_line(" 4'd0: begin // MOV"); + self.emit_line(" if (dst_reg < 27) reg_file[dst_reg] <= r1;"); + self.emit_line(" end"); + self.emit_line(" 4'd1: begin // JZ"); + self.emit(" if (zero_flag) pc_next <= immediate["); + self.emit_int(self.pc_width - 1); + self.emit_line(":0];"); + self.emit_line(" end"); + self.emit_line(" 4'd2: begin // JNZ"); + self.emit(" if (!zero_flag) pc_next <= immediate["); + self.emit_int(self.pc_width - 1); + self.emit_line(":0];"); + self.emit_line(" end"); + self.emit_line(" 4'd3: begin // JMP"); + self.emit(" pc_next <= immediate["); + self.emit_int(self.pc_width - 1); + self.emit_line(":0];"); + self.emit_line(" end"); + self.emit_line(" 4'd4: begin // MUL"); + self.emit_line(" if (dst_reg < 27) reg_file[dst_reg] <= alu_result;"); + self.emit_line(" end"); + self.emit_line(" 4'd5: begin // ADD"); + self.emit_line(" if (dst_reg < 27) reg_file[dst_reg] <= alu_result;"); + self.emit_line(" end"); + self.emit_line(" 4'd6: begin // SUB"); + self.emit_line(" if (dst_reg < 27) reg_file[dst_reg] <= alu_result;"); + self.emit_line(" end"); + self.emit_line(" 4'd15: begin // HALT"); + self.emit_line(" halted_reg <= 1'b1;"); + self.emit_line(" end"); + self.emit_line(" endcase"); + self.emit_line(""); + self.emit_line(" // Update PC"); + self.emit_line(" if (opcode != 4'd15) begin"); + self.emit_line(" if (pc_next == 0) pc_reg <= pc_reg + 1;"); + self.emit_line(" else pc_reg <= pc_next;"); + self.emit_line(" pc_next <= 0;"); + self.emit_line(" end"); + self.emit_line(" end"); + self.emit_line("endcase"); + self.emit_line(""); + self.emit_line("state <= next_state;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + } + + // Emit Verilog assertions for invariants + fn emit_verilog_assertions(self: *VerilogCodegen) void { + if (self.ast.invariant_section) |inv_section| { + self.emit_line("// 362103621136212362133621436215362163621736218362193622036221362223622336224362253622636227362283622936230362313623236233362343623536236362373623836239362403624136242362433624436245362463624736248362493625036251362523625336254362553625636257362583625936260362613626236263362643626536266362673626836269362703627136272"); + self.emit_line("// TDD-Inside-Spec: Invariant Assertions"); + self.emit_line("// 362733627436275362763627736278362793628036281362823628336284362853628636287362883628936290362913629236293362943629536296362973629836299363003630136302363033630436305363063630736308363093631036311363123631336314363153631636317363183631936320363213632236323363243632536326363273632836329363303633136332363333633436335"); + self.emit_line(""); + + for (inv_section.invariants) |inv| { + self.emit_verilog_assertion(inv); + } + } + } + + // Emit a single invariant as Verilog assertion + fn emit_verilog_assertion(self: *VerilogCodegen, inv: InvariantDecl) void { + const verilog_name = mangle_verilog_name(inv.name); + + self.emit("// Invariant: "); + self.emit(inv.formal_statement); + self.emit_line(""); + self.emit("// Rationale: "); + self.emit(inv.rationale); + self.emit_line(""); + self.emit_line("// Invariant assertion: SystemVerilog assert property"); + self.emit_line("// TODO: Parse formal_statement and generate appropriate assertion:"); + self.emit_line("// - For bounds: assert (value >= min && value <= max);"); + self.emit_line("// - For equality: assert (actual == expected);"); + self.emit_line("// - For state machines: assert (state in valid_states);"); + self.emit_line("// - Use SVA for temporal properties when needed"); + self.emit_line("//"); + self.emit_line("// SystemVerilog assertion patterns:"); + self.emit_line("// - Immediate: assert (condition) $error(\"message\");"); + self.emit_line("// - Clocked: assert property @(posedge clk) disable iff (rst_n)"); + self.emit_line("// condition $error(\"message\");"); + self.emit_line("// - Temporal: assert property @(posedge clk) ##1"); + self.emit_line("// req |=> @(posedge clk) ack $error(\"req->ack timeout\");"); + self.emit_line("// - Cover: cover property @(posedge clk) condition;"); + self.emit_line(""); + self.emit_line("always @(posedge clk) begin"); + self.indent(); + self.emit("// invariant_"); + self.emit(verilog_name); + self.emit_line(": check"); + self.emit_line("// TODO: Add assertion check here based on invariant type"); + self.emit_line("// Example patterns:"); + self.emit_line("// - Immediate: assert(reg_value >= 0 && reg_value <= MAX_VAL);"); + self.emit_line("// - Clocked: assert(property @(posedge clk) disable iff (!rst_n)"); + self.emit_line("// (counter > 0) |=> @(posedge clk) (counter <= MAX));"); + self.emit_line("// - Temporal: assert(property @(posedge clk) ##1"); + self.emit_line("// (state == IDLE) |-> ##[1:$] (state != BUSY)[*2] |-> (state == IDLE));"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + } + + // Emit footer + fn emit_footer(self: *VerilogCodegen) void { + self.emit_line("endmodule"); + self.emit_line(""); + } + + // Emit testbench + fn emit_testbench(self: *VerilogCodegen) void { + self.emit_line("// Testbench"); + self.emit_line("module tri27_processor_tb;"); + self.indent(); + self.emit_line("// Clock generation"); + self.emit_line("reg clk;"); + self.emit_line("reg rst_n;"); + self.emit_line(""); + self.emit_line("// Instantiate DUT"); + self.emit("wire ["); + self.emit_int(self.addr_width - 1); + self.emit(":0] pc;"); + self.emit_line(""); + self.emit("wire ["); + self.emit_int(self.addr_width - 1); + self.emit(":0] mem_addr;"); + self.emit_line("wire mem_we;"); + self.emit("wire ["); + self.emit_int(self.data_width - 1); + self.emit(":0] mem_wdata;"); + self.emit("reg ["); + self.emit_int(self.data_width - 1); + self.emit(":0] mem_rdata;"); + self.emit_line("wire halted;"); + self.emit_line(""); + self.emit_line("tri27_processor #("); + self.emit(" .PC_WIDTH("); + self.emit_int(self.pc_width); + self.emit_line("),"); + self.emit(" .ADDR_WIDTH("); + self.emit_int(self.addr_width); + self.emit_line("),"); + self.emit(" .DATA_WIDTH("); + self.emit_int(self.data_width); + self.emit_line(")"); + self.emit_line(") dut ("); + self.emit_line(" .clk(clk),"); + self.emit_line(" .rst_n(rst_n),"); + self.emit_line(" .pc(pc),"); + self.emit_line(" .mem_addr(mem_addr),"); + self.emit_line(" .mem_we(mem_we),"); + self.emit_line(" .mem_wdata(mem_wdata),"); + self.emit_line(" .mem_rdata(mem_rdata),"); + self.emit_line(" .halted(halted)"); + self.emit_line(");"); + self.emit_line(""); + self.emit_line("// Clock: 100MHz = 10ns period"); + self.emit_line("localparam CLK_PERIOD = 10;"); + self.emit_line(""); + + // Emit test tasks from .test section + self.emit_test_tasks(); + + self.emit_line("initial begin"); + self.indent(); + self.emit_line("clk = 0;"); + self.emit_line("forever #(CLK_PERIOD/2) clk = ~clk;"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + self.emit_line("initial begin"); + self.indent(); + self.emit_line("// Reset sequence"); + self.emit_line("rst_n = 0;"); + self.emit_line("#(CLK_PERIOD * 5);"); + self.emit_line("rst_n = 1;"); + self.emit_line(""); + + // Emit test calls from .test section + self.emit_test_calls(); + + self.emit("// Wait for halt or timeout"); + self.emit_line("wait (halted || (pc >= "); + self.emit_int(@intCast(self.ast.code_section.instructions.len)); + self.emit_line("));"); + self.emit_line("#(CLK_PERIOD * 10);"); + self.emit_line(""); + self.emit_line("$display(\"Simulation complete. PC = %d\", pc);"); + self.emit_line("$finish;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("endmodule"); + self.emit_line(""); + } + + // 3633636337363383633936340363413634236343363443634536346363473634836349363503635136352363533635436355363563635736358363593636036361363623636336364363653636636367363683636936370363713637236373363743637536376363773637836379363803638136382363833638436385363863638736388363893639036391363923639336394363953639636397363983639936400364013640236403364043640536406364073640836409364103641136412364133641436415364163641736418364193642036421364223642336424364253642636427364283642936430364313643236433364343643536436364373643836439364403644136442364433644436445364463644736448364493645036451364523645336454364553645636457364583645936460364613646236463364643646536466364673646836469364703647136472364733647436475364763647736478364793648036481364823648336484364853648636487364883648936490364913649236493364943649536496364973649836499365003650136502365033650436505365063650736508365093651036511365123651336514365153651636517365183651936520365213652236523365243652536526365273652836529365303653136532365333653436535365363653736538365393654036541365423654336544365453654636547365483654936550365513655236553365543655536556365573655836559365603656136562365633656436565365663656736568365693657036571365723657336574365753657636577365783657936580365813658236583365843658536586365873658836589365903659136592365933659436595365963659736598365993660036601366023660336604366053660636607366083660936610366113661236613366143661536616366173661836619366203662136622366233662436625366263662736628366293663036631366323663336634366353663636637366383663936640366413664236643366443664536646366473664836649366503665136652366533665436655366563665736658366593666036661366623666336664366653666636667366683666936670366713667236673366743667536676366773667836679366803668136682366833668436685366863668736688366893669036691366923669336694366953669636697366983669936700367013670236703367043670536706367073670836709367103671136712367133671436715367163671736718367193672036721367223672336724367253672636727367283672936730367313673236733367343673536736367373673836739367403674136742367433674436745367463674736748367493675036751367523675336754367553675636757367583675936760367613676236763367643676536766367673676836769367703677136772367733677436775367763677736778367793678036781367823678336784367853678636787367883678936790367913679236793367943679536796367973679836799368003680136802368033680436805368063680736808368093681036811368123681336814368153681636817368183681936820368213682236823368243682536826368273682836829368303683136832368333683436835368363683736838368393684036841368423684336844368453684636847368483684936850368513685236853368543685536856368573685836859368603686136862368633686436865368663686736868368693687036871368723687336874368753687636877368783687936880368813688236883368843688536886368873688836889368903689136892368933689436895368963689736898368993690036901369023690336904369053690636907369083690936910369113691236913369143691536916369173691836919369203692136922369233692436925369263692736928369293693036931369323693336934369353693636937369383693936940369413694236943369443694536946369473694836949369503695136952369533695436955369563695736958369593696036961369623696336964369653696636967369683696936970369713697236973369743697536976369773697836979369803698136982369833698436985369863698736988369893699036991369923699336994369953699636997369983699937000370013700237003370043700537006370073700837009370103701137012370133701437015370163701737018370193702037021370223702337024370253702637027370283702937030370313703237033370343703537036370373703837039370403704137042370433704437045370463704737048370493705037051370523705337054370553705637057370583705937060370613706237063370643706537066370673706837069370703707137072370733707437075370763707737078370793708037081370823708337084370853708637087370883708937090370913709237093370943709537096370973709837099371003710137102371033710437105371063710737108371093711037111371123711337114371153711637117371183711937120371213712237123371243712537126371273712837129371303713137132371333713437135371363713737138371393714037141371423714337144371453714637147371483714937150371513715237153371543715537156371573715837159371603716137162371633716437165371663716737168371693717037171371723717337174371753717637177371783717937180371813718237183371843718537186371873718837189371903719137192371933719437195371963719737198371993720037201372023720337204372053720637207372083720937210372113721237213372143721537216372173721837219372203722137222372233722437225372263722737228372293723037231372323723337234372353723637237372383723937240372413724237243372443724537246372473724837249372503725137252372533725437255372563725737258372593726037261372623726337264372653726637267372683726937270372713727237273372743727537276372773727837279372803728137282372833728437285372863728737288372893729037291372923729337294372953729637297372983729937300373013730237303373043730537306373073730837309373103731137312373133731437315373163731737318373193732037321373223732337324373253732637327373283732937330373313733237333373343733537336373373733837339373403734137342373433734437345373463734737348373493735037351373523735337354373553735637357373583735937360373613736237363373643736537366373673736837369373703737137372373733737437375373763737737378373793738037381373823738337384373853738637387373883738937390373913739237393373943739537396373973739837399374003740137402374033740437405374063740737408374093741037411374123741337414374153741637417374183741937420374213742237423374243742537426374273742837429374303743137432374333743437435374363743737438374393744037441374423744337444374453744637447374483744937450374513745237453374543745537456374573745837459374603746137462374633746437465374663746737468374693747037471374723747337474374753747637477374783747937480374813748237483374843748537486374873748837489374903749137492374933749437495374963749737498374993750037501375023750337504375053750637507375083750937510375113751237513375143751537516375173751837519375203752137522375233752437525375263752737528375293753037531375323753337534375353753637537375383753937540375413754237543375443754537546375473754837549375503755137552375533755437555375563755737558375593756037561375623756337564375653756637567375683756937570375713757237573375743757537576375773757837579375803758137582375833758437585375863758737588375893759037591375923759337594375953759637597375983759937600376013760237603376043760537606376073760837609376103761137612376133761437615376163761737618376193762037621376223762337624376253762637627376283762937630376313763237633376343763537636376373763837639376403764137642376433764437645376463764737648376493765037651376523765337654376553765637657376583765937660376613766237663376643766537666376673766837669376703767137672376733767437675376763767737678376793768037681376823768337684376853768637687376883768937690376913769237693376943769537696376973769837699377003770137702377033770437705377063770737708377093771037711377123771337714377153771637717377183771937720377213772237723377243772537726377273772837729377303773137732377333773437735377363773737738377393774037741377423774337744377453774637747377483774937750377513775237753377543775537756377573775837759377603776137762377633776437765377663776737768377693777037771377723777337774377753777637777377783777937780377813778237783377843778537786377873778837789377903779137792 } + } + + // 377933779437795377963779737798377993780037801378023780337804378053780637807378083780937810378113781237813378143781537816378173781837819378203782137822378233782437825378263782737828378293783037831378323783337834378353783637837378383783937840378413784237843378443784537846378473784837849378503785137852378533785437855378563785737858378593786037861378623786337864378653786637867378683786937870378713787237873378743787537876378773787837879378803788137882378833788437885378863788737888378893789037891378923789337894378953789637897378983789937900379013790237903379043790537906379073790837909379103791137912379133791437915379163791737918379193792037921379223792337924379253792637927379283792937930379313793237933379343793537936379373793837939379403794137942379433794437945379463794737948379493795037951379523795337954379553795637957379583795937960379613796237963379643796537966379673796837969379703797137972379733797437975379763797737978379793798037981379823798337984379853798637987379883798937990379913799237993379943799537996379973799837999380003800138002380033800438005380063800738008380093801038011380123801338014380153801638017380183801938020380213802238023380243802538026380273802838029380303803138032380333803438035380363803738038380393804038041380423804338044380453804638047380483804938050380513805238053380543805538056380573805838059380603806138062380633806438065380663806738068380693807038071380723807338074380753807638077380783807938080380813808238083380843808538086380873808838089380903809138092380933809438095380963809738098380993810038101381023810338104381053810638107381083810938110381113811238113381143811538116381173811838119381203812138122381233812438125381263812738128381293813038131381323813338134381353813638137381383813938140381413814238143381443814538146381473814838149381503815138152381533815438155381563815738158381593816038161381623816338164381653816638167381683816938170381713817238173381743817538176381773817838179381803818138182381833818438185381863818738188381893819038191381923819338194381953819638197381983819938200382013820238203382043820538206382073820838209382103821138212382133821438215382163821738218382193822038221382223822338224382253822638227382283822938230382313823238233382343823538236382373823838239382403824138242382433824438245382463824738248382493825038251382523825338254382553825638257382583825938260382613826238263382643826538266382673826838269382703827138272382733827438275382763827738278382793828038281382823828338284382853828638287382883828938290382913829238293382943829538296382973829838299383003830138302383033830438305383063830738308383093831038311383123831338314383153831638317383183831938320383213832238323383243832538326383273832838329383303833138332383333833438335383363833738338383393834038341383423834338344383453834638347383483834938350383513835238353383543835538356383573835838359383603836138362383633836438365383663836738368383693837038371383723837338374383753837638377383783837938380383813838238383383843838538386383873838838389383903839138392383933839438395383963839738398383993840038401384023840338404384053840638407384083840938410384113841238413384143841538416384173841838419384203842138422384233842438425384263842738428384293843038431384323843338434384353843638437384383843938440384413844238443384443844538446384473844838449384503845138452384533845438455384563845738458384593846038461384623846338464384653846638467384683846938470384713847238473384743847538476384773847838479384803848138482384833848438485384863848738488384893849038491384923849338494384953849638497384983849938500385013850238503385043850538506385073850838509385103851138512385133851438515385163851738518385193852038521385223852338524385253852638527385283852938530385313853238533385343853538536385373853838539385403854138542385433854438545385463854738548385493855038551385523855338554385553855638557385583855938560385613856238563385643856538566385673856838569385703857138572385733857438575385763857738578385793858038581385823858338584385853858638587385883858938590385913859238593385943859538596385973859838599386003860138602386033860438605386063860738608386093861038611386123861338614386153861638617386183861938620386213862238623386243862538626386273862838629386303863138632386333863438635386363863738638386393864038641386423864338644386453864638647386483864938650386513865238653386543865538656386573865838659386603866138662386633866438665386663866738668386693867038671386723867338674386753867638677386783867938680386813868238683386843868538686386873868838689386903869138692386933869438695386963869738698386993870038701387023870338704387053870638707387083870938710387113871238713387143871538716387173871838719387203872138722387233872438725387263872738728387293873038731387323873338734387353873638737387383873938740387413874238743387443874538746387473874838749387503875138752387533875438755387563875738758387593876038761387623876338764387653876638767387683876938770387713877238773387743877538776387773877838779387803878138782387833878438785387863878738788387893879038791387923879338794387953879638797387983879938800388013880238803388043880538806388073880838809388103881138812388133881438815388163881738818388193882038821388223882338824388253882638827388283882938830388313883238833388343883538836388373883838839388403884138842388433884438845388463884738848388493885038851388523885338854388553885638857388583885938860388613886238863388643886538866388673886838869388703887138872388733887438875388763887738878388793888038881388823888338884388853888638887388883888938890388913889238893388943889538896388973889838899389003890138902389033890438905389063890738908389093891038911389123891338914389153891638917389183891938920389213892238923389243892538926389273892838929389303893138932389333893438935389363893738938389393894038941389423894338944389453894638947389483894938950389513895238953389543895538956389573895838959389603896138962389633896438965389663896738968389693897038971389723897338974389753897638977389783897938980389813898238983389843898538986389873898838989389903899138992389933899438995389963899738998389993900039001390023900339004390053900639007390083900939010390113901239013390143901539016390173901839019390203902139022390233902439025390263902739028390293903039031390323903339034390353903639037390383903939040390413904239043390443904539046390473904839049390503905139052390533905439055390563905739058390593906039061390623906339064390653906639067390683906939070390713907239073390743907539076390773907839079390803908139082390833908439085390863908739088390893909039091390923909339094390953909639097390983909939100391013910239103391043910539106391073910839109391103911139112391133911439115391163911739118391193912039121391223912339124391253912639127391283912939130391313913239133391343913539136391373913839139391403914139142391433914439145391463914739148391493915039151391523915339154391553915639157391583915939160391613916239163391643916539166391673916839169391703917139172391733917439175391763917739178391793918039181391823918339184391853918639187391883918939190391913919239193391943919539196391973919839199392003920139202392033920439205392063920739208392093921039211392123921339214392153921639217392183921939220392213922239223392243922539226392273922839229392303923139232392333923439235392363923739238392393924039241392423924339244392453924639247392483924939250392513925239253392543925539256392573925839259392603926139262392633926439265392663926739268392693927039271392723927339274392753927639277392783927939280392813928239283392843928539286392873928839289392903929139292392933929439295392963929739298392993930039301393023930339304393053930639307393083930939310393113931239313393143931539316393173931839319393203932139322393233932439325393263932739328393293933039331393323933339334393353933639337393383933939340393413934239343393443934539346393473934839349393503935139352393533935439355393563935739358393593936039361393623936339364393653936639367393683936939370393713937239373393743937539376393773937839379393803938139382393833938439385393863938739388393893939039391393923939339394393953939639397393983939939400394013940239403394043940539406394073940839409394103941139412394133941439415394163941739418394193942039421394223942339424394253942639427394283942939430394313943239433394343943539436394373943839439394403944139442394433944439445394463944739448394493945039451394523945339454394553945639457394583945939460394613946239463394643946539466394673946839469394703947139472394733947439475394763947739478394793948039481394823948339484394853948639487394883948939490394913949239493394943949539496394973949839499395003950139502395033950439505395063950739508395093951039511395123951339514395153951639517395183951939520395213952239523395243952539526395273952839529395303953139532395333953439535395363953739538395393954039541395423954339544395453954639547395483954939550395513955239553395543955539556395573955839559395603956139562395633956439565395663956739568395693957039571395723957339574395753957639577395783957939580395813958239583395843958539586395873958839589395903959139592395933959439595395963959739598395993960039601396023960339604396053960639607396083960939610396113961239613396143961539616396173961839619396203962139622396233962439625396263962739628396293963039631396323963339634396353963639637396383963939640396413964239643396443964539646396473964839649 // TODO: Implement FPGA module emission + 63239650 self.emit_line("// if (self.options.include_toplevel) {"); + 63339651 self.emit(" self.emit_fpga_top();"); + 63439652 self.emit(" }"); + 63539653 self.emit_line(""); + if (self.ast.test_section) |test_section| { + self.emit_line("// 396543965539656396573965839659396603966139662396633966439665396663966739668396693967039671396723967339674396753967639677396783967939680396813968239683396843968539686396873968839689396903969139692396933969439695396963969739698396993970039701397023970339704397053970639707397083970939710397113971239713397143971539716"); + self.emit_line("// TDD-Inside-Spec: Test Tasks"); + self.emit_line("// 397173971839719397203972139722397233972439725397263972739728397293973039731397323973339734397353973639737397383973939740397413974239743397443974539746397473974839749397503975139752397533975439755397563975739758397593976039761397623976339764397653976639767397683976939770397713977239773397743977539776397773977839779"); + self.emit_line(""); + + for (test_section.test_cases) |test_case| { + self.emit_verilog_test_task(test_case); + } + } + } + + // Emit a single test case as Verilog task + fn emit_verilog_test_task(self: *VerilogCodegen, test_case: TestCase) void { + const verilog_name = mangle_verilog_name(test_case.name); + + self.emit("// Test: "); + self.emit(test_case.name); + self.emit_line(""); + self.emit("// Verify: "); + self.emit(test_case.verify_description); + self.emit_line(""); + self.emit("// Expected: "); + self.emit(test_case.expected_outcome); + self.emit_line(""); + self.emit("task test_"); + self.emit(verilog_name); + self.emit_line(";"); + self.indent(); + self.emit("// TODO: Implement test: "); + self.emit(test_case.name); + self.emit_line(""); + self.emit_line("// Test task implementation requirements:"); + self.emit_line("// 1. Parse setup_description and generate initialization"); + self.emit_line("// 2. Apply stimulus to DUT inputs"); + self.emit_line("// 3. Wait for response (delay for propagation)"); + self.emit_line("// 4. Parse expected_outcome and verify with $display/$assert"); + self.emit_line("// 5. Report pass/fail via $display"); + self.emit_line(""); + self.emit_line("begin"); + self.indent(); + self.emit("// Setup: "); + self.emit(test_case.setup_description); + self.emit_line(""); + self.emit_line(" // Apply test inputs"); + self.emit_line(" // TODO: Parse verify_description for input values"); + self.emit_line(" // Example: dut.input_a = 8'hA5;"); + self.emit_line(""); + self.emit_line(" // Input value patterns:"); + self.emit_line(" // - Hex literal: dut.input = 8'hFF;"); + self.emit_line(" // - Binary: dut.flag = 1'b1;"); + self.emit_line(" // - Constant: dut.en = 1'b1;"); + self.emit_line(" // - Array: for (int i=0; i<8; i++) dut.data[i] = i;"); + self.emit_line(""); + self.emit_line(" // Wait for DUT response"); + self.emit_line(" // @(posedge clk); // Wait 1 cycle"); + self.emit_line(""); + self.emit_line(" // Verify expected output"); + self.emit_line(" // TODO: Parse expected_outcome and assert"); + self.emit_line(" // Example: if (dut.output !== expected_result)"); + self.emit_line(" // $display(\"FAIL: got %h, expected %h\", dut.output, expected_result);"); + self.emit_line(""); + self.emit_line(" // Assertion patterns:"); + self.emit_line(" // - Equality: if (actual !== expected) $error(\"FAIL\");"); + self.emit_line(" // - Range: if (value < min || value > max) $error(\"out of range\");"); + self.emit_line(" // - Non-zero: if (result == 0) $error(\"expected non-zero\");"); + self.emit_line(" // - Check X/Z: if ($isunknown(actual)) $error(\"got X or Z\");"); + self.emit_line(" // - Count: if (count != expected) $error(\"count mismatch\");"); + self.emit_line(""); + self.emit_line(" $display(\"Test "); + self.emit(test_case.name); + self.emit(": PASS\");"); + self.dedent(); + self.emit("end"); + self.emit_line(""); + self.dedent(); + self.emit("endtask"); + self.emit_line(""); + } + + // Emit test calls from .test section + fn emit_test_calls(self: *VerilogCodegen) void { + if (self.ast.test_section) |test_section| { + self.emit_line("// Run tests"); + for (test_section.test_cases) |test_case| { + const verilog_name = mangle_verilog_name(test_case.name); + self.emit("test_"); + self.emit(verilog_name); + self.emit_line("();"); + } + self.emit_line(""); + } + } + + // Mangle name to valid Verilog identifier + fn mangle_verilog_name(self: *VerilogCodegen, name: []const u8) []const u8 { + var result: []const u8 = ""; + + for (name) |c| { + if ((c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or (c >= '0' and c <= '9') or c == '_') { + result = result ++ [_]u8{c}; + } else if (c == '-' or c == ' ') { + result = result ++ "_"; + } + } + + return result; + } + + // Emit helpers + fn emit(self: *VerilogCodegen, s: []const u8) void { + self.output.append(s); + } + + fn emit_line(self: *VerilogCodegen, s: []const u8) void { + self.output.append(s); + self.output.append("\n"); + + // Add indentation for next line + var i: u32 = 0; + while (i < self.indent_level) : (i += 1) { + self.output.append(" "); + } + } + + fn emit_int(self: *VerilogCodegen, n: anytype) void { + self.emit(int_to_str(n)); + } + + fn indent(self: *VerilogCodegen) void { + self.indent_level += 1; + } + + fn dedent(self: *VerilogCodegen) void { + if (self.indent_level > 0) { + self.indent_level -= 1; + } + } + }; + + // StringBuilder + pub const StringBuilder = struct { + buffer: []u8, + len: usize, + capacity: usize, + + pub fn new(capacity: usize) StringBuilder { + return StringBuilder{ + .buffer = [_]u8{0} ** capacity, + .len = 0, + .capacity = capacity, + }; + } + + pub fn append(self: *StringBuilder, s: []const u8) void { + var i: usize = 0; + while (i < s.len and self.len < self.capacity) : (i += 1) { + self.buffer[self.len] = s[i]; + self.len += 1; + } + } + + pub fn to_string(self: *StringBuilder) []const u8 { + return self.buffer[0..self.len]; + } + }; + + // Types (simplified from AST) + pub const Program = struct { + source_file: []const u8, + code_section: CodeSection, + test_section: ?TestSection, + invariant_section: ?InvariantSection, + }; + + pub const CodeSection = struct { + instructions: []Instruction, + labels: std.StringHashMap(usize), + }; + + pub const TestSection = struct { + test_cases: []TestCase, + }; + + pub const InvariantSection = struct { + invariants: []InvariantDecl, + }; + + pub const Instruction = struct { + opcode: Opcode, + operands: []const Operand, + }; + + pub const Opcode = enum(u8) { + MOV, + JZ, + JNZ, + JMP, + MUL, + ADD, + SUB, + BIND, + BUNDLE, + HALT, + }; + + pub const Operand = union(enum) { + RegOperand: struct { reg_num: u8 }, + ImmOperand: struct { value: i32 }, + LabelOperand: struct { label_name: []const u8 }, + MemOperand: struct { base_reg: u8 }, + }; + + pub const TestCase = struct { + name: []const u8, + verify_description: []const u8, + expected_outcome: []const u8, + setup_description: []const u8, + }; + + pub const InvariantDecl = struct { + name: []const u8, + formal_statement: []const u8, + rationale: []const u8, + }; + + pub const CodegenError = struct { + line: usize, + message: []const u8, + }; + + // Calculate required bit width for a value + fn calculate_width(max_value: usize) u8 { + if (max_value == 0) return 1; + + var width: u8 = 0; + var v = max_value; + + while (v > 0) { + width += 1; + v >>= 1; + } + + return width; + } + + // Format integer as hex string with padding + fn format_hex(value: u32, width: u32) []const u8 { + const hex_chars = "0123456789ABCDEF"; + var result: []const u8 = ""; + + var i: u32 = 0; + while (i < width) : (i += 1) { + const shift = (width - 1 - i) * 4; + const digit = (value >> shift) & 0xF; + result = result ++ [_]u8{hex_chars[@intCast(digit)]}; + } + + return result; + } + + // Helper: integer to string + fn int_to_str(n: anytype) []const u8 { + // Simplified - in real implementation would use std.fmt + _ = n; + return "0"; + } +} + +// TDD-Inside-Spec: Tests and Invariants for Verilog Codegen + +test verilog_codegen_new_calculates_widths + // Verify: VerilogCodegen.new calculates correct bit widths + // Expected: pc_width based on instruction count, addr_width=12, data_width=32 + var code_section = verilog_codegen.CodeSection{ + .instructions = &.{}, + .labels = std.StringHashMap(usize).init(std.testing.allocator), + }; + var ast = verilog_codegen.Program{ + .source_file = "test.t27", + .code_section = code_section, + .test_section = null, + .invariant_section = null, + }; + var opts = verilog_codegen.VerilogCodegenOptions{ + .target_device = "XC7A100T", + .clock_freq_hz = 100_000_000, + .include_testbench = false, + .include_toplevel = true, + }; + var codegen = verilog_codegen.VerilogCodegen.new(ast, opts); + try std.testing.expect(codegen.pc_width >= 1); + try std.testing.expectEqual(@as(u8, 12), codegen.addr_width); + try std.testing.expectEqual(@as(u8, 32), codegen.data_width); + +test verilog_codegen_generate_includes_header + // Verify: generate() outputs Verilog header with timescale + // Expected: Output contains "`timescale" and "Generated by t27 compiler" + var code_section = verilog_codegen.CodeSection{ + .instructions = &.{}, + .labels = std.StringHashMap(usize).init(std.testing.allocator), + }; + var ast = verilog_codegen.Program{ + .source_file = "test.t27", + .code_section = code_section, + .test_section = null, + .invariant_section = null, + }; + var opts = verilog_codegen.VerilogCodegenOptions{ + .target_device = "XC7A100T", + .clock_freq_hz = 100_000_000, + .include_testbench = false, + .include_toplevel = true, + }; + var codegen = verilog_codegen.VerilogCodegen.new(ast, opts); + const output = codegen.generate(); + try std.testing.expect(std.mem.indexOf(u8, output, "`timescale") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "Generated by t27 compiler") != null); + +test verilog_opcode_to_bits_mappings + // Verify: opcode_to_bits returns correct 4-bit encodings + // Expected: MOV=0, JZ=1, JNZ=2, JMP=3, MUL=4, ADD=5, SUB=6, BIND=7, BUNDLE=8, HALT=15 + try std.testing.expectEqual(@as(u32, 0), verilog_codegen.VerilogCodegen.opcode_to_bits(&.{}, verilog_codegen.Opcode.MOV)); + try std.testing.expectEqual(@as(u32, 1), verilog_codegen.VerilogCodegen.opcode_to_bits(&.{}, verilog_codegen.Opcode.JZ)); + try std.testing.expectEqual(@as(u32, 15), verilog_codegen.VerilogCodegen.opcode_to_bits(&.{}, verilog_codegen.Opcode.HALT)); + +test verilog_generate_with_testbench_includes_tb_module + // Verify: generate() with include_testbench outputs testbench module + // Expected: Output contains "module tri27_processor_tb" + var code_section = verilog_codegen.CodeSection{ + .instructions = &.{}, + .labels = std.StringHashMap(usize).init(std.testing.allocator), + }; + var ast = verilog_codegen.Program{ + .source_file = "test.t27", + .code_section = code_section, + .test_section = null, + .invariant_section = null, + }; + var opts = verilog_codegen.VerilogCodegenOptions{ + .target_device = "XC7A100T", + .clock_freq_hz = 100_000_000, + .include_testbench = true, + .include_toplevel = true, + }; + var codegen = verilog_codegen.VerilogCodegen.new(ast, opts); + const output = codegen.generate(); + try std.testing.expect(std.mem.indexOf(u8, output, "module tri27_processor_tb") != null); + +test verilog_format_hex_produces_padded_output + // Verify: format_hex produces zero-padded hex string + // Expected: format_hex(0xAB, 4) returns "00AB" + _ = verilog_codegen.format_hex; + try std.testing.expect(true); + +test verilog_mangle_verilog_name_replaces_spaces + // Verify: mangle_verilog_name replaces spaces with underscores + // Expected: "test name" becomes "test_name" + _ = verilog_codegen.VerilogCodegen.mangle_verilog_name; + try std.testing.expect(true); + +test verilog_indent_dedent_manages_level + // Verify: indent/dedent correctly manage indent_level + // Expected: indent increases level, dedent decreases (never below 0) + _ = verilog_codegen.VerilogCodegen.indent; + _ = verilog_codegen.VerilogCodegen.dedent; + try std.testing.expect(true); + +invariant verilog_width_calculation_minimum_1 + // calculate_width returns at least 1 even for max_value=0 + // Rationale: Bit width must be at least 1 bit + assert verilog_codegen.calculate_width(0) == 1; + +invariant verilog_pc_width_covers_all_instructions + // pc_width is sufficient to address all instructions + // Rationale: Program counter must reach last instruction + assert true; + +invariant verilog_opcode_encoding_unique + // Each opcode has a unique 4-bit encoding + // Rationale: Unambiguous instruction decode + assert true; + +invariant verilog_generate_outputs_complete_module + // generate() outputs complete Verilog module with endmodule + // Rationale: Valid Verilog syntax + assert true; + +invariant verilog_testbench_has_clock_generation + // Testbench includes clock generation with correct period + // Rationale: Required for simulation + assert true; + +bench verilog_codegen_generate_latency + target: < 50ms + var code_section = verilog_codegen.CodeSection{ + .instructions = &.{}, + .labels = std.StringHashMap(usize).init(std.testing.allocator), + }; + var ast = verilog_codegen.Program{ + .source_file = "test.t27", + .code_section = code_section, + .test_section = null, + .invariant_section = null, + }; + var opts = verilog_codegen.VerilogCodegenOptions{ + .target_device = "XC7A100T", + .clock_freq_hz = 100_000_000, + .include_testbench = false, + .include_toplevel = true, + }; + var codegen = verilog_codegen.VerilogCodegen.new(ast, opts); + _ = codegen.generate(); + +bench verilog_encode_instruction_latency + target: < 1us + _ = verilog_codegen.VerilogCodegen.encode_instruction; + +bench verilog_opcode_to_bits_latency + target: < 100ns + _ = verilog_codegen.VerilogCodegen.opcode_to_bits; diff --git a/apps/website/public/t27/files/compiler/codegen/verilog/fpga_emission.t27 b/apps/website/public/t27/files/compiler/codegen/verilog/fpga_emission.t27 new file mode 100644 index 0000000000..97446917f4 --- /dev/null +++ b/apps/website/public/t27/files/compiler/codegen/verilog/fpga_emission.t27 @@ -0,0 +1,2358 @@ +// fpga_emission.t27 0 FPGA Module Verilog Emission +// Generates FPGA-specific Verilog modules from .t27 specs + +module fpga_emission { + using ast: @import("../../../ast.t27"); + + // FPGA Emission Context + + // FPGA module emission context + pub const FpgaCodegen = struct { + output: *StringBuilder, // Output buffer (shared from VerilogCodegen) + indent_level: u32, // Current indent level + target_device: []const u8, // Target FPGA device + clock_freq: u32, // System clock frequency (Hz) + }; + + // Create new FPGA emission context + pub fn new(output: *StringBuilder, target_device: []const u8, clock_freq: u32) FpgaCodegen { + return FpgaCodegen{ + .output = output, + .indent_level = 0, + .target_device = target_device, + .clock_freq = clock_freq, + }; + } + + // 1. Top-Level FPGA Module Emission + + // emit_fpga_top() 307 void + // Generate top-level FPGA module that combines MAC, UART, SPI + fn emit_fpga_top(self: *FpgaCodegen) void { + self.emit_line("// Trinity FPGA Top-Level Module"); + self.emit_line("// Combines MAC, UART, SPI, and Bridge"); + self.emit_line("// Target: "); + self.emit(self.target_device); + self.emit_line(" | Clock: "); + self.emit_int(self.clock_freq); + self.emit_line(" Hz"); + self.emit_line(""); + self.emit_line("`timescale 1ns / 1ps"); + self.emit_line(""); + self.emit_line("module Trinity_FPGA_Top ("); + self.indent(); + self.emit_line("// Clock and reset"); + self.emit_line("input wire clk,"); + self.emit_line("input wire rst_n,"); + self.emit_line(""); + self.emit_line("// UART"); + self.emit_line("input wire uart_rx_in,"); + self.emit_line("output wire uart_tx_out,"); + self.emit_line(""); + self.emit_line("// SPI"); + self.emit_line("input wire spi_miso_in,"); + self.emit_line("output wire spi_cs_out,"); + self.emit_line("output wire spi_sck_out,"); + self.emit_line("output wire spi_mosi_out,"); + self.emit_line(""); + self.emit_line("// Status LEDs"); + self.emit_line("output wire [3:0] led_out,"); + self.emit_line(""); + self.emit_line("// MAC interface"); + self.emit_line("input wire [26:0] mac_a_in,"); + self.emit_line("input wire [26:0] mac_b_in,"); + self.emit_line("input wire [31:0] mac_acc_in,"); + self.emit_line("output wire [31:0] mac_acc_out,"); + self.emit_line("output wire mac_valid_out"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + + // Internal signals + self.emit_line("// Internal signals"); + self.emit_line("wire [3:0] led_state;"); + self.emit_line(""); + + // Instantiate UART Bridge + self.emit_line("// UART Bridge instantiation"); + self.emit_line("UART_Bridge uart_bridge ("); + self.indent(); + self.emit_line(".clk(clk),"); + self.emit_line(".rst_n(rst_n),"); + self.emit_line(".uart_rx(uart_rx_in),"); + self.emit_line(".uart_tx(uart_tx_out)"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + + // Instantiate SPI Master + self.emit_line("// SPI Master instantiation"); + self.emit_line("SPI_Master spi_master ("); + self.indent(); + self.emit_line(".clk(clk),"); + self.emit_line(".rst_n(rst_n),"); + self.emit_line(".miso(spi_miso_in),"); + self.emit_line(".cs(spi_cs_out),"); + self.emit_line(".sck(spi_sck_out),"); + self.emit_line(".mosi(spi_mosi_out)"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + + // MAC internal wires + self.emit_line("// MAC unit wires"); + self.emit_line("wire [26:0] mac_a_wire;"); + self.emit_line("wire [26:0] mac_b_wire;"); + self.emit_line("wire [31:0] mac_acc_wire;"); + self.emit_line("wire mac_en_wire;"); + self.emit_line("wire [7:0] mac_unit_sel;"); + self.emit_line("wire [31:0] mac_result [0:7];"); + self.emit_line("wire [7:0] mac_ready [0:7];"); + self.emit_line(""); + + // Route top-level MAC inputs + self.emit_line("// MAC input routing"); + self.emit_line("assign mac_a_wire = mac_a_in;"); + self.emit_line("assign mac_b_wire = mac_b_in;"); + self.emit_line("assign mac_acc_wire = mac_acc_in;"); + self.emit_line("assign mac_en_wire = |mac_unit_sel;"); + self.emit_line(""); + + // Instantiate 8 parallel MAC units + self.emit_line("// ZeroDSP MAC units (8 parallel)"); + var unit_idx : usize = 0; + while (unit_idx < 8) { + self.emit_line("ZeroDSP_MAC mac_unit_"); + self.emit_int(unit_idx); + self.emit_line(" ("); + self.indent(); + self.emit_line(".clk(clk),"); + self.emit_line(".rst_n(rst_n),"); + self.emit_line(".en(mac_en_wire),"); + self.emit_line(".ready(mac_ready["); + self.emit_int(unit_idx); + self.emit_line("])"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + unit_idx = unit_idx + 1; + } + + // LED state mapping + self.emit_line("// LED output mapping"); + self.emit_line("assign led_out = led_state;"); + self.emit_line(""); + + // MAC output mapping + self.emit_line("// MAC output signals 308 OR-reduce ready flags for valid"); + self.emit_line("assign mac_acc_out = mac_result[0];"); + self.emit_line("assign mac_valid_out = &mac_ready[7:0];"); + self.emit_line(""); + + self.emit_line("endmodule"); + self.emit_line(""); + } + + // 2. UART Module Emission + + // emit_uart_module() 473 void + // Generate UART RX/TX module from spec + fn emit_uart_module(self: *FpgaCodegen) void { + self.emit_line("// UART Bridge Module"); + self.emit_line("// 8-N-1 protocol, 115200 baud @ 50MHz"); + self.emit_line(""); + self.emit_line("module UART_Bridge ("); + self.indent(); + self.emit_line("// Clock and reset"); + self.emit_line("input wire clk,"); + self.emit_line("input wire rst_n,"); + self.emit_line(""); + self.emit_line("// UART interface"); + self.emit_line("input wire uart_rx,"); + self.emit_line("output wire uart_tx"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + + // Baud rate divisor + const baud_divisor = self.clock_freq / 115200; + self.emit_line("// Baud rate divisor for 115200 baud @ "); + self.emit_int(self.clock_freq); + self.emit_line(" Hz"); + self.emit_line("localparam BAUD_DIVISOR = "); + self.emit_int(baud_divisor); + self.emit_line(";"); + self.emit_line(""); + + // State machine signals + self.emit_line("// TX state machine"); + self.emit_line("reg tx_busy;"); + self.emit_line("reg [2:0] tx_state;"); + self.emit_line("reg [7:0] tx_shift_reg;"); + self.emit_line("reg [2:0] tx_bit_index;"); + self.emit_line("reg [31:0] tx_baud_counter;"); + self.emit_line(""); + + self.emit_line("// RX state machine"); + self.emit_line("reg [2:0] rx_state;"); + self.emit_line("reg [7:0] rx_shift_reg;"); + self.emit_line("reg [2:0] rx_bit_index;"); + self.emit_line("reg [31:0] rx_baud_counter;"); + self.emit_line("reg [2:0] rx_sync [2:0];"); + self.emit_line("reg rx_framing_error;"); + self.emit_line(""); + + // TX states + self.emit_line("localparam TX_IDLE = 3'd0;"); + self.emit_line("localparam TX_START = 3'd1;"); + self.emit_line("localparam TX_DATA = 3'd2;"); + self.emit_line("localparam TX_STOP = 3'd3;"); + self.emit_line(""); + + // RX states + self.emit_line("localparam RX_IDLE = 3'd0;"); + self.emit_line("localparam RX_START = 3'd1;"); + self.emit_line("localparam RX_DATA = 3'd2;"); + self.emit_line("localparam RX_STOP = 3'd3;"); + self.emit_line(""); + + // TX line output + self.emit_line("// TX line state"); + self.emit_line("assign uart_tx = ("); + self.indent(); + self.emit_line("tx_state == TX_IDLE ? 1'b1 :"); + self.emit_line("tx_state == TX_START ? 1'b0 :"); + self.emit_line("tx_shift_reg[tx_bit_index]"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + + // RX input synchronization + self.emit_line("// RX input synchronizer"); + self.emit_line("always @(posedge clk) begin"); + self.indent(); + self.emit_line("rx_sync <= {rx_sync[1], rx_sync[0], uart_rx};"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + + // TX state machine + self.emit_line("// TX state machine"); + self.emit_line("always @(posedge clk) begin"); + self.indent(); + self.emit_line("if (!rst_n) begin"); + self.indent(); + self.emit_line("tx_state <= TX_IDLE;"); + self.emit_line("tx_busy <= 1'b0;"); + self.emit_line("tx_baud_counter <= 32'd0;"); + self.dedent(); + self.emit_line("end else begin"); + self.indent(); + self.emit_line("tx_baud_counter <= tx_baud_counter + 1'b1;"); + self.emit_line(""); + self.emit_line("case (tx_state)"); + self.indent(); + self.emit_line("TX_IDLE: begin"); + self.indent(); + self.emit_line("// Idle state, wait for data"); + self.dedent(); + self.emit_line("end"); + self.emit_line("TX_START: begin"); + self.indent(); + self.emit_line("if (tx_baud_counter >= BAUD_DIVISOR) begin"); + self.indent(); + self.emit_line("tx_state <= TX_DATA;"); + self.emit_line("tx_baud_counter <= 32'd0;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("TX_DATA: begin"); + self.indent(); + self.emit_line("if (tx_baud_counter >= BAUD_DIVISOR) begin"); + self.indent(); + self.emit_line("tx_baud_counter <= 32'd0;"); + self.emit_line("tx_bit_index <= tx_bit_index + 1'b1;"); + self.emit_line("if (tx_bit_index >= 3'd7) begin"); + self.indent(); + self.emit_line("tx_state <= TX_STOP;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("TX_STOP: begin"); + self.indent(); + self.emit_line("if (tx_baud_counter >= BAUD_DIVISOR) begin"); + self.indent(); + self.emit_line("tx_state <= TX_IDLE;"); + self.emit_line("tx_busy <= 1'b0;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("endcase"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + + // RX state machine + self.emit_line("// RX state machine"); + self.emit_line("always @(posedge clk) begin"); + self.indent(); + self.emit_line("if (!rst_n) begin"); + self.indent(); + self.emit_line("rx_state <= RX_IDLE;"); + self.emit_line("rx_baud_counter <= 32'd0;"); + self.emit_line("rx_framing_error <= 1'b0;"); + self.dedent(); + self.emit_line("end else begin"); + self.indent(); + self.emit_line("rx_baud_counter <= rx_baud_counter + 1'b1;"); + self.emit_line(""); + self.emit_line("case (rx_state)"); + self.indent(); + self.emit_line("RX_IDLE: begin"); + self.indent(); + self.emit_line("if (!rx_sync[1]) begin"); + self.indent(); + self.emit_line("// Start bit detected"); + self.emit_line("rx_baud_counter <= 32'd0;"); + self.emit_line("rx_state <= RX_START;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("RX_START: begin"); + self.indent(); + self.emit_line("if (rx_baud_counter >= BAUD_DIVISOR) begin"); + self.indent(); + self.emit_line("rx_baud_counter <= 32'd0;"); + self.emit_line("if (!rx_sync[1]) begin"); + self.indent(); + self.emit_line("rx_state <= RX_DATA;"); + self.emit_line("rx_bit_index <= 3'd0;"); + self.dedent(); + self.emit_line("end else begin"); + self.indent(); + self.emit_line("rx_state <= RX_IDLE;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("RX_DATA: begin"); + self.indent(); + self.emit_line("if (rx_baud_counter >= BAUD_DIVISOR) begin"); + self.indent(); + self.emit_line("rx_baud_counter <= 32'd0;"); + self.emit_line("rx_shift_reg <= {rx_shift_reg[6:0], rx_sync[1]};"); + self.emit_line("rx_bit_index <= rx_bit_index + 1'b1;"); + self.emit_line("if (rx_bit_index >= 3'd7) begin"); + self.indent(); + self.emit_line("rx_state <= RX_STOP;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("RX_STOP: begin"); + self.indent(); + self.emit_line("if (rx_baud_counter >= BAUD_DIVISOR) begin"); + self.indent(); + self.emit_line("rx_baud_counter <= 32'd0;"); + self.emit_line("// Check stop bit (should be high)"); + self.emit_line("rx_framing_error <= !rx_sync[1];"); + self.emit_line("rx_state <= RX_IDLE;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("endcase"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + + self.emit_line("endmodule"); + self.emit_line(""); + } + + // 3. SPI Module Emission + + // emit_spi_module() 650 void + // Generate SPI master module from spec (Mode 0) + fn emit_spi_module(self: *FpgaCodegen) void { + self.emit_line("// SPI Master Module"); + self.emit_line("// Mode 0: CPOL=0, CPHA=0"); + self.emit_line(""); + self.emit_line("module SPI_Master ("); + self.indent(); + self.emit_line("// Clock and reset"); + self.emit_line("input wire clk,"); + self.emit_line("input wire rst_n,"); + self.emit_line(""); + self.emit_line("// SPI interface"); + self.emit_line("input wire miso,"); + self.emit_line("output wire cs,"); + self.emit_line("output wire sck,"); + self.emit_line("output wire mosi"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + + // SPI prescaler values + self.emit_line("// Prescaler selection (divide system clock)"); + self.emit_line("localparam PRESCALER_2 = 3'd0;"); + self.emit_line("localparam PRESCALER_4 = 3'd1;"); + self.emit_line("localparam PRESCALER_8 = 3'd2;"); + self.emit_line("localparam PRESCALER_16 = 3'd3;"); + self.emit_line("localparam PRESCALER_32 = 3'd4;"); + self.emit_line("localparam PRESCALER_64 = 3'd5;"); + self.emit_line(""); + + // SPI state machine + self.emit_line("// SPI state machine"); + self.emit_line("reg [1:0] spi_state;"); + self.emit_line("reg [2:0] tx_state;"); + self.emit_line("reg cs_asserted;"); + self.emit_line("reg busy;"); + self.emit_line(""); + + // SPI configuration + self.emit_line("reg [2:0] prescaler;"); + self.emit_line("reg [4:0] data_width;"); + self.emit_line("reg [31:0] tx_data;"); + self.emit_line("reg [31:0] rx_data;"); + self.emit_line("reg [4:0] bit_count;"); + self.emit_line("reg [31:0] bit_counter;"); + self.emit_line(""); + + // States + self.emit_line("localparam SPI_IDLE = 2'd0;"); + self.emit_line("localparam SPI_CS_ASSERT = 2'd1;"); + self.emit_line("localparam SPI_TRANSFER = 2'd2;"); + self.emit_line("localparam SPI_CS_DEASSERT = 2'd3;"); + self.emit_line(""); + + // TX states + self.emit_line("localparam TX_BIT = 2'd0;"); + self.emit_line("localparam RX_BIT = 2'd1;"); + self.emit_line("localparam WAIT_EDGE = 2'd2;"); + self.emit_line(""); + + // Default values + self.emit_line("// Default configuration"); + self.emit_line("initial begin"); + self.indent(); + self.emit_line("spi_state <= SPI_IDLE;"); + self.emit_line("tx_state <= TX_BIT;"); + self.emit_line("cs_asserted <= 1'b0;"); + self.emit_line("busy <= 1'b0;"); + self.emit_line("prescaler <= PRESCALER_16;"); + self.emit_line("data_width <= 4'd8;"); + self.emit_line("bit_count <= 4'd0;"); + self.emit_line("bit_counter <= 32'd0;"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + + // Chip select output + self.emit_line("// Chip select output"); + self.emit_line("assign cs = cs_asserted;"); + self.emit_line(""); + + // Clock output (Mode 0: idle low) + self.emit_line("// SCK output (Mode 0: CPOL=0)"); + self.emit_line("assign sck = (spi_state == SPI_IDLE) ? 1'b0 :"); + self.indent(); + self.emit_line("tx_state == TX_BIT ? 1'b0 : 1'b1;"); + self.dedent(); + self.emit_line(""); + + // MOSI output + self.emit_line("// MOSI output"); + self.emit_line("assign mosi = busy && spi_state == SPI_TRANSFER ? tx_data[data_width - bit_count - 1] : 1'b0;"); + self.emit_line(""); + + // SPI state machine + self.emit_line("// SPI state machine"); + self.emit_line("always @(posedge clk) begin"); + self.indent(); + self.emit_line("if (!rst_n) begin"); + self.indent(); + self.emit_line("spi_state <= SPI_IDLE;"); + self.emit_line("cs_asserted <= 1'b0;"); + self.emit_line("busy <= 1'b0;"); + self.emit_line("bit_count <= 4'd0;"); + self.emit_line("bit_counter <= 32'd0;"); + self.dedent(); + self.emit_line("end else begin"); + self.indent(); + self.emit_line("case (spi_state)"); + self.indent(); + self.emit_line("SPI_IDLE: begin"); + self.indent(); + self.emit_line("// Idle state"); + self.dedent(); + self.emit_line("end"); + self.emit_line("SPI_CS_ASSERT: begin"); + self.indent(); + self.emit_line("// Assert chip select"); + self.emit_line("cs_asserted <= 1'b1;"); + self.emit_line("if (bit_counter >= 4'd5) begin"); + self.indent(); + self.emit_line("cs_asserted <= 1'b1;"); + self.emit_line("spi_state <= SPI_TRANSFER;"); + self.emit_line("tx_state <= TX_BIT;"); + self.emit_line("bit_counter <= 32'd0;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("SPI_TRANSFER: begin"); + self.indent(); + self.emit_line("// Transfer bits"); + self.emit_line("bit_counter <= bit_counter + 1'b1;"); + self.emit_line(""); + self.emit_line("case (tx_state)"); + self.indent(); + self.emit_line("TX_BIT: begin"); + self.indent(); + self.emit_line("if (bit_counter >= 4'd8) begin"); + self.indent(); + self.emit_line("tx_state <= RX_BIT;"); + self.emit_line("bit_counter <= 32'd0;"); + self.dedent(); + self.emit_line("end"); + self.emit_line("RX_BIT: begin"); + self.indent(); + self.emit_line("if (bit_counter >= 4'd8) begin"); + self.indent(); + self.emit_line("// Sample MISO"); + self.emit_line("rx_data <= {rx_data[30:0], miso};"); + self.emit_line("bit_count <= bit_count + 1'b1;"); + self.emit_line("bit_counter <= 32'd0;"); + self.emit_line("if (bit_count >= data_width) begin"); + self.indent(); + self.emit_line("tx_state <= WAIT_EDGE;"); + self.dedent(); + self.emit_line("end else begin"); + self.indent(); + self.emit_line("tx_state <= TX_BIT;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("WAIT_EDGE: begin"); + self.indent(); + self.emit_line("if (bit_counter >= 4'd8) begin"); + self.indent(); + self.emit_line("spi_state <= SPI_CS_DEASSERT;"); + self.emit_line("bit_counter <= 32'd0;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("endcase"); + self.dedent(); + self.emit_line("end"); + self.emit_line("SPI_CS_DEASSERT: begin"); + self.indent(); + self.emit_line("// Deassert chip select"); + self.emit_line("cs_asserted <= 1'b0;"); + self.emit_line("if (bit_counter >= 4'd5) begin"); + self.indent(); + self.emit_line("spi_state <= SPI_IDLE;"); + self.emit_line("busy <= 1'b0;"); + self.emit_line("bit_counter <= 32'd0;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("endcase"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + + self.emit_line("endmodule"); + self.emit_line(""); + } + + // 4. MAC Module Emission + + // emit_mac_module() 829 void + // Generate ZeroDSP MAC module from spec + fn emit_mac_module(self: *FpgaCodegen) void { + self.emit_line("// ZeroDSP MAC Module"); + self.emit_line("// Ternary LUT multiplication with 8 parallel units"); + self.emit_line(""); + self.emit_line("module ZeroDSP_MAC ("); + self.indent(); + self.emit_line("// Clock and reset"); + self.emit_line("input wire clk,"); + self.emit_line("input wire rst_n,"); + self.emit_line(""); + self.emit_line("// Operands (27 trits each)"); + self.emit_line("input wire [26:0] a,"); + self.emit_line("input wire [26:0] b,"); + self.emit_line(""); + self.emit_line("// Accumulator input/output"); + self.emit_line("input wire [31:0] acc_in,"); + self.emit_line("output wire [31:0] acc_out,"); + self.emit_line(""); + self.emit_line("// Control"); + self.emit_line("input wire enable,"); + self.emit_line("output wire valid"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + + // Ternary LUT entries (3x3 = 9 combinations) + self.emit_line("// Ternary LUT (3^3 entries)"); + self.emit_line("// Values: 0, 1, -1 (trinary)"); + self.emit_line("// Mapping: 0->00, 1->01, -1->10"); + self.emit_line("localparam TRIT_NEG_2 = 2'b00;"); + self.emit_line("localparam TRIT_NEG_1 = 2'b01;"); + self.emit_line("localparam TRIT_ZERO = 2'b10;"); + self.emit_line("localparam TRIT_POS_1 = 2'b11;"); + self.emit_line(""); + + // LUT lookup (9 entries for 3x3 combinations) + self.emit_line("// Ternary multiplication LUT"); + self.emit_line("function [1:0] ternary_mul_lut;"); + self.indent(); + self.emit_line("input [1:0] trit_a;"); + self.emit_line("input [1:0] trit_b;"); + self.emit_line("case ({trit_a, trit_b})"); + self.indent(); + self.emit_line("// -1 * -1 = 1"); + self.emit_line("4'b0000: ternary_mul_lut = TRIT_POS_1;"); + self.emit_line("// -1 * 0 = 0"); + self.emit_line("4'b0010: ternary_mul_lut = TRIT_ZERO;"); + self.emit_line("// -1 * 1 = -1"); + self.emit_line("4'b0100: ternary_mul_lut = TRIT_NEG_1;"); + self.emit_line("// 0 * -1 = 0"); + self.emit_line("4'b0110: ternary_mul_lut = TRIT_ZERO;"); + self.emit_line("// 0 * 0 = 0"); + self.emit_line("4'b1000: ternary_mul_lut = TRIT_ZERO;"); + self.emit_line("// 0 * 1 = 0"); + self.emit_line("4'b1010: ternary_mul_lut = TRIT_ZERO;"); + self.emit_line("// 1 * -1 = -1"); + self.emit_line("4'b1100: ternary_mul_lut = TRIT_NEG_1;"); + self.emit_line("// 1 * 0 = 0"); + self.emit_line("4'b1110: ternary_mul_lut = TRIT_ZERO;"); + self.emit_line("// 1 * 1 = 1"); + self.emit_line("4'b0001: ternary_mul_lut = TRIT_POS_1;"); + self.dedent(); + self.emit_line("endcase"); + self.emit_line("endfunction"); + self.emit_line(""); + + // 8-bit accumulator for partial products + self.emit_line("// Accumulator for partial products"); + self.emit_line("reg [31:0] accumulator;"); + self.emit_line("reg [7:0] unit_idx;"); + self.emit_line(""); + self.emit_line("initial begin"); + self.indent(); + self.emit_line("accumulator <= 32'd0;"); + self.emit_line("unit_idx <= 8'd0;"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + + // MAC operation (pipelined over 8 cycles) + self.emit_line("// MAC operation"); + self.emit_line("always @(posedge clk) begin"); + self.indent(); + self.emit_line("if (!rst_n) begin"); + self.indent(); + self.emit_line("accumulator <= 32'd0;"); + self.emit_line("unit_idx <= 8'd0;"); + self.emit_line("valid <= 1'b0;"); + self.dedent(); + self.emit_line("end else if (enable) begin"); + self.indent(); + self.emit_line("// Process 8 parallel units over 8 cycles"); + self.emit_line("case (unit_idx)"); + self.indent(); + self.emit_line("8'd0: unit_idx <= unit_idx + 1'b1;"); + self.emit_line("8'd1: unit_idx <= unit_idx + 1'b1;"); + self.emit_line("8'd2: unit_idx <= unit_idx + 1'b1;"); + self.emit_line("8'd3: unit_idx <= unit_idx + 1'b1;"); + self.emit_line("8'd4: unit_idx <= unit_idx + 1'b1;"); + self.emit_line("8'd5: unit_idx <= unit_idx + 1'b1;"); + self.emit_line("8'd6: unit_idx <= unit_idx + 1'b1;"); + self.emit_line("8'd7: begin"); + self.indent(); + self.emit_line("valid <= 1'b1;"); + self.emit_line("unit_idx <= 8'd0;"); + self.dedent(); + self.emit_line("endcase"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + + // Accumulator output + self.emit_line("// Accumulator output"); + self.emit_line("assign acc_out = accumulator;"); + self.emit_line(""); + + self.emit_line("endmodule"); + self.emit_line(""); + } + + // 5. Helper Functions + + // emit(s: []const u8) 998 void + // Append string to output + fn emit(self: *FpgaCodegen, s: []const u8) void { + self.output.append(s); + } + + // emit_line(s: []const u8) 999 void + // Append string with newline + fn emit_line(self: *FpgaCodegen, s: []const u8) void { + self.output.append(s); + self.output.append("\n"); + + var i: u32 = 0; + while (i < self.indent_level) : (i += 1) { + self.output.append(" "); + } + } + + // emit_int(n: anytype) 1000 void + // Emit integer value + fn emit_int(self: *FpgaCodegen, n: anytype) void { + self.emit(int_to_str(n)); + } + + // indent() 1001 void + // Increase indent level + fn indent(self: *FpgaCodegen) void { + self.indent_level += 1; + } + + // dedent() 1002 void + // Decrease indent level + fn dedent(self: *FpgaCodegen) void { + if (self.indent_level > 0) { + self.indent_level -= 1; + } + } + + // int_to_str(n: anytype) 1003 []const u8 + // Convert small non-negative integer to decimal string + // Supports range 0..9999 1004 sufficient for pin/port/module IDs + fn int_to_str(n: anytype) []const u8 { + if (n == 0) { + return "0"; + } + + var buf : [5]u8 = [0u8; 5]; + var val : u32 = n; + var len : usize = 0; + + while (val > 0) { + const digit = val % 10; + buf[len] = (digit as u8) + 48; + len = len + 1; + val = val / 10; + } + + // Reverse in-place + var i : usize = 0; + while (i < len / 2) { + const tmp = buf[i]; + buf[i] = buf[len - 1 - i]; + buf[len - 1 - i] = tmp; + i = i + 1; + } + + return buf[0..len]; + } + + // ======================================================================== + // 6. Bridge Module Emission + // ======================================================================== + + fn emit_bridge_module(self: *FpgaCodegen) void { + self.emit_line("// FPGA Bridge Module"); + self.emit_line("// Command parser for UART/SPI to MAC dispatch"); + self.emit_line(""); + self.emit_line("module FPGA_Bridge ("); + self.indent(); + self.emit_line("input wire clk,"); + self.emit_line("input wire rst_n,"); + self.emit_line("input wire [7:0] rx_data,"); + self.emit_line("input wire rx_valid,"); + self.emit_line("output wire [7:0] tx_data,"); + self.emit_line("output wire tx_ready,"); + self.emit_line("output wire [26:0] mac_a,"); + self.emit_line("output wire [26:0] mac_b,"); + self.emit_line("output wire [31:0] mac_acc_in,"); + self.emit_line("output wire mac_start,"); + self.emit_line("input wire [31:0] mac_acc_out,"); + self.emit_line("input wire mac_valid"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + self.emit_line("// Command parser state"); + self.emit_line("reg [7:0] cmd_buffer [0:5];"); + self.emit_line("reg [2:0] cmd_idx;"); + self.emit_line("reg cmd_ready;"); + self.emit_line("reg [7:0] tx_resp;"); + self.emit_line("reg tx_has_resp;"); + self.emit_line(""); + self.emit_line("// Opcodes"); + self.emit_line("localparam CMD_NOP = 8'h00;"); + self.emit_line("localparam CMD_MAC_MUL = 8'h01;"); + self.emit_line("localparam CMD_MAC_DOT = 8'h02;"); + self.emit_line("localparam CMD_STATUS = 8'h30;"); + self.emit_line("localparam CMD_RESET = 8'hFF;"); + self.emit_line(""); + self.emit_line("always @(posedge clk) begin"); + self.indent(); + self.emit_line("if (!rst_n) begin"); + self.indent(); + self.emit_line("cmd_idx <= 3'd0;"); + self.emit_line("cmd_ready <= 1'b0;"); + self.emit_line("tx_has_resp <= 1'b0;"); + self.dedent(); + self.emit_line("end else if (rx_valid) begin"); + self.indent(); + self.emit_line("cmd_buffer[cmd_idx] <= rx_data;"); + self.emit_line("cmd_idx <= cmd_idx + 1'b1;"); + self.emit_line("if (cmd_idx >= 3'd5) begin"); + self.indent(); + self.emit_line("cmd_ready <= 1'b1;"); + self.emit_line("cmd_idx <= 3'd0;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + self.emit_line("assign mac_start = cmd_ready;"); + self.emit_line("assign tx_data = tx_has_resp ? mac_acc_out[7:0] : 8'h00;"); + self.emit_line("assign tx_ready = tx_has_resp;"); + self.emit_line(""); + self.emit_line("endmodule"); + self.emit_line(""); + } + + // ======================================================================== + // 7. Memory Module Emission + // ======================================================================== + + fn emit_memory_module(self: *FpgaCodegen) void { + self.emit_line("// Memory Controller Module"); + self.emit_line("// Dual-port BRAM interface"); + self.emit_line(""); + self.emit_line("module MemoryController ("); + self.indent(); + self.emit_line("input wire clk,"); + self.emit_line("input wire rst_n,"); + self.emit_line("input wire [15:0] addr_a,"); + self.emit_line("input wire [31:0] wr_data_a,"); + self.emit_line("input wire wr_en_a,"); + self.emit_line("output wire [31:0] rd_data_a,"); + self.emit_line("input wire [15:0] addr_b,"); + self.emit_line("input wire [31:0] wr_data_b,"); + self.emit_line("input wire wr_en_b,"); + self.emit_line("output wire [31:0] rd_data_b"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + self.emit_line("// Dual-port BRAM"); + self.emit_line("(* ram_style = \"block\" *) reg [31:0] bram [0:65535];"); + self.emit_line(""); + self.emit_line("// Port A"); + self.emit_line("always @(posedge clk) begin"); + self.indent(); + self.emit_line("if (wr_en_a) bram[addr_a] <= wr_data_a;"); + self.emit_line("rd_data_a <= bram[addr_a];"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + self.emit_line("// Port B"); + self.emit_line("always @(posedge clk) begin"); + self.indent(); + self.emit_line("if (wr_en_b) bram[addr_b] <= wr_data_b;"); + self.emit_line("rd_data_b <= bram[addr_b];"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + self.emit_line("endmodule"); + self.emit_line(""); + } + + // ======================================================================== + // 8. FIFO Module Emission + // ======================================================================== + + fn emit_fifo_module(self: *FpgaCodegen) void { + self.emit_line("// Synchronous FIFO Module"); + self.emit_line("// Configurable depth, FWFT output"); + self.emit_line(""); + self.emit_line("module SyncFIFO ("); + self.indent(); + self.emit_line("input wire clk,"); + self.emit_line("input wire rst_n,"); + self.emit_line("input wire [31:0] din,"); + self.emit_line("input wire wr_en,"); + self.emit_line("output wire full,"); + self.emit_line("output wire [31:0] dout,"); + self.emit_line("input wire rd_en,"); + self.emit_line("output wire empty"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + self.emit_line("localparam DEPTH = 16;"); + self.emit_line("localparam ADDR_W = 4;"); + self.emit_line(""); + self.emit_line("reg [31:0] mem [0:DEPTH-1];"); + self.emit_line("reg [ADDR_W:0] wr_ptr;"); + self.emit_line("reg [ADDR_W:0] rd_ptr;"); + self.emit_line(""); + self.emit_line("assign full = (wr_ptr[ADDR_W] != rd_ptr[ADDR_W]) &&"); + self.indent(); + self.emit_line("(wr_ptr[ADDR_W-1:0] == rd_ptr[ADDR_W-1:0]);"); + self.dedent(); + self.emit_line("assign empty = (wr_ptr == rd_ptr);"); + self.emit_line("assign dout = mem[rd_ptr[ADDR_W-1:0]];"); + self.emit_line(""); + self.emit_line("always @(posedge clk) begin"); + self.indent(); + self.emit_line("if (!rst_n) begin"); + self.indent(); + self.emit_line("wr_ptr <= {(ADDR_W+1){1'b0}};"); + self.emit_line("rd_ptr <= {(ADDR_W+1){1'b0}};"); + self.dedent(); + self.emit_line("end else begin"); + self.indent(); + self.emit_line("if (wr_en && !full) begin"); + self.indent(); + self.emit_line("mem[wr_ptr[ADDR_W-1:0]] <= din;"); + self.emit_line("wr_ptr <= wr_ptr + 1'b1;"); + self.dedent(); + self.emit_line("end"); + self.emit_line("if (rd_en && !empty) begin"); + self.indent(); + self.emit_line("rd_ptr <= rd_ptr + 1'b1;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + self.emit_line("endmodule"); + self.emit_line(""); + } + + // ======================================================================== + // 9. AXI4-Lite Module Emission + // ======================================================================== + + fn emit_axi4_module(self: *FpgaCodegen) void { + self.emit_line("// AXI4-Lite Slave Module"); + self.emit_line("// 32-bit data, 16-bit address"); + self.emit_line(""); + self.emit_line("module AXI4_Lite_Slave ("); + self.indent(); + self.emit_line("input wire clk,"); + self.emit_line("input wire rst_n,"); + self.emit_line("// AW channel"); + self.emit_line("input wire [15:0] awaddr,"); + self.emit_line("input wire awvalid,"); + self.emit_line("output wire awready,"); + self.emit_line("// W channel"); + self.emit_line("input wire [31:0] wdata,"); + self.emit_line("input wire [3:0] wstrb,"); + self.emit_line("input wire wvalid,"); + self.emit_line("output wire wready,"); + self.emit_line("// B channel"); + self.emit_line("output wire [1:0] bresp,"); + self.emit_line("output wire bvalid,"); + self.emit_line("input wire bready,"); + self.emit_line("// AR channel"); + self.emit_line("input wire [15:0] araddr,"); + self.emit_line("input wire arvalid,"); + self.emit_line("output wire arready,"); + self.emit_line("// R channel"); + self.emit_line("output wire [31:0] rdata,"); + self.emit_line("output wire [1:0] rresp,"); + self.emit_line("output wire rvalid,"); + self.emit_line("input wire rready"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + self.emit_line("reg awready_r, wready_r, arready_r;"); + self.emit_line("reg bvalid_r, rvalid_r;"); + self.emit_line("reg [1:0] bresp_r, rresp_r;"); + self.emit_line("reg [31:0] rdata_r;"); + self.emit_line("reg [31:0] reg_file [0:255];"); + self.emit_line(""); + self.emit_line("assign awready = awready_r;"); + self.emit_line("assign wready = wready_r;"); + self.emit_line("assign bresp = bresp_r;"); + self.emit_line("assign bvalid = bvalid_r;"); + self.emit_line("assign arready = arready_r;"); + self.emit_line("assign rdata = rdata_r;"); + self.emit_line("assign rresp = rresp_r;"); + self.emit_line("assign rvalid = rvalid_r;"); + self.emit_line(""); + self.emit_line("always @(posedge clk) begin"); + self.indent(); + self.emit_line("if (!rst_n) begin"); + self.indent(); + self.emit_line("awready_r <= 1'b1;"); + self.emit_line("wready_r <= 1'b1;"); + self.emit_line("bvalid_r <= 1'b0;"); + self.emit_line("arready_r <= 1'b1;"); + self.emit_line("rvalid_r <= 1'b0;"); + self.dedent(); + self.emit_line("end else begin"); + self.indent(); + self.emit_line("// Write path"); + self.emit_line("if (awvalid && awready_r) awready_r <= 1'b0;"); + self.emit_line("if (wvalid && wready_r) begin"); + self.indent(); + self.emit_line("wready_r <= 1'b0;"); + self.emit_line("reg_file[awaddr[9:2]] <= wdata;"); + self.emit_line("bvalid_r <= 1'b1;"); + self.emit_line("bresp_r <= 2'b00;"); + self.dedent(); + self.emit_line("end"); + self.emit_line("if (bready && bvalid_r) bvalid_r <= 1'b0;"); + self.emit_line("// Read path"); + self.emit_line("if (arvalid && arready_r) begin"); + self.indent(); + self.emit_line("arready_r <= 1'b0;"); + self.emit_line("rdata_r <= reg_file[araddr[9:2]];"); + self.emit_line("rvalid_r <= 1'b1;"); + self.emit_line("rresp_r <= 2'b00;"); + self.dedent(); + self.emit_line("end"); + self.emit_line("if (rready && rvalid_r) rvalid_r <= 1'b0;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + self.emit_line("endmodule"); + self.emit_line(""); + } + + // ======================================================================== + // 10. APB Bridge Module Emission + // ======================================================================== + + fn emit_apb_bridge_module(self: *FpgaCodegen) void { + self.emit_line("// APB Bridge Module"); + self.emit_line("// APB4 slave with 32-bit data path"); + self.emit_line(""); + self.emit_line("module APB_Bridge ("); + self.indent(); + self.emit_line("input wire clk,"); + self.emit_line("input wire rst_n,"); + self.emit_line("input wire [15:0] paddr,"); + self.emit_line("input wire psel,"); + self.emit_line("input wire penable,"); + self.emit_line("input wire pwrite,"); + self.emit_line("input wire [31:0] pwdata,"); + self.emit_line("output wire [31:0] prdata,"); + self.emit_line("output wire pready,"); + self.emit_line("output wire pslverr"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + self.emit_line("reg [31:0] apb_regs [0:255];"); + self.emit_line("reg pready_r;"); + self.emit_line("reg pslverr_r;"); + self.emit_line("reg [31:0] prdata_r;"); + self.emit_line(""); + self.emit_line("assign pready = pready_r;"); + self.emit_line("assign pslverr = pslverr_r;"); + self.emit_line("assign prdata = prdata_r;"); + self.emit_line(""); + self.emit_line("always @(posedge clk) begin"); + self.indent(); + self.emit_line("if (!rst_n) begin"); + self.indent(); + self.emit_line("pready_r <= 1'b0;"); + self.emit_line("pslverr_r <= 1'b0;"); + self.emit_line("prdata_r <= 32'd0;"); + self.dedent(); + self.emit_line("end else begin"); + self.indent(); + self.emit_line("pready_r <= 1'b0;"); + self.emit_line("if (psel && penable) begin"); + self.indent(); + self.emit_line("pready_r <= 1'b1;"); + self.emit_line("if (pwrite) begin"); + self.indent(); + self.emit_line("apb_regs[paddr[9:2]] <= pwdata;"); + self.dedent(); + self.emit_line("end else begin"); + self.indent(); + self.emit_line("prdata_r <= apb_regs[paddr[9:2]];"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + self.emit_line("endmodule"); + self.emit_line(""); + } + + // ======================================================================== + // 11. GF16 Accelerator Module Emission + // ======================================================================== + + fn emit_gf16_accel_module(self: *FpgaCodegen) void { + self.emit_line("// GF(1^2) Accelerator Module"); + self.emit_line("// Ternary Galois Field arithmetic"); + self.emit_line(""); + self.emit_line("module GF16_Accel ("); + self.indent(); + self.emit_line("input wire clk,"); + self.emit_line("input wire rst_n,"); + self.emit_line("input wire [3:0] a,"); + self.emit_line("input wire [3:0] b,"); + self.emit_line("input wire [1:0] op,"); + self.emit_line("input wire start,"); + self.emit_line("output wire [3:0] result,"); + self.emit_line("output wire done"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + self.emit_line("// GF(1^2) addition = XOR"); + self.emit_line("wire [3:0] add_result = a ^ b;"); + self.emit_line(""); + self.emit_line("// GF(1^2) multiplication via LUT"); + self.emit_line("reg [3:0] mul_result;"); + self.emit_line("always @(*) begin"); + self.indent(); + self.emit_line("case ({a, b})"); + self.indent(); + self.emit_line("8'h00: mul_result = 4'h0;"); + self.emit_line("8'h11: mul_result = 4'h1;"); + self.emit_line("8'h12: mul_result = 4'h2;"); + self.emit_line("8'h22: mul_result = 4'h4;"); + self.emit_line("8'h23: mul_result = 4'h6;"); + self.emit_line("8'h33: mul_result = 4'h5;"); + self.emit_line("default: mul_result = 4'h0;"); + self.dedent(); + self.emit_line("endcase"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + self.emit_line("// Operation select"); + self.emit_line("reg [3:0] result_r;"); + self.emit_line("reg done_r;"); + self.emit_line("assign result = result_r;"); + self.emit_line("assign done = done_r;"); + self.emit_line(""); + self.emit_line("always @(posedge clk) begin"); + self.indent(); + self.emit_line("if (!rst_n) begin"); + self.indent(); + self.emit_line("result_r <= 4'd0;"); + self.emit_line("done_r <= 1'b0;"); + self.dedent(); + self.emit_line("end else if (start) begin"); + self.indent(); + self.emit_line("case (op)"); + self.indent(); + self.emit_line("2'd0: result_r <= add_result;"); + self.emit_line("2'd1: result_r <= mul_result;"); + self.emit_line("default: result_r <= 4'd0;"); + self.dedent(); + self.emit_line("endcase"); + self.emit_line("done_r <= 1'b1;"); + self.dedent(); + self.emit_line("end else begin"); + self.indent(); + self.emit_line("done_r <= 1'b0;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + self.emit_line("endmodule"); + self.emit_line(""); + } + + // ======================================================================== + // 12. HIR-Based Generic Module Emission + // ======================================================================== + + fn emit_generic_module(self: *FpgaCodegen, hir: HirModule) void { + self.emit_line("// Generated from HIR: "); + self.emit(hir.name); + self.emit_line(""); + self.emit_line("module "); + self.emit(hir.name); + self.emit_line(" ("); + self.indent(); + + // Emit ports + var i : usize = 0; + while (i < hir.port_count()) { + const port = hir.ports[i]; + const dir_str = if (port.direction == PortDir.input_dir) { "input" } + else if (port.direction == PortDir.output_dir) { "output" } + else { "inout" }; + self.emit(dir_str); + self.emit(" wire "); + if (port.width > 1) { + self.emit("["); + self.emit_int(port.width - 1); + self.emit(":0] "); + } + self.emit(port.name); + if (i + 1 < hir.port_count()) { + self.emit_line(","); + } else { + self.emit_line(""); + } + i = i + 1; + } + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + + // Emit internal signals + i = 0; + while (i < hir.signal_count()) { + const sig = hir.signals[i]; + const kind_str = if (sig.kind == SignalKind.reg_kind) { "reg" } else { "wire" }; + self.emit(kind_str); + self.emit(" "); + if (sig.width > 1) { + self.emit("["); + self.emit_int(sig.width - 1); + self.emit(":0] "); + } + self.emit(sig.name); + if (sig.kind == SignalKind.reg_kind && sig.reset_value != null) { + self.emit(" = "); + self.emit_int(sig.reset_value.?); + } + self.emit_line(";"); + i = i + 1; + } + if (hir.signal_count() > 0) { + self.emit_line(""); + } + + // Emit assignments + i = 0; + while (i < hir.assign_count()) { + const assign = hir.assigns[i]; + self.emit("assign "); + self.emit(assign.target); + self.emit(" = "); + self.emit(assign.value); + self.emit_line(";"); + i = i + 1; + } + if (hir.assign_count() > 0) { + self.emit_line(""); + } + + // Emit memory instances + i = 0; + while (i < hir.mems.len) { + if (hir.mems[i].name.len() == 0) { break; } + const mem = hir.mems[i]; + self.emit("(* ram_style = \""); + const style = if (mem.kind == MemKind.bram) { "block" } + else if (mem.kind == MemKind.dram) { "distributed" } + else { "block" }; + self.emit(style); + self.emit_line("\" *)"); + self.emit("reg ["); + self.emit_int(mem.data_width - 1); + self.emit(":0] "); + self.emit(mem.name); + self.emit(" [0:"); + self.emit_int(mem.depth - 1); + self.emit_line("];"); + self.emit_line(""); + i = i + 1; + } + + // Emit sub-module instances + i = 0; + while (i < hir.instances.len) { + if (hir.instances[i].name.len() == 0) { break; } + const inst = hir.instances[i]; + self.emit(inst.module_name); + self.emit(" "); + self.emit(inst.name); + self.emit_line(" ("); + self.indent(); + self.emit_line(".clk(clk),"); + self.emit_line(".rst_n(rst_n)"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + i = i + 1; + } + + self.emit_line("endmodule"); + self.emit_line(""); + } + + // ======================================================================== + // 13. Testbench Emission from HIR + // ======================================================================== + + fn emit_testbench_from_hir(self: *FpgaCodegen, hir: HirModule, period_ns: u32) void { + self.emit_line("// Auto-generated testbench for "); + self.emit(hir.name); + self.emit_line(""); + self.emit_line("`timescale 1ns / 1ps"); + self.emit_line(""); + self.emit("module tb_"); + self.emit(hir.name); + self.emit_line(";"); + + // Clock and reset + self.emit_line(""); + self.emit_line("reg clk = 1'b0;"); + self.emit_line("reg rst_n = 1'b0;"); + self.emit_line(""); + self.emit_line("always #"); + self.emit_int(period_ns / 2); + self.emit_line(" clk = ~clk;"); + self.emit_line(""); + + // Instantiate DUT ports as reg/wire + var i : usize = 0; + while (i < hir.port_count()) { + const port = hir.ports[i]; + if (port.direction == PortDir.input_dir) { + self.emit("reg "); + } else { + self.emit("wire "); + } + if (port.width > 1) { + self.emit("["); + self.emit_int(port.width - 1); + self.emit(":0] "); + } + self.emit(port.name); + self.emit_line(";"); + i = i + 1; + } + self.emit_line(""); + + // DUT instantiation + self.emit(hir.name); + self.emit_line(" dut ("); + self.indent(); + self.emit_line(".clk(clk),"); + self.emit_line(".rst_n(rst_n),"); + i = 0; + while (i < hir.port_count()) { + const port = hir.ports[i]; + if (port.name != "clk" and port.name != "rst_n") { + self.emit("."); + self.emit(port.name); + self.emit("("); + self.emit(port.name); + self.emit_line("),"); + } + i = i + 1; + } + self.dedent(); + self.emit_line(");"); + + // Test sequence + self.emit_line(""); + self.emit_line("initial begin"); + self.indent(); + self.emit_line("$display(\"--- Testbench for "); + self.emit(hir.name); + self.emit_line(" ---\");"); + self.emit_line("rst_n = 1'b0;"); + self.emit_line("#"); + self.emit_int(period_ns * 10); + self.emit_line(";"); + self.emit_line("rst_n = 1'b1;"); + self.emit_line("#"); + self.emit_int(period_ns * 10); + self.emit_line(";"); + self.emit_line("$display(\"Reset complete\");"); + self.emit_line("#"); + self.emit_int(period_ns * 100); + self.emit_line(";"); + self.emit_line("$display(\"--- ALL TESTS PASSED ---\");"); + self.emit_line("$finish;"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + self.emit_line("endmodule"); + self.emit_line(""); + } + + // ======================================================================== + // 14. XDC Constraint Emission from HIR + // ======================================================================== + + fn emit_xdc_from_hir(self: *FpgaCodegen, hir: HirModule, clk_pin: &str, rst_pin: &str, freq_hz: u32) void { + self.emit_line("## Auto-generated XDC constraints for "); + self.emit(hir.name); + self.emit_line(""); + self.emit_line(""); + + // Clock constraint + const period_ps = (1_000_000_000_000 / freq_hz); + self.emit_line("## Clock constraint"); + self.emit("create_clock -period "); + self.emit_int(period_ps / 1000); + self.emit(" [get_ports {"); + self.emit(clk_pin); + self.emit_line("}]"); + self.emit_line(""); + + // Reset false path + self.emit_line("## Reset false path"); + self.emit("set_false_path -from [get_ports {"); + self.emit(rst_pin); + self.emit_line("}]"); + self.emit_line(""); + + // Port pin assignments + self.emit_line("## Port pin assignments"); + var i : usize = 0; + while (i < hir.port_count()) { + const port = hir.ports[i]; + if (port.name == "clk" or port.name == "rst_n") { + i = i + 1; + continue; + } + self.emit("set_property PACKAGE_PIN ??? [get_ports {"); + self.emit(port.name); + self.emit_line("}]"); + self.emit("set_property IOSTANDARD LVCMOS33 [get_ports {"); + self.emit(port.name); + self.emit_line("}]"); + i = i + 1; + } + self.emit_line(""); + + // Bus port pin assignments + i = 0; + while (i < hir.bus_ports.len) { + if (hir.bus_ports[i].name.len() == 0) { break; } + const bp = hir.bus_ports[i]; + self.emit_line("## Bus port: "); + self.emit(bp.name); + self.emit_line(""); + + if (bp.bus_kind == BusKind.axi4_lite) { + self.emit_line("## AXI4-Lite signals"); + self.emit_line("set_property PACKAGE_PIN ??? [get_ports {axi_awvalid}]"); + self.emit_line("set_property IOSTANDARD LVCMOS33 [get_ports {axi_awvalid}]"); + self.emit_line("set_property PACKAGE_PIN ??? [get_ports {axi_arvalid}]"); + self.emit_line("set_property IOSTANDARD LVCMOS33 [get_ports {axi_arvalid}]"); + } else if (bp.bus_kind == BusKind.apb) { + self.emit_line("## APB signals"); + self.emit_line("set_property PACKAGE_PIN ??? [get_ports {apb_psel}]"); + self.emit_line("set_property IOSTANDARD LVCMOS33 [get_ports {apb_psel}]"); + self.emit_line("set_property PACKAGE_PIN ??? [get_ports {apb_penable}]"); + self.emit_line("set_property IOSTANDARD LVCMOS33 [get_ports {apb_penable}]"); + } else if (bp.bus_kind == BusKind.wishbone) { + self.emit_line("## Wishbone signals"); + self.emit_line("set_property PACKAGE_PIN ??? [get_ports {wb_cyc}]"); + self.emit_line("set_property IOSTANDARD LVCMOS33 [get_ports {wb_cyc}]"); + self.emit_line("set_property PACKAGE_PIN ??? [get_ports {wb_stb}]"); + self.emit_line("set_property IOSTANDARD LVCMOS33 [get_ports {wb_stb}]"); + } + self.emit_line(""); + i = i + 1; + } + + // Clock domain crossings + i = 0; + while (i < hir.clock_domains.len) { + if (hir.clock_domains[i].name.len() == 0) { break; } + const cd = hir.clock_domains[i]; + if (!cd.is_primary) { + self.emit_line("## CDC false path for "); + self.emit(cd.name); + self.emit_line(" clock domain"); + self.emit("set_clock_groups -asynchronous -group [get_clocks {clk_"); + self.emit(cd.name); + self.emit_line("}]"); + self.emit_line(""); + } + i = i + 1; + } + + // IO standard + self.emit_line("## Global IO standard"); + self.emit_line("set_property CFGBVS VCCO [current_design]"); + self.emit_line("set_property CONFIG_VOLTAGE 3.3 [current_design]"); + } + + // ======================================================================== + // 15. Conformance Testbench Emission (VCD + self-checking) + // ======================================================================== + + fn emit_conformance_testbench( + self: *FpgaCodegen, + module_name: &str, + clk_period_ns: u32, + vcd_file: &str, + ) void { + self.emit_line("// Auto-generated conformance testbench for "); + self.emit(module_name); + self.emit_line(""); + self.emit_line("// Compares VCD trace against conformance vectors"); + self.emit_line("`timescale 1ns / 1ps"); + self.emit_line(""); + self.emit("module tb_conformance_"); + self.emit(module_name); + self.emit_line(";"); + self.emit_line(""); + self.emit_line("// Scoreboard"); + self.emit_line("integer total_checks = 0;"); + self.emit_line("integer pass_count = 0;"); + self.emit_line("integer fail_count = 0;"); + self.emit_line(""); + self.emit_line("// Clock and reset"); + self.emit_line("reg clk = 1'b0;"); + self.emit_line("reg rst_n = 1'b0;"); + self.emit_line(""); + self.emit("always #"); + self.emit_int(clk_period_ns / 2); + self.emit_line(" clk = ~clk;"); + self.emit_line(""); + self.emit_line("// VCD dump"); + self.emit("initial begin"); + self.indent(); + self.emit("$dumpfile(\""); + self.emit(vcd_file); + self.emit_line("\");"); + self.emit_line("$dumpvars(0, tb_conformance_"); + self.emit(module_name); + self.emit_line(");"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + } + + fn emit_conformance_check( + self: *FpgaCodegen, + signal_name: &str, + expected_val: u32, + width: u32, + delay_ns: u32, + ) void { + self.emit_line("#"); + self.emit_int(delay_ns); + self.emit_line(";"); + self.emit("if ("); + self.emit(signal_name); + self.emit(" != "); + self.emit_int(expected_val); + self.emit_line(") begin"); + self.indent(); + self.emit("$display(\"FAIL: "); + self.emit(signal_name); + self.emit(" expected="); + self.emit_int(expected_val); + self.emit(" got=%0d\", "); + self.emit(signal_name); + self.emit_line(");"); + self.emit_line("fail_count = fail_count + 1;"); + self.dedent(); + self.emit_line("end else begin"); + self.indent(); + self.emit_line("pass_count = pass_count + 1;"); + self.dedent(); + self.emit_line("end"); + self.emit_line("total_checks = total_checks + 1;"); + } + + fn emit_conformance_check_masked( + self: *FpgaCodegen, + signal_name: &str, + expected_val: u32, + mask: u32, + delay_ns: u32, + ) void { + self.emit_line("#"); + self.emit_int(delay_ns); + self.emit_line(";"); + self.emit("if ((("); + self.emit(signal_name); + self.emit(") & "); + self.emit_int(mask); + self.emit(") != "); + self.emit_int(expected_val); + self.emit_line(") begin"); + self.indent(); + self.emit("$display(\"FAIL: "); + self.emit(signal_name); + self.emit(" masked expected="); + self.emit_int(expected_val); + self.emit(" mask="); + self.emit_int(mask); + self.emit("\");"); + self.emit_line("fail_count = fail_count + 1;"); + self.dedent(); + self.emit_line("end else begin"); + self.indent(); + self.emit_line("pass_count = pass_count + 1;"); + self.dedent(); + self.emit_line("end"); + self.emit_line("total_checks = total_checks + 1;"); + } + + fn emit_conformance_footer( + self: *FpgaCodegen, + module_name: &str, + ) void { + self.emit_line(""); + self.emit_line("// Final report"); + self.emit_line("initial begin"); + self.indent(); + self.emit_line("#1000;"); + self.emit("$display(\"--- Conformance Report for "); + self.emit(module_name); + self.emit_line(" ---\");"); + self.emit_line("$display(\"Passed: %0d / %0d\", pass_count, total_checks);"); + self.emit_line("if (fail_count > 0) begin"); + self.indent(); + self.emit_line("$display(\"FAILED: %0d check(s)\", fail_count);"); + self.emit_line("$finish;"); + self.dedent(); + self.emit_line("end else begin"); + self.indent(); + self.emit_line("$display(\"ALL CONFORMANCE CHECKS PASSED\");"); + self.emit_line("$finish;"); + self.dedent(); + self.emit_line("end"); + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + self.emit_line("endmodule"); + self.emit_line(""); + } + + // ======================================================================== + // 16. UART Conformance Testbench + // ======================================================================== + + fn emit_uart_conformance_tb(self: *FpgaCodegen) void { + self.emit_conformance_testbench("UART_Bridge", 20, "uart_conformance.vcd"); + + self.emit_line("// UART signals"); + self.emit_line("reg [7:0] tx_data = 8'h00;"); + self.emit_line("reg tx_valid = 1'b0;"); + self.emit_line("wire tx_ready;"); + self.emit_line("wire uart_tx_out;"); + self.emit_line("wire [7:0] rx_data;"); + self.emit_line("wire rx_valid;"); + self.emit_line(""); + self.emit_line("// DUT"); + self.emit_line("UART_Bridge dut ("); + self.indent(); + self.emit_line(".clk(clk),"); + self.emit_line(".rst_n(rst_n),"); + self.emit_line(".tx_data(tx_data),"); + self.emit_line(".tx_valid(tx_valid),"); + self.emit_line(".tx_ready(tx_ready),"); + self.emit_line(".uart_tx_out(uart_tx_out),"); + self.emit_line(".rx_data(rx_data),"); + self.emit_line(".rx_valid(rx_valid)"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + + self.emit_line("// Conformance test sequence"); + self.emit_line("initial begin"); + self.indent(); + self.emit_line("// Reset"); + self.emit_line("rst_n = 1'b0;"); + self.emit_line("#200;"); + self.emit_line("rst_n = 1'b1;"); + self.emit_line("#100;"); + self.emit_line(""); + + self.emit_line("// Vector: uart_tx_write_0x55"); + self.emit_line("// Expected bits: 0,1,0,1,0,1,0,1,0,1 (start + 0x55 LSB-first + stop)"); + self.emit_line("tx_data = 8'h55;"); + self.emit_line("tx_valid = 1'b1;"); + self.emit_line("#20;"); + self.emit_line("tx_valid = 1'b0;"); + self.emit_line("// Wait for TX complete (start + 8 data + stop = 10 bits * baud periods)"); + self.emit_line("#100000;"); + self.emit_line(""); + + self.emit_line("// Vector: uart_tx_write_0xAA"); + self.emit_line("tx_data = 8'hAA;"); + self.emit_line("tx_valid = 1'b1;"); + self.emit_line("#20;"); + self.emit_line("tx_valid = 1'b0;"); + self.emit_line("#100000;"); + self.emit_line(""); + + self.emit_line("// Vector: uart_tx_write_0x00"); + self.emit_line("tx_data = 8'h00;"); + self.emit_line("tx_valid = 1'b1;"); + self.emit_line("#20;"); + self.emit_line("tx_valid = 1'b0;"); + self.emit_line("#100000;"); + self.emit_line(""); + + self.emit_line("// Vector: uart_tx_write_0xFF"); + self.emit_line("tx_data = 8'hFF;"); + self.emit_line("tx_valid = 1'b1;"); + self.emit_line("#20;"); + self.emit_line("tx_valid = 1'b0;"); + self.emit_line("#100000;"); + + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + + self.emit_conformance_footer("UART_Bridge"); + } + + // ======================================================================== + // 17. MAC Conformance Testbench + // ======================================================================== + + fn emit_mac_conformance_tb(self: *FpgaCodegen) void { + self.emit_conformance_testbench("ZeroDSP_MAC", 20, "mac_conformance.vcd"); + + self.emit_line("// MAC signals"); + self.emit_line("reg [26:0] mac_a = 27'd0;"); + self.emit_line("reg [26:0] mac_b = 27'd0;"); + self.emit_line("reg mac_valid = 1'b0;"); + self.emit_line("reg mac_start = 1'b0;"); + self.emit_line("wire [31:0] mac_result;"); + self.emit_line("wire [1:0] mac_status;"); + self.emit_line("wire mac_done;"); + self.emit_line(""); + self.emit_line("// DUT"); + self.emit_line("ZeroDSP_MAC dut ("); + self.indent(); + self.emit_line(".clk(clk),"); + self.emit_line(".rst_n(rst_n),"); + self.emit_line(".a(mac_a),"); + self.emit_line(".b(mac_b),"); + self.emit_line(".valid(mac_valid),"); + self.emit_line(".start(mac_start),"); + self.emit_line(".result(mac_result),"); + self.emit_line(".status(mac_status),"); + self.emit_line(".done(mac_done)"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + + self.emit_line("// Conformance test sequence"); + self.emit_line("initial begin"); + self.indent(); + self.emit_line("// Reset"); + self.emit_line("rst_n = 1'b0;"); + self.emit_line("#200;"); + self.emit_line("rst_n = 1'b1;"); + self.emit_line("#100;"); + self.emit_line(""); + + self.emit_line("// Vector: mac_status_initially_ready"); + self.emit_conformance_check("mac_status", 0, 2, 20); + self.emit_line(""); + + self.emit_line("// Vector: mac_reset_clears_accumulator"); + self.emit_line("mac_start = 1'b1;"); + self.emit_line("#20;"); + self.emit_line("mac_start = 1'b0;"); + self.emit_line("#100;"); + self.emit_line(""); + + self.emit_line("// Vector: mac_lut_multiply_pos_pos (+1 * +1 = +1)"); + self.emit_line("mac_a = 27'd1;"); + self.emit_line("mac_b = 27'd1;"); + self.emit_line("mac_valid = 1'b1;"); + self.emit_line("#20;"); + self.emit_line("mac_valid = 1'b0;"); + self.emit_line("#200;"); + self.emit_line(""); + + self.emit_line("// Vector: mac_lut_multiply_neg_neg (-1 * -1 = +1)"); + self.emit_line("mac_a = 27'd2;"); + self.emit_line("mac_b = 27'd2;"); + self.emit_line("mac_valid = 1'b1;"); + self.emit_line("#20;"); + self.emit_line("mac_valid = 1'b0;"); + self.emit_line("#200;"); + self.emit_line(""); + + self.emit_line("// Vector: mac_lut_multiply_pos_neg (+1 * -1 = -1)"); + self.emit_line("mac_a = 27'd1;"); + self.emit_line("mac_b = 27'd2;"); + self.emit_line("mac_valid = 1'b1;"); + self.emit_line("#20;"); + self.emit_line("mac_valid = 1'b0;"); + self.emit_line("#200;"); + + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + + self.emit_conformance_footer("ZeroDSP_MAC"); + } + + // ======================================================================== + // 18. Top-Level Conformance Testbench + // ======================================================================== + + fn emit_top_conformance_tb(self: *FpgaCodegen) void { + self.emit_conformance_testbench("Trinity_FPGA_Top", 20, "top_conformance.vcd"); + + self.emit_line("// Top-level signals"); + self.emit_line("wire uart_tx_out;"); + self.emit_line("wire spi_cs_out;"); + self.emit_line("wire spi_sck_out;"); + self.emit_line("wire spi_mosi_out;"); + self.emit_line("wire [3:0] led_out;"); + self.emit_line(""); + self.emit_line("// DUT"); + self.emit_line("Trinity_FPGA_Top dut ("); + self.indent(); + self.emit_line(".clk(clk),"); + self.emit_line(".rst_n(rst_n),"); + self.emit_line(".uart_rx_in(1'b1),"); + self.emit_line(".uart_tx_out(uart_tx_out),"); + self.emit_line(".spi_miso_in(1'b0),"); + self.emit_line(".spi_cs_out(spi_cs_out),"); + self.emit_line(".spi_sck_out(spi_sck_out),"); + self.emit_line(".spi_mosi_out(spi_mosi_out),"); + self.emit_line(".led_out(led_out)"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + + self.emit_line("// Conformance test sequence"); + self.emit_line("initial begin"); + self.indent(); + self.emit_line("// Reset"); + self.emit_line("rst_n = 1'b0;"); + self.emit_line("#200;"); + self.emit_line("rst_n = 1'b1;"); + self.emit_line("#100;"); + self.emit_line(""); + + self.emit_line("// Vector: top_reset_assert -- all subsystems in reset"); + self.emit_line("// After reset release, check LED heartbeat starts"); + self.emit_line("#50000;"); + self.emit_line(""); + + self.emit_line("// Vector: top_heartbeat_period"); + self.emit_line("// Heartbeat toggles every 25M cycles at 50MHz = 500ms"); + self.emit_line("// Check LED is active (non-zero after sufficient time)"); + self.emit_line("#100000;"); + self.emit_line(""); + + self.emit_line("// Vector: top_led_set_pattern"); + self.emit_line("// LED pattern driven by heartbeat counter"); + self.emit_line("#100000;"); + + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + + self.emit_conformance_footer("Trinity_FPGA_Top"); + } + + // ======================================================================== + // 19. SPI Conformance Testbench + // ======================================================================== + + fn emit_spi_conformance_tb(self: *FpgaCodegen) void { + self.emit_conformance_testbench("SPI_Master", 20, "spi_conformance.vcd"); + + self.emit_line("// SPI signals"); + self.emit_line("reg [31:0] spi_tx_data = 32'd0;"); + self.emit_line("reg spi_start = 1'b0;"); + self.emit_line("reg [4:0] spi_len = 5'd8;"); + self.emit_line("wire spi_ready;"); + self.emit_line("wire [31:0] spi_rx_data;"); + self.emit_line("wire spi_cs_out;"); + self.emit_line("wire spi_sck_out;"); + self.emit_line("wire spi_mosi_out;"); + self.emit_line(""); + self.emit_line("// DUT"); + self.emit_line("SPI_Master dut ("); + self.indent(); + self.emit_line(".clk(clk),"); + self.emit_line(".rst_n(rst_n),"); + self.emit_line(".tx_data(spi_tx_data),"); + self.emit_line(".start(spi_start),"); + self.emit_line(".data_len(spi_len),"); + self.emit_line(".ready(spi_ready),"); + self.emit_line(".rx_data(spi_rx_data),"); + self.emit_line(".cs_out(spi_cs_out),"); + self.emit_line(".sck_out(spi_sck_out),"); + self.emit_line(".mosi_out(spi_mosi_out),"); + self.emit_line(".miso_in(1'b0)"); + self.dedent(); + self.emit_line(");"); + self.emit_line(""); + + self.emit_line("// Conformance test sequence"); + self.emit_line("initial begin"); + self.indent(); + self.emit_line("// Reset"); + self.emit_line("rst_n = 1'b0;"); + self.emit_line("#200;"); + self.emit_line("rst_n = 1'b1;"); + self.emit_line("#100;"); + self.emit_line(""); + + self.emit_line("// Vector: spi_mode_0_idle_low"); + self.emit_line("// CS should be high (inactive) after reset"); + self.emit_conformance_check("spi_cs_out", 1, 1, 20); + self.emit_line(""); + + self.emit_line("// Vector: spi_transfer_8bit -- tx_data=0xAA"); + self.emit_line("spi_tx_data = 32'hAA;"); + self.emit_line("spi_len = 5'd8;"); + self.emit_line("spi_start = 1'b1;"); + self.emit_line("#20;"); + self.emit_line("spi_start = 1'b0;"); + self.emit_line("// Wait for 8-bit transfer: 8 clocks at SPI speed"); + self.emit_line("#2000;"); + self.emit_line(""); + + self.emit_line("// Vector: spi_transfer_16bit -- tx_data=0xAAAA"); + self.emit_line("spi_tx_data = 32'hAAAA;"); + self.emit_line("spi_len = 5'd16;"); + self.emit_line("spi_start = 1'b1;"); + self.emit_line("#20;"); + self.emit_line("spi_start = 1'b0;"); + self.emit_line("#4000;"); + self.emit_line(""); + + self.emit_line("// Vector: spi_transfer_loopback"); + self.emit_line("spi_tx_data = 32'h55;"); + self.emit_line("spi_len = 5'd8;"); + self.emit_line("spi_start = 1'b1;"); + self.emit_line("#20;"); + self.emit_line("spi_start = 1'b0;"); + self.emit_line("#2000;"); + + self.dedent(); + self.emit_line("end"); + self.emit_line(""); + + self.emit_conformance_footer("SPI_Master"); + } +} + +test fpga_emission_init_empty { + let cg = FpgaCodegen.init(); + assert cg.output.len() == 0; + assert cg.indent_level == 0; +} + +test fpga_emission_emit_appends { + let mut cg = FpgaCodegen.init(); + cg.emit("module test;\n"); + assert cg.output.contains("module test"); +} + +test fpga_emission_emit_line_has_newline { + let mut cg = FpgaCodegen.init(); + cg.emit_line("wire a;"); + assert cg.output.contains("wire a;\n"); +} + +test fpga_emission_indent_increases { + let mut cg = FpgaCodegen.init(); + cg.indent(); + assert cg.indent_level == 1; + cg.indent(); + assert cg.indent_level == 2; +} + +test fpga_emission_dedent_decreases { + let mut cg = FpgaCodegen.init(); + cg.indent(); + cg.indent(); + cg.dedent(); + assert cg.indent_level == 1; +} + +test fpga_emission_dedent_floors_at_zero { + let mut cg = FpgaCodegen.init(); + cg.dedent(); + assert cg.indent_level == 0; +} + +test fpga_emission_emit_int_positive { + let mut cg = FpgaCodegen.init(); + cg.emit_int(42); + assert cg.output.contains("42"); +} + +test fpga_emission_emit_int_zero { + let mut cg = FpgaCodegen.init(); + cg.emit_int(0); + assert cg.output.contains("0"); +} + +test fpga_emission_int_to_str_basic { + let result = int_to_str(123); + assert result[0] == '1'; + assert result[1] == '2'; + assert result[2] == '3'; +} + +test fpga_emission_int_to_str_zero { + let result = int_to_str(0); + assert result[0] == '0'; + assert result.len() == 1; +} + +test fpga_emission_int_to_str_large { + let result = int_to_str(65536); + assert result.contains("65536"); +} + +test fpga_emission_emit_fpga_top_generates { + let mut cg = FpgaCodegen.init(); + cg.emit_fpga_top(); + assert cg.output.contains("Trinity_FPGA_Top"); + assert cg.output.len() > 100; +} + +test fpga_emission_emit_uart_module_generates { + let mut cg = FpgaCodegen.init(); + cg.emit_uart_module(); + assert cg.output.contains("UART_Bridge"); +} + +test fpga_emission_emit_spi_module_generates { + let mut cg = FpgaCodegen.init(); + cg.emit_spi_module(); + assert cg.output.contains("SPI_Master"); +} + +test fpga_emission_emit_mac_module_generates { + let mut cg = FpgaCodegen.init(); + cg.emit_mac_module(); + assert cg.output.contains("ZeroDSP_MAC"); +} + +test fpga_emission_all_modules_unique { + let mut cg = FpgaCodegen.init(); + cg.emit_fpga_top(); + let top_len = cg.output.len(); + cg.emit_uart_module(); + let uart_len = cg.output.len(); + assert uart_len > top_len; +} + +test fpga_emission_bridge_generates { + let mut cg = FpgaCodegen.init(); + cg.emit_bridge_module(); + assert cg.output.contains("FPGA_Bridge"); + assert cg.output.contains("cmd_buffer"); +} + +test fpga_emission_memory_generates { + let mut cg = FpgaCodegen.init(); + cg.emit_memory_module(); + assert cg.output.contains("MemoryController"); + assert cg.output.contains("bram"); +} + +test fpga_emission_fifo_generates { + let mut cg = FpgaCodegen.init(); + cg.emit_fifo_module(); + assert cg.output.contains("SyncFIFO"); + assert cg.output.contains("wr_ptr"); + assert cg.output.contains("rd_ptr"); +} + +test fpga_emission_axi4_generates { + let mut cg = FpgaCodegen.init(); + cg.emit_axi4_module(); + assert cg.output.contains("AXI4_Lite_Slave"); + assert cg.output.contains("awvalid"); + assert cg.output.contains("arvalid"); +} + +test fpga_emission_apb_bridge_generates { + let mut cg = FpgaCodegen.init(); + cg.emit_apb_bridge_module(); + assert cg.output.contains("APB_Bridge"); + assert cg.output.contains("paddr"); + assert cg.output.contains("psel"); +} + +test fpga_emission_gf16_accel_generates { + let mut cg = FpgaCodegen.init(); + cg.emit_gf16_accel_module(); + assert cg.output.contains("GF16_Accel"); + assert cg.output.contains("mul_result"); +} + +test fpga_emission_generic_module_from_hir { + let mut hir = HirModule.empty_module("TestModule"); + hir.add_port(Port.make("clk", PortDir.input_dir, 1, false)); + hir.add_port(Port.make("data_in", PortDir.input_dir, 8, false)); + hir.add_port(Port.make("data_out", PortDir.output_dir, 8, false)); + hir.add_signal(Signal.make("counter", SignalKind.reg_kind, 8, false, Some(0))); + hir.add_assign(Assign.make("data_out", "counter")); + let mut cg = FpgaCodegen.init(); + cg.emit_generic_module(hir); + assert cg.output.contains("TestModule"); + assert cg.output.contains("data_in"); + assert cg.output.contains("data_out"); + assert cg.output.contains("counter"); + assert cg.output.contains("assign"); +} + +test fpga_emission_testbench_from_hir { + let mut hir = HirModule.empty_module("MyModule"); + hir.add_port(Port.make("clk", PortDir.input_dir, 1, false)); + hir.add_port(Port.make("rst_n", PortDir.input_dir, 1, false)); + hir.add_port(Port.make("data", PortDir.output_dir, 8, false)); + let mut cg = FpgaCodegen.init(); + cg.emit_testbench_from_hir(hir, 20); + assert cg.output.contains("tb_MyModule"); + assert cg.output.contains("dut"); + assert cg.output.contains("$finish"); +} + +test fpga_emission_all_10_modules_emit { + let mut cg = FpgaCodegen.init(); + cg.emit_fpga_top(); + cg.emit_uart_module(); + cg.emit_spi_module(); + cg.emit_mac_module(); + cg.emit_bridge_module(); + cg.emit_memory_module(); + cg.emit_fifo_module(); + cg.emit_axi4_module(); + cg.emit_apb_bridge_module(); + cg.emit_gf16_accel_module(); + assert cg.output.contains("Trinity_FPGA_Top"); + assert cg.output.contains("UART_Bridge"); + assert cg.output.contains("SPI_Master"); + assert cg.output.contains("ZeroDSP_MAC"); + assert cg.output.contains("FPGA_Bridge"); + assert cg.output.contains("MemoryController"); + assert cg.output.contains("SyncFIFO"); + assert cg.output.contains("AXI4_Lite_Slave"); + assert cg.output.contains("APB_Bridge"); + assert cg.output.contains("GF16_Accel"); +} + +test fpga_emission_xdc_from_hir_basic { + let mut hir = HirModule.empty_module("TestDesign"); + hir.add_port(Port.make("clk", PortDir.input_dir, 1, false)); + hir.add_port(Port.make("rst_n", PortDir.input_dir, 1, false)); + hir.add_port(Port.make("led", PortDir.output_dir, 4, false)); + hir.add_port(Port.make("uart_tx", PortDir.output_dir, 1, false)); + let mut cg = FpgaCodegen.init(); + cg.emit_xdc_from_hir(hir, "E3", "C12", 50_000_000); + assert cg.output.contains("create_clock"); + assert cg.output.contains("set_false_path"); + assert cg.output.contains("PACKAGE_PIN"); + assert cg.output.contains("LVCMOS33"); + assert cg.output.contains("led"); + assert cg.output.contains("uart_tx"); +} + +test fpga_emission_xdc_with_axi4_bus { + let mut hir = HirModule.empty_module("AXIDesign"); + hir.add_port(Port.make("clk", PortDir.input_dir, 1, false)); + hir.add_port(Port.make("rst_n", PortDir.input_dir, 1, false)); + hir.add_bus_port(BusPort.make("axi_ctrl", BusKind.axi4_lite, 16, 32, false, 0)); + let mut cg = FpgaCodegen.init(); + cg.emit_xdc_from_hir(hir, "E3", "C12", 100_000_000); + assert cg.output.contains("AXI4-Lite"); + assert cg.output.contains("axi_awvalid"); +} + +test fpga_emission_xdc_with_clock_domains { + let mut hir = HirModule.empty_module("CDCDesign"); + hir.add_port(Port.make("clk", PortDir.input_dir, 1, false)); + hir.add_port(Port.make("rst_n", PortDir.input_dir, 1, false)); + hir.add_clock_domain(ClockDomain.make("clk_rx", 12_000_000, 0, false, CdcStrategy.two_flop)); + let mut cg = FpgaCodegen.init(); + cg.emit_xdc_from_hir(hir, "E3", "C12", 50_000_000); + assert cg.output.contains("set_clock_groups"); + assert cg.output.contains("clk_rx"); +} + +test fpga_emission_xdc_skips_clk_rst_pins { + let mut hir = HirModule.empty_module("Simple"); + hir.add_port(Port.make("clk", PortDir.input_dir, 1, false)); + hir.add_port(Port.make("rst_n", PortDir.input_dir, 1, false)); + hir.add_port(Port.make("data", PortDir.output_dir, 8, false)); + let mut cg = FpgaCodegen.init(); + cg.emit_xdc_from_hir(hir, "E3", "C12", 50_000_000); + let lines_with_clk_pin = cg.output.lines().filter(|l| l.contains("get_ports {clk}")).count(); + assert lines_with_clk_pin == 1; // only in create_clock, not in PACKAGE_PIN +} + +test fpga_emission_conformance_testbench_basic { + let mut cg = FpgaCodegen.init(); + cg.emit_conformance_testbench("MyModule", 20, "test.vcd"); + assert cg.output.contains("tb_conformance_MyModule"); + assert cg.output.contains("$dumpfile"); + assert cg.output.contains("$dumpvars"); + assert cg.output.contains("test.vcd"); + assert cg.output.contains("total_checks"); + assert cg.output.contains("pass_count"); + assert cg.output.contains("fail_count"); +} + +test fpga_emission_conformance_check_exact { + let mut cg = FpgaCodegen.init(); + cg.emit_conformance_check("led_out", 170, 8, 100); + assert cg.output.contains("led_out"); + assert cg.output.contains("170"); + assert cg.output.contains("FAIL"); + assert cg.output.contains("pass_count"); + assert cg.output.contains("fail_count"); +} + +test fpga_emission_conformance_check_masked { + let mut cg = FpgaCodegen.init(); + cg.emit_conformance_check_masked("status", 3, 15, 200); + assert cg.output.contains("status"); + assert cg.output.contains("15"); + assert cg.output.contains("3"); +} + +test fpga_emission_conformance_footer { + let mut cg = FpgaCodegen.init(); + cg.emit_conformance_footer("TestModule"); + assert cg.output.contains("Conformance Report"); + assert cg.output.contains("TestModule"); + assert cg.output.contains("ALL CONFORMANCE CHECKS PASSED"); + assert cg.output.contains("$finish"); +} + +test fpga_emission_uart_conformance_tb { + let mut cg = FpgaCodegen.init(); + cg.emit_uart_conformance_tb(); + assert cg.output.contains("tb_conformance_UART_Bridge"); + assert cg.output.contains("uart_conformance.vcd"); + assert cg.output.contains("$dumpfile"); + assert cg.output.contains("UART_Bridge dut"); + assert cg.output.contains("8'h55"); + assert cg.output.contains("8'hAA"); + assert cg.output.contains("8'h00"); + assert cg.output.contains("8'hFF"); + assert cg.output.contains("Conformance Report"); +} + +test fpga_emission_mac_conformance_tb { + let mut cg = FpgaCodegen.init(); + cg.emit_mac_conformance_tb(); + assert cg.output.contains("tb_conformance_ZeroDSP_MAC"); + assert cg.output.contains("mac_conformance.vcd"); + assert cg.output.contains("ZeroDSP_MAC dut"); + assert cg.output.contains("mac_status"); + assert cg.output.contains("mac_result"); + assert cg.output.contains("Conformance Report"); +} + +test fpga_emission_top_conformance_tb { + let mut cg = FpgaCodegen.init(); + cg.emit_top_conformance_tb(); + assert cg.output.contains("tb_conformance_Trinity_FPGA_Top"); + assert cg.output.contains("top_conformance.vcd"); + assert cg.output.contains("Trinity_FPGA_Top dut"); + assert cg.output.contains("led_out"); + assert cg.output.contains("uart_tx_out"); + assert cg.output.contains("Conformance Report"); +} + +test fpga_emission_spi_conformance_tb { + let mut cg = FpgaCodegen.init(); + cg.emit_spi_conformance_tb(); + assert cg.output.contains("tb_conformance_SPI_Master"); + assert cg.output.contains("spi_conformance.vcd"); + assert cg.output.contains("SPI_Master dut"); + assert cg.output.contains("spi_tx_data"); + assert cg.output.contains("32'hAA"); + assert cg.output.contains("32'hAAAA"); + assert cg.output.contains("32'h55"); + assert cg.output.contains("Conformance Report"); +} + +test fpga_emission_all_conformance_tbs { + let mut cg = FpgaCodegen.init(); + cg.emit_uart_conformance_tb(); + cg.emit_mac_conformance_tb(); + cg.emit_top_conformance_tb(); + cg.emit_spi_conformance_tb(); + assert cg.output.contains("uart_conformance.vcd"); + assert cg.output.contains("mac_conformance.vcd"); + assert cg.output.contains("top_conformance.vcd"); + assert cg.output.contains("spi_conformance.vcd"); + assert cg.output.contains("ALL CONFORMANCE CHECKS PASSED"); +} + +invariant fpga_emission_indent_non_negative { + let cg = FpgaCodegen.init(); + assert cg.indent_level >= 0; +} + +invariant fpga_emission_output_grows { + let mut cg = FpgaCodegen.init(); + let before = cg.output.len(); + cg.emit("x"); + assert cg.output.len() > before; +} + +invariant fpga_emission_dedent_never_negative { + let mut cg = FpgaCodegen.init(); + cg.dedent(); + cg.dedent(); + cg.dedent(); + assert cg.indent_level >= 0; +} + +invariant fpga_emission_int_to_str_no_leading_zeros { + let result = int_to_str(42); + assert result[0] != '0'; +} + +bench fpga_emission_init_latency { + measure: nanoseconds to create FpgaCodegen + target: < 100ns +} + +bench fpga_emission_emit_line_latency { + measure: nanoseconds to emit_line("wire clk;") + target: < 500ns +} + +bench fpga_emission_full_top_latency { + measure: nanoseconds to emit_fpga_top() + emit_uart_module() + emit_spi_module() + emit_mac_module() + target: < 50000ns +} diff --git a/apps/website/public/t27/files/compiler/codegen/zig/codegen.t27 b/apps/website/public/t27/files/compiler/codegen/zig/codegen.t27 new file mode 100644 index 0000000000..2e3b0a643d --- /dev/null +++ b/apps/website/public/t27/files/compiler/codegen/zig/codegen.t27 @@ -0,0 +1,1516 @@ +// codegen.t27 -- Code Generator for Zig +// Generates Zig 0.15 code from t27 AST + +module zig_codegen { + using ast: @import("../../../ast.t27"); + + // Codegen options + pub const CodegenOptions = struct { + emit_comments: bool, // Include source comments + emit_debug: bool, // Include debug information + optimize_level: u8, // 0=none, 1=safe, 2=fast, 3=small + target_triple: []const u8, // Target triple for cross-compilation + include_runtime: bool, // Include bootstrap runtime + }; + + // Codegen context + pub const ZigCodegen = struct { + ast: Program, + options: CodegenOptions, + output: StringBuilder, + indent_level: u32, + errors: []CodegenError, + symbols: std.StringHashMap([]const u8), // t27 symbol -> zig symbol + + pub fn new(ast_node: Program, opts: CodegenOptions) ZigCodegen { + return ZigCodegen{ + .ast = ast_node, + .options = opts, + .output = StringBuilder.new(65536), + .indent_level = 0, + .errors = &.{}, + .symbols = std.StringHashMap([]const u8).init(std.testing.allocator), + }; + } + + pub fn generate(self: *ZigCodegen) []const u8 { + // Header + self.emit_header(); + + // Imports + self.emit_imports(); + + // Constants + self.emit_constants(); + + // Data section + self.emit_data_section(); + + // Code section + self.emit_code_section(); + + // TDD-Inside-Spec sections + if (self.ast.spec_decl) |_| { + self.emit_spec_tests(); + } + self.emit_test_section(); + self.emit_invariant_section(); + self.emit_bench_section(); + + // Footer + self.emit_footer(); + + return self.output.to_string(); + } + + // Emit file header + fn emit_header(self: *ZigCodegen) void { + self.emit("// Generated by t27 compiler from "); + self.emit(self.ast.source_file); + self.emit_line(""); + self.emit_line("// DO NOT EDIT -- source of truth is .t27 file"); + self.emit_line(""); + self.emit_line("const std = @import(\"std\");"); + self.emit_line(""); + } + + // Emit imports + fn emit_imports(self: *ZigCodegen) void { + if (self.options.include_runtime) { + self.emit_line("// Runtime imports"); + self.emit_line("const tri_runtime = @import(\"t27/runtime/runtime.zig\");"); + self.emit_line(""); + } + } + + // Emit constants + fn emit_constants(self: *ZigCodegen) void { + if (self.ast.constants.len == 0) { + return; + } + + self.emit_line("// Constants from .const declarations"); + for (self.ast.constants) |const_def| { + const zig_name = self.mangle_name(const_def.name); + self.symbols.put(const_def.name, zig_name) catch {}; + + self.emit("pub const "); + self.emit(zig_name); + self.emit(" : "); + self.emit_type_for_value(const_def.value); + self.emit(" = "); + self.emit_int_value(const_def.value); + self.emit_line(";"); + } + self.emit_line(""); + } + + // Emit data section + fn emit_data_section(self: *ZigCodegen) void { + if (self.ast.data_section.declarations.len == 0) { + return; + } + + self.emit_line("// Data section variables"); + self.emit_line("var data_section = struct {"); + self.indent(); + + for (self.ast.data_section.declarations) |decl| { + const zig_name = self.mangle_name(decl.label); + + if (decl.label.len > 0) { + self.emit("// "); + self.emit(decl.label); + self.emit_line(""); + } + + self.emit(decl.label); + self.emit(" : "); + + // Determine Zig type + if (decl.size == 32) { + self.emit("u32"); + } else if (decl.size == 8) { + self.emit("u8"); + } else if (decl.size == 2) { + self.emit("u2"); + } else { + self.emit("[0]u8"); + } + + self.emit(" = "); + + if (decl.initial_value != 0) { + self.emit_int_value(decl.initial_value); + } else { + self.emit("0"); + } + + self.emit_line(","); + } + + self.dedent(); + self.emit_line("};"); + self.emit_line(""); + } + + // Emit code section + fn emit_code_section(self: *ZigCodegen) void { + self.emit_line("// Code section"); + self.emit_line(""); + + // VSA helper functions + self.emit_line("// VSA helper functions (ternary hypervector operations)"); + self.emit_line("fn vsa_bind(a: i64, b: i64) i64 {"); + self.indent(); + self.emit_line("// XOR-like bind for balanced ternary"); + self.emit_line("// if a_trit == 0: b_trit; else if b_trit == 0: a_trit; else: a_trit * b_trit"); + self.emit_line("var result: i64 = 0;"); + self.emit_line("var i: u32 = 0;"); + self.emit_line("while (i < 21) : (i += 3) { // 7 trits (21 bits) per register"); + self.emit_line(" const mask_a: i64 = @as(i64, 0x3) << @intCast(i);"); + self.emit_line(" const mask_b: i64 = @as(i64, 0x3) << @intCast(i);"); + self.emit_line(" const a_trit = (a & mask_a) >> @intCast(i);"); + self.emit_line(" const b_trit = (b & mask_b) >> @intCast(i);"); + self.emit_line(" const bound = if (a_trit == 0) b_trit else if (b_trit == 0) a_trit else if (a_trit == b_trit) 1 else 2;"); + self.emit_line(" result |= (bound & 0x3) << @intCast(i);"); + self.emit_line("}"); + self.emit_line("return result;"); + self.dedent(); + self.emit_line("}"); + self.emit_line(""); + + self.emit_line("fn vsa_bundle2(a: i64, b: i64) i64 {"); + self.indent(); + self.emit_line("// Bundle2: majority vote of 2 ternary vectors"); + self.emit_line("var result: i64 = 0;"); + self.emit_line("var i: u32 = 0;"); + self.emit_line("while (i < 21) : (i += 3) { // 7 trits per register"); + self.emit_line(" const mask: i64 = @as(i64, 0x3) << @intCast(i);"); + self.emit_line(" const a_trit = (a & mask) >> @intCast(i);"); + self.emit_line(" const b_trit = (b & mask) >> @intCast(i);"); + self.emit_line(" // Majority: if same, use value; if different, use 0"); + self.emit_line(" const bundled = if (a_trit == b_trit) a_trit else 0;"); + self.emit_line(" result |= (bundled & 0x3) << @intCast(i);"); + self.emit_line("}"); + self.emit_line("return result;"); + self.dedent(); + self.emit_line("}"); + self.emit_line(""); + + self.emit_line("pub fn execute() !void {"); + self.indent(); + + // Register file (27 Coptic registers) + self.emit_line("// Coptic register file: r0-r25 general, r26 = zero"); + self.emit_line("var regs : [27]i64 = undefined;"); + self.emit_line("regs[26] = 0; // Zero register"); + self.emit_line(""); + + // Label resolution + self.emit_labels(); + + // Instructions + self.emit_instructions(); + + self.dedent(); + self.emit_line("}"); + } + + // Emit labels as comptime values + fn emit_labels(self: *ZigCodegen) void { + self.emit_line("// Label addresses"); + var iter = self.ast.code_section.labels.iterator(); + while (iter.next()) |entry| { + self.emit("const L_"); + self.emit(entry.key_ptr.*); + self.emit(" : usize = "); + self.emit_int_value(@as(i64, @intCast(entry.value_ptr.*))); + self.emit_line(";"); + } + self.emit_line(""); + } + + // Emit instructions + fn emit_instructions(self: *ZigCodegen) void { + var pc: u32 = 0; // Program counter + + for (self.ast.code_section.instructions) |inst| { + // Emit instruction as comment (if debug enabled) + if (self.options.emit_debug) { + self.emit("// "); + self.emit(self.emit_opcode_name(inst.opcode)); + for (inst.operands) |op| { + self.emit(" "); + self.emit(self.operand_to_zig(op)); + } + self.emit_line(""); + } + + // Emit actual Zig code + self.emit_instruction(inst, &pc); + + pc += 1; + } + } + + // Emit single instruction as Zig code + fn emit_instruction(self: *ZigCodegen, inst: Instruction, pc: *u32) void { + switch (inst.opcode) { + Opcode.MOV => self.emit_mov(inst), + Opcode.JZ => self.emit_jz(inst, pc), + Opcode.JNZ => self.emit_jnz(inst, pc), + Opcode.JMP => self.emit_jmp(inst), + Opcode.MUL => self.emit_mul(inst), + Opcode.ADD => self.emit_add(inst), + Opcode.SUB => self.emit_sub(inst), + Opcode.BIND => self.emit_bind(inst), + Opcode.BUNDLE => self.emit_bundle(inst), + Opcode.HALT => self.emit_halt(inst), + else => self.emit_line("// Unknown opcode"), + } + } + + // Emit MOV instruction + fn emit_mov(self: *ZigCodegen, inst: Instruction) void { + if (inst.operands.len < 2) { + self.add_error("MOV requires 2 operands", inst.line, inst.column); + return; + } + + const dst = inst.operands[0]; + const src = inst.operands[1]; + + const dst_reg = self.get_reg_number(dst); + const src_str = self.operand_to_zig(src); + + self.emit("regs["); + self.emit_int_value(@as(i64, @intCast(dst_reg))); + self.emit("] = "); + self.emit(src_str); + self.emit(";"); + self.emit_line(""); + } + + // Emit MUL instruction + fn emit_mul(self: *ZigCodegen, inst: Instruction) void { + if (inst.operands.len < 3) { + self.add_error("MUL requires 3 operands", inst.line, inst.column); + return; + } + + const dst = inst.operands[0]; + const src1 = inst.operands[1]; + const src2 = inst.operands[2]; + + const dst_reg = self.get_reg_number(dst); + const src1_str = self.operand_to_zig(src1); + const src2_str = self.operand_to_zig(src2); + + self.emit("regs["); + self.emit_int_value(@as(i64, @intCast(dst_reg))); + self.emit("] = "); + self.emit(src1_str); + self.emit(" * "); + self.emit(src2_str); + self.emit(";"); + self.emit_line(""); + } + + // Emit ADD instruction + fn emit_add(self: *ZigCodegen, inst: Instruction) void { + if (inst.operands.len < 3) { + self.add_error("ADD requires 3 operands", inst.line, inst.column); + return; + } + + const dst = inst.operands[0]; + const src1 = inst.operands[1]; + const src2 = inst.operands[2]; + + const dst_reg = self.get_reg_number(dst); + const src1_str = self.operand_to_zig(src1); + const src2_str = self.operand_to_zig(src2); + + self.emit("regs["); + self.emit_int_value(@as(i64, @intCast(dst_reg))); + self.emit("] = "); + self.emit(src1_str); + self.emit(" + "); + self.emit(src2_str); + self.emit(";"); + self.emit_line(""); + } + + // Emit JZ instruction + fn emit_jz(self: *ZigCodegen, inst: Instruction, pc: *u32) void { + if (inst.operands.len < 2) { + self.add_error("JZ requires 2 operands", inst.line, inst.column); + return; + } + + const test_reg = inst.operands[0]; + const target_label = inst.operands[1]; + + const reg_str = self.operand_to_zig(test_reg); + const label_str = self.label_to_zig(target_label); + + self.emit("if "); + self.emit(reg_str); + self.emit(" == 0 {"); + self.emit_line(""); + self.indent(); + self.emit("pc = L_"); + self.emit(label_str); + self.emit(";"); + self.emit_line(""); + self.dedent(); + self.emit("}"); + self.emit_line(""); + } + + // Emit JNZ instruction + fn emit_jnz(self: *ZigCodegen, inst: Instruction, pc: *u32) void { + if (inst.operands.len < 2) { + self.add_error("JNZ requires 2 operands", inst.line, inst.column); + return; + } + + const test_reg = inst.operands[0]; + const target_label = inst.operands[1]; + + const reg_str = self.operand_to_zig(test_reg); + const label_str = self.label_to_zig(target_label); + + self.emit("if "); + self.emit(reg_str); + self.emit(" != 0 {"); + self.emit_line(""); + self.indent(); + self.emit("pc = L_"); + self.emit(label_str); + self.emit(";"); + self.emit_line(""); + self.dedent(); + self.emit("}"); + self.emit_line(""); + } + + // Emit JMP instruction + fn emit_jmp(self: *ZigCodegen, inst: Instruction) void { + if (inst.operands.len < 1) { + self.add_error("JMP requires 1 operand", inst.line, inst.column); + return; + } + + const target_label = inst.operands[0]; + const label_str = self.label_to_zig(target_label); + + self.emit("pc = L_"); + self.emit(label_str); + self.emit(";"); + self.emit_line(""); + } + + // Emit SUB instruction + fn emit_sub(self: *ZigCodegen, inst: Instruction) void { + if (inst.operands.len < 3) { + self.add_error("SUB requires 3 operands", inst.line, inst.column); + return; + } + + const dst = inst.operands[0]; + const src1 = inst.operands[1]; + const src2 = inst.operands[2]; + + const dst_reg = self.get_reg_number(dst); + const src1_str = self.operand_to_zig(src1); + const src2_str = self.operand_to_zig(src2); + + self.emit("regs["); + self.emit_int_value(@as(i64, @intCast(dst_reg))); + self.emit("] = "); + self.emit(src1_str); + self.emit(" - "); + self.emit(src2_str); + self.emit(";"); + self.emit_line(""); + } + + // Emit BIND instruction (VSA bind) + // BIND rd, rs1, rs2: rd = bind(rs1, rs2) + // Algorithm: if a == 0: b; else if b == 0: a; else: a*b (pos if equal, neg if different) + fn emit_bind(self: *ZigCodegen, inst: Instruction) void { + if (inst.operands.len < 3) { + self.add_error("BIND requires 3 operands", inst.line, inst.column); + return; + } + + const dst = inst.operands[0]; + const src1 = inst.operands[1]; + const src2 = inst.operands[2]; + + const dst_reg = self.get_reg_number(dst); + const src1_reg = self.get_reg_number(src1); + const src2_reg = self.get_reg_number(src2); + + // Generate bind operation with inline ternary logic + self.emit("// BIND: VSA bind operation (XOR-like for balanced ternary)"); + self.emit("regs["); + self.emit_int_value(@intCast(dst_reg)); + self.emit("] = vsa_bind(regs["); + self.emit_int_value(@intCast(src1_reg)); + self.emit("], regs["); + self.emit_int_value(@intCast(src2_reg)); + self.emit_line("]);"); + } + + // Emit BUNDLE instruction (VSA bundle) + // BUNDLE rd, rs1, rs2: rd = bundle2(rs1, rs2) (majority vote) + fn emit_bundle(self: *ZigCodegen, inst: Instruction) void { + if (inst.operands.len < 3) { + self.add_error("BUNDLE requires 3 operands", inst.line, inst.column); + return; + } + + const dst = inst.operands[0]; + const src1 = inst.operands[1]; + const src2 = inst.operands[2]; + + const dst_reg = self.get_reg_number(dst); + const src1_reg = self.get_reg_number(src1); + const src2_reg = self.get_reg_number(src2); + + // Generate bundle operation (majority vote of 2 vectors) + self.emit("// BUNDLE: VSA bundle2 (majority vote)"); + self.emit("regs["); + self.emit_int_value(@intCast(dst_reg)); + self.emit("] = vsa_bundle2(regs["); + self.emit_int_value(@intCast(src1_reg)); + self.emit("], regs["); + self.emit_int_value(@intCast(src2_reg)); + self.emit_line("]);"); + } + + // Emit HALT instruction + fn emit_halt(self: *ZigCodegen, inst: Instruction) void { + _ = inst; + self.emit_line("return;"); + } + + // Emit test section as Zig test blocks + fn emit_test_section(self: *ZigCodegen) void { + if (self.ast.test_section) |test_section| { + self.emit_line(""); + self.emit_line("// ==============================================================="); + self.emit_line("// TDD-Inside-Spec: Test Cases"); + self.emit_line("// ==============================================================="); + self.emit_line(""); + + for (test_section.test_cases) |test_case| { + self.emit_zig_test(test_case); + } + } + } + + // Emit a single test case as Zig test + fn emit_zig_test(self: *ZigCodegen, test_case: TestCase) void { + const zig_name = self.mangle_test_name(test_case.name); + + self.emit("test \""); + self.emit(zig_name); + self.emit("\" {"); + self.emit_line(""); + self.indent(); + + // Emit test description as comment + if (test_case.verify_description.len > 0) { + self.emit("// Verify: "); + self.emit(test_case.verify_description); + self.emit_line(""); + } + + // Emit setup as comment + if (test_case.setup_description.len > 0) { + self.emit("// Setup: "); + self.emit(test_case.setup_description); + self.emit_line(""); + } + + // Emit expected outcome as comment + if (test_case.expected_outcome.len > 0) { + self.emit("// Expected: "); + self.emit(test_case.expected_outcome); + self.emit_line(""); + } + + // TODO: Generate actual test implementation based on test type + self.emit_line("// TODO: Parse test description and generate implementation:"); + self.emit_line("// 1. Parse verify_description for action to perform"); + self.emit_line("// 2. Parse setup_description for initialization"); + self.emit_line("// 3. Parse expected_outcome for assertion"); + self.emit_line("// 4. Generate appropriate Zig test code"); + self.emit_line("//"); + self.emit_line("// Example patterns:"); + self.emit_line("// - Function call: try testing.expect(my_func(42) == expected);"); + self.emit_line("// - Property check: try testing.expect(value > 0);"); + self.emit_line("// - Error check: try testing.expectError(error.MyError, action());"); + self.emit_line("try std.testing.expect(true); // placeholder"); + + + self.dedent(); + self.emit("}"); + self.emit_line(""); + } + + // Emit invariant section as Zig tests (invariants are tested properties) + fn emit_invariant_section(self: *ZigCodegen) void { + if (self.ast.invariant_section) |inv_section| { + self.emit_line(""); + self.emit_line("// ==============================================================="); + self.emit_line("// TDD-Inside-Spec: Invariants"); + self.emit_line("// ==============================================================="); + self.emit_line(""); + + for (inv_section.invariants) |inv| { + self.emit_zig_invariant(inv); + } + } + } + + // Emit a single invariant as Zig test + fn emit_zig_invariant(self: *ZigCodegen, inv: InvariantDecl) void { + const zig_name = self.mangle_test_name(inv.name); + + self.emit("test \"invariant_"); + self.emit(zig_name); + self.emit("\" {"); + self.emit_line(""); + self.indent(); + + // Emit formal statement as comment + if (inv.formal_statement.len > 0) { + self.emit("// Invariant: "); + self.emit(inv.formal_statement); + self.emit_line(""); + } + + // Emit rationale as comment + if (inv.rationale.len > 0) { + self.emit("// Rationale: "); + self.emit(inv.rationale); + self.emit_line(""); + } + + // TODO: Generate actual invariant check from formal_statement + self.emit_line("// TODO: Parse formal_statement and generate assertion:"); + self.emit_line("// - For bounds: try testing.expect(value >= min and value <= max);"); + self.emit_line("// - For equality: try testing.expect(actual == expected);"); + self.emit_line("// - For emptiness: try testing.expect(count == 0);"); + self.emit_line("// - For non-null: try testing.expect(ptr != null);"); + self.emit_line("//"); + self.emit_line("// Example: try testing.expect(regs[26] == 0); // Zero register invariant"); + self.emit_line("try std.testing.expect(true); // placeholder"); + + + self.dedent(); + self.emit("}"); + self.emit_line(""); + } + + // Emit bench section as Zig benchmarks + fn emit_bench_section(self: *ZigCodegen) void { + if (self.ast.bench_section) |bench_section| { + self.emit_line(""); + self.emit_line("// ==============================================================="); + self.emit_line("// TDD-Inside-Spec: Benchmarks"); + self.emit_line("// ==============================================================="); + self.emit_line(""); + + for (bench_section.benchmarks) |benchmark| { + self.emit_zig_bench(benchmark); + } + } + } + + // Emit a single benchmark as Zig test + fn emit_zig_bench(self: *ZigCodegen, benchmark: BenchDecl) void { + const zig_name = self.mangle_test_name(benchmark.name); + + self.emit("test \"bench_"); + self.emit(zig_name); + self.emit("\" {"); + self.emit_line(""); + self.indent(); + + // Emit measurement description as comment + if (benchmark.measure_description.len > 0) { + self.emit("// Measure: "); + self.emit(benchmark.measure_description); + self.emit_line(""); + } + + // Emit target as comment + if (benchmark.target) |target| { + self.emit("// Target: "); + self.emit(target); + self.emit_line(""); + } + + // Emit units as comment + self.emit("// Units: "); + self.emit(benchmark.units); + self.emit_line(""); + + // Benchmark implementation using std.testing.benchmark + self.emit_line("// TODO: Parse benchmark spec and generate implementation:"); + self.emit_line("// 1. Parse measure_description for what to measure"); + self.emit_line("// 2. Parse target for function/call to benchmark"); + self.emit_line("// 3. Determine iterations (default: 1000 or adaptive)"); + self.emit_line("// 4. Generate Zig benchmark code using std.testing.benchmark"); + self.emit_line(""); + self.emit_line("// Example patterns:"); + self.emit_line("// - Simple timing: const timer = try std.time.Timer.start();"); + self.emit_line("// const start = timer.read();"); + self.emit_line("// _ = my_function_under_test(input);"); + self.emit_line("// const elapsed_ns = timer.read() - start;"); + self.emit_line("// - Throughput: const ops_per_sec = @as(f64, @floatFromInt(iterations)) /"); + self.emit_line("// @as(f64, @floatFromInt(elapsed_ns)) * 1e9;"); + self.emit_line("// - Memory: const mem_before = std.heap.page_allocator.query();"); + self.emit_line(""); + self.emit_line("const timer = try std.time.Timer.start();"); + self.emit_line("_ = timer;"); + self.emit_line("try std.testing.expect(true);"); + + self.dedent(); + self.emit("}"); + self.emit_line(""); + } + + // Emit spec tests (high-level TDD with given/when/then) + fn emit_spec_tests(self: *ZigCodegen) void { + if (self.ast.spec_decl) |spec| { + self.emit_line(""); + self.emit("// ==============================================================="); + self.emit("// TDD-Inside-Spec: Spec Tests (from spec "); + self.emit(spec.name); + self.emit(")"); + self.emit_line("// ==============================================================="); + self.emit_line(""); + + // Emit test blocks + for (spec.test_blocks) |test_block| { + self.emit_spec_test_block(test_block); + } + + // Emit invariants as tests + for (spec.invariants) |inv| { + self.emit_spec_invariant(inv); + } + + // Emit rules as tests + for (spec.rules) |rule| { + self.emit_spec_rule(rule); + } + } + } + + // Emit a single spec test block as Zig test + fn emit_spec_test_block(self: *ZigCodegen, test_block: TestBlock) void { + const zig_name = self.mangle_test_name(test_block.name); + + self.emit("test \""); + self.emit(zig_name); + self.emit("\" {"); + self.emit_line(""); + self.indent(); + + // Emit given clauses as setup + for (test_block.given_clauses) |given| { + self.emit("// Given: "); + self.emit(given.variable); + self.emit(" = "); + self.emit(given.expression); + self.emit_line(""); + self.emit("const "); + self.emit(self.mangle_name(given.variable)); + self.emit(" = "); + self.emit(self.translate_expression(given.expression)); + self.emit_line(";"); + } + + // Emit when clauses as actions + for (test_block.when_clauses) |when| { + self.emit("// When: "); + self.emit(when.variable); + self.emit(" = "); + self.emit(when.expression); + self.emit_line(""); + self.emit("const "); + self.emit(self.mangle_name(when.variable)); + self.emit(" = "); + self.emit(self.translate_expression(when.expression)); + self.emit_line(";"); + } + + // Emit then clauses as assertions + for (test_block.then_clauses) |then| { + self.emit("// Then: "); + self.emit(then.expression); + self.emit_line(""); + self.emit("try std.testing.expect("); + self.emit(self.translate_expression(then.expression)); + self.emit(");"); + self.emit_line(""); + } + + self.dedent(); + self.emit("}"); + self.emit_line(""); + } + + // Emit a spec invariant as Zig test + fn emit_spec_invariant(self: *ZigCodegen, inv: AssertStmt) void { + self.emit("test \"invariant_"); + self.emit_int(@as(i64, @intCast(inv.line))); + self.emit("\" {"); + self.emit_line(""); + self.indent(); + + self.emit("// Assert: "); + self.emit(inv.expression); + self.emit_line(""); + self.emit("try std.testing.expect("); + self.emit(self.translate_expression(inv.expression)); + self.emit(");"); + self.emit_line(""); + + self.dedent(); + self.emit("}"); + self.emit_line(""); + } + + // Emit a spec rule as Zig test + fn emit_spec_rule(self: *ZigCodegen, rule: RuleDecl) void { + self.emit("test \"rule_"); + self.emit(self.mangle_test_name(rule.name)); + self.emit("\" {"); + self.emit_line(""); + self.indent(); + + self.emit("// Rule: "); + self.emit(rule.name); + self.emit_line(""); + + for (rule.expect_clauses) |expect| { + self.emit("// Expect: "); + self.emit(expect.expression); + self.emit_line(""); + self.emit("try std.testing.expect("); + self.emit(self.translate_expression(expect.expression)); + self.emit(");"); + self.emit_line(""); + } + + self.dedent(); + self.emit("}"); + self.emit_line(""); + } + + // Translate t27 expression to Zig expression (simplified) + fn translate_expression(self: *ZigCodegen, expr: []const u8) []const u8 { + _ = self; + // Simple translation - just return as-is for now + // A full implementation would need to parse and translate the expression + return expr; + } + + // Generate conformance JSON from spec tests + pub fn generate_conformance(self: *ZigCodegen) []const u8 { + var json: []const u8 = "{\n"; + json = json ++ " \"description\": \"" ++ self.ast.source_file ++ "\",\n"; + json = json ++ " \"generated_at\": \"[timestamp]\",\n"; + json = json ++ " \"test_vectors\": [\n"; + + // Add test vectors from test_section + if (self.ast.test_section) |test_section| { + for (test_section.test_cases) |test_case| { + json = json ++ " {\n"; + json = json ++ " \"name\": \"" ++ test_case.name ++ "\",\n"; + json = json ++ " \"verify\": \"" ++ test_case.verify_description ++ "\",\n"; + json = json ++ " \"expected\": \"" ++ test_case.expected_outcome ++ "\"\n"; + json = json ++ " },\n"; + } + } + + // Add test vectors from spec_decl + if (self.ast.spec_decl) |spec| { + for (spec.test_blocks) |test_block| { + json = json ++ " {\n"; + json = json ++ " \"name\": \"" ++ test_block.name ++ "\",\n"; + json = json ++ " \"type\": \"spec_test\",\n"; + json = json ++ " \"given\": ["; + for (test_block.given_clauses) |given| { + json = json ++ " {\"" ++ given.variable ++ "\": \"" ++ given.expression ++ "\"},\n"; + } + json = json ++ " ],\n"; + json = json ++ " \"when\": ["; + for (test_block.when_clauses) |when| { + json = json ++ " {\"" ++ when.variable ++ "\": \"" ++ when.expression ++ "\"},\n"; + } + json = json ++ " ],\n"; + json = json ++ " \"then\": ["; + for (test_block.then_clauses) |then| { + json = json ++ " \"" ++ then.expression ++ "\",\n"; + } + json = json ++ " ]\n"; + json = json ++ " },\n"; + } + + // Add invariants + for (spec.invariants) |inv| { + json = json ++ " {\n"; + json = json ++ " \"name\": \"invariant_" ++ int_to_str(@as(i64, @intCast(inv.line))) ++ "\",\n"; + json = json ++ " \"type\": \"invariant\",\n"; + json = json ++ " \"assert\": \"" ++ inv.expression ++ "\"\n"; + json = json ++ " },\n"; + } + } + + // Remove trailing comma and close + json = json[0..json.len - 2]; + json = json ++ "\n"; + json = json ++ " ]\n"; + json = json ++ "}\n"; + + return json; + } + + // Get register number from operand + fn get_reg_number(self: *ZigCodegen, op: Operand) u8 { + return switch (op) { + .RegOperand => |r| r.reg_num, + else => 0, + }; + } + + // Convert operand to Zig expression + fn operand_to_zig(self: *ZigCodegen, op: Operand) []const u8 { + return switch (op) { + .RegOperand => |r| "regs[" ++ int_to_str(@as(i64, @intCast(r.reg_num))) ++ "]", + .ImmOperand => |i| int_to_str(i.value), + .LabelOperand => |l| "L_" ++ l.label_name, + .MemOperand => |m| { + if (m.offset != 0) { + return "@ptrCast([*]i64, &data_section." ++ int_to_str(@as(i64, @intCast(m.base_reg))) ++ ") + " ++ int_to_str(@as(i64, m.offset)); + } else { + return "@ptrCast([*]i64, &data_section." ++ int_to_str(@as(i64, @intCast(m.base_reg))) ++ ")"; + } + }, + else => "0", + }; + } + + // Convert label operand to Zig label name + fn label_to_zig(self: *ZigCodegen, op: Operand) []const u8 { + return switch (op) { + .LabelOperand => |l| l.label_name, + else => "", + }; + } + + // Emit opcode name + fn emit_opcode_name(self: *ZigCodegen, opcode: Opcode) []const u8 { + return switch (opcode) { + Opcode.MOV => "MOV", + Opcode.JZ => "JZ", + Opcode.JNZ => "JNZ", + Opcode.JMP => "JMP", + Opcode.MUL => "MUL", + Opcode.ADD => "ADD", + Opcode.SUB => "SUB", + Opcode.BIND => "BIND", + Opcode.BUNDLE => "BUNDLE", + Opcode.HALT => "HALT", + else => "???", + }; + } + + // Emit type for value + fn emit_type_for_value(self: *ZigCodegen, value: i64) void { + if (value >= 0 and value <= 255) { + self.emit("u8"); + } else if (value >= -32768 and value <= 32767) { + self.emit("i32"); + } else { + self.emit("i64"); + } + } + + // Emit integer value + fn emit_int_value(self: *ZigCodegen, value: i64) void { + self.emit(int_to_str(value)); + } + + // Mangle t27 name to valid Zig identifier + fn mangle_name(self: *ZigCodegen, name: []const u8) []const u8 { + var result: []const u8 = ""; + + for (name, 0..) |c, i| { + if ((c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or c == '_') { + result = result ++ [_]u8{c}; + } else if (c >= '0' and c <= '9') { + if (i > 0) { // Can't start with digit + result = result ++ [_]u8{c}; + } else { + result = result ++ "_"; + } + } else { + result = result ++ "_"; + } + } + + return result; + } + + // Mangle test/invariant/bench name to valid Zig identifier + fn mangle_test_name(self: *ZigCodegen, name: []const u8) []const u8 { + var result: []const u8 = ""; + + for (name) |c| { + if ((c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or (c >= '0' and c <= '9') or c == '_') { + result = result ++ [_]u8{c}; + } else if (c == '-' or c == ' ') { + result = result ++ "_"; + } + } + + return result; + } + + // Add error + fn add_error(self: *ZigCodegen, msg: []const u8, line: u32, column: u32) void { + _ = self.errors; + _ = msg; + _ = line; + _ = column; + // self.errors.append(CodegenError{ .message = msg, .line = line, .column = column }); + } + + // Emit helpers + fn emit(self: *ZigCodegen, s: []const u8) void { + self.output.append(s); + } + + fn emit_line(self: *ZigCodegen, s: []const u8) void { + self.output.append(s); + self.output.append("\n"); + + // Reset indentation for next line + if (self.indent_level > 0) { + var i: u32 = 0; + while (i < self.indent_level) : (i += 1) { + self.output.append(" "); + } + } + } + + fn emit_int(self: *ZigCodegen, n: i64) void { + self.emit(int_to_str(n)); + } + + fn indent(self: *ZigCodegen) void { + self.indent_level += 1; + } + + fn dedent(self: *ZigCodegen) void { + if (self.indent_level > 0) { + self.indent_level -= 1; + } + } + }; + + // StringBuilder + pub const StringBuilder = struct { + buffer: []u8, + len: usize, + capacity: usize, + + pub fn new(capacity: usize) StringBuilder { + return StringBuilder{ + .buffer = [_]u8{0} ** capacity, + .len = 0, + .capacity = capacity, + }; + } + + pub fn append(self: *StringBuilder, s: []const u8) void { + var i: usize = 0; + while (i < s.len and self.len < self.capacity) : (i += 1) { + self.buffer[self.len] = s[i]; + self.len += 1; + } + } + + pub fn to_string(self: *StringBuilder) []const u8 { + return self.buffer[0..self.len]; + } + }; + + // Types (simplified from AST) + pub const Program = struct { + source_file: []const u8, + constants: []ConstDef, + data_section: DataSection, + code_section: CodeSection, + spec_decl: ?SpecDecl, + test_section: ?TestSection, + invariant_section: ?InvariantSection, + bench_section: ?BenchSection, + }; + + pub const ConstDef = struct { + name: []const u8, + value: i64, + }; + + pub const DataSection = struct { + declarations: []DataDecl, + }; + + pub const DataDecl = struct { + label: []const u8, + size: u32, + initial_value: i64, + }; + + pub const CodeSection = struct { + instructions: []Instruction, + labels: std.StringHashMap(usize), + }; + + pub const Instruction = struct { + opcode: Opcode, + operands: []const Operand, + line: u32 = 0, + column: u32 = 0, + }; + + pub const Opcode = enum(u8) { + MOV, + JZ, + JNZ, + JMP, + MUL, + ADD, + SUB, + BIND, + BUNDLE, + HALT, + }; + + pub const Operand = union(enum) { + RegOperand: struct { reg_num: u8 }, + ImmOperand: struct { value: i32 }, + LabelOperand: struct { label_name: []const u8 }, + MemOperand: struct { base_reg: u32, offset: i32 }, + }; + + pub const SpecDecl = struct { + name: []const u8, + test_blocks: []TestBlock, + invariants: []AssertStmt, + rules: []RuleDecl, + }; + + pub const TestBlock = struct { + name: []const u8, + given_clauses: []Clause, + when_clauses: []Clause, + then_clauses: []Clause, + }; + + pub const Clause = struct { + variable: []const u8, + expression: []const u8, + }; + + pub const AssertStmt = struct { + line: u32, + expression: []const u8, + }; + + pub const RuleDecl = struct { + name: []const u8, + expect_clauses: []Clause, + }; + + pub const TestSection = struct { + test_cases: []TestCase, + }; + + pub const InvariantSection = struct { + invariants: []InvariantDecl, + }; + + pub const BenchSection = struct { + benchmarks: []BenchDecl, + }; + + pub const TestCase = struct { + name: []const u8, + verify_description: []const u8, + expected_outcome: []const u8, + setup_description: []const u8, + }; + + pub const InvariantDecl = struct { + name: []const u8, + formal_statement: []const u8, + rationale: []const u8, + }; + + pub const BenchDecl = struct { + name: []const u8, + measure_description: []const u8, + target: ?[]const u8, + units: []const u8, + }; + + pub const CodegenError = struct { + message: []const u8, + line: u32, + column: u32, + }; + + // Helper: integer to string + fn int_to_str(n: i64) []const u8 { + // Simplified - in real implementation would use std.fmt + if (n == 0) return "0"; + var result: []const u8 = ""; + var v = n; + if (v < 0) { + result = "-"; + v = -v; + } + var buf: [21]u8 = undefined; + var i: usize = 20; + while (v > 0) : (i -= 1) { + buf[i] = '0' + @as(u8, @intCast(v % 10)); + v /= 10; + } + return result ++ buf[i + 1 ..]; + } +} + +// ======================================================================================================= +// TDD-Inside-Spec: Tests and Invariants for Zig Codegen +// ======================================================================================================= + +test zig_codegen_new_creates_generator + // Verify: ZigCodegen.new creates a generator with AST and options + // Expected: ZigCodegen has ast, options, and initialized StringBuilder + var code_section = zig_codegen.CodeSection{ + .instructions = &.{}, + .labels = std.StringHashMap(usize).init(std.testing.allocator), + }; + var ast = zig_codegen.Program{ + .source_file = "test.t27", + .constants = &.{}, + .data_section = zig_codegen.DataSection{ + .declarations = &.{}, + }, + .code_section = code_section, + .spec_decl = null, + .test_section = null, + .invariant_section = null, + .bench_section = null, + }; + var opts = zig_codegen.CodegenOptions{ + .emit_comments = true, + .emit_debug = true, + .optimize_level = 0, + .target_triple = "native", + .include_runtime = true, + }; + var codegen = zig_codegen.ZigCodegen.new(ast, opts); + _ = codegen; + try std.testing.expect(true); + +test zig_codegen_generate_includes_header + // Verify: generate() outputs Zig header with std import + // Expected: Output contains "Generated by t27 compiler" and std import + var code_section = zig_codegen.CodeSection{ + .instructions = &.{}, + .labels = std.StringHashMap(usize).init(std.testing.allocator), + }; + var ast = zig_codegen.Program{ + .source_file = "test.t27", + .constants = &.{}, + .data_section = zig_codegen.DataSection{ + .declarations = &.{}, + }, + .code_section = code_section, + .spec_decl = null, + .test_section = null, + .invariant_section = null, + .bench_section = null, + }; + var opts = zig_codegen.CodegenOptions{ + .emit_comments = true, + .emit_debug = false, + .optimize_level = 0, + .target_triple = "native", + .include_runtime = false, + }; + var codegen = zig_codegen.ZigCodegen.new(ast, opts); + const output = codegen.generate(); + try std.testing.expect(std.mem.indexOf(u8, output, "Generated by t27 compiler") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "@import(\"std\")") != null); + +test zig_codegen_emit_instructions_emits_code + // Verify: emit_instructions generates Zig code for instructions + // Expected: Output contains register file and instructions + var code_section = zig_codegen.CodeSection{ + .instructions = &.{ + .{ .opcode = zig_codegen.Opcode.MOV, .operands = &.{} }, + .{ .opcode = zig_codegen.Opcode.HALT, .operands = &.{} }, + }, + .labels = std.StringHashMap(usize).init(std.testing.allocator), + }; + var ast = zig_codegen.Program{ + .source_file = "test.t27", + .constants = &.{}, + .data_section = zig_codegen.DataSection{ + .declarations = &.{}, + }, + .code_section = code_section, + .spec_decl = null, + .test_section = null, + .invariant_section = null, + .bench_section = null, + }; + var opts = zig_codegen.CodegenOptions{ + .emit_comments = false, + .emit_debug = false, + .optimize_level = 0, + .target_triple = "native", + .include_runtime = false, + }; + var codegen = zig_codegen.ZigCodegen.new(ast, opts); + const output = codegen.generate(); + try std.testing.expect(std.mem.indexOf(u8, output, "regs : [27]i64") != null); + +test zig_codegen_emit_test_section_outputs_tests + // Verify: emit_test_section generates Zig test blocks + // Expected: Output contains "test \"..." blocks + var test_section = zig_codegen.TestSection{ + .test_cases = &.{ + .{ + .name = "my_test", + .verify_description = "Verify something works", + .expected_outcome = "Expected result", + .setup_description = "", + }, + }, + }; + var code_section = zig_codegen.CodeSection{ + .instructions = &.{}, + .labels = std.StringHashMap(usize).init(std.testing.allocator), + }; + var ast = zig_codegen.Program{ + .source_file = "test.t27", + .constants = &.{}, + .data_section = zig_codegen.DataSection{ + .declarations = &.{}, + }, + .code_section = code_section, + .spec_decl = null, + .test_section = test_section, + .invariant_section = null, + .bench_section = null, + }; + var opts = zig_codegen.CodegenOptions{ + .emit_comments = true, + .emit_debug = false, + .optimize_level = 0, + .target_triple = "native", + .include_runtime = false, + }; + var codegen = zig_codegen.ZigCodegen.new(ast, opts); + const output = codegen.generate(); + try std.testing.expect(std.mem.indexOf(u8, output, "test \"") != null); + +test zig_codegen_emit_invariant_section_outputs_invariants + // Verify: emit_invariant_section generates invariant tests + // Expected: Output contains "test \"invariant_" blocks + var inv_section = zig_codegen.InvariantSection{ + .invariants = &.{ + .{ + .name = "my_invariant", + .formal_statement = "Property holds", + .rationale = "Important for correctness", + }, + }, + }; + var code_section = zig_codegen.CodeSection{ + .instructions = &.{}, + .labels = std.StringHashMap(usize).init(std.testing.allocator), + }; + var ast = zig_codegen.Program{ + .source_file = "test.t27", + .constants = &.{}, + .data_section = zig_codegen.DataSection{ + .declarations = &.{}, + }, + .code_section = code_section, + .spec_decl = null, + .test_section = null, + .invariant_section = inv_section, + .bench_section = null, + }; + var opts = zig_codegen.CodegenOptions{ + .emit_comments = true, + .emit_debug = false, + .optimize_level = 0, + .target_triple = "native", + .include_runtime = false, + }; + var codegen = zig_codegen.ZigCodegen.new(ast, opts); + const output = codegen.generate(); + try std.testing.expect(std.mem.indexOf(u8, output, "invariant_") != null); + +test zig_codegen_emit_bench_section_outputs_benchmarks + // Verify: emit_bench_section generates benchmark tests + // Expected: Output contains "test \"bench_" blocks + var bench_section = zig_codegen.BenchSection{ + .benchmarks = &.{ + .{ + .name = "my_bench", + .measure_description = "Measure something", + .target = null, + .units = "ns", + }, + }, + }; + var code_section = zig_codegen.CodeSection{ + .instructions = &.{}, + .labels = std.StringHashMap(usize).init(std.testing.allocator), + }; + var ast = zig_codegen.Program{ + .source_file = "test.t27", + .constants = &.{}, + .data_section = zig_codegen.DataSection{ + .declarations = &.{}, + }, + .code_section = code_section, + .spec_decl = null, + .test_section = null, + .invariant_section = null, + .bench_section = bench_section, + }; + var opts = zig_codegen.CodegenOptions{ + .emit_comments = true, + .emit_debug = false, + .optimize_level = 0, + .target_triple = "native", + .include_runtime = false, + }; + var codegen = zig_codegen.ZigCodegen.new(ast, opts); + const output = codegen.generate(); + try std.testing.expect(std.mem.indexOf(u8, output, "bench_") != null); + +test zig_codegen_opcode_name_returns_correct_mnemonics + // Verify: emit_opcode_name returns correct mnemonic for each opcode + // Expected: MOV, JZ, JNZ, JMP, MUL, ADD, SUB, BIND, BUNDLE, HALT + var codegen = zig_codegen.ZigCodegen; + try std.testing.expectEqualStrings("MOV", codegen.emit_opcode_name(zig_codegen.Opcode.MOV)); + try std.testing.expectEqualStrings("JZ", codegen.emit_opcode_name(zig_codegen.Opcode.JZ)); + try std.testing.expectEqualStrings("HALT", codegen.emit_opcode_name(zig_codegen.Opcode.HALT)); + +test zig_codegen_mangle_name_validates_identifiers + // Verify: mangle_name produces valid Zig identifiers + // Expected: Invalid characters replaced with underscore + var codegen = zig_codegen.ZigCodegen; + const result = codegen.mangle_name("test-name with spaces"); + try std.testing.expectEqualStrings("test_name_with_spaces", result); + +test zig_codegen_mangle_test_name_handles_special_chars + // Verify: mangle_test_name handles hyphens and spaces + // Expected: "my-test-name" becomes "my_test_name" + var codegen = zig_codegen.ZigCodegen; + const result = codegen.mangle_test_name("my-test-name"); + try std.testing.expectEqualStrings("my_test_name", result); + +invariant zig_codegen_generate_outputs_complete_module + // generate() outputs complete Zig module with execute function + // Rationale: Generated code must be valid Zig + assert true; + +invariant zig_codegen_register_file_size + // Register file always has 27 registers (r0-r25 general, r26 zero) + // Rationale: Coptic encoding requires 27 registers + assert true; + +invariant zig_codegen_zero_register_always_zero + // Register 26 (zero register) is initialized to 0 + // Rationale: Zero register must always read as 0 + assert true; + +invariant zig_codegen_mangle_name_no_leading_digits + // Mangled names never start with digits + // Rationale: Valid Zig identifiers cannot start with digits + assert true; + +invariant zig_codegen_indent_level_non_negative + // indent_level never goes below 0 + // Rationale: Prevents negative indentation errors + assert true; + +invariant zig_codegen_conformance_json_valid_structure + // generate_conformance() produces valid JSON structure + // Rationale: Conformance JSON must be parseable + assert true; + +bench zig_codegen_generate_latency + target: < 100ms + var code_section = zig_codegen.CodeSection{ + .instructions = &.{}, + .labels = std.StringHashMap(usize).init(std.testing.allocator), + }; + var ast = zig_codegen.Program{ + .source_file = "test.t27", + .constants = &.{}, + .data_section = zig_codegen.DataSection{ + .declarations = &.{}, + }, + .code_section = code_section, + .spec_decl = null, + .test_section = null, + .invariant_section = null, + .bench_section = null, + }; + var opts = zig_codegen.CodegenOptions{ + .emit_comments = true, + .emit_debug = false, + .optimize_level = 0, + .target_triple = "native", + .include_runtime = false, + }; + var codegen = zig_codegen.ZigCodegen.new(ast, opts); + _ = codegen.generate(); + +bench zig_codegen_emit_instruction_latency + target: < 1us per instruction + var codegen = zig_codegen.ZigCodegen; + var inst = zig_codegen.Instruction{ + .opcode = zig_codegen.Opcode.MOV, + .operands = &.{}, + }; + var pc: u32 = 0; + _ = codegen.emit_instruction(inst, &pc); + +bench zig_codegen_operand_to_zig_latency + target: < 500ns + var codegen = zig_codegen.ZigCodegen; + _ = codegen.operand_to_zig; diff --git a/apps/website/public/t27/files/compiler/codegen/zig/runtime.t27 b/apps/website/public/t27/files/compiler/codegen/zig/runtime.t27 new file mode 100644 index 0000000000..1c7696adde --- /dev/null +++ b/apps/website/public/t27/files/compiler/codegen/zig/runtime.t27 @@ -0,0 +1,409 @@ +// runtime.t27 -- Zig Runtime Code Generation +// Generates Zig backend from compiler/runtime/*.t27 specifications +// phi^2 + 1/phi^2 = 3 | TRINITY + +module zig_runtime { + using commands: @import("compiler/runtime/commands.t27"); + using validation: @import("compiler/runtime/validation.t27"); + + // ===================================================================== + // Zig Runtime Generation + // ===================================================================== + + // Generate main.zig entry point + pub fn generate_main() []const u8 { + return "// This file is generated from compiler/runtime/runtime.t27\n" ++ + "// DO NOT EDIT - Changes will be overwritten on next tri gen\n" ++ + "// Generated at: " ++ get_current_timestamp() ++ "\n" ++ + "// Source spec: compiler/runtime/runtime.t27\n" ++ + "\n" ++ + "const std = @import(\"std\");\n" ++ + "const commands = @import(\"commands.zig\");\n" ++ + "\n" ++ + "pub fn main() !u8 {\n" ++ + " const allocator = std.heap.page_allocator;\n" ++ + " const args = try std.process.argsAlloc(allocator);\n" ++ + " defer std.process.argsFree(allocator, args);\n" ++ + "\n" ++ + " if (args.len < 2) {\n" ++ + " try commands.help(\"\");\n" ++ + " return 0;\n" ++ + " }\n" ++ + "\n" ++ + " const command = args[1];\n" ++ + " const command_args = args[2..];\n" ++ + "\n" ++ + " return dispatch_command(command, command_args);\n" ++ + "}\n" ++ + "\n" ++ + "fn dispatch_command(command: []const u8, args: [][]const u8) !u8 {\n" ++ + " if (std.mem.eql(u8, command, \"spec\")) {\n" ++ + " return commands.spec_dispatch(args);\n" ++ + " } else if (std.mem.eql(u8, command, \"gen\")) {\n" ++ + " return commands.gen_dispatch(args);\n" ++ + " } else if (std.mem.eql(u8, command, \"git\")) {\n" ++ + " return commands.git_dispatch(args);\n" ++ + " } else if (std.mem.eql(u8, command, \"lint\")) {\n" ++ + " return commands.lint_dispatch(args);\n" ++ + " } else if (std.mem.eql(u8, command, \"skill\")) {\n" ++ + " return commands.skill_dispatch(args);\n" ++ + " } else if (std.mem.eql(u8, command, \"help\")) {\n" ++ + " try commands.help(if (args.len > 0) args[0] else \"\");\n" ++ + " return 0;\n" ++ + " } else {\n" ++ + " std.debug.print(\"Unknown command: {s}\\n\", .{command});\n" ++ + " try commands.help(\"\");\n" ++ + " return 1;\n" ++ + " }\n" ++ + "}\n"; + } + + // Generate commands.zig + pub fn generate_commands_module() []const u8 { + return "// This file is generated from compiler/runtime/commands.t27\n" ++ + "// DO NOT EDIT - Changes will be overwritten on next tri gen\n" ++ + "// Generated at: " ++ get_current_timestamp() ++ "\n" ++ + "// Source spec: compiler/runtime/commands.t27\n" ++ + "\n" ++ + "const std = @import(\"std\");\n" ++ + "const validation = @import(\"validation.zig\");\n" ++ + "\n" ++ + "pub fn spec_dispatch(args: [][]const u8) !u8 {\n" ++ + " if (args.len < 1) {\n" ++ + " try help(\"spec\");\n" ++ + " return 0;\n" ++ + " }\n" ++ + "\n" ++ + " const subcommand = args[0];\n" ++ + "\n" ++ + " if (std.mem.eql(u8, subcommand, \"create\")) {\n" ++ + " if (args.len < 2) {\n" ++ + " std.debug.print(\"ERROR: spec name required\\n\", .{});\n" ++ + " return 1;\n" ++ + " }\n" ++ + " const name = args[1];\n" ++ + " const kind = if (args.len > 2) args[2] else \"feature\";\n" ++ + " return spec_create(name, kind);\n" ++ + " } else if (std.mem.eql(u8, subcommand, \"validate\")) {\n" ++ + " if (args.len < 2) {\n" ++ + " std.debug.print(\"ERROR: spec path required\\n\", .{});\n" ++ + " return 1;\n" ++ + " }\n" ++ + " return spec_validate(args[1]);\n" ++ + " } else if (std.mem.eql(u8, subcommand, \"list\")) {\n" ++ + " return spec_list();\n" ++ + " } else {\n" ++ + " try help(\"spec\");\n" ++ + " return 1;\n" ++ + " }\n" ++ + "}\n" ++ + "\n" ++ + "pub fn gen_dispatch(args: [][]const u8) !u8 {\n" ++ + " if (args.len == 0 or std.mem.eql(u8, args[0], \"--all\")) {\n" ++ + " const backend = if (args.len > 1) args[1] else \"zig\";\n" ++ + " return gen_all(backend);\n" ++ + " } else {\n" ++ + " const spec_path = args[0];\n" ++ + " const backend = if (args.len > 1) args[1] else \"zig\";\n" ++ + " return gen_spec(spec_path, backend, false);\n" ++ + " }\n" ++ + "}\n" ++ + "\n" ++ + "pub fn git_dispatch(args: [][]const u8) !u8 {\n" ++ + " const allocator = std.heap.page_allocator;\n" ++ + "\n" ++ + " if (args.len == 0 or std.mem.eql(u8, args[0], \"status\")) {\n" ++ + " return git_status_with_skill();\n" ++ + " } else if (std.mem.eql(u8, args[0], \"commit\")) {\n" ++ + " const all = parse_bool_flag(args, \"--all\");\n" ++ + " const message = parse_string_flag(args, \"-m\");\n" ++ + " const mode = parse_string_flag(args, \"--mode\") orelse \"normal\";\n" ++ + " return git_commit(all, message, mode);\n" ++ + " } else if (std.mem.eql(u8, args[0], \"push\")) {\n" ++ + " const mode = parse_string_flag(args, \"--mode\") orelse \"normal\";\n" ++ + " return git_push(\"\", \"\", mode);\n" ++ + " }\n" ++ + "\n" ++ + " return 0;\n" ++ + "}\n" ++ + "\n" ++ + "pub fn lint_dispatch(args: [][]const u8) !u8 {\n" ++ + " const strict = parse_bool_flag(args, \"--strict\");\n" ++ + "\n" ++ + " if (args.len == 0 or std.mem.eql(u8, args[0], \"--all\")) {\n" ++ + " return lint_all(strict);\n" ++ + " } else {\n" ++ + " return lint_file(args[0], strict);\n" ++ + " }\n" ++ + "}\n" ++ + "\n" ++ + "pub fn skill_dispatch(args: [][]const u8) !u8 {\n" ++ + " if (args.len == 0) {\n" ++ + " return skill_status();\n" ++ + " }\n" ++ + "\n" ++ + " const subcommand = args[0];\n" ++ + "\n" ++ + " if (std.mem.eql(u8, subcommand, \"begin\")) {\n" ++ + " const issue = parse_string_flag(args, \"--issue\") orelse \"\";\n" ++ + " const kind = parse_string_flag(args, \"--kind\") orelse \"feature\";\n" ++ + " return skill_begin(issue, kind);\n" ++ + " } else if (std.mem.eql(u8, subcommand, \"seal\")) {\n" ++ + " return skill_seal();\n" ++ + " } else if (std.mem.eql(u8, subcommand, \"status\")) {\n" ++ + " return skill_status();\n" ++ + " }\n" ++ + "\n" ++ + " return 0;\n" ++ + "}\n" ++ + "\n" ++ + "fn parse_bool_flag(args: [][]const u8, flag: []const u8) bool {\n" ++ + " for (args) |arg| {\n" ++ + " if (std.mem.eql(u8, arg, flag)) return true;\n" ++ + " }\n" ++ + " return false;\n" ++ + "}\n" ++ + "\n" ++ + "fn parse_string_flag(args: [][]const u8, flag: []const u8) ?[]const u8 {\n" ++ + " var i: usize = 0;\n" ++ + " while (i < args.len - 1) : (i += 1) {\n" ++ + " if (std.mem.eql(u8, args[i], flag)) {\n" ++ + " return args[i + 1];\n" ++ + " }\n" ++ + " }\n" ++ + " return null;\n" ++ + "}\n"; + } + + // Generate validation.zig + pub fn generate_validation_module() []const u8 { + return "// This file is generated from compiler/runtime/validation.t27\n" ++ + "// DO NOT EDIT - Changes will be overwritten on next tri gen\n" ++ + "// Generated at: " ++ get_current_timestamp() ++ "\n" ++ + "// Source spec: compiler/runtime/validation.t27\n" ++ + "\n" ++ + "const std = @import(\"std\");\n" ++ + "\n" ++ + "pub const ValidationResult = struct {\n" ++ + " valid: bool,\n" ++ + " error_msg: []const u8,\n" ++ + " hint: []const u8,\n" ++ + "};\n" ++ + "\n" ++ + "pub fn validate_tdd_contract(spec_path: []const u8) !ValidationResult {\n" ++ + " const content = try std.fs.cwd().readFileAlloc(\n" ++ + " std.heap.page_allocator,\n" ++ + " spec_path,\n" ++ + " 1024 * 1024,\n" ++ + " );\n" ++ + " defer std.heap.page_allocator.free(content);\n" ++ + "\n" ++ + " const has_test = std.mem.indexOf(u8, content, \".test\") != null;\n" ++ + " const has_invariant = std.mem.indexOf(u8, content, \".invariant\") != null;\n" ++ + "\n" ++ + " if (!has_test and !has_invariant) {\n" ++ + " return ValidationResult{\n" ++ + " .valid = false,\n" ++ + " .error_msg = \"TDD contract violated: spec must contain at least one 'test' or 'invariant' block\",\n" ++ + " .hint = \"See: docs/TDD-CONTRACT.md\",\n" ++ + " };\n" ++ + " }\n" ++ + "\n" ++ + " return ValidationResult{\n" ++ + " .valid = true,\n" ++ + " .error_msg = \"\",\n" ++ + " .hint = \"\",\n" ++ + " };\n" ++ + "}\n" ++ + "\n" ++ + "pub fn validate_language_policy(spec_path: []const u8) !ValidationResult {\n" ++ + " if (std.mem.startsWith(u8, spec_path, \"docs/\")) {\n" ++ + " return ValidationResult{ .valid = true, .error_msg = \"\", .hint = \"\" };\n" ++ + " }\n" ++ + "\n" ++ + " const content = try std.fs.cwd().readFileAlloc(\n" ++ + " std.heap.page_allocator,\n" ++ + " spec_path,\n" ++ + " 1024 * 1024,\n" ++ + " );\n" ++ + " defer std.heap.page_allocator.free(content);\n" ++ + "\n" ++ + " for (content) |c| {\n" ++ + " if (c >= 0x0400 and c <= 0x04FF) {\n" ++ + " return ValidationResult{\n" ++ + " .valid = false,\n" ++ + " .error_msg = \"Language policy violated: source file contains Cyrillic characters\",\n" ++ + " .hint = \"Source files must be ASCII-only. See: ADR-004-language-policy.md\",\n" ++ + " };\n" ++ + " }\n" ++ + " }\n" ++ + "\n" ++ + " return ValidationResult{\n" ++ + " .valid = true,\n" ++ + " .error_msg = \"\",\n" ++ + " .hint = \"\",\n" ++ + " };\n" ++ + "}\n" ++ + "\n" ++ + "pub fn validate_generated_header(zig_path: []const u8) !ValidationResult {\n" ++ + " const file = try std.fs.cwd().openFile(zig_path, .{});\n" ++ + " defer file.close();\n" ++ + "\n" ++ + " const reader = file.reader();\n" ++ + " var line_buffer: [256]u8 = undefined;\n" ++ + "\n" ++ + " const line1 = try reader.readUntilDelimiterOrEof(&line_buffer, '\\n') orelse \"\";\n" ++ + " if (!std.mem.indexOf(u8, line1, \"This file is generated from\") != null) {\n" ++ + " return ValidationResult{\n" ++ + " .valid = false,\n" ++ + " .error_msg = \"Zig file lacks generated header\",\n" ++ + " .hint = \"Write .t27 spec first, then run 'tri gen'\",\n" ++ + " };\n" ++ + " }\n" ++ + "\n" ++ + " const line2 = try reader.readUntilDelimiterOrEof(&line_buffer, '\\n') orelse \"\";\n" ++ + " if (!std.mem.indexOf(u8, line2, \"DO NOT EDIT\") != null) {\n" ++ + " return ValidationResult{\n" ++ + " .valid = false,\n" ++ + " .error_msg = \"Zig file header missing 'DO NOT EDIT' warning\",\n" ++ + " .hint = \"Generated files must not be edited\",\n" ++ + " };\n" ++ + " }\n" ++ + "\n" ++ + " return ValidationResult{\n" ++ + " .valid = true,\n" ++ + " .error_msg = \"\",\n" ++ + " .hint = \"\",\n" ++ + " };\n" ++ + "}\n"; + } + + // Generate src/tri/main.zig (entry point) + pub fn generate_main_zig() []const u8 { + return generate_main(); + } + + // Generate src/tri/commands.zig + pub fn generate_commands_zig() []const u8 { + return generate_commands_module(); + } + + // Generate src/tri/validation.zig + pub fn generate_validation_zig() []const u8 { + return generate_validation_module(); + } + + // ===================================================================== + // Code generation entry point + // ===================================================================== + + // Generate all Zig runtime files + pub fn generate_runtime_zig() i32 { + const base_dir = "src/tri/"; + create_dir_if_not_exists(base_dir); + + write_file(base_dir ++ "main.zig", generate_main_zig()); + write_file(base_dir ++ "commands.zig", generate_commands_zig()); + write_file(base_dir ++ "validation.zig", generate_validation_zig()); + + print("Generated Zig runtime:"); + print(" " ++ base_dir ++ "main.zig"); + print(" " ++ base_dir ++ "commands.zig"); + print(" " ++ base_dir ++ "validation.zig"); + + return 0; + } +} + +// ======================================================================================================= +// TDD-Inside-Spec: Tests and Invariants for Runtime Code Generation +// ======================================================================================================= + +test generate_main_contains_header + const main_zig = zig_runtime.generate_main(); + assert(std.mem.indexOf(u8, main_zig, "This file is generated from compiler/runtime/runtime.t27") != null); + +test generate_main_contains_dispatch_function + const main_zig = zig_runtime.generate_main(); + assert(std.mem.indexOf(u8, main_zig, "fn dispatch_command") != null); + +test generate_main_includes_commands_import + const main_zig = zig_runtime.generate_main(); + assert(std.mem.indexOf(u8, main_zig, "const commands = @import") != null); + +test generate_commands_module_contains_spec_dispatch + const commands_zig = zig_runtime.generate_commands_module(); + assert(std.mem.indexOf(u8, commands_zig, "pub fn spec_dispatch") != null); + +test generate_commands_module_contains_validation_import + const commands_zig = zig_runtime.generate_commands_module(); + assert(std.mem.indexOf(u8, commands_zig, "const validation = @import") != null); + +test generate_validation_module_contains_tdd_contract_check + const validation_zig = zig_runtime.generate_validation_module(); + assert(std.mem.indexOf(u8, validation_zig, "validate_tdd_contract") != null); + +test generate_validation_module_contains_language_policy_check + const validation_zig = zig_runtime.generate_validation_module(); + assert(std.mem.indexOf(u8, validation_zig, "validate_language_policy") != null); + +test generate_validation_module_contains_header_check + const validation_zig = zig_runtime.generate_validation_module(); + assert(std.mem.indexOf(u8, validation_zig, "validate_generated_header") != null); + +test generate_runtime_zig_returns_zero_on_success + const result = zig_runtime.generate_runtime_zig(); + assert(result == 0); + +invariant generate_main_always_includes_header + // generate_main always includes generated header + const main_zig = zig_runtime.generate_main(); + assert(std.mem.indexOf(u8, main_zig, "This file is generated from") != null); + +invariant generate_main_always_has_main_function + // generate_main always exports pub fn main() + const main_zig = zig_runtime.generate_main(); + assert(std.mem.indexOf(u8, main_zig, "pub fn main() !u8") != null); + +invariant generate_commands_module_always_has_dispatch_functions + // generate_commands_module generates all dispatch functions + const commands_zig = zig_runtime.generate_commands_module(); + assert(std.mem.indexOf(u8, commands_zig, "pub fn spec_dispatch") != null); + assert(std.mem.indexOf(u8, commands_zig, "pub fn gen_dispatch") != null); + assert(std.mem.indexOf(u8, commands_zig, "pub fn git_dispatch") != null); + assert(std.mem.indexOf(u8, commands_zig, "pub fn lint_dispatch") != null); + assert(std.mem.indexOf(u8, commands_zig, "pub fn skill_dispatch") != null); + +invariant generate_validation_module_defines_validation_result + // generate_validation_module defines ValidationResult struct + const validation_zig = zig_runtime.generate_validation_module(); + assert(std.mem.indexOf(u8, validation_zig, "pub const ValidationResult = struct") != null); + +invariant all_generated_files_have_do_not_edit_header + // All generated files contain "DO NOT EDIT" warning + const main_zig = zig_runtime.generate_main(); + const commands_zig = zig_runtime.generate_commands_module(); + const validation_zig = zig_runtime.generate_validation_module(); + assert(std.mem.indexOf(u8, main_zig, "DO NOT EDIT") != null); + assert(std.mem.indexOf(u8, commands_zig, "DO NOT EDIT") != null); + assert(std.mem.indexOf(u8, validation_zig, "DO NOT EDIT") != null); + +bench generate_main_latency + target: < 10us + const result = zig_runtime.generate_main(); + _ = result; + +bench generate_commands_module_latency + target: < 50us + const result = zig_runtime.generate_commands_module(); + _ = result; + +bench generate_validation_module_latency + target: < 20us + const result = zig_runtime.generate_validation_module(); + _ = result; + +bench generate_runtime_zig_latency + target: < 100us + const result = zig_runtime.generate_runtime_zig(); + _ = result; diff --git a/apps/website/public/t27/files/compiler/parser/lexer.t27 b/apps/website/public/t27/files/compiler/parser/lexer.t27 new file mode 100644 index 0000000000..2e5304127f --- /dev/null +++ b/apps/website/public/t27/files/compiler/parser/lexer.t27 @@ -0,0 +1,597 @@ +; compiler/parser/lexer.t27 -- Lexer for TRI-27 Assembly +; Tokenizes source code into Token stream for parser +; phi^2 + 1/phi^2 = 3 | TRINITY + +module trilexer; + +using base::types; + +// Configuration +pub const MAX_IDENTIFIER_LEN: usize = 64; +pub const MAX_NUMBER_LEN: usize = 20; +pub const MAX_TOKENS: usize = 10000; + +// Token types +pub const TokenType = enum(u8) { + eof = 0, + newline = 1, + dot = 2, + colon = 3, + semicolon = 4, + comma = 5, + hash = 6, + lparen = 7, + rparen = 8, + lbracket = 9, + rbracket = 10, + plus = 11, + minus = 12, + star = 13, + slash = 14, + percent = 15, + and = 16, + or = 17, + xor = 18, + tilde = 19, + lt = 20, + gt = 21, + eq = 22, + excl = 23, + + // Keywords + use = 24, + const = 25, + data = 26, + code = 27, + dword = 28, + dspace = 29, + dtrit = 30, + + // TDD-Inside-Spec sections (assembly-style) + test = 31, + invariant = 32, + bench = 33, + verify = 34, + expected = 35, + setup = 36, + rationale = 37, + measure = 38, + target = 39, + + // TDD-Inside-Spec high-level keywords (spec-style) + spec = 40, + rule = 41, + given = 42, + when = 43, + then = 44, + assert = 45, + and_kw = 46, + expect = 47, + + // Literals and identifiers + integer = 48, + float = 49, + string = 50, + identifier = 51, + reg = 52, + label = 53, + + // Opcodes + mov = 60, + jz = 61, + jnz = 62, + jmp = 63, + jge = 64, + jgt = 65, + jle = 66, + jlt = 67, + jeq = 68, + jne = 69, + call = 70, + ret = 71, + mul = 72, + add = 73, + sub = 74, + div = 75, + bind = 76, + bundle = 77, + halt = 78, + push = 79, + pop = 80, + load = 81, + store = 82, + shl = 83, + shr = 84, + and_op = 85, + or_op = 86, + xor_op = 87, + not = 88, + neg = 89, + sqrt = 90, + tanh = 91, + trap = 92, +}; + +// Token struct +pub const Token = struct { + type: TokenType, + line: u32, + column: u32, + text: []const u8, + value: u64, +}; + +// Lexer state +pub const Lexer = struct { + source: []const u8, + source_file: []const u8, + pos: usize = 0, + line: u32 = 1, + column: u32 = 1, + current_char: u8 = 0, + + // Working buffers + identifier_buffer: [MAX_IDENTIFIER_LEN]u8 = [_]u8{0} ** MAX_IDENTIFIER_LEN, + number_buffer: [MAX_NUMBER_LEN]u8 = [_]u8{0} ** MAX_NUMBER_LEN, + + // Initialize lexer with source + pub fn init(lexer: *Lexer, source: []const u8, source_file: []const u8) void { + lexer.source = source; + lexer.source_file = source_file; + lexer.pos = 0; + lexer.line = 1; + lexer.column = 1; + lexer.current_char = if (source.len > 0) source[0] else 0; + } + + // Main entry point: tokenize source into Token array + pub fn tokenize(lexer: *Lexer, tokens: []Token) usize { + var token_count: usize = 0; + + while (lexer.current_char != 0) { + // Skip whitespace (except newline) + lexer.skip_whitespace(); + + // Check for end of source + if (lexer.current_char == 0) break; + + // Check for newline + if (lexer.current_char == '\n') { + tokens[token_count] = Token{ + .type = .newline, + .line = lexer.line, + .column = lexer.column, + .text = "\n", + .value = 0, + }; + token_count += 1; + lexer.line += 1; + lexer.column = 1; + lexer.advance_char(); + continue; + } + + // Check for comment (;) + if (lexer.current_char == ';') { + lexer.skip_comment(); + continue; + } + + // Check for directives (.use, .const, etc.) + if (lexer.current_char == '.') { + lexer.advance_char(); + const ident = lexer.read_identifier(); + const token_type = lexer.lookup_keyword(ident); + tokens[token_count] = Token{ + .type = token_type, + .line = lexer.line, + .column = lexer.column, + .text = ident, + .value = 0, + }; + token_count += 1; + continue; + } + + // Check for single-character tokens + const single_token = lexer.read_single_char(); + if (single_token != .identifier) { + tokens[token_count] = Token{ + .type = single_token, + .line = lexer.line, + .column = lexer.column, + .text = lexer.identifier_buffer[0..1], + .value = 0, + }; + token_count += 1; + continue; + } + + // Check for digit (number) + if (lexer.current_char >= '0' and lexer.current_char <= '9') { + const (value, text) = lexer.read_number(); + tokens[token_count] = Token{ + .type = .integer, + .line = lexer.line, + .column = lexer.column, + .text = text, + .value = value, + }; + token_count += 1; + continue; + } + + // Identifier, keyword, or register + const ident = lexer.read_identifier(); + const token_type = lexer.lookup_keyword(ident); + + // Check if it's a register + if (token_type == .identifier) { + const (is_reg, reg_num) = lexer.is_register(ident); + if (is_reg) { + tokens[token_count] = Token{ + .type = .reg, + .line = lexer.line, + .column = lexer.column, + .text = ident, + .value = reg_num, + }; + } else { + tokens[token_count] = Token{ + .type = .identifier, + .line = lexer.line, + .column = lexer.column, + .text = ident, + .value = 0, + }; + } + } else { + tokens[token_count] = Token{ + .type = token_type, + .line = lexer.line, + .column = lexer.column, + .text = ident, + .value = 0, + }; + } + token_count += 1; + } + + // Emit EOF token + tokens[token_count] = Token{ + .type = .eof, + .line = lexer.line, + .column = lexer.column, + .text = "", + .value = 0, + }; + token_count += 1; + + return token_count; + } + + // Advance to next character + fn advance_char(lexer: *Lexer) void { + if (lexer.pos < lexer.source.len) { + lexer.pos += 1; + lexer.column += 1; + lexer.current_char = if (lexer.pos < lexer.source.len) + lexer.source[lexer.pos] + else + 0; + } else { + lexer.current_char = 0; + } + } + + // Peek at next character + fn peek_char(lexer: *Lexer) u8 { + if (lexer.pos + 1 < lexer.source.len) { + return lexer.source[lexer.pos + 1]; + } + return 0; + } + + // Skip whitespace (except newline) + fn skip_whitespace(lexer: *Lexer) void { + while (lexer.current_char == ' ' or + lexer.current_char == '\t' or + lexer.current_char == '\r') + { + lexer.advance_char(); + } + } + + // Skip comment + fn skip_comment(lexer: *Lexer) void { + while (lexer.current_char != 0 and lexer.current_char != '\n') { + lexer.advance_char(); + } + } + + // Read identifier into buffer + fn read_identifier(lexer: *Lexer) []const u8 { + var i: usize = 0; + while ((lexer.current_char >= '0' and lexer.current_char <= '9') or + (lexer.current_char >= 'A' and lexer.current_char <= 'Z') or + (lexer.current_char >= 'a' and lexer.current_char <= 'z') or + lexer.current_char == '_') + { + if (i < MAX_IDENTIFIER_LEN - 1) { + lexer.identifier_buffer[i] = lexer.current_char; + i += 1; + } + lexer.advance_char(); + } + lexer.identifier_buffer[i] = 0; + return lexer.identifier_buffer[0..i]; + } + + // Read single character token + fn read_single_char(lexer: *Lexer) TokenType { + const c = lexer.current_char; + lexer.advance_char(); + + return switch (c) { + ':' => .colon, + ';' => .semicolon, + ',' => .comma, + '#' => .hash, + '(' => .lparen, + ')' => .rparen, + '[' => .lbracket, + ']' => .rbracket, + '+' => .plus, + '-' => .minus, + '*' => .star, + '/' => .slash, + '%' => .percent, + '&' => .and, + '|' => .or, + '^' => .xor, + '~' => .tilde, + '<' => .lt, + '>' => .gt, + '=' => .eq, + '!' => .excl, + else => .identifier, + }; + } + + // Read number (integer or float) + fn read_number(lexer: *Lexer) struct { u64, []const u8 } { + var value: u64 = 0; + var is_hex = false; + var is_binary = false; + var i: usize = 0; + + // Check for hex prefix + if (lexer.current_char == '0') { + lexer.advance_char(); + const next = lexer.current_char; + if (next == 'x' or next == 'X') { + is_hex = true; + lexer.advance_char(); + } else if (next == 'b' or next == 'B') { + is_binary = true; + lexer.advance_char(); + } else { + // Just a zero + lexer.number_buffer[i] = '0'; + i += 1; + } + } + + const base: u64 = if (is_hex) 16 else if (is_binary) 2 else 10; + + while (lexer.is_digit(lexer.current_char) or + (is_hex and lexer.is_hex_digit(lexer.current_char))) + { + const digit = if (lexer.current_char >= '0' and lexer.current_char <= '9') + @as(u64, lexer.current_char - '0') + else if (lexer.current_char >= 'a' and lexer.current_char <= 'f') + @as(u64, lexer.current_char - 'a' + 10) + else + @as(u64, lexer.current_char - 'A' + 10); + + value = value * base + digit; + + if (i < MAX_NUMBER_LEN - 1) { + lexer.number_buffer[i] = lexer.current_char; + i += 1; + } + lexer.advance_char(); + } + + return .{ value, lexer.number_buffer[0..i] }; + } + + // Check if character is a digit + fn is_digit(lexer: *Lexer, c: u8) bool { + _ = lexer; + return c >= '0' and c <= '9'; + } + + // Check if character is a hex digit + fn is_hex_digit(lexer: *Lexer, c: u8) bool { + _ = lexer; + return (c >= '0' and c <= '9') or + (c >= 'a' and c <= 'f') or + (c >= 'A' and c <= 'F'); + } + + // Check if identifier is a register (r0-r26, R0-R26) + fn is_register(lexer: *Lexer, ident: []const u8) struct { bool, u64 } { + _ = lexer; + if (ident.len < 2) return .{ false, 0 }; + + const first = ident[0]; + if (first != 'r' and first != 'R') return .{ false, 0 }; + + // Parse register number + var num: u64 = 0; + for (ident[1..]) |c| { + if (c < '0' or c > '9') return .{ false, 0 }; + num = num * 10 + (c - '0'); + } + + if (num > 26) return .{ false, 0 }; + return .{ true, num }; + } + + // Lookup keyword in table + fn lookup_keyword(lexer: *Lexer, ident: []const u8) TokenType { + _ = lexer; + // Simple keyword lookup (would use hash table in implementation) + if (std.mem.eql(u8, ident, "use")) return .use; + if (std.mem.eql(u8, ident, "const")) return .const; + if (std.mem.eql(u8, ident, "data")) return .data; + if (std.mem.eql(u8, ident, "code")) return .code; + if (std.mem.eql(u8, ident, "dword")) return .dword; + if (std.mem.eql(u8, ident, "dspace")) return .dspace; + if (std.mem.eql(u8, ident, "dtrit")) return .dtrit; + if (std.mem.eql(u8, ident, "test")) return .test; + if (std.mem.eql(u8, ident, "invariant")) return .invariant; + if (std.mem.eql(u8, ident, "bench")) return .bench; + if (std.mem.eql(u8, ident, "verify")) return .verify; + if (std.mem.eql(u8, ident, "expected")) return .expected; + if (std.mem.eql(u8, ident, "setup")) return .setup; + if (std.mem.eql(u8, ident, "rationale")) return .rationale; + if (std.mem.eql(u8, ident, "measure")) return .measure; + if (std.mem.eql(u8, ident, "target")) return .target; + if (std.mem.eql(u8, ident, "spec")) return .spec; + if (std.mem.eql(u8, ident, "rule")) return .rule; + if (std.mem.eql(u8, ident, "given")) return .given; + if (std.mem.eql(u8, ident, "when")) return .when; + if (std.mem.eql(u8, ident, "then")) return .then; + if (std.mem.eql(u8, ident, "assert")) return .assert; + if (std.mem.eql(u8, ident, "and")) return .and_kw; + if (std.mem.eql(u8, ident, "expect")) return .expect; + if (std.mem.eql(u8, ident, "mov")) return .mov; + if (std.mem.eql(u8, ident, "jz")) return .jz; + if (std.mem.eql(u8, ident, "jnz")) return .jnz; + if (std.mem.eql(u8, ident, "jmp")) return .jmp; + if (std.mem.eql(u8, ident, "jge")) return .jge; + if (std.mem.eql(u8, ident, "jgt")) return .jgt; + if (std.mem.eql(u8, ident, "jle")) return .jle; + if (std.mem.eql(u8, ident, "jlt")) return .jlt; + if (std.mem.eql(u8, ident, "jeq")) return .jeq; + if (std.mem.eql(u8, ident, "jne")) return .jne; + if (std.mem.eql(u8, ident, "call")) return .call; + if (std.mem.eql(u8, ident, "ret")) return .ret; + if (std.mem.eql(u8, ident, "mul")) return .mul; + if (std.mem.eql(u8, ident, "add")) return .add; + if (std.mem.eql(u8, ident, "sub")) return .sub; + if (std.mem.eql(u8, ident, "div")) return .div; + if (std.mem.eql(u8, ident, "bind")) return .bind; + if (std.mem.eql(u8, ident, "bundle")) return .bundle; + if (std.mem.eql(u8, ident, "halt")) return .halt; + if (std.mem.eql(u8, ident, "push")) return .push; + if (std.mem.eql(u8, ident, "pop")) return .pop; + if (std.mem.eql(u8, ident, "load")) return .load; + if (std.mem.eql(u8, ident, "store")) return .store; + if (std.mem.eql(u8, ident, "shl")) return .shl; + if (std.mem.eql(u8, ident, "shr")) return .shr; + if (std.mem.eql(u8, ident, "and")) return .and_op; + if (std.mem.eql(u8, ident, "or")) return .or_op; + if (std.mem.eql(u8, ident, "xor")) return .xor_op; + if (std.mem.eql(u8, ident, "not")) return .not; + if (std.mem.eql(u8, ident, "neg")) return .neg; + if (std.mem.eql(u8, ident, "sqrt")) return .sqrt; + if (std.mem.eql(u8, ident, "tanh")) return .tanh; + if (std.mem.eql(u8, ident, "trap")) return .trap; + + return .identifier; + } +}; + +// Tests +test "test_tokenize_spec_keywords" { + // Verify: all T27 keywords are recognized + // Setup: tokenize source with all keywords (use, const, data, code, etc.) + // Expected: each keyword has correct TOKEN_* type +} + +test "test_token_roundtrip" { + // Verify: token text matches source for literals and identifiers + // Setup: tokenize source with identifiers and literals + // Expected: token.text == source substring +} + +test "test_register_recognition" { + // Verify: r0-r26 and R0-R26 are recognized as registers + // Setup: tokenize "r0 R15 r26" + // Expected: tokens with correct reg numbers +} + +test "test_number_formats" { + // Verify: decimal, hex (0x), binary (0b) numbers parsed correctly + // Setup: tokenize "42 0x2A 0b101010" + // Expected: all three evaluate to 42 +} + +test "test_comment_skipping" { + // Verify: comments (; comment) are not tokenized + // Setup: tokenize "instruction ; this is a comment" + // Expected: only "instruction" token, no comment tokens +} + +test "test_line_tracking" { + // Verify: token line and column are correct + // Setup: tokenize multi-line source + // Expected: token.line and token.column match source positions +} + +test "test_newline_handling" { + // Verify: newlines are tokenized and line counter increments + // Setup: tokenize source with multiple lines + // Expected: line number increments after each newline +} + +test "test_string_literal" { + // Verify: string literals are parsed correctly + // Setup: tokenize '"hello world"' + // Expected: token type STRING, text includes quotes +} + +// Invariants +invariant "token_roundtrip" { + // For all valid tokens: token.text == source[token.position : token.length] + // Rationale: Lexical analysis must preserve source text +} + +invariant "line_column_accuracy" { + // token.line and token.column accurately reflect source position + // Rationale: Error messages must point to correct location +} + +invariant "register_range_validity" { + // Only r0-r26 and R0-R26 are valid registers + // Rationale: Register encoding has fixed range (27 registers) +} + +invariant "number_format_validity" { + // 0x prefix -> hex, 0b prefix -> binary, else decimal + // Rationale: Number prefixes are unambiguous +} + +invariant "eof_token_termination" { + // Token stream always ends with EOF token + // Rationale: Parser needs explicit end marker +} + +invariant "no_lexical_errors_on_valid_source" { + // For syntactically valid t27: lexer produces no errors + // Rationale: Clean source should lex cleanly +} + +// Benchmarks +bench "bench_tokenize_throughput_chars_per_sec" { + // Measure: characters processed per second + // Target: > 1M chars/sec for typical source +} + +bench "test_token_memory_overhead" { + // Measure: bytes per token + // Target: < 64 bytes per token (efficient storage) +} diff --git a/apps/website/public/t27/files/compiler/parser/parser.t27 b/apps/website/public/t27/files/compiler/parser/parser.t27 new file mode 100644 index 0000000000..35487f4796 --- /dev/null +++ b/apps/website/public/t27/files/compiler/parser/parser.t27 @@ -0,0 +1,1294 @@ +// parser.t27 -- Parser for TRI-27 Assembly +// Builds AST from tokens produced by lexer +// phi^2 + 1/phi^2 = 3 | TRINITY + +module parser { + using lexer: @import("lexer.t27"); + using ast: @import("../../ast.t27"); + + // Parser state + pub const Parser = struct { + tokens: []Token, + pos: u32, + current: Token, + context: CompilerContext, + }; + + // Create new parser + pub fn Parser.new(tokens: []Token, source_file: []const u8) Parser { + const p = Parser{ + .tokens = tokens, + .pos = 0, + .current = Token{ .type = TokenType.EOF, .text = "", .value = 0, .line = 0, .column = 0, .source_file = source_file }, + .context = CompilerContext{ + .ast_root = Program{ + .node_type = NodeType.Program, + .line = 1, + .column = 1, + .source_file = source_file, + .spec_decl = null, + .constants = &[0]ConstDef{}, + .data_section = DataSection{}, + .code_section = CodeSection{}, + .test_section = null, + .invariant_section = null, + .bench_section = null, + .exports = &[0][]const u8{}, + .imports = &[0][]const u8{}, + }, + .symbol_table = SymbolTable.new(null), + .errors = &[0]CompileError{}, + .warnings = &[0]CompileWarning{}, + .current_phase = CompilationPhase.Parsing, + }, + }; + if (p.tokens.len > 0) { + p.current = p.tokens[0]; + } + return p; + } + + // Advance to next token + pub fn Parser.advance() Token { + const prev = self.current; + self.pos += 1; + if (self.pos < self.tokens.len) { + self.current = self.tokens[self.pos]; + } else { + self.current = Token{ + .type = TokenType.EOF, + .text = "", + .value = 0, + .line = 0, + .column = 0, + .source_file = self.context.ast_root.source_file, + }; + } + return prev; + } + + // Peek at next token + pub fn Parser.peek() Token { + if (self.pos + 1 < self.tokens.len) { + return self.tokens[self.pos + 1]; + } + return Token{ + .type = TokenType.EOF, + .text = "", + .value = 0, + .line = 0, + .column = 0, + .source_file = self.context.ast_root.source_file, + }; + } + + // Check if current token matches expected type + pub fn Parser.check(token_type: TokenType) bool { + return self.current.type == token_type; + } + + // Consume token if it matches, otherwise error + pub fn Parser.consume(token_type: TokenType, error_msg: []const u8) Token { + if (self.check(token_type)) { + return self.advance(); + } + self.context.add_error(error_msg, self.current.line, self.current.column); + return self.current; + } + + // Expect token type without consuming + pub fn Parser.expect(token_type: TokenType, error_msg: []const u8) void { + if (!self.check(token_type)) { + self.context.add_error(error_msg, self.current.line, self.current.column); + } + } + + // Skip newlines + pub fn Parser.skip_newlines() void { + while (self.check(TokenType.Newline)) { + _ = self.advance(); + } + } + + // Match any of the given token types + pub fn Parser.match(types: []const TokenType) bool { + for (types) |t| { + if (self.check(t)) { + return true; + } + } + return false; + } + + // Parse entire program + pub fn Parser.parse_program() Program { + self.skip_newlines(); + + // Check for spec-style header (high-level TDD) + if (self.check(TokenType.Spec)) { + _ = self.parse_spec_decl(); + self.skip_newlines(); + + // Parse test blocks in spec + while (self.check(TokenType.Test)) { + const test_block = self.parse_test_block(); + if (self.context.ast_root.spec_decl != null) { + self.context.ast_root.spec_decl.?.test_blocks.append(test_block); + } + self.skip_newlines(); + } + + // Parse invariants in spec + while (self.check(TokenType.Invariant)) { + const inv = self.parse_invariant_block(); + if (self.context.ast_root.spec_decl != null) { + self.context.ast_root.spec_decl.?.invariants.append(inv); + } + self.skip_newlines(); + } + + // Parse rules in spec + while (self.check(TokenType.Rule)) { + const rule = self.parse_rule_block(); + if (self.context.ast_root.spec_decl != null) { + self.context.ast_root.spec_decl.?.rules.append(rule); + } + self.skip_newlines(); + } + + // Validate spec has tests + _ = self.validate_spec(); + } else { + // Assembly-style parsing + + // Parse constants (.const declarations) + while (self.check(TokenType.Const) or self.check(TokenType.Dot)) { + _ = self.parse_const_def(); + self.skip_newlines(); + } + + // Parse data section + if (self.match(&[_]TokenType{TokenType.Data})) { + _ = self.parse_data_section(); + self.skip_newlines(); + } + + // Parse code section + if (self.match(&[_]TokenType{TokenType.Code})) { + _ = self.parse_code_section(); + self.skip_newlines(); + } + + // Parse test section (assembly-style) + if (self.match(&[_]TokenType{TokenType.Test})) { + _ = self.parse_test_section(); + self.skip_newlines(); + } + + // Parse invariant section (assembly-style) + if (self.match(&[_]TokenType{TokenType.Invariant})) { + _ = self.parse_invariant_section(); + self.skip_newlines(); + } + + // Parse bench section + if (self.match(&[_]TokenType{TokenType.Bench})) { + _ = self.parse_bench_section(); + self.skip_newlines(); + } + + // Validate assembly spec has tests + _ = self.validate_spec(); + } + + return self.context.ast_root; + } + + // Parse constant definition + pub fn Parser.parse_const_def() ConstDef { + var dot_token = Token{ .type = TokenType.Dot, .text = ".", .value = 0, .line = 0, .column = 0, .source_file = "" }; + + // Optional . prefix + if (self.check(TokenType.Dot)) { + dot_token = self.advance(); + } + + const const_token = self.consume(TokenType.Const, "Expected 'const' keyword"); + const name = self.consume(TokenType.Identifier, "Expected constant name"); + const value = self.consume(TokenType.Integer, "Expected integer value for constant"); + + const const_def = ConstDef{ + .node_type = NodeType.ConstDef, + .line = dot_token.line, + .column = dot_token.column, + .source_file = self.context.ast_root.source_file, + .name = name.text, + .value = value.value, + }; + + // Add to AST + self.context.ast_root.constants.append(const_def); + + // Add to symbol table + const symbol = Symbol{ + .name = name.text, + .node = ast_node_from_const(const_def), + .scope = "global", + .is_exported = true, + .is_defined = true, + }; + self.context.symbol_table.add(symbol); + + return const_def; + } + + // Parse data section + pub fn Parser.parse_data_section() DataSection { + const data_token = self.advance(); // Consume 'data' + self.skip_newlines(); + + const data_section = DataSection{ + .node_type = NodeType.DataSection, + .line = data_token.line, + .column = data_token.column, + .source_file = self.context.ast_root.source_file, + .declarations = &[0]DataDecl{}, + }; + + // Parse data declarations until .code or EOF + while (!self.check(TokenType.Code) and !self.check(TokenType.EOF)) { + self.skip_newlines(); + + // Check for label + var label: []const u8 = ""; + if (self.check(TokenType.Identifier) and self.peek().type == TokenType.Colon) { + const label_token = self.advance(); + label = label_token.text; + _ = self.consume(TokenType.Colon, "Expected colon after label"); + self.skip_newlines(); + } + + // Parse directive + if (self.match(&[_]TokenType{TokenType.Dot})) { + _ = self.advance(); // Consume '.' + + if (self.match(&[_]TokenType{TokenType.DWord})) { + self.parse_dword(label, &data_section); + } else if (self.match(&[_]TokenType{TokenType.DSpace})) { + self.parse_dspace(label, &data_section); + } else if (self.match(&[_]TokenType{TokenType.DTrit})) { + self.parse_dtrit(label, &data_section); + } + } + + self.skip_newlines(); + } + + self.context.ast_root.data_section = data_section; + return data_section; + } + + // Parse .dword declaration + pub fn Parser.parse_dword(label: []const u8, section: *DataSection) void { + const dword_token = self.advance(); // Consume 'dword' + const value = self.consume(TokenType.Integer, "Expected integer for .dword"); + + const decl = DataDecl{ + .node_type = NodeType.DWord, + .line = dword_token.line, + .column = dword_token.column, + .source_file = self.context.ast_root.source_file, + .size = 32, // 4 bytes + .initial_value = value.value, + .label = label, + }; + + section.declarations.append(decl); + } + + // Parse .dspace declaration + pub fn Parser.parse_dspace(label: []const u8, section: *DataSection) void { + const dspace_token = self.advance(); // Consume 'dspace' + const value = self.consume(TokenType.Integer, "Expected integer for .dspace"); + + const decl = DataDecl{ + .node_type = NodeType.DSpace, + .line = dspace_token.line, + .column = dspace_token.column, + .source_file = self.context.ast_root.source_file, + .size = 32, // 4 bytes + .initial_value = value.value, + .label = label, + }; + + section.declarations.append(decl); + } + + // Parse .dtrit declaration + pub fn Parser.parse_dtrit(label: []const u8, section: *DataSection) void { + const dtrit_token = self.advance(); // Consume 'dtrit' + const value = self.consume(TokenType.Integer, "Expected integer for .dtrit"); + + const decl = DataDecl{ + .node_type = NodeType.DTrit, + .line = dtrit_token.line, + .column = dtrit_token.column, + .source_file = self.context.ast_root.source_file, + .size = 2, // 2 bits per trit + .initial_value = value.value, + .label = label, + }; + + section.declarations.append(decl); + } + + // Parse code section + pub fn Parser.parse_code_section() CodeSection { + const code_token = self.advance(); // Consume 'code' + self.skip_newlines(); + + var code_section = CodeSection{ + .node_type = NodeType.CodeSection, + .line = code_token.line, + .column = code_token.column, + .source_file = self.context.ast_root.source_file, + .instructions = &[0]Instruction{}, + .labels = std.StringHashMap(u32).init(std.heap.page_allocator), + }; + + // Parse instructions until EOF + while (!self.check(TokenType.EOF)) { + self.skip_newlines(); + + // Check for label + if (self.check(TokenType.Identifier) and self.peek().type == TokenType.Colon) { + const label_token = self.advance(); + const label_name = label_token.text; + _ = self.consume(TokenType.Colon, "Expected colon after label"); + + // Record label position + code_section.labels.put(label_name, @intCast(code_section.instructions.len)); + + // Add to symbol table + const symbol = Symbol{ + .name = label_name, + .node = ast_node_from_label(label_token), + .scope = "code", + .is_exported = false, + .is_defined = true, + }; + self.context.symbol_table.add(symbol); + + self.skip_newlines(); + continue; + } + + // Parse instruction + if (self.check(TokenType.Identifier)) { + const inst = self.parse_instruction(); + if (inst != null) { + code_section.instructions.append(inst.?); + } + } + + self.skip_newlines(); + } + + self.context.ast_root.code_section = code_section; + return code_section; + } + + // Parse single instruction + pub fn Parser.parse_instruction() ?Instruction { + const opcode_token = self.current; + const opcode = self.get_opcode(opcode_token.text); + + if (opcode == null) { + self.context.add_error("Unknown instruction: " ++ opcode_token.text, opcode_token.line, opcode_token.column); + _ = self.advance(); + return null; + } + + _ = self.advance(); // Consume opcode + + var operands: []const Operand = &[0]Operand{}; + + // Parse operands + if (!self.check(TokenType.Semicolon) and !self.check(TokenType.Newline) and !self.check(TokenType.EOF)) { + const operand = self.parse_operand(); + if (operand != null) { + operands = operands ++ operand.?; + } + + // Parse comma-separated operands + while (self.check(TokenType.Comma)) { + _ = self.advance(); + self.skip_newlines(); + const op = self.parse_operand(); + if (op != null) { + operands = operands ++ op.?; + } + } + } + + // Optional comment + if (self.check(TokenType.Semicolon)) { + // Skip to end of line + while (!self.check(TokenType.Newline) and !self.check(TokenType.EOF)) { + _ = self.advance(); + } + } + + return Instruction{ + .node_type = NodeType.Instruction, + .line = opcode_token.line, + .column = opcode_token.column, + .source_file = self.context.ast_root.source_file, + .opcode = opcode.?, + .operands = operands, + }; + } + + // Parse operand + pub fn Parser.parse_operand() ?Operand { + self.skip_newlines(); + + // Register (r0-r26) + if (self.check(TokenType.Reg)) { + const reg_token = self.advance(); + return RegOperand{ + .node_type = NodeType.Reg, + .line = reg_token.line, + .column = reg_token.column, + .source_file = self.context.ast_root.source_file, + .operand_type = OperandType.Register, + .reg_num = @intCast(reg_token.value), + }; + } + + // Immediate (#value) + if (self.check(TokenType.Hash)) { + _ = self.advance(); // Consume '#' + const value_token = self.consume(TokenType.Integer, "Expected integer after '#'"); + return ImmOperand{ + .node_type = NodeType.Imm, + .line = value_token.line, + .column = value_token.column, + .source_file = self.context.ast_root.source_file, + .operand_type = OperandType.Immediate, + .value = value_token.value, + }; + } + + // Label reference + if (self.check(TokenType.Identifier)) { + const label_token = self.advance(); + return LabelOperand{ + .node_type = NodeType.Label, + .line = label_token.line, + .column = label_token.column, + .source_file = self.context.ast_root.source_file, + .operand_type = OperandType.LabelRef, + .label_name = label_token.text, + }; + } + + // Memory reference [reg] or [reg + offset] + if (self.check(TokenType.LBracket)) { + const lbracket_token = self.advance(); // Consume '[' and capture position + self.skip_newlines(); + + var base_reg: u8 = 0; + var offset: i16 = 0; + + if (self.check(TokenType.Reg)) { + const reg_token = self.advance(); + base_reg = @intCast(reg_token.value); + } + + self.skip_newlines(); + + // Check for offset + if (self.match(&[_]TokenType{ TokenType.Plus, TokenType.Minus })) { + const op = self.advance(); + const value_token = self.consume(TokenType.Integer, "Expected integer offset"); + if (op.type == TokenType.Plus) { + offset = @intCast(value_token.value); + } else { + offset = -@as(i16, @intCast(value_token.value)); + } + } + + _ = self.consume(TokenType.RBracket, "Expected ']'"); + + return MemOperand{ + .node_type = NodeType.Mem, + .line = lbracket_token.line, + .column = lbracket_token.column, + .source_file = self.context.ast_root.source_file, + .operand_type = OperandType.Memory, + .base_reg = base_reg, + .offset = offset, + }; + } + + self.context.add_error("Expected register, immediate, label, or memory reference", self.current.line, self.current.column); + return null; + } + + // Get opcode from mnemonic + pub fn Parser.get_opcode(mnemonic: []const u8) ?Opcode { + const upper = std.ascii.toUpperCase(mnemonic); + + if (std.mem.eql(u8, upper, "MOV")) return Opcode.MOV; + if (std.mem.eql(u8, upper, "JZ")) return Opcode.JZ; + if (std.mem.eql(u8, upper, "JNZ")) return Opcode.JNZ; + if (std.mem.eql(u8, upper, "JMP")) return Opcode.JMP; + if (std.mem.eql(u8, upper, "MUL")) return Opcode.MUL; + if (std.mem.eql(u8, upper, "ADD")) return Opcode.ADD; + if (std.mem.eql(u8, upper, "SUB")) return Opcode.SUB; + if (std.mem.eql(u8, upper, "BIND")) return Opcode.BIND; + if (std.mem.eql(u8, upper, "BUNDLE")) return Opcode.BUNDLE; + if (std.mem.eql(u8, upper, "HALT")) return Opcode.HALT; + + return null; + } + + // ===================================================================== + // TDD-Inside-Spec: High-level TDD parsing (spec-style) + // ===================================================================== + + // Parse spec declaration + pub fn Parser.parse_spec_decl() SpecDecl { + const spec_token = self.consume(TokenType.Spec, "Expected 'spec' keyword"); + const name_token = self.consume(TokenType.Identifier, "Expected spec name"); + + const spec_decl = SpecDecl{ + .node_type = NodeType.SpecDecl, + .line = spec_token.line, + .column = spec_token.column, + .source_file = self.context.ast_root.source_file, + .name = name_token.text, + .constants = &[0]ConstDef{}, + .test_blocks = &[0]TestBlock{}, + .invariants = &[0]InvariantDecl{}, + .rules = &[0]RuleDecl{}, + }; + + self.context.ast_root.spec_decl = spec_decl; + return spec_decl; + } + + // Parse test block with given/when/then clauses + pub fn Parser.parse_test_block() TestBlock { + const test_token = self.consume(TokenType.Test, "Expected 'test' keyword"); + const name_token = self.consume(TokenType.Identifier, "Expected test name"); + + var test_block = TestBlock{ + .node_type = NodeType.TestBlock, + .line = test_token.line, + .column = test_token.column, + .source_file = self.context.ast_root.source_file, + .name = name_token.text, + .given_clauses = &[0]GivenClause{}, + .when_clauses = &[0]WhenClause{}, + .then_clauses = &[0]ThenClause{}, + }; + + // Parse clauses + while (!self.check(TokenType.EOF) and !self.check(TokenType.Test) and + !self.check(TokenType.Invariant) and !self.check(TokenType.Bench)) { + self.skip_newlines(); + + if (self.check(TokenType.Given)) { + _ = self.advance(); + const var_token = self.consume(TokenType.Identifier, "Expected variable name in given"); + _ = self.consume(TokenType.Eq, "Expected '=' in given"); + // Parse expression (simplified - consume rest of line) + const expr_token = self.current; + self.skip_to_newline(); + + test_block.given_clauses = test_block.given_clauses ++ &[_]GivenClause{ + .{ + .node_type = NodeType.GivenClause, + .line = var_token.line, + .column = var_token.column, + .source_file = self.context.ast_root.source_file, + .variable = var_token.text, + .expression = expr_token.text, + }, + }; + } else if (self.check(TokenType.When)) { + _ = self.advance(); + const var_token = self.consume(TokenType.Identifier, "Expected variable name in when"); + _ = self.consume(TokenType.Eq, "Expected '=' in when"); + const expr_token = self.current; + self.skip_to_newline(); + + test_block.when_clauses = test_block.when_clauses ++ &[_]WhenClause{ + .{ + .node_type = NodeType.WhenClause, + .line = var_token.line, + .column = var_token.column, + .source_file = self.context.ast_root.source_file, + .variable = var_token.text, + .expression = expr_token.text, + }, + }; + } else if (self.check(TokenType.And)) { + _ = self.advance(); + // Determine if this is and/given, and/when, or and/then + var clause_type: []const u8 = "given"; + if (test_block.when_clauses.len > 0) { + clause_type = "when"; + } + if (test_block.then_clauses.len > 0) { + clause_type = "then"; + } + + var var_token = self.current; + if (self.check(TokenType.Identifier)) { + var_token = self.advance(); + _ = self.consume(TokenType.Eq, "Expected '=' in and clause"); + } + const expr_token = self.current; + self.skip_to_newline(); + + _ = test_block; // Use clause_type + _ = var_token; + _ = expr_token; + // AndClause appended to appropriate section + } else if (self.check(TokenType.Then)) { + _ = self.advance(); + const expr_token = self.current; + self.skip_to_newline(); + + test_block.then_clauses = test_block.then_clauses ++ &[_]ThenClause{ + .{ + .node_type = NodeType.ThenClause, + .line = expr_token.line, + .column = expr_token.column, + .source_file = self.context.ast_root.source_file, + .expression = expr_token.text, + }, + }; + } else { + // Unknown token, skip + _ = self.advance(); + } + } + + return test_block; + } + + // Parse invariant with assert + pub fn Parser.parse_invariant_block() InvariantDecl { + const inv_token = self.consume(TokenType.Invariant, "Expected 'invariant' keyword"); + const name_token = self.consume(TokenType.Identifier, "Expected invariant name"); + + // Parse formal statement (rest of line or until next keyword) + var formal_stmt: []const u8 = ""; + if (self.check(TokenType.Assert)) { + _ = self.advance(); + formal_stmt = self.current.text; + self.skip_to_newline(); + } else { + // Old-style assembly invariant + while (!self.check(TokenType.Newline) and !self.check(TokenType.EOF)) { + formal_stmt = formal_stmt ++ self.current.text ++ " "; + _ = self.advance(); + } + } + + const inv_decl = InvariantDecl{ + .node_type = NodeType.InvariantDecl, + .line = inv_token.line, + .column = inv_token.column, + .source_file = self.context.ast_root.source_file, + .name = name_token.text, + .formal_statement = formal_stmt, + .rationale = "", + }; + + return inv_decl; + } + + // Parse rule block with expect + pub fn Parser.parse_rule_block() RuleDecl { + const rule_token = self.consume(TokenType.Rule, "Expected 'rule' keyword"); + const name_token = self.consume(TokenType.Identifier, "Expected rule name"); + + var rule_decl = RuleDecl{ + .node_type = NodeType.RuleDecl, + .line = rule_token.line, + .column = rule_token.column, + .source_file = self.context.ast_root.source_file, + .name = name_token.text, + .expect_clauses = &[0]ExpectClause{}, + }; + + // Parse expect clauses + while (self.check(TokenType.Expect)) { + _ = self.advance(); + const expr_token = self.current; + self.skip_to_newline(); + + rule_decl.expect_clauses = rule_decl.expect_clauses ++ &[_]ExpectClause{ + .{ + .node_type = NodeType.ExpectClause, + .line = expr_token.line, + .column = expr_token.column, + .source_file = self.context.ast_root.source_file, + .expression = expr_token.text, + }, + }; + } + + return rule_decl; + } + + // Validate spec has at least one test or invariant + pub fn Parser.validate_spec() bool { + var has_tests = false; + + if (self.context.ast_root.test_section != null and + self.context.ast_root.test_section.?.test_cases.len > 0) { + has_tests = true; + } + + if (self.context.ast_root.spec_decl != null) { + const spec = self.context.ast_root.spec_decl.?; + if (spec.test_blocks.len > 0 or spec.invariants.len > 0) { + has_tests = true; + } + } + + if (!has_tests) { + self.context.add_error( + "TDD contract violated: spec must contain at least one 'test' or 'invariant' block", + self.context.ast_root.line, 1); + return false; + } + + return true; + } + + // Skip to end of line (for expression parsing) + pub fn Parser.skip_to_newline() void { + while (!self.check(TokenType.Newline) and !self.check(TokenType.EOF)) { + _ = self.advance(); + } + } + + // ===================================================================== + // TDD-Inside-Spec: Assembly-style TDD parsing (.test, .invariant, .bench) + // ===================================================================== + + // Parse test section (assembly-style) + // Format: .test followed by comment-style test definitions + pub fn Parser.parse_test_section() TestSection { + const test_token = self.advance(); // Consume 'test' + + var test_section = TestSection{ + .node_type = NodeType.Test, + .line = test_token.line, + .column = test_token.column, + .source_file = self.context.ast_root.source_file, + .test_cases = &[0]TestCase{}, + }; + + // Parse test cases (comment-style) + while (!self.check(TokenType.Data) and !self.check(TokenType.Code) and + !self.check(TokenType.Invariant) and !self.check(TokenType.Bench) and + !self.check(TokenType.EOF)) { + self.skip_newlines(); + + // Look for comment-style test definition: ; test_name + if (self.check(TokenType.Semicolon)) { + _ = self.advance(); + if (self.check(TokenType.Identifier)) { + const name_token = self.advance(); + + // Skip to next test or section + var verify_desc: []const u8 = ""; + var expected_desc: []const u8 = ""; + var setup_desc: []const u8 = ""; + var rationale: []const u8 = ""; + + // Parse optional annotations + while (!self.check(TokenType.Semicolon) and !self.check(TokenType.EOF)) { + if (self.check(TokenType.Identifier)) { + const id = self.current.text; + _ = self.advance(); + if (std.mem.eql(u8, id, "Verify:") or std.mem.eql(u8, id, "verify")) { + verify_desc = self.current.text; + } else if (std.mem.eql(u8, id, "Expected:") or std.mem.eql(u8, id, "expected")) { + expected_desc = self.current.text; + } else if (std.mem.eql(u8, id, "Setup:") or std.mem.eql(u8, id, "setup")) { + setup_desc = self.current.text; + } else if (std.mem.eql(u8, id, "Rationale:") or std.mem.eql(u8, id, "rationale")) { + rationale = self.current.text; + } + } + _ = self.advance(); + if (self.check(TokenType.Newline)) { + break; + } + } + + const test_case = TestCase{ + .node_type = NodeType.TestCase, + .line = name_token.line, + .column = name_token.column, + .source_file = self.context.ast_root.source_file, + .name = name_token.text, + .verify_description = verify_desc, + .expected_outcome = expected_desc, + .setup_description = setup_desc, + .rationale = rationale, + }; + + test_section.test_cases = test_section.test_cases ++ &[_]TestCase{test_case}; + } + } else { + _ = self.advance(); + } + } + + self.context.ast_root.test_section = test_section; + return test_section; + } + + // Parse invariant section (assembly-style) + pub fn Parser.parse_invariant_section() InvariantSection { + const inv_token = self.advance(); // Consume 'invariant' + + var inv_section = InvariantSection{ + .node_type = NodeType.Invariant, + .line = inv_token.line, + .column = inv_token.column, + .source_file = self.context.ast_root.source_file, + .invariants = &[0]InvariantDecl{}, + }; + + // Parse invariants (comment-style) + while (!self.check(TokenType.Data) and !self.check(TokenType.Code) and + !self.check(TokenType.Test) and !self.check(TokenType.Bench) and + !self.check(TokenType.EOF)) { + self.skip_newlines(); + + // Look for comment-style invariant: ; invariant_name + if (self.check(TokenType.Semicolon)) { + _ = self.advance(); + if (self.check(TokenType.Identifier)) { + const name_token = self.advance(); + + // Parse formal statement and rationale + var formal_stmt: []const u8 = ""; + var rationale: []const u8 = ""; + + while (!self.check(TokenType.Semicolon) and !self.check(TokenType.EOF)) { + if (self.check(TokenType.Identifier)) { + const id = self.current.text; + _ = self.advance(); + if (std.mem.eql(u8, id, "Rationale:") or std.mem.eql(u8, id, "rationale")) { + rationale = self.current.text; + } else { + formal_stmt = formal_stmt ++ id ++ " "; + } + } else { + formal_stmt = formal_stmt ++ self.current.text ++ " "; + _ = self.advance(); + } + if (self.check(TokenType.Newline)) { + break; + } + } + + const inv_decl = InvariantDecl{ + .node_type = NodeType.InvariantDecl, + .line = name_token.line, + .column = name_token.column, + .source_file = self.context.ast_root.source_file, + .name = name_token.text, + .formal_statement = formal_stmt, + .rationale = rationale, + }; + + inv_section.invariants = inv_section.invariants ++ &[_]InvariantDecl{inv_decl}; + } + } else { + _ = self.advance(); + } + } + + self.context.ast_root.invariant_section = inv_section; + return inv_section; + } + + // Parse bench section (assembly-style) + pub fn Parser.parse_bench_section() BenchSection { + const bench_token = self.advance(); // Consume 'bench' + + var bench_section = BenchSection{ + .node_type = NodeType.Bench, + .line = bench_token.line, + .column = bench_token.column, + .source_file = self.context.ast_root.source_file, + .benchmarks = &[0]BenchDecl{}, + }; + + // Parse benchmarks (comment-style) + while (!self.check(TokenType.Data) and !self.check(TokenType.Code) and + !self.check(TokenType.Test) and !self.check(TokenType.Invariant) and + !self.check(TokenType.EOF)) { + self.skip_newlines(); + + // Look for comment-style benchmark: ; bench_name + if (self.check(TokenType.Semicolon)) { + _ = self.advance(); + if (self.check(TokenType.Identifier)) { + const name_token = self.advance(); + + // Parse measure description and target + var measure_desc: []const u8 = ""; + var target: []const u8 = ""; + var units: []const u8 = ""; + + while (!self.check(TokenType.Semicolon) and !self.check(TokenType.EOF)) { + if (self.check(TokenType.Identifier)) { + const id = self.current.text; + _ = self.advance(); + if (std.mem.eql(u8, id, "Measure:") or std.mem.eql(u8, id, "measure")) { + measure_desc = self.current.text; + } else if (std.mem.eql(u8, id, "Target:") or std.mem.eql(u8, id, "target")) { + target = self.current.text; + } else { + units = units ++ id ++ " "; + } + } else { + _ = self.advance(); + } + if (self.check(TokenType.Newline)) { + break; + } + } + + const bench_decl = BenchDecl{ + .node_type = NodeType.BenchDecl, + .line = name_token.line, + .column = name_token.column, + .source_file = self.context.ast_root.source_file, + .name = name_token.text, + .measure_description = measure_desc, + .target = if (target.len > 0) target else null, + .units = units, + }; + + bench_section.benchmarks = bench_section.benchmarks ++ &[_]BenchDecl{bench_decl}; + } + } else { + _ = self.advance(); + } + } + + self.context.ast_root.bench_section = bench_section; + return bench_section; + } + + // Main parsing entry point + pub fn parse(source: []const u8, source_file: []const u8) CompilerContext { + // Language Policy: No Cyrillic in source files (SOUL.md Law #1) + var policy_parser = Parser.new(&[_]Token{}, source_file); + if (!policy_parser.validate_no_cyrillic(source, source_file)) { + // Return context with the error added + return policy_parser.context; + } + + // Tokenize + const tokens = tokenize(source, source_file); + + // Parse + var parser = Parser.new(tokens, source_file); + _ = parser.parse_program(); + + return parser.context; + } + + // Helper: create AST node from const def + pub fn ast_node_from_const(const_def: ConstDef) ASTNode { + return ASTNode{ + .node_type = NodeType.ConstDef, + .line = const_def.line, + .column = const_def.column, + .source_file = const_def.source_file, + }; + } + + // Helper: create AST node from label token + pub fn ast_node_from_label(token: Token) ASTNode { + return ASTNode{ + .node_type = NodeType.Label, + .line = token.line, + .column = token.column, + .source_file = token.source_file, + }; + } + + // ===================================================================== + // Language Policy: No Cyrillic in Source Files (SOUL.md Law #1) + // ===================================================================== + + // Validate source file contains no Cyrillic characters + pub fn Parser.validate_no_cyrillic(source: []const u8, source_file: []const u8) bool { + // Check if file is in docs/ directory (Cyrillic allowed in docs) + if (std.mem.indexOf(u8, source_file, "/docs/") != null or + std.mem.startsWith(u8, source_file, "docs/")) { + return true; + } + + // Scan for Cyrillic characters (U+0400-U+04FF) + for (source, 0..) |c, i| { + // Check for Cyrillic range + // U+0400 (1040) to U+04FF (1279) + if (c >= 0xD0 and c <= 0xD0) { // May be start of 2-byte Cyrillic + if (i + 1 < source.len) { + const next_c = source[i + 1]; + const codepoint = (@as(u32, c) << 8) | @as(u32, next_c); + // Cyrillic block: U+0400-U+04FF + if (codepoint >= 0x0400 and codepoint <= 0x04FF) { + self.context.add_error( + "Language policy violation: source file contains Cyrillic characters (U+0400-U+04FF). Source files (.t27, .tri, .zig, .c, .v) must be ASCII-only. See SOUL.md Law #1.", + self.get_line_at_pos(source, @intCast(i)), + self.get_column_at_pos(source, @intCast(i)), + ); + return false; + } + } + } + } + + return true; + } + + // Helper: get line number at position + pub fn Parser.get_line_at_pos(source: []const u8, pos: u32) u32 { + var line: u32 = 1; + for (source[0..pos]) |c| { + if (c == '\n') { + line += 1; + } + } + return line; + } + + // Helper: get column number at position + pub fn Parser.get_column_at_pos(source: []const u8, pos: u32) u32 { + var column: u32 = 1; + var i: u32 = pos; + + while (i > 0 and source[i - 1] != '\n') { + i -= 1; + column += 1; + } + + return column; + } + + // ===================================================================== + // TDD-Inside-Spec: Alternative section parsing implementations + // ===================================================================== + + // Parse field description after field keyword (e.g., "Verify: description text") + pub fn Parser.parse_field_description() []const u8 { + // Skip colon if present + if (self.check(TokenType.Colon)) { + _ = self.advance(); + } + + // Collect rest of comment as description + return self.parse_comment_rest(); + } + + // Parse the rest of a comment line as text + pub fn Parser.parse_comment_rest() []const u8 { + var result: []const u8 = ""; + + while (!self.check(TokenType.Newline) and !self.check(TokenType.EOF)) { + const token = self.advance(); + if (token.type == TokenType.Identifier or token.type == TokenType.Integer) { + if (result.len > 0) { + result = result ++ " "; + } + result = result ++ token.text; + } + } + + return result; + } + + // Skip to end of line + pub fn Parser.skip_line() void { + while (!self.check(TokenType.Newline) and !self.check(TokenType.EOF)) { + _ = self.advance(); + } + } + + // Infer units from measurement description + pub fn Parser.infer_units(desc: []const u8) []const u8 { + const lower = std.ascii.toLowerCase(desc); + + if (std.mem.indexOf(u8, lower, "cycle") != null) { + return "cycles"; + } else if (std.mem.indexOf(u8, lower, "ns") != null) { + return "ns"; + } else if (std.mem.indexOf(u8, lower, "ms") != null) { + return "ms"; + } else if (std.mem.indexOf(u8, lower, "sec") != null) { + return "s"; + } else if (std.mem.indexOf(u8, lower, "ops") != null) { + return "ops/sec"; + } else if (std.mem.indexOf(u8, lower, "throughput") != null) { + return "lines/sec"; + } else if (std.mem.indexOf(u8, lower, "latency") != null) { + return "ns"; + } else if (std.mem.indexOf(u8, lower, "overhead") != null) { + return "bytes"; + } else if (std.mem.indexOf(u8, lower, "memory") != null) { + return "bytes"; + } + + return "units"; + } + + // ========== TDD-Inside-Spec: Tests and Invariants for Parser ========== + + test test_parser_empty_program + // Verify: parser handles empty input + // Setup: parse empty string + // Expected: program with no errors + const source = ""; + const ctx = parser.parse(source, "test.t27"); + try std.testing.expect(ctx.errors.len == 0); + +test test_parser_const_definition + // Verify: parser extracts constant definitions + // Setup: parse .const MAX 100 + // Expected: constant in AST with correct name and value + const source = ".const MAX 100"; + const ctx = parser.parse(source, "test.t27"); + try std.testing.expect(ctx.ast_root.constants.len == 1); + try std.testing.expect(std.mem.eql(u8, ctx.ast_root.constants[0].name, "MAX")); + +test test_parser_data_section + // Verify: parser extracts data section + // Setup: parse .data section with labels + // Expected: data_section populated correctly + const source = ".data\nstart: .dword 42"; + const ctx = parser.parse(source, "test.t27"); + try std.testing.expect(ctx.ast_root.data_section.declarations.len == 1); + +test test_parser_code_section + // Verify: parser extracts code section with labels and instructions + // Setup: parse .code section with MOV instruction + // Expected: code_section has instruction at label + const source = ".code\nstart: MOV r0, #1"; + const ctx = parser.parse(source, "test.t27"); + try std.testing.expect(ctx.ast_root.code_section.instructions.len == 1); + +test test_parser_instruction_operands + // Verify: parser handles all operand types + // Setup: parse instruction with register, immediate, label, memory + // Expected: operands parsed correctly + const source = ".code\nMOV r0, #1\nJMP label\nLOAD r0, [r1]"; + const ctx = parser.parse(source, "test.t27"); + try std.testing.expect(ctx.ast_root.code_section.instructions.len == 3); + +test test_parser_test_section + // Verify: parser extracts test section + // Setup: parse .test section with comment-style tests + // Expected: test_section populated with test cases + const source = ".test\n; test_name\n; Verify: something works\n; Expected: 42"; + const ctx = parser.parse(source, "test.t27"); + try std.testing.expect(ctx.ast_root.test_section != null); + try std.testing.expect(ctx.ast_root.test_section.?.test_cases.len == 1); + +test test_parser_invariant_section + // Verify: parser extracts invariant section + // Setup: parse .invariant section + // Expected: invariant_section populated with invariants + const source = ".invariant\n; arena_no_leak\n; heap_ptr never exceeds heap_end\n; Rationale: bounds checking"; + const ctx = parser.parse(source, "test.t27"); + try std.testing.expect(ctx.ast_root.invariant_section != null); + +test test_parser_bench_section + // Verify: parser extracts bench section + // Setup: parse .bench section + // Expected: bench_section populated with benchmarks + const source = ".bench\n; alloc_latency\n; Measure: allocation time\n; Target: < 10 cycles"; + const ctx = parser.parse(source, "test.t27"); + try std.testing.expect(ctx.ast_root.bench_section != null); + +test test_parser_spec_style + // Verify: parser handles high-level spec-style + // Setup: parse spec with test block + // Expected: spec_decl populated with test_blocks + const source = "spec test_spec\n test basic\n given x = 1\n when y = x + 1\n then y == 2"; + const ctx = parser.parse(source, "test.t27"); + try std.testing.expect(ctx.ast_root.spec_decl != null); + +test test_parser_opcode_mnemonic + // Verify: get_opcode maps mnemonics correctly + // Setup: call get_opcode with various mnemonics + // Expected: correct Opcode returned + var p = parser.Parser.new(&[_]parser.Token{}, "test.t27"); + try std.testing.expect(p.get_opcode("MOV") == parser.Opcode.MOV); + try std.testing.expect(p.get_opcode("ADD") == parser.Opcode.ADD); + try std.testing.expect(p.get_opcode("UNKNOWN") == null); + +test test_parser_language_policy_cyrillic + // Verify: parser rejects Cyrillic in source (SOUL.md Law #1) + // Setup: parse source with Cyrillic characters + // Expected: error added to context + const source = "const DD D~D'D.Dc 42"; // Cyrillic + const ctx = parser.parse(source, "test.t27"); + try std.testing.expect(ctx.errors.len > 0); + +test test_parser_language_policy_docs_exception + // Verify: Cyrillic allowed in docs/ directory + // Setup: parse source in docs/ path with Cyrillic + // Expected: no error + const source = "const DD D~D'D.Dc 42"; + const ctx = parser.parse(source, "docs/README.md"); + try std.testing.expect(ctx.errors.len == 0); + +invariant parser_no_crash_on_empty + // parser never crashes on empty input + // Rationale: Robustness + const source = ""; + _ = parser.parse(source, "test.t27"); + +invariant parser_symbol_table_consistent + // symbol_table contains all defined labels and constants + // Rationale: Linking correctness + const source = ".const X 1\n.code\nlabel: MOV r0, #X"; + const ctx = parser.parse(source, "test.t27"); + _ = ctx; + // assert ctx.symbol_table has entries for "X" and "label" + +invariant parser_tdd_contract_enforced + // validate_spec enforces at least one test or invariant + // Rationale: TDD-Inside-Spec compliance + // A spec without tests should have an error in context + // (specific validation is tested in test_parser_validate_spec) + +bench bench_parse_throughput_lines_per_sec + target: > 10K lines/sec + const source = ".code\nMOV r0, #1\nMOV r1, #2\nADD r0, r1\nHALT"; + _ = parser.parse(source, "test.t27"); + +bench bench_parse_latency_ns_per_line + target: < 1000 ns/line + const source = ".const MAX 100\n.data\nval: .dword 0\n.code\nstart: MOV r0, #MAX\nHALT"; + _ = parser.parse(source, "test.t27"); +} diff --git a/apps/website/public/t27/files/compiler/runtime/commands.t27 b/apps/website/public/t27/files/compiler/runtime/commands.t27 new file mode 100644 index 0000000000..1936924616 --- /dev/null +++ b/apps/website/public/t27/files/compiler/runtime/commands.t27 @@ -0,0 +1,1039 @@ +// commands.t27 -- CLI Command Specifications +// Individual command specifications for tri CLI +// phi^2 + 1/phi^2 = 3 | TRINITY + +module commands { + // ===================================================================== + // Command Enum + // ===================================================================== + + pub const Command = enum(u8) { + SpecCommand, // spec create, validate, list + GenCommand, // gen , gen --all + CompileProjectCommand, // compile-project: multi-file with resolved imports + GitCommand, // git commit, push, status + LintCommand, // lint [file], lint --all + SkillCommand, // skill begin, seal, status + HelpCommand, // help + }; + + // ===================================================================== + // Command: tri spec + // ===================================================================== + + // tri spec create [--kind feature|bugfix|hotfix|recovery] + pub fn spec_create(name: []const u8, kind: []const u8) i32 { + // Validate name + if (name.len == 0) { + error_print("ERROR: spec name cannot be empty"); + return 1; + } + + // Validate kind + const valid_kinds = [_][]const u8{ "feature", "bugfix", "hotfix", "recovery" }; + var valid = false; + for (valid_kinds) |k| { + if (std.mem.eql(u8, kind, k)) { + valid = true; + break; + } + } + + if (!valid) { + error_print("ERROR: invalid kind: "); + error_print(kind); + error_print("\nValid kinds: feature, bugfix, hotfix, recovery"); + return 1; + } + + // Create spec file + const spec_path = "specs/" ++ name ++ ".t27"; + + if (file_exists(spec_path)) { + error_print("ERROR: spec already exists: "); + error_print(spec_path); + return 1; + } + + const spec_content = generate_spec_template(name, kind); + write_file(spec_path, spec_content); + + print("Created spec: "); + print(spec_path); + print("\nKind: "); + print(kind); + print("\n"); + print("NOTE: Spec must contain at least one 'test' or 'invariant' block"); + print("Run 'tri gen "); + print(spec_path); + print("' to generate code"); + + return 0; + } + + // tri spec validate + pub fn spec_validate(spec_path: []const u8) i32 { + // Check if spec exists + if (!file_exists(spec_path)) { + error_print("ERROR: spec not found: "); + error_print(spec_path); + return 1; + } + + // Parse spec + const spec = parse_spec(spec_path); + if (spec == null) { + error_print("ERROR: failed to parse spec: "); + error_print(spec_path); + return 1; + } + + // Check TDD contract + if (spec.?.test_blocks.len == 0 and spec.?.invariant_blocks.len == 0) { + error_print("ERROR: TDD contract violated"); + error_print("Spec must contain at least one 'test' or 'invariant' block"); + error_print("See: docs/TDD-CONTRACT.md"); + return 1; + } + + // Check language policy + if (contains_cyrillic(spec.?.content) and !is_docs_path(spec_path)) { + error_print("ERROR: Language policy violated"); + error_print("Source files must not contain Cyrillic characters"); + error_print("See: ADR-004-language-policy.md"); + return 1; + } + + // Validate structure + const validation_result = validate_spec_structure(spec.?); + if (validation_result != 0) { + return validation_result; + } + + print("Valid: "); + print(spec_path); + print("\n Tests: "); + print_int(spec.?.test_blocks.len); + print("\n Invariants: "); + print_int(spec.?.invariant_blocks.len); + + return 0; + } + + // tri spec list + pub fn spec_list() i32 { + const spec_files = list_files("specs/**/*.t27"); + + if (spec_files.len == 0) { + print("No spec files found in specs/"); + return 0; + } + + print("Spec files ("); + print_int(spec_files.len); + print("):\n"); + + for (spec_files) |spec_path| { + const spec = parse_spec(spec_path); + const status = if (spec != null) blk: { + var buf: [64]u8 = undefined; + const t_count = std.fmt.bufPrintInt(&buf, spec.?.test_blocks.len, 10, .lower, .{}) catch ""; + const i_count = std.fmt.bufPrintInt(&buf, spec.?.invariant_blocks.len, 10, .lower, .{}) catch ""; + break :blk "T:" ++ t_count ++ " I:" ++ i_count; + } else { + "ERROR" + }; + print(" "); + print(spec_path); + print(" ["); + print(status); + print("]\n"); + } + + return 0; + } + + // ===================================================================== + // Command: tri gen + // ===================================================================== + + // tri gen [--backend zig|c|verilog] [--emit-conformance] + pub fn gen_spec(spec_path: []const u8, backend: []const u8, emit_conformance: bool) i32 { + // Validate spec first + const validation = spec_validate(spec_path); + if (validation != 0) { + error_print("ERROR: spec validation failed, cannot generate code"); + return validation; + } + + // Parse spec + const spec = parse_spec(spec_path); + if (spec == null) { + error_print("ERROR: failed to parse spec"); + return 1; + } + + // Generate backend code + var result: i32 = 0; + if (std.mem.eql(u8, backend, "zig")) { + result = generate_zig(spec.?); + } else if (std.mem.eql(u8, backend, "c")) { + result = generate_c(spec.?); + } else if (std.mem.eql(u8, backend, "verilog")) { + result = generate_verilog(spec.?); + } else { + error_print("ERROR: unknown backend: "); + error_print(backend); + return 1; + } + + if (result != 0) { + error_print("ERROR: code generation failed"); + return result; + } + + // Generate conformance if requested + if (emit_conformance) { + const conformance_result = generate_conformance(spec.?); + if (conformance_result != 0) { + error_print("WARNING: conformance generation failed"); + } + } + + print("Generated code from: "); + print(spec_path); + print("\n Backend: "); + print(backend); + + return 0; + } + + // tri gen --all + pub fn gen_all(backend: []const u8) i32 { + const spec_files = list_files("specs/**/*.t27"); + var failed: i32 = 0; + + for (spec_files) |spec_path| { + const result = gen_spec(spec_path, backend, false); + if (result != 0) { + failed += 1; + error_print("Failed to generate: "); + error_print(spec_path); + } + } + + const total = spec_files.len; + const success = @as(i32, @intCast(total)) - failed; + + print("\nGeneration complete:\n"); + print(" Total: "); + print_int(total); + print("\n Success: "); + print_int(success); + print("\n Failed: "); + print_int(failed); + + return if (failed > 0) 1 else 0; + } + + // ===================================================================== + // Command: tri compile-project + // ===================================================================== + + // tri compile-project --output [--backend zig|c|verilog] + // Generates ALL specs into a coherent project with working inter-file imports. + // + // Pass 1: Scan all .t27 files, build module -> file path map + // Pass 2: For each file, resolve `use X::Y` to correct relative path + // Pass 3: Write all files + build.zig to output directory + pub fn compile_project(output_dir: []const u8, backend: []const u8) i32 { + // Validate backend + if (!std.mem.eql(u8, backend, "zig") and + !std.mem.eql(u8, backend, "c") and + !std.mem.eql(u8, backend, "verilog")) + { + error_print("ERROR: unknown backend: "); + error_print(backend); + error_print("\nValid backends: zig, c, verilog"); + return 1; + } + + // Pass 1: Scan all .t27 files and build module map + const spec_files = list_files("specs/**/*.t27"); + const compiler_files = list_files("compiler/**/*.t27"); + const all_files = concat(spec_files, compiler_files); + + var module_map: ModuleMap = undefined; + module_map.init(); + + for (all_files) |file_path| { + const source = read_file(file_path); + if (source == null) continue; + + const rel_path = strip_prefix(file_path); + const module_key = path_to_module_key(rel_path); + module_map.put(module_key, rel_path); + } + + print("Module map: "); + print_int(module_map.count()); + print(" entries\n"); + + // Pass 2: Compile each file with resolved imports + var success: i32 = 0; + var failed: i32 = 0; + + for (all_files) |file_path| { + const source = read_file(file_path); + if (source == null) { + failed += 1; + continue; + } + + const rel_path = strip_prefix(file_path); + const code = compile_with_resolved_imports(source.?, rel_path, backend, module_map); + + if (code == null) { + failed += 1; + error_print("Failed: "); + error_print(file_path); + error_print("\n"); + continue; + } + + const ext = backend_extension(backend); + const dest = output_dir ++ "/" ++ replace(rel_path, ".t27", ext); + const dir = dir_path(dest); + create_dir_if_not_exists(dir); + write_file(dest, code.?); + success += 1; + } + + // Pass 3: Generate build.zig (Zig backend only) + if (std.mem.eql(u8, backend, "zig")) { + const build_zig = generate_build_zig(all_files, output_dir); + write_file(output_dir ++ "/build.zig", build_zig); + print("Generated build.zig\n"); + } + + print("\ncompile-project complete:\n"); + print(" Output: "); + print(output_dir); + print("\n Success: "); + print_int(success); + print("\n Failed: "); + print_int(failed); + + return if (failed > 0) 1 else 0; + } + + // ===================================================================== + // Command: tri lint + // ===================================================================== + + // tri lint [file] [--strict] + pub fn lint_file(file_path: []const u8, strict: bool) i32 { + // Check file extension + if (!std.mem.endsWith(u8, file_path, ".t27")) { + error_print("ERROR: .t27 file required"); + return 1; + } + + // Validate spec + const result = spec_validate(file_path); + if (result != 0) { + return result; + } + + // Strict mode checks + if (strict) { + const spec = parse_spec(file_path); + if (spec.?.test_blocks.len == 0 and spec.?.invariant_blocks.len == 0) { + error_print("ERROR: strict mode requires tests"); + return 1; + } + } + + print("Lint passed: "); + print(file_path); + return 0; + } + + // tri lint --all + pub fn lint_all(strict: bool) i32 { + const spec_files = list_files("specs/**/*.t27"); + var failed: i32 = 0; + + for (spec_files) |spec_path| { + const result = lint_file(spec_path, strict); + if (result != 0) { + failed += 1; + } + } + + if (failed > 0) { + error_print("Lint failed: "); + print_int(failed); + error_print(" files"); + return 1; + } + + print("All specs passed lint"); + return 0; + } + + // ===================================================================== + // Command: tri skill + // ===================================================================== + + // tri skill begin --issue [--kind feature|bugfix|hotfix|recovery] + pub fn skill_begin(issue_id: []const u8, kind: []const u8) i32 { + const registry_path = ".trinity/skills/registry.json"; + + // Validate issue ID + if (issue_id.len == 0) { + error_print("ERROR: issue ID required"); + error_print("Usage: tri skill begin --issue "); + return 1; + } + + // Load or create registry + const registry = load_or_create_registry(registry_path); + + // Create new skill + var skill_id_buf: [32]u8 = undefined; + const skill_id = std.fmt.bufPrint(&skill_id_buf, "skill-{d}", .{registry.skills.len + 1}) catch ""; + const timestamp = get_current_timestamp(); + const branch = git_get_current_branch(); + + const new_skill = Skill{ + .id = skill_id, + .status = "active", + .kind = kind, + .issue = issue_id, + .branch = branch, + .created_at = timestamp, + .updated_at = timestamp, + .sealed_at = null, + .commit = null, + .commit_at = null, + .pushed = false, + .pushed_at = null, + .verdict = null, + .verdict_at = null, + .seal_hash = null, + .artifacts = "", + .metadata = SkillMetadata{ + .title = "", + .author = "cli", + .priority = "P1", + .tags = &[_][]const u8{}, + }, + }; + + registry.skills.append(new_skill); + write_registry(registry_path, registry); + + print("Skill started:\n"); + print(" ID: "); + print(skill_id); + print("\n Issue: "); + print(issue_id); + print("\n Kind: "); + print(kind); + print("\n Branch: "); + print(branch); + + return 0; + } + + // tri skill seal + pub fn skill_seal() i32 { + const registry_path = ".trinity/skills/registry.json"; + + if (!file_exists(registry_path)) { + error_print("ERROR: no active skill found"); + error_print("Run 'tri skill begin --issue N' first"); + return 1; + } + + const registry = load_registry(registry_path); + const skill = find_active_skill(registry); + + if (skill == null) { + error_print("ERROR: no active skill found"); + return 1; + } + + // Seal the skill + const timestamp = get_current_timestamp(); + skill.?.status = "sealed"; + skill.?.sealed_at = timestamp; + skill.?.seal_hash = compute_seal_hash(skill.?); + skill.?.updated_at = timestamp; + + write_registry(registry_path, registry); + + print("Skill sealed:\n"); + print(" ID: "); + print(skill.?.id); + print("\n Seal hash: "); + print(skill.?.seal_hash orelse ""); + print("\n"); + print("Run 'tri git commit' and 'tri git push' to complete"); + + return 0; + } + + // tri skill status + pub fn skill_status() i32 { + const registry_path = ".trinity/skills/registry.json"; + + if (!file_exists(registry_path)) { + print("No skill registry found"); + print("Run 'tri skill begin --issue N' to start a skill"); + return 0; + } + + const registry = load_registry(registry_path); + const skill = find_active_or_sealed_skill(registry); + + if (skill == null) { + print("No active or sealed skill found"); + return 0; + } + + print("Current skill:\n"); + print(" ID: "); + print(skill.?.id); + print("\n Status: "); + print(skill.?.status); + print("\n Kind: "); + print(skill.?.kind); + print("\n Issue: "); + print(skill.?.issue); + print("\n Branch: "); + print(skill.?.branch); + + if (skill.?.verdict) |verdict| { + print("\n Verdict: "); + print(verdict); + } + + if (skill.?.artifacts.len > 0) { + print("\n Artifacts: "); + print(skill.?.artifacts); + } + + return 0; + } + + // ===================================================================== + // Command: tri help + // ===================================================================== + + // tri help [command] + pub fn help(command: []const u8) i32 { + if (command.len == 0) { + print("Trinity t27 CLI - Spec-First Development Framework\n"); + print("\nUsage: tri [options]\n"); + print("\nCommands:\n"); + print(" spec Manage specifications\n"); + print(" create Create a new spec\n"); + print(" validate Validate a spec\n"); + print(" list List all specs\n"); + print("\n"); + print(" gen Generate code from specs\n"); + print(" Generate from spec\n"); + print(" --all Generate from all specs\n"); + print(" --backend Backend: zig, c, verilog\n"); + print("\n"); + print(" compile-project Compile all specs into coherent project\n"); + print(" --output Output directory (default: build)\n"); + print(" --backend Backend: zig, c, verilog\n"); + print("\n"); + print(" git Git integration with skill workflow\n"); + print(" commit Commit with skill validation\n"); + print(" push Push with skill validation\n"); + print(" status Show git and skill status\n"); + print("\n"); + print(" lint Lint specifications\n"); + print(" Lint a spec\n"); + print(" --all Lint all specs\n"); + print(" --strict Enable strict mode\n"); + print("\n"); + print(" skill Skill workflow management\n"); + print(" begin --issue N Start a new skill\n"); + print(" seal Seal current skill\n"); + print(" status Show skill status\n"); + print("\n"); + print(" help Show help\n"); + print(" Show command help\n"); + print("\n"); + print("Documentation:\n"); + print(" docs/SOUL.md Constitutional laws\n"); + print(" docs/TDD-CONTRACT.md TDD requirements\n"); + print(" docs/GENERATED-HEADER-POLICY.md Generated file policy"); + return 0; + } + + // Command-specific help + if (std.mem.eql(u8, command, "spec")) { + print("tri spec - Specification management\n"); + print("\nUsage:\n"); + print(" tri spec create [--kind ]\n"); + print(" tri spec validate \n"); + print(" tri spec list\n"); + print("\nKinds: feature, bugfix, hotfix, recovery"); + } else if (std.mem.eql(u8, command, "gen")) { + print("tri gen - Code generation from specs\n"); + print("\nUsage:\n"); + print(" tri gen [--backend ]\n"); + print(" tri gen --all [--backend ]\n"); + print("\nBackends: zig (default), c, verilog"); + } else if (std.mem.eql(u8, command, "git")) { + print("tri git - Git integration with skill workflow\n"); + print("\nUsage:\n"); + print(" tri git commit [--all] [-m ] [--mode ]\n"); + print(" tri git push [ ] [--mode ]\n"); + print(" tri git status\n"); + print("\nModes: normal, strict, local"); + } else if (std.mem.eql(u8, command, "lint")) { + print("tri lint - Specification linting\n"); + print("\nUsage:\n"); + print(" tri lint [--strict]\n"); + print(" tri lint --all [--strict]"); + } else if (std.mem.eql(u8, command, "skill")) { + print("tri skill - Skill workflow management\n"); + print("\nUsage:\n"); + print(" tri skill begin --issue [--kind ]\n"); + print(" tri skill seal\n"); + print(" tri skill status"); + } else { + error_print("Unknown command: "); + error_print(command); + return 1; + } + + return 0; + } + + // ===================================================================== + // Helper functions + // ===================================================================== + + fn generate_spec_template(name: []const u8, kind: []const u8) []const u8 { + return "// " ++ name ++ ".t27 -- " ++ kind ++ " specification\n" ++ + "// Generated by: tri spec create\n" ++ + "// phi^2 + 1/phi^2 = 3 | TRINITY\n" ++ + "\n" ++ + "// =====================================================================\n" ++ + "// TDD-Inside-Spec: This spec MUST contain at least one test or invariant\n" ++ + "// =====================================================================\n" ++ + "\n" ++ + "test my_test\n" ++ + " // Verify: functionality works correctly\n" ++ + " try std.testing.expect(true);\n" ++ + "\n" ++ + "invariant my_invariant\n" ++ + " // For all valid inputs: output is in valid range\n" ++ + " assert true;\n"; + } + + fn generate_zig(spec: Spec) i32 { + // Generate Zig code with proper header + const header = "// This file is generated from " ++ spec.path ++ "\n" ++ + "// DO NOT EDIT - Changes will be overwritten on next tri gen\n" ++ + "// Generated at: " ++ get_current_timestamp() ++ "\n" ++ + "// Source spec: " ++ spec.path ++ "\n\n"; + + const code = generate_zig_impl(spec); + var output_path = spec.path; + output_path = replace(output_path, ".t27", ".zig"); + output_path = replace(output_path, "specs/", "src/"); + + // Ensure output directory exists + const dir = dir_path(output_path); + create_dir_if_not_exists(dir); + + write_file(output_path, header ++ code); + return 0; + } + + fn generate_c(spec: Spec) i32 { + // Similar to generate_zig but for C + _ = spec; + return 0; + } + + fn generate_verilog(spec: Spec) i32 { + // Similar to generate_zig but for Verilog + _ = spec; + return 0; + } + + fn generate_conformance(spec: Spec) i32 { + // Generate conformance JSON from test blocks + const conformance = Conformance{ + .version = "1.0", + .spec = spec.path, + .tests = extract_tests(spec), + }; + + const output_path = "conformance/" ++ replace(replace(spec.path, ".t27", ""), "specs/", "") ++ ".json"; + write_file(output_path, serialize_json(conformance)); + return 0; + } + + fn load_or_create_registry(path: []const u8) SkillRegistry { + if (file_exists(path)) { + return load_registry(path); + } else { + return SkillRegistry{ + .version = "1.0", + .skills = &[_]Skill{}, + }; + } + } + + fn compute_seal_hash(skill: Skill) []const u8 { + // Compute hash of skill state for seal verification + const data = skill.id ++ skill.status ++ skill.issue ++ skill.updated_at; + return sha256(data); + } + + // ===================================================================== + // Type definitions + // ===================================================================== + + pub const Spec = struct { + path: []const u8, + content: []const u8, + test_blocks: []TestBlock, + invariant_blocks: []InvariantBlock, + }; + + pub const TestBlock = struct { + statements: []const u8, + }; + + pub const InvariantBlock = struct { + statements: []const u8, + }; + + pub const Skill = struct { + id: []const u8, + status: []const u8, + kind: []const u8, + issue: []const u8, + branch: []const u8, + created_at: []const u8, + updated_at: []const u8, + sealed_at: ?[]const u8, + commit: ?[]const u8, + commit_at: ?[]const u8, + pushed: bool, + pushed_at: ?[]const u8, + verdict: ?[]const u8, + verdict_at: ?[]const u8, + seal_hash: ?[]const u8, + artifacts: []const u8, + metadata: SkillMetadata, + }; + + pub const SkillMetadata = struct { + title: []const u8, + author: []const u8, + priority: []const u8, + tags: [][]const u8, + }; + + pub const SkillRegistry = struct { + version: []const u8, + skills: []Skill, + }; + + pub const Conformance = struct { + version: []const u8, + spec: []const u8, + tests: []TestBlock, + }; + + // ===================================================================== + // Module Map for compile-project + // ===================================================================== + + pub const ModuleMapEntry = struct { + key: []const u8, // e.g. "base::types" + path: []const u8, // e.g. "base/types" + }; + + pub const ModuleMap = struct { + entries: [256]ModuleMapEntry, + count_val: usize, + + pub fn init(self: *ModuleMap) void { + self.count_val = 0; + } + + pub fn put(self: *ModuleMap, key: []const u8, path: []const u8) void { + if (self.count_val < 256) { + self.entries[self.count_val] = ModuleMapEntry{ .key = key, .path = path }; + self.count_val += 1; + } + } + + pub fn get(self: ModuleMap, key: []const u8) ?[]const u8 { + for (self.entries[0..self.count_val]) |entry| { + if (std.mem.eql(u8, entry.key, key)) { + return entry.path; + } + } + return null; + } + + pub fn count(self: ModuleMap) usize { + return self.count_val; + } + }; + + fn path_to_module_key(rel_path: []const u8) []const u8 { + // Convert "base/types.t27" -> "base::types" + var result = replace(rel_path, ".t27", ""); + result = replace(result, "/", "::"); + return result; + } + + fn backend_extension(backend: []const u8) []const u8 { + if (std.mem.eql(u8, backend, "verilog")) return ".v"; + if (std.mem.eql(u8, backend, "c")) return ".c"; + return ".zig"; + } + + fn strip_prefix(file_path: []const u8) []const u8 { + // Strip "specs/" or "compiler/" prefix + if (std.mem.startsWith(u8, file_path, "specs/")) { + return file_path[6..]; + } + if (std.mem.startsWith(u8, file_path, "compiler/")) { + return file_path[9..]; + } + return file_path; + } +} + +// ======================================================================================================= +// TDD-Inside-Spec: Tests and Invariants for Commands +// ======================================================================================================= + +test spec_create_returns_zero_on_success + const result = commands.spec_create("test_spec", "feature"); + try std.testing.expect(result == 0); + +test spec_create_rejects_empty_name + const result = commands.spec_create("", "feature"); + try std.testing.expect(result == 1); + +test spec_create_rejects_invalid_kind + const result = commands.spec_create("test_spec", "invalid"); + try std.testing.expect(result == 1); + +test spec_validate_returns_zero_for_valid_spec + // Simulate valid spec with tests and no Cyrillic + const result = commands.spec_validate("specs/valid.t27"); + // In real test, would mock parse_spec to return valid spec + _ = result; + try std.testing.expect(true); + +test spec_validate_returns_one_for_spec_without_tests + // Simulate spec without tests + const result = commands.spec_validate("specs/no_tests.t27"); + _ = result; + try std.testing.expect(true); + +test spec_validate_rejects_cyrillic_in_source + // Simulate spec with Cyrillic + const result = commands.spec_validate("specs/with_cyrillic.t27"); + _ = result; + try std.testing.expect(true); + +test gen_spec_returns_zero_on_success + const result = commands.gen_spec("specs/valid.t27", "zig", false); + _ = result; + try std.testing.expect(true); + +test gen_spec_returns_one_for_invalid_spec + const result = commands.gen_spec("specs/invalid.t27", "zig", false); + _ = result; + try std.testing.expect(true); + +test gen_all_returns_zero_if_all_succeed + const result = commands.gen_all("zig"); + _ = result; + try std.testing.expect(true); + +test gen_all_returns_one_if_any_fails + const result = commands.gen_all("zig"); + _ = result; + try std.testing.expect(true); + +test lint_file_returns_zero_for_valid_spec + const result = commands.lint_file("specs/valid.t27", false); + _ = result; + try std.testing.expect(true); + +test lint_file_returns_one_for_invalid_spec + const result = commands.lint_file("specs/invalid.t27", false); + _ = result; + try std.testing.expect(true); + +test lint_strict_requires_tests + const result = commands.lint_file("specs/no_tests.t27", true); + _ = result; + try std.testing.expect(true); + +test skill_begin_returns_zero_on_success + const result = commands.skill_begin("123", "feature"); + _ = result; + try std.testing.expect(true); + +test skill_begin_rejects_empty_issue_id + const result = commands.skill_begin("", "feature"); + _ = result; + try std.testing.expect(true); + +test skill_seal_returns_zero_on_success + const result = commands.skill_seal(); + _ = result; + try std.testing.expect(true); + +test skill_seal_returns_one_without_active_skill + const result = commands.skill_seal(); + _ = result; + try std.testing.expect(true); + +test skill_status_returns_zero_always + const result = commands.skill_status(); + try std.testing.expect(result == 0); + +test help_returns_zero_always + const result = commands.help(""); + try std.testing.expect(result == 0); + +test generate_spec_template_contains_test_block + const template = commands.generate_spec_template("test", "feature"); + try std.testing.expect(std.mem.indexOf(u8, template, "test") != null); + +test spec_create_command_enum_value + const spec_command = commands.Command.SpecCommand; + try std.testing.expect(@intFromEnum(spec_command) == 0); + +test gen_command_enum_value + const gen_command = commands.Command.GenCommand; + try std.testing.expect(@intFromEnum(gen_command) == 1); + +test compile_project_command_enum_value + const compile_project_command = commands.Command.CompileProjectCommand; + try std.testing.expect(@intFromEnum(compile_project_command) == 2); + +test compile_project_returns_one_for_invalid_backend + const result = commands.compile_project("build", "invalid"); + try std.testing.expect(result == 1); + +test compile_project_returns_zero_on_success + const result = commands.compile_project("/tmp/t27-test", "zig"); + _ = result; + try std.testing.expect(true); + +test module_map_put_and_get + var map: commands.ModuleMap = undefined; + map.init(); + map.put("base::types", "base/types"); + const result = map.get("base::types"); + try std.testing.expect(result != null); + +test module_map_get_returns_null_for_missing + var map: commands.ModuleMap = undefined; + map.init(); + const result = map.get("nonexistent"); + try std.testing.expect(result == null); + +test path_to_module_key_converts_correctly + const result = commands.path_to_module_key("base/types.t27"); + try std.testing.expectEqualStrings("base::types", result); + +test strip_prefix_strips_specs + const result = commands.strip_prefix("specs/base/types.t27"); + try std.testing.expectEqualStrings("base/types.t27", result); + +test strip_prefix_strips_compiler + const result = commands.strip_prefix("compiler/cli/gen.t27"); + try std.testing.expectEqualStrings("cli/gen.t27", result); + +test backend_extension_returns_zig_by_default + const result = commands.backend_extension("zig"); + try std.testing.expectEqualStrings(".zig", result); + +test backend_extension_returns_v_for_verilog + const result = commands.backend_extension("verilog"); + try std.testing.expectEqualStrings(".v", result); + +invariant spec_create_always_creates_file + // spec_create creates a .t27 file + assert true; + +invariant spec_validate_checks_tdd_contract + // spec_validate enforces at least one test or invariant + assert true; + +invariant spec_validate_checks_language_policy + // spec_validate rejects Cyrillic in source files + assert true; + +invariant gen_spec_validates_first + // gen_spec runs spec_validate before generating + assert true; + +invariant generate_zig_includes_header + // Generated Zig files have DO NOT EDIT header + assert true; + +invariant skill_begin_creates_registry_if_missing + // skill_begin creates .trinity/skills/registry.json if missing + assert true; + +invariant skill_seal_computes_seal_hash + // skill_seal sets seal_hash field + assert true; + +invariant command_enum_has_seven_values + // Command enum has 7 values (added CompileProjectCommand) + assert @typeInfo(commands.Command).Enum.fields.len == 7; + +invariant compile_project_validates_backend + // compile_project rejects invalid backends + assert true; + +invariant compile_project_generates_build_zig_for_zig_backend + // compile_project generates build.zig when backend is zig + assert true; + +invariant compile_project_resolves_imports + // compile_project resolves use X::Y to correct relative paths + assert true; + +bench spec_create_latency + target: < 10ms + _ = commands.spec_create("test", "feature"); + +bench spec_validate_latency + target: < 5ms + _ = commands.spec_validate("specs/valid.t27"); + +bench gen_spec_latency + target: < 100ms + _ = commands.gen_spec("specs/valid.t27", "zig", false); + +bench skill_begin_latency + target: < 10ms + _ = commands.skill_begin("123", "feature"); diff --git a/apps/website/public/t27/files/compiler/runtime/runtime.t27 b/apps/website/public/t27/files/compiler/runtime/runtime.t27 new file mode 100644 index 0000000000..53dc9596d5 --- /dev/null +++ b/apps/website/public/t27/files/compiler/runtime/runtime.t27 @@ -0,0 +1,339 @@ +// compiler/runtime/runtime.t27 -- T27 Runtime Specification +// Runtime environment for executing t27 programs +// phi^2 + 1/phi^2 = 3 | TRINITY + +module triruntime { + using base_types: @import("../../specs/base/types.t27"); + using registers: @import("../../specs/isa/registers.t27"); + + // Configuration + pub const STACK_SIZE: usize = 4096; + pub const HEAP_SIZE: usize = 65536; + pub const MAX_THREADS: usize = 8; + pub const MAX_CHANNELS: usize = 16; + + // Thread states + pub const ThreadState = enum(u8) { + idle = 0, + running = 1, + blocked = 2, + }; + + // Runtime state + pub const Runtime = struct { + // Stack + stack_base: usize = 0, + stack_ptr: usize = 0, + + // Heap + heap_base: usize = 0, + heap_ptr: usize = 0, + heap_end: usize = 0, + + // Thread management + thread_states: [MAX_THREADS]ThreadState = [_]ThreadState{.idle} ** MAX_THREADS, + thread_sp: [MAX_THREADS]usize = [_]usize{0} ** MAX_THREADS, + + // Channels + channel_buffers: [MAX_CHANNELS]usize = [_]usize{0} ** MAX_CHANNELS, + channel_sizes: [MAX_CHANNELS]usize = [_]usize{0} ** MAX_CHANNELS, + channel_readers: [MAX_CHANNELS]u8 = [_]u8{0} ** MAX_CHANNELS, + channel_writers: [MAX_CHANNELS]u8 = [_]u8{0} ** MAX_CHANNELS, + + // Exception handling + exception_handler: usize = 0, + exception_code: u8 = 0, + + // Performance monitoring + cycle_counter: usize = 0, + instruction_counter: usize = 0, + }; + + // Initialize runtime environment + pub fn runtime_init(rt: *Runtime) !void { + try alloc_stack(rt); + try alloc_heap(rt); + init_threads(rt); + init_channels(rt); + rt.cycle_counter = 0; + rt.instruction_counter = 0; + setup_exception_handler(rt); + } + + // Execute t27 program starting from entry point + pub fn runtime_execute(rt: *Runtime, entry: usize) i32 { + setup_stack_frame(rt); + // Call entry point (simplified for spec) + _ = entry; + return 0; // Exit code + } + + // Clean up and shutdown runtime + pub fn runtime_shutdown(rt: *Runtime) void { + print_stats(rt); + // Free heap if managed + _ = rt; + } + + // Allocate stack memory + fn alloc_stack(rt: *Runtime) !void { + // Allocate STACK_SIZE bytes + // For spec: assume allocation succeeds + rt.stack_base = 0xDEADBEEF; // Placeholder + rt.stack_ptr = rt.stack_base + STACK_SIZE; + } + + // Allocate heap memory + fn alloc_heap(rt: *Runtime) !void { + // Allocate HEAP_SIZE bytes + rt.heap_base = 0xBADDCAFE; // Placeholder + rt.heap_ptr = rt.heap_base; + rt.heap_end = rt.heap_base + HEAP_SIZE; + } + + // Initialize thread management + fn init_threads(rt: *Runtime) void { + // Set all threads to idle + for (&rt.thread_states) |*state| { + state.* = .idle; + } + } + + // Initialize communication channels + fn init_channels(rt: *Runtime) void { + // Set all channels to empty + for (0..MAX_CHANNELS) |i| { + rt.channel_sizes[i] = 0; + rt.channel_readers[i] = 0; + rt.channel_writers[i] = 0; + } + } + + // Set up exception handler + fn setup_exception_handler(rt: *Runtime) void { + // Set default exception handler + rt.exception_handler = 0x1000; + } + + // Set up initial stack frame + fn setup_stack_frame(rt: *Runtime) void { + // Push return address (dummy for entry point) + rt.stack_ptr -= 4; + // Push frame pointer + rt.stack_ptr -= 4; + } + + // Allocate memory from heap (bump allocator) + pub fn runtime_alloc(rt: *Runtime, size: usize) usize { + if (rt.heap_ptr + size > rt.heap_end) { + return 0; // Out of memory + } + const ptr = rt.heap_ptr; + rt.heap_ptr += size; + return ptr; + } + + // Free memory (no-op for bump allocator) + pub fn runtime_free(rt: *Runtime, ptr: usize) void { + _ = rt; + _ = ptr; + // No-op for bump allocator + } + + // Create a new channel + pub fn channel_create(rt: *Runtime, size: usize) usize { + // Find free channel + for (0..MAX_CHANNELS) |i| { + if (rt.channel_sizes[i] == 0) { + const buffer = runtime_alloc(rt, size); + if (buffer == 0) { + return 0xFFFFFFFF; // -1 on failure + } + rt.channel_buffers[i] = buffer; + rt.channel_sizes[i] = size; + return @intCast(i); + } + } + return 0xFFFFFFFF; // -1 on failure + } + + // Send data to channel + pub fn channel_send(rt: *Runtime, channel_id: usize, data: usize) bool { + if (channel_id >= MAX_CHANNELS or rt.channel_sizes[channel_id] == 0) { + return false; + } + // Store data (simplified for spec) + rt.channel_sizes[channel_id] -= 1; + _ = data; + return true; + } + + // Receive data from channel + pub fn channel_recv(rt: *Runtime, channel_id: usize) usize { + if (channel_id >= MAX_CHANNELS or rt.channel_sizes[channel_id] == 0) { + return 0; + } + // Load data (simplified for spec) + rt.channel_sizes[channel_id] += 1; + return 0; + } + + // Spawn a new thread + pub fn thread_spawn(rt: *Runtime, entry: usize, arg: usize) usize { + // Find free thread slot + for (0..MAX_THREADS) |i| { + if (rt.thread_states[i] == .idle) { + rt.thread_states[i] = .running; + rt.thread_sp[i] = rt.stack_base + STACK_SIZE; + _ = entry; + _ = arg; + return @intCast(i); + } + } + return 0xFFFFFFFF; // -1 on failure + } + + // Yield execution to another thread + pub fn thread_yield(rt: *Runtime) void { + // Simple round-robin scheduling + _ = rt; + } + + // Print performance statistics + fn print_stats(rt: *Runtime) void { + // Print cycle counter + // Print instruction counter + // Print memory usage + _ = rt; + } +} + +// ======================================================================================================= +// TDD-Inside-Spec: Tests and Invariants for Runtime +// ======================================================================================================= + +test test_bootstrap_init_sequence + // Verify: runtime_init initializes all components in correct order + // Setup: call runtime_init on fresh runtime + // Expected: stack allocated, heap allocated, threads idle, channels empty + var rt = triruntime.Runtime{}; + _ = triruntime.runtime_init(&rt); + try std.testing.expect(rt.stack_base != 0); + try std.testing.expect(rt.heap_base != 0); + +test test_arena_no_leak + // Verify: all allocations are freed (or tracked) in bump allocator + // Setup: allocate various sizes, check heap state + // Expected: no untracked allocations, heap_ptr <= heap_end + var rt = triruntime.Runtime{}; + _ = triruntime.runtime_init(&rt); + const ptr1 = triruntime.runtime_alloc(&rt, 100); + const ptr2 = triruntime.runtime_alloc(&rt, 200); + _ = ptr1; + _ = ptr2; + try std.testing.expect(rt.heap_ptr <= rt.heap_end); + +test test_channel_send_recv_roundtrip + // Verify: data sent through channel is received correctly + // Setup: create channel, send value, recv value + // Expected: received value == sent value + var rt = triruntime.Runtime{}; + _ = triruntime.runtime_init(&rt); + const channel_id = triruntime.channel_create(&rt, 16); + try std.testing.expect(channel_id != 0xFFFFFFFF); + const sent = triruntime.channel_send(&rt, channel_id, 42); + try std.testing.expect(sent); + _ = triruntime.channel_recv(&rt, channel_id); + +test test_thread_yield_fairness + // Verify: thread_yield cycles through all running threads + // Setup: spawn 3 threads, each yields, verify round-robin + // Expected: each thread gets CPU time + var rt = triruntime.Runtime{}; + _ = triruntime.runtime_init(&rt); + const t1 = triruntime.thread_spawn(&rt, 0x1000, 0); + const t2 = triruntime.thread_spawn(&rt, 0x2000, 0); + const t3 = triruntime.thread_spawn(&rt, 0x3000, 0); + try std.testing.expect(t1 != 0xFFFFFFFF); + try std.testing.expect(t2 != 0xFFFFFFFF); + try std.testing.expect(t3 != 0xFFFFFFFF); + triruntime.thread_yield(&rt); + +test test_exception_handler_invocation + // Verify: exception handler is called on trap + // Setup: trigger TRAP, check exception_code + // Expected: exception_handler called, exception_code set + var rt = triruntime.Runtime{}; + _ = triruntime.runtime_init(&rt); + try std.testing.expect(rt.exception_handler != 0); + +test test_stack_growth_direction + // Verify: stack grows downward (SP decreases) + // Setup: push values, track SP before/after + // Expected: SP after < SP before + var rt = triruntime.Runtime{}; + _ = triruntime.runtime_init(&rt); + const sp_before = rt.stack_ptr; + _ = triruntime.setup_stack_frame(&rt); + try std.testing.expect(rt.stack_ptr < sp_before); + +test test_heap_bump_allocator_properties + // Verify: bump allocator is O(1) and contiguous + // Setup: allocate multiple times, check addresses + // Expected: addresses are contiguous and increasing + var rt = triruntime.Runtime{}; + _ = triruntime.runtime_init(&rt); + const p1 = triruntime.runtime_alloc(&rt, 16); + const p2 = triruntime.runtime_alloc(&rt, 16); + try std.testing.expect(p2 > p1); + +invariant arena_no_leak + // heap_ptr never exceeds heap_end + // Rationale: Bump allocator must stay within bounds + const rt = triruntime.Runtime{}; + assert rt.heap_ptr <= rt.heap_end; + +invariant stack_bounds_valid + // stack_ptr stays within [stack_base, stack_base + STACK_SIZE) + // Rationale: Stack must not overflow or underflow + const rt = triruntime.Runtime{}; + assert rt.stack_ptr >= rt.stack_base; + assert rt.stack_ptr <= rt.stack_base + triruntime.STACK_SIZE; + +invariant thread_count_limit + // Active threads never exceed MAX_THREADS + // Rationale: Thread pool has fixed size + const MAX_THREADS = triruntime.MAX_THREADS; + assert true; // Runtime enforces this via thread_states array size + +invariant channel_capacity_limit + // Channel size never exceeds initial allocation + // Rationale: Channels have bounded capacity + assert true; // Channels have fixed size buffers + +invariant exception_handler_set + // exception_handler is never NULL after init + // Rationale: System must have default error handler + var rt = triruntime.Runtime{}; + _ = triruntime.runtime_init(&rt); + assert rt.exception_handler != 0; + +bench bench_alloc_latency_cycles + target: < 10 cycles + var rt = triruntime.Runtime{}; + _ = triruntime.runtime_init(&rt); + _ = triruntime.runtime_alloc(&rt, 16); + +bench test_channel_throughput_ops_per_sec + target: > 1M ops/sec + var rt = triruntime.Runtime{}; + _ = triruntime.runtime_init(&rt); + const channel_id = triruntime.channel_create(&rt, 16); + _ = triruntime.channel_send(&rt, channel_id, 0); + _ = triruntime.channel_recv(&rt, channel_id); + +bench test_thread_context_switch_cycles + target: < 100 cycles + var rt = triruntime.Runtime{}; + _ = triruntime.runtime_init(&rt); + triruntime.thread_yield(&rt); diff --git a/apps/website/public/t27/files/compiler/runtime/validation.t27 b/apps/website/public/t27/files/compiler/runtime/validation.t27 new file mode 100644 index 0000000000..082be11211 --- /dev/null +++ b/apps/website/public/t27/files/compiler/runtime/validation.t27 @@ -0,0 +1,514 @@ +// validation.t27 -- Validation Rules and Invariants +// TDD and language policy validation for t27 specs +// phi^2 + 1/phi^2 = 3 | TRINITY + +module validation_rules { + // ===================================================================== + // Validation Result Structure + // ===================================================================== + + pub const ValidationResult = struct { + valid: bool, + error: []const u8, + hint: []const u8, + }; + + // ===================================================================== + // Validation Rules + // ===================================================================== + + // Rule: TDD-Inside-Spec - Specs must have tests or invariants + pub fn validate_tdd_contract(spec: Spec) ValidationResult { + const has_test = spec.test_blocks.len > 0; + const has_invariant = spec.invariant_blocks.len > 0; + + if (!has_test and !has_invariant) { + return ValidationResult{ + .valid = false, + .error = "TDD contract violated: spec must contain at least one 'test' or 'invariant' block", + .hint = "See: docs/TDD-CONTRACT.md", + }; + } + + if (has_test) { + // Check test blocks are not empty + for (spec.test_blocks) |test_block| { + if (test_block.statements.len == 0) { + return ValidationResult{ + .valid = false, + .error = "Empty test block found", + .hint = "Test blocks must contain test cases", + }; + } + } + } + + if (has_invariant) { + // Check invariants have assertions + for (spec.invariant_blocks) |inv_block| { + if (inv_block.statements.len == 0) { + return ValidationResult{ + .valid = false, + .error = "Empty invariant block found", + .hint = "Invariant blocks must contain assertions", + }; + } + } + } + + return ValidationResult{ .valid = true, .error = "", .hint = "" }; + } + + // Rule: Language Policy - Source files must be ASCII-only + pub fn validate_language_policy(spec: Spec) ValidationResult { + // Check if path is docs/ (Cyrillic allowed in docs) + if (is_docs_path(spec.path)) { + return ValidationResult{ .valid = true, .error = "", .hint = "" }; + } + + // Check for Cyrillic characters (U+0400-U+04FF) + if (contains_cyrillic(spec.content)) { + return ValidationResult{ + .valid = false, + .error = "Language policy violated: source file contains Cyrillic characters", + .hint = "Source files must be ASCII-only. See: ADR-004-language-policy.md", + }; + } + + return ValidationResult{ .valid = true, .error = "", .hint = "" }; + } + + // Rule: De-Zig Strict - Zig files must have generated header + pub fn validate_generated_header(zig_file_path: []const u8, zig_content: []const u8) ValidationResult { + var lines = std.mem.splitScalar(u8, zig_content, '\n'); + + if (lines.len < 4) { + return ValidationResult{ + .valid = false, + .error = "Zig file is too short (missing generated header)", + .hint = "Zig files must have generated header. See: docs/GENERATED-HEADER-POLICY.md", + }; + } + + // Check header pattern + if (!std.mem.indexOf(u8, lines[0], "This file is generated from") != null) { + return ValidationResult{ + .valid = false, + .error = "Zig file lacks generated header", + .hint = "Write .t27 spec first, then run 'tri gen'", + }; + } + + if (!std.mem.indexOf(u8, lines[1], "DO NOT EDIT") != null) { + return ValidationResult{ + .valid = false, + .error = "Zig file header missing 'DO NOT EDIT' warning", + .hint = "Generated files must not be edited", + }; + } + + return ValidationResult{ .valid = true, .error = "", .hint = "" }; + } + + // Rule: Spec structure validation + pub fn validate_spec_structure(spec: Spec) ValidationResult { + // Check spec has name/title + if (spec.name.len == 0) { + return ValidationResult{ + .valid = false, + .error = "Spec has no name/title", + .hint = "Add spec name as comment at top of file", + }; + } + + // Check spec has at least one .code or .data or .const section + const has_code = spec.code_blocks.len > 0; + const has_data = spec.data_blocks.len > 0; + const has_const = spec.const_blocks.len > 0; + + // This is OK for pure test specs, but warn + _ = has_code; + _ = has_data; + _ = has_const; + + return ValidationResult{ .valid = true, .error = "", .hint = "" }; + } + + // Rule: Git push validation - sealed skill required + pub fn validate_push_skill(skill: Skill) ValidationResult { + if (!std.mem.eql(u8, skill.status, "sealed")) { + return ValidationResult{ + .valid = false, + .error = "Cannot push: skill not sealed", + .hint = "Run 'tri skill seal' first", + }; + } + + // Check verdict + if (std.mem.eql(u8, skill.verdict, "TOXIC")) { + return ValidationResult{ + .valid = false, + .error = "Cannot push: skill has toxic verdict", + .hint = "Fix issues or supersede with new skill", + }; + } + + return ValidationResult{ .valid = true, .error = "", .hint = "" }; + } + + // Rule: Git commit validation - active or sealed skill required + pub fn validate_commit_skill(skill: Skill) ValidationResult { + if (!std.mem.eql(u8, skill.status, "active") and !std.mem.eql(u8, skill.status, "sealed")) { + return ValidationResult{ + .valid = false, + .error = "Cannot commit: no active or sealed skill", + .hint = "Run 'tri skill begin --issue N' first", + }; + } + + // Check issue binding + if (skill.issue.len == 0) { + return ValidationResult{ + .valid = false, + .error = "Cannot commit: skill has no bound issue", + .hint = "Run 'tri skill begin --issue N' to bind issue", + }; + } + + return ValidationResult{ .valid = true, .error = "", .hint = "" }; + } + + // Rule: Policy Matrix - artifact requirements by skill kind + pub fn validate_policy_matrix(skill: Skill) ValidationResult { + const kind = skill.kind; + const artifacts = skill.artifacts; + + if (std.mem.eql(u8, kind, "recovery")) { + const checkpoints = count_checkpoints(artifacts); + const has_spec = contains_substring(artifacts, "spec"); + const has_docs = contains_substring(artifacts, "docs"); + + if (checkpoints < 3 or !has_spec or !has_docs) { + return ValidationResult{ + .valid = false, + .error = "Policy gate failed for recovery skill", + .hint = "Recovery skills require: >=3 checkpoints, spec, docs", + }; + } + } else if (std.mem.eql(u8, kind, "hotfix")) { + const checkpoints = count_checkpoints(artifacts); + + if (checkpoints < 1) { + return ValidationResult{ + .valid = false, + .error = "Policy gate failed for hotfix skill", + .hint = "Hotfix skills require: >=1 checkpoint", + }; + } + } else if (std.mem.eql(u8, kind, "feature") or std.mem.eql(u8, kind, "bugfix")) { + // Feature and bugfix require spec with tests + const has_spec = contains_substring(artifacts, "spec"); + + if (!has_spec) { + return ValidationResult{ + .valid = false, + .error = "Policy gate failed: spec required", + .hint = kind ++ " skills require spec changes", + }; + } + } + + return ValidationResult{ .valid = true, .error = "", .hint = "" }; + } + + // Rule: Strict mode - remote URL validation + pub fn validate_remote_url(remote_url: []const u8) ValidationResult { + if (std.mem.indexOf(u8, remote_url, "github.com/gHashTag/t27") == null) { + return ValidationResult{ + .valid = false, + .error = "Forbidden remote URL", + .hint = "Strict mode requires github.com/gHashTag/t27", + }; + } + + return ValidationResult{ .valid = true, .error = "", .hint = "" }; + } + + // ===================================================================== + // Helper functions + // ===================================================================== + + // Check for Cyrillic characters (U+0400-U+04FF) + pub fn contains_cyrillic(text: []const u8) bool { + var i: usize = 0; + while (i < text.len) : (i += 1) { + const c = text[i]; + // UTF-8 check for Cyrillic range (simplified) + if (c == 0xD0) { + if (i + 1 < text.len) { + const next_c = text[i + 1]; + // Cyrillic block: U+0400-U+04FF + if (next_c >= 0x80 and next_c <= 0xBF) { + return true; + } + } + } + } + return false; + } + + // Check if path is in docs/ directory + pub fn is_docs_path(path: []const u8) bool { + return std.mem.startsWith(u8, path, "docs/") or + std.mem.eql(u8, path, "README.md"); + } + + // Count checkpoint occurrences in artifacts string + pub fn count_checkpoints(artifacts: []const u8) i32 { + var count: i32 = 0; + var i: usize = 0; + const checkpoint_str = "checkpoint"; + + while (i < artifacts.len) : (i += 1) { + if (std.mem.startsWith(u8, artifacts[i..], checkpoint_str)) { + count += 1; + } + } + return count; + } + + // Check if artifacts contains substring + pub fn contains_substring(artifacts: []const u8, substr: []const u8) bool { + return std.mem.indexOf(u8, artifacts, substr) != null; + } +} + +// =========================================================================================== +// TDD-Inside-Spec: Tests and Invariants for Validation Rules +// =========================================================================================== + +test validate_tdd_contract_passes_with_tests + const spec_with_tests = Spec{ .test_blocks = &[_]TestBlock{{.statements = &[_]u8{}}} }; + const result = validation_rules.validate_tdd_contract(spec_with_tests); + try std.testing.expect(result.valid == true); + +test validate_tdd_contract_passes_with_invariants + const spec_with_invariants = Spec{ .invariant_blocks = &[_]InvariantBlock{{.statements = &[_]u8{}}} }; + const result = validation_rules.validate_tdd_contract(spec_with_invariants); + try std.testing.expect(result.valid == true); + +test validate_tdd_contract_fails_without_tests_or_invariants + const spec_empty = Spec{ .test_blocks = &[_]TestBlock{}, .invariant_blocks = &[_]InvariantBlock{} }; + const result = validation_rules.validate_tdd_contract(spec_empty); + try std.testing.expect(result.valid == false); + +test validate_tdd_contract_fails_with_empty_test_block + const spec_empty_test = Spec{ .test_blocks = &[_]TestBlock{{.statements = &[_]u8{}}} }; + const result = validation_rules.validate_tdd_contract(spec_empty_test); + try std.testing.expect(result.valid == false); + +test validate_language_policy_passes_ascii_only + const spec_ascii = Spec{ .path = "specs/test.t27", .content = "test content" }; + const result = validation_rules.validate_language_policy(spec_ascii); + try std.testing.expect(result.valid == true); + +test validate_language_policy_passes_in_docs + const spec_docs = Spec{ .path = "docs/test.md", .content = "test" }; + const result = validation_rules.validate_language_policy(spec_docs); + try std.testing.expect(result.valid == true); + +test validate_language_policy_fails_cyrillic_in_source + const spec_cyrillic = Spec{ .path = "specs/test.t27", .content = "test" }; + _ = spec_cyrillic; + // Note: actual Cyrillic check would use contains_cyrillic + // For now, skip this test since we can't easily embed Cyrillic in test + try std.testing.expect(true); + +test validate_generated_header_passes_with_header + const header = + \\// This file is generated from specs/test.t27 + \\// DO NOT EDIT - Changes will be overwritten + \\// Generated at: 2026-04-04T00:00:00Z + \\// Source spec: specs/test.t27 + \\pub fn test() {} + ; + const result = validation_rules.validate_generated_header("test.zig", header); + try std.testing.expect(result.valid == true); + +test validate_generated_header_fails_without_header + const no_header = "pub fn test() {}"; + const result = validation_rules.validate_generated_header("test.zig", no_header); + try std.testing.expect(result.valid == false); + +test validate_generated_header_fails_missing_do_not_edit + const no_warning = + \\// This file is generated from specs/test.t27 + \\// Generated at: 2026-04-04T00:00:00Z + \\pub fn test() {} + ; + const result = validation_rules.validate_generated_header("test.zig", no_warning); + try std.testing.expect(result.valid == false); + +test validate_push_skill_passes_sealed_non_toxic + const skill = Skill{ .status = "sealed", .verdict = "NOT TOXIC" }; + const result = validation_rules.validate_push_skill(skill); + try std.testing.expect(result.valid == true); + +test validate_push_skill_fails_not_sealed + const skill = Skill{ .status = "active", .verdict = "NOT TOXIC" }; + const result = validation_rules.validate_push_skill(skill); + try std.testing.expect(result.valid == false); + +test validate_push_skill_fails_toxic + const skill = Skill{ .status = "sealed", .verdict = "TOXIC" }; + const result = validation_rules.validate_push_skill(skill); + try std.testing.expect(result.valid == false); + +test validate_commit_skill_passes_active_with_issue + const skill = Skill{ .status = "active", .issue = "123" }; + const result = validation_rules.validate_commit_skill(skill); + try std.testing.expect(result.valid == true); + +test validate_commit_skill_passes_sealed_with_issue + const skill = Skill{ .status = "sealed", .issue = "123" }; + const result = validation_rules.validate_commit_skill(skill); + try std.testing.expect(result.valid == true); + +test validate_commit_skill_fails_without_issue + const skill = Skill{ .status = "active", .issue = "" }; + const result = validation_rules.validate_commit_skill(skill); + try std.testing.expect(result.valid == false); + +test validate_policy_matrix_passes_recovery_with_all + const skill = Skill{ .kind = "recovery", .artifacts = "checkpoint1,checkpoint2,checkpoint3,spec,docs" }; + const result = validation_rules.validate_policy_matrix(skill); + try std.testing.expect(result.valid == true); + +test validate_policy_matrix_fails_recovery_missing_checkpoints + const skill = Skill{ .kind = "recovery", .artifacts = "checkpoint1,checkpoint2,spec,docs" }; + const result = validation_rules.validate_policy_matrix(skill); + try std.testing.expect(result.valid == false); + +test validate_policy_matrix_fails_hotfix_without_checkpoint + const skill = Skill{ .kind = "hotfix", .artifacts = "spec,docs" }; + const result = validation_rules.validate_policy_matrix(skill); + try std.testing.expect(result.valid == false); + +test validate_policy_matrix_passes_hotfix_with_checkpoint + const skill = Skill{ .kind = "hotfix", .artifacts = "checkpoint1,spec" }; + const result = validation_rules.validate_policy_matrix(skill); + try std.testing.expect(result.valid == true); + +test validate_policy_matrix_fails_feature_without_spec + const skill = Skill{ .kind = "feature", .artifacts = "docs" }; + const result = validation_rules.validate_policy_matrix(skill); + try std.testing.expect(result.valid == false); + +test validate_remote_url_passes_correct_remote + const url = "https://github.com/gHashTag/t27.git"; + const result = validation_rules.validate_remote_url(url); + try std.testing.expect(result.valid == true); + +test validate_remote_url_fails_wrong_remote + const url = "https://gitlab.com/other/repo.git"; + const result = validation_rules.validate_remote_url(url); + try std.testing.expect(result.valid == false); + +test contains_cyrillic_returns_false_for_ascii + const text = "test content without cyrillic"; + const result = validation_rules.contains_cyrillic(text); + try std.testing.expect(result == false); + +test is_docs_path_returns_true_for_docs + const path = "docs/test.md"; + const result = validation_rules.is_docs_path(path); + try std.testing.expect(result == true); + +test is_docs_path_returns_false_for_specs + const path = "specs/test.t27"; + const result = validation_rules.is_docs_path(path); + try std.testing.expect(result == false); + +test count_checkpoints_returns_count + const artifacts = "checkpoint1,checkpoint2,checkpoint3,spec"; + const result = validation_rules.count_checkpoints(artifacts); + try std.testing.expect(result == 3); + +invariant validate_tdd_contract_always_returns_result + // validate_tdd_contract always returns a ValidationResult + assert true; + +invariant validate_language_policy_always_returns_result + // validate_language_policy always returns a ValidationResult + assert true; + +invariant validate_generated_header_always_returns_result + // validate_generated_header always returns a ValidationResult + assert true; + +invariant validate_push_skill_always_returns_result + // validate_push_skill always returns a ValidationResult + assert true; + +invariant validate_commit_skill_always_returns_result + // validate_commit_skill always returns a ValidationResult + assert true; + +invariant validate_policy_matrix_always_returns_result + // validate_policy_matrix always returns a ValidationResult + assert true; + +invariant is_docs_path_for_docs_prefix + // All paths starting with "docs/" are docs paths + const path = "docs/test.md"; + assert validation_rules.is_docs_path(path); + +invariant count_checkpoints_never_negative + // Checkpoint count is always >= 0 + assert validation_rules.count_checkpoints("") >= 0; + +bench validate_tdd_contract_latency + target: < 100us + const spec_with_tests = Spec{ .test_blocks = &[_]TestBlock{{.statements = &[_]u8{}}} }; + const start = std.time.nanoTimestamp(); + _ = validation_rules.validate_tdd_contract(spec_with_tests); + const end = std.time.nanoTimestamp(); + const elapsed = end - start; + try std.testing.expect(elapsed < 100000); + +bench validate_language_policy_latency + target: < 50us + const spec_ascii = Spec{ .path = "specs/test.t27", .content = "test" }; + const start = std.time.nanoTimestamp(); + _ = validation_rules.validate_language_policy(spec_ascii); + const end = std.time.nanoTimestamp(); + const elapsed = end - start; + try std.testing.expect(elapsed < 50000); + +bench validate_generated_header_latency + target: < 10us + const header = + \\// header\npub fn test() {} + ; + const start = std.time.nanoTimestamp(); + _ = validation_rules.validate_generated_header("test.zig", header); + const end = std.time.nanoTimestamp(); + const elapsed = end - start; + try std.testing.expect(elapsed < 10000); + +bench validate_policy_matrix_latency + target: < 10us + const skill = Skill{ .kind = "feature", .artifacts = "spec" }; + const start = std.time.nanoTimestamp(); + _ = validation_rules.validate_policy_matrix(skill); + const end = std.time.nanoTimestamp(); + const elapsed = end - start; + try std.testing.expect(elapsed < 10000); + +bench contains_cyrillic_latency + target: < 1us + const start = std.time.nanoTimestamp(); + _ = validation_rules.contains_cyrillic("test content without cyrillic"); + const end = std.time.nanoTimestamp(); + const elapsed = end - start; + try std.testing.expect(elapsed < 1000); diff --git a/apps/website/public/t27/files/compiler/skill/registry.t27 b/apps/website/public/t27/files/compiler/skill/registry.t27 new file mode 100644 index 0000000000..8fe00cd831 --- /dev/null +++ b/apps/website/public/t27/files/compiler/skill/registry.t27 @@ -0,0 +1,312 @@ +// registry.t27 -- Skill Registry JSON Structure (ADR-002) +// Defines the structure for tri skill workflow registry +// phi^2 + 1/phi^2 = 3 | TRINITY + +module skill_registry { + // ===================================================================== + // Skill Registry Structure + // ===================================================================== + + pub const RegistryPath: []const u8 = ".trinity/skills/registry.json"; + + // Skill status enum + pub const SkillStatus = enum(u8) { + Active = 0, // Skill is currently being worked on + Sealed = 1, // Skill has been sealed, ready for commit/push + Paused = 2, // Skill work is paused + Blocked = 3, // Skill is blocked, cannot proceed + Completed = 4, // Skill work is complete + }; + + // Skill kind enum + pub const SkillKind = enum(u8) { + Feature = 0, // New feature implementation + Bugfix = 1, // Bug fix + Hotfix = 2, // Urgent hotfix (limited scope) + Recovery = 3, // Recovery from previous failure + Refactor = 4, // Code refactoring + }; + + // Skill verdict enum + pub const SkillVerdict = enum(u8) { + NotToxic = 0, // Skill is safe to proceed + Toxic = 1, // Skill has toxic elements, must be fixed + }; + + // Skill metadata + pub const SkillMetadata = struct { + title: []const u8, + author: []const u8, + priority: []const u8, // P0, P1, P2, P3 + tags: [][]const u8, + }; + + // Skill structure + pub const Skill = struct { + id: []const u8, // Unique skill identifier + status: SkillStatus, // Current status + kind: SkillKind, // Type of work + issue: ?[]const u8, // Bound GitHub issue ID + branch: []const u8, // Git branch + created_at: []const u8, // Creation timestamp + updated_at: []const u8, // Last update timestamp + sealed_at: ?[]const u8, // Seal timestamp + commit: ?[]const u8, // Last commit hash + commit_at: ?[]const u8, // Commit timestamp + pushed: bool, // Whether pushed to remote + pushed_at: ?[]const u8, // Push timestamp + verdict: ?SkillVerdict, // Toxicity verdict + verdict_at: ?[]const u8, // Verdict timestamp + seal_hash: ?[]const u8, // Hash of sealed state + artifacts: []const u8, // Artifact list + metadata: SkillMetadata, // Additional metadata + }; + + // Skill Registry structure + pub const SkillRegistry = struct { + version: []const u8, + skills: []Skill, + }; + + // ===================================================================== + // Policy Matrix (for git push validation) + // ===================================================================== + + // Recovery skill requirements: + // - Minimum 3 checkpoints + // - Spec must be modified + // - Docs must be modified + // - Verdict must be NOT TOXIC + + // Hotfix skill requirements: + // - Minimum 1 checkpoint + // - Changes only in "fix only" areas (no breaking changes) + // - Verdict must be NOT TOXIC + + // Feature skill requirements: + // - At least 1 test (per TDD contract) + // - Spec has at least one test/invariant + // - Verdict must be NOT TOXIC + + // ===================================================================== + // Registry JSON Schema + // ===================================================================== + + /* + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["version", "skills"], + "properties": { + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+$" + }, + "skills": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "status", "kind", "branch", "created_at", "updated_at"], + "properties": { + "id": { + "type": "string", + "pattern": "^skill-\\d+$" + }, + "status": { + "type": "string", + "enum": ["active", "sealed", "paused", "blocked", "completed"] + }, + "kind": { + "type": "string", + "enum": ["feature", "bugfix", "hotfix", "recovery", "refactor"] + }, + "issue": { "type": "string" }, + "branch": { "type": "string" }, + "created_at": { "type": "string", "format": "date-time" }, + "updated_at": { "type": "string", "format": "date-time" }, + "sealed_at": { "type": "string", "format": "date-time" }, + "commit": { + "type": "string", + "pattern": "^[a-f0-9]{40}$" + }, + "commit_at": { "type": "string", "format": "date-time" }, + "pushed": { "type": "boolean" }, + "pushed_at": { "type": "string", "format": "date-time" }, + "verdict": { + "type": "string", + "enum": ["NOT TOXIC", "TOXIC"] + }, + "verdict_at": { "type": "string", "format": "date-time" }, + "seal_hash": { "type": "string" }, + "artifacts": { "type": "string" }, + "metadata": { + "type": "object", + "properties": { + "title": { "type": "string" }, + "author": { "type": "string" }, + "priority": { + "type": "string", + "enum": ["P0", "P1", "P2", "P3"] + }, + "tags": { + "type": "array", + "items": { "type": "string" } + } + } + } + } + } + } + } + } + */ + + // ===================================================================== + // Helper Functions + // ===================================================================== + + // Create empty skill registry + pub fn create_registry() SkillRegistry { + return SkillRegistry{ + .version = "1.0", + .skills = &[_]Skill{}, + }; + } + + // Create minimal skill with required fields + pub fn create_skill(id: []const u8, kind: SkillKind, branch: []const u8) Skill { + const now = "2026-04-04T00:00:00Z"; + return Skill{ + .id = id, + .status = SkillStatus.Active, + .kind = kind, + .issue = null, + .branch = branch, + .created_at = now, + .updated_at = now, + .sealed_at = null, + .commit = null, + .commit_at = null, + .pushed = false, + .pushed_at = null, + .verdict = null, + .verdict_at = null, + .seal_hash = null, + .artifacts = "", + .metadata = SkillMetadata{ + .title = "", + .author = "", + .priority = "P1", + .tags = &[_][]const u8{}, + }, + }; + } + + // Check if skill is ready for commit + pub fn can_commit(skill: Skill) bool { + return skill.status == SkillStatus.Sealed; + } + + // Check if skill is ready for push + pub fn can_push(skill: Skill) bool { + return skill.status == SkillStatus.Sealed and + skill.commit != null and + skill.verdict == SkillVerdict.NotToxic; + } +} + +// =========================================================================================== +// TDD-Inside-Spec: Tests and Invariants for Skill Registry +// =========================================================================================== + +test skill_status_active_value + const status = skill_registry.SkillStatus.Active; + try std.testing.expect(@intFromEnum(status) == 0); + +test skill_status_sealed_value + const status = skill_registry.SkillStatus.Sealed; + try std.testing.expect(@intFromEnum(status) == 1); + +test skill_kind_feature_value + const kind = skill_registry.SkillKind.Feature; + try std.testing.expect(@intFromEnum(kind) == 0); + +test skill_verdict_not_toxic_value + const verdict = skill_registry.SkillVerdict.NotToxic; + try std.testing.expect(@intFromEnum(verdict) == 0); + +test skill_registry_has_version_field + const registry = skill_registry.create_registry(); + try std.testing.expectEqualStrings("1.0", registry.version); + +test skill_registry_has_skills_array + const registry = skill_registry.create_registry(); + try std.testing.expectEqual(@as(usize, 0), registry.skills.len); + +test skill_has_required_id_field + const skill = skill_registry.create_skill("skill-001", skill_registry.SkillKind.Feature, "main"); + try std.testing.expectEqualStrings("skill-001", skill.id); + +test skill_metadata_priority_valid + const metadata = skill_registry.SkillMetadata{ + .title = "Test", + .author = "test", + .priority = "P1", + .tags = &[_][]const u8{}, + }; + try std.testing.expectEqualStrings("P1", metadata.priority); + +test skill_status_enum_count + // Active, Sealed, Paused, Blocked, Completed = 5 + try std.testing.expectEqual(@as(usize, 5), 5); + +test skill_kind_enum_count + // Feature, Bugfix, Hotfix, Recovery, Refactor = 5 + try std.testing.expectEqual(@as(usize, 5), 5); + +invariant skill_status_active_is_zero + assert @intFromEnum(skill_registry.SkillStatus.Active) == 0; + +invariant skill_status_sealed_is_one + assert @intFromEnum(skill_registry.SkillStatus.Sealed) == 1; + +invariant skill_kind_feature_is_zero + assert @intFromEnum(skill_registry.SkillKind.Feature) == 0; + +invariant skill_verdict_not_toxic_is_zero + assert @intFromEnum(skill_registry.SkillVerdict.NotToxic) == 0; + +invariant skill_verdict_toxic_is_one + assert @intFromEnum(skill_registry.SkillVerdict.Toxic) == 1; + +invariant skill_metadata_has_priority_field + // Valid priorities are P0, P1, P2, P3 + assert true; + +invariant skill_registry_version_is_string + // version field must be string type + assert true; + +invariant skill_id_format_matches_pattern + // Skill ID must match pattern "skill-\d+" + assert true; + +invariant skill_commit_hash_is_40_hex_chars + // Commit hash must be 40 hexadecimal characters + assert true; + +bench skill_registry_create_latency + target: < 100ns + const start = std.time.nanoTimestamp(); + _ = skill_registry.create_registry(); + const end = std.time.nanoTimestamp(); + const elapsed = end - start; + try std.testing.expect(elapsed < 100); + +bench skill_create_latency + target: < 200ns + const start = std.time.nanoTimestamp(); + _ = skill_registry.create_skill("skill-001", skill_registry.SkillKind.Feature, "main"); + const end = std.time.nanoTimestamp(); + const elapsed = end - start; + try std.testing.expect(elapsed < 200); diff --git a/apps/website/public/t27/files/contrib/backend/zig/legacy/main_zig_handwritten.t27 b/apps/website/public/t27/files/contrib/backend/zig/legacy/main_zig_handwritten.t27 new file mode 100644 index 0000000000..cb6f18e0f2 --- /dev/null +++ b/apps/website/public/t27/files/contrib/backend/zig/legacy/main_zig_handwritten.t27 @@ -0,0 +1,1125 @@ +// tri.zig -- Trinity T27 CLI Runtime +// phi^2 + 1/phi^2 = 3 | TRINITY + +const std = @import("std"); + +// ===================================================================== +// AST Types (simplified for CLI runtime) +// ===================================================================== + +const AST = struct { + const Program = struct { + constants: []Const = &[0]Const{}, + spec_decl: ?SpecDecl = null, + test_section: ?TestSection = null, + invariant_section: ?InvariantSection = null, + }; + + const Const = struct { + name: []const u8, + value: f64, + }; + + const SpecDecl = struct { + name: []const u8, + test_blocks: []TestBlock = &[0]TestBlock{}, + invariants: []InvariantBlock = &[0]InvariantBlock{}, + }; + + const TestBlock = struct { + name: []const u8, + clauses: []Clause = &[0]Clause{}, + }; + + const InvariantBlock = struct { + name: []const u8, + expression: []const u8, + }; + + const TestSection = struct { + test_cases: []TestCase = &[0]TestCase{}, + }; + + const InvariantSection = struct { + invariants: []Invariant = &[0]Invariant{}, + }; + + const TestCase = struct { + name: []const u8, + verify: []const u8 = "", + setup: []const u8 = "", + expected_value: []const u8 = "", + }; + + const Invariant = struct { + name: []const u8, + statement: []const u8 = "", + rationale: []const u8 = "", + }; + + const Clause = struct { + kind: ClauseKind, + content: []const u8, + }; + + const ClauseKind = enum { + given, + when, + then, + and, + assert, + expect, + }; +}; + +// ===================================================================== +// Parse Context +// ===================================================================== + +const ParseContext = struct { + ast_root: AST.Program, + errors: []Error = &[0]Error{}, + + const Error = struct { + line: usize, + column: usize, + message: []const u8, + }; +}; + +// ===================================================================== +// CLI Entry Point +// ===================================================================== + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + // Parse command line arguments + const args = try std.process.argsAlloc(allocator); + defer allocator.free(args); + + if (args.len < 2) { + try printUsage(); + return; + } + + const command = args[1]; + + // Route to command handler + if (std.mem.eql(u8, command, "spec")) { + try runSpecCommand(allocator, args[2..]); + } else if (std.mem.eql(u8, command, "gen")) { + try runGenCommand(allocator, args[2..]); + } else if (std.mem.eql(u8, command, "git")) { + try runGitCommand(allocator, args[2..]); + } else if (std.mem.eql(u8, command, "lint")) { + try runLintCommand(allocator, args[2..]); + } else if (std.mem.eql(u8, command, "help")) { + try printUsage(); + } else { + std.debug.print("Unknown command: {s}\n", .{command}); + try printUsage(); + std.process.exit(1); + } +} + +// ===================================================================== +// Command Handlers +// ===================================================================== + +fn runSpecCommand(allocator: std.mem.Allocator, args: []const []const u8) !void { + if (args.len == 0) { + try printError("spec command requires subcommand: create, validate, list\n"); + return; + } + + const subcommand = args[0]; + + if (std.mem.eql(u8, subcommand, "create")) { + if (args.len < 2) { + try printError("tri spec create [--path ]\n"); + return; + } + const name = args[1]; + const path = if (args.len >= 4 and std.mem.eql(u8, args[2], "--path")) + args[3] + else + ""; + + try specCreate(allocator, name, path); + } else if (std.mem.eql(u8, subcommand, "validate")) { + if (args.len < 2) { + try printError("tri spec validate \n"); + return; + } + try specValidate(allocator, args[1]); + } else if (std.mem.eql(u8, subcommand, "list")) { + try specList(allocator); + } else { + try printError("Unknown spec subcommand: {s}\n", .{subcommand}); + } +} + +fn runGenCommand(allocator: std.mem.Allocator, args: []const []const u8) !void { + const options = try parseGenOptions(allocator, args); + + if (options.all) { + try genAll(allocator, options); + } else if (options.spec_path) |spec| { + try gen(allocator, spec, options); + } else { + try printError("tri gen | --all\n"); + } +} + +fn runGitCommand(allocator: std.mem.Allocator, args: []const []const u8) !void { + if (args.len == 0) { + try printError("git command requires subcommand: commit, push, status\n"); + return; + } + + const subcommand = args[0]; + + if (std.mem.eql(u8, subcommand, "commit")) { + try gitCommit(allocator, args[1..]); + } else if (std.mem.eql(u8, subcommand, "push")) { + try gitPush(allocator, args[1..]); + } else if (std.mem.eql(u8, subcommand, "status")) { + try gitStatusWithCell(allocator); + } else { + try printError("Unknown git subcommand: {s}\n", .{subcommand}); + } +} + +fn runLintCommand(allocator: std.mem.Allocator, args: []const []const u8) !void { + const all = args.len > 0 and std.mem.eql(u8, args[0], "--all"); + + if (all) { + try lintAll(allocator); + } else if (args.len == 1) { + try lintFile(allocator, args[0]); + } else { + try lintAll(allocator); + } +} + +// ===================================================================== +// Spec Commands +// ===================================================================== + +fn specCreate(allocator: std.mem.Allocator, name: []const u8, path: []const u8) !void { + // Validate spec name + if (!isValidSpecName(name)) { + try printError("Invalid spec name: {s}\n", .{name}); + try printError("Spec names must be lowercase alphanumeric with underscores\n"); + return error.InvalidSpecName; + } + + // Determine output path + const output_path = if (path.len == 0) + try std.fmt.allocPrint(allocator, "specs/{s}.t27", .{name}) + else + try allocator.dupe(u8, path); + + // Check if file exists + if (fileExists(output_path)) { + try printError("Spec already exists: {s}\n", .{output_path}); + try printError("Use --force to overwrite (not implemented yet)\n"); + return error.FileExists; + } + + // Create spec with TDD template + const content = generateSpecTemplate(allocator, name); + try std.fs.cwd().writeFile(output_path, content); + + std.debug.print("Created spec: {s}\n", .{output_path}); + try std.io.getStdOut().writeAll( + \\Next steps: + \\ 1. Add constants, functions to your spec + \\ 2. Add test cases using 'test ' blocks + \\ 3. Add invariants using 'invariant ' blocks + \\ 4. Run 'tri gen {s}.t27' to generate code + \\ 5. Run 'tri test' to execute tests + , .{name}); +} + +fn specValidate(allocator: std.mem.Allocator, spec_path: []const u8) !void { + if (!fileExists(spec_path)) { + try printError("Spec not found: {s}\n", .{spec_path}); + return error.FileNotFound; + } + + const content = try std.fs.cwd().readFileAlloc(allocator, spec_path, 1024 * 1024); + defer allocator.free(content); + + // Simple parser (placeholder - would use real parser) + const context = parseSimple(content, spec_path); + + if (context.errors.len > 0) { + try printError("Parse errors in {s}:\n", .{spec_path}); + for (context.errors) |err| { + try std.io.getStdErr().writer().print( + " Line {d}:{d}: {s}\n", + .{ err.line, err.column, err.message }, + ); + } + return error.ParseError; + } + + // Validate TDD compliance + const tdd_errors = try validateTDDCompliance(allocator, &context.ast_root); + + if (tdd_errors.items.len > 0) { + try printError("TDD compliance violations in {s}:\n", .{spec_path}); + for (tdd_errors.items) |err| { + try std.io.getStdErr().writer().print(" {s}\n", .{err}); + } + tdd_errors.deinit(); + return error.TDDViolation; + } + tdd_errors.deinit(); + + std.debug.print("Spec {s} is TDD compliant\n\n", .{spec_path}); + + // Print summary + try printSummary(&context.ast_root); +} + +fn specList(allocator: std.mem.Allocator) !void { + const spec_dir = "specs"; + var dir = std.fs.cwd().openDir(spec_dir) catch |err| { + if (err == error.FileNotFound) { + try std.io.getStdOut().writeAll("No specs directory found\n"); + return; + } + return err; + }; + defer dir.close(); + + var spec_files = std.ArrayList([]const u8).init(allocator); + defer { + for (spec_files.items) |f| allocator.free(f); + spec_files.deinit(); + } + + var walker = try dir.walk(allocator); + defer walker.deinit(); + + while (try walker.next()) |entry| { + if (entry.kind == .file and std.mem.endsWith(u8, entry.path, ".t27")) { + const path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ spec_dir, entry.path }); + try spec_files.append(path); + } + } + + if (spec_files.items.len == 0) { + try std.io.getStdOut().writeAll("No spec files found\n"); + return; + } + + try std.io.getStdOut().writeAll("Spec files in project:\n\n"); + + for (spec_files.items) |spec_path| { + defer allocator.free(spec_path); + + const rel_path = spec_path["specs/".len..]; + const content = try std.fs.cwd().readFileAlloc(allocator, spec_path, 1024 * 1024); + defer allocator.free(content); + + // Quick check for tests + const has_tests = std.mem.indexOf(u8, content, ".test") != null or + std.mem.indexOf(u8, content, "test ") != null; + + const status = if (has_tests) "ok" else "x"; + try std.io.getStdOut().writer().print(" {s} {s}\n", .{ status, rel_path }); + } +} + +// ===================================================================== +// Gen Commands +// ===================================================================== + +const GenOptions = struct { + backend: []const u8 = "zig", + output_dir: []const u8 = "", + emit_tests: bool = true, + emit_conformance: bool = true, + optimize_level: u8 = 0, + no_prototype: bool = true, + all: bool = false, + spec_path: ?[]const u8 = null, +}; + +fn parseGenOptions(allocator: std.mem.Allocator, args: []const []const u8) !GenOptions { + _ = allocator; + var options = GenOptions{}; + + var i: usize = 0; + while (i < args.len) : (i += 1) { + const arg = args[i]; + + if (std.mem.eql(u8, arg, "--backend")) { + if (i + 1 < args.len) { + options.backend = args[i + 1]; + i += 1; + } + } else if (std.mem.eql(u8, arg, "--output-dir")) { + if (i + 1 < args.len) { + options.output_dir = args[i + 1]; + i += 1; + } + } else if (std.mem.eql(u8, arg, "--no-tests")) { + options.emit_tests = false; + } else if (std.mem.eql(u8, arg, "--no-conformance")) { + options.emit_conformance = false; + } else if (std.mem.eql(u8, arg, "--all")) { + options.all = true; + } else if (!std.mem.startsWith(u8, arg, "-")) { + options.spec_path = arg; + } + } + + return options; +} + +fn gen(allocator: std.mem.Allocator, spec_path: []const u8, options: GenOptions) !void { + // Check if spec exists + if (!fileExists(spec_path)) { + try printError("Spec not found: {s}\n", .{spec_path}); + return error.FileNotFound; + } + + const content = try std.fs.cwd().readFileAlloc(allocator, spec_path, 1024 * 1024); + defer allocator.free(content); + + // Validate language policy (no Cyrillic) + if (!validateNoCyrillic(content, spec_path)) { + try printError("Language policy violation: {s} contains Cyrillic\n", .{spec_path}); + return error.LanguagePolicyViolation; + } + + // Parse spec + const context = parseSimple(content, spec_path); + + if (context.errors.len > 0) { + try printError("Parse errors in {s}:\n", .{spec_path}); + for (context.errors) |err| { + try std.io.getStdErr().writer().print( + " Line {d}:{d}: {s}\n", + .{ err.line, err.column, err.message }, + ); + } + return error.ParseError; + } + + // TDD compliance check + const has_tests = hasTestsOrInvariants(&context.ast_root); + + if (!has_tests) { + try printError("TDD contract violated: no tests in spec\n\n"); + try std.io.getStdErr().writeAll( + \\The t27 project follows Test-Driven Development where: + \\ 1. Every spec MUST have at least one 'test' or 'invariant' block + \\ 2. Tests are written BEFORE or WITH the implementation + \\ 3. Conformance JSON is generated FROM tests, not hand-written + \\ + \\To fix this error: + \\ 1. Add a .test section with test cases to your spec + \\ 2. Or add a .invariant section with invariant declarations + \\ 3. Or use the high-level TDD syntax with 'test' and 'invariant' blocks + \\ + \\Example: + \\ .test + \\ ; my_test + \\ ; Verify: functionality works + \\ ; Expected: correct result + \\ + \\ .invariant + \\ ; my_invariant + \\ ; For all valid inputs: output is valid + \\ + \\NOTE: There is NO --allow-no-tests flag (prototype mode is disabled per policy) + ); + return error.TDDViolation; + } + + std.debug.print("TDD contract validated\n", .{}); + + // Generate implementation code + const impl_output = try generateImplementation(allocator, spec_path, &context.ast_root, options); + defer allocator.free(impl_output); + + // Generate test code + const test_output = if (options.emit_tests) + try generateTests(allocator, &context.ast_root, options) + else + try allocator.dupe(u8, ""); + defer if (options.emit_tests) allocator.free(test_output); + + // Generate conformance JSON + const conf_output = if (options.emit_conformance) + try generateConformance(allocator, &context.ast_root) + else + try allocator.dupe(u8, ""); + defer if (options.emit_conformance) allocator.free(conf_output); + + std.debug.print("Code generation complete!\n", .{}); +} + +fn genAll(allocator: std.mem.Allocator, options: GenOptions) !void { + const spec_files = try globSpecs(allocator); + defer { + for (spec_files.items) |f| allocator.free(f); + spec_files.deinit(); + } + + if (spec_files.items.len == 0) { + try std.io.getStdOut().writeAll("No spec files found\n"); + return; + } + + std.debug.print("Found {d} spec file(s)\n\n", .{spec_files.items.len}); + + var success_count: usize = 0; + var fail_count: usize = 0; + + for (spec_files.items) |spec_path| { + std.debug.print("Generating: {s}\n", .{spec_path}); + const result = gen(allocator, spec_path, options); + + if (result) |_| { + success_count += 1; + } else |_| { + fail_count += 1; + try std.io.getStdErr().writer().print(" Failed: {s}\n", .{spec_path}); + } + } + + try std.io.getStdOut().writer().print( + "\nGeneration complete:\n Success: {d}\n Failed: {d}\n", + .{ success_count, fail_count }, + ); + + if (fail_count > 0) { + return error.GenerationFailed; + } +} + +// ===================================================================== +// Git Commands +// ===================================================================== + +fn gitCommit(allocator: std.mem.Allocator, args: []const []const u8) !void { + _ = allocator; + var message: []const u8 = ""; + var all = false; + var mode: []const u8 = "strict"; + + var i: usize = 0; + while (i < args.len) : (i += 1) { + const arg = args[i]; + if (std.mem.eql(u8, arg, "--all") or std.mem.eql(u8, arg, "-a")) { + all = true; + } else if (std.mem.eql(u8, arg, "-m")) { + if (i + 1 < args.len) { + message = args[i + 1]; + i += 1; + } + } else if (std.mem.eql(u8, arg, "--mode")) { + if (i + 1 < args.len) { + mode = args[i + 1]; + i += 1; + } + } + } + + // TODO: Implement full git commit with cell validation + // For now, delegate to git + const git_argv = &[_][]const u8{ "git", "commit" } ++ if (all) &[_][]const u8{"--all"} else &[_][]const u8{} ++ + if (message.len > 0) &[_][]const u8{ "-m", message } else &[_][]const u8{}; + + try runGitDirect(git_argv); + std.debug.print("Mode: {s}\n", .{mode}); +} + +fn gitPush(allocator: std.mem.Allocator, args: []const []const u8) !void { + _ = allocator; + var remote: []const u8 = "origin"; + var branch: []const u8 = ""; + var mode: []const u8 = "strict"; + + var i: usize = 0; + while (i < args.len) : (i += 1) { + const arg = args[i]; + if (std.mem.eql(u8, arg, "--mode")) { + if (i + 1 < args.len) { + mode = args[i + 1]; + i += 1; + } + } else if (!std.mem.startsWith(u8, arg, "-")) { + if (remote.len == 0) { + remote = arg; + } else if (branch.len == 0) { + branch = arg; + } + } + } + + // TODO: Implement full git push with cell validation + const branch_arg = if (branch.len > 0) branch else "HEAD"; + const git_argv = &[_][]const u8{ "git", "push", remote, branch_arg }; + + try runGitDirect(git_argv); + std.debug.print("Mode: {s}\n", .{mode}); +} + +fn gitStatusWithCell(allocator: std.mem.Allocator) !void { + _ = allocator; + + // Show git status + try runGitDirect(&[_][]const u8{ "git", "status", "--porcelain" }); + + // TODO: Show cell info if registry exists + const registry_path = ".trinity/cells/registry.json"; + if (fileExists(registry_path)) { + std.debug.print("\nCell info: TODO - implement\n", .{}); + } +} + +// ===================================================================== +// Lint Commands +// ===================================================================== + +fn lintFile(allocator: std.mem.Allocator, file_path: []const u8) !void { + if (!fileExists(file_path)) { + try printError("File not found: {s}\n", .{file_path}); + return error.FileNotFound; + } + + const content = try std.fs.cwd().readFileAlloc(allocator, file_path, 1024 * 1024); + defer allocator.free(content); + + var errors: usize = 0; + + // Check 1: Language policy (no Cyrillic) + if (!validateNoCyrillic(content, file_path)) { + try printError("x Language policy: contains Cyrillic\n", .{}); + errors += 1; + } else { + std.debug.print("ok Language policy: ASCII-only\n", .{}); + } + + // Check 2: TDD compliance (if .t27 file) + if (std.mem.endsWith(u8, file_path, ".t27")) { + const context = parseSimple(content, file_path); + + if (context.errors.len > 0) { + try printError("x Parse errors: {d}\n", .{context.errors.len}); + errors += context.errors.len; + } else { + std.debug.print("ok Parse: OK\n", .{}); + } + + if (!hasTestsOrInvariants(&context.ast_root)) { + try printError("x TDD contract: no tests or invariants\n", .{}); + errors += 1; + } else { + std.debug.print("ok TDD contract: has tests/invariants\n", .{}); + } + } + + if (errors == 0) { + std.debug.print("\nok {s} is compliant\n", .{file_path}); + } else { + std.debug.print("\nx {s} has {d} violation(s)\n", .{ file_path, errors }); + return error.LintFailed; + } +} + +fn lintAll(allocator: std.mem.Allocator) !void { + const files = try globSourceFiles(allocator); + defer { + for (files.items) |f| allocator.free(f); + files.deinit(); + } + + if (files.items.len == 0) { + try std.io.getStdOut().writeAll("No source files found\n"); + return; + } + + std.debug.print("Linting {d} file(s)...\n\n", .{files.items.len}); + + var errors: usize = 0; + + for (files.items) |file| { + if (lintFile(allocator, file)) |_| {} else |_| { + errors += 1; + } + } + + if (errors == 0) { + std.debug.print("\nok All files are compliant\n", .{}); + } else { + try std.io.getStdErr().writer().print("\nx {d} file(s) have violations\n", .{errors}); + return error.LintFailed; + } +} + +// ===================================================================== +// Helper Functions +// ===================================================================== + +fn printUsage() !void { + try std.io.getStdOut().writeAll( + \\tri - Trinity T27 CLI + \\ + \\Usage: + \\ tri [arguments] + \\ + \\Commands: + \\ spec create [--path ] Create a new spec with TDD template + \\ spec validate Validate spec for TDD compliance + \\ spec list List all specs in project + \\ + \\ gen Generate code from spec + \\ [--backend ] Target backend (default: zig) + \\ [--output-dir ] Output directory + \\ [--no-tests] Skip test generation + \\ [--no-conformance] Skip conformance JSON + \\ gen --all Generate code for all specs + \\ + \\ git commit [--all] [-m "msg"] Commit with cell validation + \\ [--mode strict|normal|local] Commit mode (default: strict) + \\ git push [remote] [branch] [--mode ...] Push with cell validation + \\ git status Show git status with cell info + \\ + \\ lint [file] Check file or all for compliance + \\ --all Check all source files + \\ + \\ help Show this help message + \\ + \\Environment Variables: + \\ TRI_BACKEND Default backend for gen (default: zig) + \\ TRI_OUTPUT_DIR Default output directory + \\ + \\SOUL.md Constitutional Laws: + \\ Law #1: Source files MUST NOT contain Cyrillic (docs may) + \\ Law #2: Every spec MUST have tests or invariants (no prototype mode) + \\ + \\For more information, see: + \\ docs/SOUL.md - Constitutional laws + \\ docs/TDD-CONTRACT.md - TDD contract details + \\ docs/TRI_SYNTAX_VNEXT.md - Language syntax + ); +} + +fn printError(msg: []const u8) !void { + try std.io.getStdErr().writer().print("{s}\n", .{msg}); +} + +fn printErrorFmt(comptime fmt: []const u8, args: anytype) !void { + const writer = std.io.getStdErr().writer(); + try writer.print(fmt, args); + try writer.writeByte('\n'); +} + +fn fileExists(path: []const u8) bool { + std.fs.cwd().openFile(path, .{}) catch |err| { + if (err == error.FileNotFound) return false; + return true; + }; + return true; +} + +fn isValidSpecName(name: []const u8) bool { + if (name.len == 0) return false; + + // Must start with lowercase letter + const first = name[0]; + if (first < 'a' or first > 'z') return false; + + // Only alphanumeric and underscores allowed + for (name) |c| { + if ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or c == '_') { + continue; + } + return false; + } + + return true; +} + +fn generateSpecTemplate(allocator: std.mem.Allocator, name: []const u8) ![]const u8 { + return std.fmt.allocPrint(allocator, + \\; {s}.t27 -- Specification for {s} + \\; phi^2 + 1/phi^2 = 3 | TRINITY + \\ + \\; This file is the source of truth for {s}. + \\; Generated code is a derived artifact - DO NOT EDIT generated files. + \\; + \\; TDD Contract: This spec MUST include at least one test or invariant. + \\ + \\use base::types + \\ + \\; =============================================================== + \\; Constants + \\; =============================================================== + \\ + \\; Example constant + \\const EXAMPLE_CONSTANT = 42 + \\ + \\; =============================================================== + \\; Data Section + \\; =============================================================== + \\ + \\.data + \\ .const DATA_INIT 0 + \\ + \\; =============================================================== + \\; Code Section + \\; =============================================================== + \\ + \\.code + \\main: + \\ ; Your code here + \\ HALT + \\ + \\; =============================================================== + \\; TDD-Inside-Spec: Tests and Invariants + \\; =============================================================== + \\ + \\.test + \\ ; example_test + \\ ; Verify: example functionality works correctly + \\ ; Setup: initialize with EXAMPLE_CONSTANT + \\ ; Expected: returns expected value + \\ + \\.invariant + \\ ; example_invariant + \\ ; For all valid inputs: output is in valid range + \\ ; Rationale: Ensures function correctness + \\ + \\.bench + \\ ; example_benchmark + \\ ; Measure: operations per second + \\ ; Target: > 1M ops/sec + , .{name, name, name, name}); +} + +fn validateNoCyrillic(content: []const u8, file_path: []const u8) bool { + // Check if file is in docs/ directory (Cyrillic allowed) + if (std.mem.indexOf(u8, file_path, "/docs/") != null or + std.mem.startsWith(u8, file_path, "docs/")) { + return true; + } + + // Scan for Cyrillic characters (U+0400-U+04FF) + var i: usize = 0; + while (i < content.len) : (i += 1) { + const c = content[i]; + + // Check for 2-byte UTF-8 Cyrillic starting with 0xD0-0xD4 + if (c >= 0xD0 and c <= 0xD4) { + if (i + 1 < content.len) { + const next_c = content[i + 1]; + const codepoint = (@as(u16, c) << 8) | next_c; + // Cyrillic block: U+0400-U+04FF + if (codepoint >= 0x0400 and codepoint <= 0x04FF) { + return false; + } + } + } + } + + return true; +} + +fn hasTestsOrInvariants(ast_root: *const AST.Program) bool { + // Check test_section + if (ast_root.test_section) |test_section| { + if (test_section.test_cases.len > 0) return true; + } + + // Check invariant_section + if (ast_root.invariant_section) |inv_section| { + if (inv_section.invariants.len > 0) return true; + } + + // Check spec_decl (high-level TDD) + if (ast_root.spec_decl) |spec_decl| { + if (spec_decl.test_blocks.len > 0) return true; + if (spec_decl.invariants.len > 0) return true; + } + + return false; +} + +fn validateTDDCompliance(allocator: std.mem.Allocator, ast_root: *const AST.Program) !std.ArrayList([]const u8) { + var errors = std.ArrayList([]const u8).init(allocator); + + if (!hasTestsOrInvariants(ast_root)) { + try errors.append(try allocator.dupe(u8, "TDD contract violated: spec must contain at least one 'test' or 'invariant' block")); + } + + return errors; +} + +fn printSummary(ast_root: *const AST.Program) !void { + try std.io.getStdOut().writer().print( + \\Summary: + \\ Constants: {d} + , .{ast_root.constants.len}); + + if (ast_root.spec_decl) |spec| { + try std.io.getStdOut().writer().print( + \\ Tests (spec-style): {d} + \\ Invariants (spec-style): {d} + , .{ spec.test_blocks.len, spec.invariants.len }); + } + + if (ast_root.test_section) |test| { + try std.io.getStdOut().writer().print( + \\ Tests (assembly-style): {d} + , .{test.test_cases.len}); + } + + if (ast_root.invariant_section) |inv| { + try std.io.getStdOut().writer().print( + \\ Invariants (assembly-style): {d} + , .{inv.invariants.len}); + } + + try std.io.getStdOut().writeByte('\n'); +} + +// ===================================================================== +// Simple Parser (placeholder - would use real parser.t27) +// ===================================================================== + +fn parseSimple(content: []const u8, file_path: []const u8) ParseContext { + _ = file_path; + + var program = AST.Program{}; + + // Simple line-by-line parsing for .test and .invariant sections + var lines = std.mem.splitScalar(u8, content, '\n'); + var in_test = false; + var in_invariant = false; + var test_cases = std.ArrayList(AST.TestCase).init(std.heap.page_allocator); + var invariants = std.ArrayList(AST.Invariant).init(std.heap.page_allocator); + + while (lines.next()) |line| { + const trimmed = std.mem.trim(u8, line, " \t\r"); + + if (std.mem.startsWith(u8, trimmed, ".test")) { + in_test = true; + in_invariant = false; + } else if (std.mem.startsWith(u8, trimmed, ".invariant")) { + in_invariant = true; + in_test = false; + } else if (std.mem.startsWith(u8, trimmed, ".") and + !std.mem.startsWith(u8, trimmed, ".test") and + !std.mem.startsWith(u8, trimmed, ".invariant") and + !std.mem.startsWith(u8, trimmed, ".bench")) { + in_test = false; + in_invariant = false; + } else if (in_test and trimmed.len > 0 and !std.mem.startsWith(u8, trimmed, ";")) { + // Found a test case (non-comment, non-empty) + const name = std.mem.trim(u8, trimmed, " \t\r"); + test_cases.append(AST.TestCase{ + .name = name, + .verify = "", + .setup = "", + .expected = "", + }) catch {}; + } else if (in_invariant and trimmed.len > 0 and !std.mem.startsWith(u8, trimmed, ";")) { + // Found an invariant + const name = std.mem.trim(u8, trimmed, " \t\r"); + invariants.append(AST.Invariant{ + .name = name, + .statement = "", + .rationale = "", + }) catch {}; + } + } + + if (test_cases.items.len > 0) { + program.test_section = AST.TestSection{ + .test_cases = test_cases.toOwnedSlice(), + }; + } + + if (invariants.items.len > 0) { + program.invariant_section = AST.InvariantSection{ + .invariants = invariants.toOwnedSlice(), + }; + } + + return ParseContext{ + .ast_root = program, + .errors = &[0]ParseContext.Error{}, + }; +} + +// ===================================================================== +// Code Generation Helpers +// ===================================================================== + +fn generateImplementation(allocator: std.mem.Allocator, spec_path: []const u8, ast_root: *const AST.Program, options: GenOptions) ![]const u8 { + _ = spec_path; + _ = ast_root; + _ = options; + + // Placeholder + return try allocator.dupe(u8, "// Generated implementation for spec\n"); +} + +fn generateTests(allocator: std.mem.Allocator, ast_root: *const AST.Program, options: GenOptions) ![]const u8 { + _ = options; + + var output = std.ArrayList(u8).init(allocator); + defer output.deinit(); + + try output.appendSlice("// Generated tests for spec\n"); + + if (ast_root.test_section) |test_section| { + for (test_section.test_cases) |test_case| { + try output.appendSlice("test \""); + try output.appendSlice(test_case.name); + try output.appendSlice("\" {\n"); + try output.appendSlice(" // TODO: implement test\n"); + try output.appendSlice("}\n\n"); + } + } + + if (ast_root.invariant_section) |inv_section| { + for (inv_section.invariants) |inv| { + try output.appendSlice("// invariant: "); + try output.appendSlice(inv.name); + try output.appendSlice("\n"); + } + } + + return output.toOwnedSlice(); +} + +fn generateConformance(allocator: std.mem.Allocator, ast_root: *const AST.Program) ![]const u8 { + var output = std.ArrayList(u8).init(allocator); + defer output.deinit(); + + try output.appendSlice("{\n"); + try output.appendSlice(" \"description\": \"t27 conformance\",\n"); + try output.appendSlice(" \"test_vectors\": [\n"); + + if (ast_root.test_section) |test_section| { + for (test_section.test_cases, 0..) |test_case, i| { + try output.appendSlice(" {\n"); + try output.appendSlice(" \"name\": \""); + try output.appendSlice(test_case.name); + try output.appendSlice("\",\n"); + try output.appendSlice(" \"verify\": \""); + try output.appendSlice(if (test_case.verify.len > 0) test_case.verify else "functionality works correctly"); + try output.appendSlice("\",\n"); + try output.appendSlice(" \"expected\": \""); + try output.appendSlice(if (test_case.expected.len > 0) test_case.expected else "correct result"); + try output.appendSlice("\"\n"); + try output.appendSlice(" }"); + if (i < test_section.test_cases.len - 1) { + try output.appendSlice(","); + } + try output.appendSlice("\n"); + } + } + + try output.appendSlice(" ]\n"); + try output.appendSlice("}\n"); + + return output.toOwnedSlice(); +} + +// ===================================================================== +// Git Helpers +// ===================================================================== + +fn runGitDirect(argv: []const []const u8) !void { + const result = std.process.Child.exec(.{ + .allocator = std.heap.page_allocator, + .argv = argv, + }) catch |err| { + try printErrorFmt("Failed to execute git: {}\n", .{@errorName(err)}); + return error.GitFailed; + }; + defer result.deinit(); + + try std.io.getStdOut().writeAll(result.stdout); + + const term = try result.wait(); + if (term != .Exited or term.Exited != 0) { + try std.io.getStdErr().writeAll(result.stderr); + return error.GitFailed; + } +} + +// ===================================================================== +// File System Helpers +// ===================================================================== + +fn globSpecs(allocator: std.mem.Allocator) !std.ArrayList([]const u8) { + var spec_files = std.ArrayList([]const u8).init(allocator); + + var dir = std.fs.cwd().openDir("specs") catch |err| { + if (err == error.FileNotFound) return spec_files; + return err; + }; + defer dir.close(); + + var walker = try dir.walk(allocator); + defer walker.deinit(); + + while (try walker.next()) |entry| { + if (entry.kind == .file and std.mem.endsWith(u8, entry.path, ".t27")) { + const path = try std.fmt.allocPrint(allocator, "specs/{s}", .{entry.path}); + try spec_files.append(path); + } + } + + return spec_files; +} + +fn globSourceFiles(allocator: std.mem.Allocator) !std.ArrayList([]const u8) { + var files = std.ArrayList([]const u8).init(allocator); + + // Common source extensions + const extensions = [_][]const u8{ ".t27", ".tri", ".zig", ".c", ".h", ".v", ".verilog" }; + + // Recursively find files + var walker = try std.fs.cwd().walk(allocator, .{ + .max_depth = 10, + .skip_hidden_files = true, + }) catch |err| { + if (err == error.FileNotFound) return files; + return err; + }; + defer walker.deinit(); + + while (try walker.next()) |entry| { + if (entry.kind == .file) { + const path = entry.path; + // Skip docs/ directory + if (std.mem.indexOf(u8, path, "/docs/") != null) continue; + if (std.mem.indexOf(u8, path, "docs/") == 0) continue; + + for (extensions) |ext| { + if (std.mem.endsWith(u8, path, ext)) { + try files.append(try allocator.dupe(u8, path)); + break; + } + } + } + } + + return files; +} diff --git a/apps/website/public/t27/files/examples/fpga/qmtech_minimal/design.t27 b/apps/website/public/t27/files/examples/fpga/qmtech_minimal/design.t27 new file mode 100644 index 0000000000..c886d7f042 --- /dev/null +++ b/apps/website/public/t27/files/examples/fpga/qmtech_minimal/design.t27 @@ -0,0 +1,150 @@ +# QMTECH XC7A100T Minimal Design +# Trinity .t27 specification for minimal FPGA implementation + +import fpga.modules.heartbeat +import fpga.modules.uart_loopback + +# Board configuration +board: qmtech_xc7a100t_minimal { + # Clock configuration + clock: external_12mhz { + pin: E3 + frequency: 12_000_000 # 12 MHz + io_standard: LVCMOS33 + bank: 34 + } + + # Reset (active low) + reset: active_low { + pin: C14 + io_standard: LVCMOS33 + bank: 34 + } + + # UART communication + uart: uart_115200 { + tx_pin: T15 + rx_pin: T14 + baud_rate: 115200 + io_standard: LVCMOS33 + bank: 34 + } + + # LED outputs (8-bit) + leds: led_array { + pins: [H17, K15, J13, N14, R18, U18, T13, T11] + io_standard: LVCMOS33 + bank: 35 + } +} + +# Design instantiation +design: minimal_heartbeat { + # Use heartbeat module for LED pattern + heartbeat: heartbeat_module { + clock: board.clock + reset: board.reset + output: board.leds + + # Heartbeat pattern parameters + period: 1_000_000 # 1 second at 12MHz + pattern: [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02] + } + + # UART loopback for testing + uart: uart_loopback_module { + clock: board.clock + reset: board.reset + uart: board.uart + + # Test message: "HELLO\n" + test_message: "HELLO\n" + repeat_interval: 5_000_000 # 5 seconds + } +} + +# Constraints +constraints: xdc { + # Clock constraints + create_clock -name clk_12mhz -period 83.333 [get_ports clk] + + # IO constraints + set_io_standard LVCMOS33 [get_ports *] + + # False path for async reset (if needed) + set_false_path -from [get_ports rst_n] + + # Timing constraints for UART + set_max_delay -from [get_ports uart_tx] -to [get_ports uart_rx] -period 10 +} + +# Verification +test: { + # Check that heartbeat pattern changes + test_heartbeat_pattern: { + description: "Verify LED heartbeat pattern cycles correctly" + setup: { + clock_cycles: 100_000 + } + expect: { + led_values_change: true + pattern_matches: [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80] + } + } + + # UART communication test + test_uart_loopback: { + description: "Verify UART loopback transmits test message" + setup: { + baud_rate: 115200 + message: "HELLO\n" + } + expect: { + bytes_transmitted: 6 + received_matches_transmitted: true + } + } +} + +# Invariants +invariant: { + # Resource usage constraints + max_luts: 100 + max_ffs: 50 + max_bram: 0 + max_dsp: 0 + + # Timing constraints + max_frequency: 12_000_000 + + # IO bank usage + io_banks_used: [34, 35] + total_io_pins: 12 # 1 clk + 1 rst + 2 uart + 8 led +} + +# Benchmarks +benchmark: { + # Timing analysis + setup_time: "2.0 ns" + hold_time: "0.5 ns" + clock_to_out: "5.0 ns" + + # Power estimation (approximate) + static_power: "50 mW" + dynamic_power: "100 mW @ 12MHz" +} + +# Documentation +documentation: { + title: "QMTECH XC7A100T Minimal Design" + description: "Heartbeat LED pattern + UART loopback using Trinity t27 toolchain" + target_device: "XC7A100T-1CSG324C" + toolchain: "Yosys + nextpnr-xilinx + prjxray" + vivado_dependency: "none" + + pinout_reference: "See specs/boards/qmtech_xc7a100t_minimal.t27" + generated_verilog: "build/fpga/generated/minimal_design.v" + final_bitstream: "build/fpga/bitstream.bit" +} + +# phi^2 + 1/phi^2 = 3 | TRINITY \ No newline at end of file diff --git a/apps/website/public/t27/specs/account/auth.t27 b/apps/website/public/t27/files/specs/account/auth.t27 similarity index 100% rename from apps/website/public/t27/specs/account/auth.t27 rename to apps/website/public/t27/files/specs/account/auth.t27 diff --git a/apps/website/public/t27/specs/account/repo.t27 b/apps/website/public/t27/files/specs/account/repo.t27 similarity index 100% rename from apps/website/public/t27/specs/account/repo.t27 rename to apps/website/public/t27/files/specs/account/repo.t27 diff --git a/apps/website/public/t27/specs/account/schema.t27 b/apps/website/public/t27/files/specs/account/schema.t27 similarity index 100% rename from apps/website/public/t27/specs/account/schema.t27 rename to apps/website/public/t27/files/specs/account/schema.t27 diff --git a/apps/website/public/t27/specs/api/c_api_contract.t27 b/apps/website/public/t27/files/specs/api/c_api_contract.t27 similarity index 100% rename from apps/website/public/t27/specs/api/c_api_contract.t27 rename to apps/website/public/t27/files/specs/api/c_api_contract.t27 diff --git a/apps/website/public/t27/specs/api/sdk_contract.t27 b/apps/website/public/t27/files/specs/api/sdk_contract.t27 similarity index 100% rename from apps/website/public/t27/specs/api/sdk_contract.t27 rename to apps/website/public/t27/files/specs/api/sdk_contract.t27 diff --git a/apps/website/public/t27/specs/api/tri_net_api.t27 b/apps/website/public/t27/files/specs/api/tri_net_api.t27 similarity index 100% rename from apps/website/public/t27/specs/api/tri_net_api.t27 rename to apps/website/public/t27/files/specs/api/tri_net_api.t27 diff --git a/apps/website/public/t27/specs/ar/asp_solver.t27 b/apps/website/public/t27/files/specs/ar/asp_solver.t27 similarity index 100% rename from apps/website/public/t27/specs/ar/asp_solver.t27 rename to apps/website/public/t27/files/specs/ar/asp_solver.t27 diff --git a/apps/website/public/t27/specs/ar/coa_planning.t27 b/apps/website/public/t27/files/specs/ar/coa_planning.t27 similarity index 100% rename from apps/website/public/t27/specs/ar/coa_planning.t27 rename to apps/website/public/t27/files/specs/ar/coa_planning.t27 diff --git a/apps/website/public/t27/specs/ar/composition.t27 b/apps/website/public/t27/files/specs/ar/composition.t27 similarity index 100% rename from apps/website/public/t27/specs/ar/composition.t27 rename to apps/website/public/t27/files/specs/ar/composition.t27 diff --git a/apps/website/public/t27/specs/ar/datalog_engine.t27 b/apps/website/public/t27/files/specs/ar/datalog_engine.t27 similarity index 100% rename from apps/website/public/t27/specs/ar/datalog_engine.t27 rename to apps/website/public/t27/files/specs/ar/datalog_engine.t27 diff --git a/apps/website/public/t27/specs/ar/explainability.t27 b/apps/website/public/t27/files/specs/ar/explainability.t27 similarity index 100% rename from apps/website/public/t27/specs/ar/explainability.t27 rename to apps/website/public/t27/files/specs/ar/explainability.t27 diff --git a/apps/website/public/t27/specs/ar/proof_trace.t27 b/apps/website/public/t27/files/specs/ar/proof_trace.t27 similarity index 100% rename from apps/website/public/t27/specs/ar/proof_trace.t27 rename to apps/website/public/t27/files/specs/ar/proof_trace.t27 diff --git a/apps/website/public/t27/specs/ar/restraint.t27 b/apps/website/public/t27/files/specs/ar/restraint.t27 similarity index 100% rename from apps/website/public/t27/specs/ar/restraint.t27 rename to apps/website/public/t27/files/specs/ar/restraint.t27 diff --git a/apps/website/public/t27/specs/ar/ternary_logic.t27 b/apps/website/public/t27/files/specs/ar/ternary_logic.t27 similarity index 100% rename from apps/website/public/t27/specs/ar/ternary_logic.t27 rename to apps/website/public/t27/files/specs/ar/ternary_logic.t27 diff --git a/apps/website/public/t27/specs/auth/config.t27 b/apps/website/public/t27/files/specs/auth/config.t27 similarity index 100% rename from apps/website/public/t27/specs/auth/config.t27 rename to apps/website/public/t27/files/specs/auth/config.t27 diff --git a/apps/website/public/t27/specs/automation/wrapup-auto.t27 b/apps/website/public/t27/files/specs/automation/wrapup-auto.t27 similarity index 100% rename from apps/website/public/t27/specs/automation/wrapup-auto.t27 rename to apps/website/public/t27/files/specs/automation/wrapup-auto.t27 diff --git a/apps/website/public/t27/specs/base/debounce.t27 b/apps/website/public/t27/files/specs/base/debounce.t27 similarity index 100% rename from apps/website/public/t27/specs/base/debounce.t27 rename to apps/website/public/t27/files/specs/base/debounce.t27 diff --git a/apps/website/public/t27/specs/base/ops.t27 b/apps/website/public/t27/files/specs/base/ops.t27 similarity index 100% rename from apps/website/public/t27/specs/base/ops.t27 rename to apps/website/public/t27/files/specs/base/ops.t27 diff --git a/apps/website/public/t27/specs/base/ring_32.t27 b/apps/website/public/t27/files/specs/base/ring_32.t27 similarity index 100% rename from apps/website/public/t27/specs/base/ring_32.t27 rename to apps/website/public/t27/files/specs/base/ring_32.t27 diff --git a/apps/website/public/t27/specs/base/seed.t27 b/apps/website/public/t27/files/specs/base/seed.t27 similarity index 100% rename from apps/website/public/t27/specs/base/seed.t27 rename to apps/website/public/t27/files/specs/base/seed.t27 diff --git a/apps/website/public/t27/specs/base/ternary_add.t27 b/apps/website/public/t27/files/specs/base/ternary_add.t27 similarity index 100% rename from apps/website/public/t27/specs/base/ternary_add.t27 rename to apps/website/public/t27/files/specs/base/ternary_add.t27 diff --git a/apps/website/public/t27/specs/base/ternary_encoding.t27 b/apps/website/public/t27/files/specs/base/ternary_encoding.t27 similarity index 100% rename from apps/website/public/t27/specs/base/ternary_encoding.t27 rename to apps/website/public/t27/files/specs/base/ternary_encoding.t27 diff --git a/apps/website/public/t27/specs/base/ternary_memory.t27 b/apps/website/public/t27/files/specs/base/ternary_memory.t27 similarity index 100% rename from apps/website/public/t27/specs/base/ternary_memory.t27 rename to apps/website/public/t27/files/specs/base/ternary_memory.t27 diff --git a/apps/website/public/t27/specs/base/types.t27 b/apps/website/public/t27/files/specs/base/types.t27 similarity index 100% rename from apps/website/public/t27/specs/base/types.t27 rename to apps/website/public/t27/files/specs/base/types.t27 diff --git a/apps/website/public/t27/specs/benchmarks/bench_main.t27 b/apps/website/public/t27/files/specs/benchmarks/bench_main.t27 similarity index 100% rename from apps/website/public/t27/specs/benchmarks/bench_main.t27 rename to apps/website/public/t27/files/specs/benchmarks/bench_main.t27 diff --git a/apps/website/public/t27/specs/benchmarks/bench_nn.t27 b/apps/website/public/t27/files/specs/benchmarks/bench_nn.t27 similarity index 100% rename from apps/website/public/t27/specs/benchmarks/bench_nn.t27 rename to apps/website/public/t27/files/specs/benchmarks/bench_nn.t27 diff --git a/apps/website/public/t27/specs/benchmarks/gf16_bfloat16_nmse.t27 b/apps/website/public/t27/files/specs/benchmarks/gf16_bfloat16_nmse.t27 similarity index 100% rename from apps/website/public/t27/specs/benchmarks/gf16_bfloat16_nmse.t27 rename to apps/website/public/t27/files/specs/benchmarks/gf16_bfloat16_nmse.t27 diff --git a/apps/website/public/t27/specs/benchmarks/ternary_vs_binary.t27 b/apps/website/public/t27/files/specs/benchmarks/ternary_vs_binary.t27 similarity index 100% rename from apps/website/public/t27/specs/benchmarks/ternary_vs_binary.t27 rename to apps/website/public/t27/files/specs/benchmarks/ternary_vs_binary.t27 diff --git a/apps/website/public/t27/specs/boards/arty_a7.t27 b/apps/website/public/t27/files/specs/boards/arty_a7.t27 similarity index 100% rename from apps/website/public/t27/specs/boards/arty_a7.t27 rename to apps/website/public/t27/files/specs/boards/arty_a7.t27 diff --git a/apps/website/public/t27/specs/boards/xc7a100t_full.t27 b/apps/website/public/t27/files/specs/boards/xc7a100t_full.t27 similarity index 100% rename from apps/website/public/t27/specs/boards/xc7a100t_full.t27 rename to apps/website/public/t27/files/specs/boards/xc7a100t_full.t27 diff --git a/apps/website/public/t27/specs/boards/xc7a100t_minimal.t27 b/apps/website/public/t27/files/specs/boards/xc7a100t_minimal.t27 similarity index 100% rename from apps/website/public/t27/specs/boards/xc7a100t_minimal.t27 rename to apps/website/public/t27/files/specs/boards/xc7a100t_minimal.t27 diff --git a/apps/website/public/t27/specs/brain/brain.t27 b/apps/website/public/t27/files/specs/brain/brain.t27 similarity index 100% rename from apps/website/public/t27/specs/brain/brain.t27 rename to apps/website/public/t27/files/specs/brain/brain.t27 diff --git a/apps/website/public/t27/specs/brain/bus.t27 b/apps/website/public/t27/files/specs/brain/bus.t27 similarity index 100% rename from apps/website/public/t27/specs/brain/bus.t27 rename to apps/website/public/t27/files/specs/brain/bus.t27 diff --git a/apps/website/public/t27/specs/brain/cognitive_loop.t27 b/apps/website/public/t27/files/specs/brain/cognitive_loop.t27 similarity index 100% rename from apps/website/public/t27/specs/brain/cognitive_loop.t27 rename to apps/website/public/t27/files/specs/brain/cognitive_loop.t27 diff --git a/apps/website/public/t27/specs/brain/neural_gamma.t27 b/apps/website/public/t27/files/specs/brain/neural_gamma.t27 similarity index 100% rename from apps/website/public/t27/specs/brain/neural_gamma.t27 rename to apps/website/public/t27/files/specs/brain/neural_gamma.t27 diff --git a/apps/website/public/t27/specs/brain/phi_timing.t27 b/apps/website/public/t27/files/specs/brain/phi_timing.t27 similarity index 100% rename from apps/website/public/t27/specs/brain/phi_timing.t27 rename to apps/website/public/t27/files/specs/brain/phi_timing.t27 diff --git a/apps/website/public/t27/specs/brain/unified_state.t27 b/apps/website/public/t27/files/specs/brain/unified_state.t27 similarity index 100% rename from apps/website/public/t27/specs/brain/unified_state.t27 rename to apps/website/public/t27/files/specs/brain/unified_state.t27 diff --git a/apps/website/public/t27/specs/bus/pubsub.t27 b/apps/website/public/t27/files/specs/bus/pubsub.t27 similarity index 100% rename from apps/website/public/t27/specs/bus/pubsub.t27 rename to apps/website/public/t27/files/specs/bus/pubsub.t27 diff --git a/apps/website/public/t27/specs/bus/schema.t27 b/apps/website/public/t27/files/specs/bus/schema.t27 similarity index 100% rename from apps/website/public/t27/specs/bus/schema.t27 rename to apps/website/public/t27/files/specs/bus/schema.t27 diff --git a/apps/website/public/t27/specs/cloud/railway_deploy.t27 b/apps/website/public/t27/files/specs/cloud/railway_deploy.t27 similarity index 100% rename from apps/website/public/t27/specs/cloud/railway_deploy.t27 rename to apps/website/public/t27/files/specs/cloud/railway_deploy.t27 diff --git a/apps/website/public/t27/specs/compiler/diagnostics.t27 b/apps/website/public/t27/files/specs/compiler/diagnostics.t27 similarity index 100% rename from apps/website/public/t27/specs/compiler/diagnostics.t27 rename to apps/website/public/t27/files/specs/compiler/diagnostics.t27 diff --git a/apps/website/public/t27/specs/compiler/lexer.t27 b/apps/website/public/t27/files/specs/compiler/lexer.t27 similarity index 100% rename from apps/website/public/t27/specs/compiler/lexer.t27 rename to apps/website/public/t27/files/specs/compiler/lexer.t27 diff --git a/apps/website/public/t27/specs/compiler/linker.t27 b/apps/website/public/t27/files/specs/compiler/linker.t27 similarity index 100% rename from apps/website/public/t27/specs/compiler/linker.t27 rename to apps/website/public/t27/files/specs/compiler/linker.t27 diff --git a/apps/website/public/t27/specs/compiler/meta_compile.t27 b/apps/website/public/t27/files/specs/compiler/meta_compile.t27 similarity index 100% rename from apps/website/public/t27/specs/compiler/meta_compile.t27 rename to apps/website/public/t27/files/specs/compiler/meta_compile.t27 diff --git a/apps/website/public/t27/specs/compiler/mod_structure.t27 b/apps/website/public/t27/files/specs/compiler/mod_structure.t27 similarity index 100% rename from apps/website/public/t27/specs/compiler/mod_structure.t27 rename to apps/website/public/t27/files/specs/compiler/mod_structure.t27 diff --git a/apps/website/public/t27/specs/compiler/optimizer.t27 b/apps/website/public/t27/files/specs/compiler/optimizer.t27 similarity index 100% rename from apps/website/public/t27/specs/compiler/optimizer.t27 rename to apps/website/public/t27/files/specs/compiler/optimizer.t27 diff --git a/apps/website/public/t27/specs/compiler/parser.t27 b/apps/website/public/t27/files/specs/compiler/parser.t27 similarity index 100% rename from apps/website/public/t27/specs/compiler/parser.t27 rename to apps/website/public/t27/files/specs/compiler/parser.t27 diff --git a/apps/website/public/t27/specs/compiler/pipeline.t27 b/apps/website/public/t27/files/specs/compiler/pipeline.t27 similarity index 100% rename from apps/website/public/t27/specs/compiler/pipeline.t27 rename to apps/website/public/t27/files/specs/compiler/pipeline.t27 diff --git a/apps/website/public/t27/specs/compiler/stdlib.t27 b/apps/website/public/t27/files/specs/compiler/stdlib.t27 similarity index 100% rename from apps/website/public/t27/specs/compiler/stdlib.t27 rename to apps/website/public/t27/files/specs/compiler/stdlib.t27 diff --git a/apps/website/public/t27/specs/compiler/typechecker.t27 b/apps/website/public/t27/files/specs/compiler/typechecker.t27 similarity index 100% rename from apps/website/public/t27/specs/compiler/typechecker.t27 rename to apps/website/public/t27/files/specs/compiler/typechecker.t27 diff --git a/apps/website/public/t27/specs/config/load.t27 b/apps/website/public/t27/files/specs/config/load.t27 similarity index 100% rename from apps/website/public/t27/specs/config/load.t27 rename to apps/website/public/t27/files/specs/config/load.t27 diff --git a/apps/website/public/t27/specs/config/migrate.t27 b/apps/website/public/t27/files/specs/config/migrate.t27 similarity index 100% rename from apps/website/public/t27/specs/config/migrate.t27 rename to apps/website/public/t27/files/specs/config/migrate.t27 diff --git a/apps/website/public/t27/specs/config/paths.t27 b/apps/website/public/t27/files/specs/config/paths.t27 similarity index 100% rename from apps/website/public/t27/specs/config/paths.t27 rename to apps/website/public/t27/files/specs/config/paths.t27 diff --git a/apps/website/public/t27/specs/config/schema.t27 b/apps/website/public/t27/files/specs/config/schema.t27 similarity index 100% rename from apps/website/public/t27/specs/config/schema.t27 rename to apps/website/public/t27/files/specs/config/schema.t27 diff --git a/apps/website/public/t27/specs/conformance/e2e_scenarios.t27 b/apps/website/public/t27/files/specs/conformance/e2e_scenarios.t27 similarity index 100% rename from apps/website/public/t27/specs/conformance/e2e_scenarios.t27 rename to apps/website/public/t27/files/specs/conformance/e2e_scenarios.t27 diff --git a/apps/website/public/t27/specs/demos/jones_topology_decision_gate.t27 b/apps/website/public/t27/files/specs/demos/jones_topology_decision_gate.t27 similarity index 100% rename from apps/website/public/t27/specs/demos/jones_topology_decision_gate.t27 rename to apps/website/public/t27/files/specs/demos/jones_topology_decision_gate.t27 diff --git a/apps/website/public/t27/specs/demos/jones_topology_filter.t27 b/apps/website/public/t27/files/specs/demos/jones_topology_filter.t27 similarity index 100% rename from apps/website/public/t27/specs/demos/jones_topology_filter.t27 rename to apps/website/public/t27/files/specs/demos/jones_topology_filter.t27 diff --git a/apps/website/public/t27/specs/demos/simple_test.t27 b/apps/website/public/t27/files/specs/demos/simple_test.t27 similarity index 100% rename from apps/website/public/t27/specs/demos/simple_test.t27 rename to apps/website/public/t27/files/specs/demos/simple_test.t27 diff --git a/apps/website/public/t27/specs/depin/prove.t27 b/apps/website/public/t27/files/specs/depin/prove.t27 similarity index 100% rename from apps/website/public/t27/specs/depin/prove.t27 rename to apps/website/public/t27/files/specs/depin/prove.t27 diff --git a/apps/website/public/t27/specs/enrichment/audio_overview.t27 b/apps/website/public/t27/files/specs/enrichment/audio_overview.t27 similarity index 100% rename from apps/website/public/t27/specs/enrichment/audio_overview.t27 rename to apps/website/public/t27/files/specs/enrichment/audio_overview.t27 diff --git a/apps/website/public/t27/specs/enrichment/youtube_transcript.t27 b/apps/website/public/t27/files/specs/enrichment/youtube_transcript.t27 similarity index 100% rename from apps/website/public/t27/specs/enrichment/youtube_transcript.t27 rename to apps/website/public/t27/files/specs/enrichment/youtube_transcript.t27 diff --git a/apps/website/public/t27/specs/file/operations.t27 b/apps/website/public/t27/files/specs/file/operations.t27 similarity index 100% rename from apps/website/public/t27/specs/file/operations.t27 rename to apps/website/public/t27/files/specs/file/operations.t27 diff --git a/apps/website/public/t27/specs/file/schema.t27 b/apps/website/public/t27/files/specs/file/schema.t27 similarity index 100% rename from apps/website/public/t27/specs/file/schema.t27 rename to apps/website/public/t27/files/specs/file/schema.t27 diff --git a/apps/website/public/t27/specs/file/watcher.t27 b/apps/website/public/t27/files/specs/file/watcher.t27 similarity index 100% rename from apps/website/public/t27/specs/file/watcher.t27 rename to apps/website/public/t27/files/specs/file/watcher.t27 diff --git a/apps/website/public/t27/specs/fpga/apb_bridge.t27 b/apps/website/public/t27/files/specs/fpga/apb_bridge.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/apb_bridge.t27 rename to apps/website/public/t27/files/specs/fpga/apb_bridge.t27 diff --git a/apps/website/public/t27/specs/fpga/assembler.t27 b/apps/website/public/t27/files/specs/fpga/assembler.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/assembler.t27 rename to apps/website/public/t27/files/specs/fpga/assembler.t27 diff --git a/apps/website/public/t27/specs/fpga/axi4.t27 b/apps/website/public/t27/files/specs/fpga/axi4.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/axi4.t27 rename to apps/website/public/t27/files/specs/fpga/axi4.t27 diff --git a/apps/website/public/t27/specs/fpga/boards/arty_a7_integration.t27 b/apps/website/public/t27/files/specs/fpga/boards/arty_a7_integration.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/boards/arty_a7_integration.t27 rename to apps/website/public/t27/files/specs/fpga/boards/arty_a7_integration.t27 diff --git a/apps/website/public/t27/specs/fpga/boards/qmtech_a100t_integration.t27 b/apps/website/public/t27/files/specs/fpga/boards/qmtech_a100t_integration.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/boards/qmtech_a100t_integration.t27 rename to apps/website/public/t27/files/specs/fpga/boards/qmtech_a100t_integration.t27 diff --git a/apps/website/public/t27/specs/fpga/bootrom.t27 b/apps/website/public/t27/files/specs/fpga/bootrom.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/bootrom.t27 rename to apps/website/public/t27/files/specs/fpga/bootrom.t27 diff --git a/apps/website/public/t27/specs/fpga/bridge.t27 b/apps/website/public/t27/files/specs/fpga/bridge.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/bridge.t27 rename to apps/website/public/t27/files/specs/fpga/bridge.t27 diff --git a/apps/website/public/t27/specs/fpga/clock_domain.t27 b/apps/website/public/t27/files/specs/fpga/clock_domain.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/clock_domain.t27 rename to apps/website/public/t27/files/specs/fpga/clock_domain.t27 diff --git a/apps/website/public/t27/specs/fpga/crossopt.t27 b/apps/website/public/t27/files/specs/fpga/crossopt.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/crossopt.t27 rename to apps/website/public/t27/files/specs/fpga/crossopt.t27 diff --git a/apps/website/public/t27/specs/fpga/cts.t27 b/apps/website/public/t27/files/specs/fpga/cts.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/cts.t27 rename to apps/website/public/t27/files/specs/fpga/cts.t27 diff --git a/apps/website/public/t27/specs/fpga/dft.t27 b/apps/website/public/t27/files/specs/fpga/dft.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/dft.t27 rename to apps/website/public/t27/files/specs/fpga/dft.t27 diff --git a/apps/website/public/t27/specs/fpga/e2e_demo.t27 b/apps/website/public/t27/files/specs/fpga/e2e_demo.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/e2e_demo.t27 rename to apps/website/public/t27/files/specs/fpga/e2e_demo.t27 diff --git a/apps/website/public/t27/specs/fpga/fifo.t27 b/apps/website/public/t27/files/specs/fpga/fifo.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/fifo.t27 rename to apps/website/public/t27/files/specs/fpga/fifo.t27 diff --git a/apps/website/public/t27/specs/fpga/formal.t27 b/apps/website/public/t27/files/specs/fpga/formal.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/formal.t27 rename to apps/website/public/t27/files/specs/fpga/formal.t27 diff --git a/apps/website/public/t27/specs/fpga/gf16_accel.t27 b/apps/website/public/t27/files/specs/fpga/gf16_accel.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/gf16_accel.t27 rename to apps/website/public/t27/files/specs/fpga/gf16_accel.t27 diff --git a/apps/website/public/t27/specs/fpga/hir.t27 b/apps/website/public/t27/files/specs/fpga/hir.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/hir.t27 rename to apps/website/public/t27/files/specs/fpga/hir.t27 diff --git a/apps/website/public/t27/specs/fpga/hw_types.t27 b/apps/website/public/t27/files/specs/fpga/hw_types.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/hw_types.t27 rename to apps/website/public/t27/files/specs/fpga/hw_types.t27 diff --git a/apps/website/public/t27/specs/fpga/linker.t27 b/apps/website/public/t27/files/specs/fpga/linker.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/linker.t27 rename to apps/website/public/t27/files/specs/fpga/linker.t27 diff --git a/apps/website/public/t27/specs/fpga/mac.t27 b/apps/website/public/t27/files/specs/fpga/mac.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/mac.t27 rename to apps/website/public/t27/files/specs/fpga/mac.t27 diff --git a/apps/website/public/t27/specs/fpga/memory.t27 b/apps/website/public/t27/files/specs/fpga/memory.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/memory.t27 rename to apps/website/public/t27/files/specs/fpga/memory.t27 diff --git a/apps/website/public/t27/specs/fpga/partition.t27 b/apps/website/public/t27/files/specs/fpga/partition.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/partition.t27 rename to apps/website/public/t27/files/specs/fpga/partition.t27 diff --git a/apps/website/public/t27/specs/fpga/placement.t27 b/apps/website/public/t27/files/specs/fpga/placement.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/placement.t27 rename to apps/website/public/t27/files/specs/fpga/placement.t27 diff --git a/apps/website/public/t27/specs/fpga/power.t27 b/apps/website/public/t27/files/specs/fpga/power.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/power.t27 rename to apps/website/public/t27/files/specs/fpga/power.t27 diff --git a/apps/website/public/t27/specs/fpga/power_analysis.t27 b/apps/website/public/t27/files/specs/fpga/power_analysis.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/power_analysis.t27 rename to apps/website/public/t27/files/specs/fpga/power_analysis.t27 diff --git a/apps/website/public/t27/specs/fpga/router.t27 b/apps/website/public/t27/files/specs/fpga/router.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/router.t27 rename to apps/website/public/t27/files/specs/fpga/router.t27 diff --git a/apps/website/public/t27/specs/fpga/simulator.t27 b/apps/website/public/t27/files/specs/fpga/simulator.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/simulator.t27 rename to apps/website/public/t27/files/specs/fpga/simulator.t27 diff --git a/apps/website/public/t27/specs/fpga/spi.t27 b/apps/website/public/t27/files/specs/fpga/spi.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/spi.t27 rename to apps/website/public/t27/files/specs/fpga/spi.t27 diff --git a/apps/website/public/t27/specs/fpga/stdlib.t27 b/apps/website/public/t27/files/specs/fpga/stdlib.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/stdlib.t27 rename to apps/website/public/t27/files/specs/fpga/stdlib.t27 diff --git a/apps/website/public/t27/specs/fpga/ternary_isa.t27 b/apps/website/public/t27/files/specs/fpga/ternary_isa.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/ternary_isa.t27 rename to apps/website/public/t27/files/specs/fpga/ternary_isa.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench.t27 b/apps/website/public/t27/files/specs/fpga/testbench.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench.t27 rename to apps/website/public/t27/files/specs/fpga/testbench.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/apb_bridge_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/apb_bridge_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/apb_bridge_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/apb_bridge_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/assembler_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/assembler_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/assembler_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/assembler_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/axi4_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/axi4_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/axi4_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/axi4_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/bootrom_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/bootrom_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/bootrom_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/bootrom_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/bridge_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/bridge_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/bridge_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/bridge_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/clock_domain_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/clock_domain_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/clock_domain_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/clock_domain_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/cts_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/cts_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/cts_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/cts_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/dft_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/dft_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/dft_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/dft_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/fifo_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/fifo_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/fifo_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/fifo_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/formal_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/formal_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/formal_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/formal_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/gf16_accel_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/gf16_accel_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/gf16_accel_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/gf16_accel_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/hir_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/hir_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/hir_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/hir_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/integration_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/integration_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/integration_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/integration_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/linker_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/linker_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/linker_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/linker_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/mac_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/mac_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/mac_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/mac_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/memory_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/memory_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/memory_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/memory_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/partition_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/partition_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/partition_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/partition_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/placement_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/placement_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/placement_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/placement_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/power_analysis_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/power_analysis_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/power_analysis_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/power_analysis_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/power_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/power_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/power_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/power_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/router_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/router_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/router_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/router_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/simulator_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/simulator_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/simulator_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/simulator_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/spi_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/spi_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/spi_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/spi_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/stdlib_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/stdlib_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/stdlib_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/stdlib_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/ternary_isa_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/ternary_isa_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/ternary_isa_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/ternary_isa_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/timing_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/timing_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/timing_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/timing_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/top_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/top_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/top_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/top_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/uart_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/uart_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/uart_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/uart_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/vcd_conformance_compare_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/vcd_conformance_compare_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/vcd_conformance_compare_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/vcd_conformance_compare_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/testbench/vcd_trace_tb.t27 b/apps/website/public/t27/files/specs/fpga/testbench/vcd_trace_tb.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/testbench/vcd_trace_tb.t27 rename to apps/website/public/t27/files/specs/fpga/testbench/vcd_trace_tb.t27 diff --git a/apps/website/public/t27/specs/fpga/timing.t27 b/apps/website/public/t27/files/specs/fpga/timing.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/timing.t27 rename to apps/website/public/t27/files/specs/fpga/timing.t27 diff --git a/apps/website/public/t27/specs/fpga/top_level.t27 b/apps/website/public/t27/files/specs/fpga/top_level.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/top_level.t27 rename to apps/website/public/t27/files/specs/fpga/top_level.t27 diff --git a/apps/website/public/t27/specs/fpga/uart.t27 b/apps/website/public/t27/files/specs/fpga/uart.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/uart.t27 rename to apps/website/public/t27/files/specs/fpga/uart.t27 diff --git a/apps/website/public/t27/specs/fpga/vcd_conformance_compare.t27 b/apps/website/public/t27/files/specs/fpga/vcd_conformance_compare.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/vcd_conformance_compare.t27 rename to apps/website/public/t27/files/specs/fpga/vcd_conformance_compare.t27 diff --git a/apps/website/public/t27/specs/fpga/vcd_trace.t27 b/apps/website/public/t27/files/specs/fpga/vcd_trace.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/vcd_trace.t27 rename to apps/website/public/t27/files/specs/fpga/vcd_trace.t27 diff --git a/apps/website/public/t27/specs/fpga/verification/build_verify.t27 b/apps/website/public/t27/files/specs/fpga/verification/build_verify.t27 similarity index 100% rename from apps/website/public/t27/specs/fpga/verification/build_verify.t27 rename to apps/website/public/t27/files/specs/fpga/verification/build_verify.t27 diff --git a/apps/website/public/t27/specs/git/diff.t27 b/apps/website/public/t27/files/specs/git/diff.t27 similarity index 100% rename from apps/website/public/t27/specs/git/diff.t27 rename to apps/website/public/t27/files/specs/git/diff.t27 diff --git a/apps/website/public/t27/specs/git/operations.t27 b/apps/website/public/t27/files/specs/git/operations.t27 similarity index 100% rename from apps/website/public/t27/specs/git/operations.t27 rename to apps/website/public/t27/files/specs/git/operations.t27 diff --git a/apps/website/public/t27/specs/git/schema.t27 b/apps/website/public/t27/files/specs/git/schema.t27 similarity index 100% rename from apps/website/public/t27/specs/git/schema.t27 rename to apps/website/public/t27/files/specs/git/schema.t27 diff --git a/apps/website/public/t27/specs/git/status.t27 b/apps/website/public/t27/files/specs/git/status.t27 similarity index 100% rename from apps/website/public/t27/specs/git/status.t27 rename to apps/website/public/t27/files/specs/git/status.t27 diff --git a/apps/website/public/t27/specs/github/auth.t27 b/apps/website/public/t27/files/specs/github/auth.t27 similarity index 100% rename from apps/website/public/t27/specs/github/auth.t27 rename to apps/website/public/t27/files/specs/github/auth.t27 diff --git a/apps/website/public/t27/specs/github/comments.t27 b/apps/website/public/t27/files/specs/github/comments.t27 similarity index 100% rename from apps/website/public/t27/specs/github/comments.t27 rename to apps/website/public/t27/files/specs/github/comments.t27 diff --git a/apps/website/public/t27/specs/github/issues.t27 b/apps/website/public/t27/files/specs/github/issues.t27 similarity index 100% rename from apps/website/public/t27/specs/github/issues.t27 rename to apps/website/public/t27/files/specs/github/issues.t27 diff --git a/apps/website/public/t27/specs/github/prs.t27 b/apps/website/public/t27/files/specs/github/prs.t27 similarity index 100% rename from apps/website/public/t27/specs/github/prs.t27 rename to apps/website/public/t27/files/specs/github/prs.t27 diff --git a/apps/website/public/t27/specs/github/tests/e2e_full_flow.t27 b/apps/website/public/t27/files/specs/github/tests/e2e_full_flow.t27 similarity index 100% rename from apps/website/public/t27/specs/github/tests/e2e_full_flow.t27 rename to apps/website/public/t27/files/specs/github/tests/e2e_full_flow.t27 diff --git a/apps/website/public/t27/specs/graph/knowledge_graph.t27 b/apps/website/public/t27/files/specs/graph/knowledge_graph.t27 similarity index 100% rename from apps/website/public/t27/specs/graph/knowledge_graph.t27 rename to apps/website/public/t27/files/specs/graph/knowledge_graph.t27 diff --git a/apps/website/public/t27/specs/hslm/forward_pass.t27 b/apps/website/public/t27/files/specs/hslm/forward_pass.t27 similarity index 100% rename from apps/website/public/t27/specs/hslm/forward_pass.t27 rename to apps/website/public/t27/files/specs/hslm/forward_pass.t27 diff --git a/apps/website/public/t27/specs/interop/gf_cross_language.t27 b/apps/website/public/t27/files/specs/interop/gf_cross_language.t27 similarity index 100% rename from apps/website/public/t27/specs/interop/gf_cross_language.t27 rename to apps/website/public/t27/files/specs/interop/gf_cross_language.t27 diff --git a/apps/website/public/t27/specs/isa/registers.t27 b/apps/website/public/t27/files/specs/isa/registers.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/registers.t27 rename to apps/website/public/t27/files/specs/isa/registers.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_arithmetic.t27 b/apps/website/public/t27/files/specs/isa/ternary_arithmetic.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_arithmetic.t27 rename to apps/website/public/t27/files/specs/isa/ternary_arithmetic.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_bitwise.t27 b/apps/website/public/t27/files/specs/isa/ternary_bitwise.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_bitwise.t27 rename to apps/website/public/t27/files/specs/isa/ternary_bitwise.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_control_flow.t27 b/apps/website/public/t27/files/specs/isa/ternary_control_flow.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_control_flow.t27 rename to apps/website/public/t27/files/specs/isa/ternary_control_flow.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_deque.t27 b/apps/website/public/t27/files/specs/isa/ternary_deque.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_deque.t27 rename to apps/website/public/t27/files/specs/isa/ternary_deque.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_encoding.t27 b/apps/website/public/t27/files/specs/isa/ternary_encoding.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_encoding.t27 rename to apps/website/public/t27/files/specs/isa/ternary_encoding.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_gates.t27 b/apps/website/public/t27/files/specs/isa/ternary_gates.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_gates.t27 rename to apps/website/public/t27/files/specs/isa/ternary_gates.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_graph.t27 b/apps/website/public/t27/files/specs/isa/ternary_graph.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_graph.t27 rename to apps/website/public/t27/files/specs/isa/ternary_graph.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_hash.t27 b/apps/website/public/t27/files/specs/isa/ternary_hash.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_hash.t27 rename to apps/website/public/t27/files/specs/isa/ternary_hash.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_memory.t27 b/apps/website/public/t27/files/specs/isa/ternary_memory.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_memory.t27 rename to apps/website/public/t27/files/specs/isa/ternary_memory.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_pattern_matching.t27 b/apps/website/public/t27/files/specs/isa/ternary_pattern_matching.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_pattern_matching.t27 rename to apps/website/public/t27/files/specs/isa/ternary_pattern_matching.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_search.t27 b/apps/website/public/t27/files/specs/isa/ternary_search.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_search.t27 rename to apps/website/public/t27/files/specs/isa/ternary_search.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_set.t27 b/apps/website/public/t27/files/specs/isa/ternary_set.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_set.t27 rename to apps/website/public/t27/files/specs/isa/ternary_set.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_shift.t27 b/apps/website/public/t27/files/specs/isa/ternary_shift.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_shift.t27 rename to apps/website/public/t27/files/specs/isa/ternary_shift.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_sorting.t27 b/apps/website/public/t27/files/specs/isa/ternary_sorting.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_sorting.t27 rename to apps/website/public/t27/files/specs/isa/ternary_sorting.t27 diff --git a/apps/website/public/t27/specs/isa/ternary_tree.t27 b/apps/website/public/t27/files/specs/isa/ternary_tree.t27 similarity index 100% rename from apps/website/public/t27/specs/isa/ternary_tree.t27 rename to apps/website/public/t27/files/specs/isa/ternary_tree.t27 diff --git a/apps/website/public/t27/specs/jit/jit.t27 b/apps/website/public/t27/files/specs/jit/jit.t27 similarity index 100% rename from apps/website/public/t27/specs/jit/jit.t27 rename to apps/website/public/t27/files/specs/jit/jit.t27 diff --git a/apps/website/public/t27/specs/lsp/client.t27 b/apps/website/public/t27/files/specs/lsp/client.t27 similarity index 100% rename from apps/website/public/t27/specs/lsp/client.t27 rename to apps/website/public/t27/files/specs/lsp/client.t27 diff --git a/apps/website/public/t27/specs/lsp/language.t27 b/apps/website/public/t27/files/specs/lsp/language.t27 similarity index 100% rename from apps/website/public/t27/specs/lsp/language.t27 rename to apps/website/public/t27/files/specs/lsp/language.t27 diff --git a/apps/website/public/t27/specs/lsp/protocol.t27 b/apps/website/public/t27/files/specs/lsp/protocol.t27 similarity index 100% rename from apps/website/public/t27/specs/lsp/protocol.t27 rename to apps/website/public/t27/files/specs/lsp/protocol.t27 diff --git a/apps/website/public/t27/specs/lsp/schema.t27 b/apps/website/public/t27/files/specs/lsp/schema.t27 similarity index 100% rename from apps/website/public/t27/specs/lsp/schema.t27 rename to apps/website/public/t27/files/specs/lsp/schema.t27 diff --git a/apps/website/public/t27/specs/lsp/server.t27 b/apps/website/public/t27/files/specs/lsp/server.t27 similarity index 100% rename from apps/website/public/t27/specs/lsp/server.t27 rename to apps/website/public/t27/files/specs/lsp/server.t27 diff --git a/apps/website/public/t27/specs/math/constants.t27 b/apps/website/public/t27/files/specs/math/constants.t27 similarity index 100% rename from apps/website/public/t27/specs/math/constants.t27 rename to apps/website/public/t27/files/specs/math/constants.t27 diff --git a/apps/website/public/t27/specs/math/e8_lie_algebra.t27 b/apps/website/public/t27/files/specs/math/e8_lie_algebra.t27 similarity index 100% rename from apps/website/public/t27/specs/math/e8_lie_algebra.t27 rename to apps/website/public/t27/files/specs/math/e8_lie_algebra.t27 diff --git a/apps/website/public/t27/specs/math/gf_competitive.t27 b/apps/website/public/t27/files/specs/math/gf_competitive.t27 similarity index 100% rename from apps/website/public/t27/specs/math/gf_competitive.t27 rename to apps/website/public/t27/files/specs/math/gf_competitive.t27 diff --git a/apps/website/public/t27/specs/math/pellis_precision_verify.t27 b/apps/website/public/t27/files/specs/math/pellis_precision_verify.t27 similarity index 100% rename from apps/website/public/t27/specs/math/pellis_precision_verify.t27 rename to apps/website/public/t27/files/specs/math/pellis_precision_verify.t27 diff --git a/apps/website/public/t27/specs/math/phi_split_optimality.t27 b/apps/website/public/t27/files/specs/math/phi_split_optimality.t27 similarity index 100% rename from apps/website/public/t27/specs/math/phi_split_optimality.t27 rename to apps/website/public/t27/files/specs/math/phi_split_optimality.t27 diff --git a/apps/website/public/t27/specs/math/phi_universal_attractor.t27 b/apps/website/public/t27/files/specs/math/phi_universal_attractor.t27 similarity index 100% rename from apps/website/public/t27/specs/math/phi_universal_attractor.t27 rename to apps/website/public/t27/files/specs/math/phi_universal_attractor.t27 diff --git a/apps/website/public/t27/specs/math/property_test_template.t27 b/apps/website/public/t27/files/specs/math/property_test_template.t27 similarity index 100% rename from apps/website/public/t27/specs/math/property_test_template.t27 rename to apps/website/public/t27/files/specs/math/property_test_template.t27 diff --git a/apps/website/public/t27/specs/math/radix_economy.t27 b/apps/website/public/t27/files/specs/math/radix_economy.t27 similarity index 100% rename from apps/website/public/t27/specs/math/radix_economy.t27 rename to apps/website/public/t27/files/specs/math/radix_economy.t27 diff --git a/apps/website/public/t27/specs/math/sacred_physics.t27 b/apps/website/public/t27/files/specs/math/sacred_physics.t27 similarity index 100% rename from apps/website/public/t27/specs/math/sacred_physics.t27 rename to apps/website/public/t27/files/specs/math/sacred_physics.t27 diff --git a/apps/website/public/t27/specs/math/zamolodchikov_e8.t27 b/apps/website/public/t27/files/specs/math/zamolodchikov_e8.t27 similarity index 100% rename from apps/website/public/t27/specs/math/zamolodchikov_e8.t27 rename to apps/website/public/t27/files/specs/math/zamolodchikov_e8.t27 diff --git a/apps/website/public/t27/specs/memory/formula_embed.t27 b/apps/website/public/t27/files/specs/memory/formula_embed.t27 similarity index 100% rename from apps/website/public/t27/specs/memory/formula_embed.t27 rename to apps/website/public/t27/files/specs/memory/formula_embed.t27 diff --git a/apps/website/public/t27/specs/memory/memory_primitives.t27 b/apps/website/public/t27/files/specs/memory/memory_primitives.t27 similarity index 100% rename from apps/website/public/t27/specs/memory/memory_primitives.t27 rename to apps/website/public/t27/files/specs/memory/memory_primitives.t27 diff --git a/apps/website/public/t27/specs/memory/notebooklm.t27 b/apps/website/public/t27/files/specs/memory/notebooklm.t27 similarity index 100% rename from apps/website/public/t27/specs/memory/notebooklm.t27 rename to apps/website/public/t27/files/specs/memory/notebooklm.t27 diff --git a/apps/website/public/t27/specs/memory/semantic_search.t27 b/apps/website/public/t27/files/specs/memory/semantic_search.t27 similarity index 100% rename from apps/website/public/t27/specs/memory/semantic_search.t27 rename to apps/website/public/t27/files/specs/memory/semantic_search.t27 diff --git a/apps/website/public/t27/specs/ml/activation/elu_activation.t27 b/apps/website/public/t27/files/specs/ml/activation/elu_activation.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/activation/elu_activation.t27 rename to apps/website/public/t27/files/specs/ml/activation/elu_activation.t27 diff --git a/apps/website/public/t27/specs/ml/activation/gelu_activation.t27 b/apps/website/public/t27/files/specs/ml/activation/gelu_activation.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/activation/gelu_activation.t27 rename to apps/website/public/t27/files/specs/ml/activation/gelu_activation.t27 diff --git a/apps/website/public/t27/specs/ml/activation/gelu_approx_activation.t27 b/apps/website/public/t27/files/specs/ml/activation/gelu_approx_activation.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/activation/gelu_approx_activation.t27 rename to apps/website/public/t27/files/specs/ml/activation/gelu_approx_activation.t27 diff --git a/apps/website/public/t27/specs/ml/activation/leaky_relu_activation.t27 b/apps/website/public/t27/files/specs/ml/activation/leaky_relu_activation.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/activation/leaky_relu_activation.t27 rename to apps/website/public/t27/files/specs/ml/activation/leaky_relu_activation.t27 diff --git a/apps/website/public/t27/specs/ml/activation/relu_activation.t27 b/apps/website/public/t27/files/specs/ml/activation/relu_activation.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/activation/relu_activation.t27 rename to apps/website/public/t27/files/specs/ml/activation/relu_activation.t27 diff --git a/apps/website/public/t27/specs/ml/activation/sigmoid_activation.t27 b/apps/website/public/t27/files/specs/ml/activation/sigmoid_activation.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/activation/sigmoid_activation.t27 rename to apps/website/public/t27/files/specs/ml/activation/sigmoid_activation.t27 diff --git a/apps/website/public/t27/specs/ml/activation/silu_swish_activation.t27 b/apps/website/public/t27/files/specs/ml/activation/silu_swish_activation.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/activation/silu_swish_activation.t27 rename to apps/website/public/t27/files/specs/ml/activation/silu_swish_activation.t27 diff --git a/apps/website/public/t27/specs/ml/activation/silu_swish_vbt_activation.t27 b/apps/website/public/t27/files/specs/ml/activation/silu_swish_vbt_activation.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/activation/silu_swish_vbt_activation.t27 rename to apps/website/public/t27/files/specs/ml/activation/silu_swish_vbt_activation.t27 diff --git a/apps/website/public/t27/specs/ml/activation/softmax.t27 b/apps/website/public/t27/files/specs/ml/activation/softmax.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/activation/softmax.t27 rename to apps/website/public/t27/files/specs/ml/activation/softmax.t27 diff --git a/apps/website/public/t27/specs/ml/activation/tanh_activation.t27 b/apps/website/public/t27/files/specs/ml/activation/tanh_activation.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/activation/tanh_activation.t27 rename to apps/website/public/t27/files/specs/ml/activation/tanh_activation.t27 diff --git a/apps/website/public/t27/specs/ml/layers/avgpool2d_layer.t27 b/apps/website/public/t27/files/specs/ml/layers/avgpool2d_layer.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/layers/avgpool2d_layer.t27 rename to apps/website/public/t27/files/specs/ml/layers/avgpool2d_layer.t27 diff --git a/apps/website/public/t27/specs/ml/layers/batchnorm_layer.t27 b/apps/website/public/t27/files/specs/ml/layers/batchnorm_layer.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/layers/batchnorm_layer.t27 rename to apps/website/public/t27/files/specs/ml/layers/batchnorm_layer.t27 diff --git a/apps/website/public/t27/specs/ml/layers/conv2d_layer.t27 b/apps/website/public/t27/files/specs/ml/layers/conv2d_layer.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/layers/conv2d_layer.t27 rename to apps/website/public/t27/files/specs/ml/layers/conv2d_layer.t27 diff --git a/apps/website/public/t27/specs/ml/layers/dense_layer.t27 b/apps/website/public/t27/files/specs/ml/layers/dense_layer.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/layers/dense_layer.t27 rename to apps/website/public/t27/files/specs/ml/layers/dense_layer.t27 diff --git a/apps/website/public/t27/specs/ml/layers/dropout_layer.t27 b/apps/website/public/t27/files/specs/ml/layers/dropout_layer.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/layers/dropout_layer.t27 rename to apps/website/public/t27/files/specs/ml/layers/dropout_layer.t27 diff --git a/apps/website/public/t27/specs/ml/layers/embedding_layer.t27 b/apps/website/public/t27/files/specs/ml/layers/embedding_layer.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/layers/embedding_layer.t27 rename to apps/website/public/t27/files/specs/ml/layers/embedding_layer.t27 diff --git a/apps/website/public/t27/specs/ml/layers/flatten_layer.t27 b/apps/website/public/t27/files/specs/ml/layers/flatten_layer.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/layers/flatten_layer.t27 rename to apps/website/public/t27/files/specs/ml/layers/flatten_layer.t27 diff --git a/apps/website/public/t27/specs/ml/layers/layernorm_layer.t27 b/apps/website/public/t27/files/specs/ml/layers/layernorm_layer.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/layers/layernorm_layer.t27 rename to apps/website/public/t27/files/specs/ml/layers/layernorm_layer.t27 diff --git a/apps/website/public/t27/specs/ml/layers/maxpool2d_layer.t27 b/apps/website/public/t27/files/specs/ml/layers/maxpool2d_layer.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/layers/maxpool2d_layer.t27 rename to apps/website/public/t27/files/specs/ml/layers/maxpool2d_layer.t27 diff --git a/apps/website/public/t27/specs/ml/layers/residual_connection.t27 b/apps/website/public/t27/files/specs/ml/layers/residual_connection.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/layers/residual_connection.t27 rename to apps/website/public/t27/files/specs/ml/layers/residual_connection.t27 diff --git a/apps/website/public/t27/specs/ml/loss/binary_crossentropy_loss.t27 b/apps/website/public/t27/files/specs/ml/loss/binary_crossentropy_loss.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/loss/binary_crossentropy_loss.t27 rename to apps/website/public/t27/files/specs/ml/loss/binary_crossentropy_loss.t27 diff --git a/apps/website/public/t27/specs/ml/loss/contrastive_loss.t27 b/apps/website/public/t27/files/specs/ml/loss/contrastive_loss.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/loss/contrastive_loss.t27 rename to apps/website/public/t27/files/specs/ml/loss/contrastive_loss.t27 diff --git a/apps/website/public/t27/specs/ml/loss/cross_entropy_loss.t27 b/apps/website/public/t27/files/specs/ml/loss/cross_entropy_loss.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/loss/cross_entropy_loss.t27 rename to apps/website/public/t27/files/specs/ml/loss/cross_entropy_loss.t27 diff --git a/apps/website/public/t27/specs/ml/loss/huber_loss.t27 b/apps/website/public/t27/files/specs/ml/loss/huber_loss.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/loss/huber_loss.t27 rename to apps/website/public/t27/files/specs/ml/loss/huber_loss.t27 diff --git a/apps/website/public/t27/specs/ml/loss/kl_divergence.t27 b/apps/website/public/t27/files/specs/ml/loss/kl_divergence.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/loss/kl_divergence.t27 rename to apps/website/public/t27/files/specs/ml/loss/kl_divergence.t27 diff --git a/apps/website/public/t27/specs/ml/loss/mse_loss.t27 b/apps/website/public/t27/files/specs/ml/loss/mse_loss.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/loss/mse_loss.t27 rename to apps/website/public/t27/files/specs/ml/loss/mse_loss.t27 diff --git a/apps/website/public/t27/specs/ml/optimizer/adagrad.t27 b/apps/website/public/t27/files/specs/ml/optimizer/adagrad.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/optimizer/adagrad.t27 rename to apps/website/public/t27/files/specs/ml/optimizer/adagrad.t27 diff --git a/apps/website/public/t27/specs/ml/optimizer/adam.t27 b/apps/website/public/t27/files/specs/ml/optimizer/adam.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/optimizer/adam.t27 rename to apps/website/public/t27/files/specs/ml/optimizer/adam.t27 diff --git a/apps/website/public/t27/specs/ml/optimizer/adamw.t27 b/apps/website/public/t27/files/specs/ml/optimizer/adamw.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/optimizer/adamw.t27 rename to apps/website/public/t27/files/specs/ml/optimizer/adamw.t27 diff --git a/apps/website/public/t27/specs/ml/optimizer/lamb.t27 b/apps/website/public/t27/files/specs/ml/optimizer/lamb.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/optimizer/lamb.t27 rename to apps/website/public/t27/files/specs/ml/optimizer/lamb.t27 diff --git a/apps/website/public/t27/specs/ml/optimizer/lr_scheduler.t27 b/apps/website/public/t27/files/specs/ml/optimizer/lr_scheduler.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/optimizer/lr_scheduler.t27 rename to apps/website/public/t27/files/specs/ml/optimizer/lr_scheduler.t27 diff --git a/apps/website/public/t27/specs/ml/optimizer/rmsprop.t27 b/apps/website/public/t27/files/specs/ml/optimizer/rmsprop.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/optimizer/rmsprop.t27 rename to apps/website/public/t27/files/specs/ml/optimizer/rmsprop.t27 diff --git a/apps/website/public/t27/specs/ml/optimizer/sgd.t27 b/apps/website/public/t27/files/specs/ml/optimizer/sgd.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/optimizer/sgd.t27 rename to apps/website/public/t27/files/specs/ml/optimizer/sgd.t27 diff --git a/apps/website/public/t27/specs/ml/optimizer/sgd_momentum.t27 b/apps/website/public/t27/files/specs/ml/optimizer/sgd_momentum.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/optimizer/sgd_momentum.t27 rename to apps/website/public/t27/files/specs/ml/optimizer/sgd_momentum.t27 diff --git a/apps/website/public/t27/specs/ml/pathway/mlp.t27 b/apps/website/public/t27/files/specs/ml/pathway/mlp.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/pathway/mlp.t27 rename to apps/website/public/t27/files/specs/ml/pathway/mlp.t27 diff --git a/apps/website/public/t27/specs/ml/recurrent/attention_mechanism.t27 b/apps/website/public/t27/files/specs/ml/recurrent/attention_mechanism.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/recurrent/attention_mechanism.t27 rename to apps/website/public/t27/files/specs/ml/recurrent/attention_mechanism.t27 diff --git a/apps/website/public/t27/specs/ml/recurrent/bilstm.t27 b/apps/website/public/t27/files/specs/ml/recurrent/bilstm.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/recurrent/bilstm.t27 rename to apps/website/public/t27/files/specs/ml/recurrent/bilstm.t27 diff --git a/apps/website/public/t27/specs/ml/recurrent/gru_cell.t27 b/apps/website/public/t27/files/specs/ml/recurrent/gru_cell.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/recurrent/gru_cell.t27 rename to apps/website/public/t27/files/specs/ml/recurrent/gru_cell.t27 diff --git a/apps/website/public/t27/specs/ml/recurrent/lstm_cell.t27 b/apps/website/public/t27/files/specs/ml/recurrent/lstm_cell.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/recurrent/lstm_cell.t27 rename to apps/website/public/t27/files/specs/ml/recurrent/lstm_cell.t27 diff --git a/apps/website/public/t27/specs/ml/recurrent/lstm_single.t27 b/apps/website/public/t27/files/specs/ml/recurrent/lstm_single.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/recurrent/lstm_single.t27 rename to apps/website/public/t27/files/specs/ml/recurrent/lstm_single.t27 diff --git a/apps/website/public/t27/specs/ml/recurrent/rnn_cell.t27 b/apps/website/public/t27/files/specs/ml/recurrent/rnn_cell.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/recurrent/rnn_cell.t27 rename to apps/website/public/t27/files/specs/ml/recurrent/rnn_cell.t27 diff --git a/apps/website/public/t27/specs/ml/recurrent/self_attention.t27 b/apps/website/public/t27/files/specs/ml/recurrent/self_attention.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/recurrent/self_attention.t27 rename to apps/website/public/t27/files/specs/ml/recurrent/self_attention.t27 diff --git a/apps/website/public/t27/specs/ml/recurrent/seq2seq.t27 b/apps/website/public/t27/files/specs/ml/recurrent/seq2seq.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/recurrent/seq2seq.t27 rename to apps/website/public/t27/files/specs/ml/recurrent/seq2seq.t27 diff --git a/apps/website/public/t27/specs/ml/rl/advantage_estimator.t27 b/apps/website/public/t27/files/specs/ml/rl/advantage_estimator.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/rl/advantage_estimator.t27 rename to apps/website/public/t27/files/specs/ml/rl/advantage_estimator.t27 diff --git a/apps/website/public/t27/specs/ml/rl/dqn.t27 b/apps/website/public/t27/files/specs/ml/rl/dqn.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/rl/dqn.t27 rename to apps/website/public/t27/files/specs/ml/rl/dqn.t27 diff --git a/apps/website/public/t27/specs/ml/rl/dqn_target_network.t27 b/apps/website/public/t27/files/specs/ml/rl/dqn_target_network.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/rl/dqn_target_network.t27 rename to apps/website/public/t27/files/specs/ml/rl/dqn_target_network.t27 diff --git a/apps/website/public/t27/specs/ml/rl/ppo_actor.t27 b/apps/website/public/t27/files/specs/ml/rl/ppo_actor.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/rl/ppo_actor.t27 rename to apps/website/public/t27/files/specs/ml/rl/ppo_actor.t27 diff --git a/apps/website/public/t27/specs/ml/rl/ppo_clip_loss.t27 b/apps/website/public/t27/files/specs/ml/rl/ppo_clip_loss.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/rl/ppo_clip_loss.t27 rename to apps/website/public/t27/files/specs/ml/rl/ppo_clip_loss.t27 diff --git a/apps/website/public/t27/specs/ml/rl/ppo_critic.t27 b/apps/website/public/t27/files/specs/ml/rl/ppo_critic.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/rl/ppo_critic.t27 rename to apps/website/public/t27/files/specs/ml/rl/ppo_critic.t27 diff --git a/apps/website/public/t27/specs/ml/rl/sac_actor.t27 b/apps/website/public/t27/files/specs/ml/rl/sac_actor.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/rl/sac_actor.t27 rename to apps/website/public/t27/files/specs/ml/rl/sac_actor.t27 diff --git a/apps/website/public/t27/specs/ml/rl/sac_critic.t27 b/apps/website/public/t27/files/specs/ml/rl/sac_critic.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/rl/sac_critic.t27 rename to apps/website/public/t27/files/specs/ml/rl/sac_critic.t27 diff --git a/apps/website/public/t27/specs/ml/transformer/encoder_block.t27 b/apps/website/public/t27/files/specs/ml/transformer/encoder_block.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/transformer/encoder_block.t27 rename to apps/website/public/t27/files/specs/ml/transformer/encoder_block.t27 diff --git a/apps/website/public/t27/specs/ml/transformer/feed_forward.t27 b/apps/website/public/t27/files/specs/ml/transformer/feed_forward.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/transformer/feed_forward.t27 rename to apps/website/public/t27/files/specs/ml/transformer/feed_forward.t27 diff --git a/apps/website/public/t27/specs/ml/transformer/feed_forward_network.t27 b/apps/website/public/t27/files/specs/ml/transformer/feed_forward_network.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/transformer/feed_forward_network.t27 rename to apps/website/public/t27/files/specs/ml/transformer/feed_forward_network.t27 diff --git a/apps/website/public/t27/specs/ml/transformer/mha_block.t27 b/apps/website/public/t27/files/specs/ml/transformer/mha_block.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/transformer/mha_block.t27 rename to apps/website/public/t27/files/specs/ml/transformer/mha_block.t27 diff --git a/apps/website/public/t27/specs/ml/transformer/multi_head_attention.t27 b/apps/website/public/t27/files/specs/ml/transformer/multi_head_attention.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/transformer/multi_head_attention.t27 rename to apps/website/public/t27/files/specs/ml/transformer/multi_head_attention.t27 diff --git a/apps/website/public/t27/specs/ml/transformer/multi_head_attn.t27 b/apps/website/public/t27/files/specs/ml/transformer/multi_head_attn.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/transformer/multi_head_attn.t27 rename to apps/website/public/t27/files/specs/ml/transformer/multi_head_attn.t27 diff --git a/apps/website/public/t27/specs/ml/transformer/norm.t27 b/apps/website/public/t27/files/specs/ml/transformer/norm.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/transformer/norm.t27 rename to apps/website/public/t27/files/specs/ml/transformer/norm.t27 diff --git a/apps/website/public/t27/specs/ml/transformer/positional_enc.t27 b/apps/website/public/t27/files/specs/ml/transformer/positional_enc.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/transformer/positional_enc.t27 rename to apps/website/public/t27/files/specs/ml/transformer/positional_enc.t27 diff --git a/apps/website/public/t27/specs/ml/transformer/positional_encoding.t27 b/apps/website/public/t27/files/specs/ml/transformer/positional_encoding.t27 similarity index 100% rename from apps/website/public/t27/specs/ml/transformer/positional_encoding.t27 rename to apps/website/public/t27/files/specs/ml/transformer/positional_encoding.t27 diff --git a/apps/website/public/t27/specs/neural/forward_pass.t27 b/apps/website/public/t27/files/specs/neural/forward_pass.t27 similarity index 100% rename from apps/website/public/t27/specs/neural/forward_pass.t27 rename to apps/website/public/t27/files/specs/neural/forward_pass.t27 diff --git a/apps/website/public/t27/specs/nn/attention.t27 b/apps/website/public/t27/files/specs/nn/attention.t27 similarity index 100% rename from apps/website/public/t27/specs/nn/attention.t27 rename to apps/website/public/t27/files/specs/nn/attention.t27 diff --git a/apps/website/public/t27/specs/nn/hslm.t27 b/apps/website/public/t27/files/specs/nn/hslm.t27 similarity index 100% rename from apps/website/public/t27/specs/nn/hslm.t27 rename to apps/website/public/t27/files/specs/nn/hslm.t27 diff --git a/apps/website/public/t27/specs/nn/phi_rope.t27 b/apps/website/public/t27/files/specs/nn/phi_rope.t27 similarity index 100% rename from apps/website/public/t27/specs/nn/phi_rope.t27 rename to apps/website/public/t27/files/specs/nn/phi_rope.t27 diff --git a/apps/website/public/t27/specs/nn/sacred_attention.t27 b/apps/website/public/t27/files/specs/nn/sacred_attention.t27 similarity index 100% rename from apps/website/public/t27/specs/nn/sacred_attention.t27 rename to apps/website/public/t27/files/specs/nn/sacred_attention.t27 diff --git a/apps/website/public/t27/specs/numeric/bigint.t27 b/apps/website/public/t27/files/specs/numeric/bigint.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/bigint.t27 rename to apps/website/public/t27/files/specs/numeric/bigint.t27 diff --git a/apps/website/public/t27/specs/numeric/formats.t27 b/apps/website/public/t27/files/specs/numeric/formats.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/formats.t27 rename to apps/website/public/t27/files/specs/numeric/formats.t27 diff --git a/apps/website/public/t27/specs/numeric/gf12.t27 b/apps/website/public/t27/files/specs/numeric/gf12.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/gf12.t27 rename to apps/website/public/t27/files/specs/numeric/gf12.t27 diff --git a/apps/website/public/t27/specs/numeric/gf16.t27 b/apps/website/public/t27/files/specs/numeric/gf16.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/gf16.t27 rename to apps/website/public/t27/files/specs/numeric/gf16.t27 diff --git a/apps/website/public/t27/specs/numeric/gf20.t27 b/apps/website/public/t27/files/specs/numeric/gf20.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/gf20.t27 rename to apps/website/public/t27/files/specs/numeric/gf20.t27 diff --git a/apps/website/public/t27/specs/numeric/gf24.t27 b/apps/website/public/t27/files/specs/numeric/gf24.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/gf24.t27 rename to apps/website/public/t27/files/specs/numeric/gf24.t27 diff --git a/apps/website/public/t27/specs/numeric/gf32.t27 b/apps/website/public/t27/files/specs/numeric/gf32.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/gf32.t27 rename to apps/website/public/t27/files/specs/numeric/gf32.t27 diff --git a/apps/website/public/t27/specs/numeric/gf4.t27 b/apps/website/public/t27/files/specs/numeric/gf4.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/gf4.t27 rename to apps/website/public/t27/files/specs/numeric/gf4.t27 diff --git a/apps/website/public/t27/specs/numeric/gf8.t27 b/apps/website/public/t27/files/specs/numeric/gf8.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/gf8.t27 rename to apps/website/public/t27/files/specs/numeric/gf8.t27 diff --git a/apps/website/public/t27/specs/numeric/gf_competitive.t27 b/apps/website/public/t27/files/specs/numeric/gf_competitive.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/gf_competitive.t27 rename to apps/website/public/t27/files/specs/numeric/gf_competitive.t27 diff --git a/apps/website/public/t27/specs/numeric/goldenfloat_family.t27 b/apps/website/public/t27/files/specs/numeric/goldenfloat_family.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/goldenfloat_family.t27 rename to apps/website/public/t27/files/specs/numeric/goldenfloat_family.t27 diff --git a/apps/website/public/t27/specs/numeric/pellis_verify.t27 b/apps/website/public/t27/files/specs/numeric/pellis_verify.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/pellis_verify.t27 rename to apps/website/public/t27/files/specs/numeric/pellis_verify.t27 diff --git a/apps/website/public/t27/specs/numeric/phi_ratio.t27 b/apps/website/public/t27/files/specs/numeric/phi_ratio.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/phi_ratio.t27 rename to apps/website/public/t27/files/specs/numeric/phi_ratio.t27 diff --git a/apps/website/public/t27/specs/numeric/requant_boundary.t27 b/apps/website/public/t27/files/specs/numeric/requant_boundary.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/requant_boundary.t27 rename to apps/website/public/t27/files/specs/numeric/requant_boundary.t27 diff --git a/apps/website/public/t27/specs/numeric/tf3.t27 b/apps/website/public/t27/files/specs/numeric/tf3.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/tf3.t27 rename to apps/website/public/t27/files/specs/numeric/tf3.t27 diff --git a/apps/website/public/t27/specs/numeric/trinity_numeric_surface.t27 b/apps/website/public/t27/files/specs/numeric/trinity_numeric_surface.t27 similarity index 100% rename from apps/website/public/t27/specs/numeric/trinity_numeric_surface.t27 rename to apps/website/public/t27/files/specs/numeric/trinity_numeric_surface.t27 diff --git a/apps/website/public/t27/specs/physics/chimera_best_gamma.t27 b/apps/website/public/t27/files/specs/physics/chimera_best_gamma.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/chimera_best_gamma.t27 rename to apps/website/public/t27/files/specs/physics/chimera_best_gamma.t27 diff --git a/apps/website/public/t27/specs/physics/e8_lqg_bridge.t27 b/apps/website/public/t27/files/specs/physics/e8_lqg_bridge.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/e8_lqg_bridge.t27 rename to apps/website/public/t27/files/specs/physics/e8_lqg_bridge.t27 diff --git a/apps/website/public/t27/specs/physics/formula_discovery.t27 b/apps/website/public/t27/files/specs/physics/formula_discovery.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/formula_discovery.t27 rename to apps/website/public/t27/files/specs/physics/formula_discovery.t27 diff --git a/apps/website/public/t27/specs/physics/formula_registry.t27 b/apps/website/public/t27/files/specs/physics/formula_registry.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/formula_registry.t27 rename to apps/website/public/t27/files/specs/physics/formula_registry.t27 diff --git a/apps/website/public/t27/specs/physics/gamma-conflict.t27 b/apps/website/public/t27/files/specs/physics/gamma-conflict.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/gamma-conflict.t27 rename to apps/website/public/t27/files/specs/physics/gamma-conflict.t27 diff --git a/apps/website/public/t27/specs/physics/gamma_conjecture.t27 b/apps/website/public/t27/files/specs/physics/gamma_conjecture.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/gamma_conjecture.t27 rename to apps/website/public/t27/files/specs/physics/gamma_conjecture.t27 diff --git a/apps/website/public/t27/specs/physics/gi1_analysis.t27 b/apps/website/public/t27/files/specs/physics/gi1_analysis.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/gi1_analysis.t27 rename to apps/website/public/t27/files/specs/physics/gi1_analysis.t27 diff --git a/apps/website/public/t27/specs/physics/hslm_benchmark.t27 b/apps/website/public/t27/files/specs/physics/hslm_benchmark.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/hslm_benchmark.t27 rename to apps/website/public/t27/files/specs/physics/hslm_benchmark.t27 diff --git a/apps/website/public/t27/specs/physics/lqg_cs_bridge.t27 b/apps/website/public/t27/files/specs/physics/lqg_cs_bridge.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/lqg_cs_bridge.t27 rename to apps/website/public/t27/files/specs/physics/lqg_cs_bridge.t27 diff --git a/apps/website/public/t27/specs/physics/lqg_entropy.t27 b/apps/website/public/t27/files/specs/physics/lqg_entropy.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/lqg_entropy.t27 rename to apps/website/public/t27/files/specs/physics/lqg_entropy.t27 diff --git a/apps/website/public/t27/specs/physics/p2_brain_physics.t27 b/apps/website/public/t27/files/specs/physics/p2_brain_physics.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/p2_brain_physics.t27 rename to apps/website/public/t27/files/specs/physics/p2_brain_physics.t27 diff --git a/apps/website/public/t27/specs/physics/pellis-formulas.t27 b/apps/website/public/t27/files/specs/physics/pellis-formulas.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/pellis-formulas.t27 rename to apps/website/public/t27/files/specs/physics/pellis-formulas.t27 diff --git a/apps/website/public/t27/specs/physics/quantum.t27 b/apps/website/public/t27/files/specs/physics/quantum.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/quantum.t27 rename to apps/website/public/t27/files/specs/physics/quantum.t27 diff --git a/apps/website/public/t27/specs/physics/sacred_verification.t27 b/apps/website/public/t27/files/specs/physics/sacred_verification.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/sacred_verification.t27 rename to apps/website/public/t27/files/specs/physics/sacred_verification.t27 diff --git a/apps/website/public/t27/specs/physics/su2_chern_simons.t27 b/apps/website/public/t27/files/specs/physics/su2_chern_simons.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/su2_chern_simons.t27 rename to apps/website/public/t27/files/specs/physics/su2_chern_simons.t27 diff --git a/apps/website/public/t27/specs/physics/zamolodchikov_4d_conjecture.t27 b/apps/website/public/t27/files/specs/physics/zamolodchikov_4d_conjecture.t27 similarity index 100% rename from apps/website/public/t27/specs/physics/zamolodchikov_4d_conjecture.t27 rename to apps/website/public/t27/files/specs/physics/zamolodchikov_4d_conjecture.t27 diff --git a/apps/website/public/t27/specs/pins/emitter_xdc.t27 b/apps/website/public/t27/files/specs/pins/emitter_xdc.t27 similarity index 100% rename from apps/website/public/t27/specs/pins/emitter_xdc.t27 rename to apps/website/public/t27/files/specs/pins/emitter_xdc.t27 diff --git a/apps/website/public/t27/specs/pins/ir.t27 b/apps/website/public/t27/files/specs/pins/ir.t27 similarity index 100% rename from apps/website/public/t27/specs/pins/ir.t27 rename to apps/website/public/t27/files/specs/pins/ir.t27 diff --git a/apps/website/public/t27/specs/pins/parser.t27 b/apps/website/public/t27/files/specs/pins/parser.t27 similarity index 100% rename from apps/website/public/t27/specs/pins/parser.t27 rename to apps/website/public/t27/files/specs/pins/parser.t27 diff --git a/apps/website/public/t27/specs/pipeline/benchmarks.t27 b/apps/website/public/t27/files/specs/pipeline/benchmarks.t27 similarity index 100% rename from apps/website/public/t27/specs/pipeline/benchmarks.t27 rename to apps/website/public/t27/files/specs/pipeline/benchmarks.t27 diff --git a/apps/website/public/t27/specs/pipeline/e2e_test.t27 b/apps/website/public/t27/files/specs/pipeline/e2e_test.t27 similarity index 100% rename from apps/website/public/t27/specs/pipeline/e2e_test.t27 rename to apps/website/public/t27/files/specs/pipeline/e2e_test.t27 diff --git a/apps/website/public/t27/specs/pipeline/experience_save.t27 b/apps/website/public/t27/files/specs/pipeline/experience_save.t27 similarity index 100% rename from apps/website/public/t27/specs/pipeline/experience_save.t27 rename to apps/website/public/t27/files/specs/pipeline/experience_save.t27 diff --git a/apps/website/public/t27/specs/portable/relay_observer.t27 b/apps/website/public/t27/files/specs/portable/relay_observer.t27 similarity index 100% rename from apps/website/public/t27/specs/portable/relay_observer.t27 rename to apps/website/public/t27/files/specs/portable/relay_observer.t27 diff --git a/apps/website/public/t27/specs/provider/adapters.t27 b/apps/website/public/t27/files/specs/provider/adapters.t27 similarity index 100% rename from apps/website/public/t27/specs/provider/adapters.t27 rename to apps/website/public/t27/files/specs/provider/adapters.t27 diff --git a/apps/website/public/t27/specs/provider/schema.t27 b/apps/website/public/t27/files/specs/provider/schema.t27 similarity index 100% rename from apps/website/public/t27/specs/provider/schema.t27 rename to apps/website/public/t27/files/specs/provider/schema.t27 diff --git a/apps/website/public/t27/specs/provider/stream.t27 b/apps/website/public/t27/files/specs/provider/stream.t27 similarity index 100% rename from apps/website/public/t27/specs/provider/stream.t27 rename to apps/website/public/t27/files/specs/provider/stream.t27 diff --git a/apps/website/public/t27/specs/provider/transform.t27 b/apps/website/public/t27/files/specs/provider/transform.t27 similarity index 100% rename from apps/website/public/t27/specs/provider/transform.t27 rename to apps/website/public/t27/files/specs/provider/transform.t27 diff --git a/apps/website/public/t27/specs/queen/brain_summaries.t27 b/apps/website/public/t27/files/specs/queen/brain_summaries.t27 similarity index 100% rename from apps/website/public/t27/specs/queen/brain_summaries.t27 rename to apps/website/public/t27/files/specs/queen/brain_summaries.t27 diff --git a/apps/website/public/t27/specs/queen/lotus.t27 b/apps/website/public/t27/files/specs/queen/lotus.t27 similarity index 100% rename from apps/website/public/t27/specs/queen/lotus.t27 rename to apps/website/public/t27/files/specs/queen/lotus.t27 diff --git a/apps/website/public/t27/specs/queen/task_analysis.t27 b/apps/website/public/t27/files/specs/queen/task_analysis.t27 similarity index 100% rename from apps/website/public/t27/specs/queen/task_analysis.t27 rename to apps/website/public/t27/files/specs/queen/task_analysis.t27 diff --git a/apps/website/public/t27/specs/runtime/execute.t27 b/apps/website/public/t27/files/specs/runtime/execute.t27 similarity index 100% rename from apps/website/public/t27/specs/runtime/execute.t27 rename to apps/website/public/t27/files/specs/runtime/execute.t27 diff --git a/apps/website/public/t27/specs/runtime/instance.t27 b/apps/website/public/t27/files/specs/runtime/instance.t27 similarity index 100% rename from apps/website/public/t27/specs/runtime/instance.t27 rename to apps/website/public/t27/files/specs/runtime/instance.t27 diff --git a/apps/website/public/t27/specs/runtime/process.t27 b/apps/website/public/t27/files/specs/runtime/process.t27 similarity index 100% rename from apps/website/public/t27/specs/runtime/process.t27 rename to apps/website/public/t27/files/specs/runtime/process.t27 diff --git a/apps/website/public/t27/specs/sacred/cosmology.t27 b/apps/website/public/t27/files/specs/sacred/cosmology.t27 similarity index 100% rename from apps/website/public/t27/specs/sacred/cosmology.t27 rename to apps/website/public/t27/files/specs/sacred/cosmology.t27 diff --git a/apps/website/public/t27/specs/sacred/dark_matter.t27 b/apps/website/public/t27/files/specs/sacred/dark_matter.t27 similarity index 100% rename from apps/website/public/t27/specs/sacred/dark_matter.t27 rename to apps/website/public/t27/files/specs/sacred/dark_matter.t27 diff --git a/apps/website/public/t27/specs/sacred/gravity.t27 b/apps/website/public/t27/files/specs/sacred/gravity.t27 similarity index 100% rename from apps/website/public/t27/specs/sacred/gravity.t27 rename to apps/website/public/t27/files/specs/sacred/gravity.t27 diff --git a/apps/website/public/t27/specs/sacred/monopoles.t27 b/apps/website/public/t27/files/specs/sacred/monopoles.t27 similarity index 100% rename from apps/website/public/t27/specs/sacred/monopoles.t27 rename to apps/website/public/t27/files/specs/sacred/monopoles.t27 diff --git a/apps/website/public/t27/specs/sacred/quantum.t27 b/apps/website/public/t27/files/specs/sacred/quantum.t27 similarity index 100% rename from apps/website/public/t27/specs/sacred/quantum.t27 rename to apps/website/public/t27/files/specs/sacred/quantum.t27 diff --git a/apps/website/public/t27/specs/sacred/quantum_gravity.t27 b/apps/website/public/t27/files/specs/sacred/quantum_gravity.t27 similarity index 100% rename from apps/website/public/t27/specs/sacred/quantum_gravity.t27 rename to apps/website/public/t27/files/specs/sacred/quantum_gravity.t27 diff --git a/apps/website/public/t27/specs/sacred/sacred_constants.t27 b/apps/website/public/t27/files/specs/sacred/sacred_constants.t27 similarity index 100% rename from apps/website/public/t27/specs/sacred/sacred_constants.t27 rename to apps/website/public/t27/files/specs/sacred/sacred_constants.t27 diff --git a/apps/website/public/t27/specs/sacred/sacred_governance.t27 b/apps/website/public/t27/files/specs/sacred/sacred_governance.t27 similarity index 100% rename from apps/website/public/t27/specs/sacred/sacred_governance.t27 rename to apps/website/public/t27/files/specs/sacred/sacred_governance.t27 diff --git a/apps/website/public/t27/specs/sacred/sacred_identity.t27 b/apps/website/public/t27/files/specs/sacred/sacred_identity.t27 similarity index 100% rename from apps/website/public/t27/specs/sacred/sacred_identity.t27 rename to apps/website/public/t27/files/specs/sacred/sacred_identity.t27 diff --git a/apps/website/public/t27/specs/sacred/superconductivity.t27 b/apps/website/public/t27/files/specs/sacred/superconductivity.t27 similarity index 100% rename from apps/website/public/t27/specs/sacred/superconductivity.t27 rename to apps/website/public/t27/files/specs/sacred/superconductivity.t27 diff --git a/apps/website/public/t27/specs/sandbox/health.t27 b/apps/website/public/t27/files/specs/sandbox/health.t27 similarity index 100% rename from apps/website/public/t27/specs/sandbox/health.t27 rename to apps/website/public/t27/files/specs/sandbox/health.t27 diff --git a/apps/website/public/t27/specs/sandbox/https_enforce.t27 b/apps/website/public/t27/files/specs/sandbox/https_enforce.t27 similarity index 100% rename from apps/website/public/t27/specs/sandbox/https_enforce.t27 rename to apps/website/public/t27/files/specs/sandbox/https_enforce.t27 diff --git a/apps/website/public/t27/specs/sandbox/modules.t27 b/apps/website/public/t27/files/specs/sandbox/modules.t27 similarity index 100% rename from apps/website/public/t27/specs/sandbox/modules.t27 rename to apps/website/public/t27/files/specs/sandbox/modules.t27 diff --git a/apps/website/public/t27/specs/sandbox/orphan_detection.t27 b/apps/website/public/t27/files/specs/sandbox/orphan_detection.t27 similarity index 100% rename from apps/website/public/t27/specs/sandbox/orphan_detection.t27 rename to apps/website/public/t27/files/specs/sandbox/orphan_detection.t27 diff --git a/apps/website/public/t27/specs/sandbox/session_timeout.t27 b/apps/website/public/t27/files/specs/sandbox/session_timeout.t27 similarity index 100% rename from apps/website/public/t27/specs/sandbox/session_timeout.t27 rename to apps/website/public/t27/files/specs/sandbox/session_timeout.t27 diff --git a/apps/website/public/t27/specs/server/agent-runner.t27 b/apps/website/public/t27/files/specs/server/agent-runner.t27 similarity index 100% rename from apps/website/public/t27/specs/server/agent-runner.t27 rename to apps/website/public/t27/files/specs/server/agent-runner.t27 diff --git a/apps/website/public/t27/specs/server/api.t27 b/apps/website/public/t27/files/specs/server/api.t27 similarity index 100% rename from apps/website/public/t27/specs/server/api.t27 rename to apps/website/public/t27/files/specs/server/api.t27 diff --git a/apps/website/public/t27/specs/server/http.t27 b/apps/website/public/t27/files/specs/server/http.t27 similarity index 100% rename from apps/website/public/t27/specs/server/http.t27 rename to apps/website/public/t27/files/specs/server/http.t27 diff --git a/apps/website/public/t27/specs/server/mdns.t27 b/apps/website/public/t27/files/specs/server/mdns.t27 similarity index 100% rename from apps/website/public/t27/specs/server/mdns.t27 rename to apps/website/public/t27/files/specs/server/mdns.t27 diff --git a/apps/website/public/t27/specs/server/project.t27 b/apps/website/public/t27/files/specs/server/project.t27 similarity index 100% rename from apps/website/public/t27/specs/server/project.t27 rename to apps/website/public/t27/files/specs/server/project.t27 diff --git a/apps/website/public/t27/specs/server/provider.t27 b/apps/website/public/t27/files/specs/server/provider.t27 similarity index 100% rename from apps/website/public/t27/specs/server/provider.t27 rename to apps/website/public/t27/files/specs/server/provider.t27 diff --git a/apps/website/public/t27/specs/server/router.t27 b/apps/website/public/t27/files/specs/server/router.t27 similarity index 100% rename from apps/website/public/t27/specs/server/router.t27 rename to apps/website/public/t27/files/specs/server/router.t27 diff --git a/apps/website/public/t27/specs/server/routes.t27 b/apps/website/public/t27/files/specs/server/routes.t27 similarity index 100% rename from apps/website/public/t27/specs/server/routes.t27 rename to apps/website/public/t27/files/specs/server/routes.t27 diff --git a/apps/website/public/t27/specs/server/session.t27 b/apps/website/public/t27/files/specs/server/session.t27 similarity index 100% rename from apps/website/public/t27/specs/server/session.t27 rename to apps/website/public/t27/files/specs/server/session.t27 diff --git a/apps/website/public/t27/specs/server/sse.t27 b/apps/website/public/t27/files/specs/server/sse.t27 similarity index 100% rename from apps/website/public/t27/specs/server/sse.t27 rename to apps/website/public/t27/files/specs/server/sse.t27 diff --git a/apps/website/public/t27/specs/server/vm.t27 b/apps/website/public/t27/files/specs/server/vm.t27 similarity index 100% rename from apps/website/public/t27/specs/server/vm.t27 rename to apps/website/public/t27/files/specs/server/vm.t27 diff --git a/apps/website/public/t27/specs/shell/environment.t27 b/apps/website/public/t27/files/specs/shell/environment.t27 similarity index 100% rename from apps/website/public/t27/specs/shell/environment.t27 rename to apps/website/public/t27/files/specs/shell/environment.t27 diff --git a/apps/website/public/t27/specs/shell/process.t27 b/apps/website/public/t27/files/specs/shell/process.t27 similarity index 100% rename from apps/website/public/t27/specs/shell/process.t27 rename to apps/website/public/t27/files/specs/shell/process.t27 diff --git a/apps/website/public/t27/specs/shell/schema.t27 b/apps/website/public/t27/files/specs/shell/schema.t27 similarity index 100% rename from apps/website/public/t27/specs/shell/schema.t27 rename to apps/website/public/t27/files/specs/shell/schema.t27 diff --git a/apps/website/public/t27/specs/storage/kv.t27 b/apps/website/public/t27/files/specs/storage/kv.t27 similarity index 100% rename from apps/website/public/t27/specs/storage/kv.t27 rename to apps/website/public/t27/files/specs/storage/kv.t27 diff --git a/apps/website/public/t27/specs/storage/lock.t27 b/apps/website/public/t27/files/specs/storage/lock.t27 similarity index 100% rename from apps/website/public/t27/specs/storage/lock.t27 rename to apps/website/public/t27/files/specs/storage/lock.t27 diff --git a/apps/website/public/t27/specs/storage/migrate.t27 b/apps/website/public/t27/files/specs/storage/migrate.t27 similarity index 100% rename from apps/website/public/t27/specs/storage/migrate.t27 rename to apps/website/public/t27/files/specs/storage/migrate.t27 diff --git a/apps/website/public/t27/specs/storage/schema.t27 b/apps/website/public/t27/files/specs/storage/schema.t27 similarity index 100% rename from apps/website/public/t27/specs/storage/schema.t27 rename to apps/website/public/t27/files/specs/storage/schema.t27 diff --git a/apps/website/public/t27/specs/sync/index.t27 b/apps/website/public/t27/files/specs/sync/index.t27 similarity index 100% rename from apps/website/public/t27/specs/sync/index.t27 rename to apps/website/public/t27/files/specs/sync/index.t27 diff --git a/apps/website/public/t27/specs/sync/schema.t27 b/apps/website/public/t27/files/specs/sync/schema.t27 similarity index 100% rename from apps/website/public/t27/specs/sync/schema.t27 rename to apps/website/public/t27/files/specs/sync/schema.t27 diff --git a/apps/website/public/t27/specs/ternary/bigint.t27 b/apps/website/public/t27/files/specs/ternary/bigint.t27 similarity index 100% rename from apps/website/public/t27/specs/ternary/bigint.t27 rename to apps/website/public/t27/files/specs/ternary/bigint.t27 diff --git a/apps/website/public/t27/specs/ternary/hybrid_arithmetic.t27 b/apps/website/public/t27/files/specs/ternary/hybrid_arithmetic.t27 similarity index 100% rename from apps/website/public/t27/specs/ternary/hybrid_arithmetic.t27 rename to apps/website/public/t27/files/specs/ternary/hybrid_arithmetic.t27 diff --git a/apps/website/public/t27/specs/ternary/hybrid_bigint.t27 b/apps/website/public/t27/files/specs/ternary/hybrid_bigint.t27 similarity index 100% rename from apps/website/public/t27/specs/ternary/hybrid_bigint.t27 rename to apps/website/public/t27/files/specs/ternary/hybrid_bigint.t27 diff --git a/apps/website/public/t27/specs/ternary/packed_trit.t27 b/apps/website/public/t27/files/specs/ternary/packed_trit.t27 similarity index 100% rename from apps/website/public/t27/specs/ternary/packed_trit.t27 rename to apps/website/public/t27/files/specs/ternary/packed_trit.t27 diff --git a/apps/website/public/t27/specs/test_framework/core.t27 b/apps/website/public/t27/files/specs/test_framework/core.t27 similarity index 100% rename from apps/website/public/t27/specs/test_framework/core.t27 rename to apps/website/public/t27/files/specs/test_framework/core.t27 diff --git a/apps/website/public/t27/specs/test_framework/graph_drift_detection.t27 b/apps/website/public/t27/files/specs/test_framework/graph_drift_detection.t27 similarity index 100% rename from apps/website/public/t27/specs/test_framework/graph_drift_detection.t27 rename to apps/website/public/t27/files/specs/test_framework/graph_drift_detection.t27 diff --git a/apps/website/public/t27/specs/test_framework/property_test_template.t27 b/apps/website/public/t27/files/specs/test_framework/property_test_template.t27 similarity index 100% rename from apps/website/public/t27/specs/test_framework/property_test_template.t27 rename to apps/website/public/t27/files/specs/test_framework/property_test_template.t27 diff --git a/apps/website/public/t27/specs/test_framework/runner.t27 b/apps/website/public/t27/files/specs/test_framework/runner.t27 similarity index 100% rename from apps/website/public/t27/specs/test_framework/runner.t27 rename to apps/website/public/t27/files/specs/test_framework/runner.t27 diff --git a/apps/website/public/t27/specs/test_framework/verilog_bench_harness.t27 b/apps/website/public/t27/files/specs/test_framework/verilog_bench_harness.t27 similarity index 100% rename from apps/website/public/t27/specs/test_framework/verilog_bench_harness.t27 rename to apps/website/public/t27/files/specs/test_framework/verilog_bench_harness.t27 diff --git a/apps/website/public/t27/specs/tools/registry.t27 b/apps/website/public/t27/files/specs/tools/registry.t27 similarity index 100% rename from apps/website/public/t27/specs/tools/registry.t27 rename to apps/website/public/t27/files/specs/tools/registry.t27 diff --git a/apps/website/public/t27/specs/tools/schema.t27 b/apps/website/public/t27/files/specs/tools/schema.t27 similarity index 100% rename from apps/website/public/t27/specs/tools/schema.t27 rename to apps/website/public/t27/files/specs/tools/schema.t27 diff --git a/apps/website/public/t27/specs/tools/tri_to_t27_converter.t27 b/apps/website/public/t27/files/specs/tools/tri_to_t27_converter.t27 similarity index 100% rename from apps/website/public/t27/specs/tools/tri_to_t27_converter.t27 rename to apps/website/public/t27/files/specs/tools/tri_to_t27_converter.t27 diff --git a/apps/website/public/t27/specs/tri/agent/agent_run.t27 b/apps/website/public/t27/files/specs/tri/agent/agent_run.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/agent/agent_run.t27 rename to apps/website/public/t27/files/specs/tri/agent/agent_run.t27 diff --git a/apps/website/public/t27/specs/tri/agent/agents.t27 b/apps/website/public/t27/files/specs/tri/agent/agents.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/agent/agents.t27 rename to apps/website/public/t27/files/specs/tri/agent/agents.t27 diff --git a/apps/website/public/t27/specs/tri/agent/autonomous_lifecycle.t27 b/apps/website/public/t27/files/specs/tri/agent/autonomous_lifecycle.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/agent/autonomous_lifecycle.t27 rename to apps/website/public/t27/files/specs/tri/agent/autonomous_lifecycle.t27 diff --git a/apps/website/public/t27/specs/tri/agent/autonomous_universe.t27 b/apps/website/public/t27/files/specs/tri/agent/autonomous_universe.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/agent/autonomous_universe.t27 rename to apps/website/public/t27/files/specs/tri/agent/autonomous_universe.t27 diff --git a/apps/website/public/t27/specs/tri/agent/eternal_monitor.t27 b/apps/website/public/t27/files/specs/tri/agent/eternal_monitor.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/agent/eternal_monitor.t27 rename to apps/website/public/t27/files/specs/tri/agent/eternal_monitor.t27 diff --git a/apps/website/public/t27/specs/tri/agent/experience_hooks.t27 b/apps/website/public/t27/files/specs/tri/agent/experience_hooks.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/agent/experience_hooks.t27 rename to apps/website/public/t27/files/specs/tri/agent/experience_hooks.t27 diff --git a/apps/website/public/t27/specs/tri/agent/faculty_board.t27 b/apps/website/public/t27/files/specs/tri/agent/faculty_board.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/agent/faculty_board.t27 rename to apps/website/public/t27/files/specs/tri/agent/faculty_board.t27 diff --git a/apps/website/public/t27/specs/tri/agent/governance_agent.t27 b/apps/website/public/t27/files/specs/tri/agent/governance_agent.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/agent/governance_agent.t27 rename to apps/website/public/t27/files/specs/tri/agent/governance_agent.t27 diff --git a/apps/website/public/t27/specs/tri/agent/handoff.t27 b/apps/website/public/t27/files/specs/tri/agent/handoff.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/agent/handoff.t27 rename to apps/website/public/t27/files/specs/tri/agent/handoff.t27 diff --git a/apps/website/public/t27/specs/tri/agent/memory.t27 b/apps/website/public/t27/files/specs/tri/agent/memory.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/agent/memory.t27 rename to apps/website/public/t27/files/specs/tri/agent/memory.t27 diff --git a/apps/website/public/t27/specs/tri/agent/swarm_agents.t27 b/apps/website/public/t27/files/specs/tri/agent/swarm_agents.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/agent/swarm_agents.t27 rename to apps/website/public/t27/files/specs/tri/agent/swarm_agents.t27 diff --git a/apps/website/public/t27/specs/tri/collections/array.t27 b/apps/website/public/t27/files/specs/tri/collections/array.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/array.t27 rename to apps/website/public/t27/files/specs/tri/collections/array.t27 diff --git a/apps/website/public/t27/specs/tri/collections/bitmap.t27 b/apps/website/public/t27/files/specs/tri/collections/bitmap.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/bitmap.t27 rename to apps/website/public/t27/files/specs/tri/collections/bitmap.t27 diff --git a/apps/website/public/t27/specs/tri/collections/bitset.t27 b/apps/website/public/t27/files/specs/tri/collections/bitset.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/bitset.t27 rename to apps/website/public/t27/files/specs/tri/collections/bitset.t27 diff --git a/apps/website/public/t27/specs/tri/collections/bitvector.t27 b/apps/website/public/t27/files/specs/tri/collections/bitvector.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/bitvector.t27 rename to apps/website/public/t27/files/specs/tri/collections/bitvector.t27 diff --git a/apps/website/public/t27/specs/tri/collections/btree.t27 b/apps/website/public/t27/files/specs/tri/collections/btree.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/btree.t27 rename to apps/website/public/t27/files/specs/tri/collections/btree.t27 diff --git a/apps/website/public/t27/specs/tri/collections/circular_buffer.t27 b/apps/website/public/t27/files/specs/tri/collections/circular_buffer.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/circular_buffer.t27 rename to apps/website/public/t27/files/specs/tri/collections/circular_buffer.t27 diff --git a/apps/website/public/t27/specs/tri/collections/context.t27 b/apps/website/public/t27/files/specs/tri/collections/context.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/context.t27 rename to apps/website/public/t27/files/specs/tri/collections/context.t27 diff --git a/apps/website/public/t27/specs/tri/collections/deque.t27 b/apps/website/public/t27/files/specs/tri/collections/deque.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/deque.t27 rename to apps/website/public/t27/files/specs/tri/collections/deque.t27 diff --git a/apps/website/public/t27/specs/tri/collections/either.t27 b/apps/website/public/t27/files/specs/tri/collections/either.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/either.t27 rename to apps/website/public/t27/files/specs/tri/collections/either.t27 diff --git a/apps/website/public/t27/specs/tri/collections/interval.t27 b/apps/website/public/t27/files/specs/tri/collections/interval.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/interval.t27 rename to apps/website/public/t27/files/specs/tri/collections/interval.t27 diff --git a/apps/website/public/t27/specs/tri/collections/linked_list.t27 b/apps/website/public/t27/files/specs/tri/collections/linked_list.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/linked_list.t27 rename to apps/website/public/t27/files/specs/tri/collections/linked_list.t27 diff --git a/apps/website/public/t27/specs/tri/collections/list.t27 b/apps/website/public/t27/files/specs/tri/collections/list.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/list.t27 rename to apps/website/public/t27/files/specs/tri/collections/list.t27 diff --git a/apps/website/public/t27/specs/tri/collections/lockfree_stack.t27 b/apps/website/public/t27/files/specs/tri/collections/lockfree_stack.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/lockfree_stack.t27 rename to apps/website/public/t27/files/specs/tri/collections/lockfree_stack.t27 diff --git a/apps/website/public/t27/specs/tri/collections/lru.t27 b/apps/website/public/t27/files/specs/tri/collections/lru.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/lru.t27 rename to apps/website/public/t27/files/specs/tri/collections/lru.t27 diff --git a/apps/website/public/t27/specs/tri/collections/lru_cache.t27 b/apps/website/public/t27/files/specs/tri/collections/lru_cache.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/lru_cache.t27 rename to apps/website/public/t27/files/specs/tri/collections/lru_cache.t27 diff --git a/apps/website/public/t27/specs/tri/collections/map.t27 b/apps/website/public/t27/files/specs/tri/collections/map.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/map.t27 rename to apps/website/public/t27/files/specs/tri/collections/map.t27 diff --git a/apps/website/public/t27/specs/tri/collections/maybe.t27 b/apps/website/public/t27/files/specs/tri/collections/maybe.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/maybe.t27 rename to apps/website/public/t27/files/specs/tri/collections/maybe.t27 diff --git a/apps/website/public/t27/specs/tri/collections/namespace.t27 b/apps/website/public/t27/files/specs/tri/collections/namespace.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/namespace.t27 rename to apps/website/public/t27/files/specs/tri/collections/namespace.t27 diff --git a/apps/website/public/t27/specs/tri/collections/option.t27 b/apps/website/public/t27/files/specs/tri/collections/option.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/option.t27 rename to apps/website/public/t27/files/specs/tri/collections/option.t27 diff --git a/apps/website/public/t27/specs/tri/collections/priority_queue.t27 b/apps/website/public/t27/files/specs/tri/collections/priority_queue.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/priority_queue.t27 rename to apps/website/public/t27/files/specs/tri/collections/priority_queue.t27 diff --git a/apps/website/public/t27/specs/tri/collections/queue.t27 b/apps/website/public/t27/files/specs/tri/collections/queue.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/queue.t27 rename to apps/website/public/t27/files/specs/tri/collections/queue.t27 diff --git a/apps/website/public/t27/specs/tri/collections/result.t27 b/apps/website/public/t27/files/specs/tri/collections/result.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/result.t27 rename to apps/website/public/t27/files/specs/tri/collections/result.t27 diff --git a/apps/website/public/t27/specs/tri/collections/ring_buffer.t27 b/apps/website/public/t27/files/specs/tri/collections/ring_buffer.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/ring_buffer.t27 rename to apps/website/public/t27/files/specs/tri/collections/ring_buffer.t27 diff --git a/apps/website/public/t27/specs/tri/collections/set.t27 b/apps/website/public/t27/files/specs/tri/collections/set.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/set.t27 rename to apps/website/public/t27/files/specs/tri/collections/set.t27 diff --git a/apps/website/public/t27/specs/tri/collections/skip_list.t27 b/apps/website/public/t27/files/specs/tri/collections/skip_list.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/skip_list.t27 rename to apps/website/public/t27/files/specs/tri/collections/skip_list.t27 diff --git a/apps/website/public/t27/specs/tri/collections/stack.t27 b/apps/website/public/t27/files/specs/tri/collections/stack.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/stack.t27 rename to apps/website/public/t27/files/specs/tri/collections/stack.t27 diff --git a/apps/website/public/t27/specs/tri/collections/state.t27 b/apps/website/public/t27/files/specs/tri/collections/state.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/state.t27 rename to apps/website/public/t27/files/specs/tri/collections/state.t27 diff --git a/apps/website/public/t27/specs/tri/collections/tuple.t27 b/apps/website/public/t27/files/specs/tri/collections/tuple.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/tuple.t27 rename to apps/website/public/t27/files/specs/tri/collections/tuple.t27 diff --git a/apps/website/public/t27/specs/tri/collections/variant.t27 b/apps/website/public/t27/files/specs/tri/collections/variant.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/collections/variant.t27 rename to apps/website/public/t27/files/specs/tri/collections/variant.t27 diff --git a/apps/website/public/t27/specs/tri/crypto/base32.t27 b/apps/website/public/t27/files/specs/tri/crypto/base32.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/crypto/base32.t27 rename to apps/website/public/t27/files/specs/tri/crypto/base32.t27 diff --git a/apps/website/public/t27/specs/tri/crypto/base64.t27 b/apps/website/public/t27/files/specs/tri/crypto/base64.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/crypto/base64.t27 rename to apps/website/public/t27/files/specs/tri/crypto/base64.t27 diff --git a/apps/website/public/t27/specs/tri/crypto/crypto.t27 b/apps/website/public/t27/files/specs/tri/crypto/crypto.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/crypto/crypto.t27 rename to apps/website/public/t27/files/specs/tri/crypto/crypto.t27 diff --git a/apps/website/public/t27/specs/tri/crypto/ecc.t27 b/apps/website/public/t27/files/specs/tri/crypto/ecc.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/crypto/ecc.t27 rename to apps/website/public/t27/files/specs/tri/crypto/ecc.t27 diff --git a/apps/website/public/t27/specs/tri/crypto/hex.t27 b/apps/website/public/t27/files/specs/tri/crypto/hex.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/crypto/hex.t27 rename to apps/website/public/t27/files/specs/tri/crypto/hex.t27 diff --git a/apps/website/public/t27/specs/tri/crypto/hmac.t27 b/apps/website/public/t27/files/specs/tri/crypto/hmac.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/crypto/hmac.t27 rename to apps/website/public/t27/files/specs/tri/crypto/hmac.t27 diff --git a/apps/website/public/t27/specs/tri/crypto/reed_solomon.t27 b/apps/website/public/t27/files/specs/tri/crypto/reed_solomon.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/crypto/reed_solomon.t27 rename to apps/website/public/t27/files/specs/tri/crypto/reed_solomon.t27 diff --git a/apps/website/public/t27/specs/tri/crypto/rsa.t27 b/apps/website/public/t27/files/specs/tri/crypto/rsa.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/crypto/rsa.t27 rename to apps/website/public/t27/files/specs/tri/crypto/rsa.t27 diff --git a/apps/website/public/t27/specs/tri/crypto/sha256.t27 b/apps/website/public/t27/files/specs/tri/crypto/sha256.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/crypto/sha256.t27 rename to apps/website/public/t27/files/specs/tri/crypto/sha256.t27 diff --git a/apps/website/public/t27/specs/tri/encoding/bson.t27 b/apps/website/public/t27/files/specs/tri/encoding/bson.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/encoding/bson.t27 rename to apps/website/public/t27/files/specs/tri/encoding/bson.t27 diff --git a/apps/website/public/t27/specs/tri/encoding/csv.t27 b/apps/website/public/t27/files/specs/tri/encoding/csv.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/encoding/csv.t27 rename to apps/website/public/t27/files/specs/tri/encoding/csv.t27 diff --git a/apps/website/public/t27/specs/tri/encoding/html.t27 b/apps/website/public/t27/files/specs/tri/encoding/html.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/encoding/html.t27 rename to apps/website/public/t27/files/specs/tri/encoding/html.t27 diff --git a/apps/website/public/t27/specs/tri/encoding/json.t27 b/apps/website/public/t27/files/specs/tri/encoding/json.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/encoding/json.t27 rename to apps/website/public/t27/files/specs/tri/encoding/json.t27 diff --git a/apps/website/public/t27/specs/tri/encoding/markup.t27 b/apps/website/public/t27/files/specs/tri/encoding/markup.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/encoding/markup.t27 rename to apps/website/public/t27/files/specs/tri/encoding/markup.t27 diff --git a/apps/website/public/t27/specs/tri/encoding/mime.t27 b/apps/website/public/t27/files/specs/tri/encoding/mime.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/encoding/mime.t27 rename to apps/website/public/t27/files/specs/tri/encoding/mime.t27 diff --git a/apps/website/public/t27/specs/tri/encoding/msgpack.t27 b/apps/website/public/t27/files/specs/tri/encoding/msgpack.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/encoding/msgpack.t27 rename to apps/website/public/t27/files/specs/tri/encoding/msgpack.t27 diff --git a/apps/website/public/t27/specs/tri/encoding/xml.t27 b/apps/website/public/t27/files/specs/tri/encoding/xml.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/encoding/xml.t27 rename to apps/website/public/t27/files/specs/tri/encoding/xml.t27 diff --git a/apps/website/public/t27/specs/tri/graph/bellman_ford.t27 b/apps/website/public/t27/files/specs/tri/graph/bellman_ford.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/graph/bellman_ford.t27 rename to apps/website/public/t27/files/specs/tri/graph/bellman_ford.t27 diff --git a/apps/website/public/t27/specs/tri/graph/dijkstra.t27 b/apps/website/public/t27/files/specs/tri/graph/dijkstra.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/graph/dijkstra.t27 rename to apps/website/public/t27/files/specs/tri/graph/dijkstra.t27 diff --git a/apps/website/public/t27/specs/tri/graph/disjoint_set.t27 b/apps/website/public/t27/files/specs/tri/graph/disjoint_set.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/graph/disjoint_set.t27 rename to apps/website/public/t27/files/specs/tri/graph/disjoint_set.t27 diff --git a/apps/website/public/t27/specs/tri/graph/graph.t27 b/apps/website/public/t27/files/specs/tri/graph/graph.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/graph/graph.t27 rename to apps/website/public/t27/files/specs/tri/graph/graph.t27 diff --git a/apps/website/public/t27/specs/tri/graph/graph_bfs.t27 b/apps/website/public/t27/files/specs/tri/graph/graph_bfs.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/graph/graph_bfs.t27 rename to apps/website/public/t27/files/specs/tri/graph/graph_bfs.t27 diff --git a/apps/website/public/t27/specs/tri/graph/graph_dfs.t27 b/apps/website/public/t27/files/specs/tri/graph/graph_dfs.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/graph/graph_dfs.t27 rename to apps/website/public/t27/files/specs/tri/graph/graph_dfs.t27 diff --git a/apps/website/public/t27/specs/tri/graph/prims_mst.t27 b/apps/website/public/t27/files/specs/tri/graph/prims_mst.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/graph/prims_mst.t27 rename to apps/website/public/t27/files/specs/tri/graph/prims_mst.t27 diff --git a/apps/website/public/t27/specs/tri/graph/topological_sort.t27 b/apps/website/public/t27/files/specs/tri/graph/topological_sort.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/graph/topological_sort.t27 rename to apps/website/public/t27/files/specs/tri/graph/topological_sort.t27 diff --git a/apps/website/public/t27/specs/tri/io/compress.t27 b/apps/website/public/t27/files/specs/tri/io/compress.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/io/compress.t27 rename to apps/website/public/t27/files/specs/tri/io/compress.t27 diff --git a/apps/website/public/t27/specs/tri/io/filesystem.t27 b/apps/website/public/t27/files/specs/tri/io/filesystem.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/io/filesystem.t27 rename to apps/website/public/t27/files/specs/tri/io/filesystem.t27 diff --git a/apps/website/public/t27/specs/tri/io/fs.t27 b/apps/website/public/t27/files/specs/tri/io/fs.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/io/fs.t27 rename to apps/website/public/t27/files/specs/tri/io/fs.t27 diff --git a/apps/website/public/t27/specs/tri/io/io.t27 b/apps/website/public/t27/files/specs/tri/io/io.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/io/io.t27 rename to apps/website/public/t27/files/specs/tri/io/io.t27 diff --git a/apps/website/public/t27/specs/tri/io/reader.t27 b/apps/website/public/t27/files/specs/tri/io/reader.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/io/reader.t27 rename to apps/website/public/t27/files/specs/tri/io/reader.t27 diff --git a/apps/website/public/t27/specs/tri/io/writer.t27 b/apps/website/public/t27/files/specs/tri/io/writer.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/io/writer.t27 rename to apps/website/public/t27/files/specs/tri/io/writer.t27 diff --git a/apps/website/public/t27/specs/tri/io/zip.t27 b/apps/website/public/t27/files/specs/tri/io/zip.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/io/zip.t27 rename to apps/website/public/t27/files/specs/tri/io/zip.t27 diff --git a/apps/website/public/t27/specs/tri/math/bezier.t27 b/apps/website/public/t27/files/specs/tri/math/bezier.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/math/bezier.t27 rename to apps/website/public/t27/files/specs/tri/math/bezier.t27 diff --git a/apps/website/public/t27/specs/tri/math/constants.t27 b/apps/website/public/t27/files/specs/tri/math/constants.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/math/constants.t27 rename to apps/website/public/t27/files/specs/tri/math/constants.t27 diff --git a/apps/website/public/t27/specs/tri/math/math.t27 b/apps/website/public/t27/files/specs/tri/math/math.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/math/math.t27 rename to apps/website/public/t27/files/specs/tri/math/math.t27 diff --git a/apps/website/public/t27/specs/tri/math/matrix.t27 b/apps/website/public/t27/files/specs/tri/math/matrix.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/math/matrix.t27 rename to apps/website/public/t27/files/specs/tri/math/matrix.t27 diff --git a/apps/website/public/t27/specs/tri/math/measurement.t27 b/apps/website/public/t27/files/specs/tri/math/measurement.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/math/measurement.t27 rename to apps/website/public/t27/files/specs/tri/math/measurement.t27 diff --git a/apps/website/public/t27/specs/tri/math/polynomial.t27 b/apps/website/public/t27/files/specs/tri/math/polynomial.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/math/polynomial.t27 rename to apps/website/public/t27/files/specs/tri/math/polynomial.t27 diff --git a/apps/website/public/t27/specs/tri/math/probability.t27 b/apps/website/public/t27/files/specs/tri/math/probability.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/math/probability.t27 rename to apps/website/public/t27/files/specs/tri/math/probability.t27 diff --git a/apps/website/public/t27/specs/tri/math/statistics.t27 b/apps/website/public/t27/files/specs/tri/math/statistics.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/math/statistics.t27 rename to apps/website/public/t27/files/specs/tri/math/statistics.t27 diff --git a/apps/website/public/t27/specs/tri/net/async.t27 b/apps/website/public/t27/files/specs/tri/net/async.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/net/async.t27 rename to apps/website/public/t27/files/specs/tri/net/async.t27 diff --git a/apps/website/public/t27/specs/tri/net/async_stream.t27 b/apps/website/public/t27/files/specs/tri/net/async_stream.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/net/async_stream.t27 rename to apps/website/public/t27/files/specs/tri/net/async_stream.t27 diff --git a/apps/website/public/t27/specs/tri/net/channel.t27 b/apps/website/public/t27/files/specs/tri/net/channel.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/net/channel.t27 rename to apps/website/public/t27/files/specs/tri/net/channel.t27 diff --git a/apps/website/public/t27/specs/tri/net/cloud.t27 b/apps/website/public/t27/files/specs/tri/net/cloud.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/net/cloud.t27 rename to apps/website/public/t27/files/specs/tri/net/cloud.t27 diff --git a/apps/website/public/t27/specs/tri/net/http.t27 b/apps/website/public/t27/files/specs/tri/net/http.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/net/http.t27 rename to apps/website/public/t27/files/specs/tri/net/http.t27 diff --git a/apps/website/public/t27/specs/tri/net/net.t27 b/apps/website/public/t27/files/specs/tri/net/net.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/net/net.t27 rename to apps/website/public/t27/files/specs/tri/net/net.t27 diff --git a/apps/website/public/t27/specs/tri/net/url.t27 b/apps/website/public/t27/files/specs/tri/net/url.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/net/url.t27 rename to apps/website/public/t27/files/specs/tri/net/url.t27 diff --git a/apps/website/public/t27/specs/tri/pipeline/batch_runner.t27 b/apps/website/public/t27/files/specs/tri/pipeline/batch_runner.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/pipeline/batch_runner.t27 rename to apps/website/public/t27/files/specs/tri/pipeline/batch_runner.t27 diff --git a/apps/website/public/t27/specs/tri/pipeline/builder.t27 b/apps/website/public/t27/files/specs/tri/pipeline/builder.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/pipeline/builder.t27 rename to apps/website/public/t27/files/specs/tri/pipeline/builder.t27 diff --git a/apps/website/public/t27/specs/tri/pipeline/cloud_orchestrator.t27 b/apps/website/public/t27/files/specs/tri/pipeline/cloud_orchestrator.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/pipeline/cloud_orchestrator.t27 rename to apps/website/public/t27/files/specs/tri/pipeline/cloud_orchestrator.t27 diff --git a/apps/website/public/t27/specs/tri/pipeline/codegen.t27 b/apps/website/public/t27/files/specs/tri/pipeline/codegen.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/pipeline/codegen.t27 rename to apps/website/public/t27/files/specs/tri/pipeline/codegen.t27 diff --git a/apps/website/public/t27/specs/tri/pipeline/pipeline.t27 b/apps/website/public/t27/files/specs/tri/pipeline/pipeline.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/pipeline/pipeline.t27 rename to apps/website/public/t27/files/specs/tri/pipeline/pipeline.t27 diff --git a/apps/website/public/t27/specs/tri/pipeline/pipeline_parallel.t27 b/apps/website/public/t27/files/specs/tri/pipeline/pipeline_parallel.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/pipeline/pipeline_parallel.t27 rename to apps/website/public/t27/files/specs/tri/pipeline/pipeline_parallel.t27 diff --git a/apps/website/public/t27/specs/tri/pipeline/spec_parser.t27 b/apps/website/public/t27/files/specs/tri/pipeline/spec_parser.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/pipeline/spec_parser.t27 rename to apps/website/public/t27/files/specs/tri/pipeline/spec_parser.t27 diff --git a/apps/website/public/t27/specs/tri/pipeline/spec_writer.t27 b/apps/website/public/t27/files/specs/tri/pipeline/spec_writer.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/pipeline/spec_writer.t27 rename to apps/website/public/t27/files/specs/tri/pipeline/spec_writer.t27 diff --git a/apps/website/public/t27/specs/tri/pipeline/workflow.t27 b/apps/website/public/t27/files/specs/tri/pipeline/workflow.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/pipeline/workflow.t27 rename to apps/website/public/t27/files/specs/tri/pipeline/workflow.t27 diff --git a/apps/website/public/t27/specs/tri/pipeline/workflow_executor.t27 b/apps/website/public/t27/files/specs/tri/pipeline/workflow_executor.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/pipeline/workflow_executor.t27 rename to apps/website/public/t27/files/specs/tri/pipeline/workflow_executor.t27 diff --git a/apps/website/public/t27/specs/tri/pipeline/workflow_parser.t27 b/apps/website/public/t27/files/specs/tri/pipeline/workflow_parser.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/pipeline/workflow_parser.t27 rename to apps/website/public/t27/files/specs/tri/pipeline/workflow_parser.t27 diff --git a/apps/website/public/t27/specs/tri/search/aho_corasick.t27 b/apps/website/public/t27/files/specs/tri/search/aho_corasick.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/search/aho_corasick.t27 rename to apps/website/public/t27/files/specs/tri/search/aho_corasick.t27 diff --git a/apps/website/public/t27/specs/tri/search/bloom_filter.t27 b/apps/website/public/t27/files/specs/tri/search/bloom_filter.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/search/bloom_filter.t27 rename to apps/website/public/t27/files/specs/tri/search/bloom_filter.t27 diff --git a/apps/website/public/t27/specs/tri/search/boyer_moore.t27 b/apps/website/public/t27/files/specs/tri/search/boyer_moore.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/search/boyer_moore.t27 rename to apps/website/public/t27/files/specs/tri/search/boyer_moore.t27 diff --git a/apps/website/public/t27/specs/tri/search/knuth_morris_pratt.t27 b/apps/website/public/t27/files/specs/tri/search/knuth_morris_pratt.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/search/knuth_morris_pratt.t27 rename to apps/website/public/t27/files/specs/tri/search/knuth_morris_pratt.t27 diff --git a/apps/website/public/t27/specs/tri/search/match.t27 b/apps/website/public/t27/files/specs/tri/search/match.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/search/match.t27 rename to apps/website/public/t27/files/specs/tri/search/match.t27 diff --git a/apps/website/public/t27/specs/tri/search/pattern.t27 b/apps/website/public/t27/files/specs/tri/search/pattern.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/search/pattern.t27 rename to apps/website/public/t27/files/specs/tri/search/pattern.t27 diff --git a/apps/website/public/t27/specs/tri/search/rabin_karp.t27 b/apps/website/public/t27/files/specs/tri/search/rabin_karp.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/search/rabin_karp.t27 rename to apps/website/public/t27/files/specs/tri/search/rabin_karp.t27 diff --git a/apps/website/public/t27/specs/tri/search/regex.t27 b/apps/website/public/t27/files/specs/tri/search/regex.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/search/regex.t27 rename to apps/website/public/t27/files/specs/tri/search/regex.t27 diff --git a/apps/website/public/t27/specs/tri/search/regex_advanced.t27 b/apps/website/public/t27/files/specs/tri/search/regex_advanced.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/search/regex_advanced.t27 rename to apps/website/public/t27/files/specs/tri/search/regex_advanced.t27 diff --git a/apps/website/public/t27/specs/tri/search/search.t27 b/apps/website/public/t27/files/specs/tri/search/search.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/search/search.t27 rename to apps/website/public/t27/files/specs/tri/search/search.t27 diff --git a/apps/website/public/t27/specs/tri/sort/counting_sort.t27 b/apps/website/public/t27/files/specs/tri/sort/counting_sort.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/sort/counting_sort.t27 rename to apps/website/public/t27/files/specs/tri/sort/counting_sort.t27 diff --git a/apps/website/public/t27/specs/tri/sort/heap_sort.t27 b/apps/website/public/t27/files/specs/tri/sort/heap_sort.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/sort/heap_sort.t27 rename to apps/website/public/t27/files/specs/tri/sort/heap_sort.t27 diff --git a/apps/website/public/t27/specs/tri/sort/insertion_sort.t27 b/apps/website/public/t27/files/specs/tri/sort/insertion_sort.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/sort/insertion_sort.t27 rename to apps/website/public/t27/files/specs/tri/sort/insertion_sort.t27 diff --git a/apps/website/public/t27/specs/tri/sort/merge_sort.t27 b/apps/website/public/t27/files/specs/tri/sort/merge_sort.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/sort/merge_sort.t27 rename to apps/website/public/t27/files/specs/tri/sort/merge_sort.t27 diff --git a/apps/website/public/t27/specs/tri/sort/quick_sort.t27 b/apps/website/public/t27/files/specs/tri/sort/quick_sort.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/sort/quick_sort.t27 rename to apps/website/public/t27/files/specs/tri/sort/quick_sort.t27 diff --git a/apps/website/public/t27/specs/tri/sort/radix_sort.t27 b/apps/website/public/t27/files/specs/tri/sort/radix_sort.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/sort/radix_sort.t27 rename to apps/website/public/t27/files/specs/tri/sort/radix_sort.t27 diff --git a/apps/website/public/t27/specs/tri/sort/selection_sort.t27 b/apps/website/public/t27/files/specs/tri/sort/selection_sort.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/sort/selection_sort.t27 rename to apps/website/public/t27/files/specs/tri/sort/selection_sort.t27 diff --git a/apps/website/public/t27/specs/tri/sort/shell_sort.t27 b/apps/website/public/t27/files/specs/tri/sort/shell_sort.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/sort/shell_sort.t27 rename to apps/website/public/t27/files/specs/tri/sort/shell_sort.t27 diff --git a/apps/website/public/t27/specs/tri/sort/sort.t27 b/apps/website/public/t27/files/specs/tri/sort/sort.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/sort/sort.t27 rename to apps/website/public/t27/files/specs/tri/sort/sort.t27 diff --git a/apps/website/public/t27/specs/tri/sort/tim_sort.t27 b/apps/website/public/t27/files/specs/tri/sort/tim_sort.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/sort/tim_sort.t27 rename to apps/website/public/t27/files/specs/tri/sort/tim_sort.t27 diff --git a/apps/website/public/t27/specs/tri/trees/avl_tree.t27 b/apps/website/public/t27/files/specs/tri/trees/avl_tree.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/trees/avl_tree.t27 rename to apps/website/public/t27/files/specs/tri/trees/avl_tree.t27 diff --git a/apps/website/public/t27/specs/tri/trees/b_tree.t27 b/apps/website/public/t27/files/specs/tri/trees/b_tree.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/trees/b_tree.t27 rename to apps/website/public/t27/files/specs/tri/trees/b_tree.t27 diff --git a/apps/website/public/t27/specs/tri/trees/fenwick_tree.t27 b/apps/website/public/t27/files/specs/tri/trees/fenwick_tree.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/trees/fenwick_tree.t27 rename to apps/website/public/t27/files/specs/tri/trees/fenwick_tree.t27 diff --git a/apps/website/public/t27/specs/tri/trees/kd_tree.t27 b/apps/website/public/t27/files/specs/tri/trees/kd_tree.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/trees/kd_tree.t27 rename to apps/website/public/t27/files/specs/tri/trees/kd_tree.t27 diff --git a/apps/website/public/t27/specs/tri/trees/octree.t27 b/apps/website/public/t27/files/specs/tri/trees/octree.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/trees/octree.t27 rename to apps/website/public/t27/files/specs/tri/trees/octree.t27 diff --git a/apps/website/public/t27/specs/tri/trees/quadtree.t27 b/apps/website/public/t27/files/specs/tri/trees/quadtree.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/trees/quadtree.t27 rename to apps/website/public/t27/files/specs/tri/trees/quadtree.t27 diff --git a/apps/website/public/t27/specs/tri/trees/red_black_tree.t27 b/apps/website/public/t27/files/specs/tri/trees/red_black_tree.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/trees/red_black_tree.t27 rename to apps/website/public/t27/files/specs/tri/trees/red_black_tree.t27 diff --git a/apps/website/public/t27/specs/tri/trees/rtree.t27 b/apps/website/public/t27/files/specs/tri/trees/rtree.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/trees/rtree.t27 rename to apps/website/public/t27/files/specs/tri/trees/rtree.t27 diff --git a/apps/website/public/t27/specs/tri/trees/segment_tree.t27 b/apps/website/public/t27/files/specs/tri/trees/segment_tree.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/trees/segment_tree.t27 rename to apps/website/public/t27/files/specs/tri/trees/segment_tree.t27 diff --git a/apps/website/public/t27/specs/tri/trees/splay_tree.t27 b/apps/website/public/t27/files/specs/tri/trees/splay_tree.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/trees/splay_tree.t27 rename to apps/website/public/t27/files/specs/tri/trees/splay_tree.t27 diff --git a/apps/website/public/t27/specs/tri/trees/suffix_array.t27 b/apps/website/public/t27/files/specs/tri/trees/suffix_array.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/trees/suffix_array.t27 rename to apps/website/public/t27/files/specs/tri/trees/suffix_array.t27 diff --git a/apps/website/public/t27/specs/tri/trees/tree.t27 b/apps/website/public/t27/files/specs/tri/trees/tree.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/trees/tree.t27 rename to apps/website/public/t27/files/specs/tri/trees/tree.t27 diff --git a/apps/website/public/t27/specs/tri/trees/trie.t27 b/apps/website/public/t27/files/specs/tri/trees/trie.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/trees/trie.t27 rename to apps/website/public/t27/files/specs/tri/trees/trie.t27 diff --git a/apps/website/public/t27/specs/tri/utils/args.t27 b/apps/website/public/t27/files/specs/tri/utils/args.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/args.t27 rename to apps/website/public/t27/files/specs/tri/utils/args.t27 diff --git a/apps/website/public/t27/specs/tri/utils/arrow_time.t27 b/apps/website/public/t27/files/specs/tri/utils/arrow_time.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/arrow_time.t27 rename to apps/website/public/t27/files/specs/tri/utils/arrow_time.t27 diff --git a/apps/website/public/t27/specs/tri/utils/bytes.t27 b/apps/website/public/t27/files/specs/tri/utils/bytes.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/bytes.t27 rename to apps/website/public/t27/files/specs/tri/utils/bytes.t27 diff --git a/apps/website/public/t27/specs/tri/utils/color.t27 b/apps/website/public/t27/files/specs/tri/utils/color.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/color.t27 rename to apps/website/public/t27/files/specs/tri/utils/color.t27 diff --git a/apps/website/public/t27/specs/tri/utils/colors.t27 b/apps/website/public/t27/files/specs/tri/utils/colors.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/colors.t27 rename to apps/website/public/t27/files/specs/tri/utils/colors.t27 diff --git a/apps/website/public/t27/specs/tri/utils/config.t27 b/apps/website/public/t27/files/specs/tri/utils/config.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/config.t27 rename to apps/website/public/t27/files/specs/tri/utils/config.t27 diff --git a/apps/website/public/t27/specs/tri/utils/error.t27 b/apps/website/public/t27/files/specs/tri/utils/error.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/error.t27 rename to apps/website/public/t27/files/specs/tri/utils/error.t27 diff --git a/apps/website/public/t27/specs/tri/utils/exit_codes.t27 b/apps/website/public/t27/files/specs/tri/utils/exit_codes.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/exit_codes.t27 rename to apps/website/public/t27/files/specs/tri/utils/exit_codes.t27 diff --git a/apps/website/public/t27/specs/tri/utils/help.t27 b/apps/website/public/t27/files/specs/tri/utils/help.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/help.t27 rename to apps/website/public/t27/files/specs/tri/utils/help.t27 diff --git a/apps/website/public/t27/specs/tri/utils/logger.t27 b/apps/website/public/t27/files/specs/tri/utils/logger.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/logger.t27 rename to apps/website/public/t27/files/specs/tri/utils/logger.t27 diff --git a/apps/website/public/t27/specs/tri/utils/logging.t27 b/apps/website/public/t27/files/specs/tri/utils/logging.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/logging.t27 rename to apps/website/public/t27/files/specs/tri/utils/logging.t27 diff --git a/apps/website/public/t27/specs/tri/utils/random.t27 b/apps/website/public/t27/files/specs/tri/utils/random.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/random.t27 rename to apps/website/public/t27/files/specs/tri/utils/random.t27 diff --git a/apps/website/public/t27/specs/tri/utils/string.t27 b/apps/website/public/t27/files/specs/tri/utils/string.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/string.t27 rename to apps/website/public/t27/files/specs/tri/utils/string.t27 diff --git a/apps/website/public/t27/specs/tri/utils/template.t27 b/apps/website/public/t27/files/specs/tri/utils/template.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/template.t27 rename to apps/website/public/t27/files/specs/tri/utils/template.t27 diff --git a/apps/website/public/t27/specs/tri/utils/terminal.t27 b/apps/website/public/t27/files/specs/tri/utils/terminal.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/terminal.t27 rename to apps/website/public/t27/files/specs/tri/utils/terminal.t27 diff --git a/apps/website/public/t27/specs/tri/utils/text.t27 b/apps/website/public/t27/files/specs/tri/utils/text.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/text.t27 rename to apps/website/public/t27/files/specs/tri/utils/text.t27 diff --git a/apps/website/public/t27/specs/tri/utils/time.t27 b/apps/website/public/t27/files/specs/tri/utils/time.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/time.t27 rename to apps/website/public/t27/files/specs/tri/utils/time.t27 diff --git a/apps/website/public/t27/specs/tri/utils/utf8.t27 b/apps/website/public/t27/files/specs/tri/utils/utf8.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/utf8.t27 rename to apps/website/public/t27/files/specs/tri/utils/utf8.t27 diff --git a/apps/website/public/t27/specs/tri/utils/version.t27 b/apps/website/public/t27/files/specs/tri/utils/version.t27 similarity index 100% rename from apps/website/public/t27/specs/tri/utils/version.t27 rename to apps/website/public/t27/files/specs/tri/utils/version.t27 diff --git a/apps/website/public/t27/specs/vm/jit_semantics.t27 b/apps/website/public/t27/files/specs/vm/jit_semantics.t27 similarity index 100% rename from apps/website/public/t27/specs/vm/jit_semantics.t27 rename to apps/website/public/t27/files/specs/vm/jit_semantics.t27 diff --git a/apps/website/public/t27/specs/vsa/jones_polynomial.t27 b/apps/website/public/t27/files/specs/vsa/jones_polynomial.t27 similarity index 100% rename from apps/website/public/t27/specs/vsa/jones_polynomial.t27 rename to apps/website/public/t27/files/specs/vsa/jones_polynomial.t27 diff --git a/apps/website/public/t27/specs/vsa/ops.t27 b/apps/website/public/t27/files/specs/vsa/ops.t27 similarity index 100% rename from apps/website/public/t27/specs/vsa/ops.t27 rename to apps/website/public/t27/files/specs/vsa/ops.t27 diff --git a/apps/website/public/t27/specs/vsa/packed_vsa.t27 b/apps/website/public/t27/files/specs/vsa/packed_vsa.t27 similarity index 100% rename from apps/website/public/t27/specs/vsa/packed_vsa.t27 rename to apps/website/public/t27/files/specs/vsa/packed_vsa.t27 diff --git a/apps/website/public/t27/specs/vsa/sdk.t27 b/apps/website/public/t27/files/specs/vsa/sdk.t27 similarity index 100% rename from apps/website/public/t27/specs/vsa/sdk.t27 rename to apps/website/public/t27/files/specs/vsa/sdk.t27 diff --git a/apps/website/public/t27/specs/vsa/sequence_hdc.t27 b/apps/website/public/t27/files/specs/vsa/sequence_hdc.t27 similarity index 100% rename from apps/website/public/t27/specs/vsa/sequence_hdc.t27 rename to apps/website/public/t27/files/specs/vsa/sequence_hdc.t27 diff --git a/apps/website/public/t27/specs/vsa/similarity_search.t27 b/apps/website/public/t27/files/specs/vsa/similarity_search.t27 similarity index 100% rename from apps/website/public/t27/specs/vsa/similarity_search.t27 rename to apps/website/public/t27/files/specs/vsa/similarity_search.t27 diff --git a/apps/website/public/t27/specs/vsa/vsa_core.t27 b/apps/website/public/t27/files/specs/vsa/vsa_core.t27 similarity index 100% rename from apps/website/public/t27/specs/vsa/vsa_core.t27 rename to apps/website/public/t27/files/specs/vsa/vsa_core.t27 diff --git a/apps/website/public/t27/files/test_highlight.t27 b/apps/website/public/t27/files/test_highlight.t27 new file mode 100644 index 0000000000..97eb03cece --- /dev/null +++ b/apps/website/public/t27/files/test_highlight.t27 @@ -0,0 +1,56 @@ +; T27 Syntax Highlighting Test File +// This tests all syntax elements + +pub module test; + +; Constants with PHI +pub const PHI: f64 = 1.6180339887498948482; +pub const PHI_SQ: f64 = 2.6180339887498948482; +pub const TRINITY: i8 = 3; + +; Ternary types +pub const Trit = enum(i8) { + neg = -1, + neu = 0, + pos = 1 +} + +; Struct with arrays +pub struct State { + trits: [9]Trit, + buffer: [128]u8 = [_]u8{0} ** 128, + phi: f64 +} + +; Function with builtins +pub fn compute_phi(x: i8) f64 { + const casted = @intCast(f64, x); + return casted * PHI; +} + +; Switch expression +pub fn invert_trit(a: Trit) Trit { + return switch (a) { + .neg => .pos, + .neu => .neu, + .pos => .neg + } +} + +; For loop with range +pub fn sum_range(n: i32) i32 { + var total: i32 = 0; + for (0..n) |i| { + total += i; + } + return total; +} + +; Array literal +pub const zeros = [16]i8{0}; + +; Builtin usage +pub fn test_builtin() void { + const result = @as(Trit, .pos); + @compileAssert(true, "test"); +} diff --git a/apps/website/public/t27/files/tests/comprehensive_suite.t27 b/apps/website/public/t27/files/tests/comprehensive_suite.t27 new file mode 100644 index 0000000000..710d472d40 --- /dev/null +++ b/apps/website/public/t27/files/tests/comprehensive_suite.t27 @@ -0,0 +1,18 @@ +; comprehensive_suite.t27 -- documents the repository integration suite +; Executed by: t27c suite (or tri test). No shell runners under tests/. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module tests-comprehensive-suite; + +pub const SUITE_PHASE_COUNT : u8 = 6; + +pub const PHASE_PARSE : u8 = 1; +pub const PHASE_GEN_ZIG : u8 = 2; +pub const PHASE_GEN_VERILOG : u8 = 3; +pub const PHASE_GEN_C : u8 = 4; +pub const PHASE_SEAL_VERIFY : u8 = 5; +pub const PHASE_FIXED_POINT : u8 = 6; + +test "suite_has_six_phases" { + try std.testing.expectEqual(@as(u8, 6), SUITE_PHASE_COUNT); +} diff --git a/apps/website/public/t27/files/tests/ring0_trivial.t27 b/apps/website/public/t27/files/tests/ring0_trivial.t27 new file mode 100644 index 0000000000..e89f15aa03 --- /dev/null +++ b/apps/website/public/t27/files/tests/ring0_trivial.t27 @@ -0,0 +1,10 @@ +// ring-0 trivial test +// phi^2 + 1/phi^2 = 3 | TRINITY + +module ring0; + +pub const ONE : i8 = 1; +pub const ZERO : i8 = 0; +pub const NEG : i8 = -1; +pub const HEX : u16 = 0xFF; +pub const BIN : u8 = 0b10; diff --git a/apps/website/public/t27/files/vscode-trinity-swe/test_highlight.t27 b/apps/website/public/t27/files/vscode-trinity-swe/test_highlight.t27 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/website/public/t27/manifest.json b/apps/website/public/t27/manifest.json index 72393cc1ef..15804a0ced 100644 --- a/apps/website/public/t27/manifest.json +++ b/apps/website/public/t27/manifest.json @@ -1 +1 @@ -{"generatedFrom":{"repo":"gHashTag/t27","commit":"8d980d937fcd9a7044a8ffa9e7eb3cbc63e0daf5","shortCommit":"8d980d937","specsOrCompilerDirty":false},"wasmBytes":488709,"specCount":497,"totalLines":116410,"categories":{"tri":150,"fpga":66,"ml":60,"isa":16,"numeric":16,"physics":16,"server":11,"compiler":10,"math":10,"sacred":10,"ar":8,"base":8,"vsa":7,"brain":6,"github":5,"lsp":5,"sandbox":5,"test_framework":5,"benchmarks":4,"config":4,"git":4,"memory":4,"nn":4,"provider":4,"storage":4,"ternary":4,"account":3,"api":3,"boards":3,"demos":3,"file":3,"pins":3,"pipeline":3,"queen":3,"runtime":3,"shell":3,"tools":3,"bus":2,"enrichment":2,"sync":2,"auth":1,"automation":1,"cloud":1,"conformance":1,"depin":1,"graph":1,"hslm":1,"interop":1,"jit":1,"neural":1,"portable":1,"vm":1},"specs":[{"path":"account/auth.t27","category":"account","name":"auth","module":"AccountAuth","lines":278,"bytes":9231},{"path":"account/repo.t27","category":"account","name":"repo","module":"AccountRepo","lines":197,"bytes":5474},{"path":"account/schema.t27","category":"account","name":"schema","module":"Account","lines":227,"bytes":6623},{"path":"api/c_api_contract.t27","category":"api","name":"c_api_contract","module":null,"lines":243,"bytes":7087},{"path":"api/sdk_contract.t27","category":"api","name":"sdk_contract","module":null,"lines":407,"bytes":10914},{"path":"api/tri_net_api.t27","category":"api","name":"tri_net_api","module":null,"lines":190,"bytes":5445},{"path":"ar/asp_solver.t27","category":"ar","name":"asp_solver","module":null,"lines":556,"bytes":16355},{"path":"ar/coa_planning.t27","category":"ar","name":"coa_planning","module":null,"lines":638,"bytes":20107},{"path":"ar/composition.t27","category":"ar","name":"composition","module":null,"lines":571,"bytes":19109},{"path":"ar/datalog_engine.t27","category":"ar","name":"datalog_engine","module":null,"lines":350,"bytes":10935},{"path":"ar/explainability.t27","category":"ar","name":"explainability","module":null,"lines":556,"bytes":16253},{"path":"ar/proof_trace.t27","category":"ar","name":"proof_trace","module":null,"lines":313,"bytes":9671},{"path":"ar/restraint.t27","category":"ar","name":"restraint","module":null,"lines":438,"bytes":14096},{"path":"ar/ternary_logic.t27","category":"ar","name":"ternary_logic","module":null,"lines":473,"bytes":13762},{"path":"auth/config.t27","category":"auth","name":"config","module":"AuthConfig","lines":295,"bytes":9593},{"path":"automation/wrapup-auto.t27","category":"automation","name":"wrapup-auto","module":"automation","lines":51,"bytes":1625},{"path":"base/debounce.t27","category":"base","name":"debounce","module":"base","lines":99,"bytes":2655},{"path":"base/ops.t27","category":"base","name":"ops","module":"tritype","lines":1490,"bytes":47606},{"path":"base/ring_32.t27","category":"base","name":"ring_32","module":"base","lines":25,"bytes":596},{"path":"base/seed.t27","category":"base","name":"seed","module":"seed","lines":93,"bytes":2627},{"path":"base/ternary_add.t27","category":"base","name":"ternary_add","module":"ternary_add","lines":413,"bytes":14371},{"path":"base/ternary_encoding.t27","category":"base","name":"ternary_encoding","module":"TernaryEncoding","lines":415,"bytes":13711},{"path":"base/ternary_memory.t27","category":"base","name":"ternary_memory","module":"TernaryMemory","lines":417,"bytes":13794},{"path":"base/types.t27","category":"base","name":"types","module":"tritype","lines":1680,"bytes":55612},{"path":"benchmarks/bench_main.t27","category":"benchmarks","name":"bench_main","module":null,"lines":95,"bytes":2523},{"path":"benchmarks/bench_nn.t27","category":"benchmarks","name":"bench_nn","module":null,"lines":166,"bytes":3216},{"path":"benchmarks/gf16_bfloat16_nmse.t27","category":"benchmarks","name":"gf16_bfloat16_nmse","module":null,"lines":153,"bytes":4300},{"path":"benchmarks/ternary_vs_binary.t27","category":"benchmarks","name":"ternary_vs_binary","module":null,"lines":92,"bytes":1965},{"path":"boards/arty_a7.t27","category":"boards","name":"arty_a7","module":"BoardArtyA7","lines":288,"bytes":7435},{"path":"boards/xc7a100t_full.t27","category":"boards","name":"xc7a100t_full","module":"BoardFullXC7A100T","lines":357,"bytes":9813},{"path":"boards/xc7a100t_minimal.t27","category":"boards","name":"xc7a100t_minimal","module":"BoardMinimalXC7A100T","lines":393,"bytes":10805},{"path":"brain/brain.t27","category":"brain","name":"brain","module":null,"lines":58,"bytes":2756},{"path":"brain/bus.t27","category":"brain","name":"bus","module":"brain","lines":17,"bytes":419},{"path":"brain/cognitive_loop.t27","category":"brain","name":"cognitive_loop","module":"brain","lines":17,"bytes":515},{"path":"brain/neural_gamma.t27","category":"brain","name":"neural_gamma","module":null,"lines":89,"bytes":2008},{"path":"brain/phi_timing.t27","category":"brain","name":"phi_timing","module":"brain","lines":80,"bytes":3193},{"path":"brain/unified_state.t27","category":"brain","name":"unified_state","module":"brain","lines":94,"bytes":2948},{"path":"bus/pubsub.t27","category":"bus","name":"pubsub","module":"bus","lines":670,"bytes":17611},{"path":"bus/schema.t27","category":"bus","name":"schema","module":"bus","lines":657,"bytes":17052},{"path":"cloud/railway_deploy.t27","category":"cloud","name":"railway_deploy","module":"cloud","lines":363,"bytes":9070},{"path":"compiler/diagnostics.t27","category":"compiler","name":"diagnostics","module":"Diagnostics","lines":148,"bytes":4220},{"path":"compiler/lexer.t27","category":"compiler","name":"lexer","module":"Lexing","lines":583,"bytes":22503},{"path":"compiler/linker.t27","category":"compiler","name":"linker","module":"Linking","lines":156,"bytes":4048},{"path":"compiler/meta_compile.t27","category":"compiler","name":"meta_compile","module":"MetaCompilation","lines":70,"bytes":1886},{"path":"compiler/mod_structure.t27","category":"compiler","name":"mod_structure","module":"compiler","lines":124,"bytes":3540},{"path":"compiler/optimizer.t27","category":"compiler","name":"optimizer","module":"Optimization","lines":286,"bytes":8976},{"path":"compiler/parser.t27","category":"compiler","name":"parser","module":"Parsing","lines":1619,"bytes":55123},{"path":"compiler/pipeline.t27","category":"compiler","name":"pipeline","module":"Pipeline","lines":170,"bytes":4503},{"path":"compiler/stdlib.t27","category":"compiler","name":"stdlib","module":"Stdlib","lines":663,"bytes":16035},{"path":"compiler/typechecker.t27","category":"compiler","name":"typechecker","module":"TypeChecking","lines":694,"bytes":24448},{"path":"config/load.t27","category":"config","name":"load","module":"config","lines":608,"bytes":16362},{"path":"config/migrate.t27","category":"config","name":"migrate","module":"config","lines":663,"bytes":18030},{"path":"config/paths.t27","category":"config","name":"paths","module":"config","lines":641,"bytes":15895},{"path":"config/schema.t27","category":"config","name":"schema","module":"config","lines":703,"bytes":19048},{"path":"conformance/e2e_scenarios.t27","category":"conformance","name":"e2e_scenarios","module":null,"lines":265,"bytes":7796},{"path":"demos/jones_topology_decision_gate.t27","category":"demos","name":"jones_topology_decision_gate","module":"JonesTopologyDecisionGate","lines":347,"bytes":29767},{"path":"demos/jones_topology_filter.t27","category":"demos","name":"jones_topology_filter","module":"JonesTopologyFilter","lines":318,"bytes":10711},{"path":"demos/simple_test.t27","category":"demos","name":"simple_test","module":"SimpleTest","lines":15,"bytes":277},{"path":"depin/prove.t27","category":"depin","name":"prove","module":"depin","lines":285,"bytes":8731},{"path":"enrichment/audio_overview.t27","category":"enrichment","name":"audio_overview","module":"enrichment","lines":163,"bytes":5680},{"path":"enrichment/youtube_transcript.t27","category":"enrichment","name":"youtube_transcript","module":"enrichment","lines":606,"bytes":20285},{"path":"file/operations.t27","category":"file","name":"operations","module":"FileOperations","lines":446,"bytes":13273},{"path":"file/schema.t27","category":"file","name":"schema","module":"File","lines":334,"bytes":11786},{"path":"file/watcher.t27","category":"file","name":"watcher","module":"FileWatcher","lines":551,"bytes":16816},{"path":"fpga/apb_bridge.t27","category":"fpga","name":"apb_bridge","module":"ApbBridge","lines":348,"bytes":9210},{"path":"fpga/assembler.t27","category":"fpga","name":"assembler","module":"Assembler","lines":347,"bytes":8722},{"path":"fpga/axi4.t27","category":"fpga","name":"axi4","module":"Axi4","lines":394,"bytes":10640},{"path":"fpga/boards/arty_a7_integration.t27","category":"fpga","name":"arty_a7_integration","module":"ArtyA7_Integration","lines":137,"bytes":4043},{"path":"fpga/boards/qmtech_a100t_integration.t27","category":"fpga","name":"qmtech_a100t_integration","module":"QMTech_A100T_Integration","lines":96,"bytes":2568},{"path":"fpga/bootrom.t27","category":"fpga","name":"bootrom","module":"BootROM","lines":140,"bytes":4020},{"path":"fpga/bridge.t27","category":"fpga","name":"bridge","module":"FPGA_Bridge","lines":501,"bytes":17323},{"path":"fpga/clock_domain.t27","category":"fpga","name":"clock_domain","module":"ClockDomain","lines":231,"bytes":5913},{"path":"fpga/crossopt.t27","category":"fpga","name":"crossopt","module":"CrossOpt","lines":126,"bytes":3615},{"path":"fpga/cts.t27","category":"fpga","name":"cts","module":"CTS","lines":225,"bytes":5735},{"path":"fpga/dft.t27","category":"fpga","name":"dft","module":"DFT","lines":264,"bytes":6604},{"path":"fpga/e2e_demo.t27","category":"fpga","name":"e2e_demo","module":"E2eDemo","lines":253,"bytes":6432},{"path":"fpga/fifo.t27","category":"fpga","name":"fifo","module":"Fifo","lines":365,"bytes":9822},{"path":"fpga/formal.t27","category":"fpga","name":"formal","module":"Formal","lines":353,"bytes":9441},{"path":"fpga/gf16_accel.t27","category":"fpga","name":"gf16_accel","module":"Gf16Accel","lines":423,"bytes":11072},{"path":"fpga/hir.t27","category":"fpga","name":"hir","module":"Hir","lines":692,"bytes":19053},{"path":"fpga/hw_types.t27","category":"fpga","name":"hw_types","module":"HwTypes","lines":335,"bytes":8002},{"path":"fpga/linker.t27","category":"fpga","name":"linker","module":"Linker","lines":327,"bytes":8187},{"path":"fpga/mac.t27","category":"fpga","name":"mac","module":"ZeroDSP_MAC","lines":623,"bytes":22498},{"path":"fpga/memory.t27","category":"fpga","name":"memory","module":"Memory","lines":355,"bytes":9348},{"path":"fpga/partition.t27","category":"fpga","name":"partition","module":"Partition","lines":306,"bytes":9004},{"path":"fpga/placement.t27","category":"fpga","name":"placement","module":"Placement","lines":242,"bytes":6335},{"path":"fpga/power.t27","category":"fpga","name":"power","module":"Power","lines":230,"bytes":6278},{"path":"fpga/power_analysis.t27","category":"fpga","name":"power_analysis","module":"PowerAnalysis","lines":453,"bytes":12807},{"path":"fpga/router.t27","category":"fpga","name":"router","module":"Router","lines":257,"bytes":6620},{"path":"fpga/simulator.t27","category":"fpga","name":"simulator","module":"Simulator","lines":278,"bytes":7050},{"path":"fpga/spi.t27","category":"fpga","name":"spi","module":"SPI_Master","lines":415,"bytes":13093},{"path":"fpga/stdlib.t27","category":"fpga","name":"stdlib","module":"Stdlib","lines":372,"bytes":17315},{"path":"fpga/ternary_isa.t27","category":"fpga","name":"ternary_isa","module":"TernaryIsa","lines":543,"bytes":14462},{"path":"fpga/testbench.t27","category":"fpga","name":"testbench","module":"Testbench","lines":271,"bytes":6930},{"path":"fpga/testbench/apb_bridge_tb.t27","category":"fpga","name":"apb_bridge_tb","module":"APB_Bridge_Testbench","lines":144,"bytes":3247},{"path":"fpga/testbench/assembler_tb.t27","category":"fpga","name":"assembler_tb","module":"Assembler_Testbench","lines":111,"bytes":2952},{"path":"fpga/testbench/axi4_tb.t27","category":"fpga","name":"axi4_tb","module":"AXI4_Testbench","lines":179,"bytes":4359},{"path":"fpga/testbench/bootrom_tb.t27","category":"fpga","name":"bootrom_tb","module":"BootROM_Testbench","lines":87,"bytes":1856},{"path":"fpga/testbench/bridge_tb.t27","category":"fpga","name":"bridge_tb","module":"Bridge_Testbench","lines":124,"bytes":2796},{"path":"fpga/testbench/clock_domain_tb.t27","category":"fpga","name":"clock_domain_tb","module":"ClockDomain_Testbench","lines":109,"bytes":2439},{"path":"fpga/testbench/cts_tb.t27","category":"fpga","name":"cts_tb","module":"CTS_Testbench","lines":119,"bytes":2939},{"path":"fpga/testbench/dft_tb.t27","category":"fpga","name":"dft_tb","module":"DFT_Testbench","lines":130,"bytes":3050},{"path":"fpga/testbench/fifo_tb.t27","category":"fpga","name":"fifo_tb","module":"FIFO_Testbench","lines":163,"bytes":3606},{"path":"fpga/testbench/formal_tb.t27","category":"fpga","name":"formal_tb","module":"Formal_Testbench","lines":132,"bytes":3125},{"path":"fpga/testbench/gf16_accel_tb.t27","category":"fpga","name":"gf16_accel_tb","module":"GF16_Accel_Testbench","lines":136,"bytes":3024},{"path":"fpga/testbench/hir_tb.t27","category":"fpga","name":"hir_tb","module":"HIR_Testbench","lines":105,"bytes":2327},{"path":"fpga/testbench/integration_tb.t27","category":"fpga","name":"integration_tb","module":"Integration_Testbench","lines":126,"bytes":2971},{"path":"fpga/testbench/linker_tb.t27","category":"fpga","name":"linker_tb","module":"Linker_Testbench","lines":89,"bytes":2000},{"path":"fpga/testbench/mac_tb.t27","category":"fpga","name":"mac_tb","module":"MAC_Testbench","lines":554,"bytes":17571},{"path":"fpga/testbench/memory_tb.t27","category":"fpga","name":"memory_tb","module":"Memory_Testbench","lines":136,"bytes":3129},{"path":"fpga/testbench/partition_tb.t27","category":"fpga","name":"partition_tb","module":"Partition_Testbench","lines":94,"bytes":2266},{"path":"fpga/testbench/placement_tb.t27","category":"fpga","name":"placement_tb","module":"Placement_Testbench","lines":115,"bytes":2993},{"path":"fpga/testbench/power_analysis_tb.t27","category":"fpga","name":"power_analysis_tb","module":"PowerAnalysis_Testbench","lines":130,"bytes":3169},{"path":"fpga/testbench/power_tb.t27","category":"fpga","name":"power_tb","module":"Power_Testbench","lines":118,"bytes":2926},{"path":"fpga/testbench/router_tb.t27","category":"fpga","name":"router_tb","module":"Router_Testbench","lines":112,"bytes":2833},{"path":"fpga/testbench/simulator_tb.t27","category":"fpga","name":"simulator_tb","module":"Simulator_Testbench","lines":92,"bytes":2071},{"path":"fpga/testbench/spi_tb.t27","category":"fpga","name":"spi_tb","module":"SPI_Testbench","lines":124,"bytes":2963},{"path":"fpga/testbench/stdlib_tb.t27","category":"fpga","name":"stdlib_tb","module":"Stdlib_Testbench","lines":112,"bytes":2313},{"path":"fpga/testbench/ternary_isa_tb.t27","category":"fpga","name":"ternary_isa_tb","module":"Ternary_ISA_Testbench","lines":148,"bytes":3310},{"path":"fpga/testbench/timing_tb.t27","category":"fpga","name":"timing_tb","module":"Timing_Testbench","lines":98,"bytes":2410},{"path":"fpga/testbench/top_tb.t27","category":"fpga","name":"top_tb","module":"Top_Level_Testbench","lines":218,"bytes":5626},{"path":"fpga/testbench/uart_tb.t27","category":"fpga","name":"uart_tb","module":"UART_Testbench","lines":397,"bytes":11180},{"path":"fpga/testbench/vcd_conformance_compare_tb.t27","category":"fpga","name":"vcd_conformance_compare_tb","module":"VcdConformanceCompare_Testbench","lines":107,"bytes":2611},{"path":"fpga/testbench/vcd_trace_tb.t27","category":"fpga","name":"vcd_trace_tb","module":"VCD_Trace_Testbench","lines":86,"bytes":1929},{"path":"fpga/timing.t27","category":"fpga","name":"timing","module":"Timing","lines":376,"bytes":9419},{"path":"fpga/top_level.t27","category":"fpga","name":"top_level","module":"ZeroDSP_TopLevel","lines":220,"bytes":5551},{"path":"fpga/uart.t27","category":"fpga","name":"uart","module":"ZeroDSP_UART","lines":211,"bytes":5733},{"path":"fpga/vcd_conformance_compare.t27","category":"fpga","name":"vcd_conformance_compare","module":"VcdConformanceCompare","lines":436,"bytes":11816},{"path":"fpga/vcd_trace.t27","category":"fpga","name":"vcd_trace","module":"VcdTrace","lines":292,"bytes":7555},{"path":"fpga/verification/build_verify.t27","category":"fpga","name":"build_verify","module":"BuildVerify","lines":87,"bytes":2190},{"path":"git/diff.t27","category":"git","name":"diff","module":"GitDiff","lines":230,"bytes":7832},{"path":"git/operations.t27","category":"git","name":"operations","module":"GitOperations","lines":176,"bytes":5799},{"path":"git/schema.t27","category":"git","name":"schema","module":"Git","lines":229,"bytes":6205},{"path":"git/status.t27","category":"git","name":"status","module":"GitStatus","lines":189,"bytes":6535},{"path":"github/auth.t27","category":"github","name":"auth","module":"github","lines":27,"bytes":708},{"path":"github/comments.t27","category":"github","name":"comments","module":"github","lines":18,"bytes":398},{"path":"github/issues.t27","category":"github","name":"issues","module":"github","lines":22,"bytes":484},{"path":"github/prs.t27","category":"github","name":"prs","module":"github","lines":22,"bytes":472},{"path":"github/tests/e2e_full_flow.t27","category":"github","name":"e2e_full_flow","module":"github","lines":122,"bytes":3778},{"path":"graph/knowledge_graph.t27","category":"graph","name":"knowledge_graph","module":"KnowledgeGraph","lines":406,"bytes":17952},{"path":"hslm/forward_pass.t27","category":"hslm","name":"forward_pass","module":"ForwardPass","lines":306,"bytes":14239},{"path":"interop/gf_cross_language.t27","category":"interop","name":"gf_cross_language","module":"GFCrossLanguageConformance","lines":99,"bytes":3587},{"path":"isa/registers.t27","category":"isa","name":"registers","module":"ISARegisters","lines":600,"bytes":20530},{"path":"isa/ternary_arithmetic.t27","category":"isa","name":"ternary_arithmetic","module":"TernaryArithmetic","lines":501,"bytes":16736},{"path":"isa/ternary_bitwise.t27","category":"isa","name":"ternary_bitwise","module":"TernaryBitwise","lines":404,"bytes":14046},{"path":"isa/ternary_control_flow.t27","category":"isa","name":"ternary_control_flow","module":"TernaryControlFlow","lines":392,"bytes":12518},{"path":"isa/ternary_deque.t27","category":"isa","name":"ternary_deque","module":"TernaryDeque","lines":498,"bytes":16013},{"path":"isa/ternary_encoding.t27","category":"isa","name":"ternary_encoding","module":null,"lines":42,"bytes":944},{"path":"isa/ternary_gates.t27","category":"isa","name":"ternary_gates","module":"TernaryGates","lines":410,"bytes":14744},{"path":"isa/ternary_graph.t27","category":"isa","name":"ternary_graph","module":"TernaryGraph","lines":131,"bytes":3644},{"path":"isa/ternary_hash.t27","category":"isa","name":"ternary_hash","module":"TernaryHashTable","lines":167,"bytes":5024},{"path":"isa/ternary_memory.t27","category":"isa","name":"ternary_memory","module":"ISAMemoryOps","lines":522,"bytes":18705},{"path":"isa/ternary_pattern_matching.t27","category":"isa","name":"ternary_pattern_matching","module":"TernaryPatternMatching","lines":173,"bytes":5331},{"path":"isa/ternary_search.t27","category":"isa","name":"ternary_search","module":"TernarySearch","lines":165,"bytes":4613},{"path":"isa/ternary_set.t27","category":"isa","name":"ternary_set","module":"TernarySet","lines":182,"bytes":5215},{"path":"isa/ternary_shift.t27","category":"isa","name":"ternary_shift","module":"TernaryShift","lines":420,"bytes":14016},{"path":"isa/ternary_sorting.t27","category":"isa","name":"ternary_sorting","module":"TernarySorting","lines":173,"bytes":4909},{"path":"isa/ternary_tree.t27","category":"isa","name":"ternary_tree","module":"TernaryTree","lines":141,"bytes":4645},{"path":"jit/jit.t27","category":"jit","name":"jit","module":"jit","lines":875,"bytes":29745},{"path":"lsp/client.t27","category":"lsp","name":"client","module":"lsp","lines":505,"bytes":14486},{"path":"lsp/language.t27","category":"lsp","name":"language","module":"lsp","lines":527,"bytes":14976},{"path":"lsp/protocol.t27","category":"lsp","name":"protocol","module":"lsp","lines":681,"bytes":20652},{"path":"lsp/schema.t27","category":"lsp","name":"schema","module":"lsp","lines":589,"bytes":15702},{"path":"lsp/server.t27","category":"lsp","name":"server","module":"lsp","lines":555,"bytes":15397},{"path":"math/constants.t27","category":"math","name":"constants","module":"Constants","lines":459,"bytes":16818},{"path":"math/e8_lie_algebra.t27","category":"math","name":"e8_lie_algebra","module":"E8LieAlgebra","lines":390,"bytes":14031},{"path":"math/gf_competitive.t27","category":"math","name":"gf_competitive","module":"GFCompetitive","lines":122,"bytes":4213},{"path":"math/pellis_precision_verify.t27","category":"math","name":"pellis_precision_verify","module":"PellisPrecision","lines":204,"bytes":8117},{"path":"math/phi_split_optimality.t27","category":"math","name":"phi_split_optimality","module":"PhiSplitOptimality","lines":336,"bytes":12692},{"path":"math/phi_universal_attractor.t27","category":"math","name":"phi_universal_attractor","module":"PhiUniversalAttractor","lines":333,"bytes":12924},{"path":"math/property_test_template.t27","category":"math","name":"property_test_template","module":"PropertyTestTemplate","lines":513,"bytes":16168},{"path":"math/radix_economy.t27","category":"math","name":"radix_economy","module":"RadixEconomy","lines":331,"bytes":10783},{"path":"math/sacred_physics.t27","category":"math","name":"sacred_physics","module":"SacredPhysics","lines":461,"bytes":18991},{"path":"math/zamolodchikov_e8.t27","category":"math","name":"zamolodchikov_e8","module":"ZamolodchikovE8","lines":324,"bytes":11409},{"path":"memory/formula_embed.t27","category":"memory","name":"formula_embed","module":"FormulaEmbed","lines":202,"bytes":5767},{"path":"memory/memory_primitives.t27","category":"memory","name":"memory_primitives","module":"MemoryPrimitives","lines":180,"bytes":5305},{"path":"memory/notebooklm.t27","category":"memory","name":"notebooklm","module":"NotebookLM","lines":486,"bytes":18329},{"path":"memory/semantic_search.t27","category":"memory","name":"semantic_search","module":"SemanticSearch","lines":141,"bytes":4471},{"path":"ml/activation/elu_activation.t27","category":"ml","name":"elu_activation","module":"Elu","lines":117,"bytes":5464},{"path":"ml/activation/gelu_activation.t27","category":"ml","name":"gelu_activation","module":"Gelu","lines":68,"bytes":3490},{"path":"ml/activation/gelu_approx_activation.t27","category":"ml","name":"gelu_approx_activation","module":"GeluApprox","lines":82,"bytes":4233},{"path":"ml/activation/leaky_relu_activation.t27","category":"ml","name":"leaky_relu_activation","module":"LeakyRelu","lines":95,"bytes":4569},{"path":"ml/activation/relu_activation.t27","category":"ml","name":"relu_activation","module":"Relu","lines":62,"bytes":3341},{"path":"ml/activation/sigmoid_activation.t27","category":"ml","name":"sigmoid_activation","module":"Sigmoid","lines":63,"bytes":3005},{"path":"ml/activation/silu_swish_activation.t27","category":"ml","name":"silu_swish_activation","module":"SiluSwish","lines":88,"bytes":4161},{"path":"ml/activation/silu_swish_vbt_activation.t27","category":"ml","name":"silu_swish_vbt_activation","module":"SiluSwishVbt","lines":16,"bytes":674},{"path":"ml/activation/softmax.t27","category":"ml","name":"softmax","module":"Softmax","lines":83,"bytes":4349},{"path":"ml/activation/tanh_activation.t27","category":"ml","name":"tanh_activation","module":"Tanh","lines":54,"bytes":2372},{"path":"ml/layers/avgpool2d_layer.t27","category":"ml","name":"avgpool2d_layer","module":"Avgpool2d","lines":123,"bytes":6532},{"path":"ml/layers/batchnorm_layer.t27","category":"ml","name":"batchnorm_layer","module":"Batchnorm","lines":111,"bytes":5804},{"path":"ml/layers/conv2d_layer.t27","category":"ml","name":"conv2d_layer","module":"Conv2d","lines":92,"bytes":4236},{"path":"ml/layers/dense_layer.t27","category":"ml","name":"dense_layer","module":"Dense","lines":90,"bytes":4267},{"path":"ml/layers/dropout_layer.t27","category":"ml","name":"dropout_layer","module":"Dropout","lines":70,"bytes":3702},{"path":"ml/layers/embedding_layer.t27","category":"ml","name":"embedding_layer","module":"Embedding","lines":121,"bytes":5785},{"path":"ml/layers/flatten_layer.t27","category":"ml","name":"flatten_layer","module":"Flatten","lines":114,"bytes":5624},{"path":"ml/layers/layernorm_layer.t27","category":"ml","name":"layernorm_layer","module":"Layernorm","lines":36,"bytes":1927},{"path":"ml/layers/maxpool2d_layer.t27","category":"ml","name":"maxpool2d_layer","module":"Maxpool2d","lines":74,"bytes":4252},{"path":"ml/layers/residual_connection.t27","category":"ml","name":"residual_connection","module":"Residual","lines":27,"bytes":1408},{"path":"ml/loss/binary_crossentropy_loss.t27","category":"ml","name":"binary_crossentropy_loss","module":"BinaryCe","lines":120,"bytes":5850},{"path":"ml/loss/contrastive_loss.t27","category":"ml","name":"contrastive_loss","module":"ContrastiveLoss","lines":35,"bytes":1858},{"path":"ml/loss/cross_entropy_loss.t27","category":"ml","name":"cross_entropy_loss","module":"CrossEntropy","lines":82,"bytes":4052},{"path":"ml/loss/huber_loss.t27","category":"ml","name":"huber_loss","module":"HuberLoss","lines":35,"bytes":1823},{"path":"ml/loss/kl_divergence.t27","category":"ml","name":"kl_divergence","module":"KlDivergence","lines":27,"bytes":1319},{"path":"ml/loss/mse_loss.t27","category":"ml","name":"mse_loss","module":"MseLoss","lines":35,"bytes":1788},{"path":"ml/optimizer/adagrad.t27","category":"ml","name":"adagrad","module":"Adagrad","lines":113,"bytes":5650},{"path":"ml/optimizer/adam.t27","category":"ml","name":"adam","module":"Adam","lines":47,"bytes":2422},{"path":"ml/optimizer/adamw.t27","category":"ml","name":"adamw","module":"Adamw","lines":306,"bytes":12635},{"path":"ml/optimizer/lamb.t27","category":"ml","name":"lamb","module":"Lamb","lines":134,"bytes":6076},{"path":"ml/optimizer/lr_scheduler.t27","category":"ml","name":"lr_scheduler","module":"Scheduler","lines":150,"bytes":7274},{"path":"ml/optimizer/rmsprop.t27","category":"ml","name":"rmsprop","module":"Rmsprop","lines":43,"bytes":2269},{"path":"ml/optimizer/sgd.t27","category":"ml","name":"sgd","module":"Sgd","lines":104,"bytes":4735},{"path":"ml/optimizer/sgd_momentum.t27","category":"ml","name":"sgd_momentum","module":"SgdMomentum","lines":280,"bytes":13039},{"path":"ml/pathway/mlp.t27","category":"ml","name":"mlp","module":"Mlp","lines":112,"bytes":5025},{"path":"ml/recurrent/attention_mechanism.t27","category":"ml","name":"attention_mechanism","module":"Attention","lines":95,"bytes":5259},{"path":"ml/recurrent/bilstm.t27","category":"ml","name":"bilstm","module":"Bilstm","lines":134,"bytes":6356},{"path":"ml/recurrent/gru_cell.t27","category":"ml","name":"gru_cell","module":"Gru","lines":138,"bytes":6267},{"path":"ml/recurrent/lstm_cell.t27","category":"ml","name":"lstm_cell","module":"Lstm","lines":142,"bytes":6603},{"path":"ml/recurrent/lstm_single.t27","category":"ml","name":"lstm_single","module":"LstmCell","lines":127,"bytes":6220},{"path":"ml/recurrent/rnn_cell.t27","category":"ml","name":"rnn_cell","module":"RnnCell","lines":120,"bytes":5605},{"path":"ml/recurrent/self_attention.t27","category":"ml","name":"self_attention","module":"SelfAttention","lines":57,"bytes":2662},{"path":"ml/recurrent/seq2seq.t27","category":"ml","name":"seq2seq","module":"Seq2seq","lines":121,"bytes":6457},{"path":"ml/rl/advantage_estimator.t27","category":"ml","name":"advantage_estimator","module":"Advantage","lines":128,"bytes":6229},{"path":"ml/rl/dqn.t27","category":"ml","name":"dqn","module":"Dqn","lines":119,"bytes":5990},{"path":"ml/rl/dqn_target_network.t27","category":"ml","name":"dqn_target_network","module":"DqnTarget","lines":112,"bytes":5334},{"path":"ml/rl/ppo_actor.t27","category":"ml","name":"ppo_actor","module":"PpoActor","lines":155,"bytes":7732},{"path":"ml/rl/ppo_clip_loss.t27","category":"ml","name":"ppo_clip_loss","module":"PpoClipLoss","lines":141,"bytes":7501},{"path":"ml/rl/ppo_critic.t27","category":"ml","name":"ppo_critic","module":"PpoCritic","lines":106,"bytes":5376},{"path":"ml/rl/sac_actor.t27","category":"ml","name":"sac_actor","module":"SacActor","lines":128,"bytes":6327},{"path":"ml/rl/sac_critic.t27","category":"ml","name":"sac_critic","module":"SacCritic","lines":164,"bytes":7664},{"path":"ml/transformer/encoder_block.t27","category":"ml","name":"encoder_block","module":"EncoderBlock","lines":117,"bytes":5609},{"path":"ml/transformer/feed_forward.t27","category":"ml","name":"feed_forward","module":"FeedForward","lines":437,"bytes":20203},{"path":"ml/transformer/feed_forward_network.t27","category":"ml","name":"feed_forward_network","module":"FeedForward","lines":42,"bytes":2390},{"path":"ml/transformer/mha_block.t27","category":"ml","name":"mha_block","module":"MHABlock","lines":383,"bytes":17868},{"path":"ml/transformer/multi_head_attention.t27","category":"ml","name":"multi_head_attention","module":"MultiHeadAttn","lines":38,"bytes":1951},{"path":"ml/transformer/multi_head_attn.t27","category":"ml","name":"multi_head_attn","module":"MultiHeadAttention","lines":464,"bytes":21855},{"path":"ml/transformer/norm.t27","category":"ml","name":"norm","module":"LayerNorm","lines":478,"bytes":23093},{"path":"ml/transformer/positional_enc.t27","category":"ml","name":"positional_enc","module":"PositionalEncoding","lines":379,"bytes":17042},{"path":"ml/transformer/positional_encoding.t27","category":"ml","name":"positional_encoding","module":"PositionalEnc","lines":42,"bytes":2296},{"path":"neural/forward_pass.t27","category":"neural","name":"forward_pass","module":"forward_pass","lines":1095,"bytes":32248},{"path":"nn/attention.t27","category":"nn","name":"attention","module":"SacredAttention","lines":634,"bytes":22599},{"path":"nn/hslm.t27","category":"nn","name":"hslm","module":"HSLM","lines":632,"bytes":21721},{"path":"nn/phi_rope.t27","category":"nn","name":"phi_rope","module":null,"lines":47,"bytes":1243},{"path":"nn/sacred_attention.t27","category":"nn","name":"sacred_attention","module":null,"lines":50,"bytes":1242},{"path":"numeric/bigint.t27","category":"numeric","name":"bigint","module":"BigInt","lines":391,"bytes":14358},{"path":"numeric/formats.t27","category":"numeric","name":"formats","module":"Formats","lines":400,"bytes":13628},{"path":"numeric/gf12.t27","category":"numeric","name":"gf12","module":"GF12","lines":482,"bytes":13953},{"path":"numeric/gf16.t27","category":"numeric","name":"gf16","module":"triformat","lines":3392,"bytes":100829},{"path":"numeric/gf20.t27","category":"numeric","name":"gf20","module":"GF20","lines":469,"bytes":13578},{"path":"numeric/gf24.t27","category":"numeric","name":"gf24","module":"GF24","lines":469,"bytes":13626},{"path":"numeric/gf32.t27","category":"numeric","name":"gf32","module":"GF32","lines":480,"bytes":14168},{"path":"numeric/gf4.t27","category":"numeric","name":"gf4","module":"GF4","lines":306,"bytes":9145},{"path":"numeric/gf8.t27","category":"numeric","name":"gf8","module":"GF8","lines":522,"bytes":15315},{"path":"numeric/gf_competitive.t27","category":"numeric","name":"gf_competitive","module":"GFCompetitive","lines":108,"bytes":3355},{"path":"numeric/goldenfloat_family.t27","category":"numeric","name":"goldenfloat_family","module":"GoldenFloatFamily","lines":411,"bytes":14364},{"path":"numeric/pellis_verify.t27","category":"numeric","name":"pellis_verify","module":"PellisVerify","lines":65,"bytes":2169},{"path":"numeric/phi_ratio.t27","category":"numeric","name":"phi_ratio","module":"PhiRatio","lines":653,"bytes":24250},{"path":"numeric/requant_boundary.t27","category":"numeric","name":"requant_boundary","module":"RequantBoundary","lines":217,"bytes":9731},{"path":"numeric/tf3.t27","category":"numeric","name":"tf3","module":"triformat","lines":1662,"bytes":47300},{"path":"numeric/trinity_numeric_surface.t27","category":"numeric","name":"trinity_numeric_surface","module":"trinity","lines":38,"bytes":1703},{"path":"physics/chimera_best_gamma.t27","category":"physics","name":"chimera_best_gamma","module":"chimera","lines":20,"bytes":468},{"path":"physics/e8_lqg_bridge.t27","category":"physics","name":"e8_lqg_bridge","module":null,"lines":113,"bytes":2853},{"path":"physics/formula_discovery.t27","category":"physics","name":"formula_discovery","module":"FormulaDiscovery","lines":158,"bytes":4536},{"path":"physics/formula_registry.t27","category":"physics","name":"formula_registry","module":null,"lines":213,"bytes":6261},{"path":"physics/gamma-conflict.t27","category":"physics","name":"gamma-conflict","module":null,"lines":256,"bytes":6580},{"path":"physics/gamma_conjecture.t27","category":"physics","name":"gamma_conjecture","module":"GammaConjecture","lines":277,"bytes":11590},{"path":"physics/gi1_analysis.t27","category":"physics","name":"gi1_analysis","module":"GI1Analysis","lines":238,"bytes":9763},{"path":"physics/hslm_benchmark.t27","category":"physics","name":"hslm_benchmark","module":null,"lines":90,"bytes":2287},{"path":"physics/lqg_cs_bridge.t27","category":"physics","name":"lqg_cs_bridge","module":null,"lines":141,"bytes":4725},{"path":"physics/lqg_entropy.t27","category":"physics","name":"lqg_entropy","module":null,"lines":67,"bytes":2841},{"path":"physics/p2_brain_physics.t27","category":"physics","name":"p2_brain_physics","module":"P2Brain","lines":165,"bytes":6932},{"path":"physics/pellis-formulas.t27","category":"physics","name":"pellis-formulas","module":"PellisFormulas","lines":67,"bytes":2116},{"path":"physics/quantum.t27","category":"physics","name":"quantum","module":null,"lines":72,"bytes":1715},{"path":"physics/sacred_verification.t27","category":"physics","name":"sacred_verification","module":"SacredVerification","lines":605,"bytes":24309},{"path":"physics/su2_chern_simons.t27","category":"physics","name":"su2_chern_simons","module":"SU2ChernSimons","lines":349,"bytes":12227},{"path":"physics/zamolodchikov_4d_conjecture.t27","category":"physics","name":"zamolodchikov_4d_conjecture","module":"Zamolodchikov4DConjecture","lines":254,"bytes":10731},{"path":"pins/emitter_xdc.t27","category":"pins","name":"emitter_xdc","module":"EmitterXDC","lines":295,"bytes":9345},{"path":"pins/ir.t27","category":"pins","name":"ir","module":"PinsIR","lines":391,"bytes":11981},{"path":"pins/parser.t27","category":"pins","name":"parser","module":"PinsParser","lines":583,"bytes":17861},{"path":"pipeline/benchmarks.t27","category":"pipeline","name":"benchmarks","module":"PipelineBenchmarks","lines":86,"bytes":2598},{"path":"pipeline/e2e_test.t27","category":"pipeline","name":"e2e_test","module":"PipelineE2E","lines":151,"bytes":5240},{"path":"pipeline/experience_save.t27","category":"pipeline","name":"experience_save","module":"ExperienceSave","lines":127,"bytes":3752},{"path":"portable/relay_observer.t27","category":"portable","name":"relay_observer","module":"portable","lines":651,"bytes":21457},{"path":"provider/adapters.t27","category":"provider","name":"adapters","module":"provider","lines":619,"bytes":16564},{"path":"provider/schema.t27","category":"provider","name":"schema","module":"provider","lines":605,"bytes":15237},{"path":"provider/stream.t27","category":"provider","name":"stream","module":"provider","lines":612,"bytes":16334},{"path":"provider/transform.t27","category":"provider","name":"transform","module":"provider","lines":522,"bytes":15346},{"path":"queen/brain_summaries.t27","category":"queen","name":"brain_summaries","module":"BrainSummaries","lines":395,"bytes":14418},{"path":"queen/lotus.t27","category":"queen","name":"lotus","module":"QueenLotus","lines":804,"bytes":26358},{"path":"queen/task_analysis.t27","category":"queen","name":"task_analysis","module":"queen","lines":178,"bytes":5115},{"path":"runtime/execute.t27","category":"runtime","name":"execute","module":"runtime","lines":735,"bytes":19042},{"path":"runtime/instance.t27","category":"runtime","name":"instance","module":"runtime","lines":780,"bytes":20135},{"path":"runtime/process.t27","category":"runtime","name":"process","module":"runtime","lines":762,"bytes":19101},{"path":"sacred/cosmology.t27","category":"sacred","name":"cosmology","module":"TriCosmology","lines":16,"bytes":629},{"path":"sacred/dark_matter.t27","category":"sacred","name":"dark_matter","module":"dark_matter","lines":16,"bytes":629},{"path":"sacred/gravity.t27","category":"sacred","name":"gravity","module":"TriGravity","lines":16,"bytes":625},{"path":"sacred/monopoles.t27","category":"sacred","name":"monopoles","module":"TriMonopoles","lines":16,"bytes":629},{"path":"sacred/quantum.t27","category":"sacred","name":"quantum","module":"TriQuantum","lines":16,"bytes":625},{"path":"sacred/quantum_gravity.t27","category":"sacred","name":"quantum_gravity","module":"quantum_gravity","lines":16,"bytes":637},{"path":"sacred/sacred_constants.t27","category":"sacred","name":"sacred_constants","module":"SacredConstants","lines":24,"bytes":1160},{"path":"sacred/sacred_governance.t27","category":"sacred","name":"sacred_governance","module":"SacredGovernance","lines":85,"bytes":3036},{"path":"sacred/sacred_identity.t27","category":"sacred","name":"sacred_identity","module":"SacredIdentity","lines":77,"bytes":2757},{"path":"sacred/superconductivity.t27","category":"sacred","name":"superconductivity","module":"TriSuperconductivity","lines":16,"bytes":645},{"path":"sandbox/health.t27","category":"sandbox","name":"health","module":"sandbox","lines":43,"bytes":1234},{"path":"sandbox/https_enforce.t27","category":"sandbox","name":"https_enforce","module":"sandbox","lines":224,"bytes":7449},{"path":"sandbox/modules.t27","category":"sandbox","name":"modules","module":"sandbox","lines":22,"bytes":536},{"path":"sandbox/orphan_detection.t27","category":"sandbox","name":"orphan_detection","module":"sandbox","lines":257,"bytes":9041},{"path":"sandbox/session_timeout.t27","category":"sandbox","name":"session_timeout","module":"sandbox","lines":195,"bytes":6252},{"path":"server/agent-runner.t27","category":"server","name":"agent-runner","module":"AgentRunner","lines":249,"bytes":8126},{"path":"server/api.t27","category":"server","name":"api","module":"Api","lines":148,"bytes":4198},{"path":"server/http.t27","category":"server","name":"http","module":"server","lines":453,"bytes":12359},{"path":"server/mdns.t27","category":"server","name":"mdns","module":"server","lines":567,"bytes":14817},{"path":"server/project.t27","category":"server","name":"project","module":"Project","lines":206,"bytes":6094},{"path":"server/provider.t27","category":"server","name":"provider","module":"Provider","lines":257,"bytes":8105},{"path":"server/router.t27","category":"server","name":"router","module":"server","lines":575,"bytes":15018},{"path":"server/routes.t27","category":"server","name":"routes","module":"Routes","lines":36,"bytes":690},{"path":"server/session.t27","category":"server","name":"session","module":"Session","lines":273,"bytes":8497},{"path":"server/sse.t27","category":"server","name":"sse","module":"server","lines":563,"bytes":15052},{"path":"server/vm.t27","category":"server","name":"vm","module":"VM","lines":593,"bytes":22613},{"path":"shell/environment.t27","category":"shell","name":"environment","module":"ShellEnvironment","lines":304,"bytes":9509},{"path":"shell/process.t27","category":"shell","name":"process","module":"ShellProcess","lines":305,"bytes":8856},{"path":"shell/schema.t27","category":"shell","name":"schema","module":"Shell","lines":406,"bytes":12544},{"path":"storage/kv.t27","category":"storage","name":"kv","module":"StorageKv","lines":159,"bytes":5529},{"path":"storage/lock.t27","category":"storage","name":"lock","module":"StorageLock","lines":179,"bytes":6388},{"path":"storage/migrate.t27","category":"storage","name":"migrate","module":"StorageMigrate","lines":193,"bytes":6892},{"path":"storage/schema.t27","category":"storage","name":"schema","module":"Storage","lines":146,"bytes":4070},{"path":"sync/index.t27","category":"sync","name":"index","module":"sync","lines":779,"bytes":20867},{"path":"sync/schema.t27","category":"sync","name":"schema","module":"sync","lines":722,"bytes":19125},{"path":"ternary/bigint.t27","category":"ternary","name":"bigint","module":"TernaryBigInt","lines":1441,"bytes":43916},{"path":"ternary/hybrid_arithmetic.t27","category":"ternary","name":"hybrid_arithmetic","module":"HybridArithmetic","lines":492,"bytes":18595},{"path":"ternary/hybrid_bigint.t27","category":"ternary","name":"hybrid_bigint","module":"hybrid_bigint","lines":1065,"bytes":30092},{"path":"ternary/packed_trit.t27","category":"ternary","name":"packed_trit","module":"PackedTrit","lines":430,"bytes":16022},{"path":"test_framework/core.t27","category":"test_framework","name":"core","module":"core","lines":317,"bytes":10877},{"path":"test_framework/graph_drift_detection.t27","category":"test_framework","name":"graph_drift_detection","module":"GraphDriftDetection","lines":650,"bytes":23027},{"path":"test_framework/property_test_template.t27","category":"test_framework","name":"property_test_template","module":"PBTTemplate","lines":433,"bytes":16346},{"path":"test_framework/runner.t27","category":"test_framework","name":"runner","module":"runner","lines":618,"bytes":19960},{"path":"test_framework/verilog_bench_harness.t27","category":"test_framework","name":"verilog_bench_harness","module":"VerilogBenchHarness","lines":460,"bytes":17844},{"path":"tools/registry.t27","category":"tools","name":"registry","module":"ToolsRegistry","lines":523,"bytes":16726},{"path":"tools/schema.t27","category":"tools","name":"schema","module":"Tools","lines":523,"bytes":16580},{"path":"tools/tri_to_t27_converter.t27","category":"tools","name":"tri_to_t27_converter","module":"TriToT27Converter","lines":406,"bytes":16958},{"path":"tri/agent/agent_run.t27","category":"tri","name":"agent_run","module":"AgentRun","lines":16,"bytes":624},{"path":"tri/agent/agents.t27","category":"tri","name":"agents","module":"agents","lines":16,"bytes":619},{"path":"tri/agent/autonomous_lifecycle.t27","category":"tri","name":"autonomous_lifecycle","module":"TriAutonomousLifecycle","lines":73,"bytes":2953},{"path":"tri/agent/autonomous_universe.t27","category":"tri","name":"autonomous_universe","module":"AutonomousUniverse","lines":16,"bytes":645},{"path":"tri/agent/eternal_monitor.t27","category":"tri","name":"eternal_monitor","module":"EternalMonitor","lines":70,"bytes":2500},{"path":"tri/agent/experience_hooks.t27","category":"tri","name":"experience_hooks","module":"experience_hooks","lines":16,"bytes":639},{"path":"tri/agent/faculty_board.t27","category":"tri","name":"faculty_board","module":"FacultyBoard","lines":63,"bytes":2412},{"path":"tri/agent/governance_agent.t27","category":"tri","name":"governance_agent","module":"GovernanceAgent","lines":98,"bytes":3447},{"path":"tri/agent/handoff.t27","category":"tri","name":"handoff","module":"Handoff","lines":67,"bytes":2325},{"path":"tri/agent/memory.t27","category":"tri","name":"memory","module":"memory","lines":16,"bytes":619},{"path":"tri/agent/swarm_agents.t27","category":"tri","name":"swarm_agents","module":"SwarmAgents","lines":143,"bytes":5212},{"path":"tri/collections/array.t27","category":"tri","name":"array","module":"TriArray","lines":123,"bytes":5387},{"path":"tri/collections/bitmap.t27","category":"tri","name":"bitmap","module":"TriBitmap","lines":101,"bytes":4201},{"path":"tri/collections/bitset.t27","category":"tri","name":"bitset","module":"TriBitset","lines":97,"bytes":3588},{"path":"tri/collections/bitvector.t27","category":"tri","name":"bitvector","module":"TriBitvector","lines":93,"bytes":4192},{"path":"tri/collections/btree.t27","category":"tri","name":"btree","module":"TriBtree","lines":85,"bytes":4316},{"path":"tri/collections/circular_buffer.t27","category":"tri","name":"circular_buffer","module":"TriCircularBuffer","lines":79,"bytes":3676},{"path":"tri/collections/context.t27","category":"tri","name":"context","module":"TriContext","lines":16,"bytes":625},{"path":"tri/collections/deque.t27","category":"tri","name":"deque","module":"TriDeque","lines":77,"bytes":3243},{"path":"tri/collections/either.t27","category":"tri","name":"either","module":"TriEither","lines":75,"bytes":3222},{"path":"tri/collections/interval.t27","category":"tri","name":"interval","module":"TriInterval","lines":86,"bytes":4186},{"path":"tri/collections/linked_list.t27","category":"tri","name":"linked_list","module":"TriLinkedList","lines":84,"bytes":3032},{"path":"tri/collections/list.t27","category":"tri","name":"list","module":"TriList","lines":97,"bytes":3261},{"path":"tri/collections/lockfree_stack.t27","category":"tri","name":"lockfree_stack","module":"TriLockfreeStack","lines":70,"bytes":3118},{"path":"tri/collections/lru.t27","category":"tri","name":"lru","module":"TriLru","lines":57,"bytes":2516},{"path":"tri/collections/lru_cache.t27","category":"tri","name":"lru_cache","module":"TriLruCache","lines":68,"bytes":2554},{"path":"tri/collections/map.t27","category":"tri","name":"map","module":"TriMap","lines":73,"bytes":3019},{"path":"tri/collections/maybe.t27","category":"tri","name":"maybe","module":"TriMaybe","lines":81,"bytes":3955},{"path":"tri/collections/namespace.t27","category":"tri","name":"namespace","module":"TriNamespace","lines":58,"bytes":2700},{"path":"tri/collections/option.t27","category":"tri","name":"option","module":"TriOption","lines":79,"bytes":3504},{"path":"tri/collections/priority_queue.t27","category":"tri","name":"priority_queue","module":"TriPriorityQueue","lines":78,"bytes":3380},{"path":"tri/collections/queue.t27","category":"tri","name":"queue","module":"TriQueue","lines":70,"bytes":2992},{"path":"tri/collections/result.t27","category":"tri","name":"result","module":"TriResult","lines":79,"bytes":3655},{"path":"tri/collections/ring_buffer.t27","category":"tri","name":"ring_buffer","module":"TriRing","lines":101,"bytes":4905},{"path":"tri/collections/set.t27","category":"tri","name":"set","module":"TriSet","lines":65,"bytes":2723},{"path":"tri/collections/skip_list.t27","category":"tri","name":"skip_list","module":"TriSkipList","lines":69,"bytes":3150},{"path":"tri/collections/stack.t27","category":"tri","name":"stack","module":"TriStack","lines":71,"bytes":3014},{"path":"tri/collections/state.t27","category":"tri","name":"state","module":"TriState","lines":65,"bytes":2474},{"path":"tri/collections/tuple.t27","category":"tri","name":"tuple","module":"TriTuple","lines":72,"bytes":2619},{"path":"tri/collections/variant.t27","category":"tri","name":"variant","module":"TriVariant","lines":63,"bytes":2922},{"path":"tri/crypto/base32.t27","category":"tri","name":"base32","module":"TriBase32","lines":78,"bytes":4129},{"path":"tri/crypto/base64.t27","category":"tri","name":"base64","module":"TriBase64","lines":97,"bytes":4954},{"path":"tri/crypto/crypto.t27","category":"tri","name":"crypto","module":"TriCrypto","lines":56,"bytes":2500},{"path":"tri/crypto/ecc.t27","category":"tri","name":"ecc","module":"TriEcc","lines":89,"bytes":4348},{"path":"tri/crypto/hex.t27","category":"tri","name":"hex","module":"TriHex","lines":64,"bytes":2929},{"path":"tri/crypto/hmac.t27","category":"tri","name":"hmac","module":"TriHmac","lines":56,"bytes":2274},{"path":"tri/crypto/reed_solomon.t27","category":"tri","name":"reed_solomon","module":"TriReedSolomon","lines":46,"bytes":2205},{"path":"tri/crypto/rsa.t27","category":"tri","name":"rsa","module":"TriRsa","lines":83,"bytes":4009},{"path":"tri/crypto/sha256.t27","category":"tri","name":"sha256","module":"TriSha256","lines":67,"bytes":2952},{"path":"tri/encoding/bson.t27","category":"tri","name":"bson","module":"TriBson","lines":70,"bytes":3544},{"path":"tri/encoding/csv.t27","category":"tri","name":"csv","module":"TriCsv","lines":72,"bytes":3155},{"path":"tri/encoding/html.t27","category":"tri","name":"html","module":"TriHtml","lines":48,"bytes":2223},{"path":"tri/encoding/json.t27","category":"tri","name":"json","module":"TriJson","lines":67,"bytes":2655},{"path":"tri/encoding/markup.t27","category":"tri","name":"markup","module":"TriMarkup","lines":53,"bytes":2716},{"path":"tri/encoding/mime.t27","category":"tri","name":"mime","module":"TriMime","lines":57,"bytes":2794},{"path":"tri/encoding/msgpack.t27","category":"tri","name":"msgpack","module":"TriMsgpack","lines":93,"bytes":4485},{"path":"tri/encoding/xml.t27","category":"tri","name":"xml","module":"TriXml","lines":48,"bytes":2195},{"path":"tri/graph/bellman_ford.t27","category":"tri","name":"bellman_ford","module":"TriBellmanFord","lines":37,"bytes":1918},{"path":"tri/graph/dijkstra.t27","category":"tri","name":"dijkstra","module":"TriDijkstra","lines":38,"bytes":1854},{"path":"tri/graph/disjoint_set.t27","category":"tri","name":"disjoint_set","module":"TriDisjointSet","lines":93,"bytes":4626},{"path":"tri/graph/graph.t27","category":"tri","name":"graph","module":"TriGraph","lines":61,"bytes":2379},{"path":"tri/graph/graph_bfs.t27","category":"tri","name":"graph_bfs","module":"TriGraphBfs","lines":113,"bytes":5554},{"path":"tri/graph/graph_dfs.t27","category":"tri","name":"graph_dfs","module":"TriGraphDfs","lines":38,"bytes":1808},{"path":"tri/graph/prims_mst.t27","category":"tri","name":"prims_mst","module":"TriPrimsMst","lines":38,"bytes":1795},{"path":"tri/graph/topological_sort.t27","category":"tri","name":"topological_sort","module":"TriTopological","lines":47,"bytes":2082},{"path":"tri/io/compress.t27","category":"tri","name":"compress","module":"TriCompress","lines":58,"bytes":3115},{"path":"tri/io/filesystem.t27","category":"tri","name":"filesystem","module":"TriFilesystem","lines":113,"bytes":5345},{"path":"tri/io/fs.t27","category":"tri","name":"fs","module":"TriFs","lines":73,"bytes":2757},{"path":"tri/io/io.t27","category":"tri","name":"io","module":"TriIo","lines":67,"bytes":3086},{"path":"tri/io/reader.t27","category":"tri","name":"reader","module":"TriReader","lines":65,"bytes":2550},{"path":"tri/io/writer.t27","category":"tri","name":"writer","module":"TriWriter","lines":66,"bytes":2628},{"path":"tri/io/zip.t27","category":"tri","name":"zip","module":"TriZipper","lines":77,"bytes":2847},{"path":"tri/math/bezier.t27","category":"tri","name":"bezier","module":"TriBezier","lines":75,"bytes":3811},{"path":"tri/math/constants.t27","category":"tri","name":"constants","module":"TriConstants","lines":128,"bytes":5390},{"path":"tri/math/math.t27","category":"tri","name":"math","module":"TriMath","lines":16,"bytes":619},{"path":"tri/math/matrix.t27","category":"tri","name":"matrix","module":"TriMatrix","lines":96,"bytes":4182},{"path":"tri/math/measurement.t27","category":"tri","name":"measurement","module":"TriMeasurement","lines":16,"bytes":633},{"path":"tri/math/polynomial.t27","category":"tri","name":"polynomial","module":"TriPolynomial","lines":80,"bytes":3851},{"path":"tri/math/probability.t27","category":"tri","name":"probability","module":"TriProbability","lines":67,"bytes":2602},{"path":"tri/math/statistics.t27","category":"tri","name":"statistics","module":"TriStatistics","lines":77,"bytes":2735},{"path":"tri/net/async.t27","category":"tri","name":"async","module":"TriAsync","lines":86,"bytes":3896},{"path":"tri/net/async_stream.t27","category":"tri","name":"async_stream","module":"TriAsyncStream","lines":86,"bytes":4087},{"path":"tri/net/channel.t27","category":"tri","name":"channel","module":"TriChannel","lines":67,"bytes":2629},{"path":"tri/net/cloud.t27","category":"tri","name":"cloud","module":"TriCloud","lines":16,"bytes":621},{"path":"tri/net/http.t27","category":"tri","name":"http","module":"TriHttp","lines":133,"bytes":6147},{"path":"tri/net/net.t27","category":"tri","name":"net","module":"TriNet","lines":71,"bytes":3117},{"path":"tri/net/url.t27","category":"tri","name":"url","module":"TriUrl","lines":83,"bytes":4112},{"path":"tri/pipeline/batch_runner.t27","category":"tri","name":"batch_runner","module":"BatchRunner","lines":115,"bytes":4376},{"path":"tri/pipeline/builder.t27","category":"tri","name":"builder","module":"TriBuilder","lines":107,"bytes":3957},{"path":"tri/pipeline/cloud_orchestrator.t27","category":"tri","name":"cloud_orchestrator","module":"CloudOrchestrator","lines":16,"bytes":643},{"path":"tri/pipeline/codegen.t27","category":"tri","name":"codegen","module":"TestSpec","lines":27,"bytes":1147},{"path":"tri/pipeline/pipeline.t27","category":"tri","name":"pipeline","module":"TriPipeline","lines":16,"bytes":627},{"path":"tri/pipeline/pipeline_parallel.t27","category":"tri","name":"pipeline_parallel","module":"TriPipelineParallel","lines":50,"bytes":1900},{"path":"tri/pipeline/spec_parser.t27","category":"tri","name":"spec_parser","module":"TriSpecParser","lines":16,"bytes":632},{"path":"tri/pipeline/spec_writer.t27","category":"tri","name":"spec_writer","module":"SpecWriter","lines":46,"bytes":1622},{"path":"tri/pipeline/workflow.t27","category":"tri","name":"workflow","module":"Workflow","lines":31,"bytes":1263},{"path":"tri/pipeline/workflow_executor.t27","category":"tri","name":"workflow_executor","module":"WorkflowExecutor","lines":16,"bytes":641},{"path":"tri/pipeline/workflow_parser.t27","category":"tri","name":"workflow_parser","module":"WorkflowParser","lines":16,"bytes":637},{"path":"tri/search/aho_corasick.t27","category":"tri","name":"aho_corasick","module":"TriAhoCorasick","lines":99,"bytes":4285},{"path":"tri/search/bloom_filter.t27","category":"tri","name":"bloom_filter","module":"TriBloomFilter","lines":58,"bytes":2868},{"path":"tri/search/boyer_moore.t27","category":"tri","name":"boyer_moore","module":"TriBoyerMoore","lines":72,"bytes":3868},{"path":"tri/search/knuth_morris_pratt.t27","category":"tri","name":"knuth_morris_pratt","module":"TriKmp","lines":85,"bytes":4347},{"path":"tri/search/match.t27","category":"tri","name":"match","module":null,"lines":68,"bytes":3333},{"path":"tri/search/pattern.t27","category":"tri","name":"pattern","module":"TriPattern","lines":61,"bytes":3040},{"path":"tri/search/rabin_karp.t27","category":"tri","name":"rabin_karp","module":"TriRabinKarp","lines":73,"bytes":3867},{"path":"tri/search/regex.t27","category":"tri","name":"regex","module":"TriRegex","lines":91,"bytes":4367},{"path":"tri/search/regex_advanced.t27","category":"tri","name":"regex_advanced","module":"TriRegexAdvanced","lines":62,"bytes":2766},{"path":"tri/search/search.t27","category":"tri","name":"search","module":"TriSearch","lines":63,"bytes":3032},{"path":"tri/sort/counting_sort.t27","category":"tri","name":"counting_sort","module":"TriCountingSort","lines":27,"bytes":1352},{"path":"tri/sort/heap_sort.t27","category":"tri","name":"heap_sort","module":"TriHeapSort","lines":27,"bytes":1258},{"path":"tri/sort/insertion_sort.t27","category":"tri","name":"insertion_sort","module":"TriInsertionSort","lines":27,"bytes":1262},{"path":"tri/sort/merge_sort.t27","category":"tri","name":"merge_sort","module":"TriMergeSort","lines":37,"bytes":1665},{"path":"tri/sort/quick_sort.t27","category":"tri","name":"quick_sort","module":"TriQuickSort","lines":41,"bytes":1923},{"path":"tri/sort/radix_sort.t27","category":"tri","name":"radix_sort","module":"TriRadixSort","lines":56,"bytes":2879},{"path":"tri/sort/selection_sort.t27","category":"tri","name":"selection_sort","module":"TriSelectionSort","lines":27,"bytes":1247},{"path":"tri/sort/shell_sort.t27","category":"tri","name":"shell_sort","module":"TriShellSort","lines":27,"bytes":1257},{"path":"tri/sort/sort.t27","category":"tri","name":"sort","module":"TriSort","lines":57,"bytes":3188},{"path":"tri/sort/tim_sort.t27","category":"tri","name":"tim_sort","module":"TriTimSort","lines":27,"bytes":1311},{"path":"tri/trees/avl_tree.t27","category":"tri","name":"avl_tree","module":"TriAvlTree","lines":76,"bytes":2769},{"path":"tri/trees/b_tree.t27","category":"tri","name":"b_tree","module":"TriBTree","lines":96,"bytes":4616},{"path":"tri/trees/fenwick_tree.t27","category":"tri","name":"fenwick_tree","module":"TriFenwick","lines":101,"bytes":4544},{"path":"tri/trees/kd_tree.t27","category":"tri","name":"kd_tree","module":"TriKdTree","lines":89,"bytes":3998},{"path":"tri/trees/octree.t27","category":"tri","name":"octree","module":"TriOctree","lines":103,"bytes":4639},{"path":"tri/trees/quadtree.t27","category":"tri","name":"quadtree","module":"TriQuadtree","lines":110,"bytes":5036},{"path":"tri/trees/red_black_tree.t27","category":"tri","name":"red_black_tree","module":"TriRbTree","lines":81,"bytes":2892},{"path":"tri/trees/rtree.t27","category":"tri","name":"rtree","module":"TriRtree","lines":88,"bytes":3876},{"path":"tri/trees/segment_tree.t27","category":"tri","name":"segment_tree","module":"TriSegmentTree","lines":67,"bytes":3238},{"path":"tri/trees/splay_tree.t27","category":"tri","name":"splay_tree","module":"TriSplayTree","lines":76,"bytes":2802},{"path":"tri/trees/suffix_array.t27","category":"tri","name":"suffix_array","module":"TriSuffixArray","lines":83,"bytes":4311},{"path":"tri/trees/tree.t27","category":"tri","name":"tree","module":"TriTree","lines":85,"bytes":3768},{"path":"tri/trees/trie.t27","category":"tri","name":"trie","module":"TriTrie","lines":93,"bytes":4263},{"path":"tri/utils/args.t27","category":"tri","name":"args","module":null,"lines":86,"bytes":4133},{"path":"tri/utils/arrow_time.t27","category":"tri","name":"arrow_time","module":"arrow_time","lines":16,"bytes":627},{"path":"tri/utils/bytes.t27","category":"tri","name":"bytes","module":"TriBytes","lines":106,"bytes":3875},{"path":"tri/utils/color.t27","category":"tri","name":"color","module":"TriColor","lines":95,"bytes":4563},{"path":"tri/utils/colors.t27","category":"tri","name":"colors","module":"TriColors","lines":16,"bytes":623},{"path":"tri/utils/config.t27","category":"tri","name":"config","module":"TriConfig","lines":89,"bytes":4082},{"path":"tri/utils/error.t27","category":"tri","name":"error","module":"TriError","lines":26,"bytes":1415},{"path":"tri/utils/exit_codes.t27","category":"tri","name":"exit_codes","module":"TriExitCodes","lines":35,"bytes":1562},{"path":"tri/utils/help.t27","category":"tri","name":"help","module":"TriHelp","lines":32,"bytes":1370},{"path":"tri/utils/logger.t27","category":"tri","name":"logger","module":null,"lines":66,"bytes":2659},{"path":"tri/utils/logging.t27","category":"tri","name":"logging","module":"TriLogging","lines":102,"bytes":5075},{"path":"tri/utils/random.t27","category":"tri","name":"random","module":"TriRandom","lines":97,"bytes":4367},{"path":"tri/utils/string.t27","category":"tri","name":"string","module":"string","lines":16,"bytes":619},{"path":"tri/utils/template.t27","category":"tri","name":"template","module":"TriTemplate","lines":70,"bytes":3884},{"path":"tri/utils/terminal.t27","category":"tri","name":"terminal","module":"TriTerminal","lines":59,"bytes":2447},{"path":"tri/utils/text.t27","category":"tri","name":"text","module":"TriText","lines":67,"bytes":2793},{"path":"tri/utils/time.t27","category":"tri","name":"time","module":"TriTime","lines":93,"bytes":4138},{"path":"tri/utils/utf8.t27","category":"tri","name":"utf8","module":"TriUtf8","lines":82,"bytes":3595},{"path":"tri/utils/version.t27","category":"tri","name":"version","module":"TriVersion","lines":85,"bytes":4113},{"path":"vm/jit_semantics.t27","category":"vm","name":"jit_semantics","module":"JitSemantics","lines":319,"bytes":14011},{"path":"vsa/jones_polynomial.t27","category":"vsa","name":"jones_polynomial","module":"JonesPolynomial","lines":353,"bytes":12139},{"path":"vsa/ops.t27","category":"vsa","name":"ops","module":"VSAOps","lines":670,"bytes":27126},{"path":"vsa/packed_vsa.t27","category":"vsa","name":"packed_vsa","module":"PackedVsa","lines":291,"bytes":11657},{"path":"vsa/sdk.t27","category":"vsa","name":"sdk","module":"SDK","lines":626,"bytes":24071},{"path":"vsa/sequence_hdc.t27","category":"vsa","name":"sequence_hdc","module":"SequenceHdc","lines":342,"bytes":15174},{"path":"vsa/similarity_search.t27","category":"vsa","name":"similarity_search","module":"VSASimilaritySearch","lines":467,"bytes":15246},{"path":"vsa/vsa_core.t27","category":"vsa","name":"vsa_core","module":"vsa_core","lines":1012,"bytes":29297}]} \ No newline at end of file +{"generatedFrom":{"repo":"gHashTag/t27","commit":"8b797214d8ef685d1b8d6e61d60325e838196b03","shortCommit":"8b797214d","specsOrCompilerDirty":true},"wasmBytes":488709,"specCount":667,"totalLines":232888,"categories":{"specs/tri":150,"specs/fpga":66,"specs/ml":60,"chips/euler":49,"chips/gamma":49,"chips/phi":49,"specs/isa":16,"specs/numeric":16,"specs/physics":16,"specs/server":11,"specs/compiler":10,"specs/math":10,"specs/sacred":10,"specs/ar":8,"specs/base":8,"specs/vsa":7,"compiler/codegen":6,"specs/brain":6,"specs/github":5,"specs/lsp":5,"specs/sandbox":5,"specs/test_framework":5,"specs/benchmarks":4,"specs/config":4,"specs/git":4,"specs/memory":4,"specs/nn":4,"specs/provider":4,"specs/storage":4,"specs/ternary":4,"compiler/cli":3,"compiler/runtime":3,"specs/account":3,"specs/api":3,"specs/boards":3,"specs/demos":3,"specs/file":3,"specs/pins":3,"specs/pipeline":3,"specs/queen":3,"specs/runtime":3,"specs/shell":3,"specs/tools":3,"compiler/parser":2,"specs/bus":2,"specs/enrichment":2,"specs/sync":2,"tests":2,"bootstrap/specs":1,"compiler":1,"compiler/skill":1,"contrib/backend":1,"examples/fpga":1,"specs/auth":1,"specs/automation":1,"specs/cloud":1,"specs/conformance":1,"specs/depin":1,"specs/graph":1,"specs/hslm":1,"specs/interop":1,"specs/jit":1,"specs/neural":1,"specs/portable":1,"specs/vm":1,"root":1,"vscode-trinity-swe":1},"specs":[{"path":"bootstrap/specs/physics/formula_registry.t27","category":"bootstrap/specs","name":"formula_registry","module":null,"lines":210,"bytes":6193},{"path":"chips/euler/specs/fpga/avs_controller_48.t27","category":"chips/euler","name":"avs_controller_48","module":"avs-controller-48","lines":607,"bytes":20060},{"path":"chips/euler/specs/fpga/avs_controller_96.t27","category":"chips/euler","name":"avs_controller_96","module":"avs-controller-96","lines":735,"bytes":24836},{"path":"chips/euler/specs/fpga/avs_reconf.t27","category":"chips/euler","name":"avs_reconf","module":"avs-reconf","lines":702,"bytes":24801},{"path":"chips/euler/specs/fpga/dfs_gate.t27","category":"chips/euler","name":"dfs_gate","module":"sacred-dfs_gate","lines":600,"bytes":22017},{"path":"chips/euler/specs/fpga/drowsy_ret.t27","category":"chips/euler","name":"drowsy_ret","module":"sacred-drowsy_ret","lines":716,"bytes":26247},{"path":"chips/euler/specs/fpga/fbb_active_path.t27","category":"chips/euler","name":"fbb_active_path","module":"fbb-active-path","lines":756,"bytes":26695},{"path":"chips/euler/specs/fpga/fp8_e4m3.t27","category":"chips/euler","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652},{"path":"chips/euler/specs/fpga/fp8_e5m2.t27","category":"chips/euler","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826},{"path":"chips/euler/specs/fpga/gf128.t27","category":"chips/euler","name":"gf128","module":"GF128","lines":358,"bytes":10740},{"path":"chips/euler/specs/fpga/gf16_to_fp16.t27","category":"chips/euler","name":"gf16_to_fp16","module":"gf16-to-fp16","lines":574,"bytes":18936},{"path":"chips/euler/specs/fpga/gf16_to_posit16.t27","category":"chips/euler","name":"gf16_to_posit16","module":"gf16-to-posit16","lines":664,"bytes":20622},{"path":"chips/euler/specs/fpga/gf256.t27","category":"chips/euler","name":"gf256","module":"GF256","lines":336,"bytes":9935},{"path":"chips/euler/specs/fpga/gf32_to_fp32.t27","category":"chips/euler","name":"gf32_to_fp32","module":"gf32-to-fp32","lines":757,"bytes":25067},{"path":"chips/euler/specs/fpga/gf64.t27","category":"chips/euler","name":"gf64","module":"GF64","lines":320,"bytes":9095},{"path":"chips/euler/specs/fpga/holo_mux_x4.t27","category":"chips/euler","name":"holo_mux_x4","module":"sacred-holo_mux_x4","lines":581,"bytes":21272},{"path":"chips/euler/specs/fpga/int4.t27","category":"chips/euler","name":"int4","module":"Int4","lines":334,"bytes":8538},{"path":"chips/euler/specs/fpga/int8.t27","category":"chips/euler","name":"int8","module":"Int8","lines":493,"bytes":13029},{"path":"chips/euler/specs/fpga/lane_l_precheck.t27","category":"chips/euler","name":"lane_l_precheck","module":"sacred-lane_precheck","lines":493,"bytes":17229},{"path":"chips/euler/specs/fpga/lut_npu_81_entry.t27","category":"chips/euler","name":"lut_npu_81_entry","module":"sacred-lut_npu_81_entry","lines":419,"bytes":13484},{"path":"chips/euler/specs/fpga/nf4.t27","category":"chips/euler","name":"nf4","module":"NF4","lines":313,"bytes":8164},{"path":"chips/euler/specs/fpga/null_pe.t27","category":"chips/euler","name":"null_pe","module":"sacred-null_pe","lines":568,"bytes":20320},{"path":"chips/euler/specs/fpga/posit16.t27","category":"chips/euler","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004},{"path":"chips/euler/specs/fpga/purkinje_thermal_gate.t27","category":"chips/euler","name":"purkinje_thermal_gate","module":"purkinje-thermal-gate","lines":704,"bytes":24460},{"path":"chips/euler/specs/fpga/sparse_mask.t27","category":"chips/euler","name":"sparse_mask","module":"sacred-sparse_mask","lines":556,"bytes":17492},{"path":"chips/euler/specs/fpga/sparse_skip.t27","category":"chips/euler","name":"sparse_skip","module":"sacred-sparse_skip","lines":476,"bytes":15117},{"path":"chips/euler/specs/fpga/spec_exit.t27","category":"chips/euler","name":"spec_exit","module":"sacred-spec_exit","lines":541,"bytes":19745},{"path":"chips/euler/specs/fpga/stoch_round.t27","category":"chips/euler","name":"stoch_round","module":"sacred-stoch_round","lines":575,"bytes":19164},{"path":"chips/euler/specs/fpga/subth_clk.t27","category":"chips/euler","name":"subth_clk","module":"sacred-subth_clk","lines":478,"bytes":17539},{"path":"chips/euler/specs/numeric/binary16.t27","category":"chips/euler","name":"binary16","module":"Binary16","lines":420,"bytes":12171},{"path":"chips/euler/specs/numeric/formats.t27","category":"chips/euler","name":"formats","module":"Formats","lines":538,"bytes":17523},{"path":"chips/euler/specs/numeric/fp8_e4m3.t27","category":"chips/euler","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652},{"path":"chips/euler/specs/numeric/fp8_e5m2.t27","category":"chips/euler","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826},{"path":"chips/euler/specs/numeric/gf12.t27","category":"chips/euler","name":"gf12","module":"GF12","lines":482,"bytes":16369},{"path":"chips/euler/specs/numeric/gf128.t27","category":"chips/euler","name":"gf128","module":"triformat-gf128","lines":506,"bytes":17137},{"path":"chips/euler/specs/numeric/gf16.t27","category":"chips/euler","name":"gf16","module":"triformat-gf16","lines":3437,"bytes":101513},{"path":"chips/euler/specs/numeric/gf20.t27","category":"chips/euler","name":"gf20","module":"GF20","lines":469,"bytes":15992},{"path":"chips/euler/specs/numeric/gf24.t27","category":"chips/euler","name":"gf24","module":"GF24","lines":469,"bytes":16040},{"path":"chips/euler/specs/numeric/gf256.t27","category":"chips/euler","name":"gf256","module":"triformat-gf256","lines":595,"bytes":20164},{"path":"chips/euler/specs/numeric/gf32.t27","category":"chips/euler","name":"gf32","module":"GF32","lines":480,"bytes":16589},{"path":"chips/euler/specs/numeric/gf4.t27","category":"chips/euler","name":"gf4","module":"GF4","lines":306,"bytes":11167},{"path":"chips/euler/specs/numeric/gf64.t27","category":"chips/euler","name":"gf64","module":"triformat-gf64","lines":764,"bytes":24203},{"path":"chips/euler/specs/numeric/gf8.t27","category":"chips/euler","name":"gf8","module":"GF8","lines":522,"bytes":17732},{"path":"chips/euler/specs/numeric/goldenfloat_family.t27","category":"chips/euler","name":"goldenfloat_family","module":"GoldenFloatFamily","lines":450,"bytes":17346},{"path":"chips/euler/specs/numeric/int4.t27","category":"chips/euler","name":"int4","module":"triformat-int4","lines":766,"bytes":20533},{"path":"chips/euler/specs/numeric/int8.t27","category":"chips/euler","name":"int8","module":"triformat-int8","lines":1151,"bytes":30614},{"path":"chips/euler/specs/numeric/nf4.t27","category":"chips/euler","name":"nf4","module":"triformat-nf4","lines":839,"bytes":23771},{"path":"chips/euler/specs/numeric/phi_ratio.t27","category":"chips/euler","name":"phi_ratio","module":"PhiRatio","lines":695,"bytes":25855},{"path":"chips/euler/specs/numeric/posit16.t27","category":"chips/euler","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004},{"path":"chips/euler/specs/numeric/tri_net_formats.t27","category":"chips/euler","name":"tri_net_formats","module":"TriNetFormats","lines":993,"bytes":32170},{"path":"chips/gamma/specs/fpga/avs_controller_48.t27","category":"chips/gamma","name":"avs_controller_48","module":"avs-controller-48","lines":607,"bytes":20060},{"path":"chips/gamma/specs/fpga/avs_controller_96.t27","category":"chips/gamma","name":"avs_controller_96","module":"avs-controller-96","lines":735,"bytes":24836},{"path":"chips/gamma/specs/fpga/avs_reconf.t27","category":"chips/gamma","name":"avs_reconf","module":"avs-reconf","lines":702,"bytes":24801},{"path":"chips/gamma/specs/fpga/dfs_gate.t27","category":"chips/gamma","name":"dfs_gate","module":"sacred-dfs_gate","lines":600,"bytes":22017},{"path":"chips/gamma/specs/fpga/drowsy_ret.t27","category":"chips/gamma","name":"drowsy_ret","module":"sacred-drowsy_ret","lines":716,"bytes":26247},{"path":"chips/gamma/specs/fpga/fbb_active_path.t27","category":"chips/gamma","name":"fbb_active_path","module":"fbb-active-path","lines":756,"bytes":26695},{"path":"chips/gamma/specs/fpga/fp8_e4m3.t27","category":"chips/gamma","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652},{"path":"chips/gamma/specs/fpga/fp8_e5m2.t27","category":"chips/gamma","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826},{"path":"chips/gamma/specs/fpga/gf128.t27","category":"chips/gamma","name":"gf128","module":"GF128","lines":358,"bytes":10740},{"path":"chips/gamma/specs/fpga/gf16_to_fp16.t27","category":"chips/gamma","name":"gf16_to_fp16","module":"gf16-to-fp16","lines":574,"bytes":18936},{"path":"chips/gamma/specs/fpga/gf16_to_posit16.t27","category":"chips/gamma","name":"gf16_to_posit16","module":"gf16-to-posit16","lines":664,"bytes":20622},{"path":"chips/gamma/specs/fpga/gf256.t27","category":"chips/gamma","name":"gf256","module":"GF256","lines":336,"bytes":9935},{"path":"chips/gamma/specs/fpga/gf32_to_fp32.t27","category":"chips/gamma","name":"gf32_to_fp32","module":"gf32-to-fp32","lines":757,"bytes":25067},{"path":"chips/gamma/specs/fpga/gf64.t27","category":"chips/gamma","name":"gf64","module":"GF64","lines":320,"bytes":9095},{"path":"chips/gamma/specs/fpga/holo_mux_x4.t27","category":"chips/gamma","name":"holo_mux_x4","module":"sacred-holo_mux_x4","lines":581,"bytes":21272},{"path":"chips/gamma/specs/fpga/int4.t27","category":"chips/gamma","name":"int4","module":"Int4","lines":334,"bytes":8538},{"path":"chips/gamma/specs/fpga/int8.t27","category":"chips/gamma","name":"int8","module":"Int8","lines":493,"bytes":13029},{"path":"chips/gamma/specs/fpga/lane_l_precheck.t27","category":"chips/gamma","name":"lane_l_precheck","module":"sacred-lane_precheck","lines":493,"bytes":17229},{"path":"chips/gamma/specs/fpga/lut_npu_81_entry.t27","category":"chips/gamma","name":"lut_npu_81_entry","module":"sacred-lut_npu_81_entry","lines":419,"bytes":13484},{"path":"chips/gamma/specs/fpga/nf4.t27","category":"chips/gamma","name":"nf4","module":"NF4","lines":313,"bytes":8164},{"path":"chips/gamma/specs/fpga/null_pe.t27","category":"chips/gamma","name":"null_pe","module":"sacred-null_pe","lines":568,"bytes":20320},{"path":"chips/gamma/specs/fpga/posit16.t27","category":"chips/gamma","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004},{"path":"chips/gamma/specs/fpga/purkinje_thermal_gate.t27","category":"chips/gamma","name":"purkinje_thermal_gate","module":"purkinje-thermal-gate","lines":704,"bytes":24460},{"path":"chips/gamma/specs/fpga/sparse_mask.t27","category":"chips/gamma","name":"sparse_mask","module":"sacred-sparse_mask","lines":556,"bytes":17492},{"path":"chips/gamma/specs/fpga/sparse_skip.t27","category":"chips/gamma","name":"sparse_skip","module":"sacred-sparse_skip","lines":476,"bytes":15117},{"path":"chips/gamma/specs/fpga/spec_exit.t27","category":"chips/gamma","name":"spec_exit","module":"sacred-spec_exit","lines":541,"bytes":19745},{"path":"chips/gamma/specs/fpga/stoch_round.t27","category":"chips/gamma","name":"stoch_round","module":"sacred-stoch_round","lines":575,"bytes":19164},{"path":"chips/gamma/specs/fpga/subth_clk.t27","category":"chips/gamma","name":"subth_clk","module":"sacred-subth_clk","lines":478,"bytes":17539},{"path":"chips/gamma/specs/numeric/binary16.t27","category":"chips/gamma","name":"binary16","module":"Binary16","lines":420,"bytes":12171},{"path":"chips/gamma/specs/numeric/formats.t27","category":"chips/gamma","name":"formats","module":"Formats","lines":538,"bytes":17523},{"path":"chips/gamma/specs/numeric/fp8_e4m3.t27","category":"chips/gamma","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652},{"path":"chips/gamma/specs/numeric/fp8_e5m2.t27","category":"chips/gamma","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826},{"path":"chips/gamma/specs/numeric/gf12.t27","category":"chips/gamma","name":"gf12","module":"GF12","lines":482,"bytes":16369},{"path":"chips/gamma/specs/numeric/gf128.t27","category":"chips/gamma","name":"gf128","module":"triformat-gf128","lines":506,"bytes":17137},{"path":"chips/gamma/specs/numeric/gf16.t27","category":"chips/gamma","name":"gf16","module":"triformat-gf16","lines":3437,"bytes":101513},{"path":"chips/gamma/specs/numeric/gf20.t27","category":"chips/gamma","name":"gf20","module":"GF20","lines":469,"bytes":15992},{"path":"chips/gamma/specs/numeric/gf24.t27","category":"chips/gamma","name":"gf24","module":"GF24","lines":469,"bytes":16040},{"path":"chips/gamma/specs/numeric/gf256.t27","category":"chips/gamma","name":"gf256","module":"triformat-gf256","lines":595,"bytes":20164},{"path":"chips/gamma/specs/numeric/gf32.t27","category":"chips/gamma","name":"gf32","module":"GF32","lines":480,"bytes":16589},{"path":"chips/gamma/specs/numeric/gf4.t27","category":"chips/gamma","name":"gf4","module":"GF4","lines":306,"bytes":11167},{"path":"chips/gamma/specs/numeric/gf64.t27","category":"chips/gamma","name":"gf64","module":"triformat-gf64","lines":764,"bytes":24203},{"path":"chips/gamma/specs/numeric/gf8.t27","category":"chips/gamma","name":"gf8","module":"GF8","lines":522,"bytes":17732},{"path":"chips/gamma/specs/numeric/goldenfloat_family.t27","category":"chips/gamma","name":"goldenfloat_family","module":"GoldenFloatFamily","lines":450,"bytes":17346},{"path":"chips/gamma/specs/numeric/int4.t27","category":"chips/gamma","name":"int4","module":"triformat-int4","lines":766,"bytes":20533},{"path":"chips/gamma/specs/numeric/int8.t27","category":"chips/gamma","name":"int8","module":"triformat-int8","lines":1151,"bytes":30614},{"path":"chips/gamma/specs/numeric/nf4.t27","category":"chips/gamma","name":"nf4","module":"triformat-nf4","lines":839,"bytes":23771},{"path":"chips/gamma/specs/numeric/phi_ratio.t27","category":"chips/gamma","name":"phi_ratio","module":"PhiRatio","lines":695,"bytes":25855},{"path":"chips/gamma/specs/numeric/posit16.t27","category":"chips/gamma","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004},{"path":"chips/gamma/specs/numeric/tri_net_formats.t27","category":"chips/gamma","name":"tri_net_formats","module":"TriNetFormats","lines":993,"bytes":32170},{"path":"chips/phi/specs/fpga/avs_controller_48.t27","category":"chips/phi","name":"avs_controller_48","module":"avs-controller-48","lines":607,"bytes":20060},{"path":"chips/phi/specs/fpga/avs_controller_96.t27","category":"chips/phi","name":"avs_controller_96","module":"avs-controller-96","lines":735,"bytes":24836},{"path":"chips/phi/specs/fpga/avs_reconf.t27","category":"chips/phi","name":"avs_reconf","module":"avs-reconf","lines":702,"bytes":24801},{"path":"chips/phi/specs/fpga/dfs_gate.t27","category":"chips/phi","name":"dfs_gate","module":"sacred-dfs_gate","lines":600,"bytes":22017},{"path":"chips/phi/specs/fpga/drowsy_ret.t27","category":"chips/phi","name":"drowsy_ret","module":"sacred-drowsy_ret","lines":716,"bytes":26247},{"path":"chips/phi/specs/fpga/fbb_active_path.t27","category":"chips/phi","name":"fbb_active_path","module":"fbb-active-path","lines":756,"bytes":26695},{"path":"chips/phi/specs/fpga/fp8_e4m3.t27","category":"chips/phi","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652},{"path":"chips/phi/specs/fpga/fp8_e5m2.t27","category":"chips/phi","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826},{"path":"chips/phi/specs/fpga/gf128.t27","category":"chips/phi","name":"gf128","module":"GF128","lines":358,"bytes":10740},{"path":"chips/phi/specs/fpga/gf16_to_fp16.t27","category":"chips/phi","name":"gf16_to_fp16","module":"gf16-to-fp16","lines":574,"bytes":18936},{"path":"chips/phi/specs/fpga/gf16_to_posit16.t27","category":"chips/phi","name":"gf16_to_posit16","module":"gf16-to-posit16","lines":664,"bytes":20622},{"path":"chips/phi/specs/fpga/gf256.t27","category":"chips/phi","name":"gf256","module":"GF256","lines":336,"bytes":9935},{"path":"chips/phi/specs/fpga/gf32_to_fp32.t27","category":"chips/phi","name":"gf32_to_fp32","module":"gf32-to-fp32","lines":757,"bytes":25067},{"path":"chips/phi/specs/fpga/gf64.t27","category":"chips/phi","name":"gf64","module":"GF64","lines":320,"bytes":9095},{"path":"chips/phi/specs/fpga/holo_mux_x4.t27","category":"chips/phi","name":"holo_mux_x4","module":"sacred-holo_mux_x4","lines":581,"bytes":21272},{"path":"chips/phi/specs/fpga/int4.t27","category":"chips/phi","name":"int4","module":"Int4","lines":334,"bytes":8538},{"path":"chips/phi/specs/fpga/int8.t27","category":"chips/phi","name":"int8","module":"Int8","lines":493,"bytes":13029},{"path":"chips/phi/specs/fpga/lane_l_precheck.t27","category":"chips/phi","name":"lane_l_precheck","module":"sacred-lane_precheck","lines":493,"bytes":17229},{"path":"chips/phi/specs/fpga/lut_npu_81_entry.t27","category":"chips/phi","name":"lut_npu_81_entry","module":"sacred-lut_npu_81_entry","lines":419,"bytes":13484},{"path":"chips/phi/specs/fpga/nf4.t27","category":"chips/phi","name":"nf4","module":"NF4","lines":313,"bytes":8164},{"path":"chips/phi/specs/fpga/null_pe.t27","category":"chips/phi","name":"null_pe","module":"sacred-null_pe","lines":568,"bytes":20320},{"path":"chips/phi/specs/fpga/posit16.t27","category":"chips/phi","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004},{"path":"chips/phi/specs/fpga/purkinje_thermal_gate.t27","category":"chips/phi","name":"purkinje_thermal_gate","module":"purkinje-thermal-gate","lines":704,"bytes":24460},{"path":"chips/phi/specs/fpga/sparse_mask.t27","category":"chips/phi","name":"sparse_mask","module":"sacred-sparse_mask","lines":556,"bytes":17492},{"path":"chips/phi/specs/fpga/sparse_skip.t27","category":"chips/phi","name":"sparse_skip","module":"sacred-sparse_skip","lines":476,"bytes":15117},{"path":"chips/phi/specs/fpga/spec_exit.t27","category":"chips/phi","name":"spec_exit","module":"sacred-spec_exit","lines":541,"bytes":19745},{"path":"chips/phi/specs/fpga/stoch_round.t27","category":"chips/phi","name":"stoch_round","module":"sacred-stoch_round","lines":575,"bytes":19164},{"path":"chips/phi/specs/fpga/subth_clk.t27","category":"chips/phi","name":"subth_clk","module":"sacred-subth_clk","lines":478,"bytes":17539},{"path":"chips/phi/specs/numeric/binary16.t27","category":"chips/phi","name":"binary16","module":"Binary16","lines":420,"bytes":12171},{"path":"chips/phi/specs/numeric/formats.t27","category":"chips/phi","name":"formats","module":"Formats","lines":538,"bytes":17523},{"path":"chips/phi/specs/numeric/fp8_e4m3.t27","category":"chips/phi","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652},{"path":"chips/phi/specs/numeric/fp8_e5m2.t27","category":"chips/phi","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826},{"path":"chips/phi/specs/numeric/gf12.t27","category":"chips/phi","name":"gf12","module":"GF12","lines":482,"bytes":16369},{"path":"chips/phi/specs/numeric/gf128.t27","category":"chips/phi","name":"gf128","module":"triformat-gf128","lines":506,"bytes":17137},{"path":"chips/phi/specs/numeric/gf16.t27","category":"chips/phi","name":"gf16","module":"triformat-gf16","lines":3437,"bytes":101513},{"path":"chips/phi/specs/numeric/gf20.t27","category":"chips/phi","name":"gf20","module":"GF20","lines":469,"bytes":15992},{"path":"chips/phi/specs/numeric/gf24.t27","category":"chips/phi","name":"gf24","module":"GF24","lines":469,"bytes":16040},{"path":"chips/phi/specs/numeric/gf256.t27","category":"chips/phi","name":"gf256","module":"triformat-gf256","lines":595,"bytes":20164},{"path":"chips/phi/specs/numeric/gf32.t27","category":"chips/phi","name":"gf32","module":"GF32","lines":480,"bytes":16589},{"path":"chips/phi/specs/numeric/gf4.t27","category":"chips/phi","name":"gf4","module":"GF4","lines":306,"bytes":11167},{"path":"chips/phi/specs/numeric/gf64.t27","category":"chips/phi","name":"gf64","module":"triformat-gf64","lines":764,"bytes":24203},{"path":"chips/phi/specs/numeric/gf8.t27","category":"chips/phi","name":"gf8","module":"GF8","lines":522,"bytes":17732},{"path":"chips/phi/specs/numeric/goldenfloat_family.t27","category":"chips/phi","name":"goldenfloat_family","module":"GoldenFloatFamily","lines":450,"bytes":17346},{"path":"chips/phi/specs/numeric/int4.t27","category":"chips/phi","name":"int4","module":"triformat-int4","lines":766,"bytes":20533},{"path":"chips/phi/specs/numeric/int8.t27","category":"chips/phi","name":"int8","module":"triformat-int8","lines":1151,"bytes":30614},{"path":"chips/phi/specs/numeric/nf4.t27","category":"chips/phi","name":"nf4","module":"triformat-nf4","lines":839,"bytes":23771},{"path":"chips/phi/specs/numeric/phi_ratio.t27","category":"chips/phi","name":"phi_ratio","module":"PhiRatio","lines":695,"bytes":25855},{"path":"chips/phi/specs/numeric/posit16.t27","category":"chips/phi","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004},{"path":"chips/phi/specs/numeric/tri_net_formats.t27","category":"chips/phi","name":"tri_net_formats","module":"TriNetFormats","lines":993,"bytes":32170},{"path":"compiler/ast.t27","category":"compiler","name":"ast","module":"ast","lines":612,"bytes":18378},{"path":"compiler/cli/gen.t27","category":"compiler/cli","name":"gen","module":"gen_commands","lines":512,"bytes":16753},{"path":"compiler/cli/git.t27","category":"compiler/cli","name":"git","module":"git_commands","lines":617,"bytes":20978},{"path":"compiler/cli/spec.t27","category":"compiler/cli","name":"spec","module":"spec_commands","lines":488,"bytes":16727},{"path":"compiler/codegen/c/codegen.t27","category":"compiler/codegen","name":"codegen","module":"tricgen-c","lines":274,"bytes":7080},{"path":"compiler/codegen/testgen.t27","category":"compiler/codegen","name":"testgen","module":"testgen","lines":910,"bytes":36448},{"path":"compiler/codegen/verilog/codegen.t27","category":"compiler/codegen","name":"codegen","module":"verilog_codegen","lines":1068,"bytes":228219},{"path":"compiler/codegen/verilog/fpga_emission.t27","category":"compiler/codegen","name":"fpga_emission","module":"fpga_emission","lines":2359,"bytes":86803},{"path":"compiler/codegen/zig/codegen.t27","category":"compiler/codegen","name":"codegen","module":"zig_codegen","lines":1517,"bytes":54679},{"path":"compiler/codegen/zig/runtime.t27","category":"compiler/codegen","name":"runtime","module":"zig_runtime","lines":410,"bytes":20013},{"path":"compiler/parser/lexer.t27","category":"compiler/parser","name":"lexer","module":"trilexer","lines":598,"bytes":18435},{"path":"compiler/parser/parser.t27","category":"compiler/parser","name":"parser","module":"parser","lines":1295,"bytes":49454},{"path":"compiler/runtime/commands.t27","category":"compiler/runtime","name":"commands","module":"commands","lines":1040,"bytes":35002},{"path":"compiler/runtime/runtime.t27","category":"compiler/runtime","name":"runtime","module":"triruntime","lines":340,"bytes":11466},{"path":"compiler/runtime/validation.t27","category":"compiler/runtime","name":"validation","module":"validation_rules","lines":515,"bytes":20567},{"path":"compiler/skill/registry.t27","category":"compiler/skill","name":"registry","module":"skill_registry","lines":313,"bytes":11026},{"path":"contrib/backend/zig/legacy/main_zig_handwritten.t27","category":"contrib/backend","name":"main_zig_handwritten","module":null,"lines":1126,"bytes":36956},{"path":"examples/fpga/qmtech_minimal/design.t27","category":"examples/fpga","name":"design","module":null,"lines":150,"bytes":3670},{"path":"specs/account/auth.t27","category":"specs/account","name":"auth","module":"AccountAuth","lines":278,"bytes":9231},{"path":"specs/account/repo.t27","category":"specs/account","name":"repo","module":"AccountRepo","lines":197,"bytes":5474},{"path":"specs/account/schema.t27","category":"specs/account","name":"schema","module":"Account","lines":227,"bytes":6623},{"path":"specs/api/c_api_contract.t27","category":"specs/api","name":"c_api_contract","module":null,"lines":243,"bytes":7087},{"path":"specs/api/sdk_contract.t27","category":"specs/api","name":"sdk_contract","module":null,"lines":407,"bytes":10914},{"path":"specs/api/tri_net_api.t27","category":"specs/api","name":"tri_net_api","module":null,"lines":190,"bytes":5445},{"path":"specs/ar/asp_solver.t27","category":"specs/ar","name":"asp_solver","module":null,"lines":556,"bytes":16355},{"path":"specs/ar/coa_planning.t27","category":"specs/ar","name":"coa_planning","module":null,"lines":638,"bytes":20107},{"path":"specs/ar/composition.t27","category":"specs/ar","name":"composition","module":null,"lines":571,"bytes":19109},{"path":"specs/ar/datalog_engine.t27","category":"specs/ar","name":"datalog_engine","module":null,"lines":350,"bytes":10935},{"path":"specs/ar/explainability.t27","category":"specs/ar","name":"explainability","module":null,"lines":556,"bytes":16253},{"path":"specs/ar/proof_trace.t27","category":"specs/ar","name":"proof_trace","module":null,"lines":313,"bytes":9671},{"path":"specs/ar/restraint.t27","category":"specs/ar","name":"restraint","module":null,"lines":438,"bytes":14096},{"path":"specs/ar/ternary_logic.t27","category":"specs/ar","name":"ternary_logic","module":null,"lines":473,"bytes":13762},{"path":"specs/auth/config.t27","category":"specs/auth","name":"config","module":"AuthConfig","lines":295,"bytes":9593},{"path":"specs/automation/wrapup-auto.t27","category":"specs/automation","name":"wrapup-auto","module":"automation","lines":51,"bytes":1625},{"path":"specs/base/debounce.t27","category":"specs/base","name":"debounce","module":"base-debounce","lines":99,"bytes":2655},{"path":"specs/base/ops.t27","category":"specs/base","name":"ops","module":"tritype-ops","lines":1490,"bytes":47606},{"path":"specs/base/ring_32.t27","category":"specs/base","name":"ring_32","module":"base-ring-32","lines":25,"bytes":596},{"path":"specs/base/seed.t27","category":"specs/base","name":"seed","module":"seed","lines":93,"bytes":2627},{"path":"specs/base/ternary_add.t27","category":"specs/base","name":"ternary_add","module":"ternary_add","lines":413,"bytes":14371},{"path":"specs/base/ternary_encoding.t27","category":"specs/base","name":"ternary_encoding","module":"TernaryEncoding","lines":415,"bytes":13711},{"path":"specs/base/ternary_memory.t27","category":"specs/base","name":"ternary_memory","module":"TernaryMemory","lines":417,"bytes":13794},{"path":"specs/base/types.t27","category":"specs/base","name":"types","module":"tritype-base","lines":1680,"bytes":55612},{"path":"specs/benchmarks/bench_main.t27","category":"specs/benchmarks","name":"bench_main","module":null,"lines":95,"bytes":2523},{"path":"specs/benchmarks/bench_nn.t27","category":"specs/benchmarks","name":"bench_nn","module":null,"lines":166,"bytes":3216},{"path":"specs/benchmarks/gf16_bfloat16_nmse.t27","category":"specs/benchmarks","name":"gf16_bfloat16_nmse","module":null,"lines":153,"bytes":4300},{"path":"specs/benchmarks/ternary_vs_binary.t27","category":"specs/benchmarks","name":"ternary_vs_binary","module":null,"lines":92,"bytes":1965},{"path":"specs/boards/arty_a7.t27","category":"specs/boards","name":"arty_a7","module":"BoardArtyA7","lines":288,"bytes":7435},{"path":"specs/boards/xc7a100t_full.t27","category":"specs/boards","name":"xc7a100t_full","module":"BoardFullXC7A100T","lines":357,"bytes":9813},{"path":"specs/boards/xc7a100t_minimal.t27","category":"specs/boards","name":"xc7a100t_minimal","module":"BoardMinimalXC7A100T","lines":393,"bytes":10805},{"path":"specs/brain/brain.t27","category":"specs/brain","name":"brain","module":null,"lines":58,"bytes":2756},{"path":"specs/brain/bus.t27","category":"specs/brain","name":"bus","module":"brain-bus","lines":17,"bytes":419},{"path":"specs/brain/cognitive_loop.t27","category":"specs/brain","name":"cognitive_loop","module":"brain-cognitive-loop","lines":17,"bytes":515},{"path":"specs/brain/neural_gamma.t27","category":"specs/brain","name":"neural_gamma","module":null,"lines":89,"bytes":2008},{"path":"specs/brain/phi_timing.t27","category":"specs/brain","name":"phi_timing","module":"brain-phi-timing","lines":80,"bytes":3193},{"path":"specs/brain/unified_state.t27","category":"specs/brain","name":"unified_state","module":"brain-unified-state","lines":94,"bytes":2948},{"path":"specs/bus/pubsub.t27","category":"specs/bus","name":"pubsub","module":"bus-pubsub","lines":670,"bytes":17611},{"path":"specs/bus/schema.t27","category":"specs/bus","name":"schema","module":"bus-schema","lines":657,"bytes":17052},{"path":"specs/cloud/railway_deploy.t27","category":"specs/cloud","name":"railway_deploy","module":"cloud-railway-deploy","lines":363,"bytes":9070},{"path":"specs/compiler/diagnostics.t27","category":"specs/compiler","name":"diagnostics","module":"Diagnostics","lines":148,"bytes":4220},{"path":"specs/compiler/lexer.t27","category":"specs/compiler","name":"lexer","module":"Lexing","lines":583,"bytes":22503},{"path":"specs/compiler/linker.t27","category":"specs/compiler","name":"linker","module":"Linking","lines":156,"bytes":4048},{"path":"specs/compiler/meta_compile.t27","category":"specs/compiler","name":"meta_compile","module":"MetaCompilation","lines":70,"bytes":1886},{"path":"specs/compiler/mod_structure.t27","category":"specs/compiler","name":"mod_structure","module":"compiler-mod-structure","lines":124,"bytes":3540},{"path":"specs/compiler/optimizer.t27","category":"specs/compiler","name":"optimizer","module":"Optimization","lines":286,"bytes":8976},{"path":"specs/compiler/parser.t27","category":"specs/compiler","name":"parser","module":"Parsing","lines":1619,"bytes":55123},{"path":"specs/compiler/pipeline.t27","category":"specs/compiler","name":"pipeline","module":"Pipeline","lines":170,"bytes":4503},{"path":"specs/compiler/stdlib.t27","category":"specs/compiler","name":"stdlib","module":"Stdlib","lines":663,"bytes":16035},{"path":"specs/compiler/typechecker.t27","category":"specs/compiler","name":"typechecker","module":"TypeChecking","lines":694,"bytes":24448},{"path":"specs/config/load.t27","category":"specs/config","name":"load","module":"config-load","lines":608,"bytes":16362},{"path":"specs/config/migrate.t27","category":"specs/config","name":"migrate","module":"config-migrate","lines":663,"bytes":18030},{"path":"specs/config/paths.t27","category":"specs/config","name":"paths","module":"config-paths","lines":641,"bytes":15895},{"path":"specs/config/schema.t27","category":"specs/config","name":"schema","module":"config-schema","lines":703,"bytes":19048},{"path":"specs/conformance/e2e_scenarios.t27","category":"specs/conformance","name":"e2e_scenarios","module":null,"lines":265,"bytes":7796},{"path":"specs/demos/jones_topology_decision_gate.t27","category":"specs/demos","name":"jones_topology_decision_gate","module":"JonesTopologyDecisionGate","lines":347,"bytes":29767},{"path":"specs/demos/jones_topology_filter.t27","category":"specs/demos","name":"jones_topology_filter","module":"JonesTopologyFilter","lines":318,"bytes":10711},{"path":"specs/demos/simple_test.t27","category":"specs/demos","name":"simple_test","module":"SimpleTest","lines":15,"bytes":277},{"path":"specs/depin/prove.t27","category":"specs/depin","name":"prove","module":"depin","lines":285,"bytes":8731},{"path":"specs/enrichment/audio_overview.t27","category":"specs/enrichment","name":"audio_overview","module":"enrichment","lines":163,"bytes":5680},{"path":"specs/enrichment/youtube_transcript.t27","category":"specs/enrichment","name":"youtube_transcript","module":"enrichment","lines":606,"bytes":20285},{"path":"specs/file/operations.t27","category":"specs/file","name":"operations","module":"FileOperations","lines":446,"bytes":13273},{"path":"specs/file/schema.t27","category":"specs/file","name":"schema","module":"File","lines":334,"bytes":11786},{"path":"specs/file/watcher.t27","category":"specs/file","name":"watcher","module":"FileWatcher","lines":551,"bytes":16816},{"path":"specs/fpga/apb_bridge.t27","category":"specs/fpga","name":"apb_bridge","module":"ApbBridge","lines":348,"bytes":9210},{"path":"specs/fpga/assembler.t27","category":"specs/fpga","name":"assembler","module":"Assembler","lines":347,"bytes":8722},{"path":"specs/fpga/axi4.t27","category":"specs/fpga","name":"axi4","module":"Axi4","lines":394,"bytes":10640},{"path":"specs/fpga/boards/arty_a7_integration.t27","category":"specs/fpga","name":"arty_a7_integration","module":"ArtyA7_Integration","lines":137,"bytes":4043},{"path":"specs/fpga/boards/qmtech_a100t_integration.t27","category":"specs/fpga","name":"qmtech_a100t_integration","module":"QMTech_A100T_Integration","lines":96,"bytes":2568},{"path":"specs/fpga/bootrom.t27","category":"specs/fpga","name":"bootrom","module":"BootROM","lines":140,"bytes":4020},{"path":"specs/fpga/bridge.t27","category":"specs/fpga","name":"bridge","module":"FPGA_Bridge","lines":501,"bytes":17323},{"path":"specs/fpga/clock_domain.t27","category":"specs/fpga","name":"clock_domain","module":"ClockDomain","lines":231,"bytes":5913},{"path":"specs/fpga/crossopt.t27","category":"specs/fpga","name":"crossopt","module":"CrossOpt","lines":126,"bytes":3615},{"path":"specs/fpga/cts.t27","category":"specs/fpga","name":"cts","module":"CTS","lines":225,"bytes":5735},{"path":"specs/fpga/dft.t27","category":"specs/fpga","name":"dft","module":"DFT","lines":264,"bytes":6604},{"path":"specs/fpga/e2e_demo.t27","category":"specs/fpga","name":"e2e_demo","module":"E2eDemo","lines":253,"bytes":6432},{"path":"specs/fpga/fifo.t27","category":"specs/fpga","name":"fifo","module":"Fifo","lines":365,"bytes":9822},{"path":"specs/fpga/formal.t27","category":"specs/fpga","name":"formal","module":"Formal","lines":353,"bytes":9441},{"path":"specs/fpga/gf16_accel.t27","category":"specs/fpga","name":"gf16_accel","module":"Gf16Accel","lines":423,"bytes":11072},{"path":"specs/fpga/hir.t27","category":"specs/fpga","name":"hir","module":"Hir","lines":692,"bytes":19053},{"path":"specs/fpga/hw_types.t27","category":"specs/fpga","name":"hw_types","module":"HwTypes","lines":335,"bytes":8002},{"path":"specs/fpga/linker.t27","category":"specs/fpga","name":"linker","module":"Linker","lines":327,"bytes":8187},{"path":"specs/fpga/mac.t27","category":"specs/fpga","name":"mac","module":"ZeroDSP_MAC","lines":623,"bytes":22498},{"path":"specs/fpga/memory.t27","category":"specs/fpga","name":"memory","module":"Memory","lines":355,"bytes":9348},{"path":"specs/fpga/partition.t27","category":"specs/fpga","name":"partition","module":"Partition","lines":306,"bytes":9004},{"path":"specs/fpga/placement.t27","category":"specs/fpga","name":"placement","module":"Placement","lines":242,"bytes":6335},{"path":"specs/fpga/power.t27","category":"specs/fpga","name":"power","module":"Power","lines":230,"bytes":6278},{"path":"specs/fpga/power_analysis.t27","category":"specs/fpga","name":"power_analysis","module":"PowerAnalysis","lines":453,"bytes":12807},{"path":"specs/fpga/router.t27","category":"specs/fpga","name":"router","module":"Router","lines":257,"bytes":6620},{"path":"specs/fpga/simulator.t27","category":"specs/fpga","name":"simulator","module":"Simulator","lines":278,"bytes":7050},{"path":"specs/fpga/spi.t27","category":"specs/fpga","name":"spi","module":"SPI_Master","lines":415,"bytes":13093},{"path":"specs/fpga/stdlib.t27","category":"specs/fpga","name":"stdlib","module":"Stdlib","lines":372,"bytes":17315},{"path":"specs/fpga/ternary_isa.t27","category":"specs/fpga","name":"ternary_isa","module":"TernaryIsa","lines":543,"bytes":14462},{"path":"specs/fpga/testbench.t27","category":"specs/fpga","name":"testbench","module":"Testbench","lines":271,"bytes":6930},{"path":"specs/fpga/testbench/apb_bridge_tb.t27","category":"specs/fpga","name":"apb_bridge_tb","module":"APB_Bridge_Testbench","lines":144,"bytes":3247},{"path":"specs/fpga/testbench/assembler_tb.t27","category":"specs/fpga","name":"assembler_tb","module":"Assembler_Testbench","lines":111,"bytes":2952},{"path":"specs/fpga/testbench/axi4_tb.t27","category":"specs/fpga","name":"axi4_tb","module":"AXI4_Testbench","lines":179,"bytes":4359},{"path":"specs/fpga/testbench/bootrom_tb.t27","category":"specs/fpga","name":"bootrom_tb","module":"BootROM_Testbench","lines":87,"bytes":1856},{"path":"specs/fpga/testbench/bridge_tb.t27","category":"specs/fpga","name":"bridge_tb","module":"Bridge_Testbench","lines":124,"bytes":2796},{"path":"specs/fpga/testbench/clock_domain_tb.t27","category":"specs/fpga","name":"clock_domain_tb","module":"ClockDomain_Testbench","lines":109,"bytes":2439},{"path":"specs/fpga/testbench/cts_tb.t27","category":"specs/fpga","name":"cts_tb","module":"CTS_Testbench","lines":119,"bytes":2939},{"path":"specs/fpga/testbench/dft_tb.t27","category":"specs/fpga","name":"dft_tb","module":"DFT_Testbench","lines":130,"bytes":3050},{"path":"specs/fpga/testbench/fifo_tb.t27","category":"specs/fpga","name":"fifo_tb","module":"FIFO_Testbench","lines":163,"bytes":3606},{"path":"specs/fpga/testbench/formal_tb.t27","category":"specs/fpga","name":"formal_tb","module":"Formal_Testbench","lines":132,"bytes":3125},{"path":"specs/fpga/testbench/gf16_accel_tb.t27","category":"specs/fpga","name":"gf16_accel_tb","module":"GF16_Accel_Testbench","lines":136,"bytes":3024},{"path":"specs/fpga/testbench/hir_tb.t27","category":"specs/fpga","name":"hir_tb","module":"HIR_Testbench","lines":105,"bytes":2327},{"path":"specs/fpga/testbench/integration_tb.t27","category":"specs/fpga","name":"integration_tb","module":"Integration_Testbench","lines":126,"bytes":2971},{"path":"specs/fpga/testbench/linker_tb.t27","category":"specs/fpga","name":"linker_tb","module":"Linker_Testbench","lines":89,"bytes":2000},{"path":"specs/fpga/testbench/mac_tb.t27","category":"specs/fpga","name":"mac_tb","module":"MAC_Testbench","lines":554,"bytes":17571},{"path":"specs/fpga/testbench/memory_tb.t27","category":"specs/fpga","name":"memory_tb","module":"Memory_Testbench","lines":136,"bytes":3129},{"path":"specs/fpga/testbench/partition_tb.t27","category":"specs/fpga","name":"partition_tb","module":"Partition_Testbench","lines":94,"bytes":2266},{"path":"specs/fpga/testbench/placement_tb.t27","category":"specs/fpga","name":"placement_tb","module":"Placement_Testbench","lines":115,"bytes":2993},{"path":"specs/fpga/testbench/power_analysis_tb.t27","category":"specs/fpga","name":"power_analysis_tb","module":"PowerAnalysis_Testbench","lines":130,"bytes":3169},{"path":"specs/fpga/testbench/power_tb.t27","category":"specs/fpga","name":"power_tb","module":"Power_Testbench","lines":118,"bytes":2926},{"path":"specs/fpga/testbench/router_tb.t27","category":"specs/fpga","name":"router_tb","module":"Router_Testbench","lines":112,"bytes":2833},{"path":"specs/fpga/testbench/simulator_tb.t27","category":"specs/fpga","name":"simulator_tb","module":"Simulator_Testbench","lines":92,"bytes":2071},{"path":"specs/fpga/testbench/spi_tb.t27","category":"specs/fpga","name":"spi_tb","module":"SPI_Testbench","lines":124,"bytes":2963},{"path":"specs/fpga/testbench/stdlib_tb.t27","category":"specs/fpga","name":"stdlib_tb","module":"Stdlib_Testbench","lines":112,"bytes":2313},{"path":"specs/fpga/testbench/ternary_isa_tb.t27","category":"specs/fpga","name":"ternary_isa_tb","module":"Ternary_ISA_Testbench","lines":148,"bytes":3310},{"path":"specs/fpga/testbench/timing_tb.t27","category":"specs/fpga","name":"timing_tb","module":"Timing_Testbench","lines":98,"bytes":2410},{"path":"specs/fpga/testbench/top_tb.t27","category":"specs/fpga","name":"top_tb","module":"Top_Level_Testbench","lines":218,"bytes":5626},{"path":"specs/fpga/testbench/uart_tb.t27","category":"specs/fpga","name":"uart_tb","module":"UART_Testbench","lines":397,"bytes":11180},{"path":"specs/fpga/testbench/vcd_conformance_compare_tb.t27","category":"specs/fpga","name":"vcd_conformance_compare_tb","module":"VcdConformanceCompare_Testbench","lines":107,"bytes":2611},{"path":"specs/fpga/testbench/vcd_trace_tb.t27","category":"specs/fpga","name":"vcd_trace_tb","module":"VCD_Trace_Testbench","lines":86,"bytes":1929},{"path":"specs/fpga/timing.t27","category":"specs/fpga","name":"timing","module":"Timing","lines":376,"bytes":9419},{"path":"specs/fpga/top_level.t27","category":"specs/fpga","name":"top_level","module":"ZeroDSP_TopLevel","lines":220,"bytes":5551},{"path":"specs/fpga/uart.t27","category":"specs/fpga","name":"uart","module":"ZeroDSP_UART","lines":211,"bytes":5733},{"path":"specs/fpga/vcd_conformance_compare.t27","category":"specs/fpga","name":"vcd_conformance_compare","module":"VcdConformanceCompare","lines":436,"bytes":11816},{"path":"specs/fpga/vcd_trace.t27","category":"specs/fpga","name":"vcd_trace","module":"VcdTrace","lines":292,"bytes":7555},{"path":"specs/fpga/verification/build_verify.t27","category":"specs/fpga","name":"build_verify","module":"BuildVerify","lines":87,"bytes":2190},{"path":"specs/git/diff.t27","category":"specs/git","name":"diff","module":"GitDiff","lines":230,"bytes":7832},{"path":"specs/git/operations.t27","category":"specs/git","name":"operations","module":"GitOperations","lines":176,"bytes":5799},{"path":"specs/git/schema.t27","category":"specs/git","name":"schema","module":"Git","lines":229,"bytes":6205},{"path":"specs/git/status.t27","category":"specs/git","name":"status","module":"GitStatus","lines":189,"bytes":6535},{"path":"specs/github/auth.t27","category":"specs/github","name":"auth","module":"github","lines":27,"bytes":708},{"path":"specs/github/comments.t27","category":"specs/github","name":"comments","module":"github","lines":18,"bytes":398},{"path":"specs/github/issues.t27","category":"specs/github","name":"issues","module":"github","lines":22,"bytes":484},{"path":"specs/github/prs.t27","category":"specs/github","name":"prs","module":"github","lines":22,"bytes":472},{"path":"specs/github/tests/e2e_full_flow.t27","category":"specs/github","name":"e2e_full_flow","module":"github","lines":122,"bytes":3778},{"path":"specs/graph/knowledge_graph.t27","category":"specs/graph","name":"knowledge_graph","module":"KnowledgeGraph","lines":406,"bytes":17952},{"path":"specs/hslm/forward_pass.t27","category":"specs/hslm","name":"forward_pass","module":"ForwardPass","lines":306,"bytes":14239},{"path":"specs/interop/gf_cross_language.t27","category":"specs/interop","name":"gf_cross_language","module":"GFCrossLanguageConformance","lines":99,"bytes":3587},{"path":"specs/isa/registers.t27","category":"specs/isa","name":"registers","module":"ISARegisters","lines":600,"bytes":20530},{"path":"specs/isa/ternary_arithmetic.t27","category":"specs/isa","name":"ternary_arithmetic","module":"TernaryArithmetic","lines":501,"bytes":16736},{"path":"specs/isa/ternary_bitwise.t27","category":"specs/isa","name":"ternary_bitwise","module":"TernaryBitwise","lines":404,"bytes":14046},{"path":"specs/isa/ternary_control_flow.t27","category":"specs/isa","name":"ternary_control_flow","module":"TernaryControlFlow","lines":392,"bytes":12518},{"path":"specs/isa/ternary_deque.t27","category":"specs/isa","name":"ternary_deque","module":"TernaryDeque","lines":498,"bytes":16013},{"path":"specs/isa/ternary_encoding.t27","category":"specs/isa","name":"ternary_encoding","module":null,"lines":42,"bytes":944},{"path":"specs/isa/ternary_gates.t27","category":"specs/isa","name":"ternary_gates","module":"TernaryGates","lines":410,"bytes":14744},{"path":"specs/isa/ternary_graph.t27","category":"specs/isa","name":"ternary_graph","module":"TernaryGraph","lines":131,"bytes":3644},{"path":"specs/isa/ternary_hash.t27","category":"specs/isa","name":"ternary_hash","module":"TernaryHashTable","lines":167,"bytes":5024},{"path":"specs/isa/ternary_memory.t27","category":"specs/isa","name":"ternary_memory","module":"ISAMemoryOps","lines":522,"bytes":18705},{"path":"specs/isa/ternary_pattern_matching.t27","category":"specs/isa","name":"ternary_pattern_matching","module":"TernaryPatternMatching","lines":173,"bytes":5331},{"path":"specs/isa/ternary_search.t27","category":"specs/isa","name":"ternary_search","module":"TernarySearch","lines":165,"bytes":4613},{"path":"specs/isa/ternary_set.t27","category":"specs/isa","name":"ternary_set","module":"TernarySet","lines":182,"bytes":5215},{"path":"specs/isa/ternary_shift.t27","category":"specs/isa","name":"ternary_shift","module":"TernaryShift","lines":420,"bytes":14016},{"path":"specs/isa/ternary_sorting.t27","category":"specs/isa","name":"ternary_sorting","module":"TernarySorting","lines":173,"bytes":4909},{"path":"specs/isa/ternary_tree.t27","category":"specs/isa","name":"ternary_tree","module":"TernaryTree","lines":141,"bytes":4645},{"path":"specs/jit/jit.t27","category":"specs/jit","name":"jit","module":"jit","lines":875,"bytes":29745},{"path":"specs/lsp/client.t27","category":"specs/lsp","name":"client","module":"lsp-client","lines":505,"bytes":14486},{"path":"specs/lsp/language.t27","category":"specs/lsp","name":"language","module":"lsp-language","lines":527,"bytes":14976},{"path":"specs/lsp/protocol.t27","category":"specs/lsp","name":"protocol","module":"lsp-protocol","lines":681,"bytes":20652},{"path":"specs/lsp/schema.t27","category":"specs/lsp","name":"schema","module":"lsp-schema","lines":589,"bytes":15702},{"path":"specs/lsp/server.t27","category":"specs/lsp","name":"server","module":"lsp-server","lines":555,"bytes":15397},{"path":"specs/math/constants.t27","category":"specs/math","name":"constants","module":"Constants","lines":459,"bytes":16818},{"path":"specs/math/e8_lie_algebra.t27","category":"specs/math","name":"e8_lie_algebra","module":"E8LieAlgebra","lines":390,"bytes":14031},{"path":"specs/math/gf_competitive.t27","category":"specs/math","name":"gf_competitive","module":"GFCompetitive","lines":122,"bytes":4213},{"path":"specs/math/pellis_precision_verify.t27","category":"specs/math","name":"pellis_precision_verify","module":"PellisPrecision","lines":204,"bytes":8117},{"path":"specs/math/phi_split_optimality.t27","category":"specs/math","name":"phi_split_optimality","module":"PhiSplitOptimality","lines":336,"bytes":12692},{"path":"specs/math/phi_universal_attractor.t27","category":"specs/math","name":"phi_universal_attractor","module":"PhiUniversalAttractor","lines":333,"bytes":12924},{"path":"specs/math/property_test_template.t27","category":"specs/math","name":"property_test_template","module":"PropertyTestTemplate","lines":513,"bytes":16168},{"path":"specs/math/radix_economy.t27","category":"specs/math","name":"radix_economy","module":"RadixEconomy","lines":331,"bytes":10783},{"path":"specs/math/sacred_physics.t27","category":"specs/math","name":"sacred_physics","module":"SacredPhysics","lines":461,"bytes":18991},{"path":"specs/math/zamolodchikov_e8.t27","category":"specs/math","name":"zamolodchikov_e8","module":"ZamolodchikovE8","lines":324,"bytes":11409},{"path":"specs/memory/formula_embed.t27","category":"specs/memory","name":"formula_embed","module":"FormulaEmbed","lines":202,"bytes":5767},{"path":"specs/memory/memory_primitives.t27","category":"specs/memory","name":"memory_primitives","module":"MemoryPrimitives","lines":180,"bytes":5305},{"path":"specs/memory/notebooklm.t27","category":"specs/memory","name":"notebooklm","module":"NotebookLM","lines":486,"bytes":18329},{"path":"specs/memory/semantic_search.t27","category":"specs/memory","name":"semantic_search","module":"SemanticSearch","lines":141,"bytes":4471},{"path":"specs/ml/activation/elu_activation.t27","category":"specs/ml","name":"elu_activation","module":"Elu","lines":117,"bytes":5464},{"path":"specs/ml/activation/gelu_activation.t27","category":"specs/ml","name":"gelu_activation","module":"Gelu","lines":68,"bytes":3490},{"path":"specs/ml/activation/gelu_approx_activation.t27","category":"specs/ml","name":"gelu_approx_activation","module":"GeluApprox","lines":82,"bytes":4233},{"path":"specs/ml/activation/leaky_relu_activation.t27","category":"specs/ml","name":"leaky_relu_activation","module":"LeakyRelu","lines":95,"bytes":4569},{"path":"specs/ml/activation/relu_activation.t27","category":"specs/ml","name":"relu_activation","module":"Relu","lines":62,"bytes":3341},{"path":"specs/ml/activation/sigmoid_activation.t27","category":"specs/ml","name":"sigmoid_activation","module":"Sigmoid","lines":63,"bytes":3005},{"path":"specs/ml/activation/silu_swish_activation.t27","category":"specs/ml","name":"silu_swish_activation","module":"SiluSwish","lines":88,"bytes":4161},{"path":"specs/ml/activation/silu_swish_vbt_activation.t27","category":"specs/ml","name":"silu_swish_vbt_activation","module":"SiluSwishVbt","lines":16,"bytes":674},{"path":"specs/ml/activation/softmax.t27","category":"specs/ml","name":"softmax","module":"Softmax","lines":83,"bytes":4349},{"path":"specs/ml/activation/tanh_activation.t27","category":"specs/ml","name":"tanh_activation","module":"Tanh","lines":54,"bytes":2372},{"path":"specs/ml/layers/avgpool2d_layer.t27","category":"specs/ml","name":"avgpool2d_layer","module":"Avgpool2d","lines":123,"bytes":6532},{"path":"specs/ml/layers/batchnorm_layer.t27","category":"specs/ml","name":"batchnorm_layer","module":"Batchnorm","lines":111,"bytes":5804},{"path":"specs/ml/layers/conv2d_layer.t27","category":"specs/ml","name":"conv2d_layer","module":"Conv2d","lines":92,"bytes":4236},{"path":"specs/ml/layers/dense_layer.t27","category":"specs/ml","name":"dense_layer","module":"Dense","lines":90,"bytes":4267},{"path":"specs/ml/layers/dropout_layer.t27","category":"specs/ml","name":"dropout_layer","module":"Dropout","lines":70,"bytes":3702},{"path":"specs/ml/layers/embedding_layer.t27","category":"specs/ml","name":"embedding_layer","module":"Embedding","lines":121,"bytes":5785},{"path":"specs/ml/layers/flatten_layer.t27","category":"specs/ml","name":"flatten_layer","module":"Flatten","lines":114,"bytes":5624},{"path":"specs/ml/layers/layernorm_layer.t27","category":"specs/ml","name":"layernorm_layer","module":"Layernorm","lines":36,"bytes":1927},{"path":"specs/ml/layers/maxpool2d_layer.t27","category":"specs/ml","name":"maxpool2d_layer","module":"Maxpool2d","lines":74,"bytes":4252},{"path":"specs/ml/layers/residual_connection.t27","category":"specs/ml","name":"residual_connection","module":"Residual","lines":27,"bytes":1408},{"path":"specs/ml/loss/binary_crossentropy_loss.t27","category":"specs/ml","name":"binary_crossentropy_loss","module":"BinaryCe","lines":120,"bytes":5850},{"path":"specs/ml/loss/contrastive_loss.t27","category":"specs/ml","name":"contrastive_loss","module":"ContrastiveLoss","lines":35,"bytes":1858},{"path":"specs/ml/loss/cross_entropy_loss.t27","category":"specs/ml","name":"cross_entropy_loss","module":"CrossEntropy","lines":82,"bytes":4052},{"path":"specs/ml/loss/huber_loss.t27","category":"specs/ml","name":"huber_loss","module":"HuberLoss","lines":35,"bytes":1823},{"path":"specs/ml/loss/kl_divergence.t27","category":"specs/ml","name":"kl_divergence","module":"KlDivergence","lines":27,"bytes":1319},{"path":"specs/ml/loss/mse_loss.t27","category":"specs/ml","name":"mse_loss","module":"MseLoss","lines":35,"bytes":1788},{"path":"specs/ml/optimizer/adagrad.t27","category":"specs/ml","name":"adagrad","module":"Adagrad","lines":113,"bytes":5650},{"path":"specs/ml/optimizer/adam.t27","category":"specs/ml","name":"adam","module":"Adam","lines":47,"bytes":2422},{"path":"specs/ml/optimizer/adamw.t27","category":"specs/ml","name":"adamw","module":"Adamw","lines":306,"bytes":12635},{"path":"specs/ml/optimizer/lamb.t27","category":"specs/ml","name":"lamb","module":"Lamb","lines":134,"bytes":6076},{"path":"specs/ml/optimizer/lr_scheduler.t27","category":"specs/ml","name":"lr_scheduler","module":"Scheduler","lines":150,"bytes":7274},{"path":"specs/ml/optimizer/rmsprop.t27","category":"specs/ml","name":"rmsprop","module":"Rmsprop","lines":43,"bytes":2269},{"path":"specs/ml/optimizer/sgd.t27","category":"specs/ml","name":"sgd","module":"Sgd","lines":104,"bytes":4735},{"path":"specs/ml/optimizer/sgd_momentum.t27","category":"specs/ml","name":"sgd_momentum","module":"SgdMomentum","lines":280,"bytes":13039},{"path":"specs/ml/pathway/mlp.t27","category":"specs/ml","name":"mlp","module":"Mlp","lines":112,"bytes":5025},{"path":"specs/ml/recurrent/attention_mechanism.t27","category":"specs/ml","name":"attention_mechanism","module":"Attention","lines":95,"bytes":5259},{"path":"specs/ml/recurrent/bilstm.t27","category":"specs/ml","name":"bilstm","module":"Bilstm","lines":134,"bytes":6356},{"path":"specs/ml/recurrent/gru_cell.t27","category":"specs/ml","name":"gru_cell","module":"Gru","lines":138,"bytes":6267},{"path":"specs/ml/recurrent/lstm_cell.t27","category":"specs/ml","name":"lstm_cell","module":"Lstm","lines":142,"bytes":6603},{"path":"specs/ml/recurrent/lstm_single.t27","category":"specs/ml","name":"lstm_single","module":"LstmCell","lines":127,"bytes":6220},{"path":"specs/ml/recurrent/rnn_cell.t27","category":"specs/ml","name":"rnn_cell","module":"RnnCell","lines":120,"bytes":5605},{"path":"specs/ml/recurrent/self_attention.t27","category":"specs/ml","name":"self_attention","module":"SelfAttention","lines":57,"bytes":2662},{"path":"specs/ml/recurrent/seq2seq.t27","category":"specs/ml","name":"seq2seq","module":"Seq2seq","lines":121,"bytes":6457},{"path":"specs/ml/rl/advantage_estimator.t27","category":"specs/ml","name":"advantage_estimator","module":"Advantage","lines":128,"bytes":6229},{"path":"specs/ml/rl/dqn.t27","category":"specs/ml","name":"dqn","module":"Dqn","lines":119,"bytes":5990},{"path":"specs/ml/rl/dqn_target_network.t27","category":"specs/ml","name":"dqn_target_network","module":"DqnTarget","lines":112,"bytes":5334},{"path":"specs/ml/rl/ppo_actor.t27","category":"specs/ml","name":"ppo_actor","module":"PpoActor","lines":155,"bytes":7732},{"path":"specs/ml/rl/ppo_clip_loss.t27","category":"specs/ml","name":"ppo_clip_loss","module":"PpoClipLoss","lines":141,"bytes":7501},{"path":"specs/ml/rl/ppo_critic.t27","category":"specs/ml","name":"ppo_critic","module":"PpoCritic","lines":106,"bytes":5376},{"path":"specs/ml/rl/sac_actor.t27","category":"specs/ml","name":"sac_actor","module":"SacActor","lines":128,"bytes":6327},{"path":"specs/ml/rl/sac_critic.t27","category":"specs/ml","name":"sac_critic","module":"SacCritic","lines":164,"bytes":7664},{"path":"specs/ml/transformer/encoder_block.t27","category":"specs/ml","name":"encoder_block","module":"EncoderBlock","lines":117,"bytes":5609},{"path":"specs/ml/transformer/feed_forward.t27","category":"specs/ml","name":"feed_forward","module":"FeedForward","lines":437,"bytes":20203},{"path":"specs/ml/transformer/feed_forward_network.t27","category":"specs/ml","name":"feed_forward_network","module":"FeedForward","lines":42,"bytes":2390},{"path":"specs/ml/transformer/mha_block.t27","category":"specs/ml","name":"mha_block","module":"MHABlock","lines":383,"bytes":17868},{"path":"specs/ml/transformer/multi_head_attention.t27","category":"specs/ml","name":"multi_head_attention","module":"MultiHeadAttn","lines":38,"bytes":1951},{"path":"specs/ml/transformer/multi_head_attn.t27","category":"specs/ml","name":"multi_head_attn","module":"MultiHeadAttention","lines":464,"bytes":21855},{"path":"specs/ml/transformer/norm.t27","category":"specs/ml","name":"norm","module":"LayerNorm","lines":478,"bytes":23093},{"path":"specs/ml/transformer/positional_enc.t27","category":"specs/ml","name":"positional_enc","module":"PositionalEncoding","lines":379,"bytes":17042},{"path":"specs/ml/transformer/positional_encoding.t27","category":"specs/ml","name":"positional_encoding","module":"PositionalEnc","lines":42,"bytes":2296},{"path":"specs/neural/forward_pass.t27","category":"specs/neural","name":"forward_pass","module":"forward_pass","lines":1095,"bytes":32248},{"path":"specs/nn/attention.t27","category":"specs/nn","name":"attention","module":"SacredAttention","lines":634,"bytes":22599},{"path":"specs/nn/hslm.t27","category":"specs/nn","name":"hslm","module":"HSLM","lines":632,"bytes":21721},{"path":"specs/nn/phi_rope.t27","category":"specs/nn","name":"phi_rope","module":null,"lines":47,"bytes":1243},{"path":"specs/nn/sacred_attention.t27","category":"specs/nn","name":"sacred_attention","module":null,"lines":50,"bytes":1242},{"path":"specs/numeric/bigint.t27","category":"specs/numeric","name":"bigint","module":"BigInt","lines":391,"bytes":14358},{"path":"specs/numeric/formats.t27","category":"specs/numeric","name":"formats","module":"Formats","lines":400,"bytes":13628},{"path":"specs/numeric/gf12.t27","category":"specs/numeric","name":"gf12","module":"GF12","lines":482,"bytes":13953},{"path":"specs/numeric/gf16.t27","category":"specs/numeric","name":"gf16","module":"triformat-gf16","lines":3392,"bytes":100829},{"path":"specs/numeric/gf20.t27","category":"specs/numeric","name":"gf20","module":"GF20","lines":469,"bytes":13578},{"path":"specs/numeric/gf24.t27","category":"specs/numeric","name":"gf24","module":"GF24","lines":469,"bytes":13626},{"path":"specs/numeric/gf32.t27","category":"specs/numeric","name":"gf32","module":"GF32","lines":480,"bytes":14168},{"path":"specs/numeric/gf4.t27","category":"specs/numeric","name":"gf4","module":"GF4","lines":306,"bytes":9145},{"path":"specs/numeric/gf8.t27","category":"specs/numeric","name":"gf8","module":"GF8","lines":522,"bytes":15315},{"path":"specs/numeric/gf_competitive.t27","category":"specs/numeric","name":"gf_competitive","module":"GFCompetitive","lines":108,"bytes":3355},{"path":"specs/numeric/goldenfloat_family.t27","category":"specs/numeric","name":"goldenfloat_family","module":"GoldenFloatFamily","lines":411,"bytes":14364},{"path":"specs/numeric/pellis_verify.t27","category":"specs/numeric","name":"pellis_verify","module":"PellisVerify","lines":65,"bytes":2169},{"path":"specs/numeric/phi_ratio.t27","category":"specs/numeric","name":"phi_ratio","module":"PhiRatio","lines":653,"bytes":24250},{"path":"specs/numeric/requant_boundary.t27","category":"specs/numeric","name":"requant_boundary","module":"RequantBoundary","lines":217,"bytes":9731},{"path":"specs/numeric/tf3.t27","category":"specs/numeric","name":"tf3","module":"triformat-tf3","lines":1662,"bytes":47300},{"path":"specs/numeric/trinity_numeric_surface.t27","category":"specs/numeric","name":"trinity_numeric_surface","module":"trinity-numeric-surface","lines":38,"bytes":1703},{"path":"specs/physics/chimera_best_gamma.t27","category":"specs/physics","name":"chimera_best_gamma","module":"chimera","lines":20,"bytes":468},{"path":"specs/physics/e8_lqg_bridge.t27","category":"specs/physics","name":"e8_lqg_bridge","module":null,"lines":113,"bytes":2853},{"path":"specs/physics/formula_discovery.t27","category":"specs/physics","name":"formula_discovery","module":"FormulaDiscovery","lines":158,"bytes":4536},{"path":"specs/physics/formula_registry.t27","category":"specs/physics","name":"formula_registry","module":null,"lines":213,"bytes":6261},{"path":"specs/physics/gamma-conflict.t27","category":"specs/physics","name":"gamma-conflict","module":null,"lines":256,"bytes":6580},{"path":"specs/physics/gamma_conjecture.t27","category":"specs/physics","name":"gamma_conjecture","module":"GammaConjecture","lines":277,"bytes":11590},{"path":"specs/physics/gi1_analysis.t27","category":"specs/physics","name":"gi1_analysis","module":"GI1Analysis","lines":238,"bytes":9763},{"path":"specs/physics/hslm_benchmark.t27","category":"specs/physics","name":"hslm_benchmark","module":null,"lines":90,"bytes":2287},{"path":"specs/physics/lqg_cs_bridge.t27","category":"specs/physics","name":"lqg_cs_bridge","module":null,"lines":141,"bytes":4725},{"path":"specs/physics/lqg_entropy.t27","category":"specs/physics","name":"lqg_entropy","module":null,"lines":67,"bytes":2841},{"path":"specs/physics/p2_brain_physics.t27","category":"specs/physics","name":"p2_brain_physics","module":"P2Brain","lines":165,"bytes":6932},{"path":"specs/physics/pellis-formulas.t27","category":"specs/physics","name":"pellis-formulas","module":"PellisFormulas","lines":67,"bytes":2116},{"path":"specs/physics/quantum.t27","category":"specs/physics","name":"quantum","module":null,"lines":72,"bytes":1715},{"path":"specs/physics/sacred_verification.t27","category":"specs/physics","name":"sacred_verification","module":"SacredVerification","lines":605,"bytes":24309},{"path":"specs/physics/su2_chern_simons.t27","category":"specs/physics","name":"su2_chern_simons","module":"SU2ChernSimons","lines":349,"bytes":12227},{"path":"specs/physics/zamolodchikov_4d_conjecture.t27","category":"specs/physics","name":"zamolodchikov_4d_conjecture","module":"Zamolodchikov4DConjecture","lines":254,"bytes":10731},{"path":"specs/pins/emitter_xdc.t27","category":"specs/pins","name":"emitter_xdc","module":"EmitterXDC","lines":295,"bytes":9345},{"path":"specs/pins/ir.t27","category":"specs/pins","name":"ir","module":"PinsIR","lines":391,"bytes":11981},{"path":"specs/pins/parser.t27","category":"specs/pins","name":"parser","module":"PinsParser","lines":583,"bytes":17861},{"path":"specs/pipeline/benchmarks.t27","category":"specs/pipeline","name":"benchmarks","module":"PipelineBenchmarks","lines":86,"bytes":2598},{"path":"specs/pipeline/e2e_test.t27","category":"specs/pipeline","name":"e2e_test","module":"PipelineE2E","lines":151,"bytes":5240},{"path":"specs/pipeline/experience_save.t27","category":"specs/pipeline","name":"experience_save","module":"ExperienceSave","lines":127,"bytes":3752},{"path":"specs/portable/relay_observer.t27","category":"specs/portable","name":"relay_observer","module":"portable","lines":651,"bytes":21457},{"path":"specs/provider/adapters.t27","category":"specs/provider","name":"adapters","module":"provider-adapters","lines":619,"bytes":16564},{"path":"specs/provider/schema.t27","category":"specs/provider","name":"schema","module":"provider-schema","lines":605,"bytes":15237},{"path":"specs/provider/stream.t27","category":"specs/provider","name":"stream","module":"provider-stream","lines":612,"bytes":16334},{"path":"specs/provider/transform.t27","category":"specs/provider","name":"transform","module":"provider-transform","lines":522,"bytes":15346},{"path":"specs/queen/brain_summaries.t27","category":"specs/queen","name":"brain_summaries","module":"BrainSummaries","lines":395,"bytes":14418},{"path":"specs/queen/lotus.t27","category":"specs/queen","name":"lotus","module":"QueenLotus","lines":804,"bytes":26358},{"path":"specs/queen/task_analysis.t27","category":"specs/queen","name":"task_analysis","module":"queen-task-analysis","lines":178,"bytes":5115},{"path":"specs/runtime/execute.t27","category":"specs/runtime","name":"execute","module":"runtime-execute","lines":735,"bytes":19042},{"path":"specs/runtime/instance.t27","category":"specs/runtime","name":"instance","module":"runtime-instance","lines":780,"bytes":20135},{"path":"specs/runtime/process.t27","category":"specs/runtime","name":"process","module":"runtime-process","lines":762,"bytes":19101},{"path":"specs/sacred/cosmology.t27","category":"specs/sacred","name":"cosmology","module":"TriCosmology","lines":16,"bytes":629},{"path":"specs/sacred/dark_matter.t27","category":"specs/sacred","name":"dark_matter","module":"dark_matter","lines":16,"bytes":629},{"path":"specs/sacred/gravity.t27","category":"specs/sacred","name":"gravity","module":"TriGravity","lines":16,"bytes":625},{"path":"specs/sacred/monopoles.t27","category":"specs/sacred","name":"monopoles","module":"TriMonopoles","lines":16,"bytes":629},{"path":"specs/sacred/quantum.t27","category":"specs/sacred","name":"quantum","module":"TriQuantum","lines":16,"bytes":625},{"path":"specs/sacred/quantum_gravity.t27","category":"specs/sacred","name":"quantum_gravity","module":"quantum_gravity","lines":16,"bytes":637},{"path":"specs/sacred/sacred_constants.t27","category":"specs/sacred","name":"sacred_constants","module":"SacredConstants","lines":24,"bytes":1160},{"path":"specs/sacred/sacred_governance.t27","category":"specs/sacred","name":"sacred_governance","module":"SacredGovernance","lines":85,"bytes":3036},{"path":"specs/sacred/sacred_identity.t27","category":"specs/sacred","name":"sacred_identity","module":"SacredIdentity","lines":77,"bytes":2757},{"path":"specs/sacred/superconductivity.t27","category":"specs/sacred","name":"superconductivity","module":"TriSuperconductivity","lines":16,"bytes":645},{"path":"specs/sandbox/health.t27","category":"specs/sandbox","name":"health","module":"sandbox","lines":43,"bytes":1234},{"path":"specs/sandbox/https_enforce.t27","category":"specs/sandbox","name":"https_enforce","module":"sandbox","lines":224,"bytes":7449},{"path":"specs/sandbox/modules.t27","category":"specs/sandbox","name":"modules","module":"sandbox","lines":22,"bytes":536},{"path":"specs/sandbox/orphan_detection.t27","category":"specs/sandbox","name":"orphan_detection","module":"sandbox","lines":257,"bytes":9041},{"path":"specs/sandbox/session_timeout.t27","category":"specs/sandbox","name":"session_timeout","module":"sandbox","lines":195,"bytes":6252},{"path":"specs/server/agent-runner.t27","category":"specs/server","name":"agent-runner","module":"AgentRunner","lines":249,"bytes":8126},{"path":"specs/server/api.t27","category":"specs/server","name":"api","module":"Api","lines":148,"bytes":4198},{"path":"specs/server/http.t27","category":"specs/server","name":"http","module":"server-http","lines":453,"bytes":12359},{"path":"specs/server/mdns.t27","category":"specs/server","name":"mdns","module":"server-mdns","lines":567,"bytes":14817},{"path":"specs/server/project.t27","category":"specs/server","name":"project","module":"Project","lines":206,"bytes":6094},{"path":"specs/server/provider.t27","category":"specs/server","name":"provider","module":"Provider","lines":257,"bytes":8105},{"path":"specs/server/router.t27","category":"specs/server","name":"router","module":"server-router","lines":575,"bytes":15018},{"path":"specs/server/routes.t27","category":"specs/server","name":"routes","module":"Routes","lines":36,"bytes":690},{"path":"specs/server/session.t27","category":"specs/server","name":"session","module":"Session","lines":273,"bytes":8497},{"path":"specs/server/sse.t27","category":"specs/server","name":"sse","module":"server-sse","lines":563,"bytes":15052},{"path":"specs/server/vm.t27","category":"specs/server","name":"vm","module":"VM","lines":593,"bytes":22613},{"path":"specs/shell/environment.t27","category":"specs/shell","name":"environment","module":"ShellEnvironment","lines":304,"bytes":9509},{"path":"specs/shell/process.t27","category":"specs/shell","name":"process","module":"ShellProcess","lines":305,"bytes":8856},{"path":"specs/shell/schema.t27","category":"specs/shell","name":"schema","module":"Shell","lines":406,"bytes":12544},{"path":"specs/storage/kv.t27","category":"specs/storage","name":"kv","module":"StorageKv","lines":159,"bytes":5529},{"path":"specs/storage/lock.t27","category":"specs/storage","name":"lock","module":"StorageLock","lines":179,"bytes":6388},{"path":"specs/storage/migrate.t27","category":"specs/storage","name":"migrate","module":"StorageMigrate","lines":193,"bytes":6892},{"path":"specs/storage/schema.t27","category":"specs/storage","name":"schema","module":"Storage","lines":146,"bytes":4070},{"path":"specs/sync/index.t27","category":"specs/sync","name":"index","module":"sync-index","lines":779,"bytes":20867},{"path":"specs/sync/schema.t27","category":"specs/sync","name":"schema","module":"sync-schema","lines":722,"bytes":19125},{"path":"specs/ternary/bigint.t27","category":"specs/ternary","name":"bigint","module":"TernaryBigInt","lines":1441,"bytes":43916},{"path":"specs/ternary/hybrid_arithmetic.t27","category":"specs/ternary","name":"hybrid_arithmetic","module":"HybridArithmetic","lines":492,"bytes":18595},{"path":"specs/ternary/hybrid_bigint.t27","category":"specs/ternary","name":"hybrid_bigint","module":"hybrid_bigint","lines":1065,"bytes":30092},{"path":"specs/ternary/packed_trit.t27","category":"specs/ternary","name":"packed_trit","module":"PackedTrit","lines":430,"bytes":16022},{"path":"specs/test_framework/core.t27","category":"specs/test_framework","name":"core","module":"core","lines":317,"bytes":10877},{"path":"specs/test_framework/graph_drift_detection.t27","category":"specs/test_framework","name":"graph_drift_detection","module":"GraphDriftDetection","lines":650,"bytes":23027},{"path":"specs/test_framework/property_test_template.t27","category":"specs/test_framework","name":"property_test_template","module":"PBTTemplate","lines":433,"bytes":16346},{"path":"specs/test_framework/runner.t27","category":"specs/test_framework","name":"runner","module":"runner","lines":618,"bytes":19960},{"path":"specs/test_framework/verilog_bench_harness.t27","category":"specs/test_framework","name":"verilog_bench_harness","module":"VerilogBenchHarness","lines":460,"bytes":17844},{"path":"specs/tools/registry.t27","category":"specs/tools","name":"registry","module":"ToolsRegistry","lines":523,"bytes":16726},{"path":"specs/tools/schema.t27","category":"specs/tools","name":"schema","module":"Tools","lines":523,"bytes":16580},{"path":"specs/tools/tri_to_t27_converter.t27","category":"specs/tools","name":"tri_to_t27_converter","module":"TriToT27Converter","lines":406,"bytes":16958},{"path":"specs/tri/agent/agent_run.t27","category":"specs/tri","name":"agent_run","module":"AgentRun","lines":16,"bytes":624},{"path":"specs/tri/agent/agents.t27","category":"specs/tri","name":"agents","module":"agents","lines":16,"bytes":619},{"path":"specs/tri/agent/autonomous_lifecycle.t27","category":"specs/tri","name":"autonomous_lifecycle","module":"TriAutonomousLifecycle","lines":73,"bytes":2953},{"path":"specs/tri/agent/autonomous_universe.t27","category":"specs/tri","name":"autonomous_universe","module":"AutonomousUniverse","lines":16,"bytes":645},{"path":"specs/tri/agent/eternal_monitor.t27","category":"specs/tri","name":"eternal_monitor","module":"EternalMonitor","lines":70,"bytes":2500},{"path":"specs/tri/agent/experience_hooks.t27","category":"specs/tri","name":"experience_hooks","module":"experience_hooks","lines":16,"bytes":639},{"path":"specs/tri/agent/faculty_board.t27","category":"specs/tri","name":"faculty_board","module":"FacultyBoard","lines":63,"bytes":2412},{"path":"specs/tri/agent/governance_agent.t27","category":"specs/tri","name":"governance_agent","module":"GovernanceAgent","lines":98,"bytes":3447},{"path":"specs/tri/agent/handoff.t27","category":"specs/tri","name":"handoff","module":"Handoff","lines":67,"bytes":2325},{"path":"specs/tri/agent/memory.t27","category":"specs/tri","name":"memory","module":"memory","lines":16,"bytes":619},{"path":"specs/tri/agent/swarm_agents.t27","category":"specs/tri","name":"swarm_agents","module":"SwarmAgents","lines":143,"bytes":5212},{"path":"specs/tri/collections/array.t27","category":"specs/tri","name":"array","module":"TriArray","lines":123,"bytes":5387},{"path":"specs/tri/collections/bitmap.t27","category":"specs/tri","name":"bitmap","module":"TriBitmap","lines":101,"bytes":4201},{"path":"specs/tri/collections/bitset.t27","category":"specs/tri","name":"bitset","module":"TriBitset","lines":97,"bytes":3588},{"path":"specs/tri/collections/bitvector.t27","category":"specs/tri","name":"bitvector","module":"TriBitvector","lines":93,"bytes":4192},{"path":"specs/tri/collections/btree.t27","category":"specs/tri","name":"btree","module":"TriBtree","lines":85,"bytes":4316},{"path":"specs/tri/collections/circular_buffer.t27","category":"specs/tri","name":"circular_buffer","module":"TriCircularBuffer","lines":79,"bytes":3676},{"path":"specs/tri/collections/context.t27","category":"specs/tri","name":"context","module":"TriContext","lines":16,"bytes":625},{"path":"specs/tri/collections/deque.t27","category":"specs/tri","name":"deque","module":"TriDeque","lines":77,"bytes":3243},{"path":"specs/tri/collections/either.t27","category":"specs/tri","name":"either","module":"TriEither","lines":75,"bytes":3222},{"path":"specs/tri/collections/interval.t27","category":"specs/tri","name":"interval","module":"TriInterval","lines":86,"bytes":4186},{"path":"specs/tri/collections/linked_list.t27","category":"specs/tri","name":"linked_list","module":"TriLinkedList","lines":84,"bytes":3032},{"path":"specs/tri/collections/list.t27","category":"specs/tri","name":"list","module":"TriList","lines":97,"bytes":3261},{"path":"specs/tri/collections/lockfree_stack.t27","category":"specs/tri","name":"lockfree_stack","module":"TriLockfreeStack","lines":70,"bytes":3118},{"path":"specs/tri/collections/lru.t27","category":"specs/tri","name":"lru","module":"TriLru","lines":57,"bytes":2516},{"path":"specs/tri/collections/lru_cache.t27","category":"specs/tri","name":"lru_cache","module":"TriLruCache","lines":68,"bytes":2554},{"path":"specs/tri/collections/map.t27","category":"specs/tri","name":"map","module":"TriMap","lines":73,"bytes":3019},{"path":"specs/tri/collections/maybe.t27","category":"specs/tri","name":"maybe","module":"TriMaybe","lines":81,"bytes":3955},{"path":"specs/tri/collections/namespace.t27","category":"specs/tri","name":"namespace","module":"TriNamespace","lines":58,"bytes":2700},{"path":"specs/tri/collections/option.t27","category":"specs/tri","name":"option","module":"TriOption","lines":79,"bytes":3504},{"path":"specs/tri/collections/priority_queue.t27","category":"specs/tri","name":"priority_queue","module":"TriPriorityQueue","lines":78,"bytes":3380},{"path":"specs/tri/collections/queue.t27","category":"specs/tri","name":"queue","module":"TriQueue","lines":70,"bytes":2992},{"path":"specs/tri/collections/result.t27","category":"specs/tri","name":"result","module":"TriResult","lines":79,"bytes":3655},{"path":"specs/tri/collections/ring_buffer.t27","category":"specs/tri","name":"ring_buffer","module":"TriRing","lines":101,"bytes":4905},{"path":"specs/tri/collections/set.t27","category":"specs/tri","name":"set","module":"TriSet","lines":65,"bytes":2723},{"path":"specs/tri/collections/skip_list.t27","category":"specs/tri","name":"skip_list","module":"TriSkipList","lines":69,"bytes":3150},{"path":"specs/tri/collections/stack.t27","category":"specs/tri","name":"stack","module":"TriStack","lines":71,"bytes":3014},{"path":"specs/tri/collections/state.t27","category":"specs/tri","name":"state","module":"TriState","lines":65,"bytes":2474},{"path":"specs/tri/collections/tuple.t27","category":"specs/tri","name":"tuple","module":"TriTuple","lines":72,"bytes":2619},{"path":"specs/tri/collections/variant.t27","category":"specs/tri","name":"variant","module":"TriVariant","lines":63,"bytes":2922},{"path":"specs/tri/crypto/base32.t27","category":"specs/tri","name":"base32","module":"TriBase32","lines":78,"bytes":4129},{"path":"specs/tri/crypto/base64.t27","category":"specs/tri","name":"base64","module":"TriBase64","lines":97,"bytes":4954},{"path":"specs/tri/crypto/crypto.t27","category":"specs/tri","name":"crypto","module":"TriCrypto","lines":56,"bytes":2500},{"path":"specs/tri/crypto/ecc.t27","category":"specs/tri","name":"ecc","module":"TriEcc","lines":89,"bytes":4348},{"path":"specs/tri/crypto/hex.t27","category":"specs/tri","name":"hex","module":"TriHex","lines":64,"bytes":2929},{"path":"specs/tri/crypto/hmac.t27","category":"specs/tri","name":"hmac","module":"TriHmac","lines":56,"bytes":2274},{"path":"specs/tri/crypto/reed_solomon.t27","category":"specs/tri","name":"reed_solomon","module":"TriReedSolomon","lines":46,"bytes":2205},{"path":"specs/tri/crypto/rsa.t27","category":"specs/tri","name":"rsa","module":"TriRsa","lines":83,"bytes":4009},{"path":"specs/tri/crypto/sha256.t27","category":"specs/tri","name":"sha256","module":"TriSha256","lines":67,"bytes":2952},{"path":"specs/tri/encoding/bson.t27","category":"specs/tri","name":"bson","module":"TriBson","lines":70,"bytes":3544},{"path":"specs/tri/encoding/csv.t27","category":"specs/tri","name":"csv","module":"TriCsv","lines":72,"bytes":3155},{"path":"specs/tri/encoding/html.t27","category":"specs/tri","name":"html","module":"TriHtml","lines":48,"bytes":2223},{"path":"specs/tri/encoding/json.t27","category":"specs/tri","name":"json","module":"TriJson","lines":67,"bytes":2655},{"path":"specs/tri/encoding/markup.t27","category":"specs/tri","name":"markup","module":"TriMarkup","lines":53,"bytes":2716},{"path":"specs/tri/encoding/mime.t27","category":"specs/tri","name":"mime","module":"TriMime","lines":57,"bytes":2794},{"path":"specs/tri/encoding/msgpack.t27","category":"specs/tri","name":"msgpack","module":"TriMsgpack","lines":93,"bytes":4485},{"path":"specs/tri/encoding/xml.t27","category":"specs/tri","name":"xml","module":"TriXml","lines":48,"bytes":2195},{"path":"specs/tri/graph/bellman_ford.t27","category":"specs/tri","name":"bellman_ford","module":"TriBellmanFord","lines":37,"bytes":1918},{"path":"specs/tri/graph/dijkstra.t27","category":"specs/tri","name":"dijkstra","module":"TriDijkstra","lines":38,"bytes":1854},{"path":"specs/tri/graph/disjoint_set.t27","category":"specs/tri","name":"disjoint_set","module":"TriDisjointSet","lines":93,"bytes":4626},{"path":"specs/tri/graph/graph.t27","category":"specs/tri","name":"graph","module":"TriGraph","lines":61,"bytes":2379},{"path":"specs/tri/graph/graph_bfs.t27","category":"specs/tri","name":"graph_bfs","module":"TriGraphBfs","lines":113,"bytes":5554},{"path":"specs/tri/graph/graph_dfs.t27","category":"specs/tri","name":"graph_dfs","module":"TriGraphDfs","lines":38,"bytes":1808},{"path":"specs/tri/graph/prims_mst.t27","category":"specs/tri","name":"prims_mst","module":"TriPrimsMst","lines":38,"bytes":1795},{"path":"specs/tri/graph/topological_sort.t27","category":"specs/tri","name":"topological_sort","module":"TriTopological","lines":47,"bytes":2082},{"path":"specs/tri/io/compress.t27","category":"specs/tri","name":"compress","module":"TriCompress","lines":58,"bytes":3115},{"path":"specs/tri/io/filesystem.t27","category":"specs/tri","name":"filesystem","module":"TriFilesystem","lines":113,"bytes":5345},{"path":"specs/tri/io/fs.t27","category":"specs/tri","name":"fs","module":"TriFs","lines":73,"bytes":2757},{"path":"specs/tri/io/io.t27","category":"specs/tri","name":"io","module":"TriIo","lines":67,"bytes":3086},{"path":"specs/tri/io/reader.t27","category":"specs/tri","name":"reader","module":"TriReader","lines":65,"bytes":2550},{"path":"specs/tri/io/writer.t27","category":"specs/tri","name":"writer","module":"TriWriter","lines":66,"bytes":2628},{"path":"specs/tri/io/zip.t27","category":"specs/tri","name":"zip","module":"TriZipper","lines":77,"bytes":2847},{"path":"specs/tri/math/bezier.t27","category":"specs/tri","name":"bezier","module":"TriBezier","lines":75,"bytes":3811},{"path":"specs/tri/math/constants.t27","category":"specs/tri","name":"constants","module":"TriConstants","lines":128,"bytes":5390},{"path":"specs/tri/math/math.t27","category":"specs/tri","name":"math","module":"TriMath","lines":16,"bytes":619},{"path":"specs/tri/math/matrix.t27","category":"specs/tri","name":"matrix","module":"TriMatrix","lines":96,"bytes":4182},{"path":"specs/tri/math/measurement.t27","category":"specs/tri","name":"measurement","module":"TriMeasurement","lines":16,"bytes":633},{"path":"specs/tri/math/polynomial.t27","category":"specs/tri","name":"polynomial","module":"TriPolynomial","lines":80,"bytes":3851},{"path":"specs/tri/math/probability.t27","category":"specs/tri","name":"probability","module":"TriProbability","lines":67,"bytes":2602},{"path":"specs/tri/math/statistics.t27","category":"specs/tri","name":"statistics","module":"TriStatistics","lines":77,"bytes":2735},{"path":"specs/tri/net/async.t27","category":"specs/tri","name":"async","module":"TriAsync","lines":86,"bytes":3896},{"path":"specs/tri/net/async_stream.t27","category":"specs/tri","name":"async_stream","module":"TriAsyncStream","lines":86,"bytes":4087},{"path":"specs/tri/net/channel.t27","category":"specs/tri","name":"channel","module":"TriChannel","lines":67,"bytes":2629},{"path":"specs/tri/net/cloud.t27","category":"specs/tri","name":"cloud","module":"TriCloud","lines":16,"bytes":621},{"path":"specs/tri/net/http.t27","category":"specs/tri","name":"http","module":"TriHttp","lines":133,"bytes":6147},{"path":"specs/tri/net/net.t27","category":"specs/tri","name":"net","module":"TriNet","lines":71,"bytes":3117},{"path":"specs/tri/net/url.t27","category":"specs/tri","name":"url","module":"TriUrl","lines":83,"bytes":4112},{"path":"specs/tri/pipeline/batch_runner.t27","category":"specs/tri","name":"batch_runner","module":"BatchRunner","lines":115,"bytes":4376},{"path":"specs/tri/pipeline/builder.t27","category":"specs/tri","name":"builder","module":"TriBuilder","lines":107,"bytes":3957},{"path":"specs/tri/pipeline/cloud_orchestrator.t27","category":"specs/tri","name":"cloud_orchestrator","module":"CloudOrchestrator","lines":16,"bytes":643},{"path":"specs/tri/pipeline/codegen.t27","category":"specs/tri","name":"codegen","module":"TestSpec","lines":27,"bytes":1147},{"path":"specs/tri/pipeline/pipeline.t27","category":"specs/tri","name":"pipeline","module":"TriPipeline","lines":16,"bytes":627},{"path":"specs/tri/pipeline/pipeline_parallel.t27","category":"specs/tri","name":"pipeline_parallel","module":"TriPipelineParallel","lines":50,"bytes":1900},{"path":"specs/tri/pipeline/spec_parser.t27","category":"specs/tri","name":"spec_parser","module":"TriSpecParser","lines":16,"bytes":632},{"path":"specs/tri/pipeline/spec_writer.t27","category":"specs/tri","name":"spec_writer","module":"SpecWriter","lines":46,"bytes":1622},{"path":"specs/tri/pipeline/workflow.t27","category":"specs/tri","name":"workflow","module":"Workflow","lines":31,"bytes":1263},{"path":"specs/tri/pipeline/workflow_executor.t27","category":"specs/tri","name":"workflow_executor","module":"WorkflowExecutor","lines":16,"bytes":641},{"path":"specs/tri/pipeline/workflow_parser.t27","category":"specs/tri","name":"workflow_parser","module":"WorkflowParser","lines":16,"bytes":637},{"path":"specs/tri/search/aho_corasick.t27","category":"specs/tri","name":"aho_corasick","module":"TriAhoCorasick","lines":99,"bytes":4285},{"path":"specs/tri/search/bloom_filter.t27","category":"specs/tri","name":"bloom_filter","module":"TriBloomFilter","lines":58,"bytes":2868},{"path":"specs/tri/search/boyer_moore.t27","category":"specs/tri","name":"boyer_moore","module":"TriBoyerMoore","lines":72,"bytes":3868},{"path":"specs/tri/search/knuth_morris_pratt.t27","category":"specs/tri","name":"knuth_morris_pratt","module":"TriKmp","lines":85,"bytes":4347},{"path":"specs/tri/search/match.t27","category":"specs/tri","name":"match","module":null,"lines":68,"bytes":3333},{"path":"specs/tri/search/pattern.t27","category":"specs/tri","name":"pattern","module":"TriPattern","lines":61,"bytes":3040},{"path":"specs/tri/search/rabin_karp.t27","category":"specs/tri","name":"rabin_karp","module":"TriRabinKarp","lines":73,"bytes":3867},{"path":"specs/tri/search/regex.t27","category":"specs/tri","name":"regex","module":"TriRegex","lines":91,"bytes":4367},{"path":"specs/tri/search/regex_advanced.t27","category":"specs/tri","name":"regex_advanced","module":"TriRegexAdvanced","lines":62,"bytes":2766},{"path":"specs/tri/search/search.t27","category":"specs/tri","name":"search","module":"TriSearch","lines":63,"bytes":3032},{"path":"specs/tri/sort/counting_sort.t27","category":"specs/tri","name":"counting_sort","module":"TriCountingSort","lines":27,"bytes":1352},{"path":"specs/tri/sort/heap_sort.t27","category":"specs/tri","name":"heap_sort","module":"TriHeapSort","lines":27,"bytes":1258},{"path":"specs/tri/sort/insertion_sort.t27","category":"specs/tri","name":"insertion_sort","module":"TriInsertionSort","lines":27,"bytes":1262},{"path":"specs/tri/sort/merge_sort.t27","category":"specs/tri","name":"merge_sort","module":"TriMergeSort","lines":37,"bytes":1665},{"path":"specs/tri/sort/quick_sort.t27","category":"specs/tri","name":"quick_sort","module":"TriQuickSort","lines":41,"bytes":1923},{"path":"specs/tri/sort/radix_sort.t27","category":"specs/tri","name":"radix_sort","module":"TriRadixSort","lines":56,"bytes":2879},{"path":"specs/tri/sort/selection_sort.t27","category":"specs/tri","name":"selection_sort","module":"TriSelectionSort","lines":27,"bytes":1247},{"path":"specs/tri/sort/shell_sort.t27","category":"specs/tri","name":"shell_sort","module":"TriShellSort","lines":27,"bytes":1257},{"path":"specs/tri/sort/sort.t27","category":"specs/tri","name":"sort","module":"TriSort","lines":57,"bytes":3188},{"path":"specs/tri/sort/tim_sort.t27","category":"specs/tri","name":"tim_sort","module":"TriTimSort","lines":27,"bytes":1311},{"path":"specs/tri/trees/avl_tree.t27","category":"specs/tri","name":"avl_tree","module":"TriAvlTree","lines":76,"bytes":2769},{"path":"specs/tri/trees/b_tree.t27","category":"specs/tri","name":"b_tree","module":"TriBTree","lines":96,"bytes":4616},{"path":"specs/tri/trees/fenwick_tree.t27","category":"specs/tri","name":"fenwick_tree","module":"TriFenwick","lines":101,"bytes":4544},{"path":"specs/tri/trees/kd_tree.t27","category":"specs/tri","name":"kd_tree","module":"TriKdTree","lines":89,"bytes":3998},{"path":"specs/tri/trees/octree.t27","category":"specs/tri","name":"octree","module":"TriOctree","lines":103,"bytes":4639},{"path":"specs/tri/trees/quadtree.t27","category":"specs/tri","name":"quadtree","module":"TriQuadtree","lines":110,"bytes":5036},{"path":"specs/tri/trees/red_black_tree.t27","category":"specs/tri","name":"red_black_tree","module":"TriRbTree","lines":81,"bytes":2892},{"path":"specs/tri/trees/rtree.t27","category":"specs/tri","name":"rtree","module":"TriRtree","lines":88,"bytes":3876},{"path":"specs/tri/trees/segment_tree.t27","category":"specs/tri","name":"segment_tree","module":"TriSegmentTree","lines":67,"bytes":3238},{"path":"specs/tri/trees/splay_tree.t27","category":"specs/tri","name":"splay_tree","module":"TriSplayTree","lines":76,"bytes":2802},{"path":"specs/tri/trees/suffix_array.t27","category":"specs/tri","name":"suffix_array","module":"TriSuffixArray","lines":83,"bytes":4311},{"path":"specs/tri/trees/tree.t27","category":"specs/tri","name":"tree","module":"TriTree","lines":85,"bytes":3768},{"path":"specs/tri/trees/trie.t27","category":"specs/tri","name":"trie","module":"TriTrie","lines":93,"bytes":4263},{"path":"specs/tri/utils/args.t27","category":"specs/tri","name":"args","module":null,"lines":86,"bytes":4133},{"path":"specs/tri/utils/arrow_time.t27","category":"specs/tri","name":"arrow_time","module":"arrow_time","lines":16,"bytes":627},{"path":"specs/tri/utils/bytes.t27","category":"specs/tri","name":"bytes","module":"TriBytes","lines":106,"bytes":3875},{"path":"specs/tri/utils/color.t27","category":"specs/tri","name":"color","module":"TriColor","lines":95,"bytes":4563},{"path":"specs/tri/utils/colors.t27","category":"specs/tri","name":"colors","module":"TriColors","lines":16,"bytes":623},{"path":"specs/tri/utils/config.t27","category":"specs/tri","name":"config","module":"TriConfig","lines":89,"bytes":4082},{"path":"specs/tri/utils/error.t27","category":"specs/tri","name":"error","module":"TriError","lines":26,"bytes":1415},{"path":"specs/tri/utils/exit_codes.t27","category":"specs/tri","name":"exit_codes","module":"TriExitCodes","lines":35,"bytes":1562},{"path":"specs/tri/utils/help.t27","category":"specs/tri","name":"help","module":"TriHelp","lines":32,"bytes":1370},{"path":"specs/tri/utils/logger.t27","category":"specs/tri","name":"logger","module":null,"lines":66,"bytes":2659},{"path":"specs/tri/utils/logging.t27","category":"specs/tri","name":"logging","module":"TriLogging","lines":102,"bytes":5075},{"path":"specs/tri/utils/random.t27","category":"specs/tri","name":"random","module":"TriRandom","lines":97,"bytes":4367},{"path":"specs/tri/utils/string.t27","category":"specs/tri","name":"string","module":"string","lines":16,"bytes":619},{"path":"specs/tri/utils/template.t27","category":"specs/tri","name":"template","module":"TriTemplate","lines":70,"bytes":3884},{"path":"specs/tri/utils/terminal.t27","category":"specs/tri","name":"terminal","module":"TriTerminal","lines":59,"bytes":2447},{"path":"specs/tri/utils/text.t27","category":"specs/tri","name":"text","module":"TriText","lines":67,"bytes":2793},{"path":"specs/tri/utils/time.t27","category":"specs/tri","name":"time","module":"TriTime","lines":93,"bytes":4138},{"path":"specs/tri/utils/utf8.t27","category":"specs/tri","name":"utf8","module":"TriUtf8","lines":82,"bytes":3595},{"path":"specs/tri/utils/version.t27","category":"specs/tri","name":"version","module":"TriVersion","lines":85,"bytes":4113},{"path":"specs/vm/jit_semantics.t27","category":"specs/vm","name":"jit_semantics","module":"JitSemantics","lines":319,"bytes":14011},{"path":"specs/vsa/jones_polynomial.t27","category":"specs/vsa","name":"jones_polynomial","module":"JonesPolynomial","lines":353,"bytes":12139},{"path":"specs/vsa/ops.t27","category":"specs/vsa","name":"ops","module":"VSAOps","lines":670,"bytes":27126},{"path":"specs/vsa/packed_vsa.t27","category":"specs/vsa","name":"packed_vsa","module":"PackedVsa","lines":291,"bytes":11657},{"path":"specs/vsa/sdk.t27","category":"specs/vsa","name":"sdk","module":"SDK","lines":626,"bytes":24071},{"path":"specs/vsa/sequence_hdc.t27","category":"specs/vsa","name":"sequence_hdc","module":"SequenceHdc","lines":342,"bytes":15174},{"path":"specs/vsa/similarity_search.t27","category":"specs/vsa","name":"similarity_search","module":"VSASimilaritySearch","lines":467,"bytes":15246},{"path":"specs/vsa/vsa_core.t27","category":"specs/vsa","name":"vsa_core","module":"vsa_core","lines":1012,"bytes":29297},{"path":"test_highlight.t27","category":"root","name":"test_highlight","module":null,"lines":57,"bytes":1013},{"path":"tests/comprehensive_suite.t27","category":"tests","name":"comprehensive_suite","module":"tests-comprehensive-suite","lines":19,"bytes":560},{"path":"tests/ring0_trivial.t27","category":"tests","name":"ring0_trivial","module":"ring0","lines":11,"bytes":201},{"path":"vscode-trinity-swe/test_highlight.t27","category":"vscode-trinity-swe","name":"test_highlight","module":null,"lines":1,"bytes":0}]} \ No newline at end of file diff --git a/apps/website/scripts/sync-t27-specs.mjs b/apps/website/scripts/sync-t27-specs.mjs index 2af1e61afa..f3140ebf98 100644 --- a/apps/website/scripts/sync-t27-specs.mjs +++ b/apps/website/scripts/sync-t27-specs.mjs @@ -29,7 +29,9 @@ const SPECS_SRC = join(T27, 'specs') const WASM_SRC = join(T27, 'bindings/wasm-explorer/target/wasm32-unknown-unknown/release/t27_wasm_explorer.wasm') const OUT_DIR = join(WEBSITE, 'public/t27') -const SPECS_OUT = join(OUT_DIR, 'specs') +// `files/`, not `specs/`: paths inside are repo-root-relative now, so a spec +// from specs/demos/ would otherwise land at specs/specs/demos/. +const SPECS_OUT = join(OUT_DIR, 'files') function fail(msg) { console.error(`sync-t27-specs: ${msg}`) @@ -41,11 +43,19 @@ if (!existsSync(WASM_SRC)) fail(`wasm bridge not built.\n cd ${T27}/bindings/wa const sha = execFileSync('git', ['-C', T27, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim() const shortSha = sha.slice(0, 9) -const dirty = execFileSync('git', ['-C', T27, 'status', '--porcelain', '--', 'specs', 'bootstrap/src/compiler.rs'], { encoding: 'utf8' }).trim() +const dirty = execFileSync('git', ['-C', T27, 'status', '--porcelain', '--', 'specs', 'chips', 'compiler', 'bootstrap/src/compiler.rs'], { encoding: 'utf8' }).trim() -// Walk without a dependency -- `find` is already required by nothing else here. -const files = execFileSync('find', [SPECS_SRC, '-name', '*.t27', '-type', 'f'], { encoding: 'utf8' }) - .split('\n').filter(Boolean).sort() +// Every .t27 in the repo, not just specs/ -- `chips/` alone holds ~147 real +// specs, and a corpus that quietly stops at specs/ cannot show a problem living +// outside it. Two exclusions, both duplicates rather than judgement calls: +// .git/ -- object store +// .claude/ -- git worktrees, i.e. second checkouts of files already counted +// (the same compiler/ast.t27 appears in every worktree) +const files = execFileSync('find', [ + T27, '-name', '*.t27', '-type', 'f', + '-not', '-path', `${T27}/.git/*`, + '-not', '-path', `${T27}/.claude/*`, +], { encoding: 'utf8' }).split('\n').filter(Boolean).sort() if (!files.length) fail('no .t27 files found') @@ -54,19 +64,25 @@ mkdirSync(SPECS_OUT, { recursive: true }) const entries = [] for (const abs of files) { - const rel = relative(SPECS_SRC, abs) + // Paths are now relative to the repo root, so a spec's real home (specs/, + // chips/, compiler/…) stays visible in the UI rather than being flattened. + const rel = relative(T27, abs) const text = readFileSync(abs, 'utf8') const dest = join(SPECS_OUT, rel) mkdirSync(dirname(dest), { recursive: true }) writeFileSync(dest, text) const parts = rel.split('/') + // Two segments, not one: with the corpus widened past specs/, a single + // segment would lump all 497 specs under "specs" and all 147 chip specs + // under "chips", throwing away the grouping that makes the list navigable. + const category = parts.length > 2 ? `${parts[0]}/${parts[1]}` : parts.length > 1 ? parts[0] : 'root' // A spec's `module X {` line is a better label than its filename when the two // disagree, which they often do. - const moduleMatch = text.match(/^\s*module\s+([A-Za-z0-9_]+)/m) + const moduleMatch = text.match(/^\s*module\s+([A-Za-z0-9_-]+)/m) entries.push({ path: rel, - category: parts.length > 1 ? parts[0] : 'root', + category, name: parts[parts.length - 1].replace(/\.t27$/, ''), module: moduleMatch ? moduleMatch[1] : null, lines: text.split('\n').length, diff --git a/apps/website/src/components/Navigation.tsx b/apps/website/src/components/Navigation.tsx index 23f9e3fbf0..a31ef5c460 100644 --- a/apps/website/src/components/Navigation.tsx +++ b/apps/website/src/components/Navigation.tsx @@ -41,6 +41,7 @@ const PAGES: PageLink[] = [ { href: '#/blog', en: 'Blog', ru: 'Блог', note: 'Notes on the work as it happens', noteRu: 'Заметки по ходу работы' }, { href: '#/dashboard', en: 'Dashboard', ru: 'Панель', note: 'Project metrics', noteRu: 'Метрики проекта', color: '#00ccff' }, { href: '#/tree', en: 'Research Lab', ru: 'Исслед. лаб', note: 'Interactive visualisations', noteRu: 'Интерактивные визуализации', color: '#ffd700' }, + { href: '#/specs', en: 'Spec Explorer', ru: 'Обозреватель спек', note: 'All 497 .t27 specs, layer by layer, through the real compiler', noteRu: 'Все 497 спек .t27 по слоям через настоящий компилятор', color: '#00FF88' }, { href: DOCS_URL, en: 'Docs', ru: 'Документация', note: 'Full documentation', noteRu: 'Полная документация', external: true }, ] diff --git a/apps/website/src/components/SpecCodeView.tsx b/apps/website/src/components/SpecCodeView.tsx new file mode 100644 index 0000000000..9ab2d563ca --- /dev/null +++ b/apps/website/src/components/SpecCodeView.tsx @@ -0,0 +1,129 @@ +// Line-numbered, syntax-highlighted code pane with a copy button. +// +// Shared by every code-bearing layer in the spec explorer (source, HIR, and the +// five codegen targets) so they cannot drift apart in behaviour or styling. +// +// Rendering is plain DOM rather than an editor component: the source pane has +// to highlight the line an AST node points at, which is far simpler to do when +// each line is a real element we own. + +import { memo, useCallback, useState } from 'react' +import { CLS_COLOR, type Span } from '../lib/highlight' + +interface Props { + lines: Span[][] + /** 1-based line to highlight, e.g. the line an AST node came from. */ + activeLine?: number | null + onHoverLine?: (line: number | null) => void + raw: string + copyLabel: string + copiedLabel: string + /** Rendered above the code, e.g. a byte count. */ + meta?: string +} + +const MONO = "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace" + +function SpecCodeViewImpl({ lines, activeLine, onHoverLine, raw, copyLabel, copiedLabel, meta }: Props) { + const [copied, setCopied] = useState(false) + + const copy = useCallback(() => { + // clipboard is unavailable on insecure origins; the button simply does + // nothing visible rather than throwing an unhandled rejection. + navigator.clipboard?.writeText(raw).then( + () => { + setCopied(true) + window.setTimeout(() => setCopied(false), 1400) + }, + () => {}, + ) + }, [raw]) + + const gutter = String(lines.length).length + + return ( +
+
+ {meta && {meta}} + +
+ +
+ {lines.map((spans, i) => { + const n = i + 1 + const active = activeLine === n + return ( +
onHoverLine(n) : undefined} + onMouseLeave={onHoverLine ? () => onHoverLine(null) : undefined} + style={{ + display: 'flex', + background: active ? 'rgba(255,215,0,0.14)' : 'transparent', + borderLeft: `2px solid ${active ? '#FFD700' : 'transparent'}`, + }} + > + + {n} + + + {spans.length === 0 || (spans.length === 1 && !spans[0].text) ? ( + ' ' + ) : ( + spans.map((s, j) => ( + + {s.text} + + )) + )} + +
+ ) + })} +
+
+ ) +} + +export const SpecCodeView = memo(SpecCodeViewImpl) diff --git a/apps/website/src/lib/highlight.ts b/apps/website/src/lib/highlight.ts new file mode 100644 index 0000000000..6f6ad6c162 --- /dev/null +++ b/apps/website/src/lib/highlight.ts @@ -0,0 +1,142 @@ +// Syntax highlighting for the spec explorer. +// +// Two different jobs, deliberately solved two different ways: +// +// .t27 source -- coloured from the compiler's OWN token stream. The lexer +// already told us exactly what every span is, so there is no +// reason to re-guess it with regexes and no way for the +// colours to disagree with the compiler. +// generated code -- Zig/Verilog/C/Rust output has no token stream coming back +// from us, so this falls back to a small regex pass. It is +// presentation only; nothing downstream depends on it. + +import type { T27Token } from './t27Compiler' + +export type Cls = + | 'kw' | 'num' | 'str' | 'ident' | 'op' | 'punct' | 'comment' | 'type' | 'plain' + +export interface Span { text: string; cls: Cls } + +const KW = new Set([ + 'KwPub', 'KwConst', 'KwFn', 'KwEnum', 'KwStruct', 'KwTest', 'KwInvariant', + 'KwBench', 'KwModule', 'KwIf', 'KwElse', 'KwFor', 'KwWhile', 'KwSwitch', + 'KwReturn', 'KwVar', 'KwUsing', 'KwVoid', 'KwTrue', 'KwFalse', 'KwUse', + 'KwOr', 'KwAnd', 'KwTry', 'KwBreak', 'KwContinue', +]) + +const PUNCT = new Set([ + 'Colon', 'Comma', 'LParen', 'RParen', 'LBrace', 'RBrace', 'LBracket', + 'RBracket', 'Dot', 'Semicolon', +]) + +function classOf(kind: string): Cls { + if (KW.has(kind)) return 'kw' + if (kind === 'Number') return 'num' + if (kind === 'String' || kind === 'CharLiteral') return 'str' + if (kind === 'Ident') return 'ident' + if (PUNCT.has(kind)) return 'punct' + if (kind === 'Eof') return 'plain' + return 'op' +} + +/** + * Rebuild each source line as coloured spans, driven by the real tokens. + * + * The lexer reports 1-based line/col per token but does not emit comments or + * whitespace, so anything between two tokens is copied through verbatim and + * comment-only stretches are detected here rather than invented upstream. + */ +export function highlightSource(source: string, tokens: T27Token[]): Span[][] { + const lines = source.split('\n') + const byLine = new Map() + for (const t of tokens) { + if (t.kind === 'Eof' || !t.lexeme) continue + const arr = byLine.get(t.line) + if (arr) arr.push(t) + else byLine.set(t.line, [t]) + } + + return lines.map((text, i) => { + const toks = byLine.get(i + 1) + if (!toks || toks.length === 0) { + // No tokens on this line: it is blank, or entirely a comment. + return [{ text, cls: text.trim().startsWith('//') ? 'comment' : 'plain' }] + } + toks.sort((a, b) => a.col - b.col) + + const out: Span[] = [] + let cursor = 0 + for (const t of toks) { + // `col` is 1-based; trust the lexeme's own length rather than re-scanning. + const start = Math.max(0, t.col - 1) + if (start > cursor) out.push({ text: text.slice(cursor, start), cls: 'plain' }) + const slice = text.slice(start, start + t.lexeme.length) + // If the reported span does not match the lexeme, the line has something + // the token list cannot explain -- emit it plain rather than mis-colour. + if (slice !== t.lexeme) continue + out.push({ text: slice, cls: classOf(t.kind) }) + cursor = start + t.lexeme.length + } + if (cursor < text.length) { + const rest = text.slice(cursor) + // A trailing `//` after real tokens is a comment, not code. + const c = rest.indexOf('//') + if (c >= 0) { + if (c > 0) out.push({ text: rest.slice(0, c), cls: 'plain' }) + out.push({ text: rest.slice(c), cls: 'comment' }) + } else { + out.push({ text: rest, cls: 'plain' }) + } + } + return out.length ? out : [{ text, cls: 'plain' }] + }) +} + +const KEYWORDS: Record = { + zig: ['const', 'var', 'fn', 'pub', 'return', 'if', 'else', 'while', 'for', 'switch', 'struct', 'enum', 'union', 'try', 'catch', 'defer', 'errdefer', 'comptime', 'inline', 'test', 'and', 'or', 'orelse', 'unreachable', 'break', 'continue', 'export', 'extern', 'usingnamespace'], + verilog: ['module', 'endmodule', 'input', 'output', 'inout', 'wire', 'reg', 'logic', 'always', 'always_ff', 'always_comb', 'assign', 'begin', 'end', 'if', 'else', 'case', 'endcase', 'for', 'while', 'function', 'endfunction', 'task', 'endtask', 'parameter', 'localparam', 'integer', 'genvar', 'generate', 'endgenerate', 'posedge', 'negedge', 'default', 'initial'], + c: ['int', 'char', 'void', 'return', 'if', 'else', 'while', 'for', 'switch', 'case', 'break', 'continue', 'struct', 'enum', 'union', 'typedef', 'const', 'static', 'unsigned', 'signed', 'long', 'short', 'float', 'double', 'sizeof', 'include', 'define', 'ifndef', 'endif'], + rust: ['fn', 'let', 'mut', 'const', 'pub', 'struct', 'enum', 'impl', 'trait', 'use', 'mod', 'return', 'if', 'else', 'while', 'for', 'loop', 'match', 'break', 'continue', 'where', 'type', 'self', 'Self', 'crate', 'unsafe', 'as', 'in', 'ref', 'move'], +} + +/** Cheap regex highlighter for generated output. Presentation only. */ +export function highlightCode(code: string, lang: string): Span[][] { + const kws = new Set(KEYWORDS[lang] || []) + const lineComment = lang === 'verilog' || lang === 'c' || lang === 'rust' || lang === 'zig' ? '//' : '//' + + return code.split('\n').map((line) => { + const out: Span[] = [] + const c = line.indexOf(lineComment) + // Only treat `//` as a comment when it is not inside a string on this line. + const q = line.indexOf('"') + const codePart = c >= 0 && (q < 0 || c < q) ? line.slice(0, c) : line + const commentPart = c >= 0 && (q < 0 || c < q) ? line.slice(c) : '' + + // One pass: strings, numbers, identifiers, everything else. + const re = /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\b\d[\w'.]*\b|[A-Za-z_`$][\w$]*|\s+|.)/g + let m: RegExpExecArray | null + while ((m = re.exec(codePart)) !== null) { + const tok = m[0] + if (/^["']/.test(tok)) out.push({ text: tok, cls: 'str' }) + else if (/^\d/.test(tok)) out.push({ text: tok, cls: 'num' }) + else if (/^[`$]/.test(tok)) out.push({ text: tok, cls: 'kw' }) + else if (/^[A-Za-z_]/.test(tok)) out.push({ text: tok, cls: kws.has(tok) ? 'kw' : 'ident' }) + else if (/^\s+$/.test(tok)) out.push({ text: tok, cls: 'plain' }) + else out.push({ text: tok, cls: /[{}()[\];,.]/.test(tok) ? 'punct' : 'op' }) + } + if (commentPart) out.push({ text: commentPart, cls: 'comment' }) + return out.length ? out : [{ text: line, cls: 'plain' }] + }) +} + +export const CLS_COLOR: Record = { + kw: '#ff7b72', + num: '#79c0ff', + str: '#a5d6ff', + ident: '#e6edf3', + op: '#ff9bce', + punct: '#8b949e', + comment: '#6e7681', + type: '#ffa657', + plain: '#c9d1d9', +} diff --git a/apps/website/src/lib/t27Compiler.ts b/apps/website/src/lib/t27Compiler.ts index 5ef39ca88f..8919267e12 100644 --- a/apps/website/src/lib/t27Compiler.ts +++ b/apps/website/src/lib/t27Compiler.ts @@ -121,7 +121,7 @@ export async function loadManifest(): Promise { } export async function loadSpecSource(path: string): Promise { - const res = await fetch(`t27/specs/${path}`) + const res = await fetch(`t27/files/${path}`) if (!res.ok) throw new Error(`could not fetch spec ${path} (${res.status})`) return res.text() } diff --git a/apps/website/src/pages/SpecExplorer.tsx b/apps/website/src/pages/SpecExplorer.tsx index 1767d277e9..ed3a6e38c2 100644 --- a/apps/website/src/pages/SpecExplorer.tsx +++ b/apps/website/src/pages/SpecExplorer.tsx @@ -11,10 +11,12 @@ // came from, and avoids Monaco's CDN fetch on a page that already loads a // 477 KB wasm module. -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { Link } from 'react-router-dom' import { useI18n } from '../i18n/context' import { usePageMeta } from '../hooks/usePageMeta' +import { SpecCodeView } from '../components/SpecCodeView' +import { highlightCode, highlightSource, type Span } from '../lib/highlight' import { analyze, loadManifest, @@ -74,6 +76,10 @@ const UI = { filterKind: 'Filter by node kind', clear: 'Clear', matchedNodes: 'matching nodes', + copy: 'Copy', + copied: 'Copied', + highlightNote: 'Coloured from the compiler’s own token stream', + generated: 'generated', }, ru: { title: 'Обозреватель спек', @@ -123,6 +129,10 @@ const UI = { filterKind: 'Фильтр по типу узла', clear: 'Сбросить', matchedNodes: 'подходящих узлов', + copy: 'Копировать', + copied: 'Скопировано', + highlightNote: 'Раскрашено по собственному потоку токенов компилятора', + generated: 'сгенерировано', }, } as const @@ -143,6 +153,11 @@ const C = { mono: "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace", } +function fmtBytes(n: number): string { + if (n < 1024) return `${n}B` + return `${(n / 1024).toFixed(1)}K` +} + /** Colour a node by its family so structure reads at a glance. */ function kindColor(kind: string): string { if (kind.startsWith('Expr')) return C.accent @@ -285,12 +300,22 @@ export default function SpecExplorer() { const [kindFilter, setKindFilter] = useState('') const [tokenLimit, setTokenLimit] = useState(400) const [ms, setMs] = useState(null) - const sourceRef = useRef(null) useEffect(() => { loadManifest().then(setManifest).catch((e) => setErr(String(e))) }, []) + // Inline styles cannot carry media queries, and the header has three pieces + // of text that will happily overlap rather than wrap. Track the width and + // drop the optional ones instead. + const [narrow, setNarrow] = useState(() => (typeof window === 'undefined' ? false : window.innerWidth < 1100)) + useEffect(() => { + const onResize = () => setNarrow(window.innerWidth < 1100) + onResize() + window.addEventListener('resize', onResize) + return () => window.removeEventListener('resize', onResize) + }, []) + const filtered = useMemo(() => { if (!manifest) return [] const q = query.trim().toLowerCase() @@ -361,14 +386,24 @@ export default function SpecExplorer() { return [...counts.entries()].sort((a, b) => b[1] - a[1]) }, [result]) - const sourceLines = useMemo(() => source.split('\n'), [source]) + // Highlight the source from the compiler's own tokens -- see lib/highlight.ts. + const sourceSpans: Span[][] = useMemo( + () => (result ? highlightSource(source, result.tokens) : source.split('\n').map((l) => [{ text: l, cls: 'plain' as const }])), + [source, result], + ) + + const activeTarget = LAYERS.find((l) => l.id === layer)?.kind === 'target' ? result?.targets?.[layer] : undefined - useEffect(() => { - if (hoverLine && sourceRef.current && layer === 'source') { - const el = sourceRef.current.querySelector(`[data-line="${hoverLine}"]`) - el?.scrollIntoView({ block: 'nearest' }) + const codeSpans: Span[][] | null = useMemo(() => { + if (layer === 'hir' && result?.hir.ok && result.hir.text) return highlightCode(result.hir.text, 'verilog') + if (activeTarget?.ok && activeTarget.code) { + const langOf: Record = { + zig: 'zig', verilog: 'verilog', verilog_hir: 'verilog', c: 'c', rust: 'rust', + } + return highlightCode(activeTarget.code, langOf[layer] || 'plain') } - }, [hoverLine, layer]) + return null + }, [layer, result, activeTarget]) const lossCount = (result?.discarded.length || 0) + (result?.swallowed.length || 0) + (result?.lexerDiscarded.length || 0) @@ -392,28 +427,64 @@ export default function SpecExplorer() { }} > {/* header */} + {/* Everything after the title is allowed to shrink and then clip: on a + narrow window the provenance line would otherwise wrap under the + heading and overlap it. */}
- + {ui.back} -
- {ui.title} - {ui.subtitle} +
+ {ui.title} + {!narrow && ( + + {ui.subtitle} + + )}
-
- ◆ {ui.wasmNote} +
+ {!narrow && ◆ {ui.wasmNote}} {manifest && ( - + {ui.provenance} {manifest.generatedFrom.repo}@{manifest.generatedFrom.shortCommit} {manifest.generatedFrom.specsOrCompilerDirty ? ` (${ui.snapshotDirty})` : ''} @@ -426,7 +497,10 @@ export default function SpecExplorer() {
From 0ee39155361822ee41ee78a1dad2f731ad4eece3 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasilev Date: Sat, 5 Sep 2026 18:01:20 +0700 Subject: [PATCH 03/15] feat(website): group specs by health, open on a teaching spec, add pipeline ribbon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The library was 668 rows sorted by path, which made a catalogue of the corpus but not a way to see its state. Three changes, all from the same idea: the working specs are the product, the broken ones are one click away. HEALTH IS PRECOMPUTED, NOT GUESSED. sync-t27-specs.mjs now runs the same wasm the browser runs over the whole corpus and records per-spec health in the manifest. Compiling 668 specs in the browser just to colour a list would take minutes; doing it at sync time costs one pass and makes the list instant. 453 ok · 209 warn · 6 fail (= 668, and they sum on purpose) Worst-wins and disjoint: `fail` means a backend refused to emit at all, `warn` means it emitted but the compiler dropped or flagged something, `ok` means clean through every layer. Counts that do not add up are how a health page loses credibility, so each spec is counted exactly once. FILTER CHIPS CARRY THEIR COUNTS, defaulting to Working. Summary and navigation are one control rather than two that can disagree. Status uses shape as well as hue (✓ ⚠ ✕) so it survives a colour-blind reader, and a failing row names its own cause inline -- "verilog_hir ✕ 3 type errors" -- rather than making you open it to find out. OPENS ON specs/demos/hello_world.t27 (new, t27#3276), badged START HERE. An empty pane teaches nothing, and dropping a first-time visitor into a 1600-line FPGA spec teaches less. That file is 98 lines and clean on every axis. Each row now shows the spec's own header comment as a description (634 of 668 have one), with the directory dimmed and the basename bold. PIPELINE RIBBON above the tabs: one bar per stage, height on a shared log scale, red where a stage failed. It answers "where does this spec die" and "why is the Verilog four times the Zig" without opening five tabs. Log scale because linear flattens every stage next to a 45 KB Verilog output. Hand- written SVG -- a chart library would outweigh the page. Corpus health renders as one stacked bar rather than a 668-cell waffle: at that count cells cannot be read as proportions, and six failures would be three lost pixels. --- .../t27/files/specs/demos/hello_world.t27 | 98 ++++++++ apps/website/public/t27/manifest.json | 2 +- apps/website/scripts/sync-t27-specs.mjs | 96 ++++++++ apps/website/src/components/SpecGraphics.tsx | 144 ++++++++++++ apps/website/src/lib/t27Compiler.ts | 20 ++ apps/website/src/pages/SpecExplorer.tsx | 218 ++++++++++++++++-- 6 files changed, 561 insertions(+), 17 deletions(-) create mode 100644 apps/website/public/t27/files/specs/demos/hello_world.t27 create mode 100644 apps/website/src/components/SpecGraphics.tsx diff --git a/apps/website/public/t27/files/specs/demos/hello_world.t27 b/apps/website/public/t27/files/specs/demos/hello_world.t27 new file mode 100644 index 0000000000..4e7ef645b0 --- /dev/null +++ b/apps/website/public/t27/files/specs/demos/hello_world.t27 @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 +; hello_world.t27 -- start here +; The smallest spec that still shows every part of the language: constants, +; a type, functions, a test and an invariant. Read it top to bottom, then +; watch it become tokens, an AST, and five different target languages. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module hello-world; + +// ============================================================================ +// Constants +// +// A spec is a source of truth, so the numbers it fixes are named. `pub` makes +// a name visible to other specs; the type after `:` is not optional. +// ============================================================================ + +pub const GREETING_LEN : u8 = 5; +pub const TRINITY : i8 = 3; + +; A trit is the unit this language is built on: three states, not two. +pub const TRIT_NEG : i8 = -1; +pub const TRIT_ZERO : i8 = 0; +pub const TRIT_POS : i8 = 1; + +// ============================================================================ +// Types +// +// A struct groups named fields. Every field carries its own width, because a +// spec has to say what reaches hardware, not leave it to a compiler default. +// ============================================================================ + +pub const Greeting = packed struct { + length : u8, + trit : i8, +}; + +// ============================================================================ +// Functions +// +// A function declares its parameter types and its return type. There is no +// inference here either -- the signature is part of the specification. +// ============================================================================ + +; Add two trits and clamp the result back into {-1, 0, +1}. +; Saturation, not wrap-around: a trit that overflows stays at the rail. +pub fn trit_add(a: i8, b: i8) i8 { + const sum = a + b; + if (sum > TRIT_POS) { + return TRIT_POS; + } + if (sum < TRIT_NEG) { + return TRIT_NEG; + } + return sum; +} + +; The multiplicative identity of the trit set: multiplying by zero gives zero, +; and the sign rule is the ordinary one. +pub fn trit_mul(a: i8, b: i8) i8 { + return a * b; +} + +; Build the struct above from a length and a trit. +pub fn greeting_new(length: u8, trit: i8) Greeting { + return Greeting{ .length = length, .trit = trit }; +} + +// ============================================================================ +// Tests +// +// Tests live beside the thing they describe and are emitted into every target +// that supports them, so the same claim is checked in each language. +// ============================================================================ + +test "trit_add saturates instead of wrapping" { + try std.testing.expectEqual(@as(i8, TRIT_POS), trit_add(TRIT_POS, TRIT_POS)); + try std.testing.expectEqual(@as(i8, TRIT_NEG), trit_add(TRIT_NEG, TRIT_NEG)); + try std.testing.expectEqual(@as(i8, TRIT_ZERO), trit_add(TRIT_POS, TRIT_NEG)); +} + +test "trit_mul follows the ordinary sign rule" { + try std.testing.expectEqual(@as(i8, TRIT_POS), trit_mul(TRIT_NEG, TRIT_NEG)); + try std.testing.expectEqual(@as(i8, TRIT_NEG), trit_mul(TRIT_NEG, TRIT_POS)); + try std.testing.expectEqual(@as(i8, TRIT_ZERO), trit_mul(TRIT_ZERO, TRIT_POS)); +} + +// ============================================================================ +// Invariants +// +// An invariant is a claim that must hold for the whole module rather than for +// one example. This is the part a test cannot express on its own. +// ============================================================================ + +invariant trit_set_is_three + assert TRIT_POS - TRIT_NEG == TRINITY - 1 + +invariant greeting_length_is_fixed + assert GREETING_LEN == 5 diff --git a/apps/website/public/t27/manifest.json b/apps/website/public/t27/manifest.json index 15804a0ced..234c91729b 100644 --- a/apps/website/public/t27/manifest.json +++ b/apps/website/public/t27/manifest.json @@ -1 +1 @@ -{"generatedFrom":{"repo":"gHashTag/t27","commit":"8b797214d8ef685d1b8d6e61d60325e838196b03","shortCommit":"8b797214d","specsOrCompilerDirty":true},"wasmBytes":488709,"specCount":667,"totalLines":232888,"categories":{"specs/tri":150,"specs/fpga":66,"specs/ml":60,"chips/euler":49,"chips/gamma":49,"chips/phi":49,"specs/isa":16,"specs/numeric":16,"specs/physics":16,"specs/server":11,"specs/compiler":10,"specs/math":10,"specs/sacred":10,"specs/ar":8,"specs/base":8,"specs/vsa":7,"compiler/codegen":6,"specs/brain":6,"specs/github":5,"specs/lsp":5,"specs/sandbox":5,"specs/test_framework":5,"specs/benchmarks":4,"specs/config":4,"specs/git":4,"specs/memory":4,"specs/nn":4,"specs/provider":4,"specs/storage":4,"specs/ternary":4,"compiler/cli":3,"compiler/runtime":3,"specs/account":3,"specs/api":3,"specs/boards":3,"specs/demos":3,"specs/file":3,"specs/pins":3,"specs/pipeline":3,"specs/queen":3,"specs/runtime":3,"specs/shell":3,"specs/tools":3,"compiler/parser":2,"specs/bus":2,"specs/enrichment":2,"specs/sync":2,"tests":2,"bootstrap/specs":1,"compiler":1,"compiler/skill":1,"contrib/backend":1,"examples/fpga":1,"specs/auth":1,"specs/automation":1,"specs/cloud":1,"specs/conformance":1,"specs/depin":1,"specs/graph":1,"specs/hslm":1,"specs/interop":1,"specs/jit":1,"specs/neural":1,"specs/portable":1,"specs/vm":1,"root":1,"vscode-trinity-swe":1},"specs":[{"path":"bootstrap/specs/physics/formula_registry.t27","category":"bootstrap/specs","name":"formula_registry","module":null,"lines":210,"bytes":6193},{"path":"chips/euler/specs/fpga/avs_controller_48.t27","category":"chips/euler","name":"avs_controller_48","module":"avs-controller-48","lines":607,"bytes":20060},{"path":"chips/euler/specs/fpga/avs_controller_96.t27","category":"chips/euler","name":"avs_controller_96","module":"avs-controller-96","lines":735,"bytes":24836},{"path":"chips/euler/specs/fpga/avs_reconf.t27","category":"chips/euler","name":"avs_reconf","module":"avs-reconf","lines":702,"bytes":24801},{"path":"chips/euler/specs/fpga/dfs_gate.t27","category":"chips/euler","name":"dfs_gate","module":"sacred-dfs_gate","lines":600,"bytes":22017},{"path":"chips/euler/specs/fpga/drowsy_ret.t27","category":"chips/euler","name":"drowsy_ret","module":"sacred-drowsy_ret","lines":716,"bytes":26247},{"path":"chips/euler/specs/fpga/fbb_active_path.t27","category":"chips/euler","name":"fbb_active_path","module":"fbb-active-path","lines":756,"bytes":26695},{"path":"chips/euler/specs/fpga/fp8_e4m3.t27","category":"chips/euler","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652},{"path":"chips/euler/specs/fpga/fp8_e5m2.t27","category":"chips/euler","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826},{"path":"chips/euler/specs/fpga/gf128.t27","category":"chips/euler","name":"gf128","module":"GF128","lines":358,"bytes":10740},{"path":"chips/euler/specs/fpga/gf16_to_fp16.t27","category":"chips/euler","name":"gf16_to_fp16","module":"gf16-to-fp16","lines":574,"bytes":18936},{"path":"chips/euler/specs/fpga/gf16_to_posit16.t27","category":"chips/euler","name":"gf16_to_posit16","module":"gf16-to-posit16","lines":664,"bytes":20622},{"path":"chips/euler/specs/fpga/gf256.t27","category":"chips/euler","name":"gf256","module":"GF256","lines":336,"bytes":9935},{"path":"chips/euler/specs/fpga/gf32_to_fp32.t27","category":"chips/euler","name":"gf32_to_fp32","module":"gf32-to-fp32","lines":757,"bytes":25067},{"path":"chips/euler/specs/fpga/gf64.t27","category":"chips/euler","name":"gf64","module":"GF64","lines":320,"bytes":9095},{"path":"chips/euler/specs/fpga/holo_mux_x4.t27","category":"chips/euler","name":"holo_mux_x4","module":"sacred-holo_mux_x4","lines":581,"bytes":21272},{"path":"chips/euler/specs/fpga/int4.t27","category":"chips/euler","name":"int4","module":"Int4","lines":334,"bytes":8538},{"path":"chips/euler/specs/fpga/int8.t27","category":"chips/euler","name":"int8","module":"Int8","lines":493,"bytes":13029},{"path":"chips/euler/specs/fpga/lane_l_precheck.t27","category":"chips/euler","name":"lane_l_precheck","module":"sacred-lane_precheck","lines":493,"bytes":17229},{"path":"chips/euler/specs/fpga/lut_npu_81_entry.t27","category":"chips/euler","name":"lut_npu_81_entry","module":"sacred-lut_npu_81_entry","lines":419,"bytes":13484},{"path":"chips/euler/specs/fpga/nf4.t27","category":"chips/euler","name":"nf4","module":"NF4","lines":313,"bytes":8164},{"path":"chips/euler/specs/fpga/null_pe.t27","category":"chips/euler","name":"null_pe","module":"sacred-null_pe","lines":568,"bytes":20320},{"path":"chips/euler/specs/fpga/posit16.t27","category":"chips/euler","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004},{"path":"chips/euler/specs/fpga/purkinje_thermal_gate.t27","category":"chips/euler","name":"purkinje_thermal_gate","module":"purkinje-thermal-gate","lines":704,"bytes":24460},{"path":"chips/euler/specs/fpga/sparse_mask.t27","category":"chips/euler","name":"sparse_mask","module":"sacred-sparse_mask","lines":556,"bytes":17492},{"path":"chips/euler/specs/fpga/sparse_skip.t27","category":"chips/euler","name":"sparse_skip","module":"sacred-sparse_skip","lines":476,"bytes":15117},{"path":"chips/euler/specs/fpga/spec_exit.t27","category":"chips/euler","name":"spec_exit","module":"sacred-spec_exit","lines":541,"bytes":19745},{"path":"chips/euler/specs/fpga/stoch_round.t27","category":"chips/euler","name":"stoch_round","module":"sacred-stoch_round","lines":575,"bytes":19164},{"path":"chips/euler/specs/fpga/subth_clk.t27","category":"chips/euler","name":"subth_clk","module":"sacred-subth_clk","lines":478,"bytes":17539},{"path":"chips/euler/specs/numeric/binary16.t27","category":"chips/euler","name":"binary16","module":"Binary16","lines":420,"bytes":12171},{"path":"chips/euler/specs/numeric/formats.t27","category":"chips/euler","name":"formats","module":"Formats","lines":538,"bytes":17523},{"path":"chips/euler/specs/numeric/fp8_e4m3.t27","category":"chips/euler","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652},{"path":"chips/euler/specs/numeric/fp8_e5m2.t27","category":"chips/euler","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826},{"path":"chips/euler/specs/numeric/gf12.t27","category":"chips/euler","name":"gf12","module":"GF12","lines":482,"bytes":16369},{"path":"chips/euler/specs/numeric/gf128.t27","category":"chips/euler","name":"gf128","module":"triformat-gf128","lines":506,"bytes":17137},{"path":"chips/euler/specs/numeric/gf16.t27","category":"chips/euler","name":"gf16","module":"triformat-gf16","lines":3437,"bytes":101513},{"path":"chips/euler/specs/numeric/gf20.t27","category":"chips/euler","name":"gf20","module":"GF20","lines":469,"bytes":15992},{"path":"chips/euler/specs/numeric/gf24.t27","category":"chips/euler","name":"gf24","module":"GF24","lines":469,"bytes":16040},{"path":"chips/euler/specs/numeric/gf256.t27","category":"chips/euler","name":"gf256","module":"triformat-gf256","lines":595,"bytes":20164},{"path":"chips/euler/specs/numeric/gf32.t27","category":"chips/euler","name":"gf32","module":"GF32","lines":480,"bytes":16589},{"path":"chips/euler/specs/numeric/gf4.t27","category":"chips/euler","name":"gf4","module":"GF4","lines":306,"bytes":11167},{"path":"chips/euler/specs/numeric/gf64.t27","category":"chips/euler","name":"gf64","module":"triformat-gf64","lines":764,"bytes":24203},{"path":"chips/euler/specs/numeric/gf8.t27","category":"chips/euler","name":"gf8","module":"GF8","lines":522,"bytes":17732},{"path":"chips/euler/specs/numeric/goldenfloat_family.t27","category":"chips/euler","name":"goldenfloat_family","module":"GoldenFloatFamily","lines":450,"bytes":17346},{"path":"chips/euler/specs/numeric/int4.t27","category":"chips/euler","name":"int4","module":"triformat-int4","lines":766,"bytes":20533},{"path":"chips/euler/specs/numeric/int8.t27","category":"chips/euler","name":"int8","module":"triformat-int8","lines":1151,"bytes":30614},{"path":"chips/euler/specs/numeric/nf4.t27","category":"chips/euler","name":"nf4","module":"triformat-nf4","lines":839,"bytes":23771},{"path":"chips/euler/specs/numeric/phi_ratio.t27","category":"chips/euler","name":"phi_ratio","module":"PhiRatio","lines":695,"bytes":25855},{"path":"chips/euler/specs/numeric/posit16.t27","category":"chips/euler","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004},{"path":"chips/euler/specs/numeric/tri_net_formats.t27","category":"chips/euler","name":"tri_net_formats","module":"TriNetFormats","lines":993,"bytes":32170},{"path":"chips/gamma/specs/fpga/avs_controller_48.t27","category":"chips/gamma","name":"avs_controller_48","module":"avs-controller-48","lines":607,"bytes":20060},{"path":"chips/gamma/specs/fpga/avs_controller_96.t27","category":"chips/gamma","name":"avs_controller_96","module":"avs-controller-96","lines":735,"bytes":24836},{"path":"chips/gamma/specs/fpga/avs_reconf.t27","category":"chips/gamma","name":"avs_reconf","module":"avs-reconf","lines":702,"bytes":24801},{"path":"chips/gamma/specs/fpga/dfs_gate.t27","category":"chips/gamma","name":"dfs_gate","module":"sacred-dfs_gate","lines":600,"bytes":22017},{"path":"chips/gamma/specs/fpga/drowsy_ret.t27","category":"chips/gamma","name":"drowsy_ret","module":"sacred-drowsy_ret","lines":716,"bytes":26247},{"path":"chips/gamma/specs/fpga/fbb_active_path.t27","category":"chips/gamma","name":"fbb_active_path","module":"fbb-active-path","lines":756,"bytes":26695},{"path":"chips/gamma/specs/fpga/fp8_e4m3.t27","category":"chips/gamma","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652},{"path":"chips/gamma/specs/fpga/fp8_e5m2.t27","category":"chips/gamma","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826},{"path":"chips/gamma/specs/fpga/gf128.t27","category":"chips/gamma","name":"gf128","module":"GF128","lines":358,"bytes":10740},{"path":"chips/gamma/specs/fpga/gf16_to_fp16.t27","category":"chips/gamma","name":"gf16_to_fp16","module":"gf16-to-fp16","lines":574,"bytes":18936},{"path":"chips/gamma/specs/fpga/gf16_to_posit16.t27","category":"chips/gamma","name":"gf16_to_posit16","module":"gf16-to-posit16","lines":664,"bytes":20622},{"path":"chips/gamma/specs/fpga/gf256.t27","category":"chips/gamma","name":"gf256","module":"GF256","lines":336,"bytes":9935},{"path":"chips/gamma/specs/fpga/gf32_to_fp32.t27","category":"chips/gamma","name":"gf32_to_fp32","module":"gf32-to-fp32","lines":757,"bytes":25067},{"path":"chips/gamma/specs/fpga/gf64.t27","category":"chips/gamma","name":"gf64","module":"GF64","lines":320,"bytes":9095},{"path":"chips/gamma/specs/fpga/holo_mux_x4.t27","category":"chips/gamma","name":"holo_mux_x4","module":"sacred-holo_mux_x4","lines":581,"bytes":21272},{"path":"chips/gamma/specs/fpga/int4.t27","category":"chips/gamma","name":"int4","module":"Int4","lines":334,"bytes":8538},{"path":"chips/gamma/specs/fpga/int8.t27","category":"chips/gamma","name":"int8","module":"Int8","lines":493,"bytes":13029},{"path":"chips/gamma/specs/fpga/lane_l_precheck.t27","category":"chips/gamma","name":"lane_l_precheck","module":"sacred-lane_precheck","lines":493,"bytes":17229},{"path":"chips/gamma/specs/fpga/lut_npu_81_entry.t27","category":"chips/gamma","name":"lut_npu_81_entry","module":"sacred-lut_npu_81_entry","lines":419,"bytes":13484},{"path":"chips/gamma/specs/fpga/nf4.t27","category":"chips/gamma","name":"nf4","module":"NF4","lines":313,"bytes":8164},{"path":"chips/gamma/specs/fpga/null_pe.t27","category":"chips/gamma","name":"null_pe","module":"sacred-null_pe","lines":568,"bytes":20320},{"path":"chips/gamma/specs/fpga/posit16.t27","category":"chips/gamma","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004},{"path":"chips/gamma/specs/fpga/purkinje_thermal_gate.t27","category":"chips/gamma","name":"purkinje_thermal_gate","module":"purkinje-thermal-gate","lines":704,"bytes":24460},{"path":"chips/gamma/specs/fpga/sparse_mask.t27","category":"chips/gamma","name":"sparse_mask","module":"sacred-sparse_mask","lines":556,"bytes":17492},{"path":"chips/gamma/specs/fpga/sparse_skip.t27","category":"chips/gamma","name":"sparse_skip","module":"sacred-sparse_skip","lines":476,"bytes":15117},{"path":"chips/gamma/specs/fpga/spec_exit.t27","category":"chips/gamma","name":"spec_exit","module":"sacred-spec_exit","lines":541,"bytes":19745},{"path":"chips/gamma/specs/fpga/stoch_round.t27","category":"chips/gamma","name":"stoch_round","module":"sacred-stoch_round","lines":575,"bytes":19164},{"path":"chips/gamma/specs/fpga/subth_clk.t27","category":"chips/gamma","name":"subth_clk","module":"sacred-subth_clk","lines":478,"bytes":17539},{"path":"chips/gamma/specs/numeric/binary16.t27","category":"chips/gamma","name":"binary16","module":"Binary16","lines":420,"bytes":12171},{"path":"chips/gamma/specs/numeric/formats.t27","category":"chips/gamma","name":"formats","module":"Formats","lines":538,"bytes":17523},{"path":"chips/gamma/specs/numeric/fp8_e4m3.t27","category":"chips/gamma","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652},{"path":"chips/gamma/specs/numeric/fp8_e5m2.t27","category":"chips/gamma","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826},{"path":"chips/gamma/specs/numeric/gf12.t27","category":"chips/gamma","name":"gf12","module":"GF12","lines":482,"bytes":16369},{"path":"chips/gamma/specs/numeric/gf128.t27","category":"chips/gamma","name":"gf128","module":"triformat-gf128","lines":506,"bytes":17137},{"path":"chips/gamma/specs/numeric/gf16.t27","category":"chips/gamma","name":"gf16","module":"triformat-gf16","lines":3437,"bytes":101513},{"path":"chips/gamma/specs/numeric/gf20.t27","category":"chips/gamma","name":"gf20","module":"GF20","lines":469,"bytes":15992},{"path":"chips/gamma/specs/numeric/gf24.t27","category":"chips/gamma","name":"gf24","module":"GF24","lines":469,"bytes":16040},{"path":"chips/gamma/specs/numeric/gf256.t27","category":"chips/gamma","name":"gf256","module":"triformat-gf256","lines":595,"bytes":20164},{"path":"chips/gamma/specs/numeric/gf32.t27","category":"chips/gamma","name":"gf32","module":"GF32","lines":480,"bytes":16589},{"path":"chips/gamma/specs/numeric/gf4.t27","category":"chips/gamma","name":"gf4","module":"GF4","lines":306,"bytes":11167},{"path":"chips/gamma/specs/numeric/gf64.t27","category":"chips/gamma","name":"gf64","module":"triformat-gf64","lines":764,"bytes":24203},{"path":"chips/gamma/specs/numeric/gf8.t27","category":"chips/gamma","name":"gf8","module":"GF8","lines":522,"bytes":17732},{"path":"chips/gamma/specs/numeric/goldenfloat_family.t27","category":"chips/gamma","name":"goldenfloat_family","module":"GoldenFloatFamily","lines":450,"bytes":17346},{"path":"chips/gamma/specs/numeric/int4.t27","category":"chips/gamma","name":"int4","module":"triformat-int4","lines":766,"bytes":20533},{"path":"chips/gamma/specs/numeric/int8.t27","category":"chips/gamma","name":"int8","module":"triformat-int8","lines":1151,"bytes":30614},{"path":"chips/gamma/specs/numeric/nf4.t27","category":"chips/gamma","name":"nf4","module":"triformat-nf4","lines":839,"bytes":23771},{"path":"chips/gamma/specs/numeric/phi_ratio.t27","category":"chips/gamma","name":"phi_ratio","module":"PhiRatio","lines":695,"bytes":25855},{"path":"chips/gamma/specs/numeric/posit16.t27","category":"chips/gamma","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004},{"path":"chips/gamma/specs/numeric/tri_net_formats.t27","category":"chips/gamma","name":"tri_net_formats","module":"TriNetFormats","lines":993,"bytes":32170},{"path":"chips/phi/specs/fpga/avs_controller_48.t27","category":"chips/phi","name":"avs_controller_48","module":"avs-controller-48","lines":607,"bytes":20060},{"path":"chips/phi/specs/fpga/avs_controller_96.t27","category":"chips/phi","name":"avs_controller_96","module":"avs-controller-96","lines":735,"bytes":24836},{"path":"chips/phi/specs/fpga/avs_reconf.t27","category":"chips/phi","name":"avs_reconf","module":"avs-reconf","lines":702,"bytes":24801},{"path":"chips/phi/specs/fpga/dfs_gate.t27","category":"chips/phi","name":"dfs_gate","module":"sacred-dfs_gate","lines":600,"bytes":22017},{"path":"chips/phi/specs/fpga/drowsy_ret.t27","category":"chips/phi","name":"drowsy_ret","module":"sacred-drowsy_ret","lines":716,"bytes":26247},{"path":"chips/phi/specs/fpga/fbb_active_path.t27","category":"chips/phi","name":"fbb_active_path","module":"fbb-active-path","lines":756,"bytes":26695},{"path":"chips/phi/specs/fpga/fp8_e4m3.t27","category":"chips/phi","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652},{"path":"chips/phi/specs/fpga/fp8_e5m2.t27","category":"chips/phi","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826},{"path":"chips/phi/specs/fpga/gf128.t27","category":"chips/phi","name":"gf128","module":"GF128","lines":358,"bytes":10740},{"path":"chips/phi/specs/fpga/gf16_to_fp16.t27","category":"chips/phi","name":"gf16_to_fp16","module":"gf16-to-fp16","lines":574,"bytes":18936},{"path":"chips/phi/specs/fpga/gf16_to_posit16.t27","category":"chips/phi","name":"gf16_to_posit16","module":"gf16-to-posit16","lines":664,"bytes":20622},{"path":"chips/phi/specs/fpga/gf256.t27","category":"chips/phi","name":"gf256","module":"GF256","lines":336,"bytes":9935},{"path":"chips/phi/specs/fpga/gf32_to_fp32.t27","category":"chips/phi","name":"gf32_to_fp32","module":"gf32-to-fp32","lines":757,"bytes":25067},{"path":"chips/phi/specs/fpga/gf64.t27","category":"chips/phi","name":"gf64","module":"GF64","lines":320,"bytes":9095},{"path":"chips/phi/specs/fpga/holo_mux_x4.t27","category":"chips/phi","name":"holo_mux_x4","module":"sacred-holo_mux_x4","lines":581,"bytes":21272},{"path":"chips/phi/specs/fpga/int4.t27","category":"chips/phi","name":"int4","module":"Int4","lines":334,"bytes":8538},{"path":"chips/phi/specs/fpga/int8.t27","category":"chips/phi","name":"int8","module":"Int8","lines":493,"bytes":13029},{"path":"chips/phi/specs/fpga/lane_l_precheck.t27","category":"chips/phi","name":"lane_l_precheck","module":"sacred-lane_precheck","lines":493,"bytes":17229},{"path":"chips/phi/specs/fpga/lut_npu_81_entry.t27","category":"chips/phi","name":"lut_npu_81_entry","module":"sacred-lut_npu_81_entry","lines":419,"bytes":13484},{"path":"chips/phi/specs/fpga/nf4.t27","category":"chips/phi","name":"nf4","module":"NF4","lines":313,"bytes":8164},{"path":"chips/phi/specs/fpga/null_pe.t27","category":"chips/phi","name":"null_pe","module":"sacred-null_pe","lines":568,"bytes":20320},{"path":"chips/phi/specs/fpga/posit16.t27","category":"chips/phi","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004},{"path":"chips/phi/specs/fpga/purkinje_thermal_gate.t27","category":"chips/phi","name":"purkinje_thermal_gate","module":"purkinje-thermal-gate","lines":704,"bytes":24460},{"path":"chips/phi/specs/fpga/sparse_mask.t27","category":"chips/phi","name":"sparse_mask","module":"sacred-sparse_mask","lines":556,"bytes":17492},{"path":"chips/phi/specs/fpga/sparse_skip.t27","category":"chips/phi","name":"sparse_skip","module":"sacred-sparse_skip","lines":476,"bytes":15117},{"path":"chips/phi/specs/fpga/spec_exit.t27","category":"chips/phi","name":"spec_exit","module":"sacred-spec_exit","lines":541,"bytes":19745},{"path":"chips/phi/specs/fpga/stoch_round.t27","category":"chips/phi","name":"stoch_round","module":"sacred-stoch_round","lines":575,"bytes":19164},{"path":"chips/phi/specs/fpga/subth_clk.t27","category":"chips/phi","name":"subth_clk","module":"sacred-subth_clk","lines":478,"bytes":17539},{"path":"chips/phi/specs/numeric/binary16.t27","category":"chips/phi","name":"binary16","module":"Binary16","lines":420,"bytes":12171},{"path":"chips/phi/specs/numeric/formats.t27","category":"chips/phi","name":"formats","module":"Formats","lines":538,"bytes":17523},{"path":"chips/phi/specs/numeric/fp8_e4m3.t27","category":"chips/phi","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652},{"path":"chips/phi/specs/numeric/fp8_e5m2.t27","category":"chips/phi","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826},{"path":"chips/phi/specs/numeric/gf12.t27","category":"chips/phi","name":"gf12","module":"GF12","lines":482,"bytes":16369},{"path":"chips/phi/specs/numeric/gf128.t27","category":"chips/phi","name":"gf128","module":"triformat-gf128","lines":506,"bytes":17137},{"path":"chips/phi/specs/numeric/gf16.t27","category":"chips/phi","name":"gf16","module":"triformat-gf16","lines":3437,"bytes":101513},{"path":"chips/phi/specs/numeric/gf20.t27","category":"chips/phi","name":"gf20","module":"GF20","lines":469,"bytes":15992},{"path":"chips/phi/specs/numeric/gf24.t27","category":"chips/phi","name":"gf24","module":"GF24","lines":469,"bytes":16040},{"path":"chips/phi/specs/numeric/gf256.t27","category":"chips/phi","name":"gf256","module":"triformat-gf256","lines":595,"bytes":20164},{"path":"chips/phi/specs/numeric/gf32.t27","category":"chips/phi","name":"gf32","module":"GF32","lines":480,"bytes":16589},{"path":"chips/phi/specs/numeric/gf4.t27","category":"chips/phi","name":"gf4","module":"GF4","lines":306,"bytes":11167},{"path":"chips/phi/specs/numeric/gf64.t27","category":"chips/phi","name":"gf64","module":"triformat-gf64","lines":764,"bytes":24203},{"path":"chips/phi/specs/numeric/gf8.t27","category":"chips/phi","name":"gf8","module":"GF8","lines":522,"bytes":17732},{"path":"chips/phi/specs/numeric/goldenfloat_family.t27","category":"chips/phi","name":"goldenfloat_family","module":"GoldenFloatFamily","lines":450,"bytes":17346},{"path":"chips/phi/specs/numeric/int4.t27","category":"chips/phi","name":"int4","module":"triformat-int4","lines":766,"bytes":20533},{"path":"chips/phi/specs/numeric/int8.t27","category":"chips/phi","name":"int8","module":"triformat-int8","lines":1151,"bytes":30614},{"path":"chips/phi/specs/numeric/nf4.t27","category":"chips/phi","name":"nf4","module":"triformat-nf4","lines":839,"bytes":23771},{"path":"chips/phi/specs/numeric/phi_ratio.t27","category":"chips/phi","name":"phi_ratio","module":"PhiRatio","lines":695,"bytes":25855},{"path":"chips/phi/specs/numeric/posit16.t27","category":"chips/phi","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004},{"path":"chips/phi/specs/numeric/tri_net_formats.t27","category":"chips/phi","name":"tri_net_formats","module":"TriNetFormats","lines":993,"bytes":32170},{"path":"compiler/ast.t27","category":"compiler","name":"ast","module":"ast","lines":612,"bytes":18378},{"path":"compiler/cli/gen.t27","category":"compiler/cli","name":"gen","module":"gen_commands","lines":512,"bytes":16753},{"path":"compiler/cli/git.t27","category":"compiler/cli","name":"git","module":"git_commands","lines":617,"bytes":20978},{"path":"compiler/cli/spec.t27","category":"compiler/cli","name":"spec","module":"spec_commands","lines":488,"bytes":16727},{"path":"compiler/codegen/c/codegen.t27","category":"compiler/codegen","name":"codegen","module":"tricgen-c","lines":274,"bytes":7080},{"path":"compiler/codegen/testgen.t27","category":"compiler/codegen","name":"testgen","module":"testgen","lines":910,"bytes":36448},{"path":"compiler/codegen/verilog/codegen.t27","category":"compiler/codegen","name":"codegen","module":"verilog_codegen","lines":1068,"bytes":228219},{"path":"compiler/codegen/verilog/fpga_emission.t27","category":"compiler/codegen","name":"fpga_emission","module":"fpga_emission","lines":2359,"bytes":86803},{"path":"compiler/codegen/zig/codegen.t27","category":"compiler/codegen","name":"codegen","module":"zig_codegen","lines":1517,"bytes":54679},{"path":"compiler/codegen/zig/runtime.t27","category":"compiler/codegen","name":"runtime","module":"zig_runtime","lines":410,"bytes":20013},{"path":"compiler/parser/lexer.t27","category":"compiler/parser","name":"lexer","module":"trilexer","lines":598,"bytes":18435},{"path":"compiler/parser/parser.t27","category":"compiler/parser","name":"parser","module":"parser","lines":1295,"bytes":49454},{"path":"compiler/runtime/commands.t27","category":"compiler/runtime","name":"commands","module":"commands","lines":1040,"bytes":35002},{"path":"compiler/runtime/runtime.t27","category":"compiler/runtime","name":"runtime","module":"triruntime","lines":340,"bytes":11466},{"path":"compiler/runtime/validation.t27","category":"compiler/runtime","name":"validation","module":"validation_rules","lines":515,"bytes":20567},{"path":"compiler/skill/registry.t27","category":"compiler/skill","name":"registry","module":"skill_registry","lines":313,"bytes":11026},{"path":"contrib/backend/zig/legacy/main_zig_handwritten.t27","category":"contrib/backend","name":"main_zig_handwritten","module":null,"lines":1126,"bytes":36956},{"path":"examples/fpga/qmtech_minimal/design.t27","category":"examples/fpga","name":"design","module":null,"lines":150,"bytes":3670},{"path":"specs/account/auth.t27","category":"specs/account","name":"auth","module":"AccountAuth","lines":278,"bytes":9231},{"path":"specs/account/repo.t27","category":"specs/account","name":"repo","module":"AccountRepo","lines":197,"bytes":5474},{"path":"specs/account/schema.t27","category":"specs/account","name":"schema","module":"Account","lines":227,"bytes":6623},{"path":"specs/api/c_api_contract.t27","category":"specs/api","name":"c_api_contract","module":null,"lines":243,"bytes":7087},{"path":"specs/api/sdk_contract.t27","category":"specs/api","name":"sdk_contract","module":null,"lines":407,"bytes":10914},{"path":"specs/api/tri_net_api.t27","category":"specs/api","name":"tri_net_api","module":null,"lines":190,"bytes":5445},{"path":"specs/ar/asp_solver.t27","category":"specs/ar","name":"asp_solver","module":null,"lines":556,"bytes":16355},{"path":"specs/ar/coa_planning.t27","category":"specs/ar","name":"coa_planning","module":null,"lines":638,"bytes":20107},{"path":"specs/ar/composition.t27","category":"specs/ar","name":"composition","module":null,"lines":571,"bytes":19109},{"path":"specs/ar/datalog_engine.t27","category":"specs/ar","name":"datalog_engine","module":null,"lines":350,"bytes":10935},{"path":"specs/ar/explainability.t27","category":"specs/ar","name":"explainability","module":null,"lines":556,"bytes":16253},{"path":"specs/ar/proof_trace.t27","category":"specs/ar","name":"proof_trace","module":null,"lines":313,"bytes":9671},{"path":"specs/ar/restraint.t27","category":"specs/ar","name":"restraint","module":null,"lines":438,"bytes":14096},{"path":"specs/ar/ternary_logic.t27","category":"specs/ar","name":"ternary_logic","module":null,"lines":473,"bytes":13762},{"path":"specs/auth/config.t27","category":"specs/auth","name":"config","module":"AuthConfig","lines":295,"bytes":9593},{"path":"specs/automation/wrapup-auto.t27","category":"specs/automation","name":"wrapup-auto","module":"automation","lines":51,"bytes":1625},{"path":"specs/base/debounce.t27","category":"specs/base","name":"debounce","module":"base-debounce","lines":99,"bytes":2655},{"path":"specs/base/ops.t27","category":"specs/base","name":"ops","module":"tritype-ops","lines":1490,"bytes":47606},{"path":"specs/base/ring_32.t27","category":"specs/base","name":"ring_32","module":"base-ring-32","lines":25,"bytes":596},{"path":"specs/base/seed.t27","category":"specs/base","name":"seed","module":"seed","lines":93,"bytes":2627},{"path":"specs/base/ternary_add.t27","category":"specs/base","name":"ternary_add","module":"ternary_add","lines":413,"bytes":14371},{"path":"specs/base/ternary_encoding.t27","category":"specs/base","name":"ternary_encoding","module":"TernaryEncoding","lines":415,"bytes":13711},{"path":"specs/base/ternary_memory.t27","category":"specs/base","name":"ternary_memory","module":"TernaryMemory","lines":417,"bytes":13794},{"path":"specs/base/types.t27","category":"specs/base","name":"types","module":"tritype-base","lines":1680,"bytes":55612},{"path":"specs/benchmarks/bench_main.t27","category":"specs/benchmarks","name":"bench_main","module":null,"lines":95,"bytes":2523},{"path":"specs/benchmarks/bench_nn.t27","category":"specs/benchmarks","name":"bench_nn","module":null,"lines":166,"bytes":3216},{"path":"specs/benchmarks/gf16_bfloat16_nmse.t27","category":"specs/benchmarks","name":"gf16_bfloat16_nmse","module":null,"lines":153,"bytes":4300},{"path":"specs/benchmarks/ternary_vs_binary.t27","category":"specs/benchmarks","name":"ternary_vs_binary","module":null,"lines":92,"bytes":1965},{"path":"specs/boards/arty_a7.t27","category":"specs/boards","name":"arty_a7","module":"BoardArtyA7","lines":288,"bytes":7435},{"path":"specs/boards/xc7a100t_full.t27","category":"specs/boards","name":"xc7a100t_full","module":"BoardFullXC7A100T","lines":357,"bytes":9813},{"path":"specs/boards/xc7a100t_minimal.t27","category":"specs/boards","name":"xc7a100t_minimal","module":"BoardMinimalXC7A100T","lines":393,"bytes":10805},{"path":"specs/brain/brain.t27","category":"specs/brain","name":"brain","module":null,"lines":58,"bytes":2756},{"path":"specs/brain/bus.t27","category":"specs/brain","name":"bus","module":"brain-bus","lines":17,"bytes":419},{"path":"specs/brain/cognitive_loop.t27","category":"specs/brain","name":"cognitive_loop","module":"brain-cognitive-loop","lines":17,"bytes":515},{"path":"specs/brain/neural_gamma.t27","category":"specs/brain","name":"neural_gamma","module":null,"lines":89,"bytes":2008},{"path":"specs/brain/phi_timing.t27","category":"specs/brain","name":"phi_timing","module":"brain-phi-timing","lines":80,"bytes":3193},{"path":"specs/brain/unified_state.t27","category":"specs/brain","name":"unified_state","module":"brain-unified-state","lines":94,"bytes":2948},{"path":"specs/bus/pubsub.t27","category":"specs/bus","name":"pubsub","module":"bus-pubsub","lines":670,"bytes":17611},{"path":"specs/bus/schema.t27","category":"specs/bus","name":"schema","module":"bus-schema","lines":657,"bytes":17052},{"path":"specs/cloud/railway_deploy.t27","category":"specs/cloud","name":"railway_deploy","module":"cloud-railway-deploy","lines":363,"bytes":9070},{"path":"specs/compiler/diagnostics.t27","category":"specs/compiler","name":"diagnostics","module":"Diagnostics","lines":148,"bytes":4220},{"path":"specs/compiler/lexer.t27","category":"specs/compiler","name":"lexer","module":"Lexing","lines":583,"bytes":22503},{"path":"specs/compiler/linker.t27","category":"specs/compiler","name":"linker","module":"Linking","lines":156,"bytes":4048},{"path":"specs/compiler/meta_compile.t27","category":"specs/compiler","name":"meta_compile","module":"MetaCompilation","lines":70,"bytes":1886},{"path":"specs/compiler/mod_structure.t27","category":"specs/compiler","name":"mod_structure","module":"compiler-mod-structure","lines":124,"bytes":3540},{"path":"specs/compiler/optimizer.t27","category":"specs/compiler","name":"optimizer","module":"Optimization","lines":286,"bytes":8976},{"path":"specs/compiler/parser.t27","category":"specs/compiler","name":"parser","module":"Parsing","lines":1619,"bytes":55123},{"path":"specs/compiler/pipeline.t27","category":"specs/compiler","name":"pipeline","module":"Pipeline","lines":170,"bytes":4503},{"path":"specs/compiler/stdlib.t27","category":"specs/compiler","name":"stdlib","module":"Stdlib","lines":663,"bytes":16035},{"path":"specs/compiler/typechecker.t27","category":"specs/compiler","name":"typechecker","module":"TypeChecking","lines":694,"bytes":24448},{"path":"specs/config/load.t27","category":"specs/config","name":"load","module":"config-load","lines":608,"bytes":16362},{"path":"specs/config/migrate.t27","category":"specs/config","name":"migrate","module":"config-migrate","lines":663,"bytes":18030},{"path":"specs/config/paths.t27","category":"specs/config","name":"paths","module":"config-paths","lines":641,"bytes":15895},{"path":"specs/config/schema.t27","category":"specs/config","name":"schema","module":"config-schema","lines":703,"bytes":19048},{"path":"specs/conformance/e2e_scenarios.t27","category":"specs/conformance","name":"e2e_scenarios","module":null,"lines":265,"bytes":7796},{"path":"specs/demos/jones_topology_decision_gate.t27","category":"specs/demos","name":"jones_topology_decision_gate","module":"JonesTopologyDecisionGate","lines":347,"bytes":29767},{"path":"specs/demos/jones_topology_filter.t27","category":"specs/demos","name":"jones_topology_filter","module":"JonesTopologyFilter","lines":318,"bytes":10711},{"path":"specs/demos/simple_test.t27","category":"specs/demos","name":"simple_test","module":"SimpleTest","lines":15,"bytes":277},{"path":"specs/depin/prove.t27","category":"specs/depin","name":"prove","module":"depin","lines":285,"bytes":8731},{"path":"specs/enrichment/audio_overview.t27","category":"specs/enrichment","name":"audio_overview","module":"enrichment","lines":163,"bytes":5680},{"path":"specs/enrichment/youtube_transcript.t27","category":"specs/enrichment","name":"youtube_transcript","module":"enrichment","lines":606,"bytes":20285},{"path":"specs/file/operations.t27","category":"specs/file","name":"operations","module":"FileOperations","lines":446,"bytes":13273},{"path":"specs/file/schema.t27","category":"specs/file","name":"schema","module":"File","lines":334,"bytes":11786},{"path":"specs/file/watcher.t27","category":"specs/file","name":"watcher","module":"FileWatcher","lines":551,"bytes":16816},{"path":"specs/fpga/apb_bridge.t27","category":"specs/fpga","name":"apb_bridge","module":"ApbBridge","lines":348,"bytes":9210},{"path":"specs/fpga/assembler.t27","category":"specs/fpga","name":"assembler","module":"Assembler","lines":347,"bytes":8722},{"path":"specs/fpga/axi4.t27","category":"specs/fpga","name":"axi4","module":"Axi4","lines":394,"bytes":10640},{"path":"specs/fpga/boards/arty_a7_integration.t27","category":"specs/fpga","name":"arty_a7_integration","module":"ArtyA7_Integration","lines":137,"bytes":4043},{"path":"specs/fpga/boards/qmtech_a100t_integration.t27","category":"specs/fpga","name":"qmtech_a100t_integration","module":"QMTech_A100T_Integration","lines":96,"bytes":2568},{"path":"specs/fpga/bootrom.t27","category":"specs/fpga","name":"bootrom","module":"BootROM","lines":140,"bytes":4020},{"path":"specs/fpga/bridge.t27","category":"specs/fpga","name":"bridge","module":"FPGA_Bridge","lines":501,"bytes":17323},{"path":"specs/fpga/clock_domain.t27","category":"specs/fpga","name":"clock_domain","module":"ClockDomain","lines":231,"bytes":5913},{"path":"specs/fpga/crossopt.t27","category":"specs/fpga","name":"crossopt","module":"CrossOpt","lines":126,"bytes":3615},{"path":"specs/fpga/cts.t27","category":"specs/fpga","name":"cts","module":"CTS","lines":225,"bytes":5735},{"path":"specs/fpga/dft.t27","category":"specs/fpga","name":"dft","module":"DFT","lines":264,"bytes":6604},{"path":"specs/fpga/e2e_demo.t27","category":"specs/fpga","name":"e2e_demo","module":"E2eDemo","lines":253,"bytes":6432},{"path":"specs/fpga/fifo.t27","category":"specs/fpga","name":"fifo","module":"Fifo","lines":365,"bytes":9822},{"path":"specs/fpga/formal.t27","category":"specs/fpga","name":"formal","module":"Formal","lines":353,"bytes":9441},{"path":"specs/fpga/gf16_accel.t27","category":"specs/fpga","name":"gf16_accel","module":"Gf16Accel","lines":423,"bytes":11072},{"path":"specs/fpga/hir.t27","category":"specs/fpga","name":"hir","module":"Hir","lines":692,"bytes":19053},{"path":"specs/fpga/hw_types.t27","category":"specs/fpga","name":"hw_types","module":"HwTypes","lines":335,"bytes":8002},{"path":"specs/fpga/linker.t27","category":"specs/fpga","name":"linker","module":"Linker","lines":327,"bytes":8187},{"path":"specs/fpga/mac.t27","category":"specs/fpga","name":"mac","module":"ZeroDSP_MAC","lines":623,"bytes":22498},{"path":"specs/fpga/memory.t27","category":"specs/fpga","name":"memory","module":"Memory","lines":355,"bytes":9348},{"path":"specs/fpga/partition.t27","category":"specs/fpga","name":"partition","module":"Partition","lines":306,"bytes":9004},{"path":"specs/fpga/placement.t27","category":"specs/fpga","name":"placement","module":"Placement","lines":242,"bytes":6335},{"path":"specs/fpga/power.t27","category":"specs/fpga","name":"power","module":"Power","lines":230,"bytes":6278},{"path":"specs/fpga/power_analysis.t27","category":"specs/fpga","name":"power_analysis","module":"PowerAnalysis","lines":453,"bytes":12807},{"path":"specs/fpga/router.t27","category":"specs/fpga","name":"router","module":"Router","lines":257,"bytes":6620},{"path":"specs/fpga/simulator.t27","category":"specs/fpga","name":"simulator","module":"Simulator","lines":278,"bytes":7050},{"path":"specs/fpga/spi.t27","category":"specs/fpga","name":"spi","module":"SPI_Master","lines":415,"bytes":13093},{"path":"specs/fpga/stdlib.t27","category":"specs/fpga","name":"stdlib","module":"Stdlib","lines":372,"bytes":17315},{"path":"specs/fpga/ternary_isa.t27","category":"specs/fpga","name":"ternary_isa","module":"TernaryIsa","lines":543,"bytes":14462},{"path":"specs/fpga/testbench.t27","category":"specs/fpga","name":"testbench","module":"Testbench","lines":271,"bytes":6930},{"path":"specs/fpga/testbench/apb_bridge_tb.t27","category":"specs/fpga","name":"apb_bridge_tb","module":"APB_Bridge_Testbench","lines":144,"bytes":3247},{"path":"specs/fpga/testbench/assembler_tb.t27","category":"specs/fpga","name":"assembler_tb","module":"Assembler_Testbench","lines":111,"bytes":2952},{"path":"specs/fpga/testbench/axi4_tb.t27","category":"specs/fpga","name":"axi4_tb","module":"AXI4_Testbench","lines":179,"bytes":4359},{"path":"specs/fpga/testbench/bootrom_tb.t27","category":"specs/fpga","name":"bootrom_tb","module":"BootROM_Testbench","lines":87,"bytes":1856},{"path":"specs/fpga/testbench/bridge_tb.t27","category":"specs/fpga","name":"bridge_tb","module":"Bridge_Testbench","lines":124,"bytes":2796},{"path":"specs/fpga/testbench/clock_domain_tb.t27","category":"specs/fpga","name":"clock_domain_tb","module":"ClockDomain_Testbench","lines":109,"bytes":2439},{"path":"specs/fpga/testbench/cts_tb.t27","category":"specs/fpga","name":"cts_tb","module":"CTS_Testbench","lines":119,"bytes":2939},{"path":"specs/fpga/testbench/dft_tb.t27","category":"specs/fpga","name":"dft_tb","module":"DFT_Testbench","lines":130,"bytes":3050},{"path":"specs/fpga/testbench/fifo_tb.t27","category":"specs/fpga","name":"fifo_tb","module":"FIFO_Testbench","lines":163,"bytes":3606},{"path":"specs/fpga/testbench/formal_tb.t27","category":"specs/fpga","name":"formal_tb","module":"Formal_Testbench","lines":132,"bytes":3125},{"path":"specs/fpga/testbench/gf16_accel_tb.t27","category":"specs/fpga","name":"gf16_accel_tb","module":"GF16_Accel_Testbench","lines":136,"bytes":3024},{"path":"specs/fpga/testbench/hir_tb.t27","category":"specs/fpga","name":"hir_tb","module":"HIR_Testbench","lines":105,"bytes":2327},{"path":"specs/fpga/testbench/integration_tb.t27","category":"specs/fpga","name":"integration_tb","module":"Integration_Testbench","lines":126,"bytes":2971},{"path":"specs/fpga/testbench/linker_tb.t27","category":"specs/fpga","name":"linker_tb","module":"Linker_Testbench","lines":89,"bytes":2000},{"path":"specs/fpga/testbench/mac_tb.t27","category":"specs/fpga","name":"mac_tb","module":"MAC_Testbench","lines":554,"bytes":17571},{"path":"specs/fpga/testbench/memory_tb.t27","category":"specs/fpga","name":"memory_tb","module":"Memory_Testbench","lines":136,"bytes":3129},{"path":"specs/fpga/testbench/partition_tb.t27","category":"specs/fpga","name":"partition_tb","module":"Partition_Testbench","lines":94,"bytes":2266},{"path":"specs/fpga/testbench/placement_tb.t27","category":"specs/fpga","name":"placement_tb","module":"Placement_Testbench","lines":115,"bytes":2993},{"path":"specs/fpga/testbench/power_analysis_tb.t27","category":"specs/fpga","name":"power_analysis_tb","module":"PowerAnalysis_Testbench","lines":130,"bytes":3169},{"path":"specs/fpga/testbench/power_tb.t27","category":"specs/fpga","name":"power_tb","module":"Power_Testbench","lines":118,"bytes":2926},{"path":"specs/fpga/testbench/router_tb.t27","category":"specs/fpga","name":"router_tb","module":"Router_Testbench","lines":112,"bytes":2833},{"path":"specs/fpga/testbench/simulator_tb.t27","category":"specs/fpga","name":"simulator_tb","module":"Simulator_Testbench","lines":92,"bytes":2071},{"path":"specs/fpga/testbench/spi_tb.t27","category":"specs/fpga","name":"spi_tb","module":"SPI_Testbench","lines":124,"bytes":2963},{"path":"specs/fpga/testbench/stdlib_tb.t27","category":"specs/fpga","name":"stdlib_tb","module":"Stdlib_Testbench","lines":112,"bytes":2313},{"path":"specs/fpga/testbench/ternary_isa_tb.t27","category":"specs/fpga","name":"ternary_isa_tb","module":"Ternary_ISA_Testbench","lines":148,"bytes":3310},{"path":"specs/fpga/testbench/timing_tb.t27","category":"specs/fpga","name":"timing_tb","module":"Timing_Testbench","lines":98,"bytes":2410},{"path":"specs/fpga/testbench/top_tb.t27","category":"specs/fpga","name":"top_tb","module":"Top_Level_Testbench","lines":218,"bytes":5626},{"path":"specs/fpga/testbench/uart_tb.t27","category":"specs/fpga","name":"uart_tb","module":"UART_Testbench","lines":397,"bytes":11180},{"path":"specs/fpga/testbench/vcd_conformance_compare_tb.t27","category":"specs/fpga","name":"vcd_conformance_compare_tb","module":"VcdConformanceCompare_Testbench","lines":107,"bytes":2611},{"path":"specs/fpga/testbench/vcd_trace_tb.t27","category":"specs/fpga","name":"vcd_trace_tb","module":"VCD_Trace_Testbench","lines":86,"bytes":1929},{"path":"specs/fpga/timing.t27","category":"specs/fpga","name":"timing","module":"Timing","lines":376,"bytes":9419},{"path":"specs/fpga/top_level.t27","category":"specs/fpga","name":"top_level","module":"ZeroDSP_TopLevel","lines":220,"bytes":5551},{"path":"specs/fpga/uart.t27","category":"specs/fpga","name":"uart","module":"ZeroDSP_UART","lines":211,"bytes":5733},{"path":"specs/fpga/vcd_conformance_compare.t27","category":"specs/fpga","name":"vcd_conformance_compare","module":"VcdConformanceCompare","lines":436,"bytes":11816},{"path":"specs/fpga/vcd_trace.t27","category":"specs/fpga","name":"vcd_trace","module":"VcdTrace","lines":292,"bytes":7555},{"path":"specs/fpga/verification/build_verify.t27","category":"specs/fpga","name":"build_verify","module":"BuildVerify","lines":87,"bytes":2190},{"path":"specs/git/diff.t27","category":"specs/git","name":"diff","module":"GitDiff","lines":230,"bytes":7832},{"path":"specs/git/operations.t27","category":"specs/git","name":"operations","module":"GitOperations","lines":176,"bytes":5799},{"path":"specs/git/schema.t27","category":"specs/git","name":"schema","module":"Git","lines":229,"bytes":6205},{"path":"specs/git/status.t27","category":"specs/git","name":"status","module":"GitStatus","lines":189,"bytes":6535},{"path":"specs/github/auth.t27","category":"specs/github","name":"auth","module":"github","lines":27,"bytes":708},{"path":"specs/github/comments.t27","category":"specs/github","name":"comments","module":"github","lines":18,"bytes":398},{"path":"specs/github/issues.t27","category":"specs/github","name":"issues","module":"github","lines":22,"bytes":484},{"path":"specs/github/prs.t27","category":"specs/github","name":"prs","module":"github","lines":22,"bytes":472},{"path":"specs/github/tests/e2e_full_flow.t27","category":"specs/github","name":"e2e_full_flow","module":"github","lines":122,"bytes":3778},{"path":"specs/graph/knowledge_graph.t27","category":"specs/graph","name":"knowledge_graph","module":"KnowledgeGraph","lines":406,"bytes":17952},{"path":"specs/hslm/forward_pass.t27","category":"specs/hslm","name":"forward_pass","module":"ForwardPass","lines":306,"bytes":14239},{"path":"specs/interop/gf_cross_language.t27","category":"specs/interop","name":"gf_cross_language","module":"GFCrossLanguageConformance","lines":99,"bytes":3587},{"path":"specs/isa/registers.t27","category":"specs/isa","name":"registers","module":"ISARegisters","lines":600,"bytes":20530},{"path":"specs/isa/ternary_arithmetic.t27","category":"specs/isa","name":"ternary_arithmetic","module":"TernaryArithmetic","lines":501,"bytes":16736},{"path":"specs/isa/ternary_bitwise.t27","category":"specs/isa","name":"ternary_bitwise","module":"TernaryBitwise","lines":404,"bytes":14046},{"path":"specs/isa/ternary_control_flow.t27","category":"specs/isa","name":"ternary_control_flow","module":"TernaryControlFlow","lines":392,"bytes":12518},{"path":"specs/isa/ternary_deque.t27","category":"specs/isa","name":"ternary_deque","module":"TernaryDeque","lines":498,"bytes":16013},{"path":"specs/isa/ternary_encoding.t27","category":"specs/isa","name":"ternary_encoding","module":null,"lines":42,"bytes":944},{"path":"specs/isa/ternary_gates.t27","category":"specs/isa","name":"ternary_gates","module":"TernaryGates","lines":410,"bytes":14744},{"path":"specs/isa/ternary_graph.t27","category":"specs/isa","name":"ternary_graph","module":"TernaryGraph","lines":131,"bytes":3644},{"path":"specs/isa/ternary_hash.t27","category":"specs/isa","name":"ternary_hash","module":"TernaryHashTable","lines":167,"bytes":5024},{"path":"specs/isa/ternary_memory.t27","category":"specs/isa","name":"ternary_memory","module":"ISAMemoryOps","lines":522,"bytes":18705},{"path":"specs/isa/ternary_pattern_matching.t27","category":"specs/isa","name":"ternary_pattern_matching","module":"TernaryPatternMatching","lines":173,"bytes":5331},{"path":"specs/isa/ternary_search.t27","category":"specs/isa","name":"ternary_search","module":"TernarySearch","lines":165,"bytes":4613},{"path":"specs/isa/ternary_set.t27","category":"specs/isa","name":"ternary_set","module":"TernarySet","lines":182,"bytes":5215},{"path":"specs/isa/ternary_shift.t27","category":"specs/isa","name":"ternary_shift","module":"TernaryShift","lines":420,"bytes":14016},{"path":"specs/isa/ternary_sorting.t27","category":"specs/isa","name":"ternary_sorting","module":"TernarySorting","lines":173,"bytes":4909},{"path":"specs/isa/ternary_tree.t27","category":"specs/isa","name":"ternary_tree","module":"TernaryTree","lines":141,"bytes":4645},{"path":"specs/jit/jit.t27","category":"specs/jit","name":"jit","module":"jit","lines":875,"bytes":29745},{"path":"specs/lsp/client.t27","category":"specs/lsp","name":"client","module":"lsp-client","lines":505,"bytes":14486},{"path":"specs/lsp/language.t27","category":"specs/lsp","name":"language","module":"lsp-language","lines":527,"bytes":14976},{"path":"specs/lsp/protocol.t27","category":"specs/lsp","name":"protocol","module":"lsp-protocol","lines":681,"bytes":20652},{"path":"specs/lsp/schema.t27","category":"specs/lsp","name":"schema","module":"lsp-schema","lines":589,"bytes":15702},{"path":"specs/lsp/server.t27","category":"specs/lsp","name":"server","module":"lsp-server","lines":555,"bytes":15397},{"path":"specs/math/constants.t27","category":"specs/math","name":"constants","module":"Constants","lines":459,"bytes":16818},{"path":"specs/math/e8_lie_algebra.t27","category":"specs/math","name":"e8_lie_algebra","module":"E8LieAlgebra","lines":390,"bytes":14031},{"path":"specs/math/gf_competitive.t27","category":"specs/math","name":"gf_competitive","module":"GFCompetitive","lines":122,"bytes":4213},{"path":"specs/math/pellis_precision_verify.t27","category":"specs/math","name":"pellis_precision_verify","module":"PellisPrecision","lines":204,"bytes":8117},{"path":"specs/math/phi_split_optimality.t27","category":"specs/math","name":"phi_split_optimality","module":"PhiSplitOptimality","lines":336,"bytes":12692},{"path":"specs/math/phi_universal_attractor.t27","category":"specs/math","name":"phi_universal_attractor","module":"PhiUniversalAttractor","lines":333,"bytes":12924},{"path":"specs/math/property_test_template.t27","category":"specs/math","name":"property_test_template","module":"PropertyTestTemplate","lines":513,"bytes":16168},{"path":"specs/math/radix_economy.t27","category":"specs/math","name":"radix_economy","module":"RadixEconomy","lines":331,"bytes":10783},{"path":"specs/math/sacred_physics.t27","category":"specs/math","name":"sacred_physics","module":"SacredPhysics","lines":461,"bytes":18991},{"path":"specs/math/zamolodchikov_e8.t27","category":"specs/math","name":"zamolodchikov_e8","module":"ZamolodchikovE8","lines":324,"bytes":11409},{"path":"specs/memory/formula_embed.t27","category":"specs/memory","name":"formula_embed","module":"FormulaEmbed","lines":202,"bytes":5767},{"path":"specs/memory/memory_primitives.t27","category":"specs/memory","name":"memory_primitives","module":"MemoryPrimitives","lines":180,"bytes":5305},{"path":"specs/memory/notebooklm.t27","category":"specs/memory","name":"notebooklm","module":"NotebookLM","lines":486,"bytes":18329},{"path":"specs/memory/semantic_search.t27","category":"specs/memory","name":"semantic_search","module":"SemanticSearch","lines":141,"bytes":4471},{"path":"specs/ml/activation/elu_activation.t27","category":"specs/ml","name":"elu_activation","module":"Elu","lines":117,"bytes":5464},{"path":"specs/ml/activation/gelu_activation.t27","category":"specs/ml","name":"gelu_activation","module":"Gelu","lines":68,"bytes":3490},{"path":"specs/ml/activation/gelu_approx_activation.t27","category":"specs/ml","name":"gelu_approx_activation","module":"GeluApprox","lines":82,"bytes":4233},{"path":"specs/ml/activation/leaky_relu_activation.t27","category":"specs/ml","name":"leaky_relu_activation","module":"LeakyRelu","lines":95,"bytes":4569},{"path":"specs/ml/activation/relu_activation.t27","category":"specs/ml","name":"relu_activation","module":"Relu","lines":62,"bytes":3341},{"path":"specs/ml/activation/sigmoid_activation.t27","category":"specs/ml","name":"sigmoid_activation","module":"Sigmoid","lines":63,"bytes":3005},{"path":"specs/ml/activation/silu_swish_activation.t27","category":"specs/ml","name":"silu_swish_activation","module":"SiluSwish","lines":88,"bytes":4161},{"path":"specs/ml/activation/silu_swish_vbt_activation.t27","category":"specs/ml","name":"silu_swish_vbt_activation","module":"SiluSwishVbt","lines":16,"bytes":674},{"path":"specs/ml/activation/softmax.t27","category":"specs/ml","name":"softmax","module":"Softmax","lines":83,"bytes":4349},{"path":"specs/ml/activation/tanh_activation.t27","category":"specs/ml","name":"tanh_activation","module":"Tanh","lines":54,"bytes":2372},{"path":"specs/ml/layers/avgpool2d_layer.t27","category":"specs/ml","name":"avgpool2d_layer","module":"Avgpool2d","lines":123,"bytes":6532},{"path":"specs/ml/layers/batchnorm_layer.t27","category":"specs/ml","name":"batchnorm_layer","module":"Batchnorm","lines":111,"bytes":5804},{"path":"specs/ml/layers/conv2d_layer.t27","category":"specs/ml","name":"conv2d_layer","module":"Conv2d","lines":92,"bytes":4236},{"path":"specs/ml/layers/dense_layer.t27","category":"specs/ml","name":"dense_layer","module":"Dense","lines":90,"bytes":4267},{"path":"specs/ml/layers/dropout_layer.t27","category":"specs/ml","name":"dropout_layer","module":"Dropout","lines":70,"bytes":3702},{"path":"specs/ml/layers/embedding_layer.t27","category":"specs/ml","name":"embedding_layer","module":"Embedding","lines":121,"bytes":5785},{"path":"specs/ml/layers/flatten_layer.t27","category":"specs/ml","name":"flatten_layer","module":"Flatten","lines":114,"bytes":5624},{"path":"specs/ml/layers/layernorm_layer.t27","category":"specs/ml","name":"layernorm_layer","module":"Layernorm","lines":36,"bytes":1927},{"path":"specs/ml/layers/maxpool2d_layer.t27","category":"specs/ml","name":"maxpool2d_layer","module":"Maxpool2d","lines":74,"bytes":4252},{"path":"specs/ml/layers/residual_connection.t27","category":"specs/ml","name":"residual_connection","module":"Residual","lines":27,"bytes":1408},{"path":"specs/ml/loss/binary_crossentropy_loss.t27","category":"specs/ml","name":"binary_crossentropy_loss","module":"BinaryCe","lines":120,"bytes":5850},{"path":"specs/ml/loss/contrastive_loss.t27","category":"specs/ml","name":"contrastive_loss","module":"ContrastiveLoss","lines":35,"bytes":1858},{"path":"specs/ml/loss/cross_entropy_loss.t27","category":"specs/ml","name":"cross_entropy_loss","module":"CrossEntropy","lines":82,"bytes":4052},{"path":"specs/ml/loss/huber_loss.t27","category":"specs/ml","name":"huber_loss","module":"HuberLoss","lines":35,"bytes":1823},{"path":"specs/ml/loss/kl_divergence.t27","category":"specs/ml","name":"kl_divergence","module":"KlDivergence","lines":27,"bytes":1319},{"path":"specs/ml/loss/mse_loss.t27","category":"specs/ml","name":"mse_loss","module":"MseLoss","lines":35,"bytes":1788},{"path":"specs/ml/optimizer/adagrad.t27","category":"specs/ml","name":"adagrad","module":"Adagrad","lines":113,"bytes":5650},{"path":"specs/ml/optimizer/adam.t27","category":"specs/ml","name":"adam","module":"Adam","lines":47,"bytes":2422},{"path":"specs/ml/optimizer/adamw.t27","category":"specs/ml","name":"adamw","module":"Adamw","lines":306,"bytes":12635},{"path":"specs/ml/optimizer/lamb.t27","category":"specs/ml","name":"lamb","module":"Lamb","lines":134,"bytes":6076},{"path":"specs/ml/optimizer/lr_scheduler.t27","category":"specs/ml","name":"lr_scheduler","module":"Scheduler","lines":150,"bytes":7274},{"path":"specs/ml/optimizer/rmsprop.t27","category":"specs/ml","name":"rmsprop","module":"Rmsprop","lines":43,"bytes":2269},{"path":"specs/ml/optimizer/sgd.t27","category":"specs/ml","name":"sgd","module":"Sgd","lines":104,"bytes":4735},{"path":"specs/ml/optimizer/sgd_momentum.t27","category":"specs/ml","name":"sgd_momentum","module":"SgdMomentum","lines":280,"bytes":13039},{"path":"specs/ml/pathway/mlp.t27","category":"specs/ml","name":"mlp","module":"Mlp","lines":112,"bytes":5025},{"path":"specs/ml/recurrent/attention_mechanism.t27","category":"specs/ml","name":"attention_mechanism","module":"Attention","lines":95,"bytes":5259},{"path":"specs/ml/recurrent/bilstm.t27","category":"specs/ml","name":"bilstm","module":"Bilstm","lines":134,"bytes":6356},{"path":"specs/ml/recurrent/gru_cell.t27","category":"specs/ml","name":"gru_cell","module":"Gru","lines":138,"bytes":6267},{"path":"specs/ml/recurrent/lstm_cell.t27","category":"specs/ml","name":"lstm_cell","module":"Lstm","lines":142,"bytes":6603},{"path":"specs/ml/recurrent/lstm_single.t27","category":"specs/ml","name":"lstm_single","module":"LstmCell","lines":127,"bytes":6220},{"path":"specs/ml/recurrent/rnn_cell.t27","category":"specs/ml","name":"rnn_cell","module":"RnnCell","lines":120,"bytes":5605},{"path":"specs/ml/recurrent/self_attention.t27","category":"specs/ml","name":"self_attention","module":"SelfAttention","lines":57,"bytes":2662},{"path":"specs/ml/recurrent/seq2seq.t27","category":"specs/ml","name":"seq2seq","module":"Seq2seq","lines":121,"bytes":6457},{"path":"specs/ml/rl/advantage_estimator.t27","category":"specs/ml","name":"advantage_estimator","module":"Advantage","lines":128,"bytes":6229},{"path":"specs/ml/rl/dqn.t27","category":"specs/ml","name":"dqn","module":"Dqn","lines":119,"bytes":5990},{"path":"specs/ml/rl/dqn_target_network.t27","category":"specs/ml","name":"dqn_target_network","module":"DqnTarget","lines":112,"bytes":5334},{"path":"specs/ml/rl/ppo_actor.t27","category":"specs/ml","name":"ppo_actor","module":"PpoActor","lines":155,"bytes":7732},{"path":"specs/ml/rl/ppo_clip_loss.t27","category":"specs/ml","name":"ppo_clip_loss","module":"PpoClipLoss","lines":141,"bytes":7501},{"path":"specs/ml/rl/ppo_critic.t27","category":"specs/ml","name":"ppo_critic","module":"PpoCritic","lines":106,"bytes":5376},{"path":"specs/ml/rl/sac_actor.t27","category":"specs/ml","name":"sac_actor","module":"SacActor","lines":128,"bytes":6327},{"path":"specs/ml/rl/sac_critic.t27","category":"specs/ml","name":"sac_critic","module":"SacCritic","lines":164,"bytes":7664},{"path":"specs/ml/transformer/encoder_block.t27","category":"specs/ml","name":"encoder_block","module":"EncoderBlock","lines":117,"bytes":5609},{"path":"specs/ml/transformer/feed_forward.t27","category":"specs/ml","name":"feed_forward","module":"FeedForward","lines":437,"bytes":20203},{"path":"specs/ml/transformer/feed_forward_network.t27","category":"specs/ml","name":"feed_forward_network","module":"FeedForward","lines":42,"bytes":2390},{"path":"specs/ml/transformer/mha_block.t27","category":"specs/ml","name":"mha_block","module":"MHABlock","lines":383,"bytes":17868},{"path":"specs/ml/transformer/multi_head_attention.t27","category":"specs/ml","name":"multi_head_attention","module":"MultiHeadAttn","lines":38,"bytes":1951},{"path":"specs/ml/transformer/multi_head_attn.t27","category":"specs/ml","name":"multi_head_attn","module":"MultiHeadAttention","lines":464,"bytes":21855},{"path":"specs/ml/transformer/norm.t27","category":"specs/ml","name":"norm","module":"LayerNorm","lines":478,"bytes":23093},{"path":"specs/ml/transformer/positional_enc.t27","category":"specs/ml","name":"positional_enc","module":"PositionalEncoding","lines":379,"bytes":17042},{"path":"specs/ml/transformer/positional_encoding.t27","category":"specs/ml","name":"positional_encoding","module":"PositionalEnc","lines":42,"bytes":2296},{"path":"specs/neural/forward_pass.t27","category":"specs/neural","name":"forward_pass","module":"forward_pass","lines":1095,"bytes":32248},{"path":"specs/nn/attention.t27","category":"specs/nn","name":"attention","module":"SacredAttention","lines":634,"bytes":22599},{"path":"specs/nn/hslm.t27","category":"specs/nn","name":"hslm","module":"HSLM","lines":632,"bytes":21721},{"path":"specs/nn/phi_rope.t27","category":"specs/nn","name":"phi_rope","module":null,"lines":47,"bytes":1243},{"path":"specs/nn/sacred_attention.t27","category":"specs/nn","name":"sacred_attention","module":null,"lines":50,"bytes":1242},{"path":"specs/numeric/bigint.t27","category":"specs/numeric","name":"bigint","module":"BigInt","lines":391,"bytes":14358},{"path":"specs/numeric/formats.t27","category":"specs/numeric","name":"formats","module":"Formats","lines":400,"bytes":13628},{"path":"specs/numeric/gf12.t27","category":"specs/numeric","name":"gf12","module":"GF12","lines":482,"bytes":13953},{"path":"specs/numeric/gf16.t27","category":"specs/numeric","name":"gf16","module":"triformat-gf16","lines":3392,"bytes":100829},{"path":"specs/numeric/gf20.t27","category":"specs/numeric","name":"gf20","module":"GF20","lines":469,"bytes":13578},{"path":"specs/numeric/gf24.t27","category":"specs/numeric","name":"gf24","module":"GF24","lines":469,"bytes":13626},{"path":"specs/numeric/gf32.t27","category":"specs/numeric","name":"gf32","module":"GF32","lines":480,"bytes":14168},{"path":"specs/numeric/gf4.t27","category":"specs/numeric","name":"gf4","module":"GF4","lines":306,"bytes":9145},{"path":"specs/numeric/gf8.t27","category":"specs/numeric","name":"gf8","module":"GF8","lines":522,"bytes":15315},{"path":"specs/numeric/gf_competitive.t27","category":"specs/numeric","name":"gf_competitive","module":"GFCompetitive","lines":108,"bytes":3355},{"path":"specs/numeric/goldenfloat_family.t27","category":"specs/numeric","name":"goldenfloat_family","module":"GoldenFloatFamily","lines":411,"bytes":14364},{"path":"specs/numeric/pellis_verify.t27","category":"specs/numeric","name":"pellis_verify","module":"PellisVerify","lines":65,"bytes":2169},{"path":"specs/numeric/phi_ratio.t27","category":"specs/numeric","name":"phi_ratio","module":"PhiRatio","lines":653,"bytes":24250},{"path":"specs/numeric/requant_boundary.t27","category":"specs/numeric","name":"requant_boundary","module":"RequantBoundary","lines":217,"bytes":9731},{"path":"specs/numeric/tf3.t27","category":"specs/numeric","name":"tf3","module":"triformat-tf3","lines":1662,"bytes":47300},{"path":"specs/numeric/trinity_numeric_surface.t27","category":"specs/numeric","name":"trinity_numeric_surface","module":"trinity-numeric-surface","lines":38,"bytes":1703},{"path":"specs/physics/chimera_best_gamma.t27","category":"specs/physics","name":"chimera_best_gamma","module":"chimera","lines":20,"bytes":468},{"path":"specs/physics/e8_lqg_bridge.t27","category":"specs/physics","name":"e8_lqg_bridge","module":null,"lines":113,"bytes":2853},{"path":"specs/physics/formula_discovery.t27","category":"specs/physics","name":"formula_discovery","module":"FormulaDiscovery","lines":158,"bytes":4536},{"path":"specs/physics/formula_registry.t27","category":"specs/physics","name":"formula_registry","module":null,"lines":213,"bytes":6261},{"path":"specs/physics/gamma-conflict.t27","category":"specs/physics","name":"gamma-conflict","module":null,"lines":256,"bytes":6580},{"path":"specs/physics/gamma_conjecture.t27","category":"specs/physics","name":"gamma_conjecture","module":"GammaConjecture","lines":277,"bytes":11590},{"path":"specs/physics/gi1_analysis.t27","category":"specs/physics","name":"gi1_analysis","module":"GI1Analysis","lines":238,"bytes":9763},{"path":"specs/physics/hslm_benchmark.t27","category":"specs/physics","name":"hslm_benchmark","module":null,"lines":90,"bytes":2287},{"path":"specs/physics/lqg_cs_bridge.t27","category":"specs/physics","name":"lqg_cs_bridge","module":null,"lines":141,"bytes":4725},{"path":"specs/physics/lqg_entropy.t27","category":"specs/physics","name":"lqg_entropy","module":null,"lines":67,"bytes":2841},{"path":"specs/physics/p2_brain_physics.t27","category":"specs/physics","name":"p2_brain_physics","module":"P2Brain","lines":165,"bytes":6932},{"path":"specs/physics/pellis-formulas.t27","category":"specs/physics","name":"pellis-formulas","module":"PellisFormulas","lines":67,"bytes":2116},{"path":"specs/physics/quantum.t27","category":"specs/physics","name":"quantum","module":null,"lines":72,"bytes":1715},{"path":"specs/physics/sacred_verification.t27","category":"specs/physics","name":"sacred_verification","module":"SacredVerification","lines":605,"bytes":24309},{"path":"specs/physics/su2_chern_simons.t27","category":"specs/physics","name":"su2_chern_simons","module":"SU2ChernSimons","lines":349,"bytes":12227},{"path":"specs/physics/zamolodchikov_4d_conjecture.t27","category":"specs/physics","name":"zamolodchikov_4d_conjecture","module":"Zamolodchikov4DConjecture","lines":254,"bytes":10731},{"path":"specs/pins/emitter_xdc.t27","category":"specs/pins","name":"emitter_xdc","module":"EmitterXDC","lines":295,"bytes":9345},{"path":"specs/pins/ir.t27","category":"specs/pins","name":"ir","module":"PinsIR","lines":391,"bytes":11981},{"path":"specs/pins/parser.t27","category":"specs/pins","name":"parser","module":"PinsParser","lines":583,"bytes":17861},{"path":"specs/pipeline/benchmarks.t27","category":"specs/pipeline","name":"benchmarks","module":"PipelineBenchmarks","lines":86,"bytes":2598},{"path":"specs/pipeline/e2e_test.t27","category":"specs/pipeline","name":"e2e_test","module":"PipelineE2E","lines":151,"bytes":5240},{"path":"specs/pipeline/experience_save.t27","category":"specs/pipeline","name":"experience_save","module":"ExperienceSave","lines":127,"bytes":3752},{"path":"specs/portable/relay_observer.t27","category":"specs/portable","name":"relay_observer","module":"portable","lines":651,"bytes":21457},{"path":"specs/provider/adapters.t27","category":"specs/provider","name":"adapters","module":"provider-adapters","lines":619,"bytes":16564},{"path":"specs/provider/schema.t27","category":"specs/provider","name":"schema","module":"provider-schema","lines":605,"bytes":15237},{"path":"specs/provider/stream.t27","category":"specs/provider","name":"stream","module":"provider-stream","lines":612,"bytes":16334},{"path":"specs/provider/transform.t27","category":"specs/provider","name":"transform","module":"provider-transform","lines":522,"bytes":15346},{"path":"specs/queen/brain_summaries.t27","category":"specs/queen","name":"brain_summaries","module":"BrainSummaries","lines":395,"bytes":14418},{"path":"specs/queen/lotus.t27","category":"specs/queen","name":"lotus","module":"QueenLotus","lines":804,"bytes":26358},{"path":"specs/queen/task_analysis.t27","category":"specs/queen","name":"task_analysis","module":"queen-task-analysis","lines":178,"bytes":5115},{"path":"specs/runtime/execute.t27","category":"specs/runtime","name":"execute","module":"runtime-execute","lines":735,"bytes":19042},{"path":"specs/runtime/instance.t27","category":"specs/runtime","name":"instance","module":"runtime-instance","lines":780,"bytes":20135},{"path":"specs/runtime/process.t27","category":"specs/runtime","name":"process","module":"runtime-process","lines":762,"bytes":19101},{"path":"specs/sacred/cosmology.t27","category":"specs/sacred","name":"cosmology","module":"TriCosmology","lines":16,"bytes":629},{"path":"specs/sacred/dark_matter.t27","category":"specs/sacred","name":"dark_matter","module":"dark_matter","lines":16,"bytes":629},{"path":"specs/sacred/gravity.t27","category":"specs/sacred","name":"gravity","module":"TriGravity","lines":16,"bytes":625},{"path":"specs/sacred/monopoles.t27","category":"specs/sacred","name":"monopoles","module":"TriMonopoles","lines":16,"bytes":629},{"path":"specs/sacred/quantum.t27","category":"specs/sacred","name":"quantum","module":"TriQuantum","lines":16,"bytes":625},{"path":"specs/sacred/quantum_gravity.t27","category":"specs/sacred","name":"quantum_gravity","module":"quantum_gravity","lines":16,"bytes":637},{"path":"specs/sacred/sacred_constants.t27","category":"specs/sacred","name":"sacred_constants","module":"SacredConstants","lines":24,"bytes":1160},{"path":"specs/sacred/sacred_governance.t27","category":"specs/sacred","name":"sacred_governance","module":"SacredGovernance","lines":85,"bytes":3036},{"path":"specs/sacred/sacred_identity.t27","category":"specs/sacred","name":"sacred_identity","module":"SacredIdentity","lines":77,"bytes":2757},{"path":"specs/sacred/superconductivity.t27","category":"specs/sacred","name":"superconductivity","module":"TriSuperconductivity","lines":16,"bytes":645},{"path":"specs/sandbox/health.t27","category":"specs/sandbox","name":"health","module":"sandbox","lines":43,"bytes":1234},{"path":"specs/sandbox/https_enforce.t27","category":"specs/sandbox","name":"https_enforce","module":"sandbox","lines":224,"bytes":7449},{"path":"specs/sandbox/modules.t27","category":"specs/sandbox","name":"modules","module":"sandbox","lines":22,"bytes":536},{"path":"specs/sandbox/orphan_detection.t27","category":"specs/sandbox","name":"orphan_detection","module":"sandbox","lines":257,"bytes":9041},{"path":"specs/sandbox/session_timeout.t27","category":"specs/sandbox","name":"session_timeout","module":"sandbox","lines":195,"bytes":6252},{"path":"specs/server/agent-runner.t27","category":"specs/server","name":"agent-runner","module":"AgentRunner","lines":249,"bytes":8126},{"path":"specs/server/api.t27","category":"specs/server","name":"api","module":"Api","lines":148,"bytes":4198},{"path":"specs/server/http.t27","category":"specs/server","name":"http","module":"server-http","lines":453,"bytes":12359},{"path":"specs/server/mdns.t27","category":"specs/server","name":"mdns","module":"server-mdns","lines":567,"bytes":14817},{"path":"specs/server/project.t27","category":"specs/server","name":"project","module":"Project","lines":206,"bytes":6094},{"path":"specs/server/provider.t27","category":"specs/server","name":"provider","module":"Provider","lines":257,"bytes":8105},{"path":"specs/server/router.t27","category":"specs/server","name":"router","module":"server-router","lines":575,"bytes":15018},{"path":"specs/server/routes.t27","category":"specs/server","name":"routes","module":"Routes","lines":36,"bytes":690},{"path":"specs/server/session.t27","category":"specs/server","name":"session","module":"Session","lines":273,"bytes":8497},{"path":"specs/server/sse.t27","category":"specs/server","name":"sse","module":"server-sse","lines":563,"bytes":15052},{"path":"specs/server/vm.t27","category":"specs/server","name":"vm","module":"VM","lines":593,"bytes":22613},{"path":"specs/shell/environment.t27","category":"specs/shell","name":"environment","module":"ShellEnvironment","lines":304,"bytes":9509},{"path":"specs/shell/process.t27","category":"specs/shell","name":"process","module":"ShellProcess","lines":305,"bytes":8856},{"path":"specs/shell/schema.t27","category":"specs/shell","name":"schema","module":"Shell","lines":406,"bytes":12544},{"path":"specs/storage/kv.t27","category":"specs/storage","name":"kv","module":"StorageKv","lines":159,"bytes":5529},{"path":"specs/storage/lock.t27","category":"specs/storage","name":"lock","module":"StorageLock","lines":179,"bytes":6388},{"path":"specs/storage/migrate.t27","category":"specs/storage","name":"migrate","module":"StorageMigrate","lines":193,"bytes":6892},{"path":"specs/storage/schema.t27","category":"specs/storage","name":"schema","module":"Storage","lines":146,"bytes":4070},{"path":"specs/sync/index.t27","category":"specs/sync","name":"index","module":"sync-index","lines":779,"bytes":20867},{"path":"specs/sync/schema.t27","category":"specs/sync","name":"schema","module":"sync-schema","lines":722,"bytes":19125},{"path":"specs/ternary/bigint.t27","category":"specs/ternary","name":"bigint","module":"TernaryBigInt","lines":1441,"bytes":43916},{"path":"specs/ternary/hybrid_arithmetic.t27","category":"specs/ternary","name":"hybrid_arithmetic","module":"HybridArithmetic","lines":492,"bytes":18595},{"path":"specs/ternary/hybrid_bigint.t27","category":"specs/ternary","name":"hybrid_bigint","module":"hybrid_bigint","lines":1065,"bytes":30092},{"path":"specs/ternary/packed_trit.t27","category":"specs/ternary","name":"packed_trit","module":"PackedTrit","lines":430,"bytes":16022},{"path":"specs/test_framework/core.t27","category":"specs/test_framework","name":"core","module":"core","lines":317,"bytes":10877},{"path":"specs/test_framework/graph_drift_detection.t27","category":"specs/test_framework","name":"graph_drift_detection","module":"GraphDriftDetection","lines":650,"bytes":23027},{"path":"specs/test_framework/property_test_template.t27","category":"specs/test_framework","name":"property_test_template","module":"PBTTemplate","lines":433,"bytes":16346},{"path":"specs/test_framework/runner.t27","category":"specs/test_framework","name":"runner","module":"runner","lines":618,"bytes":19960},{"path":"specs/test_framework/verilog_bench_harness.t27","category":"specs/test_framework","name":"verilog_bench_harness","module":"VerilogBenchHarness","lines":460,"bytes":17844},{"path":"specs/tools/registry.t27","category":"specs/tools","name":"registry","module":"ToolsRegistry","lines":523,"bytes":16726},{"path":"specs/tools/schema.t27","category":"specs/tools","name":"schema","module":"Tools","lines":523,"bytes":16580},{"path":"specs/tools/tri_to_t27_converter.t27","category":"specs/tools","name":"tri_to_t27_converter","module":"TriToT27Converter","lines":406,"bytes":16958},{"path":"specs/tri/agent/agent_run.t27","category":"specs/tri","name":"agent_run","module":"AgentRun","lines":16,"bytes":624},{"path":"specs/tri/agent/agents.t27","category":"specs/tri","name":"agents","module":"agents","lines":16,"bytes":619},{"path":"specs/tri/agent/autonomous_lifecycle.t27","category":"specs/tri","name":"autonomous_lifecycle","module":"TriAutonomousLifecycle","lines":73,"bytes":2953},{"path":"specs/tri/agent/autonomous_universe.t27","category":"specs/tri","name":"autonomous_universe","module":"AutonomousUniverse","lines":16,"bytes":645},{"path":"specs/tri/agent/eternal_monitor.t27","category":"specs/tri","name":"eternal_monitor","module":"EternalMonitor","lines":70,"bytes":2500},{"path":"specs/tri/agent/experience_hooks.t27","category":"specs/tri","name":"experience_hooks","module":"experience_hooks","lines":16,"bytes":639},{"path":"specs/tri/agent/faculty_board.t27","category":"specs/tri","name":"faculty_board","module":"FacultyBoard","lines":63,"bytes":2412},{"path":"specs/tri/agent/governance_agent.t27","category":"specs/tri","name":"governance_agent","module":"GovernanceAgent","lines":98,"bytes":3447},{"path":"specs/tri/agent/handoff.t27","category":"specs/tri","name":"handoff","module":"Handoff","lines":67,"bytes":2325},{"path":"specs/tri/agent/memory.t27","category":"specs/tri","name":"memory","module":"memory","lines":16,"bytes":619},{"path":"specs/tri/agent/swarm_agents.t27","category":"specs/tri","name":"swarm_agents","module":"SwarmAgents","lines":143,"bytes":5212},{"path":"specs/tri/collections/array.t27","category":"specs/tri","name":"array","module":"TriArray","lines":123,"bytes":5387},{"path":"specs/tri/collections/bitmap.t27","category":"specs/tri","name":"bitmap","module":"TriBitmap","lines":101,"bytes":4201},{"path":"specs/tri/collections/bitset.t27","category":"specs/tri","name":"bitset","module":"TriBitset","lines":97,"bytes":3588},{"path":"specs/tri/collections/bitvector.t27","category":"specs/tri","name":"bitvector","module":"TriBitvector","lines":93,"bytes":4192},{"path":"specs/tri/collections/btree.t27","category":"specs/tri","name":"btree","module":"TriBtree","lines":85,"bytes":4316},{"path":"specs/tri/collections/circular_buffer.t27","category":"specs/tri","name":"circular_buffer","module":"TriCircularBuffer","lines":79,"bytes":3676},{"path":"specs/tri/collections/context.t27","category":"specs/tri","name":"context","module":"TriContext","lines":16,"bytes":625},{"path":"specs/tri/collections/deque.t27","category":"specs/tri","name":"deque","module":"TriDeque","lines":77,"bytes":3243},{"path":"specs/tri/collections/either.t27","category":"specs/tri","name":"either","module":"TriEither","lines":75,"bytes":3222},{"path":"specs/tri/collections/interval.t27","category":"specs/tri","name":"interval","module":"TriInterval","lines":86,"bytes":4186},{"path":"specs/tri/collections/linked_list.t27","category":"specs/tri","name":"linked_list","module":"TriLinkedList","lines":84,"bytes":3032},{"path":"specs/tri/collections/list.t27","category":"specs/tri","name":"list","module":"TriList","lines":97,"bytes":3261},{"path":"specs/tri/collections/lockfree_stack.t27","category":"specs/tri","name":"lockfree_stack","module":"TriLockfreeStack","lines":70,"bytes":3118},{"path":"specs/tri/collections/lru.t27","category":"specs/tri","name":"lru","module":"TriLru","lines":57,"bytes":2516},{"path":"specs/tri/collections/lru_cache.t27","category":"specs/tri","name":"lru_cache","module":"TriLruCache","lines":68,"bytes":2554},{"path":"specs/tri/collections/map.t27","category":"specs/tri","name":"map","module":"TriMap","lines":73,"bytes":3019},{"path":"specs/tri/collections/maybe.t27","category":"specs/tri","name":"maybe","module":"TriMaybe","lines":81,"bytes":3955},{"path":"specs/tri/collections/namespace.t27","category":"specs/tri","name":"namespace","module":"TriNamespace","lines":58,"bytes":2700},{"path":"specs/tri/collections/option.t27","category":"specs/tri","name":"option","module":"TriOption","lines":79,"bytes":3504},{"path":"specs/tri/collections/priority_queue.t27","category":"specs/tri","name":"priority_queue","module":"TriPriorityQueue","lines":78,"bytes":3380},{"path":"specs/tri/collections/queue.t27","category":"specs/tri","name":"queue","module":"TriQueue","lines":70,"bytes":2992},{"path":"specs/tri/collections/result.t27","category":"specs/tri","name":"result","module":"TriResult","lines":79,"bytes":3655},{"path":"specs/tri/collections/ring_buffer.t27","category":"specs/tri","name":"ring_buffer","module":"TriRing","lines":101,"bytes":4905},{"path":"specs/tri/collections/set.t27","category":"specs/tri","name":"set","module":"TriSet","lines":65,"bytes":2723},{"path":"specs/tri/collections/skip_list.t27","category":"specs/tri","name":"skip_list","module":"TriSkipList","lines":69,"bytes":3150},{"path":"specs/tri/collections/stack.t27","category":"specs/tri","name":"stack","module":"TriStack","lines":71,"bytes":3014},{"path":"specs/tri/collections/state.t27","category":"specs/tri","name":"state","module":"TriState","lines":65,"bytes":2474},{"path":"specs/tri/collections/tuple.t27","category":"specs/tri","name":"tuple","module":"TriTuple","lines":72,"bytes":2619},{"path":"specs/tri/collections/variant.t27","category":"specs/tri","name":"variant","module":"TriVariant","lines":63,"bytes":2922},{"path":"specs/tri/crypto/base32.t27","category":"specs/tri","name":"base32","module":"TriBase32","lines":78,"bytes":4129},{"path":"specs/tri/crypto/base64.t27","category":"specs/tri","name":"base64","module":"TriBase64","lines":97,"bytes":4954},{"path":"specs/tri/crypto/crypto.t27","category":"specs/tri","name":"crypto","module":"TriCrypto","lines":56,"bytes":2500},{"path":"specs/tri/crypto/ecc.t27","category":"specs/tri","name":"ecc","module":"TriEcc","lines":89,"bytes":4348},{"path":"specs/tri/crypto/hex.t27","category":"specs/tri","name":"hex","module":"TriHex","lines":64,"bytes":2929},{"path":"specs/tri/crypto/hmac.t27","category":"specs/tri","name":"hmac","module":"TriHmac","lines":56,"bytes":2274},{"path":"specs/tri/crypto/reed_solomon.t27","category":"specs/tri","name":"reed_solomon","module":"TriReedSolomon","lines":46,"bytes":2205},{"path":"specs/tri/crypto/rsa.t27","category":"specs/tri","name":"rsa","module":"TriRsa","lines":83,"bytes":4009},{"path":"specs/tri/crypto/sha256.t27","category":"specs/tri","name":"sha256","module":"TriSha256","lines":67,"bytes":2952},{"path":"specs/tri/encoding/bson.t27","category":"specs/tri","name":"bson","module":"TriBson","lines":70,"bytes":3544},{"path":"specs/tri/encoding/csv.t27","category":"specs/tri","name":"csv","module":"TriCsv","lines":72,"bytes":3155},{"path":"specs/tri/encoding/html.t27","category":"specs/tri","name":"html","module":"TriHtml","lines":48,"bytes":2223},{"path":"specs/tri/encoding/json.t27","category":"specs/tri","name":"json","module":"TriJson","lines":67,"bytes":2655},{"path":"specs/tri/encoding/markup.t27","category":"specs/tri","name":"markup","module":"TriMarkup","lines":53,"bytes":2716},{"path":"specs/tri/encoding/mime.t27","category":"specs/tri","name":"mime","module":"TriMime","lines":57,"bytes":2794},{"path":"specs/tri/encoding/msgpack.t27","category":"specs/tri","name":"msgpack","module":"TriMsgpack","lines":93,"bytes":4485},{"path":"specs/tri/encoding/xml.t27","category":"specs/tri","name":"xml","module":"TriXml","lines":48,"bytes":2195},{"path":"specs/tri/graph/bellman_ford.t27","category":"specs/tri","name":"bellman_ford","module":"TriBellmanFord","lines":37,"bytes":1918},{"path":"specs/tri/graph/dijkstra.t27","category":"specs/tri","name":"dijkstra","module":"TriDijkstra","lines":38,"bytes":1854},{"path":"specs/tri/graph/disjoint_set.t27","category":"specs/tri","name":"disjoint_set","module":"TriDisjointSet","lines":93,"bytes":4626},{"path":"specs/tri/graph/graph.t27","category":"specs/tri","name":"graph","module":"TriGraph","lines":61,"bytes":2379},{"path":"specs/tri/graph/graph_bfs.t27","category":"specs/tri","name":"graph_bfs","module":"TriGraphBfs","lines":113,"bytes":5554},{"path":"specs/tri/graph/graph_dfs.t27","category":"specs/tri","name":"graph_dfs","module":"TriGraphDfs","lines":38,"bytes":1808},{"path":"specs/tri/graph/prims_mst.t27","category":"specs/tri","name":"prims_mst","module":"TriPrimsMst","lines":38,"bytes":1795},{"path":"specs/tri/graph/topological_sort.t27","category":"specs/tri","name":"topological_sort","module":"TriTopological","lines":47,"bytes":2082},{"path":"specs/tri/io/compress.t27","category":"specs/tri","name":"compress","module":"TriCompress","lines":58,"bytes":3115},{"path":"specs/tri/io/filesystem.t27","category":"specs/tri","name":"filesystem","module":"TriFilesystem","lines":113,"bytes":5345},{"path":"specs/tri/io/fs.t27","category":"specs/tri","name":"fs","module":"TriFs","lines":73,"bytes":2757},{"path":"specs/tri/io/io.t27","category":"specs/tri","name":"io","module":"TriIo","lines":67,"bytes":3086},{"path":"specs/tri/io/reader.t27","category":"specs/tri","name":"reader","module":"TriReader","lines":65,"bytes":2550},{"path":"specs/tri/io/writer.t27","category":"specs/tri","name":"writer","module":"TriWriter","lines":66,"bytes":2628},{"path":"specs/tri/io/zip.t27","category":"specs/tri","name":"zip","module":"TriZipper","lines":77,"bytes":2847},{"path":"specs/tri/math/bezier.t27","category":"specs/tri","name":"bezier","module":"TriBezier","lines":75,"bytes":3811},{"path":"specs/tri/math/constants.t27","category":"specs/tri","name":"constants","module":"TriConstants","lines":128,"bytes":5390},{"path":"specs/tri/math/math.t27","category":"specs/tri","name":"math","module":"TriMath","lines":16,"bytes":619},{"path":"specs/tri/math/matrix.t27","category":"specs/tri","name":"matrix","module":"TriMatrix","lines":96,"bytes":4182},{"path":"specs/tri/math/measurement.t27","category":"specs/tri","name":"measurement","module":"TriMeasurement","lines":16,"bytes":633},{"path":"specs/tri/math/polynomial.t27","category":"specs/tri","name":"polynomial","module":"TriPolynomial","lines":80,"bytes":3851},{"path":"specs/tri/math/probability.t27","category":"specs/tri","name":"probability","module":"TriProbability","lines":67,"bytes":2602},{"path":"specs/tri/math/statistics.t27","category":"specs/tri","name":"statistics","module":"TriStatistics","lines":77,"bytes":2735},{"path":"specs/tri/net/async.t27","category":"specs/tri","name":"async","module":"TriAsync","lines":86,"bytes":3896},{"path":"specs/tri/net/async_stream.t27","category":"specs/tri","name":"async_stream","module":"TriAsyncStream","lines":86,"bytes":4087},{"path":"specs/tri/net/channel.t27","category":"specs/tri","name":"channel","module":"TriChannel","lines":67,"bytes":2629},{"path":"specs/tri/net/cloud.t27","category":"specs/tri","name":"cloud","module":"TriCloud","lines":16,"bytes":621},{"path":"specs/tri/net/http.t27","category":"specs/tri","name":"http","module":"TriHttp","lines":133,"bytes":6147},{"path":"specs/tri/net/net.t27","category":"specs/tri","name":"net","module":"TriNet","lines":71,"bytes":3117},{"path":"specs/tri/net/url.t27","category":"specs/tri","name":"url","module":"TriUrl","lines":83,"bytes":4112},{"path":"specs/tri/pipeline/batch_runner.t27","category":"specs/tri","name":"batch_runner","module":"BatchRunner","lines":115,"bytes":4376},{"path":"specs/tri/pipeline/builder.t27","category":"specs/tri","name":"builder","module":"TriBuilder","lines":107,"bytes":3957},{"path":"specs/tri/pipeline/cloud_orchestrator.t27","category":"specs/tri","name":"cloud_orchestrator","module":"CloudOrchestrator","lines":16,"bytes":643},{"path":"specs/tri/pipeline/codegen.t27","category":"specs/tri","name":"codegen","module":"TestSpec","lines":27,"bytes":1147},{"path":"specs/tri/pipeline/pipeline.t27","category":"specs/tri","name":"pipeline","module":"TriPipeline","lines":16,"bytes":627},{"path":"specs/tri/pipeline/pipeline_parallel.t27","category":"specs/tri","name":"pipeline_parallel","module":"TriPipelineParallel","lines":50,"bytes":1900},{"path":"specs/tri/pipeline/spec_parser.t27","category":"specs/tri","name":"spec_parser","module":"TriSpecParser","lines":16,"bytes":632},{"path":"specs/tri/pipeline/spec_writer.t27","category":"specs/tri","name":"spec_writer","module":"SpecWriter","lines":46,"bytes":1622},{"path":"specs/tri/pipeline/workflow.t27","category":"specs/tri","name":"workflow","module":"Workflow","lines":31,"bytes":1263},{"path":"specs/tri/pipeline/workflow_executor.t27","category":"specs/tri","name":"workflow_executor","module":"WorkflowExecutor","lines":16,"bytes":641},{"path":"specs/tri/pipeline/workflow_parser.t27","category":"specs/tri","name":"workflow_parser","module":"WorkflowParser","lines":16,"bytes":637},{"path":"specs/tri/search/aho_corasick.t27","category":"specs/tri","name":"aho_corasick","module":"TriAhoCorasick","lines":99,"bytes":4285},{"path":"specs/tri/search/bloom_filter.t27","category":"specs/tri","name":"bloom_filter","module":"TriBloomFilter","lines":58,"bytes":2868},{"path":"specs/tri/search/boyer_moore.t27","category":"specs/tri","name":"boyer_moore","module":"TriBoyerMoore","lines":72,"bytes":3868},{"path":"specs/tri/search/knuth_morris_pratt.t27","category":"specs/tri","name":"knuth_morris_pratt","module":"TriKmp","lines":85,"bytes":4347},{"path":"specs/tri/search/match.t27","category":"specs/tri","name":"match","module":null,"lines":68,"bytes":3333},{"path":"specs/tri/search/pattern.t27","category":"specs/tri","name":"pattern","module":"TriPattern","lines":61,"bytes":3040},{"path":"specs/tri/search/rabin_karp.t27","category":"specs/tri","name":"rabin_karp","module":"TriRabinKarp","lines":73,"bytes":3867},{"path":"specs/tri/search/regex.t27","category":"specs/tri","name":"regex","module":"TriRegex","lines":91,"bytes":4367},{"path":"specs/tri/search/regex_advanced.t27","category":"specs/tri","name":"regex_advanced","module":"TriRegexAdvanced","lines":62,"bytes":2766},{"path":"specs/tri/search/search.t27","category":"specs/tri","name":"search","module":"TriSearch","lines":63,"bytes":3032},{"path":"specs/tri/sort/counting_sort.t27","category":"specs/tri","name":"counting_sort","module":"TriCountingSort","lines":27,"bytes":1352},{"path":"specs/tri/sort/heap_sort.t27","category":"specs/tri","name":"heap_sort","module":"TriHeapSort","lines":27,"bytes":1258},{"path":"specs/tri/sort/insertion_sort.t27","category":"specs/tri","name":"insertion_sort","module":"TriInsertionSort","lines":27,"bytes":1262},{"path":"specs/tri/sort/merge_sort.t27","category":"specs/tri","name":"merge_sort","module":"TriMergeSort","lines":37,"bytes":1665},{"path":"specs/tri/sort/quick_sort.t27","category":"specs/tri","name":"quick_sort","module":"TriQuickSort","lines":41,"bytes":1923},{"path":"specs/tri/sort/radix_sort.t27","category":"specs/tri","name":"radix_sort","module":"TriRadixSort","lines":56,"bytes":2879},{"path":"specs/tri/sort/selection_sort.t27","category":"specs/tri","name":"selection_sort","module":"TriSelectionSort","lines":27,"bytes":1247},{"path":"specs/tri/sort/shell_sort.t27","category":"specs/tri","name":"shell_sort","module":"TriShellSort","lines":27,"bytes":1257},{"path":"specs/tri/sort/sort.t27","category":"specs/tri","name":"sort","module":"TriSort","lines":57,"bytes":3188},{"path":"specs/tri/sort/tim_sort.t27","category":"specs/tri","name":"tim_sort","module":"TriTimSort","lines":27,"bytes":1311},{"path":"specs/tri/trees/avl_tree.t27","category":"specs/tri","name":"avl_tree","module":"TriAvlTree","lines":76,"bytes":2769},{"path":"specs/tri/trees/b_tree.t27","category":"specs/tri","name":"b_tree","module":"TriBTree","lines":96,"bytes":4616},{"path":"specs/tri/trees/fenwick_tree.t27","category":"specs/tri","name":"fenwick_tree","module":"TriFenwick","lines":101,"bytes":4544},{"path":"specs/tri/trees/kd_tree.t27","category":"specs/tri","name":"kd_tree","module":"TriKdTree","lines":89,"bytes":3998},{"path":"specs/tri/trees/octree.t27","category":"specs/tri","name":"octree","module":"TriOctree","lines":103,"bytes":4639},{"path":"specs/tri/trees/quadtree.t27","category":"specs/tri","name":"quadtree","module":"TriQuadtree","lines":110,"bytes":5036},{"path":"specs/tri/trees/red_black_tree.t27","category":"specs/tri","name":"red_black_tree","module":"TriRbTree","lines":81,"bytes":2892},{"path":"specs/tri/trees/rtree.t27","category":"specs/tri","name":"rtree","module":"TriRtree","lines":88,"bytes":3876},{"path":"specs/tri/trees/segment_tree.t27","category":"specs/tri","name":"segment_tree","module":"TriSegmentTree","lines":67,"bytes":3238},{"path":"specs/tri/trees/splay_tree.t27","category":"specs/tri","name":"splay_tree","module":"TriSplayTree","lines":76,"bytes":2802},{"path":"specs/tri/trees/suffix_array.t27","category":"specs/tri","name":"suffix_array","module":"TriSuffixArray","lines":83,"bytes":4311},{"path":"specs/tri/trees/tree.t27","category":"specs/tri","name":"tree","module":"TriTree","lines":85,"bytes":3768},{"path":"specs/tri/trees/trie.t27","category":"specs/tri","name":"trie","module":"TriTrie","lines":93,"bytes":4263},{"path":"specs/tri/utils/args.t27","category":"specs/tri","name":"args","module":null,"lines":86,"bytes":4133},{"path":"specs/tri/utils/arrow_time.t27","category":"specs/tri","name":"arrow_time","module":"arrow_time","lines":16,"bytes":627},{"path":"specs/tri/utils/bytes.t27","category":"specs/tri","name":"bytes","module":"TriBytes","lines":106,"bytes":3875},{"path":"specs/tri/utils/color.t27","category":"specs/tri","name":"color","module":"TriColor","lines":95,"bytes":4563},{"path":"specs/tri/utils/colors.t27","category":"specs/tri","name":"colors","module":"TriColors","lines":16,"bytes":623},{"path":"specs/tri/utils/config.t27","category":"specs/tri","name":"config","module":"TriConfig","lines":89,"bytes":4082},{"path":"specs/tri/utils/error.t27","category":"specs/tri","name":"error","module":"TriError","lines":26,"bytes":1415},{"path":"specs/tri/utils/exit_codes.t27","category":"specs/tri","name":"exit_codes","module":"TriExitCodes","lines":35,"bytes":1562},{"path":"specs/tri/utils/help.t27","category":"specs/tri","name":"help","module":"TriHelp","lines":32,"bytes":1370},{"path":"specs/tri/utils/logger.t27","category":"specs/tri","name":"logger","module":null,"lines":66,"bytes":2659},{"path":"specs/tri/utils/logging.t27","category":"specs/tri","name":"logging","module":"TriLogging","lines":102,"bytes":5075},{"path":"specs/tri/utils/random.t27","category":"specs/tri","name":"random","module":"TriRandom","lines":97,"bytes":4367},{"path":"specs/tri/utils/string.t27","category":"specs/tri","name":"string","module":"string","lines":16,"bytes":619},{"path":"specs/tri/utils/template.t27","category":"specs/tri","name":"template","module":"TriTemplate","lines":70,"bytes":3884},{"path":"specs/tri/utils/terminal.t27","category":"specs/tri","name":"terminal","module":"TriTerminal","lines":59,"bytes":2447},{"path":"specs/tri/utils/text.t27","category":"specs/tri","name":"text","module":"TriText","lines":67,"bytes":2793},{"path":"specs/tri/utils/time.t27","category":"specs/tri","name":"time","module":"TriTime","lines":93,"bytes":4138},{"path":"specs/tri/utils/utf8.t27","category":"specs/tri","name":"utf8","module":"TriUtf8","lines":82,"bytes":3595},{"path":"specs/tri/utils/version.t27","category":"specs/tri","name":"version","module":"TriVersion","lines":85,"bytes":4113},{"path":"specs/vm/jit_semantics.t27","category":"specs/vm","name":"jit_semantics","module":"JitSemantics","lines":319,"bytes":14011},{"path":"specs/vsa/jones_polynomial.t27","category":"specs/vsa","name":"jones_polynomial","module":"JonesPolynomial","lines":353,"bytes":12139},{"path":"specs/vsa/ops.t27","category":"specs/vsa","name":"ops","module":"VSAOps","lines":670,"bytes":27126},{"path":"specs/vsa/packed_vsa.t27","category":"specs/vsa","name":"packed_vsa","module":"PackedVsa","lines":291,"bytes":11657},{"path":"specs/vsa/sdk.t27","category":"specs/vsa","name":"sdk","module":"SDK","lines":626,"bytes":24071},{"path":"specs/vsa/sequence_hdc.t27","category":"specs/vsa","name":"sequence_hdc","module":"SequenceHdc","lines":342,"bytes":15174},{"path":"specs/vsa/similarity_search.t27","category":"specs/vsa","name":"similarity_search","module":"VSASimilaritySearch","lines":467,"bytes":15246},{"path":"specs/vsa/vsa_core.t27","category":"specs/vsa","name":"vsa_core","module":"vsa_core","lines":1012,"bytes":29297},{"path":"test_highlight.t27","category":"root","name":"test_highlight","module":null,"lines":57,"bytes":1013},{"path":"tests/comprehensive_suite.t27","category":"tests","name":"comprehensive_suite","module":"tests-comprehensive-suite","lines":19,"bytes":560},{"path":"tests/ring0_trivial.t27","category":"tests","name":"ring0_trivial","module":"ring0","lines":11,"bytes":201},{"path":"vscode-trinity-swe/test_highlight.t27","category":"vscode-trinity-swe","name":"test_highlight","module":null,"lines":1,"bytes":0}]} \ No newline at end of file +{"generatedFrom":{"repo":"gHashTag/t27","commit":"7c2f47006ba0b72c8fc839b2cf91fcdef5815dcc","shortCommit":"7c2f47006","specsOrCompilerDirty":true},"wasmBytes":488709,"specCount":668,"totalLines":232987,"categories":{"specs/tri":150,"specs/fpga":66,"specs/ml":60,"chips/euler":49,"chips/gamma":49,"chips/phi":49,"specs/isa":16,"specs/numeric":16,"specs/physics":16,"specs/server":11,"specs/compiler":10,"specs/math":10,"specs/sacred":10,"specs/ar":8,"specs/base":8,"specs/vsa":7,"compiler/codegen":6,"specs/brain":6,"specs/github":5,"specs/lsp":5,"specs/sandbox":5,"specs/test_framework":5,"specs/demos":4,"specs/benchmarks":4,"specs/config":4,"specs/git":4,"specs/memory":4,"specs/nn":4,"specs/provider":4,"specs/storage":4,"specs/ternary":4,"compiler/cli":3,"compiler/runtime":3,"specs/account":3,"specs/api":3,"specs/boards":3,"specs/file":3,"specs/pins":3,"specs/pipeline":3,"specs/queen":3,"specs/runtime":3,"specs/shell":3,"specs/tools":3,"compiler/parser":2,"specs/bus":2,"specs/enrichment":2,"specs/sync":2,"tests":2,"bootstrap/specs":1,"compiler":1,"compiler/skill":1,"contrib/backend":1,"examples/fpga":1,"specs/auth":1,"specs/automation":1,"specs/cloud":1,"specs/conformance":1,"specs/depin":1,"specs/graph":1,"specs/hslm":1,"specs/interop":1,"specs/jit":1,"specs/neural":1,"specs/portable":1,"specs/vm":1,"root":1,"vscode-trinity-swe":1},"health":{"ok":453,"warn":209,"fail":6},"backendFailures":{"verilog_hir":6},"featured":"specs/demos/hello_world.t27","totals":{"tokens":1119224,"nodes":279982,"lossAffected":78,"tcAffected":149},"specs":[{"path":"specs/demos/hello_world.t27","category":"specs/demos","name":"hello_world","module":"hello-world","lines":99,"bytes":3666,"description":"hello_world.t27 -- start here The smallest spec that still shows every part of the language: constants, a type, functions, a test and an invariant. Read it top to bottom, then watch it become tokens, an AST, and five different target languages.","health":"ok","tokens":315,"nodes":107,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2670,"rust":734,"verilog":3889,"verilog_hir":515,"zig":1542},"featured":true},{"path":"bootstrap/specs/physics/formula_registry.t27","category":"bootstrap/specs","name":"formula_registry","module":null,"lines":210,"bytes":6193,"description":"Generated from FORMULA_TABLE_v06.md and FORMULA_TABLE_v07.md SSOT for Trinity formula discovery","health":"ok","tokens":822,"nodes":364,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4270,"rust":2960,"verilog":8013,"verilog_hir":1685,"zig":2759}},{"path":"chips/euler/specs/fpga/avs_controller_48.t27","category":"chips/euler","name":"avs_controller_48","module":"avs-controller-48","lines":607,"bytes":20060,"description":"avs_controller_48.t27 — 48-pin AVS Controller Adaptive voltage scaling controller for 48-pin configuration","health":"ok","tokens":2944,"nodes":877,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":12673,"rust":5671,"verilog":19133,"verilog_hir":1898,"zig":11618}},{"path":"chips/euler/specs/fpga/avs_controller_96.t27","category":"chips/euler","name":"avs_controller_96","module":"avs-controller-96","lines":735,"bytes":24836,"description":"avs_controller_96.t27 — 96-pin AVS Controller Adaptive voltage scaling controller for 96-pin configuration","health":"warn","tokens":3521,"nodes":1085,"depth":9,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":16185,"rust":7908,"verilog":23298,"verilog_hir":2373,"zig":14936}},{"path":"chips/euler/specs/fpga/avs_reconf.t27","category":"chips/euler","name":"avs_reconf","module":"avs-reconf","lines":702,"bytes":24801,"description":"avs_reconf.t27 — AVS Reconfiguration Module Dynamic AVS voltage/frequency reconfiguration support","health":"ok","tokens":3542,"nodes":1045,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":15559,"rust":8600,"verilog":21090,"verilog_hir":1972,"zig":15599}},{"path":"chips/euler/specs/fpga/dfs_gate.t27","category":"chips/euler","name":"dfs_gate","module":"sacred-dfs_gate","lines":600,"bytes":22017,"description":"dfs_gate.t27 — Sacred Opcode 0xE7: Depth-First Search Gate Hardware acceleration for DFS traversal and pattern matching","health":"ok","tokens":3969,"nodes":675,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9082,"rust":4335,"verilog":15468,"verilog_hir":2066,"zig":9770}},{"path":"chips/euler/specs/fpga/drowsy_ret.t27","category":"chips/euler","name":"drowsy_ret","module":"sacred-drowsy_ret","lines":716,"bytes":26247,"description":null,"health":"ok","tokens":4100,"nodes":117,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3538,"rust":2657,"verilog":5652,"verilog_hir":604,"zig":2340}},{"path":"chips/euler/specs/fpga/fbb_active_path.t27","category":"chips/euler","name":"fbb_active_path","module":"fbb-active-path","lines":756,"bytes":26695,"description":"fbb_active_path.t27 — FPGA Feedback Bridge Active Path Active path management for FPGA feedback bridge","health":"ok","tokens":4002,"nodes":1123,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":16298,"rust":9040,"verilog":22056,"verilog_hir":2461,"zig":15709}},{"path":"chips/euler/specs/fpga/fp8_e4m3.t27","category":"chips/euler","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652,"description":"fp8_e4m3.t27 — FP8 E4M3 8-bit Floating Point 8-bit float with 4 exponent, 3 mantissa bits (no implicit leading bit) OCP FP8 format optimized for neural network training Range: ~-240 to ~240, precision: ~6-7 significant bits","health":"warn","tokens":6035,"nodes":1575,"depth":13,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":17636,"rust":9369,"verilog":33188,"verilog_hir":2181,"zig":18818}},{"path":"chips/euler/specs/fpga/fp8_e5m2.t27","category":"chips/euler","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826,"description":"fp8_e5m2.t27 — FP8 E5M2 8-bit Floating Point 8-bit float with 5 exponent, 2 mantissa bits (with implicit leading bit) OCP FP8 format optimized for inference and wide dynamic range Range: ~-57k to ~57k, precision: ~3-4 significant bits","health":"warn","tokens":7267,"nodes":1852,"depth":13,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":20951,"rust":10713,"verilog":39194,"verilog_hir":2617,"zig":22296}},{"path":"chips/euler/specs/fpga/gf128.t27","category":"chips/euler","name":"gf128","module":"GF128","lines":358,"bytes":10740,"description":"t27/specs/numeric/gf128.t27 GoldenFloat128 - 128-bit φ-structured floating point with extended range NUMERIC-STANDARD-001 Agent 5 (P1) Paper §6.7: Extended range for high-dynamic-range applications","health":"ok","tokens":1470,"nodes":486,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":8811,"rust":4090,"verilog":15830,"verilog_hir":1032,"zig":9858}},{"path":"chips/euler/specs/fpga/gf16_to_fp16.t27","category":"chips/euler","name":"gf16_to_fp16","module":"gf16-to-fp16","lines":574,"bytes":18936,"description":null,"health":"fail","tokens":3481,"nodes":973,"depth":14,"loss":0,"tcErrors":3,"failedBackends":["verilog_hir"],"outBytes":{"c":10720,"rust":4673,"verilog":18190,"verilog_hir":null,"zig":10574}},{"path":"chips/euler/specs/fpga/gf16_to_posit16.t27","category":"chips/euler","name":"gf16_to_posit16","module":"gf16-to-posit16","lines":664,"bytes":20622,"description":"gf16_to_posit16.t27 — GF16 to Posit16 Converter GoldenFloat16 to Posit type 16 (unum 1.0) format conversion","health":"warn","tokens":3526,"nodes":1045,"depth":13,"loss":0,"tcErrors":12,"failedBackends":[],"outBytes":{"c":11873,"rust":7280,"verilog":20464,"verilog_hir":1859,"zig":11623}},{"path":"chips/euler/specs/fpga/gf256.t27","category":"chips/euler","name":"gf256","module":"GF256","lines":336,"bytes":9935,"description":"t27/specs/numeric/gf256.t27 GoldenFloat256 0 256-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 11 (P1)","health":"warn","tokens":1592,"nodes":633,"depth":11,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":8199,"rust":5222,"verilog":13893,"verilog_hir":816,"zig":8540}},{"path":"chips/euler/specs/fpga/gf32_to_fp32.t27","category":"chips/euler","name":"gf32_to_fp32","module":"gf32-to-fp32","lines":757,"bytes":25067,"description":"gf32_to_fp32.t27 — GF32 to FP32 Converter GoldenFloat32 to IEEE 754 binary32 format conversion","health":"fail","tokens":4698,"nodes":1275,"depth":15,"loss":0,"tcErrors":8,"failedBackends":["verilog_hir"],"outBytes":{"c":13733,"rust":6171,"verilog":23398,"verilog_hir":null,"zig":13832}},{"path":"chips/euler/specs/fpga/gf64.t27","category":"chips/euler","name":"gf64","module":"GF64","lines":320,"bytes":9095,"description":"t27/specs/numeric/gf64.t27 GoldenFloat64 - 64-bit φ-structured floating point NUMERIC-STANDARD-001 Agent 4 (P1)","health":"ok","tokens":1320,"nodes":425,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7523,"rust":3303,"verilog":13143,"verilog_hir":930,"zig":8487}},{"path":"chips/euler/specs/fpga/holo_mux_x4.t27","category":"chips/euler","name":"holo_mux_x4","module":"sacred-holo_mux_x4","lines":581,"bytes":21272,"description":"holo_mux_x4.t27 — Sacred Opcode 0xE6: Holographic 4x Multiplexer Hardware multiplexer for holographic data paths with 4-way select","health":"warn","tokens":4128,"nodes":298,"depth":10,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":5628,"rust":4234,"verilog":8727,"verilog_hir":1009,"zig":4301}},{"path":"chips/euler/specs/fpga/int4.t27","category":"chips/euler","name":"int4","module":"Int4","lines":334,"bytes":8538,"description":"t27/specs/numeric/int4.t27 Int4 - 4-bit signed integer NUMERIC-STANDARD-001 Agent 9 (P1)","health":"ok","tokens":1655,"nodes":298,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6850,"rust":1962,"verilog":14629,"verilog_hir":953,"zig":8156}},{"path":"chips/euler/specs/fpga/int8.t27","category":"chips/euler","name":"int8","module":"Int8","lines":493,"bytes":13029,"description":"t27/specs/numeric/int8.t27 Int8 - 8-bit signed integer NUMERIC-STANDARD-001 Agent 10 (P1)","health":"warn","tokens":2646,"nodes":463,"depth":9,"loss":2,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9729,"rust":2936,"verilog":20392,"verilog_hir":1246,"zig":12222}},{"path":"chips/euler/specs/fpga/lane_l_precheck.t27","category":"chips/euler","name":"lane_l_precheck","module":"sacred-lane_precheck","lines":493,"bytes":17229,"description":"lane_l_precheck.t27 — Sacred Opcode 0xDF: LUT Lookup Precheck Hardware pre-check for LUT lookup operations Validates lane readiness, checks LUT access permissions, and prepares address","health":"warn","tokens":3044,"nodes":31,"depth":3,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1319,"rust":737,"verilog":2233,"verilog_hir":269,"zig":644}},{"path":"chips/euler/specs/fpga/lut_npu_81_entry.t27","category":"chips/euler","name":"lut_npu_81_entry","module":"sacred-lut_npu_81_entry","lines":419,"bytes":13484,"description":"lut_npu_81_entry.t27 — Sacred Opcode 0xE3: LUT NPU 81-Entry Lookup Hardware LUT for NPU operations with 81 entries (9×9 transform)","health":"warn","tokens":2475,"nodes":273,"depth":13,"loss":1,"tcErrors":1,"failedBackends":[],"outBytes":{"c":5072,"rust":3157,"verilog":7761,"verilog_hir":1120,"zig":3540}},{"path":"chips/euler/specs/fpga/nf4.t27","category":"chips/euler","name":"nf4","module":"NF4","lines":313,"bytes":8164,"description":"t27/specs/numeric/nf4.t27 NormalFloat4 - 4-bit normalized float quantization format (Google) NUMERIC-STANDARD-001 Agent 11 (P1) NF4 format: - Represents normalized values in [0, 1] - Uses a 4-bit normalized representation","health":"ok","tokens":1284,"nodes":261,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6543,"rust":1693,"verilog":13183,"verilog_hir":914,"zig":7705}},{"path":"chips/euler/specs/fpga/null_pe.t27","category":"chips/euler","name":"null_pe","module":"sacred-null_pe","lines":568,"bytes":20320,"description":"null_pe.t27 — Sacred Opcode 0xEA: Null Processing Element Hardware null PE (processing element) for sparse acceleration","health":"warn","tokens":3694,"nodes":660,"depth":11,"loss":1,"tcErrors":4,"failedBackends":[],"outBytes":{"c":9376,"rust":5003,"verilog":14296,"verilog_hir":1369,"zig":8482}},{"path":"chips/euler/specs/fpga/posit16.t27","category":"chips/euler","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004,"description":"posit16.t27 — Posit Type 16 (Type-2 with ES=1, unum 1.0 format) 16-bit posit format with 1 exponent bit (ES=1) and 0 useed bits Alternative name: posit<16,1> Range: ~-3.8e4 to ~3.8e4, precision: ~1-2 significant bits at extremes, ~10 at 1.0","health":"warn","tokens":5421,"nodes":1544,"depth":13,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":17804,"rust":10661,"verilog":32115,"verilog_hir":2131,"zig":17940}},{"path":"chips/euler/specs/fpga/purkinje_thermal_gate.t27","category":"chips/euler","name":"purkinje_thermal_gate","module":"purkinje-thermal-gate","lines":704,"bytes":24460,"description":"purkinje_thermal_gate.t27 — Purkinje Thermal Gate Thermally-gated activation inspired by Purkinje neural dynamics","health":"ok","tokens":3682,"nodes":1063,"depth":13,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":14852,"rust":6797,"verilog":20709,"verilog_hir":1880,"zig":14728}},{"path":"chips/euler/specs/fpga/sparse_mask.t27","category":"chips/euler","name":"sparse_mask","module":"sacred-sparse_mask","lines":556,"bytes":17492,"description":"sparse_mask.t27 — Sacred Opcode 0xE8: Sparse Mask (Sparse Skip 2) Hardware for generating and applying sparse tensor masks","health":"warn","tokens":3158,"nodes":355,"depth":11,"loss":0,"tcErrors":4,"failedBackends":[],"outBytes":{"c":5066,"rust":3252,"verilog":8130,"verilog_hir":1468,"zig":4013}},{"path":"chips/euler/specs/fpga/sparse_skip.t27","category":"chips/euler","name":"sparse_skip","module":"sacred-sparse_skip","lines":476,"bytes":15117,"description":"sparse_skip.t27 — Sacred Opcode 0xE1: Sparse Skip Operation Hardware acceleration for sparse tensor operations with zero-skipping","health":"warn","tokens":2985,"nodes":630,"depth":10,"loss":0,"tcErrors":3,"failedBackends":[],"outBytes":{"c":8360,"rust":4128,"verilog":13457,"verilog_hir":1162,"zig":8132}},{"path":"chips/euler/specs/fpga/spec_exit.t27","category":"chips/euler","name":"spec_exit","module":"sacred-spec_exit","lines":541,"bytes":19745,"description":"spec_exit.t27 — Sacred Opcode 0xEB: Speculative Exit Hardware for speculative exit and recovery","health":"ok","tokens":3140,"nodes":730,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":11376,"rust":5841,"verilog":16952,"verilog_hir":1700,"zig":10561}},{"path":"chips/euler/specs/fpga/stoch_round.t27","category":"chips/euler","name":"stoch_round","module":"sacred-stoch_round","lines":575,"bytes":19164,"description":"stoch_round.t27 — Sacred Opcode 0xE9: Stochastic Rounding Hardware stochastic rounding for quantization","health":"warn","tokens":3632,"nodes":1074,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":11522,"rust":6855,"verilog":18337,"verilog_hir":1313,"zig":11697}},{"path":"chips/euler/specs/fpga/subth_clk.t27","category":"chips/euler","name":"subth_clk","module":"sacred-subth_clk","lines":478,"bytes":17539,"description":"subth_clk.t27 — Sacred Opcode 0xE5: Sub-threshold Clock Gating Hardware for sub-threshold clock gating for power reduction","health":"warn","tokens":2862,"nodes":646,"depth":9,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":10177,"rust":4457,"verilog":17524,"verilog_hir":1438,"zig":10580}},{"path":"chips/euler/specs/numeric/binary16.t27","category":"chips/euler","name":"binary16","module":"Binary16","lines":420,"bytes":12171,"description":"t27/specs/numeric/binary16.t27 Binary16 - Binary packed 16-bit format (3 bits per integer) NUMERIC-STANDARD-001 Agent 13 (P1) Binary16 format: - Packs 5 signed integers (3 bits each) into 16 bits - Each integer range: -4 to 3","health":"warn","tokens":2331,"nodes":446,"depth":9,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":9115,"rust":2957,"verilog":19728,"verilog_hir":1098,"zig":10826}},{"path":"chips/euler/specs/numeric/formats.t27","category":"chips/euler","name":"formats","module":"Formats","lines":538,"bytes":17523,"description":"specs/numeric/formats.t27 Format Conversion Utilities - GF16, f32, ternary encoding","health":"ok","tokens":1601,"nodes":224,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6234,"rust":1013,"verilog":15020,"verilog_hir":698,"zig":8733}},{"path":"chips/euler/specs/numeric/fp8_e4m3.t27","category":"chips/euler","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652,"description":"fp8_e4m3.t27 — FP8 E4M3 8-bit Floating Point 8-bit float with 4 exponent, 3 mantissa bits (no implicit leading bit) OCP FP8 format optimized for neural network training Range: ~-240 to ~240, precision: ~6-7 significant bits","health":"warn","tokens":6035,"nodes":1575,"depth":13,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":17636,"rust":9369,"verilog":33188,"verilog_hir":2181,"zig":18818}},{"path":"chips/euler/specs/numeric/fp8_e5m2.t27","category":"chips/euler","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826,"description":"fp8_e5m2.t27 — FP8 E5M2 8-bit Floating Point 8-bit float with 5 exponent, 2 mantissa bits (with implicit leading bit) OCP FP8 format optimized for inference and wide dynamic range Range: ~-57k to ~57k, precision: ~3-4 significant bits","health":"warn","tokens":7267,"nodes":1852,"depth":13,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":20951,"rust":10713,"verilog":39194,"verilog_hir":2617,"zig":22296}},{"path":"chips/euler/specs/numeric/gf12.t27","category":"chips/euler","name":"gf12","module":"GF12","lines":482,"bytes":16369,"description":"t27/specs/numeric/gf12.t27 GoldenFloat12 0 12-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 4 (P1)","health":"warn","tokens":1979,"nodes":647,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9881,"rust":4274,"verilog":19274,"verilog_hir":1059,"zig":11436}},{"path":"chips/euler/specs/numeric/gf128.t27","category":"chips/euler","name":"gf128","module":"triformat-gf128","lines":506,"bytes":17137,"description":"gf128.t27 — GoldenFloat128 Encode/Decode GF128: 128-bit floating point with 1 sign + 28 exponent + 99 mantissa Bit layout: [S(1) E(28) M(99)] = [127:127][126:99][98:0] Extended range format for high-precision scientific computing","health":"ok","tokens":2514,"nodes":762,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9715,"rust":6413,"verilog":16944,"verilog_hir":1753,"zig":9067}},{"path":"chips/euler/specs/numeric/gf16.t27","category":"chips/euler","name":"gf16","module":"triformat-gf16","lines":3437,"bytes":101513,"description":"gf16.t27 0 GoldenFloat16 Encode/Decode GF16: 16-bit floating point with 1 sign + 6 exponent + 9 mantissa Bit layout: [S(1) E(6) M(9)] = [15:15][14:9][8:0] 12 + 1/34 = 3 | TRINITY","health":"ok","tokens":16290,"nodes":6744,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":88131,"rust":19607,"verilog":169609,"verilog_hir":2914,"zig":83320}},{"path":"chips/euler/specs/numeric/gf20.t27","category":"chips/euler","name":"gf20","module":"GF20","lines":469,"bytes":15992,"description":"t27/specs/numeric/gf20.t27 GoldenFloat20 0 20-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 6 (P1)","health":"warn","tokens":1927,"nodes":639,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9537,"rust":4297,"verilog":18742,"verilog_hir":1062,"zig":10908}},{"path":"chips/euler/specs/numeric/gf24.t27","category":"chips/euler","name":"gf24","module":"GF24","lines":469,"bytes":16040,"description":"t27/specs/numeric/gf24.t27 GoldenFloat24 0 24-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 7 (P1)","health":"warn","tokens":1933,"nodes":640,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9579,"rust":4328,"verilog":18783,"verilog_hir":1074,"zig":10948}},{"path":"chips/euler/specs/numeric/gf256.t27","category":"chips/euler","name":"gf256","module":"triformat-gf256","lines":595,"bytes":20164,"description":"gf256.t27 — GoldenFloat256 Encode/Decode GF256: 256-bit floating point with 1 sign + 32 exponent + 223 mantissa Bit layout: [S(1) E(32) M(223)] = [255:255][254:223][222:0] Maximum precision format for scientific computing and simulation","health":"ok","tokens":3250,"nodes":1094,"depth":13,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":11484,"rust":7211,"verilog":20030,"verilog_hir":2273,"zig":10948}},{"path":"chips/euler/specs/numeric/gf32.t27","category":"chips/euler","name":"gf32","module":"GF32","lines":480,"bytes":16589,"description":"t27/specs/numeric/gf32.t27 GoldenFloat32 0 32-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 8 (P1)","health":"warn","tokens":1934,"nodes":641,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9779,"rust":4324,"verilog":18871,"verilog_hir":1074,"zig":11206}},{"path":"chips/euler/specs/numeric/gf4.t27","category":"chips/euler","name":"gf4","module":"GF4","lines":306,"bytes":11167,"description":"t27/specs/numeric/gf4.t27 GoldenFloat4 0 4-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 2 (P1)","health":"ok","tokens":1017,"nodes":265,"depth":14,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5819,"rust":1237,"verilog":10517,"verilog_hir":532,"zig":7261}},{"path":"chips/euler/specs/numeric/gf64.t27","category":"chips/euler","name":"gf64","module":"triformat-gf64","lines":764,"bytes":24203,"description":"gf64.t27 — GoldenFloat64 Encode/Decode GF64: 64-bit floating point with 1 sign + 18 exponent + 45 mantissa Bit layout: [S(1) E(18) M(45)] = [63:63][62:45][44:0]","health":"warn","tokens":4384,"nodes":1156,"depth":14,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":13085,"rust":6818,"verilog":25023,"verilog_hir":1810,"zig":14002}},{"path":"chips/euler/specs/numeric/gf8.t27","category":"chips/euler","name":"gf8","module":"GF8","lines":522,"bytes":17732,"description":"t27/specs/numeric/gf8.t27 GoldenFloat8 0 8-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 3 (P1)","health":"warn","tokens":2153,"nodes":661,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":10178,"rust":4222,"verilog":20381,"verilog_hir":1057,"zig":12139}},{"path":"chips/euler/specs/numeric/goldenfloat_family.t27","category":"chips/euler","name":"goldenfloat_family","module":"GoldenFloatFamily","lines":450,"bytes":17346,"description":"t27/specs/numeric/goldenfloat_family.t27 GoldenFloat Family 0 1-structured floating point formats NUMERIC-STANDARD-001 2 Agent 1 (P0)","health":"warn","tokens":1793,"nodes":356,"depth":11,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":10804,"rust":4339,"verilog":19539,"verilog_hir":686,"zig":12751}},{"path":"chips/euler/specs/numeric/int4.t27","category":"chips/euler","name":"int4","module":"triformat-int4","lines":766,"bytes":20533,"description":"int4.t27 — Int4 Signed 4-bit Integer Quantization Range: -8 to 7 Used for ultra-low precision quantization in ML","health":"ok","tokens":4078,"nodes":850,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9763,"rust":3801,"verilog":22124,"verilog_hir":2099,"zig":10462}},{"path":"chips/euler/specs/numeric/int8.t27","category":"chips/euler","name":"int8","module":"triformat-int8","lines":1151,"bytes":30614,"description":"int8.t27 — Int8 Signed 8-bit Integer Quantization Range: -128 to 127 Standard 8-bit signed integer, widely used in quantization","health":"warn","tokens":6231,"nodes":1424,"depth":10,"loss":0,"tcErrors":8,"failedBackends":[],"outBytes":{"c":15229,"rust":7199,"verilog":32292,"verilog_hir":3500,"zig":16382}},{"path":"chips/euler/specs/numeric/nf4.t27","category":"chips/euler","name":"nf4","module":"triformat-nf4","lines":839,"bytes":23771,"description":"nf4.t27 — NormalFloat4 Quantization 4-bit quantization based on normalized distribution Values: {-1, -0.667, -0.333, 0, 0.333, 0.667, 1, 0} (8 levels + zero)","health":"warn","tokens":4503,"nodes":882,"depth":9,"loss":0,"tcErrors":3,"failedBackends":[],"outBytes":{"c":10738,"rust":3543,"verilog":24108,"verilog_hir":1608,"zig":11868}},{"path":"chips/euler/specs/numeric/phi_ratio.t27","category":"chips/euler","name":"phi_ratio","module":"PhiRatio","lines":695,"bytes":25855,"description":"t27/specs/numeric/phi_ratio.t27 0-Ratio Proof 1 Derivation of GoldenFloat exp/mantissa split NUMERIC-STANDARD-001 2 Agent 9 (P0)","health":"ok","tokens":2585,"nodes":592,"depth":12,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":12153,"rust":4273,"verilog":21805,"verilog_hir":1108,"zig":17217}},{"path":"chips/euler/specs/numeric/posit16.t27","category":"chips/euler","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004,"description":"posit16.t27 — Posit Type 16 (Type-2 with ES=1, unum 1.0 format) 16-bit posit format with 1 exponent bit (ES=1) and 0 useed bits Alternative name: posit<16,1> Range: ~-3.8e4 to ~3.8e4, precision: ~1-2 significant bits at extremes, ~10 at 1.0","health":"warn","tokens":5421,"nodes":1544,"depth":13,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":17804,"rust":10661,"verilog":32115,"verilog_hir":2131,"zig":17940}},{"path":"chips/euler/specs/numeric/tri_net_formats.t27","category":"chips/euler","name":"tri_net_formats","module":"TriNetFormats","lines":993,"bytes":32170,"description":"t27/specs/numeric/tri_net_formats.t27 TRI NET Format Registry — Complete Format Specification Complete format registry for TRI-NET neural accelerator: - GoldenFloat family (GF4-GF256) - IEEE 754 formats (fp32, fp16) - Brain Float (bf16)","health":"warn","tokens":4158,"nodes":1029,"depth":15,"loss":0,"tcErrors":4,"failedBackends":[],"outBytes":{"c":19414,"rust":15653,"verilog":34212,"verilog_hir":1394,"zig":21098}},{"path":"chips/gamma/specs/fpga/avs_controller_48.t27","category":"chips/gamma","name":"avs_controller_48","module":"avs-controller-48","lines":607,"bytes":20060,"description":"avs_controller_48.t27 — 48-pin AVS Controller Adaptive voltage scaling controller for 48-pin configuration","health":"ok","tokens":2944,"nodes":877,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":12673,"rust":5671,"verilog":19133,"verilog_hir":1898,"zig":11618}},{"path":"chips/gamma/specs/fpga/avs_controller_96.t27","category":"chips/gamma","name":"avs_controller_96","module":"avs-controller-96","lines":735,"bytes":24836,"description":"avs_controller_96.t27 — 96-pin AVS Controller Adaptive voltage scaling controller for 96-pin configuration","health":"warn","tokens":3521,"nodes":1085,"depth":9,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":16185,"rust":7908,"verilog":23298,"verilog_hir":2373,"zig":14936}},{"path":"chips/gamma/specs/fpga/avs_reconf.t27","category":"chips/gamma","name":"avs_reconf","module":"avs-reconf","lines":702,"bytes":24801,"description":"avs_reconf.t27 — AVS Reconfiguration Module Dynamic AVS voltage/frequency reconfiguration support","health":"ok","tokens":3542,"nodes":1045,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":15559,"rust":8600,"verilog":21090,"verilog_hir":1972,"zig":15599}},{"path":"chips/gamma/specs/fpga/dfs_gate.t27","category":"chips/gamma","name":"dfs_gate","module":"sacred-dfs_gate","lines":600,"bytes":22017,"description":"dfs_gate.t27 — Sacred Opcode 0xE7: Depth-First Search Gate Hardware acceleration for DFS traversal and pattern matching","health":"ok","tokens":3969,"nodes":675,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9082,"rust":4335,"verilog":15468,"verilog_hir":2066,"zig":9770}},{"path":"chips/gamma/specs/fpga/drowsy_ret.t27","category":"chips/gamma","name":"drowsy_ret","module":"sacred-drowsy_ret","lines":716,"bytes":26247,"description":null,"health":"ok","tokens":4100,"nodes":117,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3538,"rust":2657,"verilog":5652,"verilog_hir":604,"zig":2340}},{"path":"chips/gamma/specs/fpga/fbb_active_path.t27","category":"chips/gamma","name":"fbb_active_path","module":"fbb-active-path","lines":756,"bytes":26695,"description":"fbb_active_path.t27 — FPGA Feedback Bridge Active Path Active path management for FPGA feedback bridge","health":"ok","tokens":4002,"nodes":1123,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":16298,"rust":9040,"verilog":22056,"verilog_hir":2461,"zig":15709}},{"path":"chips/gamma/specs/fpga/fp8_e4m3.t27","category":"chips/gamma","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652,"description":"fp8_e4m3.t27 — FP8 E4M3 8-bit Floating Point 8-bit float with 4 exponent, 3 mantissa bits (no implicit leading bit) OCP FP8 format optimized for neural network training Range: ~-240 to ~240, precision: ~6-7 significant bits","health":"warn","tokens":6035,"nodes":1575,"depth":13,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":17636,"rust":9369,"verilog":33188,"verilog_hir":2181,"zig":18818}},{"path":"chips/gamma/specs/fpga/fp8_e5m2.t27","category":"chips/gamma","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826,"description":"fp8_e5m2.t27 — FP8 E5M2 8-bit Floating Point 8-bit float with 5 exponent, 2 mantissa bits (with implicit leading bit) OCP FP8 format optimized for inference and wide dynamic range Range: ~-57k to ~57k, precision: ~3-4 significant bits","health":"warn","tokens":7267,"nodes":1852,"depth":13,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":20951,"rust":10713,"verilog":39194,"verilog_hir":2617,"zig":22296}},{"path":"chips/gamma/specs/fpga/gf128.t27","category":"chips/gamma","name":"gf128","module":"GF128","lines":358,"bytes":10740,"description":"t27/specs/numeric/gf128.t27 GoldenFloat128 - 128-bit φ-structured floating point with extended range NUMERIC-STANDARD-001 Agent 5 (P1) Paper §6.7: Extended range for high-dynamic-range applications","health":"ok","tokens":1470,"nodes":486,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":8811,"rust":4090,"verilog":15830,"verilog_hir":1032,"zig":9858}},{"path":"chips/gamma/specs/fpga/gf16_to_fp16.t27","category":"chips/gamma","name":"gf16_to_fp16","module":"gf16-to-fp16","lines":574,"bytes":18936,"description":null,"health":"fail","tokens":3481,"nodes":973,"depth":14,"loss":0,"tcErrors":3,"failedBackends":["verilog_hir"],"outBytes":{"c":10720,"rust":4673,"verilog":18190,"verilog_hir":null,"zig":10574}},{"path":"chips/gamma/specs/fpga/gf16_to_posit16.t27","category":"chips/gamma","name":"gf16_to_posit16","module":"gf16-to-posit16","lines":664,"bytes":20622,"description":"gf16_to_posit16.t27 — GF16 to Posit16 Converter GoldenFloat16 to Posit type 16 (unum 1.0) format conversion","health":"warn","tokens":3526,"nodes":1045,"depth":13,"loss":0,"tcErrors":12,"failedBackends":[],"outBytes":{"c":11873,"rust":7280,"verilog":20464,"verilog_hir":1859,"zig":11623}},{"path":"chips/gamma/specs/fpga/gf256.t27","category":"chips/gamma","name":"gf256","module":"GF256","lines":336,"bytes":9935,"description":"t27/specs/numeric/gf256.t27 GoldenFloat256 0 256-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 11 (P1)","health":"warn","tokens":1592,"nodes":633,"depth":11,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":8199,"rust":5222,"verilog":13893,"verilog_hir":816,"zig":8540}},{"path":"chips/gamma/specs/fpga/gf32_to_fp32.t27","category":"chips/gamma","name":"gf32_to_fp32","module":"gf32-to-fp32","lines":757,"bytes":25067,"description":"gf32_to_fp32.t27 — GF32 to FP32 Converter GoldenFloat32 to IEEE 754 binary32 format conversion","health":"fail","tokens":4698,"nodes":1275,"depth":15,"loss":0,"tcErrors":8,"failedBackends":["verilog_hir"],"outBytes":{"c":13733,"rust":6171,"verilog":23398,"verilog_hir":null,"zig":13832}},{"path":"chips/gamma/specs/fpga/gf64.t27","category":"chips/gamma","name":"gf64","module":"GF64","lines":320,"bytes":9095,"description":"t27/specs/numeric/gf64.t27 GoldenFloat64 - 64-bit φ-structured floating point NUMERIC-STANDARD-001 Agent 4 (P1)","health":"ok","tokens":1320,"nodes":425,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7523,"rust":3303,"verilog":13143,"verilog_hir":930,"zig":8487}},{"path":"chips/gamma/specs/fpga/holo_mux_x4.t27","category":"chips/gamma","name":"holo_mux_x4","module":"sacred-holo_mux_x4","lines":581,"bytes":21272,"description":"holo_mux_x4.t27 — Sacred Opcode 0xE6: Holographic 4x Multiplexer Hardware multiplexer for holographic data paths with 4-way select","health":"warn","tokens":4128,"nodes":298,"depth":10,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":5628,"rust":4234,"verilog":8727,"verilog_hir":1009,"zig":4301}},{"path":"chips/gamma/specs/fpga/int4.t27","category":"chips/gamma","name":"int4","module":"Int4","lines":334,"bytes":8538,"description":"t27/specs/numeric/int4.t27 Int4 - 4-bit signed integer NUMERIC-STANDARD-001 Agent 9 (P1)","health":"ok","tokens":1655,"nodes":298,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6850,"rust":1962,"verilog":14629,"verilog_hir":953,"zig":8156}},{"path":"chips/gamma/specs/fpga/int8.t27","category":"chips/gamma","name":"int8","module":"Int8","lines":493,"bytes":13029,"description":"t27/specs/numeric/int8.t27 Int8 - 8-bit signed integer NUMERIC-STANDARD-001 Agent 10 (P1)","health":"warn","tokens":2646,"nodes":463,"depth":9,"loss":2,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9729,"rust":2936,"verilog":20392,"verilog_hir":1246,"zig":12222}},{"path":"chips/gamma/specs/fpga/lane_l_precheck.t27","category":"chips/gamma","name":"lane_l_precheck","module":"sacred-lane_precheck","lines":493,"bytes":17229,"description":"lane_l_precheck.t27 — Sacred Opcode 0xDF: LUT Lookup Precheck Hardware pre-check for LUT lookup operations Validates lane readiness, checks LUT access permissions, and prepares address","health":"warn","tokens":3044,"nodes":31,"depth":3,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1319,"rust":737,"verilog":2233,"verilog_hir":269,"zig":644}},{"path":"chips/gamma/specs/fpga/lut_npu_81_entry.t27","category":"chips/gamma","name":"lut_npu_81_entry","module":"sacred-lut_npu_81_entry","lines":419,"bytes":13484,"description":"lut_npu_81_entry.t27 — Sacred Opcode 0xE3: LUT NPU 81-Entry Lookup Hardware LUT for NPU operations with 81 entries (9×9 transform)","health":"warn","tokens":2475,"nodes":273,"depth":13,"loss":1,"tcErrors":1,"failedBackends":[],"outBytes":{"c":5072,"rust":3157,"verilog":7761,"verilog_hir":1120,"zig":3540}},{"path":"chips/gamma/specs/fpga/nf4.t27","category":"chips/gamma","name":"nf4","module":"NF4","lines":313,"bytes":8164,"description":"t27/specs/numeric/nf4.t27 NormalFloat4 - 4-bit normalized float quantization format (Google) NUMERIC-STANDARD-001 Agent 11 (P1) NF4 format: - Represents normalized values in [0, 1] - Uses a 4-bit normalized representation","health":"ok","tokens":1284,"nodes":261,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6543,"rust":1693,"verilog":13183,"verilog_hir":914,"zig":7705}},{"path":"chips/gamma/specs/fpga/null_pe.t27","category":"chips/gamma","name":"null_pe","module":"sacred-null_pe","lines":568,"bytes":20320,"description":"null_pe.t27 — Sacred Opcode 0xEA: Null Processing Element Hardware null PE (processing element) for sparse acceleration","health":"warn","tokens":3694,"nodes":660,"depth":11,"loss":1,"tcErrors":4,"failedBackends":[],"outBytes":{"c":9376,"rust":5003,"verilog":14296,"verilog_hir":1369,"zig":8482}},{"path":"chips/gamma/specs/fpga/posit16.t27","category":"chips/gamma","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004,"description":"posit16.t27 — Posit Type 16 (Type-2 with ES=1, unum 1.0 format) 16-bit posit format with 1 exponent bit (ES=1) and 0 useed bits Alternative name: posit<16,1> Range: ~-3.8e4 to ~3.8e4, precision: ~1-2 significant bits at extremes, ~10 at 1.0","health":"warn","tokens":5421,"nodes":1544,"depth":13,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":17804,"rust":10661,"verilog":32115,"verilog_hir":2131,"zig":17940}},{"path":"chips/gamma/specs/fpga/purkinje_thermal_gate.t27","category":"chips/gamma","name":"purkinje_thermal_gate","module":"purkinje-thermal-gate","lines":704,"bytes":24460,"description":"purkinje_thermal_gate.t27 — Purkinje Thermal Gate Thermally-gated activation inspired by Purkinje neural dynamics","health":"ok","tokens":3682,"nodes":1063,"depth":13,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":14852,"rust":6797,"verilog":20709,"verilog_hir":1880,"zig":14728}},{"path":"chips/gamma/specs/fpga/sparse_mask.t27","category":"chips/gamma","name":"sparse_mask","module":"sacred-sparse_mask","lines":556,"bytes":17492,"description":"sparse_mask.t27 — Sacred Opcode 0xE8: Sparse Mask (Sparse Skip 2) Hardware for generating and applying sparse tensor masks","health":"warn","tokens":3158,"nodes":355,"depth":11,"loss":0,"tcErrors":4,"failedBackends":[],"outBytes":{"c":5066,"rust":3252,"verilog":8130,"verilog_hir":1468,"zig":4013}},{"path":"chips/gamma/specs/fpga/sparse_skip.t27","category":"chips/gamma","name":"sparse_skip","module":"sacred-sparse_skip","lines":476,"bytes":15117,"description":"sparse_skip.t27 — Sacred Opcode 0xE1: Sparse Skip Operation Hardware acceleration for sparse tensor operations with zero-skipping","health":"warn","tokens":2985,"nodes":630,"depth":10,"loss":0,"tcErrors":3,"failedBackends":[],"outBytes":{"c":8360,"rust":4128,"verilog":13457,"verilog_hir":1162,"zig":8132}},{"path":"chips/gamma/specs/fpga/spec_exit.t27","category":"chips/gamma","name":"spec_exit","module":"sacred-spec_exit","lines":541,"bytes":19745,"description":"spec_exit.t27 — Sacred Opcode 0xEB: Speculative Exit Hardware for speculative exit and recovery","health":"ok","tokens":3140,"nodes":730,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":11376,"rust":5841,"verilog":16952,"verilog_hir":1700,"zig":10561}},{"path":"chips/gamma/specs/fpga/stoch_round.t27","category":"chips/gamma","name":"stoch_round","module":"sacred-stoch_round","lines":575,"bytes":19164,"description":"stoch_round.t27 — Sacred Opcode 0xE9: Stochastic Rounding Hardware stochastic rounding for quantization","health":"warn","tokens":3632,"nodes":1074,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":11522,"rust":6855,"verilog":18337,"verilog_hir":1313,"zig":11697}},{"path":"chips/gamma/specs/fpga/subth_clk.t27","category":"chips/gamma","name":"subth_clk","module":"sacred-subth_clk","lines":478,"bytes":17539,"description":"subth_clk.t27 — Sacred Opcode 0xE5: Sub-threshold Clock Gating Hardware for sub-threshold clock gating for power reduction","health":"warn","tokens":2862,"nodes":646,"depth":9,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":10177,"rust":4457,"verilog":17524,"verilog_hir":1438,"zig":10580}},{"path":"chips/gamma/specs/numeric/binary16.t27","category":"chips/gamma","name":"binary16","module":"Binary16","lines":420,"bytes":12171,"description":"t27/specs/numeric/binary16.t27 Binary16 - Binary packed 16-bit format (3 bits per integer) NUMERIC-STANDARD-001 Agent 13 (P1) Binary16 format: - Packs 5 signed integers (3 bits each) into 16 bits - Each integer range: -4 to 3","health":"warn","tokens":2331,"nodes":446,"depth":9,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":9115,"rust":2957,"verilog":19728,"verilog_hir":1098,"zig":10826}},{"path":"chips/gamma/specs/numeric/formats.t27","category":"chips/gamma","name":"formats","module":"Formats","lines":538,"bytes":17523,"description":"specs/numeric/formats.t27 Format Conversion Utilities - GF16, f32, ternary encoding","health":"ok","tokens":1601,"nodes":224,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6234,"rust":1013,"verilog":15020,"verilog_hir":698,"zig":8733}},{"path":"chips/gamma/specs/numeric/fp8_e4m3.t27","category":"chips/gamma","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652,"description":"fp8_e4m3.t27 — FP8 E4M3 8-bit Floating Point 8-bit float with 4 exponent, 3 mantissa bits (no implicit leading bit) OCP FP8 format optimized for neural network training Range: ~-240 to ~240, precision: ~6-7 significant bits","health":"warn","tokens":6035,"nodes":1575,"depth":13,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":17636,"rust":9369,"verilog":33188,"verilog_hir":2181,"zig":18818}},{"path":"chips/gamma/specs/numeric/fp8_e5m2.t27","category":"chips/gamma","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826,"description":"fp8_e5m2.t27 — FP8 E5M2 8-bit Floating Point 8-bit float with 5 exponent, 2 mantissa bits (with implicit leading bit) OCP FP8 format optimized for inference and wide dynamic range Range: ~-57k to ~57k, precision: ~3-4 significant bits","health":"warn","tokens":7267,"nodes":1852,"depth":13,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":20951,"rust":10713,"verilog":39194,"verilog_hir":2617,"zig":22296}},{"path":"chips/gamma/specs/numeric/gf12.t27","category":"chips/gamma","name":"gf12","module":"GF12","lines":482,"bytes":16369,"description":"t27/specs/numeric/gf12.t27 GoldenFloat12 0 12-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 4 (P1)","health":"warn","tokens":1979,"nodes":647,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9881,"rust":4274,"verilog":19274,"verilog_hir":1059,"zig":11436}},{"path":"chips/gamma/specs/numeric/gf128.t27","category":"chips/gamma","name":"gf128","module":"triformat-gf128","lines":506,"bytes":17137,"description":"gf128.t27 — GoldenFloat128 Encode/Decode GF128: 128-bit floating point with 1 sign + 28 exponent + 99 mantissa Bit layout: [S(1) E(28) M(99)] = [127:127][126:99][98:0] Extended range format for high-precision scientific computing","health":"ok","tokens":2514,"nodes":762,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9715,"rust":6413,"verilog":16944,"verilog_hir":1753,"zig":9067}},{"path":"chips/gamma/specs/numeric/gf16.t27","category":"chips/gamma","name":"gf16","module":"triformat-gf16","lines":3437,"bytes":101513,"description":"gf16.t27 0 GoldenFloat16 Encode/Decode GF16: 16-bit floating point with 1 sign + 6 exponent + 9 mantissa Bit layout: [S(1) E(6) M(9)] = [15:15][14:9][8:0] 12 + 1/34 = 3 | TRINITY","health":"ok","tokens":16290,"nodes":6744,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":88131,"rust":19607,"verilog":169609,"verilog_hir":2914,"zig":83320}},{"path":"chips/gamma/specs/numeric/gf20.t27","category":"chips/gamma","name":"gf20","module":"GF20","lines":469,"bytes":15992,"description":"t27/specs/numeric/gf20.t27 GoldenFloat20 0 20-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 6 (P1)","health":"warn","tokens":1927,"nodes":639,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9537,"rust":4297,"verilog":18742,"verilog_hir":1062,"zig":10908}},{"path":"chips/gamma/specs/numeric/gf24.t27","category":"chips/gamma","name":"gf24","module":"GF24","lines":469,"bytes":16040,"description":"t27/specs/numeric/gf24.t27 GoldenFloat24 0 24-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 7 (P1)","health":"warn","tokens":1933,"nodes":640,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9579,"rust":4328,"verilog":18783,"verilog_hir":1074,"zig":10948}},{"path":"chips/gamma/specs/numeric/gf256.t27","category":"chips/gamma","name":"gf256","module":"triformat-gf256","lines":595,"bytes":20164,"description":"gf256.t27 — GoldenFloat256 Encode/Decode GF256: 256-bit floating point with 1 sign + 32 exponent + 223 mantissa Bit layout: [S(1) E(32) M(223)] = [255:255][254:223][222:0] Maximum precision format for scientific computing and simulation","health":"ok","tokens":3250,"nodes":1094,"depth":13,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":11484,"rust":7211,"verilog":20030,"verilog_hir":2273,"zig":10948}},{"path":"chips/gamma/specs/numeric/gf32.t27","category":"chips/gamma","name":"gf32","module":"GF32","lines":480,"bytes":16589,"description":"t27/specs/numeric/gf32.t27 GoldenFloat32 0 32-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 8 (P1)","health":"warn","tokens":1934,"nodes":641,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9779,"rust":4324,"verilog":18871,"verilog_hir":1074,"zig":11206}},{"path":"chips/gamma/specs/numeric/gf4.t27","category":"chips/gamma","name":"gf4","module":"GF4","lines":306,"bytes":11167,"description":"t27/specs/numeric/gf4.t27 GoldenFloat4 0 4-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 2 (P1)","health":"ok","tokens":1017,"nodes":265,"depth":14,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5819,"rust":1237,"verilog":10517,"verilog_hir":532,"zig":7261}},{"path":"chips/gamma/specs/numeric/gf64.t27","category":"chips/gamma","name":"gf64","module":"triformat-gf64","lines":764,"bytes":24203,"description":"gf64.t27 — GoldenFloat64 Encode/Decode GF64: 64-bit floating point with 1 sign + 18 exponent + 45 mantissa Bit layout: [S(1) E(18) M(45)] = [63:63][62:45][44:0]","health":"warn","tokens":4384,"nodes":1156,"depth":14,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":13085,"rust":6818,"verilog":25023,"verilog_hir":1810,"zig":14002}},{"path":"chips/gamma/specs/numeric/gf8.t27","category":"chips/gamma","name":"gf8","module":"GF8","lines":522,"bytes":17732,"description":"t27/specs/numeric/gf8.t27 GoldenFloat8 0 8-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 3 (P1)","health":"warn","tokens":2153,"nodes":661,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":10178,"rust":4222,"verilog":20381,"verilog_hir":1057,"zig":12139}},{"path":"chips/gamma/specs/numeric/goldenfloat_family.t27","category":"chips/gamma","name":"goldenfloat_family","module":"GoldenFloatFamily","lines":450,"bytes":17346,"description":"t27/specs/numeric/goldenfloat_family.t27 GoldenFloat Family 0 1-structured floating point formats NUMERIC-STANDARD-001 2 Agent 1 (P0)","health":"warn","tokens":1793,"nodes":356,"depth":11,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":10804,"rust":4339,"verilog":19539,"verilog_hir":686,"zig":12751}},{"path":"chips/gamma/specs/numeric/int4.t27","category":"chips/gamma","name":"int4","module":"triformat-int4","lines":766,"bytes":20533,"description":"int4.t27 — Int4 Signed 4-bit Integer Quantization Range: -8 to 7 Used for ultra-low precision quantization in ML","health":"ok","tokens":4078,"nodes":850,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9763,"rust":3801,"verilog":22124,"verilog_hir":2099,"zig":10462}},{"path":"chips/gamma/specs/numeric/int8.t27","category":"chips/gamma","name":"int8","module":"triformat-int8","lines":1151,"bytes":30614,"description":"int8.t27 — Int8 Signed 8-bit Integer Quantization Range: -128 to 127 Standard 8-bit signed integer, widely used in quantization","health":"warn","tokens":6231,"nodes":1424,"depth":10,"loss":0,"tcErrors":8,"failedBackends":[],"outBytes":{"c":15229,"rust":7199,"verilog":32292,"verilog_hir":3500,"zig":16382}},{"path":"chips/gamma/specs/numeric/nf4.t27","category":"chips/gamma","name":"nf4","module":"triformat-nf4","lines":839,"bytes":23771,"description":"nf4.t27 — NormalFloat4 Quantization 4-bit quantization based on normalized distribution Values: {-1, -0.667, -0.333, 0, 0.333, 0.667, 1, 0} (8 levels + zero)","health":"warn","tokens":4503,"nodes":882,"depth":9,"loss":0,"tcErrors":3,"failedBackends":[],"outBytes":{"c":10738,"rust":3543,"verilog":24108,"verilog_hir":1608,"zig":11868}},{"path":"chips/gamma/specs/numeric/phi_ratio.t27","category":"chips/gamma","name":"phi_ratio","module":"PhiRatio","lines":695,"bytes":25855,"description":"t27/specs/numeric/phi_ratio.t27 0-Ratio Proof 1 Derivation of GoldenFloat exp/mantissa split NUMERIC-STANDARD-001 2 Agent 9 (P0)","health":"ok","tokens":2585,"nodes":592,"depth":12,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":12153,"rust":4273,"verilog":21805,"verilog_hir":1108,"zig":17217}},{"path":"chips/gamma/specs/numeric/posit16.t27","category":"chips/gamma","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004,"description":"posit16.t27 — Posit Type 16 (Type-2 with ES=1, unum 1.0 format) 16-bit posit format with 1 exponent bit (ES=1) and 0 useed bits Alternative name: posit<16,1> Range: ~-3.8e4 to ~3.8e4, precision: ~1-2 significant bits at extremes, ~10 at 1.0","health":"warn","tokens":5421,"nodes":1544,"depth":13,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":17804,"rust":10661,"verilog":32115,"verilog_hir":2131,"zig":17940}},{"path":"chips/gamma/specs/numeric/tri_net_formats.t27","category":"chips/gamma","name":"tri_net_formats","module":"TriNetFormats","lines":993,"bytes":32170,"description":"t27/specs/numeric/tri_net_formats.t27 TRI NET Format Registry — Complete Format Specification Complete format registry for TRI-NET neural accelerator: - GoldenFloat family (GF4-GF256) - IEEE 754 formats (fp32, fp16) - Brain Float (bf16)","health":"warn","tokens":4158,"nodes":1029,"depth":15,"loss":0,"tcErrors":4,"failedBackends":[],"outBytes":{"c":19414,"rust":15653,"verilog":34212,"verilog_hir":1394,"zig":21098}},{"path":"chips/phi/specs/fpga/avs_controller_48.t27","category":"chips/phi","name":"avs_controller_48","module":"avs-controller-48","lines":607,"bytes":20060,"description":"avs_controller_48.t27 — 48-pin AVS Controller Adaptive voltage scaling controller for 48-pin configuration","health":"ok","tokens":2944,"nodes":877,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":12673,"rust":5671,"verilog":19133,"verilog_hir":1898,"zig":11618}},{"path":"chips/phi/specs/fpga/avs_controller_96.t27","category":"chips/phi","name":"avs_controller_96","module":"avs-controller-96","lines":735,"bytes":24836,"description":"avs_controller_96.t27 — 96-pin AVS Controller Adaptive voltage scaling controller for 96-pin configuration","health":"warn","tokens":3521,"nodes":1085,"depth":9,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":16185,"rust":7908,"verilog":23298,"verilog_hir":2373,"zig":14936}},{"path":"chips/phi/specs/fpga/avs_reconf.t27","category":"chips/phi","name":"avs_reconf","module":"avs-reconf","lines":702,"bytes":24801,"description":"avs_reconf.t27 — AVS Reconfiguration Module Dynamic AVS voltage/frequency reconfiguration support","health":"ok","tokens":3542,"nodes":1045,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":15559,"rust":8600,"verilog":21090,"verilog_hir":1972,"zig":15599}},{"path":"chips/phi/specs/fpga/dfs_gate.t27","category":"chips/phi","name":"dfs_gate","module":"sacred-dfs_gate","lines":600,"bytes":22017,"description":"dfs_gate.t27 — Sacred Opcode 0xE7: Depth-First Search Gate Hardware acceleration for DFS traversal and pattern matching","health":"ok","tokens":3969,"nodes":675,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9082,"rust":4335,"verilog":15468,"verilog_hir":2066,"zig":9770}},{"path":"chips/phi/specs/fpga/drowsy_ret.t27","category":"chips/phi","name":"drowsy_ret","module":"sacred-drowsy_ret","lines":716,"bytes":26247,"description":null,"health":"ok","tokens":4100,"nodes":117,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3538,"rust":2657,"verilog":5652,"verilog_hir":604,"zig":2340}},{"path":"chips/phi/specs/fpga/fbb_active_path.t27","category":"chips/phi","name":"fbb_active_path","module":"fbb-active-path","lines":756,"bytes":26695,"description":"fbb_active_path.t27 — FPGA Feedback Bridge Active Path Active path management for FPGA feedback bridge","health":"ok","tokens":4002,"nodes":1123,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":16298,"rust":9040,"verilog":22056,"verilog_hir":2461,"zig":15709}},{"path":"chips/phi/specs/fpga/fp8_e4m3.t27","category":"chips/phi","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652,"description":"fp8_e4m3.t27 — FP8 E4M3 8-bit Floating Point 8-bit float with 4 exponent, 3 mantissa bits (no implicit leading bit) OCP FP8 format optimized for neural network training Range: ~-240 to ~240, precision: ~6-7 significant bits","health":"warn","tokens":6035,"nodes":1575,"depth":13,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":17636,"rust":9369,"verilog":33188,"verilog_hir":2181,"zig":18818}},{"path":"chips/phi/specs/fpga/fp8_e5m2.t27","category":"chips/phi","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826,"description":"fp8_e5m2.t27 — FP8 E5M2 8-bit Floating Point 8-bit float with 5 exponent, 2 mantissa bits (with implicit leading bit) OCP FP8 format optimized for inference and wide dynamic range Range: ~-57k to ~57k, precision: ~3-4 significant bits","health":"warn","tokens":7267,"nodes":1852,"depth":13,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":20951,"rust":10713,"verilog":39194,"verilog_hir":2617,"zig":22296}},{"path":"chips/phi/specs/fpga/gf128.t27","category":"chips/phi","name":"gf128","module":"GF128","lines":358,"bytes":10740,"description":"t27/specs/numeric/gf128.t27 GoldenFloat128 - 128-bit φ-structured floating point with extended range NUMERIC-STANDARD-001 Agent 5 (P1) Paper §6.7: Extended range for high-dynamic-range applications","health":"ok","tokens":1470,"nodes":486,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":8811,"rust":4090,"verilog":15830,"verilog_hir":1032,"zig":9858}},{"path":"chips/phi/specs/fpga/gf16_to_fp16.t27","category":"chips/phi","name":"gf16_to_fp16","module":"gf16-to-fp16","lines":574,"bytes":18936,"description":null,"health":"fail","tokens":3481,"nodes":973,"depth":14,"loss":0,"tcErrors":3,"failedBackends":["verilog_hir"],"outBytes":{"c":10720,"rust":4673,"verilog":18190,"verilog_hir":null,"zig":10574}},{"path":"chips/phi/specs/fpga/gf16_to_posit16.t27","category":"chips/phi","name":"gf16_to_posit16","module":"gf16-to-posit16","lines":664,"bytes":20622,"description":"gf16_to_posit16.t27 — GF16 to Posit16 Converter GoldenFloat16 to Posit type 16 (unum 1.0) format conversion","health":"warn","tokens":3526,"nodes":1045,"depth":13,"loss":0,"tcErrors":12,"failedBackends":[],"outBytes":{"c":11873,"rust":7280,"verilog":20464,"verilog_hir":1859,"zig":11623}},{"path":"chips/phi/specs/fpga/gf256.t27","category":"chips/phi","name":"gf256","module":"GF256","lines":336,"bytes":9935,"description":"t27/specs/numeric/gf256.t27 GoldenFloat256 0 256-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 11 (P1)","health":"warn","tokens":1592,"nodes":633,"depth":11,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":8199,"rust":5222,"verilog":13893,"verilog_hir":816,"zig":8540}},{"path":"chips/phi/specs/fpga/gf32_to_fp32.t27","category":"chips/phi","name":"gf32_to_fp32","module":"gf32-to-fp32","lines":757,"bytes":25067,"description":"gf32_to_fp32.t27 — GF32 to FP32 Converter GoldenFloat32 to IEEE 754 binary32 format conversion","health":"fail","tokens":4698,"nodes":1275,"depth":15,"loss":0,"tcErrors":8,"failedBackends":["verilog_hir"],"outBytes":{"c":13733,"rust":6171,"verilog":23398,"verilog_hir":null,"zig":13832}},{"path":"chips/phi/specs/fpga/gf64.t27","category":"chips/phi","name":"gf64","module":"GF64","lines":320,"bytes":9095,"description":"t27/specs/numeric/gf64.t27 GoldenFloat64 - 64-bit φ-structured floating point NUMERIC-STANDARD-001 Agent 4 (P1)","health":"ok","tokens":1320,"nodes":425,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7523,"rust":3303,"verilog":13143,"verilog_hir":930,"zig":8487}},{"path":"chips/phi/specs/fpga/holo_mux_x4.t27","category":"chips/phi","name":"holo_mux_x4","module":"sacred-holo_mux_x4","lines":581,"bytes":21272,"description":"holo_mux_x4.t27 — Sacred Opcode 0xE6: Holographic 4x Multiplexer Hardware multiplexer for holographic data paths with 4-way select","health":"warn","tokens":4128,"nodes":298,"depth":10,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":5628,"rust":4234,"verilog":8727,"verilog_hir":1009,"zig":4301}},{"path":"chips/phi/specs/fpga/int4.t27","category":"chips/phi","name":"int4","module":"Int4","lines":334,"bytes":8538,"description":"t27/specs/numeric/int4.t27 Int4 - 4-bit signed integer NUMERIC-STANDARD-001 Agent 9 (P1)","health":"ok","tokens":1655,"nodes":298,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6850,"rust":1962,"verilog":14629,"verilog_hir":953,"zig":8156}},{"path":"chips/phi/specs/fpga/int8.t27","category":"chips/phi","name":"int8","module":"Int8","lines":493,"bytes":13029,"description":"t27/specs/numeric/int8.t27 Int8 - 8-bit signed integer NUMERIC-STANDARD-001 Agent 10 (P1)","health":"warn","tokens":2646,"nodes":463,"depth":9,"loss":2,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9729,"rust":2936,"verilog":20392,"verilog_hir":1246,"zig":12222}},{"path":"chips/phi/specs/fpga/lane_l_precheck.t27","category":"chips/phi","name":"lane_l_precheck","module":"sacred-lane_precheck","lines":493,"bytes":17229,"description":"lane_l_precheck.t27 — Sacred Opcode 0xDF: LUT Lookup Precheck Hardware pre-check for LUT lookup operations Validates lane readiness, checks LUT access permissions, and prepares address","health":"warn","tokens":3044,"nodes":31,"depth":3,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1319,"rust":737,"verilog":2233,"verilog_hir":269,"zig":644}},{"path":"chips/phi/specs/fpga/lut_npu_81_entry.t27","category":"chips/phi","name":"lut_npu_81_entry","module":"sacred-lut_npu_81_entry","lines":419,"bytes":13484,"description":"lut_npu_81_entry.t27 — Sacred Opcode 0xE3: LUT NPU 81-Entry Lookup Hardware LUT for NPU operations with 81 entries (9×9 transform)","health":"warn","tokens":2475,"nodes":273,"depth":13,"loss":1,"tcErrors":1,"failedBackends":[],"outBytes":{"c":5072,"rust":3157,"verilog":7761,"verilog_hir":1120,"zig":3540}},{"path":"chips/phi/specs/fpga/nf4.t27","category":"chips/phi","name":"nf4","module":"NF4","lines":313,"bytes":8164,"description":"t27/specs/numeric/nf4.t27 NormalFloat4 - 4-bit normalized float quantization format (Google) NUMERIC-STANDARD-001 Agent 11 (P1) NF4 format: - Represents normalized values in [0, 1] - Uses a 4-bit normalized representation","health":"ok","tokens":1284,"nodes":261,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6543,"rust":1693,"verilog":13183,"verilog_hir":914,"zig":7705}},{"path":"chips/phi/specs/fpga/null_pe.t27","category":"chips/phi","name":"null_pe","module":"sacred-null_pe","lines":568,"bytes":20320,"description":"null_pe.t27 — Sacred Opcode 0xEA: Null Processing Element Hardware null PE (processing element) for sparse acceleration","health":"warn","tokens":3694,"nodes":660,"depth":11,"loss":1,"tcErrors":4,"failedBackends":[],"outBytes":{"c":9376,"rust":5003,"verilog":14296,"verilog_hir":1369,"zig":8482}},{"path":"chips/phi/specs/fpga/posit16.t27","category":"chips/phi","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004,"description":"posit16.t27 — Posit Type 16 (Type-2 with ES=1, unum 1.0 format) 16-bit posit format with 1 exponent bit (ES=1) and 0 useed bits Alternative name: posit<16,1> Range: ~-3.8e4 to ~3.8e4, precision: ~1-2 significant bits at extremes, ~10 at 1.0","health":"warn","tokens":5421,"nodes":1544,"depth":13,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":17804,"rust":10661,"verilog":32115,"verilog_hir":2131,"zig":17940}},{"path":"chips/phi/specs/fpga/purkinje_thermal_gate.t27","category":"chips/phi","name":"purkinje_thermal_gate","module":"purkinje-thermal-gate","lines":704,"bytes":24460,"description":"purkinje_thermal_gate.t27 — Purkinje Thermal Gate Thermally-gated activation inspired by Purkinje neural dynamics","health":"ok","tokens":3682,"nodes":1063,"depth":13,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":14852,"rust":6797,"verilog":20709,"verilog_hir":1880,"zig":14728}},{"path":"chips/phi/specs/fpga/sparse_mask.t27","category":"chips/phi","name":"sparse_mask","module":"sacred-sparse_mask","lines":556,"bytes":17492,"description":"sparse_mask.t27 — Sacred Opcode 0xE8: Sparse Mask (Sparse Skip 2) Hardware for generating and applying sparse tensor masks","health":"warn","tokens":3158,"nodes":355,"depth":11,"loss":0,"tcErrors":4,"failedBackends":[],"outBytes":{"c":5066,"rust":3252,"verilog":8130,"verilog_hir":1468,"zig":4013}},{"path":"chips/phi/specs/fpga/sparse_skip.t27","category":"chips/phi","name":"sparse_skip","module":"sacred-sparse_skip","lines":476,"bytes":15117,"description":"sparse_skip.t27 — Sacred Opcode 0xE1: Sparse Skip Operation Hardware acceleration for sparse tensor operations with zero-skipping","health":"warn","tokens":2985,"nodes":630,"depth":10,"loss":0,"tcErrors":3,"failedBackends":[],"outBytes":{"c":8360,"rust":4128,"verilog":13457,"verilog_hir":1162,"zig":8132}},{"path":"chips/phi/specs/fpga/spec_exit.t27","category":"chips/phi","name":"spec_exit","module":"sacred-spec_exit","lines":541,"bytes":19745,"description":"spec_exit.t27 — Sacred Opcode 0xEB: Speculative Exit Hardware for speculative exit and recovery","health":"ok","tokens":3140,"nodes":730,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":11376,"rust":5841,"verilog":16952,"verilog_hir":1700,"zig":10561}},{"path":"chips/phi/specs/fpga/stoch_round.t27","category":"chips/phi","name":"stoch_round","module":"sacred-stoch_round","lines":575,"bytes":19164,"description":"stoch_round.t27 — Sacred Opcode 0xE9: Stochastic Rounding Hardware stochastic rounding for quantization","health":"warn","tokens":3632,"nodes":1074,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":11522,"rust":6855,"verilog":18337,"verilog_hir":1313,"zig":11697}},{"path":"chips/phi/specs/fpga/subth_clk.t27","category":"chips/phi","name":"subth_clk","module":"sacred-subth_clk","lines":478,"bytes":17539,"description":"subth_clk.t27 — Sacred Opcode 0xE5: Sub-threshold Clock Gating Hardware for sub-threshold clock gating for power reduction","health":"warn","tokens":2862,"nodes":646,"depth":9,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":10177,"rust":4457,"verilog":17524,"verilog_hir":1438,"zig":10580}},{"path":"chips/phi/specs/numeric/binary16.t27","category":"chips/phi","name":"binary16","module":"Binary16","lines":420,"bytes":12171,"description":"t27/specs/numeric/binary16.t27 Binary16 - Binary packed 16-bit format (3 bits per integer) NUMERIC-STANDARD-001 Agent 13 (P1) Binary16 format: - Packs 5 signed integers (3 bits each) into 16 bits - Each integer range: -4 to 3","health":"warn","tokens":2331,"nodes":446,"depth":9,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":9115,"rust":2957,"verilog":19728,"verilog_hir":1098,"zig":10826}},{"path":"chips/phi/specs/numeric/formats.t27","category":"chips/phi","name":"formats","module":"Formats","lines":538,"bytes":17523,"description":"specs/numeric/formats.t27 Format Conversion Utilities - GF16, f32, ternary encoding","health":"ok","tokens":1601,"nodes":224,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6234,"rust":1013,"verilog":15020,"verilog_hir":698,"zig":8733}},{"path":"chips/phi/specs/numeric/fp8_e4m3.t27","category":"chips/phi","name":"fp8_e4m3","module":"triformat-fp8_e4m3","lines":1046,"bytes":32652,"description":"fp8_e4m3.t27 — FP8 E4M3 8-bit Floating Point 8-bit float with 4 exponent, 3 mantissa bits (no implicit leading bit) OCP FP8 format optimized for neural network training Range: ~-240 to ~240, precision: ~6-7 significant bits","health":"warn","tokens":6035,"nodes":1575,"depth":13,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":17636,"rust":9369,"verilog":33188,"verilog_hir":2181,"zig":18818}},{"path":"chips/phi/specs/numeric/fp8_e5m2.t27","category":"chips/phi","name":"fp8_e5m2","module":"triformat-fp8_e5m2","lines":1212,"bytes":38826,"description":"fp8_e5m2.t27 — FP8 E5M2 8-bit Floating Point 8-bit float with 5 exponent, 2 mantissa bits (with implicit leading bit) OCP FP8 format optimized for inference and wide dynamic range Range: ~-57k to ~57k, precision: ~3-4 significant bits","health":"warn","tokens":7267,"nodes":1852,"depth":13,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":20951,"rust":10713,"verilog":39194,"verilog_hir":2617,"zig":22296}},{"path":"chips/phi/specs/numeric/gf12.t27","category":"chips/phi","name":"gf12","module":"GF12","lines":482,"bytes":16369,"description":"t27/specs/numeric/gf12.t27 GoldenFloat12 0 12-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 4 (P1)","health":"warn","tokens":1979,"nodes":647,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9881,"rust":4274,"verilog":19274,"verilog_hir":1059,"zig":11436}},{"path":"chips/phi/specs/numeric/gf128.t27","category":"chips/phi","name":"gf128","module":"triformat-gf128","lines":506,"bytes":17137,"description":"gf128.t27 — GoldenFloat128 Encode/Decode GF128: 128-bit floating point with 1 sign + 28 exponent + 99 mantissa Bit layout: [S(1) E(28) M(99)] = [127:127][126:99][98:0] Extended range format for high-precision scientific computing","health":"ok","tokens":2514,"nodes":762,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9715,"rust":6413,"verilog":16944,"verilog_hir":1753,"zig":9067}},{"path":"chips/phi/specs/numeric/gf16.t27","category":"chips/phi","name":"gf16","module":"triformat-gf16","lines":3437,"bytes":101513,"description":"gf16.t27 0 GoldenFloat16 Encode/Decode GF16: 16-bit floating point with 1 sign + 6 exponent + 9 mantissa Bit layout: [S(1) E(6) M(9)] = [15:15][14:9][8:0] 12 + 1/34 = 3 | TRINITY","health":"ok","tokens":16290,"nodes":6744,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":88131,"rust":19607,"verilog":169609,"verilog_hir":2914,"zig":83320}},{"path":"chips/phi/specs/numeric/gf20.t27","category":"chips/phi","name":"gf20","module":"GF20","lines":469,"bytes":15992,"description":"t27/specs/numeric/gf20.t27 GoldenFloat20 0 20-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 6 (P1)","health":"warn","tokens":1927,"nodes":639,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9537,"rust":4297,"verilog":18742,"verilog_hir":1062,"zig":10908}},{"path":"chips/phi/specs/numeric/gf24.t27","category":"chips/phi","name":"gf24","module":"GF24","lines":469,"bytes":16040,"description":"t27/specs/numeric/gf24.t27 GoldenFloat24 0 24-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 7 (P1)","health":"warn","tokens":1933,"nodes":640,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9579,"rust":4328,"verilog":18783,"verilog_hir":1074,"zig":10948}},{"path":"chips/phi/specs/numeric/gf256.t27","category":"chips/phi","name":"gf256","module":"triformat-gf256","lines":595,"bytes":20164,"description":"gf256.t27 — GoldenFloat256 Encode/Decode GF256: 256-bit floating point with 1 sign + 32 exponent + 223 mantissa Bit layout: [S(1) E(32) M(223)] = [255:255][254:223][222:0] Maximum precision format for scientific computing and simulation","health":"ok","tokens":3250,"nodes":1094,"depth":13,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":11484,"rust":7211,"verilog":20030,"verilog_hir":2273,"zig":10948}},{"path":"chips/phi/specs/numeric/gf32.t27","category":"chips/phi","name":"gf32","module":"GF32","lines":480,"bytes":16589,"description":"t27/specs/numeric/gf32.t27 GoldenFloat32 0 32-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 8 (P1)","health":"warn","tokens":1934,"nodes":641,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9779,"rust":4324,"verilog":18871,"verilog_hir":1074,"zig":11206}},{"path":"chips/phi/specs/numeric/gf4.t27","category":"chips/phi","name":"gf4","module":"GF4","lines":306,"bytes":11167,"description":"t27/specs/numeric/gf4.t27 GoldenFloat4 0 4-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 2 (P1)","health":"ok","tokens":1017,"nodes":265,"depth":14,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5819,"rust":1237,"verilog":10517,"verilog_hir":532,"zig":7261}},{"path":"chips/phi/specs/numeric/gf64.t27","category":"chips/phi","name":"gf64","module":"triformat-gf64","lines":764,"bytes":24203,"description":"gf64.t27 — GoldenFloat64 Encode/Decode GF64: 64-bit floating point with 1 sign + 18 exponent + 45 mantissa Bit layout: [S(1) E(18) M(45)] = [63:63][62:45][44:0]","health":"warn","tokens":4384,"nodes":1156,"depth":14,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":13085,"rust":6818,"verilog":25023,"verilog_hir":1810,"zig":14002}},{"path":"chips/phi/specs/numeric/gf8.t27","category":"chips/phi","name":"gf8","module":"GF8","lines":522,"bytes":17732,"description":"t27/specs/numeric/gf8.t27 GoldenFloat8 0 8-bit 1-structured floating point NUMERIC-STANDARD-001 2 Agent 3 (P1)","health":"warn","tokens":2153,"nodes":661,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":10178,"rust":4222,"verilog":20381,"verilog_hir":1057,"zig":12139}},{"path":"chips/phi/specs/numeric/goldenfloat_family.t27","category":"chips/phi","name":"goldenfloat_family","module":"GoldenFloatFamily","lines":450,"bytes":17346,"description":"t27/specs/numeric/goldenfloat_family.t27 GoldenFloat Family 0 1-structured floating point formats NUMERIC-STANDARD-001 2 Agent 1 (P0)","health":"warn","tokens":1793,"nodes":356,"depth":11,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":10804,"rust":4339,"verilog":19539,"verilog_hir":686,"zig":12751}},{"path":"chips/phi/specs/numeric/int4.t27","category":"chips/phi","name":"int4","module":"triformat-int4","lines":766,"bytes":20533,"description":"int4.t27 — Int4 Signed 4-bit Integer Quantization Range: -8 to 7 Used for ultra-low precision quantization in ML","health":"ok","tokens":4078,"nodes":850,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9763,"rust":3801,"verilog":22124,"verilog_hir":2099,"zig":10462}},{"path":"chips/phi/specs/numeric/int8.t27","category":"chips/phi","name":"int8","module":"triformat-int8","lines":1151,"bytes":30614,"description":"int8.t27 — Int8 Signed 8-bit Integer Quantization Range: -128 to 127 Standard 8-bit signed integer, widely used in quantization","health":"warn","tokens":6231,"nodes":1424,"depth":10,"loss":0,"tcErrors":8,"failedBackends":[],"outBytes":{"c":15229,"rust":7199,"verilog":32292,"verilog_hir":3500,"zig":16382}},{"path":"chips/phi/specs/numeric/nf4.t27","category":"chips/phi","name":"nf4","module":"triformat-nf4","lines":839,"bytes":23771,"description":"nf4.t27 — NormalFloat4 Quantization 4-bit quantization based on normalized distribution Values: {-1, -0.667, -0.333, 0, 0.333, 0.667, 1, 0} (8 levels + zero)","health":"warn","tokens":4503,"nodes":882,"depth":9,"loss":0,"tcErrors":3,"failedBackends":[],"outBytes":{"c":10738,"rust":3543,"verilog":24108,"verilog_hir":1608,"zig":11868}},{"path":"chips/phi/specs/numeric/phi_ratio.t27","category":"chips/phi","name":"phi_ratio","module":"PhiRatio","lines":695,"bytes":25855,"description":"t27/specs/numeric/phi_ratio.t27 0-Ratio Proof 1 Derivation of GoldenFloat exp/mantissa split NUMERIC-STANDARD-001 2 Agent 9 (P0)","health":"ok","tokens":2585,"nodes":592,"depth":12,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":12153,"rust":4273,"verilog":21805,"verilog_hir":1108,"zig":17217}},{"path":"chips/phi/specs/numeric/posit16.t27","category":"chips/phi","name":"posit16","module":"triformat-posit16","lines":979,"bytes":32004,"description":"posit16.t27 — Posit Type 16 (Type-2 with ES=1, unum 1.0 format) 16-bit posit format with 1 exponent bit (ES=1) and 0 useed bits Alternative name: posit<16,1> Range: ~-3.8e4 to ~3.8e4, precision: ~1-2 significant bits at extremes, ~10 at 1.0","health":"warn","tokens":5421,"nodes":1544,"depth":13,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":17804,"rust":10661,"verilog":32115,"verilog_hir":2131,"zig":17940}},{"path":"chips/phi/specs/numeric/tri_net_formats.t27","category":"chips/phi","name":"tri_net_formats","module":"TriNetFormats","lines":993,"bytes":32170,"description":"t27/specs/numeric/tri_net_formats.t27 TRI NET Format Registry — Complete Format Specification Complete format registry for TRI-NET neural accelerator: - GoldenFloat family (GF4-GF256) - IEEE 754 formats (fp32, fp16) - Brain Float (bf16)","health":"warn","tokens":4158,"nodes":1029,"depth":15,"loss":0,"tcErrors":4,"failedBackends":[],"outBytes":{"c":19414,"rust":15653,"verilog":34212,"verilog_hir":1394,"zig":21098}},{"path":"compiler/ast.t27","category":"compiler","name":"ast","module":"ast","lines":612,"bytes":18378,"description":"ast.t27 -- Abstract Syntax Tree for TRI-27 Assembly This file defines the AST structure used by the t27 compiler","health":"ok","tokens":2422,"nodes":392,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9914,"rust":10278,"verilog":16169,"verilog_hir":691,"zig":8098}},{"path":"compiler/cli/gen.t27","category":"compiler/cli","name":"gen","module":"gen_commands","lines":512,"bytes":16753,"description":"gen.t27 -- Code Generation with TDD Validation Commands for generating code from t27 specs with TDD enforcement","health":"warn","tokens":2065,"nodes":778,"depth":10,"loss":1,"tcErrors":2,"failedBackends":[],"outBytes":{"c":10437,"rust":10011,"verilog":15627,"verilog_hir":587,"zig":10271}},{"path":"compiler/cli/git.t27","category":"compiler/cli","name":"git","module":"git_commands","lines":617,"bytes":20978,"description":"git.t27 -- Git Integration with Tri Skill Workflow (ADR-002) Commands for git operations with skill validation and issue binding","health":"warn","tokens":2922,"nodes":1039,"depth":13,"loss":1,"tcErrors":1,"failedBackends":[],"outBytes":{"c":13791,"rust":11553,"verilog":20483,"verilog_hir":1350,"zig":12552}},{"path":"compiler/cli/spec.t27","category":"compiler/cli","name":"spec","module":"spec_commands","lines":488,"bytes":16727,"description":"spec.t27 -- Spec Management Commands Commands for creating and managing t27 specs with TDD enforcement","health":"ok","tokens":2155,"nodes":699,"depth":61,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9603,"rust":8315,"verilog":13800,"verilog_hir":648,"zig":8781}},{"path":"compiler/codegen/c/codegen.t27","category":"compiler/codegen","name":"codegen","module":"tricgen-c","lines":274,"bytes":7080,"description":"compiler/codegen/c/codegen.t27 -- C Code Generator Specification Emit C code from t27 AST","health":"warn","tokens":784,"nodes":198,"depth":8,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3979,"rust":1289,"verilog":7731,"verilog_hir":247,"zig":4271}},{"path":"compiler/codegen/testgen.t27","category":"compiler/codegen","name":"testgen","module":"testgen","lines":910,"bytes":36448,"description":"testgen.t27 -- Generic Test Generator for TDD-Inside-Spec Generates test code from spec test blocks for multiple backends","health":"warn","tokens":5455,"nodes":1622,"depth":19,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2508,"rust":2476,"verilog":5560,"verilog_hir":243,"zig":23357}},{"path":"compiler/codegen/verilog/codegen.t27","category":"compiler/codegen","name":"codegen","module":"verilog_codegen","lines":1068,"bytes":228219,"description":"codegen.t27 0 Code Generator for Verilog Generates synthesizable Verilog from t27 AST","health":"warn","tokens":6036,"nodes":602,"depth":11,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1608,"rust":715,"verilog":3257,"verilog_hir":391,"zig":7686}},{"path":"compiler/codegen/verilog/fpga_emission.t27","category":"compiler/codegen","name":"fpga_emission","module":"fpga_emission","lines":2359,"bytes":86803,"description":"fpga_emission.t27 0 FPGA Module Verilog Emission Generates FPGA-specific Verilog modules from .t27 specs","health":"warn","tokens":14509,"nodes":4844,"depth":13,"loss":1,"tcErrors":21,"failedBackends":[],"outBytes":{"c":63397,"rust":61436,"verilog":79066,"verilog_hir":2432,"zig":61764}},{"path":"compiler/codegen/zig/codegen.t27","category":"compiler/codegen","name":"codegen","module":"zig_codegen","lines":1517,"bytes":54679,"description":"codegen.t27 -- Code Generator for Zig Generates Zig 0.15 code from t27 AST","health":"warn","tokens":8455,"nodes":2024,"depth":15,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":20534,"rust":13610,"verilog":28859,"verilog_hir":2070,"zig":25822}},{"path":"compiler/codegen/zig/runtime.t27","category":"compiler/codegen","name":"runtime","module":"zig_runtime","lines":410,"bytes":20013,"description":"runtime.t27 -- Zig Runtime Code Generation Generates Zig backend from compiler/runtime/*.t27 specifications","health":"warn","tokens":1321,"nodes":587,"depth":118,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":12291,"rust":11443,"verilog":13515,"verilog_hir":625,"zig":11469}},{"path":"compiler/parser/lexer.t27","category":"compiler/parser","name":"lexer","module":"trilexer","lines":598,"bytes":18435,"description":"compiler/parser/lexer.t27 -- Lexer for TRI-27 Assembly Tokenizes source code into Token stream for parser","health":"warn","tokens":3300,"nodes":718,"depth":14,"loss":1,"tcErrors":2,"failedBackends":[],"outBytes":{"c":5277,"rust":2287,"verilog":7915,"verilog_hir":317,"zig":8087}},{"path":"compiler/parser/parser.t27","category":"compiler/parser","name":"parser","module":"parser","lines":1295,"bytes":49454,"description":"parser.t27 -- Parser for TRI-27 Assembly Builds AST from tokens produced by lexer","health":"warn","tokens":7701,"nodes":2776,"depth":24,"loss":2,"tcErrors":3,"failedBackends":[],"outBytes":{"c":36078,"rust":31904,"verilog":46522,"verilog_hir":3119,"zig":32837}},{"path":"compiler/runtime/commands.t27","category":"compiler/runtime","name":"commands","module":"commands","lines":1040,"bytes":35002,"description":"commands.t27 -- CLI Command Specifications Individual command specifications for tri CLI","health":"warn","tokens":4833,"nodes":781,"depth":12,"loss":2,"tcErrors":0,"failedBackends":[],"outBytes":{"c":10610,"rust":7609,"verilog":15909,"verilog_hir":972,"zig":9311}},{"path":"compiler/runtime/runtime.t27","category":"compiler/runtime","name":"runtime","module":"triruntime","lines":340,"bytes":11466,"description":"compiler/runtime/runtime.t27 -- T27 Runtime Specification Runtime environment for executing t27 programs","health":"warn","tokens":1603,"nodes":315,"depth":10,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5411,"rust":3029,"verilog":8428,"verilog_hir":2032,"zig":3941}},{"path":"compiler/runtime/validation.t27","category":"compiler/runtime","name":"validation","module":"validation_rules","lines":515,"bytes":20567,"description":"validation.t27 -- Validation Rules and Invariants TDD and language policy validation for t27 specs","health":"ok","tokens":2858,"nodes":544,"depth":15,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":8310,"rust":6578,"verilog":10887,"verilog_hir":1276,"zig":7732}},{"path":"compiler/skill/registry.t27","category":"compiler/skill","name":"registry","module":"skill_registry","lines":313,"bytes":11026,"description":"registry.t27 -- Skill Registry JSON Structure (ADR-002) Defines the structure for tri skill workflow registry","health":"ok","tokens":1009,"nodes":126,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3298,"rust":2508,"verilog":4648,"verilog_hir":534,"zig":2093}},{"path":"contrib/backend/zig/legacy/main_zig_handwritten.t27","category":"contrib/backend","name":"main_zig_handwritten","module":null,"lines":1126,"bytes":36956,"description":"tri.zig -- Trinity T27 CLI Runtime","health":"ok","tokens":6410,"nodes":158,"depth":16,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2140,"rust":1421,"verilog":3678,"verilog_hir":345,"zig":2677}},{"path":"examples/fpga/qmtech_minimal/design.t27","category":"examples/fpga","name":"design","module":null,"lines":150,"bytes":3670,"description":null,"health":"warn","tokens":468,"nodes":5,"depth":2,"loss":25,"tcErrors":0,"failedBackends":[],"outBytes":{"c":823,"rust":66,"verilog":1597,"verilog_hir":243,"zig":293}},{"path":"specs/account/auth.t27","category":"specs/account","name":"auth","module":"AccountAuth","lines":278,"bytes":9231,"description":"specs/account/auth.t27 Account Authentication Operations","health":"ok","tokens":961,"nodes":351,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7341,"rust":2793,"verilog":12440,"verilog_hir":807,"zig":6990}},{"path":"specs/account/repo.t27","category":"specs/account","name":"repo","module":"AccountRepo","lines":197,"bytes":5474,"description":"specs/account/repo.t27 Account Repository Operations","health":"ok","tokens":741,"nodes":246,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5928,"rust":2632,"verilog":9190,"verilog_hir":787,"zig":5271}},{"path":"specs/account/schema.t27","category":"specs/account","name":"schema","module":"Account","lines":227,"bytes":6623,"description":"specs/account/schema.t27 Account Types Specification","health":"ok","tokens":712,"nodes":294,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5001,"rust":2773,"verilog":8979,"verilog_hir":347,"zig":4811}},{"path":"specs/api/c_api_contract.t27","category":"specs/api","name":"c_api_contract","module":null,"lines":243,"bytes":7087,"description":null,"health":"warn","tokens":1357,"nodes":313,"depth":11,"loss":75,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3690,"rust":66,"verilog":6141,"verilog_hir":243,"zig":3477}},{"path":"specs/api/sdk_contract.t27","category":"specs/api","name":"sdk_contract","module":null,"lines":407,"bytes":10914,"description":null,"health":"warn","tokens":2187,"nodes":175,"depth":3,"loss":50,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5099,"rust":742,"verilog":11919,"verilog_hir":243,"zig":8361}},{"path":"specs/api/tri_net_api.t27","category":"specs/api","name":"tri_net_api","module":null,"lines":190,"bytes":5445,"description":null,"health":"warn","tokens":333,"nodes":3,"depth":2,"loss":42,"tcErrors":0,"failedBackends":[],"outBytes":{"c":687,"rust":66,"verilog":1861,"verilog_hir":243,"zig":275}},{"path":"specs/ar/asp_solver.t27","category":"specs/ar","name":"asp_solver","module":null,"lines":556,"bytes":16355,"description":"spec: AspSolver Answer Set Programming solver for neuro-symbolic reasoning","health":"ok","tokens":2583,"nodes":17,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1380,"rust":713,"verilog":2277,"verilog_hir":289,"zig":562}},{"path":"specs/ar/coa_planning.t27","category":"specs/ar","name":"coa_planning","module":null,"lines":638,"bytes":20107,"description":"spec: CoaPlanning Course of Action (COA) planning for neuro-symbolic reasoning","health":"ok","tokens":2830,"nodes":46,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2444,"rust":1550,"verilog":4045,"verilog_hir":354,"zig":1318}},{"path":"specs/ar/composition.t27","category":"specs/ar","name":"composition","module":null,"lines":571,"bytes":19109,"description":"spec: Composition ML+AR composition patterns for neuro-symbolic hybrid reasoning","health":"ok","tokens":2513,"nodes":44,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2528,"rust":1614,"verilog":4018,"verilog_hir":357,"zig":1302}},{"path":"specs/ar/datalog_engine.t27","category":"specs/ar","name":"datalog_engine","module":null,"lines":350,"bytes":10935,"description":"spec: DatalogEngine Datalog reasoning engine for neuro-symbolic AI","health":"ok","tokens":1863,"nodes":17,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1251,"rust":706,"verilog":2117,"verilog_hir":298,"zig":585}},{"path":"specs/ar/explainability.t27","category":"specs/ar","name":"explainability","module":null,"lines":556,"bytes":16253,"description":"spec: Explainability Explainable AI (XAI) mechanisms for neuro-symbolic reasoning","health":"ok","tokens":2217,"nodes":17,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1471,"rust":730,"verilog":2572,"verilog_hir":303,"zig":618}},{"path":"specs/ar/proof_trace.t27","category":"specs/ar","name":"proof_trace","module":null,"lines":313,"bytes":9671,"description":"spec: ProofTrace Bounded proof trace mechanism for explainable neuro-symbolic reasoning","health":"ok","tokens":1498,"nodes":16,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1340,"rust":558,"verilog":2319,"verilog_hir":295,"zig":527}},{"path":"specs/ar/restraint.t27","category":"specs/ar","name":"restraint","module":null,"lines":438,"bytes":14096,"description":"spec: Restraint Bounded rationality and restraint mechanisms for neuro-symbolic reasoning","health":"ok","tokens":1557,"nodes":25,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1857,"rust":935,"verilog":2926,"verilog_hir":292,"zig":799}},{"path":"specs/ar/ternary_logic.t27","category":"specs/ar","name":"ternary_logic","module":null,"lines":473,"bytes":13762,"description":"spec: TernaryLogic K3 Kleene ternary logic operations for neuro-symbolic reasoning","health":"warn","tokens":1858,"nodes":476,"depth":10,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6976,"rust":1426,"verilog":9974,"verilog_hir":954,"zig":4936}},{"path":"specs/auth/config.t27","category":"specs/auth","name":"config","module":"AuthConfig","lines":295,"bytes":9593,"description":"specs/auth/config.t27 Authentication Configuration Storage","health":"ok","tokens":1032,"nodes":371,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7214,"rust":2578,"verilog":11611,"verilog_hir":944,"zig":6848}},{"path":"specs/automation/wrapup-auto.t27","category":"specs/automation","name":"wrapup-auto","module":"automation","lines":51,"bytes":1625,"description":null,"health":"ok","tokens":162,"nodes":20,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1334,"rust":671,"verilog":2777,"verilog_hir":265,"zig":733}},{"path":"specs/base/debounce.t27","category":"specs/base","name":"debounce","module":"base-debounce","lines":99,"bytes":2655,"description":"base/debounce.t27 — φ-Structured Debouncing Trinity S³AI — Rate Limiting and Debouncing","health":"ok","tokens":242,"nodes":88,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2722,"rust":950,"verilog":4549,"verilog_hir":610,"zig":1419}},{"path":"specs/base/ops.t27","category":"specs/base","name":"ops","module":"tritype-ops","lines":1490,"bytes":47606,"description":"ops.t27 -- Trit Operations for t27 Language Trit arithmetic: multiply, add, carry, comparison","health":"warn","tokens":7372,"nodes":2700,"depth":16,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":35514,"rust":5106,"verilog":62089,"verilog_hir":1991,"zig":33464}},{"path":"specs/base/ring_32.t27","category":"specs/base","name":"ring_32","module":"base-ring-32","lines":25,"bytes":596,"description":"base/ring_32.t27 — Ring 32 Definition","health":"ok","tokens":78,"nodes":21,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1282,"rust":329,"verilog":2206,"verilog_hir":253,"zig":781}},{"path":"specs/base/seed.t27","category":"specs/base","name":"seed","module":"seed","lines":93,"bytes":2627,"description":"seed.t27 -- Minimal Golden Seed for E2E CI (#150)","health":"ok","tokens":409,"nodes":137,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2883,"rust":596,"verilog":4143,"verilog_hir":371,"zig":1850}},{"path":"specs/base/ternary_add.t27","category":"specs/base","name":"ternary_add","module":"ternary_add","lines":413,"bytes":14371,"description":"ternary_add.t27 -- Balanced Ternary Addition Formal Spec Ring 043 -- Formal carry propagation invariants, closure, range formula","health":"ok","tokens":1722,"nodes":311,"depth":13,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7495,"rust":1292,"verilog":16385,"verilog_hir":802,"zig":9474}},{"path":"specs/base/ternary_encoding.t27","category":"specs/base","name":"ternary_encoding","module":"TernaryEncoding","lines":415,"bytes":13711,"description":"t27/specs/base/ternary_encoding.t27 Ternary Encoding/Decoding Specification Ring 065 - Encoding schemes for ternary data representation Defines how binary data maps to ternary and vice versa","health":"warn","tokens":1775,"nodes":397,"depth":12,"loss":0,"tcErrors":8,"failedBackends":[],"outBytes":{"c":7138,"rust":3807,"verilog":12824,"verilog_hir":1533,"zig":5014}},{"path":"specs/base/ternary_memory.t27","category":"specs/base","name":"ternary_memory","module":"TernaryMemory","lines":417,"bytes":13794,"description":"t27/specs/base/ternary_memory.t27 Ternary Memory Specification Ring 066 - Ternary memory cell and array operations Defines how trits are stored and accessed in memory","health":"warn","tokens":1713,"nodes":370,"depth":11,"loss":0,"tcErrors":3,"failedBackends":[],"outBytes":{"c":7995,"rust":4260,"verilog":13649,"verilog_hir":1832,"zig":5785}},{"path":"specs/base/types.t27","category":"specs/base","name":"types","module":"tritype-base","lines":1680,"bytes":55612,"description":"types.t27 -- Base Types for t27 Language Trit, PackedTrit, TernaryWord definitions","health":"warn","tokens":9455,"nodes":3764,"depth":11,"loss":0,"tcErrors":3,"failedBackends":[],"outBytes":{"c":45526,"rust":6990,"verilog":82565,"verilog_hir":2958,"zig":42966}},{"path":"specs/benchmarks/bench_main.t27","category":"specs/benchmarks","name":"bench_main","module":null,"lines":95,"bytes":2523,"description":null,"health":"warn","tokens":451,"nodes":1,"depth":1,"loss":43,"tcErrors":0,"failedBackends":[],"outBytes":{"c":396,"rust":66,"verilog":1045,"verilog_hir":243,"zig":121}},{"path":"specs/benchmarks/bench_nn.t27","category":"specs/benchmarks","name":"bench_nn","module":null,"lines":166,"bytes":3216,"description":null,"health":"warn","tokens":586,"nodes":10,"depth":3,"loss":68,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1156,"rust":258,"verilog":2522,"verilog_hir":243,"zig":275}},{"path":"specs/benchmarks/gf16_bfloat16_nmse.t27","category":"specs/benchmarks","name":"gf16_bfloat16_nmse","module":null,"lines":153,"bytes":4300,"description":null,"health":"warn","tokens":683,"nodes":13,"depth":4,"loss":56,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1584,"rust":66,"verilog":3612,"verilog_hir":243,"zig":994}},{"path":"specs/benchmarks/ternary_vs_binary.t27","category":"specs/benchmarks","name":"ternary_vs_binary","module":null,"lines":92,"bytes":1965,"description":null,"health":"warn","tokens":346,"nodes":21,"depth":6,"loss":38,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1068,"rust":239,"verilog":2460,"verilog_hir":243,"zig":516}},{"path":"specs/boards/arty_a7.t27","category":"specs/boards","name":"arty_a7","module":"BoardArtyA7","lines":288,"bytes":7435,"description":"t27/specs/boards/arty_a7.t27 Digilent Arty A7 Board Profile Artix-7 XC7A35T/XC7A100T, 100MHz clock, 4 LEDs, 4 buttons, UART","health":"ok","tokens":1121,"nodes":141,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6062,"rust":3223,"verilog":9480,"verilog_hir":450,"zig":6839}},{"path":"specs/boards/xc7a100t_full.t27","category":"specs/boards","name":"xc7a100t_full","module":"BoardFullXC7A100T","lines":357,"bytes":9813,"description":"t27/specs/boards/xc7a100t_full.t27 QMTECH XC7A100T-CSG324 Full Board Profile LED + UART + SPI + MAC debug, QMTECH Wukong expansion Note: 22 pins from full QMTECH XDC are missing in prjxray-db","health":"ok","tokens":1514,"nodes":149,"depth":4,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7035,"rust":5043,"verilog":10747,"verilog_hir":410,"zig":8028}},{"path":"specs/boards/xc7a100t_minimal.t27","category":"specs/boards","name":"xc7a100t_minimal","module":"BoardMinimalXC7A100T","lines":393,"bytes":10805,"description":"t27/specs/boards/xc7a100t_minimal.t27 QMTECH XC7A100T-CSG324 Minimal Board Profile Heartbeat LED + UART loopback, prjxray-verified pins only","health":"warn","tokens":1682,"nodes":318,"depth":9,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":8809,"rust":4729,"verilog":14719,"verilog_hir":693,"zig":10566}},{"path":"specs/brain/brain.t27","category":"specs/brain","name":"brain","module":null,"lines":58,"bytes":2756,"description":null,"health":"warn","tokens":523,"nodes":15,"depth":7,"loss":15,"tcErrors":0,"failedBackends":[],"outBytes":{"c":728,"rust":66,"verilog":1839,"verilog_hir":243,"zig":349}},{"path":"specs/brain/bus.t27","category":"specs/brain","name":"bus","module":"brain-bus","lines":17,"bytes":419,"description":"bus.t27 -- inter-region messaging contract (spec-first) Message shapes and routing rules expand with region specs.","health":"ok","tokens":47,"nodes":14,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1209,"rust":174,"verilog":2076,"verilog_hir":295,"zig":358}},{"path":"specs/brain/cognitive_loop.t27","category":"specs/brain","name":"cognitive_loop","module":"brain-cognitive-loop","lines":17,"bytes":515,"description":"cognitive_loop.t27 -- sense -> evaluate -> decide -> act -> consolidate (spec-first) Phase timing contract lives in phi_timing.t27; this module holds loop identity constants.","health":"ok","tokens":49,"nodes":14,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1287,"rust":189,"verilog":2138,"verilog_hir":325,"zig":394}},{"path":"specs/brain/neural_gamma.t27","category":"specs/brain","name":"neural_gamma","module":null,"lines":89,"bytes":2008,"description":null,"health":"warn","tokens":342,"nodes":20,"depth":6,"loss":27,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1265,"rust":254,"verilog":2771,"verilog_hir":243,"zig":569}},{"path":"specs/brain/phi_timing.t27","category":"specs/brain","name":"phi_timing","module":"brain-phi-timing","lines":80,"bytes":3193,"description":"phi_timing.t27 -- phi-structured cognitive cycle timing (spec-first) Phase duration ratios follow INV-1; integer ms sum may differ slightly from 3*base_ms.","health":"ok","tokens":429,"nodes":159,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3562,"rust":1505,"verilog":5674,"verilog_hir":542,"zig":2358}},{"path":"specs/brain/unified_state.t27","category":"specs/brain","name":"unified_state","module":"brain-unified-state","lines":94,"bytes":2948,"description":"unified_state.t27 -- Trinity Brain unified state (spec-first) Normative types for Strand VI. Zig/C/Verilog are generated under gen/ via t27c.","health":"ok","tokens":373,"nodes":114,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3099,"rust":1824,"verilog":4687,"verilog_hir":401,"zig":1965}},{"path":"specs/bus/pubsub.t27","category":"specs/bus","name":"pubsub","module":"bus-pubsub","lines":670,"bytes":17611,"description":"bus/pubsub.t27 — Publish/Subscribe Patterns Pub/sub interface for event-driven communication","health":"warn","tokens":2651,"nodes":953,"depth":13,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":17465,"rust":7427,"verilog":31022,"verilog_hir":2358,"zig":14379}},{"path":"specs/bus/schema.t27","category":"specs/bus","name":"schema","module":"bus-schema","lines":657,"bytes":17052,"description":"bus/schema.t27 — Event Type Definitions Core event types and structures for the event bus","health":"ok","tokens":2568,"nodes":871,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":17523,"rust":8067,"verilog":28706,"verilog_hir":1856,"zig":14100}},{"path":"specs/cloud/railway_deploy.t27","category":"specs/cloud","name":"railway_deploy","module":"cloud-railway-deploy","lines":363,"bytes":9070,"description":"cloud/railway_deploy.t27 — Autonomous Railway Deployment","health":"warn","tokens":1092,"nodes":455,"depth":10,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":7326,"rust":2531,"verilog":12422,"verilog_hir":879,"zig":5567}},{"path":"specs/compiler/diagnostics.t27","category":"specs/compiler","name":"diagnostics","module":"Diagnostics","lines":148,"bytes":4220,"description":null,"health":"ok","tokens":680,"nodes":175,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4674,"rust":2396,"verilog":7845,"verilog_hir":774,"zig":4069}},{"path":"specs/compiler/lexer.t27","category":"specs/compiler","name":"lexer","module":"Lexing","lines":583,"bytes":22503,"description":null,"health":"ok","tokens":5304,"nodes":2394,"depth":20,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":20444,"rust":14216,"verilog":30803,"verilog_hir":1722,"zig":19077}},{"path":"specs/compiler/linker.t27","category":"specs/compiler","name":"linker","module":"Linking","lines":156,"bytes":4048,"description":null,"health":"ok","tokens":664,"nodes":64,"depth":6,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2576,"rust":1603,"verilog":3984,"verilog_hir":723,"zig":1530}},{"path":"specs/compiler/meta_compile.t27","category":"specs/compiler","name":"meta_compile","module":"MetaCompilation","lines":70,"bytes":1886,"description":null,"health":"ok","tokens":289,"nodes":92,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2802,"rust":1070,"verilog":4591,"verilog_hir":459,"zig":1891}},{"path":"specs/compiler/mod_structure.t27","category":"specs/compiler","name":"mod_structure","module":"compiler-mod-structure","lines":124,"bytes":3540,"description":"compiler/mod_structure.t27 — Module Structure and Ring Validation Trinity S³AI — Spec-First Architecture","health":"ok","tokens":419,"nodes":160,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3583,"rust":1607,"verilog":4622,"verilog_hir":393,"zig":2157}},{"path":"specs/compiler/optimizer.t27","category":"specs/compiler","name":"optimizer","module":"Optimization","lines":286,"bytes":8976,"description":null,"health":"ok","tokens":1580,"nodes":598,"depth":17,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9038,"rust":6098,"verilog":14555,"verilog_hir":1307,"zig":8579}},{"path":"specs/compiler/parser.t27","category":"specs/compiler","name":"parser","module":"Parsing","lines":1619,"bytes":55123,"description":"specs/compiler/parser.t27 T27 Parser Specification -- Self-hosting compiler core This module defines the complete recursive descent parser for the T27 language. It is a 1:1 port of bootstrap/src/compiler.rs Parser to t27 spec format.","health":"ok","tokens":10927,"nodes":3738,"depth":22,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":48560,"rust":38137,"verilog":70607,"verilog_hir":3671,"zig":44759}},{"path":"specs/compiler/pipeline.t27","category":"specs/compiler","name":"pipeline","module":"Pipeline","lines":170,"bytes":4503,"description":null,"health":"ok","tokens":710,"nodes":202,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4528,"rust":2228,"verilog":8208,"verilog_hir":620,"zig":4150}},{"path":"specs/compiler/stdlib.t27","category":"specs/compiler","name":"stdlib","module":"Stdlib","lines":663,"bytes":16035,"description":null,"health":"warn","tokens":3694,"nodes":1367,"depth":10,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":15542,"rust":10188,"verilog":29202,"verilog_hir":4222,"zig":15870}},{"path":"specs/compiler/typechecker.t27","category":"specs/compiler","name":"typechecker","module":"TypeChecking","lines":694,"bytes":24448,"description":null,"health":"ok","tokens":4983,"nodes":1777,"depth":15,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":22361,"rust":16763,"verilog":36100,"verilog_hir":2520,"zig":22869}},{"path":"specs/config/load.t27","category":"specs/config","name":"load","module":"config-load","lines":608,"bytes":16362,"description":"config/load.t27 — Config Load/Save Specification Configuration file I/O, merging, validation","health":"ok","tokens":2465,"nodes":806,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":16227,"rust":6822,"verilog":26324,"verilog_hir":2265,"zig":13487}},{"path":"specs/config/migrate.t27","category":"specs/config","name":"migrate","module":"config-migrate","lines":663,"bytes":18030,"description":"config/migrate.t27 — Config Migration Specification Version detection, upgrade, compatibility handling","health":"warn","tokens":2665,"nodes":442,"depth":9,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":10895,"rust":7917,"verilog":15625,"verilog_hir":2689,"zig":7875}},{"path":"specs/config/paths.t27","category":"specs/config","name":"paths","module":"config-paths","lines":641,"bytes":15895,"description":"config/paths.t27 — Config Paths Specification Path resolution, directory creation, validation","health":"ok","tokens":2464,"nodes":896,"depth":13,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":15854,"rust":6625,"verilog":25249,"verilog_hir":1852,"zig":12671}},{"path":"specs/config/schema.t27","category":"specs/config","name":"schema","module":"config-schema","lines":703,"bytes":19048,"description":"config/schema.t27 — Config Schema Specification Configuration structures for providers, agents, LSP","health":"ok","tokens":3071,"nodes":958,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":18410,"rust":7989,"verilog":31073,"verilog_hir":2297,"zig":15330}},{"path":"specs/conformance/e2e_scenarios.t27","category":"specs/conformance","name":"e2e_scenarios","module":null,"lines":265,"bytes":7796,"description":null,"health":"warn","tokens":1509,"nodes":425,"depth":10,"loss":64,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5137,"rust":321,"verilog":8487,"verilog_hir":243,"zig":4646}},{"path":"specs/demos/jones_topology_decision_gate.t27","category":"specs/demos","name":"jones_topology_decision_gate","module":"JonesTopologyDecisionGate","lines":347,"bytes":29767,"description":"t27/specs/demos/jones_topology_decision_gate.t27 Decision Gate TH-01..TH-05 for H_1: Structure Similarity Classifier Tests if VSA dot_product + fixed phi constant can classify structures by complexity","health":"warn","tokens":1564,"nodes":375,"depth":10,"loss":0,"tcErrors":6,"failedBackends":[],"outBytes":{"c":6245,"rust":2719,"verilog":11314,"verilog_hir":669,"zig":7796}},{"path":"specs/demos/jones_topology_filter.t27","category":"specs/demos","name":"jones_topology_filter","module":"JonesTopologyFilter","lines":318,"bytes":10711,"description":"t27/specs/demos/jones_topology_filter.t27 MVP: Structure Similarity Classifier using VSA + CS Constants WHAT THIS CODE ACTUALLY DOES: - Takes a hypervector representing a structure - Computes dot_product similarity with a reference structure - Classifies complexity based on similarity thresholds","health":"warn","tokens":1535,"nodes":338,"depth":12,"loss":0,"tcErrors":4,"failedBackends":[],"outBytes":{"c":7510,"rust":2714,"verilog":14401,"verilog_hir":672,"zig":8777}},{"path":"specs/demos/simple_test.t27","category":"specs/demos","name":"simple_test","module":"SimpleTest","lines":15,"bytes":277,"description":"Simple test spec","health":"ok","tokens":33,"nodes":8,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1013,"rust":98,"verilog":1870,"verilog_hir":249,"zig":426}},{"path":"specs/depin/prove.t27","category":"specs/depin","name":"prove","module":"depin","lines":285,"bytes":8731,"description":"DePIN proof-of-useful-compute spec Issue #40 — L-TRI-1: POST /prove endpoint","health":"ok","tokens":739,"nodes":139,"depth":6,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7541,"rust":1948,"verilog":14019,"verilog_hir":1243,"zig":5139}},{"path":"specs/enrichment/audio_overview.t27","category":"specs/enrichment","name":"audio_overview","module":"enrichment","lines":163,"bytes":5680,"description":"audio_overview.t27 — Bilingual Audio Overview for NotebookLM Ring 091 — API-only multilingual enrichment","health":"ok","tokens":454,"nodes":115,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4702,"rust":2605,"verilog":8137,"verilog_hir":942,"zig":3619}},{"path":"specs/enrichment/youtube_transcript.t27","category":"specs/enrichment","name":"youtube_transcript","module":"enrichment","lines":606,"bytes":20285,"description":"youtube_transcript.t27 — YouTube Transcript Extraction for NotebookLM Enrichment Ring 090 — Fallback for blocked YouTube URL uploads","health":"warn","tokens":2499,"nodes":749,"depth":14,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":12326,"rust":4200,"verilog":19987,"verilog_hir":879,"zig":10554}},{"path":"specs/file/operations.t27","category":"specs/file","name":"operations","module":"FileOperations","lines":446,"bytes":13273,"description":"specs/file/operations.t27 File Operations","health":"warn","tokens":1694,"nodes":251,"depth":7,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7446,"rust":2912,"verilog":11124,"verilog_hir":2164,"zig":7302}},{"path":"specs/file/schema.t27","category":"specs/file","name":"schema","module":"File","lines":334,"bytes":11786,"description":"specs/file/schema.t27 File Types Specification","health":"ok","tokens":1017,"nodes":378,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6639,"rust":4289,"verilog":12209,"verilog_hir":483,"zig":5981}},{"path":"specs/file/watcher.t27","category":"specs/file","name":"watcher","module":"FileWatcher","lines":551,"bytes":16816,"description":"specs/file/watcher.t27 File Watcher Operations","health":"warn","tokens":2139,"nodes":417,"depth":9,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":8816,"rust":2844,"verilog":13255,"verilog_hir":1374,"zig":7857}},{"path":"specs/fpga/apb_bridge.t27","category":"specs/fpga","name":"apb_bridge","module":"ApbBridge","lines":348,"bytes":9210,"description":"t27/specs/fpga/apb_bridge.t27 APB (Advanced Peripheral Bus) Bridge Specification for Trinity T27 FPGA HIR Register-mapped peripheral bridge for low-bandwidth peripherals Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1563,"nodes":450,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":8382,"rust":4644,"verilog":14269,"verilog_hir":1801,"zig":7894}},{"path":"specs/fpga/assembler.t27","category":"specs/fpga","name":"assembler","module":"Assembler","lines":347,"bytes":8722,"description":"t27/specs/fpga/assembler.t27 T27 Ternary Assembler Specification High-level assembler for the ternary ISA, compiles to machine code Supports R-type, I-type, and GF16 extended instructions Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1509,"nodes":383,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7815,"rust":4297,"verilog":13797,"verilog_hir":1651,"zig":7418}},{"path":"specs/fpga/axi4.t27","category":"specs/fpga","name":"axi4","module":"Axi4","lines":394,"bytes":10640,"description":"t27/specs/fpga/axi4.t27 AXI4-Lite and AXI4-Full Bus Interface Specification for Trinity T27 FPGA HIR Defines bus port groups for AW/AR/W/R/B channels Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1819,"nodes":655,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":8945,"rust":5423,"verilog":14318,"verilog_hir":1710,"zig":8696}},{"path":"specs/fpga/boards/arty_a7_integration.t27","category":"specs/fpga","name":"arty_a7_integration","module":"ArtyA7_Integration","lines":137,"bytes":4043,"description":"t27/specs/fpga/boards/arty_a7_integration.t27 Arty A7 Board-Level Integration Spec Full system: MAC + UART + SPI + Memory + Bridge + GF16 + TernaryISA Pin mappings match specs/fpga/constraints/arty_a7.xdc","health":"ok","tokens":636,"nodes":144,"depth":6,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4644,"rust":2081,"verilog":7952,"verilog_hir":536,"zig":3897}},{"path":"specs/fpga/boards/qmtech_a100t_integration.t27","category":"specs/fpga","name":"qmtech_a100t_integration","module":"QMTech_A100T_Integration","lines":96,"bytes":2568,"description":"t27/specs/fpga/boards/qmtech_a100t_integration.t27 QMTech XC7A100T Board-Level Integration Spec Full system for QMTech A100T development board","health":"ok","tokens":365,"nodes":62,"depth":6,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3292,"rust":1190,"verilog":5901,"verilog_hir":387,"zig":2047}},{"path":"specs/fpga/bootrom.t27","category":"specs/fpga","name":"bootrom","module":"BootROM","lines":140,"bytes":4020,"description":"t27/specs/fpga/bootrom.t27 T27 Boot ROM Specification Boot sequence stages, init vectors, integrity checksum Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":753,"nodes":154,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3724,"rust":1479,"verilog":7304,"verilog_hir":751,"zig":3770}},{"path":"specs/fpga/bridge.t27","category":"specs/fpga","name":"bridge","module":"FPGA_Bridge","lines":501,"bytes":17323,"description":"t27/specs/fpga/bridge.t27 FPGA Communication Bridge Specification Combines UART and SPI for host and peripheral communication","health":"warn","tokens":2136,"nodes":713,"depth":11,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":11048,"rust":3240,"verilog":19422,"verilog_hir":1746,"zig":12391}},{"path":"specs/fpga/clock_domain.t27","category":"specs/fpga","name":"clock_domain","module":"ClockDomain","lines":231,"bytes":5913,"description":"t27/specs/fpga/clock_domain.t27 Clock Domain Abstraction for Trinity T27 FPGA HIR Defines clock sources, PLL configs, and cross-domain crossing Uses flat structs (parser-compatible)","health":"ok","tokens":973,"nodes":200,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5581,"rust":2712,"verilog":9551,"verilog_hir":1117,"zig":4905}},{"path":"specs/fpga/crossopt.t27","category":"specs/fpga","name":"crossopt","module":"CrossOpt","lines":126,"bytes":3615,"description":"t27/specs/fpga/crossopt.t27 T27 Cross-Module Optimization Specification Inter-module constant propagation, dead signal elimination, instance merging Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":564,"nodes":127,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4067,"rust":1799,"verilog":7159,"verilog_hir":833,"zig":3173}},{"path":"specs/fpga/cts.t27","category":"specs/fpga","name":"cts","module":"CTS","lines":225,"bytes":5735,"description":"t27/specs/fpga/cts.t27 T27 Clock Tree Synthesis Specification PLL configuration, clock buffer trees, skew estimation Artix-7: BUFH=0.05ns, BUFG=0.1ns, PLL jitter=50ps, max skew=100ps Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":966,"nodes":248,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5730,"rust":2919,"verilog":10984,"verilog_hir":1296,"zig":5270}},{"path":"specs/fpga/dft.t27","category":"specs/fpga","name":"dft","module":"DFT","lines":264,"bytes":6604,"description":"t27/specs/fpga/dft.t27 T27 Design-for-Test Specification Scan chains, BIST controllers, JTAG TAP, test coverage estimation Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1105,"nodes":285,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6160,"rust":3191,"verilog":11559,"verilog_hir":1292,"zig":5916}},{"path":"specs/fpga/e2e_demo.t27","category":"specs/fpga","name":"e2e_demo","module":"E2eDemo","lines":253,"bytes":6432,"description":"t27/specs/fpga/e2e_demo.t27 T27 End-to-End Demo Specification Exercises the full toolchain: assembler -> ternary core -> GF16 -> VCD trace Validates the complete FPGA pipeline from spec to hardware simulation Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1029,"nodes":290,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5815,"rust":2924,"verilog":10543,"verilog_hir":1149,"zig":5607}},{"path":"specs/fpga/fifo.t27","category":"specs/fpga","name":"fifo","module":"Fifo","lines":365,"bytes":9822,"description":"t27/specs/fpga/fifo.t27 Synchronous and Asynchronous FIFO Stdlib for Trinity T27 FPGA HIR Defines FIFO configuration with depth, data width, and flags Uses flat arrays + count fields (parser-compatible)","health":"warn","tokens":1769,"nodes":416,"depth":8,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":7877,"rust":4050,"verilog":13443,"verilog_hir":1682,"zig":8178}},{"path":"specs/fpga/formal.t27","category":"specs/fpga","name":"formal","module":"Formal","lines":353,"bytes":9441,"description":"t27/specs/fpga/formal.t27 Formal Verification Specification for Trinity T27 FPGA HIR Defines assertion kinds, properties, and coverage points Generates SystemVerilog Assertions (SVA) alongside Verilog Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1582,"nodes":402,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":8157,"rust":4703,"verilog":14629,"verilog_hir":1571,"zig":8545}},{"path":"specs/fpga/gf16_accel.t27","category":"specs/fpga","name":"gf16_accel","module":"Gf16Accel","lines":423,"bytes":11072,"description":"t27/specs/fpga/gf16_accel.t27 GF(16) Hardware Accelerator Specification for Trinity T27 FPGA HIR Defines GF16 MAC, FFT, and VSA (Vector Space Architecture) operations Connects phi-identity (phi^2 = phi + 1, phi^2 + phi^-2 = 3) to silicon Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1778,"nodes":472,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9690,"rust":4949,"verilog":17829,"verilog_hir":1647,"zig":9839}},{"path":"specs/fpga/hir.t27","category":"specs/fpga","name":"hir","module":"Hir","lines":692,"bytes":19053,"description":"t27/specs/fpga/hir.t27 Hardware Intermediate Representation (HIR) for Trinity T27 Decouples .t27 spec semantics from Verilog/SystemVerilog emission Uses flat arrays + count fields (parser-compatible, no Vec/generics)","health":"warn","tokens":3360,"nodes":921,"depth":12,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":15494,"rust":10732,"verilog":25714,"verilog_hir":2384,"zig":15752}},{"path":"specs/fpga/hw_types.t27","category":"specs/fpga","name":"hw_types","module":"HwTypes","lines":335,"bytes":8002,"description":"t27/specs/fpga/hw_types.t27 Hardware Type System for Trinity T27 FPGA HIR Defines signal-level types with bit-accurate widths and signedness","health":"ok","tokens":1307,"nodes":339,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7257,"rust":3482,"verilog":12511,"verilog_hir":1051,"zig":6802}},{"path":"specs/fpga/linker.t27","category":"specs/fpga","name":"linker","module":"Linker","lines":327,"bytes":8187,"description":"t27/specs/fpga/linker.t27 T27 Linker Specification Links assembled object files into executable images for ternary core Handles section merging, symbol resolution, address assignment, relocations Uses flat arrays + count fields (parser-compatible)","health":"warn","tokens":1425,"nodes":349,"depth":7,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6968,"rust":3887,"verilog":12543,"verilog_hir":1552,"zig":6863}},{"path":"specs/fpga/mac.t27","category":"specs/fpga","name":"mac","module":"ZeroDSP_MAC","lines":623,"bytes":22498,"description":"t27/specs/fpga/mac.t27 ZeroDSP FPGA Multiply-Accumulate Specification Ternary MAC operations for FPGA implementation","health":"warn","tokens":3452,"nodes":668,"depth":13,"loss":0,"tcErrors":7,"failedBackends":[],"outBytes":{"c":11545,"rust":4177,"verilog":20412,"verilog_hir":2034,"zig":15620}},{"path":"specs/fpga/memory.t27","category":"specs/fpga","name":"memory","module":"Memory","lines":355,"bytes":9348,"description":"t27/specs/fpga/memory.t27 Memory (BRAM/DRAM/ROM) Abstraction for Trinity T27 FPGA HIR Defines block memory primitives with read/write ports Uses flat arrays + count fields (parser-compatible, no Vec/generics)","health":"warn","tokens":1728,"nodes":497,"depth":11,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":8205,"rust":4713,"verilog":13482,"verilog_hir":1137,"zig":8117}},{"path":"specs/fpga/partition.t27","category":"specs/fpga","name":"partition","module":"Partition","lines":306,"bytes":9004,"description":"t27/specs/fpga/partition.t27 T27 Multi-FPGA Partition Specification Automatically partitions HIR modules across multiple FPGAs Estimates inter-FPGA bandwidth and latency Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1436,"nodes":303,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6744,"rust":3257,"verilog":12950,"verilog_hir":1649,"zig":7102}},{"path":"specs/fpga/placement.t27","category":"specs/fpga","name":"placement","module":"Placement","lines":242,"bytes":6335,"description":"t27/specs/fpga/placement.t27 T27 Placement Constraint Generator Specification Auto-generates placement hints and routing constraints from HIR connectivity Groups related modules into floorplan regions for optimal routing Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1179,"nodes":286,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6165,"rust":3066,"verilog":11360,"verilog_hir":1341,"zig":5656}},{"path":"specs/fpga/power.t27","category":"specs/fpga","name":"power","module":"Power","lines":230,"bytes":6278,"description":"t27/specs/fpga/power.t27 T27 Power Estimation Specification Estimates dynamic and static power consumption for FPGA designs Artix-7 power model: LUT=10uW/MHz, FF=5uW/MHz, BRAM=50uW/MHz, DSP=100uW/MHz Static: 50mW base + 0.1uW per resource unit Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1007,"nodes":265,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6382,"rust":3151,"verilog":11385,"verilog_hir":1446,"zig":5652}},{"path":"specs/fpga/power_analysis.t27","category":"specs/fpga","name":"power_analysis","module":"PowerAnalysis","lines":453,"bytes":12807,"description":"t27/specs/fpga/power_analysis.t27 T27 Power Analysis Specification Connects power.t27 estimation model to utilization reports from synthesis Parses LUT/FF/BRAM/DSP counts from Vivado/Yosys reports Feeds utilization into Power.est_total_power() for estimation Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":2253,"nodes":624,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":10744,"rust":5517,"verilog":21275,"verilog_hir":1973,"zig":11626}},{"path":"specs/fpga/router.t27","category":"specs/fpga","name":"router","module":"Router","lines":257,"bytes":6620,"description":"t27/specs/fpga/router.t27 T27 HIR Signal Router Specification Connectivity graph analysis, fanout estimation, routing congestion prediction Estimates wire length, routing resources needed for Artix-7 Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1078,"nodes":269,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6203,"rust":2973,"verilog":12354,"verilog_hir":1351,"zig":5958}},{"path":"specs/fpga/simulator.t27","category":"specs/fpga","name":"simulator","module":"Simulator","lines":278,"bytes":7050,"description":"t27/specs/fpga/simulator.t27 HIR Cycle-Accurate Simulation Engine Specification Provides simulation primitives for verifying HIR modules pre-synthesis Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1160,"nodes":289,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6492,"rust":3421,"verilog":11008,"verilog_hir":1292,"zig":6201}},{"path":"specs/fpga/spi.t27","category":"specs/fpga","name":"spi","module":"SPI_Master","lines":415,"bytes":13093,"description":"t27/specs/fpga/spi.t27 SPI Master Specification for FPGA Mode 0: CPOL=0, CPHA=0 (SCK idle low, sample on rising edge)","health":"ok","tokens":1491,"nodes":309,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7000,"rust":2841,"verilog":12871,"verilog_hir":1218,"zig":6970}},{"path":"specs/fpga/stdlib.t27","category":"specs/fpga","name":"stdlib","module":"Stdlib","lines":372,"bytes":17315,"description":"t27/specs/fpga/stdlib.t27 T27 FPGA Standard Library IP Catalog Reusable hardware IP cores with resource utilization estimates Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":4069,"nodes":895,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":11845,"rust":8002,"verilog":13111,"verilog_hir":1183,"zig":15069}},{"path":"specs/fpga/ternary_isa.t27","category":"specs/fpga","name":"ternary_isa","module":"TernaryIsa","lines":543,"bytes":14462,"description":"t27/specs/fpga/ternary_isa.t27 Ternary ISA Hardware Implementation Specification for Trinity T27 FPGA HIR Bridges software ISA (27 registers, balanced ternary) to silicon Connects GF16 arithmetic, ternary gates, and phi-identity to hardware Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":2354,"nodes":692,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":11982,"rust":7182,"verilog":21390,"verilog_hir":1934,"zig":12758}},{"path":"specs/fpga/testbench.t27","category":"specs/fpga","name":"testbench","module":"Testbench","lines":271,"bytes":6930,"description":"t27/specs/fpga/testbench.t27 T27 HIR Testbench Auto-Generation Specification Automatically generates Verilog testbenches from HIR modules Includes clock generation, reset sequencing, stimulus, and checking Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1144,"nodes":275,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6269,"rust":3246,"verilog":11518,"verilog_hir":1479,"zig":6133}},{"path":"specs/fpga/testbench/apb_bridge_tb.t27","category":"specs/fpga","name":"apb_bridge_tb","module":"APB_Bridge_Testbench","lines":144,"bytes":3247,"description":"t27/specs/fpga/testbench/apb_bridge_tb.t27 APB Bridge Testbench Tests APB bus protocol: setup, access, wait states","health":"ok","tokens":604,"nodes":265,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3436,"rust":963,"verilog":6904,"verilog_hir":1230,"zig":2370}},{"path":"specs/fpga/testbench/assembler_tb.t27","category":"specs/fpga","name":"assembler_tb","module":"Assembler_Testbench","lines":111,"bytes":2952,"description":"t27/specs/fpga/testbench/assembler_tb.t27 Assembler/Linker Integration Testbench Tests ternary instruction encoding, program assembly, and memory linking","health":"ok","tokens":607,"nodes":204,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3761,"rust":1402,"verilog":6801,"verilog_hir":1322,"zig":3018}},{"path":"specs/fpga/testbench/axi4_tb.t27","category":"specs/fpga","name":"axi4_tb","module":"AXI4_Testbench","lines":179,"bytes":4359,"description":"t27/specs/fpga/testbench/axi4_tb.t27 AXI4 Bus Testbench Specification Tests AXI4 read/write channels, burst support, and protocol compliance","health":"warn","tokens":797,"nodes":329,"depth":9,"loss":0,"tcErrors":6,"failedBackends":[],"outBytes":{"c":4107,"rust":1749,"verilog":8763,"verilog_hir":1800,"zig":3097}},{"path":"specs/fpga/testbench/bootrom_tb.t27","category":"specs/fpga","name":"bootrom_tb","module":"BootROM_Testbench","lines":87,"bytes":1856,"description":"t27/specs/fpga/testbench/bootrom_tb.t27 Boot ROM Testbench Tests boot sequence, reset vector, and initial program loading","health":"ok","tokens":312,"nodes":118,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2621,"rust":986,"verilog":5260,"verilog_hir":905,"zig":1374}},{"path":"specs/fpga/testbench/bridge_tb.t27","category":"specs/fpga","name":"bridge_tb","module":"Bridge_Testbench","lines":124,"bytes":2796,"description":"t27/specs/fpga/testbench/bridge_tb.t27 FPGA Bridge Testbench Tests data streaming, packet framing, and cross-domain transfers","health":"ok","tokens":468,"nodes":178,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3215,"rust":1043,"verilog":6665,"verilog_hir":937,"zig":2156}},{"path":"specs/fpga/testbench/clock_domain_tb.t27","category":"specs/fpga","name":"clock_domain_tb","module":"ClockDomain_Testbench","lines":109,"bytes":2439,"description":"t27/specs/fpga/testbench/clock_domain_tb.t27 Clock Domain Crossing Testbench Tests CDC synchronizers, handshake, and metastability protection","health":"ok","tokens":403,"nodes":152,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3049,"rust":937,"verilog":6163,"verilog_hir":881,"zig":1907}},{"path":"specs/fpga/testbench/cts_tb.t27","category":"specs/fpga","name":"cts_tb","module":"CTS_Testbench","lines":119,"bytes":2939,"description":"t27/specs/fpga/testbench/cts_tb.t27 Clock Tree Synthesis Testbench Tests clock buffer insertion, skew balancing, and latency estimation","health":"ok","tokens":478,"nodes":164,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3802,"rust":1274,"verilog":7549,"verilog_hir":935,"zig":2478}},{"path":"specs/fpga/testbench/dft_tb.t27","category":"specs/fpga","name":"dft_tb","module":"DFT_Testbench","lines":130,"bytes":3050,"description":"t27/specs/fpga/testbench/dft_tb.t27 Design-for-Test Testbench Tests scan chain insertion, BIST, and JTAG interface","health":"ok","tokens":519,"nodes":206,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3594,"rust":1446,"verilog":7375,"verilog_hir":977,"zig":3016}},{"path":"specs/fpga/testbench/fifo_tb.t27","category":"specs/fpga","name":"fifo_tb","module":"FIFO_Testbench","lines":163,"bytes":3606,"description":"t27/specs/fpga/testbench/fifo_tb.t27 FIFO Testbench Specification Tests sync/async FIFO operations, flags, overflow/underflow","health":"ok","tokens":620,"nodes":230,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3729,"rust":1265,"verilog":7838,"verilog_hir":1013,"zig":2645}},{"path":"specs/fpga/testbench/formal_tb.t27","category":"specs/fpga","name":"formal_tb","module":"Formal_Testbench","lines":132,"bytes":3125,"description":"t27/specs/fpga/testbench/formal_tb.t27 Formal Verification Testbench Tests SVA assertion generation, cover points, and proof properties","health":"ok","tokens":495,"nodes":151,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3564,"rust":1243,"verilog":7574,"verilog_hir":927,"zig":2431}},{"path":"specs/fpga/testbench/gf16_accel_tb.t27","category":"specs/fpga","name":"gf16_accel_tb","module":"GF16_Accel_Testbench","lines":136,"bytes":3024,"description":"t27/specs/fpga/testbench/gf16_accel_tb.t27 GF16 Accelerator Testbench Tests Golden Float 16 arithmetic: add, mul, MAC, phi identity","health":"warn","tokens":590,"nodes":215,"depth":7,"loss":0,"tcErrors":3,"failedBackends":[],"outBytes":{"c":3459,"rust":974,"verilog":7475,"verilog_hir":1211,"zig":2371}},{"path":"specs/fpga/testbench/hir_tb.t27","category":"specs/fpga","name":"hir_tb","module":"HIR_Testbench","lines":105,"bytes":2327,"description":"t27/specs/fpga/testbench/hir_tb.t27 Hardware IR (HIR) Testbench Tests HIR node types, module hierarchy, and code generation paths","health":"ok","tokens":405,"nodes":133,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3124,"rust":882,"verilog":6360,"verilog_hir":803,"zig":1879}},{"path":"specs/fpga/testbench/integration_tb.t27","category":"specs/fpga","name":"integration_tb","module":"Integration_Testbench","lines":126,"bytes":2971,"description":"t27/specs/fpga/testbench/integration_tb.t27 Full FPGA Integration Testbench Tests top-level connectivity: MAC + UART + SPI + Memory + Bridge","health":"ok","tokens":460,"nodes":215,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3568,"rust":807,"verilog":6725,"verilog_hir":723,"zig":2336}},{"path":"specs/fpga/testbench/linker_tb.t27","category":"specs/fpga","name":"linker_tb","module":"Linker_Testbench","lines":89,"bytes":2000,"description":"t27/specs/fpga/testbench/linker_tb.t27 Linker Testbench Tests symbol resolution, address assignment, and section merging","health":"ok","tokens":360,"nodes":122,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2875,"rust":838,"verilog":5851,"verilog_hir":800,"zig":1636}},{"path":"specs/fpga/testbench/mac_tb.t27","category":"specs/fpga","name":"mac_tb","module":"MAC_Testbench","lines":554,"bytes":17571,"description":"t27/specs/fpga/testbench/mac_tb.t27 MAC Unit Testbench Specification Tests ternary LUT multiplication, MAC operations, and accumulator 01 + 1/23 = 3 | TRINITY","health":"warn","tokens":2579,"nodes":886,"depth":10,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":13203,"rust":8923,"verilog":22584,"verilog_hir":1885,"zig":13457}},{"path":"specs/fpga/testbench/memory_tb.t27","category":"specs/fpga","name":"memory_tb","module":"Memory_Testbench","lines":136,"bytes":3129,"description":"t27/specs/fpga/testbench/memory_tb.t27 Memory Subsystem Testbench Tests BRAM, register file, and memory-mapped I/O operations","health":"ok","tokens":585,"nodes":253,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3550,"rust":911,"verilog":7285,"verilog_hir":1010,"zig":2461}},{"path":"specs/fpga/testbench/partition_tb.t27","category":"specs/fpga","name":"partition_tb","module":"Partition_Testbench","lines":94,"bytes":2266,"description":"t27/specs/fpga/testbench/partition_tb.t27 FPGA Partition Testbench Tests floorplanning regions, hierarchical partitioning, and resource budgeting","health":"ok","tokens":367,"nodes":120,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3288,"rust":900,"verilog":6434,"verilog_hir":847,"zig":1929}},{"path":"specs/fpga/testbench/placement_tb.t27","category":"specs/fpga","name":"placement_tb","module":"Placement_Testbench","lines":115,"bytes":2993,"description":"t27/specs/fpga/testbench/placement_tb.t27 FPGA Placement Testbench Tests placement grid, resource allocation, and density constraints","health":"ok","tokens":556,"nodes":200,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3907,"rust":1378,"verilog":7692,"verilog_hir":964,"zig":2620}},{"path":"specs/fpga/testbench/power_analysis_tb.t27","category":"specs/fpga","name":"power_analysis_tb","module":"PowerAnalysis_Testbench","lines":130,"bytes":3169,"description":"t27/specs/fpga/testbench/power_analysis_tb.t27 Power Analysis Testbench Tests utilization parsing, power estimation, and budget checking","health":"warn","tokens":545,"nodes":122,"depth":7,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":2961,"rust":407,"verilog":7059,"verilog_hir":587,"zig":2983}},{"path":"specs/fpga/testbench/power_tb.t27","category":"specs/fpga","name":"power_tb","module":"Power_Testbench","lines":118,"bytes":2926,"description":"t27/specs/fpga/testbench/power_tb.t27 Power Analysis Testbench Tests power domain management, gating, and estimation","health":"ok","tokens":472,"nodes":147,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3588,"rust":1279,"verilog":7079,"verilog_hir":1297,"zig":2165}},{"path":"specs/fpga/testbench/router_tb.t27","category":"specs/fpga","name":"router_tb","module":"Router_Testbench","lines":112,"bytes":2833,"description":"t27/specs/fpga/testbench/router_tb.t27 FPGA Router Testbench Tests routing graph construction, pathfinding, and congestion estimation","health":"ok","tokens":555,"nodes":197,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3754,"rust":1256,"verilog":7614,"verilog_hir":1051,"zig":2438}},{"path":"specs/fpga/testbench/simulator_tb.t27","category":"specs/fpga","name":"simulator_tb","module":"Simulator_Testbench","lines":92,"bytes":2071,"description":"t27/specs/fpga/testbench/simulator_tb.t27 Simulator Testbench Tests simulation engine: cycle stepping, event scheduling, and waveform output","health":"warn","tokens":328,"nodes":110,"depth":7,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":2762,"rust":876,"verilog":5834,"verilog_hir":826,"zig":1533}},{"path":"specs/fpga/testbench/spi_tb.t27","category":"specs/fpga","name":"spi_tb","module":"SPI_Testbench","lines":124,"bytes":2963,"description":"t27/specs/fpga/testbench/spi_tb.t27 SPI Master Testbench Specification Tests SPI transfer, clock generation, chip select, and mode handling","health":"ok","tokens":490,"nodes":168,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3299,"rust":1050,"verilog":7183,"verilog_hir":930,"zig":2314}},{"path":"specs/fpga/testbench/stdlib_tb.t27","category":"specs/fpga","name":"stdlib_tb","module":"Stdlib_Testbench","lines":112,"bytes":2313,"description":"t27/specs/fpga/testbench/stdlib_tb.t27 FPGA Stdlib Testbench Tests IP core catalog, parameter validation, and helper functions","health":"ok","tokens":474,"nodes":150,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3255,"rust":1032,"verilog":6248,"verilog_hir":848,"zig":1652}},{"path":"specs/fpga/testbench/ternary_isa_tb.t27","category":"specs/fpga","name":"ternary_isa_tb","module":"Ternary_ISA_Testbench","lines":148,"bytes":3310,"description":"t27/specs/fpga/testbench/ternary_isa_tb.t27 Ternary ISA Testbench Tests ternary instruction decode, ALU operations, and encoding","health":"ok","tokens":633,"nodes":202,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3569,"rust":1112,"verilog":7925,"verilog_hir":955,"zig":2155}},{"path":"specs/fpga/testbench/timing_tb.t27","category":"specs/fpga","name":"timing_tb","module":"Timing_Testbench","lines":98,"bytes":2410,"description":"t27/specs/fpga/testbench/timing_tb.t27 Timing Analysis Testbench Tests setup/hold checks, slack computation, and clock tree constraints","health":"ok","tokens":401,"nodes":132,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3213,"rust":849,"verilog":6745,"verilog_hir":852,"zig":2671}},{"path":"specs/fpga/testbench/top_tb.t27","category":"specs/fpga","name":"top_tb","module":"Top_Level_Testbench","lines":218,"bytes":5626,"description":"t27/specs/fpga/testbench/top_tb.t27 Top-Level FPGA Testbench Specification Tests complete FPGA system with UART, SPI, MAC, and bridge 01 + 1/23 = 3 | TRINITY","health":"ok","tokens":743,"nodes":253,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4707,"rust":2220,"verilog":8237,"verilog_hir":1406,"zig":4226}},{"path":"specs/fpga/testbench/uart_tb.t27","category":"specs/fpga","name":"uart_tb","module":"UART_Testbench","lines":397,"bytes":11180,"description":"t27/specs/fpga/testbench/uart_tb.t27 UART Testbench Specification Tests UART TX/RX functionality, state machines, and timing 01 + 1/23 = 3 | TRINITY","health":"warn","tokens":1340,"nodes":536,"depth":9,"loss":0,"tcErrors":3,"failedBackends":[],"outBytes":{"c":8993,"rust":5085,"verilog":13832,"verilog_hir":1500,"zig":8491}},{"path":"specs/fpga/testbench/vcd_conformance_compare_tb.t27","category":"specs/fpga","name":"vcd_conformance_compare_tb","module":"VcdConformanceCompare_Testbench","lines":107,"bytes":2611,"description":"t27/specs/fpga/testbench/vcd_conformance_compare_tb.t27 VCD Conformance Compare Testbench Tests the conformance comparison engine: batch compare, masking, value extraction","health":"warn","tokens":457,"nodes":133,"depth":9,"loss":0,"tcErrors":3,"failedBackends":[],"outBytes":{"c":2993,"rust":848,"verilog":6435,"verilog_hir":915,"zig":2135}},{"path":"specs/fpga/testbench/vcd_trace_tb.t27","category":"specs/fpga","name":"vcd_trace_tb","module":"VCD_Trace_Testbench","lines":86,"bytes":1929,"description":"t27/specs/fpga/testbench/vcd_trace_tb.t27 VCD Trace Testbench Tests waveform dump generation, signal hierarchy, and timestamp management","health":"ok","tokens":302,"nodes":101,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2778,"rust":777,"verilog":5761,"verilog_hir":910,"zig":1545}},{"path":"specs/fpga/timing.t27","category":"specs/fpga","name":"timing","module":"Timing","lines":376,"bytes":9419,"description":"t27/specs/fpga/timing.t27 T27 Static Timing Analysis Specification Estimates critical path, slack, and Fmax from HIR module structure Artix-7 timing model: LUT=0.1ns, BRAM=2.0ns, DSP=2.5ns, routing=0.3ns Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1593,"nodes":416,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":8514,"rust":4725,"verilog":15828,"verilog_hir":1970,"zig":8948}},{"path":"specs/fpga/top_level.t27","category":"specs/fpga","name":"top_level","module":"ZeroDSP_TopLevel","lines":220,"bytes":5551,"description":"t27/specs/fpga/top_level.t27 ZeroDSP FPGA Top Level Module Integrates MAC and UART for FPGA deployment 01 + 1/23 = 3 | TRINITY","health":"warn","tokens":797,"nodes":210,"depth":7,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":5130,"rust":1530,"verilog":10381,"verilog_hir":1542,"zig":5435}},{"path":"specs/fpga/uart.t27","category":"specs/fpga","name":"uart","module":"ZeroDSP_UART","lines":211,"bytes":5733,"description":"t27/specs/fpga/uart.t27 ZeroDSP FPGA UART Specification UART for debugging and communication 01 + 1/23 = 3 | TRINITY","health":"ok","tokens":821,"nodes":222,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5349,"rust":1953,"verilog":10437,"verilog_hir":1520,"zig":5131}},{"path":"specs/fpga/vcd_conformance_compare.t27","category":"specs/fpga","name":"vcd_conformance_compare","module":"VcdConformanceCompare","lines":436,"bytes":11816,"description":"t27/specs/fpga/vcd_conformance_compare.t27 T27 VCD Conformance Comparison Engine Compares VCD simulation traces against conformance vectors Parses VCD signal values and checks them against expected results Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":2127,"nodes":636,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":10157,"rust":4884,"verilog":20087,"verilog_hir":2289,"zig":10619}},{"path":"specs/fpga/vcd_trace.t27","category":"specs/fpga","name":"vcd_trace","module":"VcdTrace","lines":292,"bytes":7555,"description":"t27/specs/fpga/vcd_trace.t27 T27 VCD Trace Emission Specification Emits Value Change Dump traces from HIR simulation IEEE 1364-2001 VCD format with variable sections Uses flat arrays + count fields (parser-compatible)","health":"ok","tokens":1433,"nodes":341,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6712,"rust":3682,"verilog":12582,"verilog_hir":1281,"zig":6780}},{"path":"specs/fpga/verification/build_verify.t27","category":"specs/fpga","name":"build_verify","module":"BuildVerify","lines":87,"bytes":2190,"description":"t27/specs/fpga/verification/build_verify.t27 FPGA Build Verification Spec Validates all FPGA specs can generate Verilog and pass structural checks","health":"ok","tokens":303,"nodes":73,"depth":6,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3134,"rust":1057,"verilog":5830,"verilog_hir":429,"zig":1748}},{"path":"specs/git/diff.t27","category":"specs/git","name":"diff","module":"GitDiff","lines":230,"bytes":7832,"description":"specs/git/diff.t27 Git Diff Operations","health":"ok","tokens":1062,"nodes":248,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5499,"rust":1839,"verilog":10021,"verilog_hir":1158,"zig":5480}},{"path":"specs/git/operations.t27","category":"specs/git","name":"operations","module":"GitOperations","lines":176,"bytes":5799,"description":"specs/git/operations.t27 Git Command Operations","health":"ok","tokens":676,"nodes":128,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4710,"rust":1852,"verilog":8099,"verilog_hir":1516,"zig":4115}},{"path":"specs/git/schema.t27","category":"specs/git","name":"schema","module":"Git","lines":229,"bytes":6205,"description":"specs/git/schema.t27 Git Types Specification","health":"ok","tokens":743,"nodes":281,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4835,"rust":2134,"verilog":9132,"verilog_hir":408,"zig":4383}},{"path":"specs/git/status.t27","category":"specs/git","name":"status","module":"GitStatus","lines":189,"bytes":6535,"description":"specs/git/status.t27 Git Status Operations","health":"ok","tokens":978,"nodes":217,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4263,"rust":1157,"verilog":8591,"verilog_hir":941,"zig":4529}},{"path":"specs/github/auth.t27","category":"specs/github","name":"auth","module":"github","lines":27,"bytes":708,"description":"specs/github/auth.t27 GitHub Authentication for t27 Ring-072 - GitHub SSOT Integration","health":"ok","tokens":88,"nodes":20,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1528,"rust":478,"verilog":2579,"verilog_hir":374,"zig":518}},{"path":"specs/github/comments.t27","category":"specs/github","name":"comments","module":"github","lines":18,"bytes":398,"description":"specs/github/comments.t27","health":"ok","tokens":64,"nodes":10,"depth":4,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1271,"rust":309,"verilog":2210,"verilog_hir":337,"zig":387}},{"path":"specs/github/issues.t27","category":"specs/github","name":"issues","module":"github","lines":22,"bytes":484,"description":"specs/github/issues.t27","health":"ok","tokens":85,"nodes":14,"depth":4,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1479,"rust":402,"verilog":2545,"verilog_hir":358,"zig":495}},{"path":"specs/github/prs.t27","category":"specs/github","name":"prs","module":"github","lines":22,"bytes":472,"description":"specs/github/prs.t27","health":"ok","tokens":85,"nodes":14,"depth":4,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1461,"rust":402,"verilog":2566,"verilog_hir":349,"zig":495}},{"path":"specs/github/tests/e2e_full_flow.t27","category":"specs/github","name":"e2e_full_flow","module":"github","lines":122,"bytes":3778,"description":"specs/github/tests/e2e_full_flow.t27 End-to-End Full Flow Test for Ring-072 GitHub SSOT Tests: Auth -> Issue -> PR -> Comment -> Sync -> Cleanup Ring-074 - E2E Tests","health":"ok","tokens":634,"nodes":7,"depth":2,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":725,"rust":66,"verilog":1603,"verilog_hir":285,"zig":404}},{"path":"specs/graph/knowledge_graph.t27","category":"specs/graph","name":"knowledge_graph","module":"KnowledgeGraph","lines":406,"bytes":17952,"description":"Module: Knowledge Graph for Vector Symbolic Architecture","health":"ok","tokens":1292,"nodes":122,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7574,"rust":2691,"verilog":13215,"verilog_hir":1196,"zig":8316}},{"path":"specs/hslm/forward_pass.t27","category":"specs/hslm","name":"forward_pass","module":"ForwardPass","lines":306,"bytes":14239,"description":"Module: Minimal Forward Pass for LLM Inference","health":"ok","tokens":867,"nodes":70,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6300,"rust":2312,"verilog":10915,"verilog_hir":1492,"zig":5614}},{"path":"specs/interop/gf_cross_language.t27","category":"specs/interop","name":"gf_cross_language","module":"GFCrossLanguageConformance","lines":99,"bytes":3587,"description":"GoldenFloat Cross-Language Conformance All languages must produce identical bits for identical inputs. Reference constant: GF32(phi) = 0x3FCF1BBD","health":"warn","tokens":404,"nodes":153,"depth":9,"loss":2,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2442,"rust":66,"verilog":5045,"verilog_hir":281,"zig":2495}},{"path":"specs/isa/registers.t27","category":"specs/isa","name":"registers","module":"ISARegisters","lines":600,"bytes":20530,"description":"t27/specs/isa/registers.t27 TRI27 ISA Register File Specification Register definitions, Coptic encoding, and register file operations","health":"warn","tokens":2197,"nodes":472,"depth":8,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":9315,"rust":3860,"verilog":18480,"verilog_hir":999,"zig":11406}},{"path":"specs/isa/ternary_arithmetic.t27","category":"specs/isa","name":"ternary_arithmetic","module":"TernaryArithmetic","lines":501,"bytes":16736,"description":"t27/specs/isa/ternary_arithmetic.t27 Ternary Arithmetic Operations Specification Ring 064 - Balanced ternary arithmetic for T27 Defines addition, subtraction, multiplication, and division","health":"warn","tokens":2496,"nodes":438,"depth":12,"loss":0,"tcErrors":7,"failedBackends":[],"outBytes":{"c":7215,"rust":4218,"verilog":12655,"verilog_hir":1454,"zig":5261}},{"path":"specs/isa/ternary_bitwise.t27","category":"specs/isa","name":"ternary_bitwise","module":"TernaryBitwise","lines":404,"bytes":14046,"description":"t27/specs/isa/ternary_bitwise.t27 Ternary Bitwise Operations Specification Ring 068 - Bitwise AND, OR, XOR for ternary data Defines bitwise operations on ternary word representations","health":"warn","tokens":2418,"nodes":316,"depth":13,"loss":0,"tcErrors":7,"failedBackends":[],"outBytes":{"c":6163,"rust":1333,"verilog":10763,"verilog_hir":825,"zig":4118}},{"path":"specs/isa/ternary_control_flow.t27","category":"specs/isa","name":"ternary_control_flow","module":"TernaryControlFlow","lines":392,"bytes":12518,"description":"t27/specs/isa/ternary_control_flow.t27 Ternary Control Flow Specification Ring 090 - Control flow operations for ternary architecture Conditional jumps, branches, and call/return","health":"ok","tokens":1489,"nodes":358,"depth":16,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7731,"rust":3153,"verilog":14463,"verilog_hir":1544,"zig":8187}},{"path":"specs/isa/ternary_deque.t27","category":"specs/isa","name":"ternary_deque","module":"TernaryDeque","lines":498,"bytes":16013,"description":"t27/specs/isa/ternary_deque.t27 Ternary Deque Operations Specification Ring 088 - Double-ended queue operations for ternary data Defines deque with push/pop from both ends","health":"warn","tokens":2960,"nodes":383,"depth":10,"loss":0,"tcErrors":3,"failedBackends":[],"outBytes":{"c":6730,"rust":2799,"verilog":11790,"verilog_hir":1426,"zig":4688}},{"path":"specs/isa/ternary_encoding.t27","category":"specs/isa","name":"ternary_encoding","module":null,"lines":42,"bytes":944,"description":"specs/isa/ternary_encoding.t27 Ternary encoding: values in {-1, 0, +1}","health":"warn","tokens":152,"nodes":5,"depth":5,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":615,"rust":66,"verilog":1534,"verilog_hir":243,"zig":246}},{"path":"specs/isa/ternary_gates.t27","category":"specs/isa","name":"ternary_gates","module":"TernaryGates","lines":410,"bytes":14744,"description":"t27/specs/isa/ternary_gates.t27 Ternary Logic Gates Specification Ring 063 - Basic ternary logic gates for balanced ternary Defines AND, OR, NOT, and other fundamental operations","health":"ok","tokens":1888,"nodes":299,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6670,"rust":3005,"verilog":12594,"verilog_hir":865,"zig":4531}},{"path":"specs/isa/ternary_graph.t27","category":"specs/isa","name":"ternary_graph","module":"TernaryGraph","lines":131,"bytes":3644,"description":"t27/specs/isa/ternary_graph.t27 Ternary Graph Operations Specification Ring 083 - Graph algorithms on ternary-weighted adjacency 01 + 1/23 = 3 | TRINITY","health":"warn","tokens":576,"nodes":228,"depth":10,"loss":0,"tcErrors":7,"failedBackends":[],"outBytes":{"c":3979,"rust":1779,"verilog":6705,"verilog_hir":870,"zig":2260}},{"path":"specs/isa/ternary_hash.t27","category":"specs/isa","name":"ternary_hash","module":"TernaryHashTable","lines":167,"bytes":5024,"description":"t27/specs/isa/ternary_hash.t27 Ternary Hash Table Operations Specification Ring 087 - Hash table with ternary keys 01 + 1/23 = 3 | TRINITY","health":"warn","tokens":1044,"nodes":504,"depth":10,"loss":0,"tcErrors":9,"failedBackends":[],"outBytes":{"c":5304,"rust":2159,"verilog":8479,"verilog_hir":735,"zig":4666}},{"path":"specs/isa/ternary_memory.t27","category":"specs/isa","name":"ternary_memory","module":"ISAMemoryOps","lines":522,"bytes":18705,"description":"t27/specs/isa/ternary_memory.t27 Ternary Memory Specification Ring 089 - Memory operations for ternary architecture Load/store operations with ternary address and data","health":"warn","tokens":2662,"nodes":566,"depth":10,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":10308,"rust":4547,"verilog":17298,"verilog_hir":1740,"zig":11637}},{"path":"specs/isa/ternary_pattern_matching.t27","category":"specs/isa","name":"ternary_pattern_matching","module":"TernaryPatternMatching","lines":173,"bytes":5331,"description":"t27/specs/isa/ternary_pattern_matching.t27 Ternary Pattern Matching Operations Specification Ring 082 - Pattern matching algorithms for ternary sequences 01 + 1/23 = 3 | TRINITY","health":"warn","tokens":1222,"nodes":604,"depth":12,"loss":0,"tcErrors":10,"failedBackends":[],"outBytes":{"c":5598,"rust":2577,"verilog":10028,"verilog_hir":632,"zig":4780}},{"path":"specs/isa/ternary_search.t27","category":"specs/isa","name":"ternary_search","module":"TernarySearch","lines":165,"bytes":4613,"description":"t27/specs/isa/ternary_search.t27 Ternary Search Operations Specification Ring 081 - Search algorithms for ternary data 01 + 1/23 = 3 | TRINITY","health":"warn","tokens":934,"nodes":456,"depth":11,"loss":0,"tcErrors":9,"failedBackends":[],"outBytes":{"c":4926,"rust":2381,"verilog":8729,"verilog_hir":625,"zig":4085}},{"path":"specs/isa/ternary_set.t27","category":"specs/isa","name":"ternary_set","module":"TernarySet","lines":182,"bytes":5215,"description":"t27/specs/isa/ternary_set.t27 Ternary Set Operations Specification Ring 085 - Set operations on ternary-valued elements 01 + 1/23 = 3 | TRINITY","health":"warn","tokens":1247,"nodes":630,"depth":11,"loss":0,"tcErrors":22,"failedBackends":[],"outBytes":{"c":5726,"rust":2708,"verilog":9880,"verilog_hir":841,"zig":4832}},{"path":"specs/isa/ternary_shift.t27","category":"specs/isa","name":"ternary_shift","module":"TernaryShift","lines":420,"bytes":14016,"description":"t27/specs/isa/ternary_shift.t27 Ternary Shift and Rotate Operations Specification Ring 067 - Bitwise/Tritwise shift and rotate operations Defines how ternary words are shifted and rotated","health":"warn","tokens":2227,"nodes":425,"depth":9,"loss":0,"tcErrors":10,"failedBackends":[],"outBytes":{"c":6598,"rust":2384,"verilog":11836,"verilog_hir":860,"zig":4824}},{"path":"specs/isa/ternary_sorting.t27","category":"specs/isa","name":"ternary_sorting","module":"TernarySorting","lines":173,"bytes":4909,"description":"t27/specs/isa/ternary_sorting.t27 Ternary Sorting Operations Specification Ring 080 - Sorting algorithms for ternary data 01 + 1/23 = 3 | TRINITY","health":"warn","tokens":986,"nodes":515,"depth":12,"loss":0,"tcErrors":9,"failedBackends":[],"outBytes":{"c":5155,"rust":821,"verilog":8782,"verilog_hir":668,"zig":3761}},{"path":"specs/isa/ternary_tree.t27","category":"specs/isa","name":"ternary_tree","module":"TernaryTree","lines":141,"bytes":4645,"description":"t27/specs/isa/ternary_tree.t27 Ternary Tree Operations Specification Ring 084 - Tree algorithms on ternary-valued nodes 01 + 1/23 = 3 | TRINITY","health":"warn","tokens":805,"nodes":373,"depth":9,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":5095,"rust":2726,"verilog":7800,"verilog_hir":799,"zig":3362}},{"path":"specs/jit/jit.t27","category":"specs/jit","name":"jit","module":"jit","lines":875,"bytes":29745,"description":"t27/specs/jit/jit.t27 Trinity JIT Compiler Compiles VSA operations to native machine code 01234 567891011: V = n 12 3^k 13 14^m 15 16^p 17 e^q JIT (Just-In-Time) compiler for VSA operations: - Compiles high-level VSA operations to native x86-64 machine code","health":"ok","tokens":4872,"nodes":1944,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":11598,"rust":1464,"verilog":20222,"verilog_hir":420,"zig":22043}},{"path":"specs/lsp/client.t27","category":"specs/lsp","name":"client","module":"lsp-client","lines":505,"bytes":14486,"description":"lsp/client.t27 — LSP Client Configuration and Capabilities Client-side protocol, capabilities, and configuration management","health":"ok","tokens":1870,"nodes":587,"depth":12,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":13232,"rust":7805,"verilog":20858,"verilog_hir":1139,"zig":11121}},{"path":"specs/lsp/language.t27","category":"specs/lsp","name":"language","module":"lsp-language","lines":527,"bytes":14976,"description":"lsp/language.t27 — Language Server Feature Mappings Language ID mappings, file extensions, and feature associations","health":"ok","tokens":2248,"nodes":700,"depth":14,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":14355,"rust":6646,"verilog":22788,"verilog_hir":1187,"zig":11639}},{"path":"specs/lsp/protocol.t27","category":"specs/lsp","name":"protocol","module":"lsp-protocol","lines":681,"bytes":20652,"description":"lsp/protocol.t27 — JSON-RPC 2.0 Protocol Mapping LSP message handling over JSON-RPC transport layer","health":"ok","tokens":2819,"nodes":957,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":16726,"rust":7069,"verilog":28373,"verilog_hir":2029,"zig":14177}},{"path":"specs/lsp/schema.t27","category":"specs/lsp","name":"schema","module":"lsp-schema","lines":589,"bytes":15702,"description":"lsp/schema.t27 — LSP Base Types Position, Range, Diagnostic definitions for Language Server Protocol","health":"ok","tokens":2768,"nodes":931,"depth":12,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":15688,"rust":7675,"verilog":26710,"verilog_hir":1560,"zig":12389}},{"path":"specs/lsp/server.t27","category":"specs/lsp","name":"server","module":"lsp-server","lines":555,"bytes":15397,"description":"lsp/server.t27 — LSP Server Lifecycle and Methods Server-side protocol initialization, lifecycle, and request handling","health":"ok","tokens":2090,"nodes":663,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":15015,"rust":7144,"verilog":25046,"verilog_hir":1880,"zig":12436}},{"path":"specs/math/constants.t27","category":"specs/math","name":"constants","module":"Constants","lines":459,"bytes":16818,"description":"t27/specs/math/constants.t27 Mathematical Constants for Trinity Computing phi^2 + 1/phi^2 = 3 | Sacred constants for ternary computing","health":"ok","tokens":1697,"nodes":495,"depth":13,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":8751,"rust":3267,"verilog":17598,"verilog_hir":671,"zig":10484}},{"path":"specs/math/e8_lie_algebra.t27","category":"specs/math","name":"e8_lie_algebra","module":"E8LieAlgebra","lines":390,"bytes":14031,"description":"t27/specs/math/e8_lie_algebra.t27 E8 Exceptional Lie Algebra -- Root System, Cartan Matrix, Eigenvalues Direction A (Priority 2) of PROJECT KEPLER->NEWTON E8 is the largest exceptional simple Lie group. Its root system contains golden ratio phi as a structural invariant through the H4 Coxeter subgroup. Key results verified computationally:","health":"ok","tokens":1848,"nodes":237,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5966,"rust":2110,"verilog":12813,"verilog_hir":744,"zig":8135}},{"path":"specs/math/gf_competitive.t27","category":"specs/math","name":"gf_competitive","module":"GFCompetitive","lines":122,"bytes":4213,"description":"t27/specs/math/gf_competitive.t27 GoldenFloat Competitive Analysis -- GF vs Posit vs IEEE 754 MATH-COMPETITIVE-001 -- Decode latency, parallelism, hardware efficiency Ring 051: Competitive analysis showing GF's structural advantages Main result: GF has O(1) parallel decode vs Posit's O(N) sequential","health":"ok","tokens":389,"nodes":127,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3367,"rust":350,"verilog":5829,"verilog_hir":303,"zig":2558}},{"path":"specs/math/pellis_precision_verify.t27","category":"specs/math","name":"pellis_precision_verify","module":"PellisPrecision","lines":204,"bytes":8117,"description":"t27/specs/math/pellis_precision_verify.t27 Arbitrary precision verification via GMP/MPFR reference NUMERIC-VERIF-001 -- Pre-registered checkpoint for CODATA 2026","health":"ok","tokens":609,"nodes":171,"depth":13,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5283,"rust":1963,"verilog":9248,"verilog_hir":545,"zig":5207}},{"path":"specs/math/phi_split_optimality.t27","category":"specs/math","name":"phi_split_optimality","module":"PhiSplitOptimality","lines":336,"bytes":12692,"description":"t27/specs/math/phi_split_optimality.t27 Phi-Split Theorems -- Self-Similarity + Optimal Rounding (CORRECTED) MATH-OPTIMALITY-001 -- Foundation for GoldenFloat being non-random THEOREM 1 (Golden Self-Similarity): phi is unique self-similar proportion for bit allocation THEOREM 2 (Optimal Rounding): round((N-1)/phi^2) minimizes phi-distance (7/7 match)","health":"ok","tokens":1298,"nodes":242,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6417,"rust":1654,"verilog":11868,"verilog_hir":778,"zig":8914}},{"path":"specs/math/phi_universal_attractor.t27","category":"specs/math","name":"phi_universal_attractor","module":"PhiUniversalAttractor","lines":333,"bytes":12924,"description":"t27/specs/math/phi_universal_attractor.t27 Phi Universal Attractor Theorems -- Theorem 3: phi as Universal Fixed-Point MATH-ATTRACTOR-001 -- Generative mechanism for phi proportion THEOREM 3: phi is the unique fixed point of balancing recursion f(x) = (x + x^-^1 + 1) / 2 This addresses the critic's concern that phi is \"fitting\" rather than a true mechanism.","health":"ok","tokens":1288,"nodes":213,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6557,"rust":1553,"verilog":12949,"verilog_hir":850,"zig":9686}},{"path":"specs/math/property_test_template.t27","category":"specs/math","name":"property_test_template","module":"PropertyTestTemplate","lines":513,"bytes":16168,"description":"t27/specs/math/property_test_template.t27 Property-Test Template for Conformance Vectors Ring 053 - Defines reusable property testing patterns This spec provides templates for property-based testing of T27 formats Properties: mathematical invariants that must hold for ALL valid inputs","health":"warn","tokens":2527,"nodes":900,"depth":13,"loss":0,"tcErrors":19,"failedBackends":[],"outBytes":{"c":10796,"rust":7168,"verilog":20816,"verilog_hir":1115,"zig":11639}},{"path":"specs/math/radix_economy.t27","category":"specs/math","name":"radix_economy","module":"RadixEconomy","lines":331,"bytes":10783,"description":"t27/specs/math/radix_economy.t27 Radix Economy Formal Spec -- Information-Theoretic Basis for Base-3 Computing E(b) = ln(b)/b, maximized at b = e ~= 2.71828 E(3)/E(e) >= 99.5%, E(3)/E(2) = 1.054 (5.4% advantage)","health":"ok","tokens":1291,"nodes":370,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6447,"rust":1515,"verilog":11770,"verilog_hir":889,"zig":8030}},{"path":"specs/math/sacred_physics.t27","category":"specs/math","name":"sacred_physics","module":"SacredPhysics","lines":461,"bytes":18991,"description":"t27/specs/math/sacred_physics.t27 Strand I 0 Mathematical Foundation Sacred Physics Layer: links TRINITY identity (phi) to gravity, cosmology and neurotime.","health":"ok","tokens":1630,"nodes":466,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9647,"rust":4299,"verilog":18572,"verilog_hir":685,"zig":11300}},{"path":"specs/math/zamolodchikov_e8.t27","category":"specs/math","name":"zamolodchikov_e8","module":"ZamolodchikovE8","lines":324,"bytes":11409,"description":"t27/specs/math/zamolodchikov_e8.t27 Zamolodchikov E8 Integrable Field Theory -- Mass Spectrum Direction A/E of PROJECT KEPLER->NEWTON In 1989, Zamolodchikov proved that the 2D Ising CFT perturbed by a magnetic field possesses E8 symmetry with exactly 8 stable particles. Their mass ratios are determined EXACTLY by E8 algebra -- not fitted.","health":"ok","tokens":1417,"nodes":406,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5961,"rust":2721,"verilog":12518,"verilog_hir":878,"zig":6983}},{"path":"specs/memory/formula_embed.t27","category":"specs/memory","name":"formula_embed","module":"FormulaEmbed","lines":202,"bytes":5767,"description":"Module: Formula Embedding for Cortical Semantic Map Cortical topographic map analog: - Formula features mapped to 27-dimensional embedding space - L2 normalization ensures unit vectors - Features: value, complexity, phi-distance, sector-id","health":"ok","tokens":826,"nodes":297,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4721,"rust":2690,"verilog":7789,"verilog_hir":932,"zig":3860}},{"path":"specs/memory/memory_primitives.t27","category":"specs/memory","name":"memory_primitives","module":"MemoryPrimitives","lines":180,"bytes":5305,"description":"t27/specs/memory/memory_primitives.t27 Native Memory Primitives Specification Ring 029 — Language-level remember/recall/forget/reflect Inspired by MemPalace associative memory architecture 01 + 1/23 = 3 | TRINITY","health":"ok","tokens":798,"nodes":392,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5583,"rust":1861,"verilog":9961,"verilog_hir":1039,"zig":3955}},{"path":"specs/memory/notebooklm.t27","category":"specs/memory","name":"notebooklm","module":"NotebookLM","lines":486,"bytes":18329,"description":"specs/memory/notebooklm.t27 NotebookLM Integration Specification Ring-071 - RAG-Backed Semantic Memory for t27 Defines interface to Google NotebookLM for persistent session memory","health":"ok","tokens":1974,"nodes":215,"depth":6,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9370,"rust":5655,"verilog":15743,"verilog_hir":1996,"zig":7514}},{"path":"specs/memory/semantic_search.t27","category":"specs/memory","name":"semantic_search","module":"SemanticSearch","lines":141,"bytes":4471,"description":"Module: Semantic Search via Hippocampus Pattern Completion CA3 pattern completion via Schaffer collaterals analog: - Query embedding compared via cosine similarity normalized by PHI - O(log n) search via HNSW index approximation - Returns top-k formula matches with similarity scores","health":"ok","tokens":564,"nodes":165,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3509,"rust":1696,"verilog":6185,"verilog_hir":540,"zig":2628}},{"path":"specs/ml/activation/elu_activation.t27","category":"specs/ml","name":"elu_activation","module":"Elu","lines":117,"bytes":5464,"description":"t27/specs/","health":"ok","tokens":321,"nodes":42,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2623,"rust":426,"verilog":5034,"verilog_hir":446,"zig":2439}},{"path":"specs/ml/activation/gelu_activation.t27","category":"specs/ml","name":"gelu_activation","module":"Gelu","lines":68,"bytes":3490,"description":"t27/specs/","health":"ok","tokens":178,"nodes":25,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1909,"rust":518,"verilog":3500,"verilog_hir":478,"zig":1487}},{"path":"specs/ml/activation/gelu_approx_activation.t27","category":"specs/ml","name":"gelu_approx_activation","module":"GeluApprox","lines":82,"bytes":4233,"description":"t27/specs/","health":"ok","tokens":257,"nodes":30,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1837,"rust":443,"verilog":3652,"verilog_hir":430,"zig":1700}},{"path":"specs/ml/activation/leaky_relu_activation.t27","category":"specs/ml","name":"leaky_relu_activation","module":"LeakyRelu","lines":95,"bytes":4569,"description":"t27/specs/","health":"ok","tokens":204,"nodes":28,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2344,"rust":433,"verilog":3717,"verilog_hir":458,"zig":1864}},{"path":"specs/ml/activation/relu_activation.t27","category":"specs/ml","name":"relu_activation","module":"Relu","lines":62,"bytes":3341,"description":"t27/specs/","health":"ok","tokens":123,"nodes":15,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1981,"rust":477,"verilog":2850,"verilog_hir":478,"zig":1068}},{"path":"specs/ml/activation/sigmoid_activation.t27","category":"specs/ml","name":"sigmoid_activation","module":"Sigmoid","lines":63,"bytes":3005,"description":"t27/specs/","health":"ok","tokens":109,"nodes":14,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1435,"rust":363,"verilog":2725,"verilog_hir":458,"zig":852}},{"path":"specs/ml/activation/silu_swish_activation.t27","category":"specs/ml","name":"silu_swish_activation","module":"SiluSwish","lines":88,"bytes":4161,"description":"t27/specs/","health":"ok","tokens":181,"nodes":24,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2429,"rust":422,"verilog":3771,"verilog_hir":457,"zig":1743}},{"path":"specs/ml/activation/silu_swish_vbt_activation.t27","category":"specs/ml","name":"silu_swish_vbt_activation","module":"SiluSwishVbt","lines":16,"bytes":674,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":644,"rust":66,"verilog":1580,"verilog_hir":253,"zig":341}},{"path":"specs/ml/activation/softmax.t27","category":"specs/ml","name":"softmax","module":"Softmax","lines":83,"bytes":4349,"description":"t27/specs/","health":"ok","tokens":198,"nodes":26,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2324,"rust":544,"verilog":3566,"verilog_hir":523,"zig":1732}},{"path":"specs/ml/activation/tanh_activation.t27","category":"specs/ml","name":"tanh_activation","module":"Tanh","lines":54,"bytes":2372,"description":"t27/specs/","health":"ok","tokens":88,"nodes":12,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1425,"rust":424,"verilog":2523,"verilog_hir":503,"zig":750}},{"path":"specs/ml/layers/avgpool2d_layer.t27","category":"specs/ml","name":"avgpool2d_layer","module":"Avgpool2d","lines":123,"bytes":6532,"description":"t27/specs/","health":"ok","tokens":668,"nodes":51,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3104,"rust":798,"verilog":5005,"verilog_hir":560,"zig":3433}},{"path":"specs/ml/layers/batchnorm_layer.t27","category":"specs/ml","name":"batchnorm_layer","module":"Batchnorm","lines":111,"bytes":5804,"description":"t27/specs/","health":"ok","tokens":420,"nodes":44,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3196,"rust":978,"verilog":4869,"verilog_hir":703,"zig":3070}},{"path":"specs/ml/layers/conv2d_layer.t27","category":"specs/ml","name":"conv2d_layer","module":"Conv2d","lines":92,"bytes":4236,"description":"t27/specs/","health":"ok","tokens":276,"nodes":38,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2851,"rust":1010,"verilog":4100,"verilog_hir":614,"zig":2018}},{"path":"specs/ml/layers/dense_layer.t27","category":"specs/ml","name":"dense_layer","module":"Dense","lines":90,"bytes":4267,"description":"t27/specs/","health":"ok","tokens":268,"nodes":36,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2775,"rust":965,"verilog":3980,"verilog_hir":612,"zig":1970}},{"path":"specs/ml/layers/dropout_layer.t27","category":"specs/ml","name":"dropout_layer","module":"Dropout","lines":70,"bytes":3702,"description":"t27/specs/","health":"ok","tokens":202,"nodes":24,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1972,"rust":556,"verilog":3597,"verilog_hir":540,"zig":1489}},{"path":"specs/ml/layers/embedding_layer.t27","category":"specs/ml","name":"embedding_layer","module":"Embedding","lines":121,"bytes":5785,"description":"t27/specs/","health":"ok","tokens":411,"nodes":45,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3078,"rust":597,"verilog":4748,"verilog_hir":453,"zig":3130}},{"path":"specs/ml/layers/flatten_layer.t27","category":"specs/ml","name":"flatten_layer","module":"Flatten","lines":114,"bytes":5624,"description":"t27/specs/","health":"ok","tokens":378,"nodes":40,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2547,"rust":521,"verilog":3976,"verilog_hir":425,"zig":2540}},{"path":"specs/ml/layers/layernorm_layer.t27","category":"specs/ml","name":"layernorm_layer","module":"Layernorm","lines":36,"bytes":1927,"description":"t27/specs/","health":"ok","tokens":90,"nodes":11,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1360,"rust":368,"verilog":2228,"verilog_hir":436,"zig":633}},{"path":"specs/ml/layers/maxpool2d_layer.t27","category":"specs/ml","name":"maxpool2d_layer","module":"Maxpool2d","lines":74,"bytes":4252,"description":"t27/specs/","health":"ok","tokens":389,"nodes":30,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1904,"rust":578,"verilog":3410,"verilog_hir":520,"zig":2172}},{"path":"specs/ml/layers/residual_connection.t27","category":"specs/ml","name":"residual_connection","module":"Residual","lines":27,"bytes":1408,"description":"t27/specs/","health":"ok","tokens":75,"nodes":8,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1152,"rust":228,"verilog":1890,"verilog_hir":443,"zig":563}},{"path":"specs/ml/loss/binary_crossentropy_loss.t27","category":"specs/ml","name":"binary_crossentropy_loss","module":"BinaryCe","lines":120,"bytes":5850,"description":"t27/specs/","health":"ok","tokens":346,"nodes":37,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3150,"rust":813,"verilog":4697,"verilog_hir":532,"zig":2772}},{"path":"specs/ml/loss/contrastive_loss.t27","category":"specs/ml","name":"contrastive_loss","module":"ContrastiveLoss","lines":35,"bytes":1858,"description":"t27/specs/","health":"ok","tokens":78,"nodes":10,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1329,"rust":320,"verilog":2183,"verilog_hir":425,"zig":590}},{"path":"specs/ml/loss/cross_entropy_loss.t27","category":"specs/ml","name":"cross_entropy_loss","module":"CrossEntropy","lines":82,"bytes":4052,"description":"t27/specs/","health":"ok","tokens":223,"nodes":31,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2651,"rust":847,"verilog":3709,"verilog_hir":531,"zig":1775}},{"path":"specs/ml/loss/huber_loss.t27","category":"specs/ml","name":"huber_loss","module":"HuberLoss","lines":35,"bytes":1823,"description":"t27/specs/","health":"ok","tokens":71,"nodes":10,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1262,"rust":290,"verilog":2123,"verilog_hir":383,"zig":541}},{"path":"specs/ml/loss/kl_divergence.t27","category":"specs/ml","name":"kl_divergence","module":"KlDivergence","lines":27,"bytes":1319,"description":"t27/specs/","health":"ok","tokens":55,"nodes":8,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1034,"rust":148,"verilog":1821,"verilog_hir":343,"zig":428}},{"path":"specs/ml/loss/mse_loss.t27","category":"specs/ml","name":"mse_loss","module":"MseLoss","lines":35,"bytes":1788,"description":"t27/specs/","health":"ok","tokens":70,"nodes":10,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1241,"rust":290,"verilog":2093,"verilog_hir":349,"zig":520}},{"path":"specs/ml/optimizer/adagrad.t27","category":"specs/ml","name":"adagrad","module":"Adagrad","lines":113,"bytes":5650,"description":"t27/specs/","health":"ok","tokens":300,"nodes":37,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2892,"rust":685,"verilog":4905,"verilog_hir":479,"zig":2570}},{"path":"specs/ml/optimizer/adam.t27","category":"specs/ml","name":"adam","module":"Adam","lines":47,"bytes":2422,"description":"t27/specs/","health":"ok","tokens":123,"nodes":20,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1540,"rust":512,"verilog":2668,"verilog_hir":438,"zig":746}},{"path":"specs/ml/optimizer/adamw.t27","category":"specs/ml","name":"adamw","module":"Adamw","lines":306,"bytes":12635,"description":"t27/specs/","health":"ok","tokens":1416,"nodes":127,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4803,"rust":419,"verilog":9828,"verilog_hir":439,"zig":6882}},{"path":"specs/ml/optimizer/lamb.t27","category":"specs/ml","name":"lamb","module":"Lamb","lines":134,"bytes":6076,"description":"t27/specs/","health":"ok","tokens":418,"nodes":56,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3376,"rust":997,"verilog":5463,"verilog_hir":588,"zig":3303}},{"path":"specs/ml/optimizer/lr_scheduler.t27","category":"specs/ml","name":"lr_scheduler","module":"Scheduler","lines":150,"bytes":7274,"description":"t27/specs/","health":"ok","tokens":643,"nodes":54,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3073,"rust":329,"verilog":5583,"verilog_hir":410,"zig":3576}},{"path":"specs/ml/optimizer/rmsprop.t27","category":"specs/ml","name":"rmsprop","module":"Rmsprop","lines":43,"bytes":2269,"description":"t27/specs/","health":"ok","tokens":87,"nodes":14,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1419,"rust":361,"verilog":2413,"verilog_hir":367,"zig":584}},{"path":"specs/ml/optimizer/sgd.t27","category":"specs/ml","name":"sgd","module":"Sgd","lines":104,"bytes":4735,"description":"t27/specs/","health":"ok","tokens":299,"nodes":40,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2595,"rust":664,"verilog":4100,"verilog_hir":466,"zig":2331}},{"path":"specs/ml/optimizer/sgd_momentum.t27","category":"specs/ml","name":"sgd_momentum","module":"SgdMomentum","lines":280,"bytes":13039,"description":"t27/specs/","health":"ok","tokens":1510,"nodes":113,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4752,"rust":410,"verilog":9393,"verilog_hir":459,"zig":7252}},{"path":"specs/ml/pathway/mlp.t27","category":"specs/ml","name":"mlp","module":"Mlp","lines":112,"bytes":5025,"description":"t27/specs/","health":"ok","tokens":287,"nodes":41,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2782,"rust":725,"verilog":4171,"verilog_hir":431,"zig":2446}},{"path":"specs/ml/recurrent/attention_mechanism.t27","category":"specs/ml","name":"attention_mechanism","module":"Attention","lines":95,"bytes":5259,"description":"t27/specs/","health":"ok","tokens":288,"nodes":30,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2808,"rust":864,"verilog":4479,"verilog_hir":683,"zig":2340}},{"path":"specs/ml/recurrent/bilstm.t27","category":"specs/ml","name":"bilstm","module":"Bilstm","lines":134,"bytes":6356,"description":"t27/specs/","health":"ok","tokens":591,"nodes":62,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3095,"rust":1146,"verilog":5499,"verilog_hir":455,"zig":3342}},{"path":"specs/ml/recurrent/gru_cell.t27","category":"specs/ml","name":"gru_cell","module":"Gru","lines":138,"bytes":6267,"description":"t27/specs/","health":"ok","tokens":731,"nodes":58,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3139,"rust":762,"verilog":4226,"verilog_hir":464,"zig":3867}},{"path":"specs/ml/recurrent/lstm_cell.t27","category":"specs/ml","name":"lstm_cell","module":"Lstm","lines":142,"bytes":6603,"description":"t27/specs/","health":"ok","tokens":616,"nodes":48,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3510,"rust":881,"verilog":4755,"verilog_hir":474,"zig":3629}},{"path":"specs/ml/recurrent/lstm_single.t27","category":"specs/ml","name":"lstm_single","module":"LstmCell","lines":127,"bytes":6220,"description":"t27/specs/","health":"ok","tokens":512,"nodes":57,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3167,"rust":1136,"verilog":5483,"verilog_hir":660,"zig":3201}},{"path":"specs/ml/recurrent/rnn_cell.t27","category":"specs/ml","name":"rnn_cell","module":"RnnCell","lines":120,"bytes":5605,"description":"t27/specs/","health":"ok","tokens":341,"nodes":44,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2931,"rust":893,"verilog":4759,"verilog_hir":572,"zig":2817}},{"path":"specs/ml/recurrent/self_attention.t27","category":"specs/ml","name":"self_attention","module":"SelfAttention","lines":57,"bytes":2662,"description":"t27/specs/","health":"ok","tokens":151,"nodes":24,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1783,"rust":790,"verilog":3100,"verilog_hir":416,"zig":884}},{"path":"specs/ml/recurrent/seq2seq.t27","category":"specs/ml","name":"seq2seq","module":"Seq2seq","lines":121,"bytes":6457,"description":"t27/specs/","health":"ok","tokens":463,"nodes":44,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2770,"rust":670,"verilog":4214,"verilog_hir":479,"zig":3471}},{"path":"specs/ml/rl/advantage_estimator.t27","category":"specs/ml","name":"advantage_estimator","module":"Advantage","lines":128,"bytes":6229,"description":"t27/specs/","health":"ok","tokens":494,"nodes":50,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3326,"rust":997,"verilog":5445,"verilog_hir":783,"zig":3390}},{"path":"specs/ml/rl/dqn.t27","category":"specs/ml","name":"dqn","module":"Dqn","lines":119,"bytes":5990,"description":"t27/specs/","health":"ok","tokens":473,"nodes":54,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3023,"rust":1121,"verilog":5388,"verilog_hir":562,"zig":3064}},{"path":"specs/ml/rl/dqn_target_network.t27","category":"specs/ml","name":"dqn_target_network","module":"DqnTarget","lines":112,"bytes":5334,"description":"t27/specs/","health":"ok","tokens":307,"nodes":41,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3104,"rust":714,"verilog":4833,"verilog_hir":521,"zig":2620}},{"path":"specs/ml/rl/ppo_actor.t27","category":"specs/ml","name":"ppo_actor","module":"PpoActor","lines":155,"bytes":7732,"description":"t27/specs/","health":"ok","tokens":527,"nodes":54,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4086,"rust":1306,"verilog":6536,"verilog_hir":696,"zig":4029}},{"path":"specs/ml/rl/ppo_clip_loss.t27","category":"specs/ml","name":"ppo_clip_loss","module":"PpoClipLoss","lines":141,"bytes":7501,"description":"t27/specs/","health":"ok","tokens":621,"nodes":49,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3785,"rust":1261,"verilog":6445,"verilog_hir":1141,"zig":4157}},{"path":"specs/ml/rl/ppo_critic.t27","category":"specs/ml","name":"ppo_critic","module":"PpoCritic","lines":106,"bytes":5376,"description":"t27/specs/","health":"ok","tokens":388,"nodes":34,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3093,"rust":855,"verilog":4217,"verilog_hir":719,"zig":2660}},{"path":"specs/ml/rl/sac_actor.t27","category":"specs/ml","name":"sac_actor","module":"SacActor","lines":128,"bytes":6327,"description":"t27/specs/","health":"ok","tokens":435,"nodes":46,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3300,"rust":1192,"verilog":5142,"verilog_hir":641,"zig":3099}},{"path":"specs/ml/rl/sac_critic.t27","category":"specs/ml","name":"sac_critic","module":"SacCritic","lines":164,"bytes":7664,"description":"t27/specs/","health":"ok","tokens":591,"nodes":74,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4134,"rust":1237,"verilog":7288,"verilog_hir":1022,"zig":4633}},{"path":"specs/ml/transformer/encoder_block.t27","category":"specs/ml","name":"encoder_block","module":"EncoderBlock","lines":117,"bytes":5609,"description":"t27/specs/","health":"ok","tokens":348,"nodes":44,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3182,"rust":1169,"verilog":5006,"verilog_hir":661,"zig":2760}},{"path":"specs/ml/transformer/feed_forward.t27","category":"specs/ml","name":"feed_forward","module":"FeedForward","lines":437,"bytes":20203,"description":"t27/specs/ml/transformer/feed_forward.t27","health":"ok","tokens":2120,"nodes":176,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7633,"rust":2606,"verilog":13909,"verilog_hir":1107,"zig":10269}},{"path":"specs/ml/transformer/feed_forward_network.t27","category":"specs/ml","name":"feed_forward_network","module":"FeedForward","lines":42,"bytes":2390,"description":"t27/specs/","health":"ok","tokens":109,"nodes":13,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1562,"rust":415,"verilog":2445,"verilog_hir":489,"zig":695}},{"path":"specs/ml/transformer/mha_block.t27","category":"specs/ml","name":"mha_block","module":"MHABlock","lines":383,"bytes":17868,"description":"t27/specs/ml/transformer/mha_block.t27","health":"ok","tokens":1636,"nodes":135,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6676,"rust":2274,"verilog":12594,"verilog_hir":949,"zig":8243}},{"path":"specs/ml/transformer/multi_head_attention.t27","category":"specs/ml","name":"multi_head_attention","module":"MultiHeadAttn","lines":38,"bytes":1951,"description":"t27/specs/","health":"ok","tokens":96,"nodes":13,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1396,"rust":387,"verilog":2292,"verilog_hir":443,"zig":650}},{"path":"specs/ml/transformer/multi_head_attn.t27","category":"specs/ml","name":"multi_head_attn","module":"MultiHeadAttention","lines":464,"bytes":21855,"description":"t27/specs/ml/transformer/multi_head_attn.t27","health":"ok","tokens":2162,"nodes":171,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":8143,"rust":3550,"verilog":14934,"verilog_hir":1407,"zig":10172}},{"path":"specs/ml/transformer/norm.t27","category":"specs/ml","name":"norm","module":"LayerNorm","lines":478,"bytes":23093,"description":"t27/specs/ml/transformer/norm.t27","health":"ok","tokens":2649,"nodes":194,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":8202,"rust":2793,"verilog":14831,"verilog_hir":1174,"zig":11913}},{"path":"specs/ml/transformer/positional_enc.t27","category":"specs/ml","name":"positional_enc","module":"PositionalEncoding","lines":379,"bytes":17042,"description":"t27/specs/ml/transformer/positional_enc.t27","health":"ok","tokens":1775,"nodes":141,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6838,"rust":2056,"verilog":11916,"verilog_hir":1161,"zig":8671}},{"path":"specs/ml/transformer/positional_encoding.t27","category":"specs/ml","name":"positional_encoding","module":"PositionalEnc","lines":42,"bytes":2296,"description":"t27/specs/","health":"ok","tokens":81,"nodes":13,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1481,"rust":355,"verilog":2381,"verilog_hir":393,"zig":605}},{"path":"specs/neural/forward_pass.t27","category":"specs/neural","name":"forward_pass","module":"forward_pass","lines":1095,"bytes":32248,"description":"Forward Pass Demo - VSA-based Neural Network Implements transformer-style forward pass using Vector Symbolic Architecture with multi-head attention, residual connections, and autoregressive generation Author: Dmitrii Vasilev","health":"warn","tokens":6341,"nodes":431,"depth":15,"loss":3,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5600,"rust":2396,"verilog":8773,"verilog_hir":899,"zig":5519}},{"path":"specs/nn/attention.t27","category":"specs/nn","name":"attention","module":"SacredAttention","lines":634,"bytes":22599,"description":"t27/specs/nn/attention.t27 Sacred Attention Specification Multi-head attention with phi-RoPE and sacred scaling (d_k^(-phi^3))","health":"warn","tokens":3013,"nodes":857,"depth":14,"loss":0,"tcErrors":21,"failedBackends":[],"outBytes":{"c":13137,"rust":3111,"verilog":21220,"verilog_hir":1255,"zig":15220}},{"path":"specs/nn/hslm.t27","category":"specs/nn","name":"hslm","module":"HSLM","lines":632,"bytes":21721,"description":"t27/specs/nn/hslm.t27 HSLM (Hierarchical Sacred Learning Model) Specification Ternary neural network with sacred constants and VSA attention","health":"warn","tokens":2715,"nodes":721,"depth":14,"loss":0,"tcErrors":23,"failedBackends":[],"outBytes":{"c":12776,"rust":4567,"verilog":22012,"verilog_hir":1699,"zig":14844}},{"path":"specs/nn/phi_rope.t27","category":"specs/nn","name":"phi_rope","module":null,"lines":47,"bytes":1243,"description":"specs/nn/phi_rope.t27 φ-RoPE: Rotary Position Embedding using Golden Ratio θ_i = PHI^(-2i/d) instead of standard 10000^(-2i/d)","health":"warn","tokens":249,"nodes":5,"depth":5,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":607,"rust":66,"verilog":1502,"verilog_hir":243,"zig":238}},{"path":"specs/nn/sacred_attention.t27","category":"specs/nn","name":"sacred_attention","module":null,"lines":50,"bytes":1242,"description":"specs/nn/sacred_attention.t27 Sacred Attention: Multi-head attention with φ-based scaling scale = head_dim^(-PHI^3) instead of standard 1/sqrt(head_dim)","health":"warn","tokens":207,"nodes":5,"depth":5,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":615,"rust":66,"verilog":1534,"verilog_hir":243,"zig":246}},{"path":"specs/numeric/bigint.t27","category":"specs/numeric","name":"bigint","module":"BigInt","lines":391,"bytes":14358,"description":"Module: Balanced Ternary BigInt","health":"ok","tokens":1096,"nodes":122,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6528,"rust":1377,"verilog":13322,"verilog_hir":864,"zig":7243}},{"path":"specs/numeric/formats.t27","category":"specs/numeric","name":"formats","module":"Formats","lines":400,"bytes":13628,"description":"specs/numeric/formats.t27 Format Conversion Utilities - GF16, f32, ternary encoding","health":"ok","tokens":1297,"nodes":153,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5040,"rust":839,"verilog":11117,"verilog_hir":698,"zig":6531}},{"path":"specs/numeric/gf12.t27","category":"specs/numeric","name":"gf12","module":"GF12","lines":482,"bytes":13953,"description":"t27/specs/numeric/gf12.t27 GoldenFloat12 -- 12-bit phi-structured floating point NUMERIC-STANDARD-001 -- Agent 4 (P1)","health":"warn","tokens":1979,"nodes":647,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9881,"rust":4274,"verilog":19274,"verilog_hir":1059,"zig":11436}},{"path":"specs/numeric/gf16.t27","category":"specs/numeric","name":"gf16","module":"triformat-gf16","lines":3392,"bytes":100829,"description":"gf16.t27 -- GoldenFloat16 Encode/Decode GF16: 16-bit floating point with 1 sign + 6 exponent + 9 mantissa Bit layout: [S(1) E(6) M(9)] = [15:15][14:9][8:0]","health":"ok","tokens":16123,"nodes":6614,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":87350,"rust":19630,"verilog":164571,"verilog_hir":2914,"zig":83405}},{"path":"specs/numeric/gf20.t27","category":"specs/numeric","name":"gf20","module":"GF20","lines":469,"bytes":13578,"description":"t27/specs/numeric/gf20.t27 GoldenFloat20 -- 20-bit phi-structured floating point NUMERIC-STANDARD-001 -- Agent 6 (P1)","health":"warn","tokens":1927,"nodes":639,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9537,"rust":4297,"verilog":18742,"verilog_hir":1062,"zig":10908}},{"path":"specs/numeric/gf24.t27","category":"specs/numeric","name":"gf24","module":"GF24","lines":469,"bytes":13626,"description":"t27/specs/numeric/gf24.t27 GoldenFloat24 -- 24-bit phi-structured floating point NUMERIC-STANDARD-001 -- Agent 7 (P1)","health":"warn","tokens":1933,"nodes":640,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9579,"rust":4328,"verilog":18783,"verilog_hir":1074,"zig":10948}},{"path":"specs/numeric/gf32.t27","category":"specs/numeric","name":"gf32","module":"GF32","lines":480,"bytes":14168,"description":"t27/specs/numeric/gf32.t27 GoldenFloat32 -- 32-bit phi-structured floating point NUMERIC-STANDARD-001 -- Agent 8 (P1)","health":"warn","tokens":1934,"nodes":641,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":9779,"rust":4324,"verilog":18871,"verilog_hir":1074,"zig":11206}},{"path":"specs/numeric/gf4.t27","category":"specs/numeric","name":"gf4","module":"GF4","lines":306,"bytes":9145,"description":"t27/specs/numeric/gf4.t27 GoldenFloat4 -- 4-bit phi-structured floating point NUMERIC-STANDARD-001 -- Agent 2 (P1)","health":"ok","tokens":1017,"nodes":265,"depth":14,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5819,"rust":1237,"verilog":10517,"verilog_hir":532,"zig":7261}},{"path":"specs/numeric/gf8.t27","category":"specs/numeric","name":"gf8","module":"GF8","lines":522,"bytes":15315,"description":"t27/specs/numeric/gf8.t27 GoldenFloat8 -- 8-bit phi-structured floating point NUMERIC-STANDARD-001 -- Agent 3 (P1)","health":"warn","tokens":2153,"nodes":661,"depth":11,"loss":0,"tcErrors":5,"failedBackends":[],"outBytes":{"c":10178,"rust":4222,"verilog":20381,"verilog_hir":1057,"zig":12139}},{"path":"specs/numeric/gf_competitive.t27","category":"specs/numeric","name":"gf_competitive","module":"GFCompetitive","lines":108,"bytes":3355,"description":"t27/specs/numeric/gf_competitive.t27 GF Competitive Analysis Specification Ring 028 — Proving GoldenFloat is not random 01 + 1/23 = 3 | TRINITY","health":"ok","tokens":468,"nodes":206,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3940,"rust":1334,"verilog":7298,"verilog_hir":809,"zig":2606}},{"path":"specs/numeric/goldenfloat_family.t27","category":"specs/numeric","name":"goldenfloat_family","module":"GoldenFloatFamily","lines":411,"bytes":14364,"description":"t27/specs/numeric/goldenfloat_family.t27 GoldenFloat Family -- phi-structured floating point formats NUMERIC-STANDARD-001 -- Agent 1 (P0)","health":"warn","tokens":1631,"nodes":353,"depth":11,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":9923,"rust":3786,"verilog":18847,"verilog_hir":686,"zig":11696}},{"path":"specs/numeric/pellis_verify.t27","category":"specs/numeric","name":"pellis_verify","module":"PellisVerify","lines":65,"bytes":2169,"description":"t27/specs/numeric/pellis_verify.t27 Pellis Verification Specification Phase 2 of GF Competitive Analysis (issue #289) GMP-backed high-precision verification of Pellis closed form 01 + 1/23 = 3 | TRINITY","health":"ok","tokens":259,"nodes":109,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2881,"rust":616,"verilog":5233,"verilog_hir":477,"zig":1693}},{"path":"specs/numeric/phi_ratio.t27","category":"specs/numeric","name":"phi_ratio","module":"PhiRatio","lines":653,"bytes":24250,"description":"t27/specs/numeric/phi_ratio.t27 0-Ratio Proof 1 Derivation of GoldenFloat exp/mantissa split NUMERIC-STANDARD-001 2 Agent 9 (P0)","health":"ok","tokens":2437,"nodes":589,"depth":12,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":11804,"rust":4273,"verilog":21661,"verilog_hir":1108,"zig":16124}},{"path":"specs/numeric/requant_boundary.t27","category":"specs/numeric","name":"requant_boundary","module":"RequantBoundary","lines":217,"bytes":9731,"description":"t27/specs/numeric/requant_boundary.t27 Activation requantizer: the threshold boundary convention. Recorded because Wave 669 had to settle a semantic question with no specification to appeal to. Two independently written implementations of the same rule -- the emitted `activation_requant` RTL and the end-to-end testbench reference in sim/tb_data_check.v -- agreed everywhere except at","health":"ok","tokens":516,"nodes":114,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2889,"rust":1116,"verilog":6484,"verilog_hir":429,"zig":3669}},{"path":"specs/numeric/tf3.t27","category":"specs/numeric","name":"tf3","module":"triformat-tf3","lines":1662,"bytes":47300,"description":"tf3.t27 -- TF3 (Ternary Float 3) Format Specification 8-bit representation for ternary neural network weights Bit layout: [S(1) E(3) M(4)] = [7:7][6:4][3:0]","health":"warn","tokens":7841,"nodes":3092,"depth":9,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":41680,"rust":9224,"verilog":88731,"verilog_hir":1921,"zig":39192}},{"path":"specs/numeric/trinity_numeric_surface.t27","category":"specs/numeric","name":"trinity_numeric_surface","module":"trinity-numeric-surface","lines":38,"bytes":1703,"description":"trinity_numeric_surface.t27 -- Public numeric interchange policy (GoldenFloat-first) NUMERIC-STANDARD-001 -- integer-backed GF raw words are the portable surface","health":"ok","tokens":100,"nodes":27,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1079,"rust":390,"verilog":2065,"verilog_hir":275,"zig":590}},{"path":"specs/physics/chimera_best_gamma.t27","category":"specs/physics","name":"chimera_best_gamma","module":"chimera","lines":20,"bytes":468,"description":"t27/specs/physics/chimera_best_gamma.t27 Best gamma formula from PDG 2024 P35_new (Delta = 0.140%)","health":"ok","tokens":51,"nodes":11,"depth":6,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":936,"rust":158,"verilog":1580,"verilog_hir":281,"zig":331}},{"path":"specs/physics/e8_lqg_bridge.t27","category":"specs/physics","name":"e8_lqg_bridge","module":null,"lines":113,"bytes":2853,"description":null,"health":"warn","tokens":717,"nodes":63,"depth":7,"loss":27,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1061,"rust":66,"verilog":2520,"verilog_hir":243,"zig":683}},{"path":"specs/physics/formula_discovery.t27","category":"specs/physics","name":"formula_discovery","module":"FormulaDiscovery","lines":158,"bytes":4536,"description":"Formula Discovery v1.0 — ULTRA ENGINE Specification","health":"ok","tokens":973,"nodes":183,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4326,"rust":2204,"verilog":6528,"verilog_hir":835,"zig":3491}},{"path":"specs/physics/formula_registry.t27","category":"specs/physics","name":"formula_registry","module":null,"lines":213,"bytes":6261,"description":"Generated from FORMULA_TABLE_v06.md and FORMULA_TABLE_v07.md SSOT for Trinity formula discovery","health":"ok","tokens":830,"nodes":368,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4489,"rust":2960,"verilog":8502,"verilog_hir":1685,"zig":2855}},{"path":"specs/physics/gamma-conflict.t27","category":"specs/physics","name":"gamma-conflict","module":null,"lines":256,"bytes":6580,"description":null,"health":"warn","tokens":1429,"nodes":5,"depth":5,"loss":65,"tcErrors":0,"failedBackends":[],"outBytes":{"c":613,"rust":66,"verilog":1526,"verilog_hir":243,"zig":244}},{"path":"specs/physics/gamma_conjecture.t27","category":"specs/physics","name":"gamma_conjecture","module":"GammaConjecture","lines":277,"bytes":11590,"description":"t27/specs/physics/gamma_conjecture.t27 Strand I -- Loop Quantum Gravity Conjecture GI1: Barbero-Immirzi Parameter from Golden Section","health":"ok","tokens":1018,"nodes":196,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6527,"rust":2239,"verilog":12477,"verilog_hir":775,"zig":7529}},{"path":"specs/physics/gi1_analysis.t27","category":"specs/physics","name":"gi1_analysis","module":"GI1Analysis","lines":238,"bytes":9763,"description":"t27/specs/physics/gi1_analysis.t27 GI1 Pre-Registration Analysis: γ_φ vs γ₁ comparison Three hypotheses tested against empirical data","health":"ok","tokens":741,"nodes":179,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6450,"rust":2499,"verilog":12296,"verilog_hir":598,"zig":6659}},{"path":"specs/physics/hslm_benchmark.t27","category":"specs/physics","name":"hslm_benchmark","module":null,"lines":90,"bytes":2287,"description":null,"health":"warn","tokens":439,"nodes":18,"depth":6,"loss":23,"tcErrors":0,"failedBackends":[],"outBytes":{"c":789,"rust":66,"verilog":1921,"verilog_hir":243,"zig":381}},{"path":"specs/physics/lqg_cs_bridge.t27","category":"specs/physics","name":"lqg_cs_bridge","module":null,"lines":141,"bytes":4725,"description":null,"health":"warn","tokens":823,"nodes":7,"depth":3,"loss":15,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1076,"rust":66,"verilog":2051,"verilog_hir":243,"zig":611}},{"path":"specs/physics/lqg_entropy.t27","category":"specs/physics","name":"lqg_entropy","module":null,"lines":67,"bytes":2841,"description":"t27/specs/physics/lqg_entropy.t27 KEPLER->NEWTON Direction B: LQG -> gamma (PRIORITY 3 - HONEST INQUIRY) Status: Final v2.2 Date: 2026-04-05 HONEST ASSESSMENT: gamma = phi^-^3 does NOT come from CS theory. This spec documents research needed to find:","health":"ok","tokens":16,"nodes":7,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1068,"rust":66,"verilog":2054,"verilog_hir":243,"zig":603}},{"path":"specs/physics/p2_brain_physics.t27","category":"specs/physics","name":"p2_brain_physics","module":"P2Brain","lines":165,"bytes":6932,"description":"Module: P2 Brain -- Physics Engine Framework","health":"ok","tokens":600,"nodes":59,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4055,"rust":1293,"verilog":6443,"verilog_hir":540,"zig":3285}},{"path":"specs/physics/pellis-formulas.t27","category":"specs/physics","name":"pellis-formulas","module":"PellisFormulas","lines":67,"bytes":2116,"description":"t27/specs/physics/pellis-formulas.t27 Trinity x Pellis hybrid -- thin-structure formulas anchored on L5 (issue #277). SSOT: invariants tie Pell ladders to phi; observables are references for tri math compare.","health":"ok","tokens":233,"nodes":51,"depth":6,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2513,"rust":724,"verilog":4518,"verilog_hir":362,"zig":1727}},{"path":"specs/physics/quantum.t27","category":"specs/physics","name":"quantum","module":null,"lines":72,"bytes":1715,"description":null,"health":"warn","tokens":268,"nodes":28,"depth":7,"loss":19,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1222,"rust":66,"verilog":3063,"verilog_hir":243,"zig":579}},{"path":"specs/physics/sacred_verification.t27","category":"specs/physics","name":"sacred_verification","module":"SacredVerification","lines":605,"bytes":24309,"description":"t27/specs/physics/sacred_verification.t27 KEPLER->NEWTON Sacred Formula Verification Spec Status: Final v1.0 Date: 2026-04-06 This spec defines the verification framework for [planned] 152 Sacred Formula equations (N implemented today). It provides a structured approach to testing which","health":"ok","tokens":2193,"nodes":103,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3293,"rust":1689,"verilog":5837,"verilog_hir":637,"zig":7227}},{"path":"specs/physics/su2_chern_simons.t27","category":"specs/physics","name":"su2_chern_simons","module":"SU2ChernSimons","lines":349,"bytes":12227,"description":"t27/specs/physics/su2_chern_simons.t27 SU(2)_k Chern-Simons Theory -- Topological QFT Foundation Direction F (Priority 1) of PROJECT KEPLER->NEWTON This module formalizes the PROVEN THEOREM: golden ratio phi emerges from SU(2) Chern-Simons theory at level k=3 as quantum dimension of Fibonacci anyons. This is NOT numerology -- it is a mathematical","health":"ok","tokens":1285,"nodes":394,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6939,"rust":2857,"verilog":14220,"verilog_hir":1037,"zig":7699}},{"path":"specs/physics/zamolodchikov_4d_conjecture.t27","category":"specs/physics","name":"zamolodchikov_4d_conjecture","module":"Zamolodchikov4DConjecture","lines":254,"bytes":10731,"description":"t27/specs/physics/zamolodchikov_4d_conjecture.t27 4D Zamolodchikov Conjecture -- The Breakthrough Hypothesis Direction E of PROJECT KEPLER->NEWTON HYPOTHESIS: A 4D quantum field theory with E8 integrable structure fixes the fundamental constants of the Standard Model through the same algebraic mechanism that fixes the 8 Zamolodchikov masses in 2D.","health":"ok","tokens":401,"nodes":66,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2974,"rust":1216,"verilog":5384,"verilog_hir":420,"zig":2806}},{"path":"specs/pins/emitter_xdc.t27","category":"specs/pins","name":"emitter_xdc","module":"EmitterXDC","lines":295,"bytes":9345,"description":"t27/specs/pins/emitter_xdc.t27 XDC Constraint Emitter from Pins IR Generates nextpnr-compatible XDC from Design/Binding/ClockDef Output format matches t27c fpga-build --minimal exactly","health":"warn","tokens":1591,"nodes":546,"depth":12,"loss":0,"tcErrors":3,"failedBackends":[],"outBytes":{"c":8597,"rust":4893,"verilog":14695,"verilog_hir":3460,"zig":8775}},{"path":"specs/pins/ir.t27","category":"specs/pins","name":"ir","module":"PinsIR","lines":391,"bytes":11981,"description":"t27/specs/pins/ir.t27 Pins Intermediate Representation (IR) Models FPGA pin assignments, I/O standards, clock constraints","health":"warn","tokens":2166,"nodes":503,"depth":17,"loss":0,"tcErrors":6,"failedBackends":[],"outBytes":{"c":8889,"rust":5188,"verilog":15838,"verilog_hir":1465,"zig":10857}},{"path":"specs/pins/parser.t27","category":"specs/pins","name":"parser","module":"PinsParser","lines":583,"bytes":17861,"description":"t27/specs/pins/parser.t27 Pins Parser for Trinity t27 Parse .t27 pin specifications into Pins IR","health":"warn","tokens":2695,"nodes":36,"depth":3,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1240,"rust":663,"verilog":2349,"verilog_hir":249,"zig":665}},{"path":"specs/pipeline/benchmarks.t27","category":"specs/pipeline","name":"benchmarks","module":"PipelineBenchmarks","lines":86,"bytes":2598,"description":"t27/specs/pipeline/benchmarks.t27 Pipeline Performance Benchmark Specification Ring 028 — tri bench run performance targets 01 + 1/23 = 3 | TRINITY","health":"ok","tokens":333,"nodes":144,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3315,"rust":904,"verilog":5548,"verilog_hir":706,"zig":1856}},{"path":"specs/pipeline/e2e_test.t27","category":"specs/pipeline","name":"e2e_test","module":"PipelineE2E","lines":151,"bytes":5240,"description":"t27/specs/pipeline/e2e_test.t27 Pipeline E2E Test Specification Ring 028 — tri pipeline end-to-end testing 01 + 1/23 = 3 | TRINITY","health":"warn","tokens":876,"nodes":454,"depth":22,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":5838,"rust":3968,"verilog":8941,"verilog_hir":662,"zig":5155}},{"path":"specs/pipeline/experience_save.t27","category":"specs/pipeline","name":"experience_save","module":"ExperienceSave","lines":127,"bytes":3752,"description":"t27/specs/pipeline/experience_save.t27 Experience Save Command Specification Ring 028 — tri experience save CLI command 01 + 1/23 = 3 | TRINITY","health":"ok","tokens":553,"nodes":275,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4453,"rust":1261,"verilog":7552,"verilog_hir":837,"zig":2928}},{"path":"specs/portable/relay_observer.t27","category":"specs/portable","name":"relay_observer","module":"portable","lines":651,"bytes":21457,"description":"relay_observer.t27 0 WebSocket Relay Observer for BrowserOS A2A Integration Ring 32 — Cloud Orchestration 12 + 1/34 = 3 | TRINITY","health":"warn","tokens":3639,"nodes":521,"depth":8,"loss":1,"tcErrors":2,"failedBackends":[],"outBytes":{"c":9245,"rust":4949,"verilog":14917,"verilog_hir":1800,"zig":7400}},{"path":"specs/provider/adapters.t27","category":"specs/provider","name":"adapters","module":"provider-adapters","lines":619,"bytes":16564,"description":"provider/adapters.t27 — HTTP Adapter Specifications HTTP request/response adapters for AI provider APIs","health":"ok","tokens":2383,"nodes":810,"depth":13,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":15900,"rust":6978,"verilog":27151,"verilog_hir":2155,"zig":13040}},{"path":"specs/provider/schema.t27","category":"specs/provider","name":"schema","module":"provider-schema","lines":605,"bytes":15237,"description":"provider/schema.t27 — Provider Message Types AI provider message structures and model types","health":"ok","tokens":2382,"nodes":768,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":15057,"rust":7082,"verilog":26249,"verilog_hir":1761,"zig":12177}},{"path":"specs/provider/stream.t27","category":"specs/provider","name":"stream","module":"provider-stream","lines":612,"bytes":16334,"description":"provider/stream.t27 — SSE/Streaming Response Handling Server-Sent Events and streaming response processing","health":"ok","tokens":2475,"nodes":901,"depth":12,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":16395,"rust":7299,"verilog":27555,"verilog_hir":1786,"zig":13439}},{"path":"specs/provider/transform.t27","category":"specs/provider","name":"transform","module":"provider-transform","lines":522,"bytes":15346,"description":"provider/transform.t27 — Cross-Provider Message Transformations Message format transformations between different AI providers","health":"ok","tokens":2101,"nodes":668,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":15975,"rust":7471,"verilog":25080,"verilog_hir":2141,"zig":12502}},{"path":"specs/queen/brain_summaries.t27","category":"specs/queen","name":"brain_summaries","module":"BrainSummaries","lines":395,"bytes":14418,"description":"t27/specs/queen/brain_summaries.t27 Queen Brain Summaries Pipeline Specification Ring 061 - Episode summarization for Queen brain Defines how experience episodes are aggregated into summaries","health":"warn","tokens":1719,"nodes":374,"depth":12,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":7519,"rust":4491,"verilog":12338,"verilog_hir":1908,"zig":5872}},{"path":"specs/queen/lotus.t27","category":"specs/queen","name":"lotus","module":"QueenLotus","lines":804,"bytes":26358,"description":"t27/specs/queen/lotus.t27 Queen Lotus 6-Phase Orchestration Specification Self-improving agent orchestration with episode-based learning","health":"warn","tokens":3007,"nodes":838,"depth":16,"loss":0,"tcErrors":10,"failedBackends":[],"outBytes":{"c":16596,"rust":10794,"verilog":28993,"verilog_hir":2629,"zig":18925}},{"path":"specs/queen/task_analysis.t27","category":"specs/queen","name":"task_analysis","module":"queen-task-analysis","lines":178,"bytes":5115,"description":"queen/task_analysis.t27 — Task Priority Analysis for Queen Trinity S³AI — Cognitive Task Orchestration","health":"warn","tokens":647,"nodes":264,"depth":11,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":4681,"rust":2200,"verilog":7115,"verilog_hir":1074,"zig":3882}},{"path":"specs/runtime/execute.t27","category":"specs/runtime","name":"execute","module":"runtime-execute","lines":735,"bytes":19042,"description":"runtime/execute.t27 — Runtime Execution Specification Task execution, promises, cancellation, timeouts","health":"warn","tokens":3173,"nodes":943,"depth":9,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":17586,"rust":8024,"verilog":23486,"verilog_hir":2879,"zig":14146}},{"path":"specs/runtime/instance.t27","category":"specs/runtime","name":"instance","module":"runtime-instance","lines":780,"bytes":20135,"description":"runtime/instance.t27 — Runtime Instance Specification Instance registration, lookup, lifecycle management","health":"warn","tokens":3124,"nodes":801,"depth":9,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":15477,"rust":7972,"verilog":22363,"verilog_hir":2613,"zig":12330}},{"path":"specs/runtime/process.t27","category":"specs/runtime","name":"process","module":"runtime-process","lines":762,"bytes":19101,"description":"runtime/process.t27 — Runtime Process Specification Process spawning, termination, piping, PTY","health":"ok","tokens":2866,"nodes":892,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":19014,"rust":8115,"verilog":29640,"verilog_hir":3062,"zig":15007}},{"path":"specs/sacred/cosmology.t27","category":"specs/sacred","name":"cosmology","module":"TriCosmology","lines":16,"bytes":629,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":628,"rust":66,"verilog":1516,"verilog_hir":253,"zig":325}},{"path":"specs/sacred/dark_matter.t27","category":"specs/sacred","name":"dark_matter","module":"dark_matter","lines":16,"bytes":629,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":626,"rust":66,"verilog":1522,"verilog_hir":251,"zig":326}},{"path":"specs/sacred/gravity.t27","category":"specs/sacred","name":"gravity","module":"TriGravity","lines":16,"bytes":625,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":618,"rust":66,"verilog":1504,"verilog_hir":249,"zig":321}},{"path":"specs/sacred/monopoles.t27","category":"specs/sacred","name":"monopoles","module":"TriMonopoles","lines":16,"bytes":629,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":628,"rust":66,"verilog":1516,"verilog_hir":253,"zig":325}},{"path":"specs/sacred/quantum.t27","category":"specs/sacred","name":"quantum","module":"TriQuantum","lines":16,"bytes":625,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":618,"rust":66,"verilog":1504,"verilog_hir":249,"zig":321}},{"path":"specs/sacred/quantum_gravity.t27","category":"specs/sacred","name":"quantum_gravity","module":"quantum_gravity","lines":16,"bytes":637,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":646,"rust":66,"verilog":1546,"verilog_hir":259,"zig":334}},{"path":"specs/sacred/sacred_constants.t27","category":"specs/sacred","name":"sacred_constants","module":"SacredConstants","lines":24,"bytes":1160,"description":"t27/specs/","health":"ok","tokens":31,"nodes":8,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":818,"rust":160,"verilog":1751,"verilog_hir":259,"zig":375}},{"path":"specs/sacred/sacred_governance.t27","category":"specs/sacred","name":"sacred_governance","module":"SacredGovernance","lines":85,"bytes":3036,"description":"t27/specs/","health":"ok","tokens":263,"nodes":58,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2342,"rust":2036,"verilog":5144,"verilog_hir":261,"zig":1700}},{"path":"specs/sacred/sacred_identity.t27","category":"specs/sacred","name":"sacred_identity","module":"SacredIdentity","lines":77,"bytes":2757,"description":"t27/specs/","health":"ok","tokens":213,"nodes":48,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1972,"rust":1644,"verilog":4666,"verilog_hir":257,"zig":1452}},{"path":"specs/sacred/superconductivity.t27","category":"specs/sacred","name":"superconductivity","module":"TriSuperconductivity","lines":16,"bytes":645,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":668,"rust":66,"verilog":1564,"verilog_hir":269,"zig":341}},{"path":"specs/sandbox/health.t27","category":"specs/sandbox","name":"health","module":"sandbox","lines":43,"bytes":1234,"description":"SANDBOX-010 + SANDBOX-011: Sandbox Health Management","health":"warn","tokens":47,"nodes":10,"depth":5,"loss":3,"tcErrors":0,"failedBackends":[],"outBytes":{"c":809,"rust":119,"verilog":1736,"verilog_hir":257,"zig":376}},{"path":"specs/sandbox/https_enforce.t27","category":"specs/sandbox","name":"https_enforce","module":"sandbox","lines":224,"bytes":7449,"description":"SANDBOX-012: HTTPS Enforcement","health":"warn","tokens":609,"nodes":136,"depth":10,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3056,"rust":1109,"verilog":5635,"verilog_hir":492,"zig":2123}},{"path":"specs/sandbox/modules.t27","category":"specs/sandbox","name":"modules","module":"sandbox","lines":22,"bytes":536,"description":null,"health":"warn","tokens":22,"nodes":6,"depth":5,"loss":2,"tcErrors":0,"failedBackends":[],"outBytes":{"c":638,"rust":66,"verilog":1514,"verilog_hir":259,"zig":286}},{"path":"specs/sandbox/orphan_detection.t27","category":"specs/sandbox","name":"orphan_detection","module":"sandbox","lines":257,"bytes":9041,"description":"SANDBOX-011: Orphaned Session Detection","health":"warn","tokens":928,"nodes":72,"depth":10,"loss":3,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2714,"rust":1241,"verilog":4155,"verilog_hir":482,"zig":1363}},{"path":"specs/sandbox/session_timeout.t27","category":"specs/sandbox","name":"session_timeout","module":"sandbox","lines":195,"bytes":6252,"description":"SANDBOX-010: Session Timeout Enforcement","health":"warn","tokens":724,"nodes":44,"depth":7,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2241,"rust":963,"verilog":3438,"verilog_hir":396,"zig":909}},{"path":"specs/server/agent-runner.t27","category":"specs/server","name":"agent-runner","module":"AgentRunner","lines":249,"bytes":8126,"description":"specs/server/agent-runner.t27 Agent Runner Specification Constitutional Law #4: De-Zig-fication - .t27 is source of truth Constitutional Law #5: De-Zig Strict - no new Rust business logic","health":"ok","tokens":927,"nodes":411,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7161,"rust":4001,"verilog":11697,"verilog_hir":2228,"zig":5311}},{"path":"specs/server/api.t27","category":"specs/server","name":"api","module":"Api","lines":148,"bytes":4198,"description":"specs/server/api.t27 API Client Types Specification Constitutional Law #4: De-Zig-fication - .t27 is source of truth Constitutional Law #5: De-Zig Strict - no new Rust business logic","health":"ok","tokens":455,"nodes":175,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4080,"rust":2605,"verilog":6806,"verilog_hir":467,"zig":2987}},{"path":"specs/server/http.t27","category":"specs/server","name":"http","module":"server-http","lines":453,"bytes":12359,"description":"http.t27 — HTTP Server Specification HTTP listener, request/response handling, middleware","health":"ok","tokens":1763,"nodes":623,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":11889,"rust":4523,"verilog":18556,"verilog_hir":1109,"zig":9445}},{"path":"specs/server/mdns.t27","category":"specs/server","name":"mdns","module":"server-mdns","lines":567,"bytes":14817,"description":"mdns.t27 — mDNS Specification Multicast DNS service discovery, announcement, resolution","health":"ok","tokens":2238,"nodes":753,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":14727,"rust":5050,"verilog":24265,"verilog_hir":1972,"zig":11652}},{"path":"specs/server/project.t27","category":"specs/server","name":"project","module":"Project","lines":206,"bytes":6094,"description":"specs/server/project.t27 Project Management Specification Constitutional Law #4: De-Zig-fication - .t27 is source of truth Constitutional Law #5: De-Zig Strict - no new Rust business logic","health":"ok","tokens":922,"nodes":429,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5819,"rust":3044,"verilog":10236,"verilog_hir":897,"zig":4812}},{"path":"specs/server/provider.t27","category":"specs/server","name":"provider","module":"Provider","lines":257,"bytes":8105,"description":"specs/server/provider.t27 LLM Provider Configuration Specification Constitutional Law #4: De-Zig-fication - .t27 is source of truth Constitutional Law #5: De-Zig Strict - no new Rust business logic","health":"ok","tokens":1125,"nodes":539,"depth":11,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7709,"rust":3824,"verilog":12605,"verilog_hir":1014,"zig":6427}},{"path":"specs/server/router.t27","category":"specs/server","name":"router","module":"server-router","lines":575,"bytes":15018,"description":"router.t27 — HTTP Router Specification URL routing, pattern matching, parameter extraction","health":"warn","tokens":2576,"nodes":723,"depth":12,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":10621,"rust":5666,"verilog":18387,"verilog_hir":1602,"zig":8572}},{"path":"specs/server/routes.t27","category":"specs/server","name":"routes","module":"Routes","lines":36,"bytes":690,"description":"specs/server/routes.t27 T27 Server Routes Specification Constitutional Law #4: De-Zig-fication","health":"ok","tokens":107,"nodes":41,"depth":6,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1460,"rust":442,"verilog":2745,"verilog_hir":241,"zig":670}},{"path":"specs/server/session.t27","category":"specs/server","name":"session","module":"Session","lines":273,"bytes":8497,"description":"specs/server/session.t27 Session Management Specification Constitutional Law #4: De-Zig-fication - .t27 is source of truth Constitutional Law #5: De-Zig Strict - no new Rust business logic","health":"ok","tokens":1249,"nodes":564,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7884,"rust":3693,"verilog":12918,"verilog_hir":1191,"zig":6567}},{"path":"specs/server/sse.t27","category":"specs/server","name":"sse","module":"server-sse","lines":563,"bytes":15052,"description":"sse.t27 — Server-Sent Events Specification SSE connections, event streaming, reconnection handling","health":"warn","tokens":2341,"nodes":830,"depth":9,"loss":0,"tcErrors":1,"failedBackends":[],"outBytes":{"c":15274,"rust":5830,"verilog":25694,"verilog_hir":2698,"zig":12490}},{"path":"specs/server/vm.t27","category":"specs/server","name":"vm","module":"VM","lines":593,"bytes":22613,"description":"specs/server/vm.t27 VSA VM - Ternary Virtual Machine for Hyperdimensional Computing","health":"ok","tokens":3391,"nodes":711,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":8443,"rust":2769,"verilog":15636,"verilog_hir":1395,"zig":14627}},{"path":"specs/shell/environment.t27","category":"specs/shell","name":"environment","module":"ShellEnvironment","lines":304,"bytes":9509,"description":"specs/shell/environment.t27 Shell Environment Operations","health":"ok","tokens":1216,"nodes":436,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7694,"rust":1744,"verilog":12051,"verilog_hir":1325,"zig":7056}},{"path":"specs/shell/process.t27","category":"specs/shell","name":"process","module":"ShellProcess","lines":305,"bytes":8856,"description":"specs/shell/process.t27 Shell Process Operations","health":"warn","tokens":1142,"nodes":260,"depth":8,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5749,"rust":1718,"verilog":8735,"verilog_hir":991,"zig":5111}},{"path":"specs/shell/schema.t27","category":"specs/shell","name":"schema","module":"Shell","lines":406,"bytes":12544,"description":"specs/shell/schema.t27 Shell Types Specification","health":"ok","tokens":1715,"nodes":722,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":9473,"rust":2814,"verilog":15703,"verilog_hir":447,"zig":8568}},{"path":"specs/storage/kv.t27","category":"specs/storage","name":"kv","module":"StorageKv","lines":159,"bytes":5529,"description":"specs/storage/kv.t27 Key-Value Storage Operations","health":"ok","tokens":750,"nodes":202,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3968,"rust":727,"verilog":8728,"verilog_hir":731,"zig":3427}},{"path":"specs/storage/lock.t27","category":"specs/storage","name":"lock","module":"StorageLock","lines":179,"bytes":6388,"description":"specs/storage/lock.t27 Locking Primitives for Storage Operations","health":"ok","tokens":684,"nodes":216,"depth":6,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4812,"rust":1220,"verilog":8382,"verilog_hir":825,"zig":3646}},{"path":"specs/storage/migrate.t27","category":"specs/storage","name":"migrate","module":"StorageMigrate","lines":193,"bytes":6892,"description":"specs/storage/migrate.t27 Data Migration Operations","health":"ok","tokens":605,"nodes":141,"depth":10,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":4677,"rust":1798,"verilog":7715,"verilog_hir":902,"zig":3384}},{"path":"specs/storage/schema.t27","category":"specs/storage","name":"schema","module":"Storage","lines":146,"bytes":4070,"description":"specs/storage/schema.t27 Storage Types Specification","health":"ok","tokens":365,"nodes":135,"depth":8,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3181,"rust":1719,"verilog":5625,"verilog_hir":347,"zig":2708}},{"path":"specs/sync/index.t27","category":"specs/sync","name":"index","module":"sync-index","lines":779,"bytes":20867,"description":"sync/index.t27 — Sync Index Specification Sync operations, checkpointing, delta management","health":"warn","tokens":3255,"nodes":1094,"depth":10,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":20881,"rust":9853,"verilog":31727,"verilog_hir":3749,"zig":16949}},{"path":"specs/sync/schema.t27","category":"specs/sync","name":"schema","module":"sync-schema","lines":722,"bytes":19125,"description":"sync/schema.t27 — Sync Schema Specification Sync ID, state, event types for change synchronization","health":"warn","tokens":3020,"nodes":1060,"depth":9,"loss":0,"tcErrors":2,"failedBackends":[],"outBytes":{"c":19100,"rust":7372,"verilog":31235,"verilog_hir":3275,"zig":15310}},{"path":"specs/ternary/bigint.t27","category":"specs/ternary","name":"bigint","module":"TernaryBigInt","lines":1441,"bytes":43916,"description":"t27/specs/ternary/bigint.t27 TVC BigInt - Balanced Ternary Arbitrary Precision Arithmetic 01234 567891011: V = n 12 3^k 13 14^m 15 16^p 17 e^q Balanced Ternary representation: - Each trit has value {-1, 0, +1} - Number = Sigma(trit[i] * 3^i) for i = 0..n-1","health":"ok","tokens":7952,"nodes":3108,"depth":15,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":18071,"rust":686,"verilog":34613,"verilog_hir":255,"zig":31957}},{"path":"specs/ternary/hybrid_arithmetic.t27","category":"specs/ternary","name":"hybrid_arithmetic","module":"HybridArithmetic","lines":492,"bytes":18595,"description":"Module: Hybrid Arithmetic - Packed Storage with Unpacked Computation","health":"ok","tokens":1784,"nodes":169,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7724,"rust":1508,"verilog":15619,"verilog_hir":722,"zig":9910}},{"path":"specs/ternary/hybrid_bigint.t27","category":"specs/ternary","name":"hybrid_bigint","module":"hybrid_bigint","lines":1065,"bytes":30092,"description":"HybridBigInt: Optimal Memory/Speed Trade-off Uses packed storage (4.5x memory savings) with unpacked computation SIMD-accelerated operations for high performance Author: Dmitrii Vasilev","health":"warn","tokens":5666,"nodes":918,"depth":11,"loss":2,"tcErrors":0,"failedBackends":[],"outBytes":{"c":14061,"rust":1180,"verilog":28642,"verilog_hir":508,"zig":13241}},{"path":"specs/ternary/packed_trit.t27","category":"specs/ternary","name":"packed_trit","module":"PackedTrit","lines":430,"bytes":16022,"description":"Module: Packed Trit Encoding (5 trits per byte)","health":"ok","tokens":1417,"nodes":209,"depth":12,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7020,"rust":1899,"verilog":14472,"verilog_hir":968,"zig":9142}},{"path":"specs/test_framework/core.t27","category":"specs/test_framework","name":"core","module":"core","lines":317,"bytes":10877,"description":"t27 Math/Physics Test Framework - Core Ring 050: Test framework core per T27-MATH-PHYSICS-TEST-FRAMEWORK-SPEC.md Provides fundamental testing constructs for scientific computing","health":"warn","tokens":1697,"nodes":69,"depth":9,"loss":2,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2382,"rust":1402,"verilog":4025,"verilog_hir":374,"zig":1264}},{"path":"specs/test_framework/graph_drift_detection.t27","category":"specs/test_framework","name":"graph_drift_detection","module":"GraphDriftDetection","lines":650,"bytes":23027,"description":"t27/specs/test_framework/graph_drift_detection.t27 Ring 054 -- Graph Drift Detection for Structural Change Detection Monitors structural changes in mathematical and physics specifications over time","health":"ok","tokens":2911,"nodes":48,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2227,"rust":1627,"verilog":3918,"verilog_hir":318,"zig":1632}},{"path":"specs/test_framework/property_test_template.t27","category":"specs/test_framework","name":"property_test_template","module":"PBTTemplate","lines":433,"bytes":16346,"description":"t27/specs/test_framework/property_test_template.t27 Ring 052 -- Property-Based Testing Template for GoldenFloat Standardized PBT patterns following T27-MATH-PHYSICS-TEST-FRAMEWORK-SPEC.md","health":"warn","tokens":2112,"nodes":82,"depth":9,"loss":4,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2377,"rust":1398,"verilog":3938,"verilog_hir":582,"zig":2509}},{"path":"specs/test_framework/runner.t27","category":"specs/test_framework","name":"runner","module":"runner","lines":618,"bytes":19960,"description":"t27 Math/Physics Test Framework - Runner Ring 050: Test runner implementation per T27-MATH-PHYSICS-TEST-FRAMEWORK-SPEC.md Entry point for `tri test ` execution","health":"warn","tokens":3333,"nodes":38,"depth":5,"loss":3,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2047,"rust":1433,"verilog":3493,"verilog_hir":388,"zig":1609}},{"path":"specs/test_framework/verilog_bench_harness.t27","category":"specs/test_framework","name":"verilog_bench_harness","module":"VerilogBenchHarness","lines":460,"bytes":17844,"description":"t27/specs/test_framework/verilog_bench_harness.t27 Ring 053 -- Verilog Bench Harness for Hardware-in-the-Loop Testing Integration framework for testing GoldenFloat operations against Verilog RTL","health":"ok","tokens":2046,"nodes":16,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1294,"rust":525,"verilog":2327,"verilog_hir":320,"zig":888}},{"path":"specs/tools/registry.t27","category":"specs/tools","name":"registry","module":"ToolsRegistry","lines":523,"bytes":16726,"description":"specs/tools/registry.t27 Tools Registry Operations","health":"warn","tokens":2185,"nodes":183,"depth":7,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5988,"rust":2257,"verilog":8504,"verilog_hir":1464,"zig":5519}},{"path":"specs/tools/schema.t27","category":"specs/tools","name":"schema","module":"Tools","lines":523,"bytes":16580,"description":"specs/tools/schema.t27 Tools Types Specification","health":"warn","tokens":2006,"nodes":225,"depth":7,"loss":2,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5028,"rust":3417,"verilog":8735,"verilog_hir":390,"zig":4612}},{"path":"specs/tools/tri_to_t27_converter.t27","category":"specs/tools","name":"tri_to_t27_converter","module":"TriToT27Converter","lines":406,"bytes":16958,"description":"t27/specs/tools/tri_to_t27_converter.t27","health":"ok","tokens":1237,"nodes":145,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":7177,"rust":3063,"verilog":13435,"verilog_hir":1039,"zig":8490}},{"path":"specs/tri/agent/agent_run.t27","category":"specs/tri","name":"agent_run","module":"AgentRun","lines":16,"bytes":624,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":612,"rust":66,"verilog":1508,"verilog_hir":245,"zig":321}},{"path":"specs/tri/agent/agents.t27","category":"specs/tri","name":"agents","module":"agents","lines":16,"bytes":619,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":601,"rust":66,"verilog":1492,"verilog_hir":241,"zig":316}},{"path":"specs/tri/agent/autonomous_lifecycle.t27","category":"specs/tri","name":"autonomous_lifecycle","module":"TriAutonomousLifecycle","lines":73,"bytes":2953,"description":"t27/specs/","health":"ok","tokens":233,"nodes":56,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2279,"rust":1747,"verilog":4475,"verilog_hir":273,"zig":1484}},{"path":"specs/tri/agent/autonomous_universe.t27","category":"specs/tri","name":"autonomous_universe","module":"AutonomousUniverse","lines":16,"bytes":645,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":662,"rust":66,"verilog":1568,"verilog_hir":265,"zig":341}},{"path":"specs/tri/agent/eternal_monitor.t27","category":"specs/tri","name":"eternal_monitor","module":"EternalMonitor","lines":70,"bytes":2500,"description":"t27/specs/","health":"ok","tokens":205,"nodes":45,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1903,"rust":1493,"verilog":3737,"verilog_hir":257,"zig":1242}},{"path":"specs/tri/agent/experience_hooks.t27","category":"specs/tri","name":"experience_hooks","module":"experience_hooks","lines":16,"bytes":639,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":651,"rust":66,"verilog":1552,"verilog_hir":261,"zig":336}},{"path":"specs/tri/agent/faculty_board.t27","category":"specs/tri","name":"faculty_board","module":"FacultyBoard","lines":63,"bytes":2412,"description":"t27/specs/","health":"ok","tokens":173,"nodes":41,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1717,"rust":1222,"verilog":3573,"verilog_hir":253,"zig":1031}},{"path":"specs/tri/agent/governance_agent.t27","category":"specs/tri","name":"governance_agent","module":"GovernanceAgent","lines":98,"bytes":3447,"description":"t27/specs/","health":"ok","tokens":309,"nodes":69,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2316,"rust":2327,"verilog":5668,"verilog_hir":259,"zig":1970}},{"path":"specs/tri/agent/handoff.t27","category":"specs/tri","name":"handoff","module":"Handoff","lines":67,"bytes":2325,"description":"t27/specs/","health":"ok","tokens":243,"nodes":46,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1790,"rust":1543,"verilog":4229,"verilog_hir":243,"zig":1359}},{"path":"specs/tri/agent/memory.t27","category":"specs/tri","name":"memory","module":"memory","lines":16,"bytes":619,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":601,"rust":66,"verilog":1492,"verilog_hir":241,"zig":316}},{"path":"specs/tri/agent/swarm_agents.t27","category":"specs/tri","name":"swarm_agents","module":"SwarmAgents","lines":143,"bytes":5212,"description":"t27/specs/","health":"ok","tokens":494,"nodes":111,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3710,"rust":3822,"verilog":7397,"verilog_hir":251,"zig":2858}},{"path":"specs/tri/collections/array.t27","category":"specs/tri","name":"array","module":"TriArray","lines":123,"bytes":5387,"description":"t27/specs/","health":"ok","tokens":588,"nodes":48,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2683,"rust":1068,"verilog":5184,"verilog_hir":782,"zig":3095}},{"path":"specs/tri/collections/bitmap.t27","category":"specs/tri","name":"bitmap","module":"TriBitmap","lines":101,"bytes":4201,"description":"t27/specs/","health":"ok","tokens":262,"nodes":27,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2330,"rust":891,"verilog":3912,"verilog_hir":747,"zig":2006}},{"path":"specs/tri/collections/bitset.t27","category":"specs/tri","name":"bitset","module":"TriBitset","lines":97,"bytes":3588,"description":"t27/specs/","health":"warn","tokens":303,"nodes":42,"depth":3,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2294,"rust":719,"verilog":4169,"verilog_hir":647,"zig":1665}},{"path":"specs/tri/collections/bitvector.t27","category":"specs/tri","name":"bitvector","module":"TriBitvector","lines":93,"bytes":4192,"description":"t27/specs/","health":"ok","tokens":360,"nodes":30,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2308,"rust":829,"verilog":3809,"verilog_hir":738,"zig":2073}},{"path":"specs/tri/collections/btree.t27","category":"specs/tri","name":"btree","module":"TriBtree","lines":85,"bytes":4316,"description":"t27/specs/","health":"ok","tokens":364,"nodes":31,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1943,"rust":662,"verilog":3786,"verilog_hir":507,"zig":2237}},{"path":"specs/tri/collections/circular_buffer.t27","category":"specs/tri","name":"circular_buffer","module":"TriCircularBuffer","lines":79,"bytes":3676,"description":"t27/specs/","health":"ok","tokens":342,"nodes":29,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2035,"rust":660,"verilog":3671,"verilog_hir":582,"zig":1953}},{"path":"specs/tri/collections/context.t27","category":"specs/tri","name":"context","module":"TriContext","lines":16,"bytes":625,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":618,"rust":66,"verilog":1504,"verilog_hir":249,"zig":321}},{"path":"specs/tri/collections/deque.t27","category":"specs/tri","name":"deque","module":"TriDeque","lines":77,"bytes":3243,"description":"t27/specs/","health":"ok","tokens":290,"nodes":26,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1923,"rust":678,"verilog":3344,"verilog_hir":597,"zig":1618}},{"path":"specs/tri/collections/either.t27","category":"specs/tri","name":"either","module":"TriEither","lines":75,"bytes":3222,"description":"t27/specs/","health":"ok","tokens":217,"nodes":25,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1773,"rust":542,"verilog":3257,"verilog_hir":523,"zig":1550}},{"path":"specs/tri/collections/interval.t27","category":"specs/tri","name":"interval","module":"TriInterval","lines":86,"bytes":4186,"description":"t27/specs/","health":"ok","tokens":393,"nodes":33,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2033,"rust":595,"verilog":4210,"verilog_hir":517,"zig":2175}},{"path":"specs/tri/collections/linked_list.t27","category":"specs/tri","name":"linked_list","module":"TriLinkedList","lines":84,"bytes":3032,"description":"t27/specs/","health":"ok","tokens":232,"nodes":37,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2033,"rust":760,"verilog":3736,"verilog_hir":503,"zig":1359}},{"path":"specs/tri/collections/list.t27","category":"specs/tri","name":"list","module":"TriList","lines":97,"bytes":3261,"description":"t27/specs/","health":"ok","tokens":263,"nodes":42,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1990,"rust":591,"verilog":3812,"verilog_hir":554,"zig":1480}},{"path":"specs/tri/collections/lockfree_stack.t27","category":"specs/tri","name":"lockfree_stack","module":"TriLockfreeStack","lines":70,"bytes":3118,"description":"t27/specs/","health":"ok","tokens":178,"nodes":22,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1724,"rust":557,"verilog":2959,"verilog_hir":471,"zig":1157}},{"path":"specs/tri/collections/lru.t27","category":"specs/tri","name":"lru","module":"TriLru","lines":57,"bytes":2516,"description":"t27/specs/","health":"ok","tokens":195,"nodes":22,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1716,"rust":534,"verilog":2905,"verilog_hir":501,"zig":1184}},{"path":"specs/tri/collections/lru_cache.t27","category":"specs/tri","name":"lru_cache","module":"TriLruCache","lines":68,"bytes":2554,"description":"t27/specs/","health":"ok","tokens":194,"nodes":33,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1715,"rust":700,"verilog":3245,"verilog_hir":477,"zig":1201}},{"path":"specs/tri/collections/map.t27","category":"specs/tri","name":"map","module":"TriMap","lines":73,"bytes":3019,"description":"t27/specs/","health":"ok","tokens":231,"nodes":21,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1742,"rust":579,"verilog":3157,"verilog_hir":548,"zig":1531}},{"path":"specs/tri/collections/maybe.t27","category":"specs/tri","name":"maybe","module":"TriMaybe","lines":81,"bytes":3955,"description":"t27/specs/","health":"ok","tokens":322,"nodes":28,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1856,"rust":493,"verilog":3552,"verilog_hir":505,"zig":1951}},{"path":"specs/tri/collections/namespace.t27","category":"specs/tri","name":"namespace","module":"TriNamespace","lines":58,"bytes":2700,"description":"t27/specs/","health":"ok","tokens":172,"nodes":27,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1397,"rust":715,"verilog":2870,"verilog_hir":253,"zig":1030}},{"path":"specs/tri/collections/option.t27","category":"specs/tri","name":"option","module":"TriOption","lines":79,"bytes":3504,"description":"t27/specs/","health":"ok","tokens":241,"nodes":27,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1676,"rust":434,"verilog":3221,"verilog_hir":482,"zig":1547}},{"path":"specs/tri/collections/priority_queue.t27","category":"specs/tri","name":"priority_queue","module":"TriPriorityQueue","lines":78,"bytes":3380,"description":"t27/specs/","health":"ok","tokens":181,"nodes":21,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1948,"rust":653,"verilog":3420,"verilog_hir":594,"zig":1411}},{"path":"specs/tri/collections/queue.t27","category":"specs/tri","name":"queue","module":"TriQueue","lines":70,"bytes":2992,"description":"t27/specs/","health":"ok","tokens":196,"nodes":20,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1675,"rust":482,"verilog":3081,"verilog_hir":465,"zig":1300}},{"path":"specs/tri/collections/result.t27","category":"specs/tri","name":"result","module":"TriResult","lines":79,"bytes":3655,"description":"t27/specs/","health":"ok","tokens":292,"nodes":24,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1778,"rust":467,"verilog":3719,"verilog_hir":513,"zig":1687}},{"path":"specs/tri/collections/ring_buffer.t27","category":"specs/tri","name":"ring_buffer","module":"TriRing","lines":101,"bytes":4905,"description":"t27/specs/","health":"ok","tokens":537,"nodes":42,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2075,"rust":550,"verilog":4370,"verilog_hir":501,"zig":2590}},{"path":"specs/tri/collections/set.t27","category":"specs/tri","name":"set","module":"TriSet","lines":65,"bytes":2723,"description":"t27/specs/","health":"ok","tokens":211,"nodes":25,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1832,"rust":548,"verilog":3133,"verilog_hir":525,"zig":1235}},{"path":"specs/tri/collections/skip_list.t27","category":"specs/tri","name":"skip_list","module":"TriSkipList","lines":69,"bytes":3150,"description":"t27/specs/","health":"ok","tokens":226,"nodes":26,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1837,"rust":667,"verilog":3281,"verilog_hir":483,"zig":1601}},{"path":"specs/tri/collections/stack.t27","category":"specs/tri","name":"stack","module":"TriStack","lines":71,"bytes":3014,"description":"t27/specs/","health":"ok","tokens":235,"nodes":22,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1657,"rust":453,"verilog":2994,"verilog_hir":458,"zig":1302}},{"path":"specs/tri/collections/state.t27","category":"specs/tri","name":"state","module":"TriState","lines":65,"bytes":2474,"description":"t27/specs/","health":"ok","tokens":183,"nodes":25,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1607,"rust":418,"verilog":2956,"verilog_hir":475,"zig":1100}},{"path":"specs/tri/collections/tuple.t27","category":"specs/tri","name":"tuple","module":"TriTuple","lines":72,"bytes":2619,"description":"t27/specs/","health":"ok","tokens":217,"nodes":30,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1675,"rust":574,"verilog":3241,"verilog_hir":495,"zig":1370}},{"path":"specs/tri/collections/variant.t27","category":"specs/tri","name":"variant","module":"TriVariant","lines":63,"bytes":2922,"description":"t27/specs/","health":"ok","tokens":192,"nodes":20,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1660,"rust":427,"verilog":3289,"verilog_hir":483,"zig":1374}},{"path":"specs/tri/crypto/base32.t27","category":"specs/tri","name":"base32","module":"TriBase32","lines":78,"bytes":4129,"description":"t27/specs/","health":"ok","tokens":323,"nodes":32,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1959,"rust":493,"verilog":3805,"verilog_hir":456,"zig":2088}},{"path":"specs/tri/crypto/base64.t27","category":"specs/tri","name":"base64","module":"TriBase64","lines":97,"bytes":4954,"description":"t27/specs/","health":"ok","tokens":380,"nodes":37,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2378,"rust":705,"verilog":4641,"verilog_hir":622,"zig":2703}},{"path":"specs/tri/crypto/crypto.t27","category":"specs/tri","name":"crypto","module":"TriCrypto","lines":56,"bytes":2500,"description":"t27/specs/","health":"ok","tokens":165,"nodes":21,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1710,"rust":513,"verilog":2912,"verilog_hir":492,"zig":992}},{"path":"specs/tri/crypto/ecc.t27","category":"specs/tri","name":"ecc","module":"TriEcc","lines":89,"bytes":4348,"description":"t27/specs/","health":"ok","tokens":428,"nodes":35,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1960,"rust":602,"verilog":3918,"verilog_hir":460,"zig":1989}},{"path":"specs/tri/crypto/hex.t27","category":"specs/tri","name":"hex","module":"TriHex","lines":64,"bytes":2929,"description":"t27/specs/","health":"ok","tokens":160,"nodes":18,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1670,"rust":489,"verilog":2824,"verilog_hir":494,"zig":1127}},{"path":"specs/tri/crypto/hmac.t27","category":"specs/tri","name":"hmac","module":"TriHmac","lines":56,"bytes":2274,"description":"t27/specs/","health":"ok","tokens":139,"nodes":21,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1487,"rust":392,"verilog":2695,"verilog_hir":440,"zig":818}},{"path":"specs/tri/crypto/reed_solomon.t27","category":"specs/tri","name":"reed_solomon","module":"TriReedSolomon","lines":46,"bytes":2205,"description":"t27/specs/","health":"ok","tokens":124,"nodes":16,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1542,"rust":425,"verilog":2540,"verilog_hir":463,"zig":806}},{"path":"specs/tri/crypto/rsa.t27","category":"specs/tri","name":"rsa","module":"TriRsa","lines":83,"bytes":4009,"description":"t27/specs/","health":"ok","tokens":289,"nodes":33,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2009,"rust":499,"verilog":3984,"verilog_hir":570,"zig":1984}},{"path":"specs/tri/crypto/sha256.t27","category":"specs/tri","name":"sha256","module":"TriSha256","lines":67,"bytes":2952,"description":"t27/specs/","health":"ok","tokens":160,"nodes":20,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1644,"rust":470,"verilog":3108,"verilog_hir":452,"zig":1153}},{"path":"specs/tri/encoding/bson.t27","category":"specs/tri","name":"bson","module":"TriBson","lines":70,"bytes":3544,"description":"t27/specs/","health":"ok","tokens":196,"nodes":31,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2013,"rust":598,"verilog":3563,"verilog_hir":411,"zig":1386}},{"path":"specs/tri/encoding/csv.t27","category":"specs/tri","name":"csv","module":"TriCsv","lines":72,"bytes":3155,"description":"t27/specs/","health":"ok","tokens":151,"nodes":20,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1675,"rust":596,"verilog":3131,"verilog_hir":445,"zig":1153}},{"path":"specs/tri/encoding/html.t27","category":"specs/tri","name":"html","module":"TriHtml","lines":48,"bytes":2223,"description":"t27/specs/","health":"warn","tokens":136,"nodes":3,"depth":2,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":396,"rust":66,"verilog":1045,"verilog_hir":243,"zig":202}},{"path":"specs/tri/encoding/json.t27","category":"specs/tri","name":"json","module":"TriJson","lines":67,"bytes":2655,"description":"t27/specs/","health":"ok","tokens":199,"nodes":31,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2025,"rust":869,"verilog":3312,"verilog_hir":504,"zig":1150}},{"path":"specs/tri/encoding/markup.t27","category":"specs/tri","name":"markup","module":"TriMarkup","lines":53,"bytes":2716,"description":"t27/specs/","health":"ok","tokens":143,"nodes":16,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1584,"rust":466,"verilog":2767,"verilog_hir":419,"zig":1078}},{"path":"specs/tri/encoding/mime.t27","category":"specs/tri","name":"mime","module":"TriMime","lines":57,"bytes":2794,"description":"t27/specs/","health":"ok","tokens":193,"nodes":20,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1588,"rust":456,"verilog":2925,"verilog_hir":409,"zig":1251}},{"path":"specs/tri/encoding/msgpack.t27","category":"specs/tri","name":"msgpack","module":"TriMsgpack","lines":93,"bytes":4485,"description":"t27/specs/","health":"ok","tokens":324,"nodes":53,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2447,"rust":815,"verilog":4806,"verilog_hir":417,"zig":2346}},{"path":"specs/tri/encoding/xml.t27","category":"specs/tri","name":"xml","module":"TriXml","lines":48,"bytes":2195,"description":"t27/specs/","health":"warn","tokens":139,"nodes":3,"depth":2,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":392,"rust":66,"verilog":1043,"verilog_hir":241,"zig":201}},{"path":"specs/tri/graph/bellman_ford.t27","category":"specs/tri","name":"bellman_ford","module":"TriBellmanFord","lines":37,"bytes":1918,"description":"t27/specs/","health":"ok","tokens":85,"nodes":12,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1380,"rust":348,"verilog":2252,"verilog_hir":432,"zig":621}},{"path":"specs/tri/graph/dijkstra.t27","category":"specs/tri","name":"dijkstra","module":"TriDijkstra","lines":38,"bytes":1854,"description":"t27/specs/","health":"ok","tokens":77,"nodes":13,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1266,"rust":318,"verilog":2209,"verilog_hir":325,"zig":567}},{"path":"specs/tri/graph/disjoint_set.t27","category":"specs/tri","name":"disjoint_set","module":"TriDisjointSet","lines":93,"bytes":4626,"description":"t27/specs/","health":"ok","tokens":379,"nodes":36,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2000,"rust":535,"verilog":3810,"verilog_hir":508,"zig":2203}},{"path":"specs/tri/graph/graph.t27","category":"specs/tri","name":"graph","module":"TriGraph","lines":61,"bytes":2379,"description":"t27/specs/","health":"ok","tokens":162,"nodes":24,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1564,"rust":520,"verilog":2782,"verilog_hir":418,"zig":978}},{"path":"specs/tri/graph/graph_bfs.t27","category":"specs/tri","name":"graph_bfs","module":"TriGraphBfs","lines":113,"bytes":5554,"description":"t27/specs/","health":"ok","tokens":700,"nodes":56,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2338,"rust":745,"verilog":4358,"verilog_hir":591,"zig":3236}},{"path":"specs/tri/graph/graph_dfs.t27","category":"specs/tri","name":"graph_dfs","module":"TriGraphDfs","lines":38,"bytes":1808,"description":"t27/specs/","health":"ok","tokens":76,"nodes":13,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1250,"rust":312,"verilog":2150,"verilog_hir":320,"zig":556}},{"path":"specs/tri/graph/prims_mst.t27","category":"specs/tri","name":"prims_mst","module":"TriPrimsMst","lines":38,"bytes":1795,"description":"t27/specs/","health":"ok","tokens":74,"nodes":13,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1234,"rust":299,"verilog":2127,"verilog_hir":315,"zig":541}},{"path":"specs/tri/graph/topological_sort.t27","category":"specs/tri","name":"topological_sort","module":"TriTopological","lines":47,"bytes":2082,"description":"t27/specs/","health":"ok","tokens":101,"nodes":17,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1396,"rust":342,"verilog":2406,"verilog_hir":393,"zig":704}},{"path":"specs/tri/io/compress.t27","category":"specs/tri","name":"compress","module":"TriCompress","lines":58,"bytes":3115,"description":"t27/specs/","health":"ok","tokens":223,"nodes":19,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1663,"rust":415,"verilog":3104,"verilog_hir":431,"zig":1284}},{"path":"specs/tri/io/filesystem.t27","category":"specs/tri","name":"filesystem","module":"TriFilesystem","lines":113,"bytes":5345,"description":"t27/specs/","health":"ok","tokens":489,"nodes":42,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2706,"rust":1046,"verilog":5060,"verilog_hir":636,"zig":2719}},{"path":"specs/tri/io/fs.t27","category":"specs/tri","name":"fs","module":"TriFs","lines":73,"bytes":2757,"description":"t27/specs/","health":"ok","tokens":203,"nodes":31,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1817,"rust":684,"verilog":3356,"verilog_hir":517,"zig":1168}},{"path":"specs/tri/io/io.t27","category":"specs/tri","name":"io","module":"TriIo","lines":67,"bytes":3086,"description":"t27/specs/","health":"ok","tokens":250,"nodes":22,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1644,"rust":449,"verilog":3139,"verilog_hir":468,"zig":1463}},{"path":"specs/tri/io/reader.t27","category":"specs/tri","name":"reader","module":"TriReader","lines":65,"bytes":2550,"description":"t27/specs/","health":"ok","tokens":193,"nodes":25,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1666,"rust":445,"verilog":2985,"verilog_hir":478,"zig":1156}},{"path":"specs/tri/io/writer.t27","category":"specs/tri","name":"writer","module":"TriWriter","lines":66,"bytes":2628,"description":"t27/specs/","health":"ok","tokens":200,"nodes":26,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1721,"rust":485,"verilog":3106,"verilog_hir":513,"zig":1221}},{"path":"specs/tri/io/zip.t27","category":"specs/tri","name":"zip","module":"TriZipper","lines":77,"bytes":2847,"description":"t27/specs/","health":"ok","tokens":203,"nodes":32,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1821,"rust":525,"verilog":3330,"verilog_hir":471,"zig":1219}},{"path":"specs/tri/math/bezier.t27","category":"specs/tri","name":"bezier","module":"TriBezier","lines":75,"bytes":3811,"description":"t27/specs/","health":"ok","tokens":423,"nodes":30,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1735,"rust":488,"verilog":3446,"verilog_hir":384,"zig":1817}},{"path":"specs/tri/math/constants.t27","category":"specs/tri","name":"constants","module":"TriConstants","lines":128,"bytes":5390,"description":"t27/specs/","health":"ok","tokens":392,"nodes":44,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2606,"rust":1120,"verilog":5046,"verilog_hir":778,"zig":2979}},{"path":"specs/tri/math/math.t27","category":"specs/tri","name":"math","module":"TriMath","lines":16,"bytes":619,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":603,"rust":66,"verilog":1486,"verilog_hir":243,"zig":315}},{"path":"specs/tri/math/matrix.t27","category":"specs/tri","name":"matrix","module":"TriMatrix","lines":96,"bytes":4182,"description":"t27/specs/","health":"ok","tokens":350,"nodes":36,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2496,"rust":856,"verilog":4568,"verilog_hir":796,"zig":2393}},{"path":"specs/tri/math/measurement.t27","category":"specs/tri","name":"measurement","module":"TriMeasurement","lines":16,"bytes":633,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":638,"rust":66,"verilog":1528,"verilog_hir":257,"zig":329}},{"path":"specs/tri/math/polynomial.t27","category":"specs/tri","name":"polynomial","module":"TriPolynomial","lines":80,"bytes":3851,"description":"t27/specs/","health":"ok","tokens":327,"nodes":26,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2198,"rust":761,"verilog":3621,"verilog_hir":650,"zig":1877}},{"path":"specs/tri/math/probability.t27","category":"specs/tri","name":"probability","module":"TriProbability","lines":67,"bytes":2602,"description":"t27/specs/","health":"ok","tokens":218,"nodes":28,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1866,"rust":504,"verilog":3352,"verilog_hir":623,"zig":1315}},{"path":"specs/tri/math/statistics.t27","category":"specs/tri","name":"statistics","module":"TriStatistics","lines":77,"bytes":2735,"description":"t27/specs/","health":"ok","tokens":233,"nodes":33,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1856,"rust":518,"verilog":3610,"verilog_hir":635,"zig":1385}},{"path":"specs/tri/net/async.t27","category":"specs/tri","name":"async","module":"TriAsync","lines":86,"bytes":3896,"description":"t27/specs/","health":"ok","tokens":286,"nodes":29,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1777,"rust":571,"verilog":3369,"verilog_hir":461,"zig":1737}},{"path":"specs/tri/net/async_stream.t27","category":"specs/tri","name":"async_stream","module":"TriAsyncStream","lines":86,"bytes":4087,"description":"t27/specs/","health":"ok","tokens":278,"nodes":31,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2151,"rust":627,"verilog":4029,"verilog_hir":521,"zig":1858}},{"path":"specs/tri/net/channel.t27","category":"specs/tri","name":"channel","module":"TriChannel","lines":67,"bytes":2629,"description":"t27/specs/","health":"ok","tokens":177,"nodes":27,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1736,"rust":511,"verilog":3165,"verilog_hir":489,"zig":1161}},{"path":"specs/tri/net/cloud.t27","category":"specs/tri","name":"cloud","module":"TriCloud","lines":16,"bytes":621,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":608,"rust":66,"verilog":1492,"verilog_hir":245,"zig":317}},{"path":"specs/tri/net/http.t27","category":"specs/tri","name":"http","module":"TriHttp","lines":133,"bytes":6147,"description":"t27/specs/","health":"ok","tokens":572,"nodes":60,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2777,"rust":1131,"verilog":4906,"verilog_hir":656,"zig":3261}},{"path":"specs/tri/net/net.t27","category":"specs/tri","name":"net","module":"TriNet","lines":71,"bytes":3117,"description":"t27/specs/","health":"ok","tokens":339,"nodes":29,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1694,"rust":532,"verilog":3198,"verilog_hir":415,"zig":1639}},{"path":"specs/tri/net/url.t27","category":"specs/tri","name":"url","module":"TriUrl","lines":83,"bytes":4112,"description":"t27/specs/","health":"ok","tokens":419,"nodes":31,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2093,"rust":701,"verilog":3951,"verilog_hir":550,"zig":2075}},{"path":"specs/tri/pipeline/batch_runner.t27","category":"specs/tri","name":"batch_runner","module":"BatchRunner","lines":115,"bytes":4376,"description":"t27/specs/","health":"ok","tokens":309,"nodes":55,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":3088,"rust":1528,"verilog":4194,"verilog_hir":251,"zig":2707}},{"path":"specs/tri/pipeline/builder.t27","category":"specs/tri","name":"builder","module":"TriBuilder","lines":107,"bytes":3957,"description":"t27/specs/","health":"ok","tokens":353,"nodes":47,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2588,"rust":886,"verilog":4598,"verilog_hir":711,"zig":2043}},{"path":"specs/tri/pipeline/cloud_orchestrator.t27","category":"specs/tri","name":"cloud_orchestrator","module":"CloudOrchestrator","lines":16,"bytes":643,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":657,"rust":66,"verilog":1562,"verilog_hir":263,"zig":339}},{"path":"specs/tri/pipeline/codegen.t27","category":"specs/tri","name":"codegen","module":"TestSpec","lines":27,"bytes":1147,"description":"t27/specs/","health":"ok","tokens":47,"nodes":12,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":845,"rust":244,"verilog":1914,"verilog_hir":245,"zig":435}},{"path":"specs/tri/pipeline/pipeline.t27","category":"specs/tri","name":"pipeline","module":"TriPipeline","lines":16,"bytes":627,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":623,"rust":66,"verilog":1510,"verilog_hir":251,"zig":323}},{"path":"specs/tri/pipeline/pipeline_parallel.t27","category":"specs/tri","name":"pipeline_parallel","module":"TriPipelineParallel","lines":50,"bytes":1900,"description":"t27/specs/","health":"ok","tokens":143,"nodes":33,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1532,"rust":913,"verilog":3101,"verilog_hir":267,"zig":882}},{"path":"specs/tri/pipeline/spec_parser.t27","category":"specs/tri","name":"spec_parser","module":"TriSpecParser","lines":16,"bytes":632,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":634,"rust":66,"verilog":1526,"verilog_hir":255,"zig":328}},{"path":"specs/tri/pipeline/spec_writer.t27","category":"specs/tri","name":"spec_writer","module":"SpecWriter","lines":46,"bytes":1622,"description":"t27/specs/","health":"ok","tokens":141,"nodes":23,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1224,"rust":901,"verilog":2502,"verilog_hir":249,"zig":819}},{"path":"specs/tri/pipeline/workflow.t27","category":"specs/tri","name":"workflow","module":"Workflow","lines":31,"bytes":1263,"description":"t27/specs/","health":"ok","tokens":72,"nodes":14,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":942,"rust":408,"verilog":2020,"verilog_hir":245,"zig":536}},{"path":"specs/tri/pipeline/workflow_executor.t27","category":"specs/tri","name":"workflow_executor","module":"WorkflowExecutor","lines":16,"bytes":641,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":652,"rust":66,"verilog":1556,"verilog_hir":261,"zig":337}},{"path":"specs/tri/pipeline/workflow_parser.t27","category":"specs/tri","name":"workflow_parser","module":"WorkflowParser","lines":16,"bytes":637,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":642,"rust":66,"verilog":1544,"verilog_hir":257,"zig":333}},{"path":"specs/tri/search/aho_corasick.t27","category":"specs/tri","name":"aho_corasick","module":"TriAhoCorasick","lines":99,"bytes":4285,"description":"t27/specs/","health":"ok","tokens":317,"nodes":41,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2240,"rust":768,"verilog":4593,"verilog_hir":529,"zig":2164}},{"path":"specs/tri/search/bloom_filter.t27","category":"specs/tri","name":"bloom_filter","module":"TriBloomFilter","lines":58,"bytes":2868,"description":"t27/specs/","health":"ok","tokens":147,"nodes":18,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1641,"rust":487,"verilog":2812,"verilog_hir":485,"zig":1155}},{"path":"specs/tri/search/boyer_moore.t27","category":"specs/tri","name":"boyer_moore","module":"TriBoyerMoore","lines":72,"bytes":3868,"description":"t27/specs/","health":"ok","tokens":240,"nodes":25,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1783,"rust":413,"verilog":3566,"verilog_hir":432,"zig":1639}},{"path":"specs/tri/search/knuth_morris_pratt.t27","category":"specs/tri","name":"knuth_morris_pratt","module":"TriKmp","lines":85,"bytes":4347,"description":"t27/specs/","health":"ok","tokens":332,"nodes":34,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1769,"rust":420,"verilog":3337,"verilog_hir":448,"zig":1947}},{"path":"specs/tri/search/match.t27","category":"specs/tri","name":"match","module":null,"lines":68,"bytes":3333,"description":"t27/specs/","health":"warn","tokens":187,"nodes":1,"depth":1,"loss":14,"tcErrors":0,"failedBackends":[],"outBytes":{"c":396,"rust":66,"verilog":1045,"verilog_hir":243,"zig":121}},{"path":"specs/tri/search/pattern.t27","category":"specs/tri","name":"pattern","module":"TriPattern","lines":61,"bytes":3040,"description":"t27/specs/","health":"ok","tokens":197,"nodes":21,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1558,"rust":402,"verilog":2849,"verilog_hir":385,"zig":1273}},{"path":"specs/tri/search/rabin_karp.t27","category":"specs/tri","name":"rabin_karp","module":"TriRabinKarp","lines":73,"bytes":3867,"description":"t27/specs/","health":"ok","tokens":269,"nodes":25,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1722,"rust":405,"verilog":3651,"verilog_hir":417,"zig":1632}},{"path":"specs/tri/search/regex.t27","category":"specs/tri","name":"regex","module":"TriRegex","lines":91,"bytes":4367,"description":"t27/specs/","health":"ok","tokens":424,"nodes":41,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2126,"rust":664,"verilog":4243,"verilog_hir":485,"zig":2310}},{"path":"specs/tri/search/regex_advanced.t27","category":"specs/tri","name":"regex_advanced","module":"TriRegexAdvanced","lines":62,"bytes":2766,"description":"t27/specs/","health":"ok","tokens":209,"nodes":27,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2132,"rust":781,"verilog":3388,"verilog_hir":566,"zig":1244}},{"path":"specs/tri/search/search.t27","category":"specs/tri","name":"search","module":"TriSearch","lines":63,"bytes":3032,"description":"t27/specs/","health":"ok","tokens":214,"nodes":21,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1632,"rust":452,"verilog":3008,"verilog_hir":487,"zig":1341}},{"path":"specs/tri/sort/counting_sort.t27","category":"specs/tri","name":"counting_sort","module":"TriCountingSort","lines":27,"bytes":1352,"description":"t27/specs/","health":"ok","tokens":61,"nodes":8,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1111,"rust":180,"verilog":1856,"verilog_hir":391,"zig":491}},{"path":"specs/tri/sort/heap_sort.t27","category":"specs/tri","name":"heap_sort","module":"TriHeapSort","lines":27,"bytes":1258,"description":"t27/specs/","health":"ok","tokens":47,"nodes":8,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1001,"rust":124,"verilog":1757,"verilog_hir":317,"zig":398}},{"path":"specs/tri/sort/insertion_sort.t27","category":"specs/tri","name":"insertion_sort","module":"TriInsertionSort","lines":27,"bytes":1262,"description":"t27/specs/","health":"ok","tokens":47,"nodes":8,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1021,"rust":124,"verilog":1767,"verilog_hir":327,"zig":403}},{"path":"specs/tri/sort/merge_sort.t27","category":"specs/tri","name":"merge_sort","module":"TriMergeSort","lines":37,"bytes":1665,"description":"t27/specs/","health":"ok","tokens":97,"nodes":13,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1297,"rust":257,"verilog":2196,"verilog_hir":398,"zig":681}},{"path":"specs/tri/sort/quick_sort.t27","category":"specs/tri","name":"quick_sort","module":"TriQuickSort","lines":41,"bytes":1923,"description":"t27/specs/","health":"ok","tokens":87,"nodes":11,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1225,"rust":213,"verilog":2232,"verilog_hir":418,"zig":747}},{"path":"specs/tri/sort/radix_sort.t27","category":"specs/tri","name":"radix_sort","module":"TriRadixSort","lines":56,"bytes":2879,"description":"t27/specs/","health":"ok","tokens":154,"nodes":17,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1589,"rust":401,"verilog":2915,"verilog_hir":398,"zig":1212}},{"path":"specs/tri/sort/selection_sort.t27","category":"specs/tri","name":"selection_sort","module":"TriSelectionSort","lines":27,"bytes":1247,"description":"t27/specs/","health":"ok","tokens":47,"nodes":8,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1021,"rust":124,"verilog":1767,"verilog_hir":327,"zig":403}},{"path":"specs/tri/sort/shell_sort.t27","category":"specs/tri","name":"shell_sort","module":"TriShellSort","lines":27,"bytes":1257,"description":"t27/specs/","health":"ok","tokens":47,"nodes":8,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1005,"rust":124,"verilog":1759,"verilog_hir":319,"zig":399}},{"path":"specs/tri/sort/sort.t27","category":"specs/tri","name":"sort","module":"TriSort","lines":57,"bytes":3188,"description":"t27/specs/","health":"ok","tokens":164,"nodes":17,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1621,"rust":441,"verilog":2742,"verilog_hir":442,"zig":1239}},{"path":"specs/tri/sort/tim_sort.t27","category":"specs/tri","name":"tim_sort","module":"TriTimSort","lines":27,"bytes":1311,"description":"t27/specs/","health":"ok","tokens":55,"nodes":8,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1055,"rust":154,"verilog":1787,"verilog_hir":349,"zig":447}},{"path":"specs/tri/trees/avl_tree.t27","category":"specs/tri","name":"avl_tree","module":"TriAvlTree","lines":76,"bytes":2769,"description":"t27/specs/","health":"ok","tokens":216,"nodes":36,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1834,"rust":720,"verilog":3548,"verilog_hir":477,"zig":1278}},{"path":"specs/tri/trees/b_tree.t27","category":"specs/tri","name":"b_tree","module":"TriBTree","lines":96,"bytes":4616,"description":"t27/specs/","health":"ok","tokens":329,"nodes":37,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2036,"rust":714,"verilog":3943,"verilog_hir":514,"zig":2180}},{"path":"specs/tri/trees/fenwick_tree.t27","category":"specs/tri","name":"fenwick_tree","module":"TriFenwick","lines":101,"bytes":4544,"description":"t27/specs/","health":"ok","tokens":356,"nodes":39,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2474,"rust":756,"verilog":4709,"verilog_hir":740,"zig":2431}},{"path":"specs/tri/trees/kd_tree.t27","category":"specs/tri","name":"kd_tree","module":"TriKdTree","lines":89,"bytes":3998,"description":"t27/specs/","health":"ok","tokens":250,"nodes":26,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1993,"rust":837,"verilog":3443,"verilog_hir":646,"zig":1555}},{"path":"specs/tri/trees/octree.t27","category":"specs/tri","name":"octree","module":"TriOctree","lines":103,"bytes":4639,"description":"t27/specs/","health":"ok","tokens":478,"nodes":42,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2109,"rust":985,"verilog":3925,"verilog_hir":627,"zig":2203}},{"path":"specs/tri/trees/quadtree.t27","category":"specs/tri","name":"quadtree","module":"TriQuadtree","lines":110,"bytes":5036,"description":"t27/specs/","health":"ok","tokens":563,"nodes":47,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2288,"rust":956,"verilog":4580,"verilog_hir":608,"zig":2512}},{"path":"specs/tri/trees/red_black_tree.t27","category":"specs/tri","name":"red_black_tree","module":"TriRbTree","lines":81,"bytes":2892,"description":"t27/specs/","health":"ok","tokens":238,"nodes":40,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2023,"rust":861,"verilog":3788,"verilog_hir":475,"zig":1343}},{"path":"specs/tri/trees/rtree.t27","category":"specs/tri","name":"rtree","module":"TriRtree","lines":88,"bytes":3876,"description":"t27/specs/","health":"ok","tokens":370,"nodes":35,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1955,"rust":776,"verilog":3913,"verilog_hir":519,"zig":1878}},{"path":"specs/tri/trees/segment_tree.t27","category":"specs/tri","name":"segment_tree","module":"TriSegmentTree","lines":67,"bytes":3238,"description":"t27/specs/","health":"ok","tokens":235,"nodes":21,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1848,"rust":581,"verilog":3096,"verilog_hir":632,"zig":1449}},{"path":"specs/tri/trees/splay_tree.t27","category":"specs/tri","name":"splay_tree","module":"TriSplayTree","lines":76,"bytes":2802,"description":"t27/specs/","health":"ok","tokens":217,"nodes":36,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1860,"rust":740,"verilog":3595,"verilog_hir":481,"zig":1300}},{"path":"specs/tri/trees/suffix_array.t27","category":"specs/tri","name":"suffix_array","module":"TriSuffixArray","lines":83,"bytes":4311,"description":"t27/specs/","health":"ok","tokens":479,"nodes":33,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1963,"rust":494,"verilog":3823,"verilog_hir":491,"zig":2269}},{"path":"specs/tri/trees/tree.t27","category":"specs/tri","name":"tree","module":"TriTree","lines":85,"bytes":3768,"description":"t27/specs/","health":"ok","tokens":210,"nodes":22,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1774,"rust":610,"verilog":3231,"verilog_hir":580,"zig":1421}},{"path":"specs/tri/trees/trie.t27","category":"specs/tri","name":"trie","module":"TriTrie","lines":93,"bytes":4263,"description":"t27/specs/","health":"ok","tokens":371,"nodes":30,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1924,"rust":743,"verilog":3272,"verilog_hir":509,"zig":1986}},{"path":"specs/tri/utils/args.t27","category":"specs/tri","name":"args","module":null,"lines":86,"bytes":4133,"description":"t27/specs/","health":"warn","tokens":329,"nodes":1,"depth":1,"loss":21,"tcErrors":0,"failedBackends":[],"outBytes":{"c":396,"rust":66,"verilog":1045,"verilog_hir":243,"zig":121}},{"path":"specs/tri/utils/arrow_time.t27","category":"specs/tri","name":"arrow_time","module":"arrow_time","lines":16,"bytes":627,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":621,"rust":66,"verilog":1516,"verilog_hir":249,"zig":324}},{"path":"specs/tri/utils/bytes.t27","category":"specs/tri","name":"bytes","module":"TriBytes","lines":106,"bytes":3875,"description":"t27/specs/","health":"ok","tokens":330,"nodes":46,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2500,"rust":836,"verilog":4600,"verilog_hir":813,"zig":1925}},{"path":"specs/tri/utils/color.t27","category":"specs/tri","name":"color","module":"TriColor","lines":95,"bytes":4563,"description":"t27/specs/","health":"ok","tokens":374,"nodes":41,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2073,"rust":583,"verilog":3816,"verilog_hir":582,"zig":2237}},{"path":"specs/tri/utils/colors.t27","category":"specs/tri","name":"colors","module":"TriColors","lines":16,"bytes":623,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":613,"rust":66,"verilog":1498,"verilog_hir":247,"zig":319}},{"path":"specs/tri/utils/config.t27","category":"specs/tri","name":"config","module":"TriConfig","lines":89,"bytes":4082,"description":"t27/specs/","health":"ok","tokens":302,"nodes":33,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2219,"rust":959,"verilog":4213,"verilog_hir":636,"zig":2083}},{"path":"specs/tri/utils/error.t27","category":"specs/tri","name":"error","module":"TriError","lines":26,"bytes":1415,"description":"t27/specs/","health":"ok","tokens":53,"nodes":17,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1031,"rust":355,"verilog":2061,"verilog_hir":245,"zig":531}},{"path":"specs/tri/utils/exit_codes.t27","category":"specs/tri","name":"exit_codes","module":"TriExitCodes","lines":35,"bytes":1562,"description":"t27/specs/","health":"ok","tokens":66,"nodes":16,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1035,"rust":347,"verilog":2025,"verilog_hir":253,"zig":536}},{"path":"specs/tri/utils/help.t27","category":"specs/tri","name":"help","module":"TriHelp","lines":32,"bytes":1370,"description":"t27/specs/","health":"ok","tokens":74,"nodes":15,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":971,"rust":444,"verilog":2076,"verilog_hir":243,"zig":561}},{"path":"specs/tri/utils/logger.t27","category":"specs/tri","name":"logger","module":null,"lines":66,"bytes":2659,"description":"t27/specs/","health":"warn","tokens":204,"nodes":32,"depth":3,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1999,"rust":828,"verilog":3359,"verilog_hir":598,"zig":1179}},{"path":"specs/tri/utils/logging.t27","category":"specs/tri","name":"logging","module":"TriLogging","lines":102,"bytes":5075,"description":"t27/specs/","health":"ok","tokens":399,"nodes":40,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2353,"rust":828,"verilog":4169,"verilog_hir":655,"zig":2494}},{"path":"specs/tri/utils/random.t27","category":"specs/tri","name":"random","module":"TriRandom","lines":97,"bytes":4367,"description":"t27/specs/","health":"ok","tokens":307,"nodes":40,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2005,"rust":418,"verilog":4161,"verilog_hir":580,"zig":1945}},{"path":"specs/tri/utils/string.t27","category":"specs/tri","name":"string","module":"string","lines":16,"bytes":619,"description":"t27/specs/","health":"ok","tokens":23,"nodes":7,"depth":5,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":601,"rust":66,"verilog":1492,"verilog_hir":241,"zig":316}},{"path":"specs/tri/utils/template.t27","category":"specs/tri","name":"template","module":"TriTemplate","lines":70,"bytes":3884,"description":"t27/specs/","health":"warn","tokens":393,"nodes":10,"depth":3,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1161,"rust":469,"verilog":1858,"verilog_hir":354,"zig":549}},{"path":"specs/tri/utils/terminal.t27","category":"specs/tri","name":"terminal","module":"TriTerminal","lines":59,"bytes":2447,"description":"t27/specs/","health":"ok","tokens":172,"nodes":34,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1793,"rust":650,"verilog":3123,"verilog_hir":457,"zig":987}},{"path":"specs/tri/utils/text.t27","category":"specs/tri","name":"text","module":"TriText","lines":67,"bytes":2793,"description":"t27/specs/","health":"ok","tokens":197,"nodes":27,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1871,"rust":589,"verilog":3336,"verilog_hir":531,"zig":1186}},{"path":"specs/tri/utils/time.t27","category":"specs/tri","name":"time","module":"TriTime","lines":93,"bytes":4138,"description":"t27/specs/","health":"ok","tokens":290,"nodes":34,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2008,"rust":710,"verilog":3624,"verilog_hir":607,"zig":1937}},{"path":"specs/tri/utils/utf8.t27","category":"specs/tri","name":"utf8","module":"TriUtf8","lines":82,"bytes":3595,"description":"t27/specs/","health":"ok","tokens":275,"nodes":27,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1911,"rust":619,"verilog":3776,"verilog_hir":526,"zig":1541}},{"path":"specs/tri/utils/version.t27","category":"specs/tri","name":"version","module":"TriVersion","lines":85,"bytes":4113,"description":"t27/specs/","health":"ok","tokens":392,"nodes":36,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1919,"rust":570,"verilog":3469,"verilog_hir":549,"zig":2117}},{"path":"specs/vm/jit_semantics.t27","category":"specs/vm","name":"jit_semantics","module":"JitSemantics","lines":319,"bytes":14011,"description":"Module: JIT Compilation Semantics","health":"ok","tokens":912,"nodes":86,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6201,"rust":1766,"verilog":11669,"verilog_hir":775,"zig":6108}},{"path":"specs/vsa/jones_polynomial.t27","category":"specs/vsa","name":"jones_polynomial","module":"JonesPolynomial","lines":353,"bytes":12139,"description":"t27/specs/vsa/jones_polynomial.t27 Jones Polynomial -- Link invariant computed from input structure V(L, t) = (-t^(-3/4))^w(L) * where is Kauffman bracket","health":"ok","tokens":1543,"nodes":511,"depth":17,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6991,"rust":1789,"verilog":14065,"verilog_hir":959,"zig":8267}},{"path":"specs/vsa/ops.t27","category":"specs/vsa","name":"ops","module":"VSAOps","lines":670,"bytes":27126,"description":"t27/specs/vsa/ops.t27 Vector Symbolic Architecture Operations Bind, Unbind, Bundle, Similarity for ternary hypervectors","health":"warn","tokens":5592,"nodes":755,"depth":13,"loss":0,"tcErrors":14,"failedBackends":[],"outBytes":{"c":12535,"rust":6035,"verilog":21963,"verilog_hir":1805,"zig":16401}},{"path":"specs/vsa/packed_vsa.t27","category":"specs/vsa","name":"packed_vsa","module":"PackedVsa","lines":291,"bytes":11657,"description":"Module: Packed VSA Operations","health":"ok","tokens":937,"nodes":90,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":5122,"rust":1025,"verilog":10094,"verilog_hir":772,"zig":5779}},{"path":"specs/vsa/sdk.t27","category":"specs/vsa","name":"sdk","module":"SDK","lines":626,"bytes":24071,"description":"specs/vsa/sdk.t27 Trinity SDK - High-level API for VSA operations","health":"ok","tokens":2726,"nodes":373,"depth":9,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":10307,"rust":3711,"verilog":19377,"verilog_hir":1790,"zig":12370}},{"path":"specs/vsa/sequence_hdc.t27","category":"specs/vsa","name":"sequence_hdc","module":"SequenceHdc","lines":342,"bytes":15174,"description":"Module: Sequence Hyperdimensional Computing (HDC)","health":"ok","tokens":1129,"nodes":105,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":6422,"rust":2425,"verilog":11543,"verilog_hir":1131,"zig":7025}},{"path":"specs/vsa/similarity_search.t27","category":"specs/vsa","name":"similarity_search","module":"VSASimilaritySearch","lines":467,"bytes":15246,"description":"t27/specs/vsa/similarity_search.t27 VSA Similarity Search Specification Ring 062 - Efficient similarity search in hyperdimensional space Defines semantic similarity operations for VSA trit vectors","health":"warn","tokens":2648,"nodes":606,"depth":14,"loss":0,"tcErrors":13,"failedBackends":[],"outBytes":{"c":8793,"rust":4785,"verilog":14138,"verilog_hir":1180,"zig":7030}},{"path":"specs/vsa/vsa_core.t27","category":"specs/vsa","name":"vsa_core","module":"vsa_core","lines":1012,"bytes":29297,"description":"t27/specs/vsa/vsa_core.t27 VSA (Vector Symbolic Architecture) Core Operations 01234 567891011: V = n 12 3^k 13 14^m 15 16^p 17 e^q VSA provides high-dimensional vector operations for: - Symbolic reasoning (bind/unbind for role-filler pairs) - Set operations (bundle for superposition)","health":"warn","tokens":5058,"nodes":2297,"depth":13,"loss":0,"tcErrors":17,"failedBackends":[],"outBytes":{"c":23664,"rust":6487,"verilog":43584,"verilog_hir":1474,"zig":20980}},{"path":"test_highlight.t27","category":"root","name":"test_highlight","module":null,"lines":57,"bytes":1013,"description":"T27 Syntax Highlighting Test File This tests all syntax elements","health":"warn","tokens":217,"nodes":59,"depth":6,"loss":1,"tcErrors":0,"failedBackends":[],"outBytes":{"c":2132,"rust":927,"verilog":3575,"verilog_hir":511,"zig":978}},{"path":"tests/comprehensive_suite.t27","category":"tests","name":"comprehensive_suite","module":"tests-comprehensive-suite","lines":19,"bytes":560,"description":"comprehensive_suite.t27 -- documents the repository integration suite Executed by: t27c suite (or tri test). No shell runners under tests/.","health":"ok","tokens":84,"nodes":23,"depth":7,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":1020,"rust":316,"verilog":1973,"verilog_hir":279,"zig":507}},{"path":"tests/ring0_trivial.t27","category":"tests","name":"ring0_trivial","module":"ring0","lines":11,"bytes":201,"description":"ring-0 trivial test","health":"ok","tokens":44,"nodes":11,"depth":3,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":602,"rust":195,"verilog":1388,"verilog_hir":239,"zig":243}},{"path":"vscode-trinity-swe/test_highlight.t27","category":"vscode-trinity-swe","name":"test_highlight","module":null,"lines":1,"bytes":0,"description":null,"health":"ok","tokens":0,"nodes":1,"depth":1,"loss":0,"tcErrors":0,"failedBackends":[],"outBytes":{"c":396,"rust":66,"verilog":1045,"verilog_hir":243,"zig":121}}]} \ No newline at end of file diff --git a/apps/website/scripts/sync-t27-specs.mjs b/apps/website/scripts/sync-t27-specs.mjs index f3140ebf98..090b4f3181 100644 --- a/apps/website/scripts/sync-t27-specs.mjs +++ b/apps/website/scripts/sync-t27-specs.mjs @@ -62,6 +62,50 @@ if (!files.length) fail('no .t27 files found') rmSync(OUT_DIR, { recursive: true, force: true }) mkdirSync(SPECS_OUT, { recursive: true }) +// Run the same wasm the browser runs, here, over the whole corpus. Health has +// to be known before a row is drawn -- the alternative is compiling 667 specs +// in the browser just to colour a list, which would take minutes. +const wasmBuf = readFileSync(WASM_SRC) +const { instance: wasmInst } = await WebAssembly.instantiate(wasmBuf, {}) +const { memory, t27_alloc, t27_free, t27_analyze } = wasmInst.exports + +function analyze(src) { + const b = Buffer.from(src, 'utf8') + const p = t27_alloc(b.length) + new Uint8Array(memory.buffer, p, b.length).set(b) + const o = t27_analyze(p, b.length) + const n = new DataView(memory.buffer).getUint32(o, true) + const json = Buffer.from(new Uint8Array(memory.buffer, o + 4, n)).toString('utf8') + t27_free(o, 4 + n) + return JSON.parse(json) +} + +/** + * A spec's own header comments, as its description. + * + * Specs open with either `//` or `;` comment lines. SPDX, decorative rules and + * the φ banner are dropped -- they are boilerplate on nearly every file and say + * nothing about the individual spec. + */ +function describe(text) { + const out = [] + for (const raw of text.split('\n')) { + const line = raw.trim() + if (line === '') { if (out.length) break; else continue } + const m = line.match(/^(?:\/\/|;)\s?(.*)$/) + if (!m) break + const body = m[1].trim() + if (!body) continue + if (/^SPDX-License-Identifier/i.test(body)) continue + if (/^[=\-_*#~]{4,}$/.test(body)) continue + if (/φ|phi\^?2/i.test(body) && /TRINITY/i.test(body)) continue + if (/^DO NOT EDIT/i.test(body)) continue + out.push(body) + if (out.length >= 6) break + } + return out.join(' ').replace(/\s+/g, ' ').trim() || null +} + const entries = [] for (const abs of files) { // Paths are now relative to the repo root, so a spec's real home (specs/, @@ -72,6 +116,16 @@ for (const abs of files) { mkdirSync(dirname(dest), { recursive: true }) writeFileSync(dest, text) + let a = null + try { a = analyze(text) } catch { a = null } + const failedBackends = a ? Object.entries(a.targets).filter(([, v]) => !v.ok).map(([k]) => k) : [] + const loss = a ? a.discarded.length + a.swallowed.length + a.lexerDiscarded.length : 0 + const tcErrors = a?.typecheck?.errorCount ?? 0 + // Three states, worst-wins. "fail" means something refused to produce output + // at all; "warn" means it produced output but the compiler flagged or dropped + // something on the way. + const health = !a || a.astError || failedBackends.length ? 'fail' : loss > 0 || tcErrors > 0 ? 'warn' : 'ok' + const parts = rel.split('/') // Two segments, not one: with the corpus widened past specs/, a single // segment would lump all 497 specs under "specs" and all 147 chip specs @@ -87,15 +141,46 @@ for (const abs of files) { module: moduleMatch ? moduleMatch[1] : null, lines: text.split('\n').length, bytes: Buffer.byteLength(text, 'utf8'), + description: describe(text), + health, + tokens: a?.tokenCount ?? 0, + nodes: a?.nodeCount ?? 0, + depth: a?.astDepth ?? 0, + loss, + tcErrors, + failedBackends, + // Output size per backend, so the library can show what a spec actually + // produces without re-running the compiler. + outBytes: a ? Object.fromEntries(Object.entries(a.targets).map(([k, v]) => [k, v.ok ? v.bytes : null])) : {}, }) } cpSync(WASM_SRC, join(OUT_DIR, 't27_compiler.wasm')) const wasmBytes = readFileSync(WASM_SRC).length +// The spec the page opens on: a deliberately small file that exercises every +// part of the language, so the first thing a visitor sees teaches the syntax +// rather than dropping them into a 1600-line FPGA spec. +const FEATURED = 'specs/demos/hello_world.t27' +const featuredIdx = entries.findIndex((e) => e.path === FEATURED) +if (featuredIdx > 0) { + const [f] = entries.splice(featuredIdx, 1) + f.featured = true + entries.unshift(f) +} else if (featuredIdx === 0) { + entries[0].featured = true +} else { + console.log(` warning: featured spec ${FEATURED} not found -- page will open on the first entry`) +} + const byCategory = {} for (const e of entries) byCategory[e.category] = (byCategory[e.category] || 0) + 1 +const health = { ok: 0, warn: 0, fail: 0 } +for (const e of entries) health[e.health]++ +const backendFailures = {} +for (const e of entries) for (const b of e.failedBackends) backendFailures[b] = (backendFailures[b] || 0) + 1 + writeFileSync(join(OUT_DIR, 'manifest.json'), JSON.stringify({ generatedFrom: { repo: 'gHashTag/t27', @@ -107,10 +192,21 @@ writeFileSync(join(OUT_DIR, 'manifest.json'), JSON.stringify({ specCount: entries.length, totalLines: entries.reduce((a, e) => a + e.lines, 0), categories: Object.fromEntries(Object.entries(byCategory).sort((a, b) => b[1] - a[1])), + health, + backendFailures, + featured: FEATURED, + totals: { + tokens: entries.reduce((a, e) => a + e.tokens, 0), + nodes: entries.reduce((a, e) => a + e.nodes, 0), + lossAffected: entries.filter((e) => e.loss > 0).length, + tcAffected: entries.filter((e) => e.tcErrors > 0).length, + }, specs: entries, }, null, 0)) console.log(`sync-t27-specs: ${entries.length} specs, ${Object.keys(byCategory).length} categories`) +console.log(` health: ${health.ok} ok · ${health.warn} warn · ${health.fail} fail`) +if (Object.keys(backendFailures).length) console.log(` backend failures: ${JSON.stringify(backendFailures)}`) console.log(` t27 @ ${shortSha}${dirty ? ' (DIRTY -- snapshot includes uncommitted spec/compiler changes)' : ''}`) console.log(` wasm ${(wasmBytes / 1024).toFixed(0)} KB -> public/t27/t27_compiler.wasm`) if (dirty) console.log(` warning: commit t27 before shipping, or the recorded SHA understates the snapshot`) diff --git a/apps/website/src/components/SpecGraphics.tsx b/apps/website/src/components/SpecGraphics.tsx new file mode 100644 index 0000000000..b0412554bc --- /dev/null +++ b/apps/website/src/components/SpecGraphics.tsx @@ -0,0 +1,144 @@ +// Inline SVG for the spec explorer. No chart library: every shape here is a +// handful of rects, which stays sharp at 12px and adds nothing to the bundle. + +import type { Health, T27Analysis } from '../lib/t27Compiler' + +export const HEALTH_COLOR: Record = { + ok: '#00FF88', + warn: '#f0a020', + fail: '#f85149', +} + +/** + * Corpus health as one stacked bar. + * + * A waffle chart (one cell per spec) was the other candidate and is prettier, + * but 668 cells cannot be read as proportions and the six failures would be + * three pixels lost in the grid. A stacked bar keeps the ratio legible and the + * counts live in the legend beside it, where they can be exact. + */ +export function HealthBar({ health, total }: { health: Record; total: number }) { + const order: Health[] = ['ok', 'warn', 'fail'] + let x = 0 + return ( + + {order.map((k) => { + const w = total > 0 ? (health[k] / total) * 100 : 0 + const el = + x += w + return el + })} + + ) +} + +/** A single spec's status as a 3px rail. Sits in a list row without adding height. */ +export function HealthDot({ health }: { health: Health }) { + return ( +