Honeycomb

4. RTL as a Lean DSL🔗

The generated Honeycomb cell is not a raw SystemVerilog string. It is a Lean value in a small RTL DSL. The emitter renders that value, and CI checks that the checked-in rtl/honeycomb_cell.sv is exactly the renderer output.

The proof reaches the DSL value itself. Four theorem groups carry the execute path:

  • cellExecCycle_refines_step relates the cycle model — a Lean transcription of the cell's execute behavior — to the ISA step function: when the cell is busy and not halted, one cycle commits exactly the ISA's architectural state for the decoded instruction at pc.

  • cellExec_refines_execDecoded closes the gap that used to be bridged by eye: it takes the generated AST's execute statement tree (cellExecStmts, the very case_ opcode tree embedded in the emitted honeycomb_cell, honeycomb_cell_sync, honeycomb_cell_pipe, and both net cells), runs it under a width- and signedness-aware statement semantics with its read-stage wires pinned to the model's reads, and proves the committed program counter, accumulator, kernel pointers, halt flag, and every register equal execDecoded's — for every opcode, every ALU funct, and all states.

  • cellRead_refines_envOf discharges that pinning: the base cell's combinational read/decode stage (cellReadAssigns, the assign items the emitted module embeds, in module order), evaluated from nothing but the sequential state and the encoded 32-bit instruction word, computes exactly the values the exec theorem consumes. Field extraction is proved against the encoding layer, and the evaluation semantics carries the one rule the exec stage never needed — SystemVerilog's sign-extending widening assignment, the difference between a correct negative immediate and a silently zero-extended one. The residual boundary is the SRAM read-port contract rdata = mem[raddr], and cellRead_sram_addresses proves the stage drives each raddr with exactly the model's wrapped index. The st/stw memory effects — invisible to the exec case tree, which only commits registers — are covered by cellRead_store_port/cellRead_weight_port: enable, wrapped address, and stored (element-truncated) value are the model's.

  • cellMacExecC_mac/cellMacExecC_kmac cover the one tree the base theorem does not: the mac and fpipe cells accumulate the drained two-stage multiplier output prod instead of a combinational product. With prod pinned to the model's product for the committing instruction (the value the run-level pipeline invariants maintain), the prod arms commit exactly execDecoded's state; the fpipe tree is proved equal to the mac tree (cellFpipeArms_eq_macArms), and beyond mac/kmac the dispatch is proved to select the very same arm ASTs the base-cell theorem evaluates (macExec_selects_shared, cellMacArms_tail_shared) — those shared-arm evaluations are not duplicated under the prod overlay.

namespace Honeycomb Honeycomb.cellExecCycle_refines_step (c : CellState) (hbusy : c.busy = true) (hhalt : c.arch.halted = false) : step defaultConfig (programOf c.imem) c.arch = some (cellExecCycle c).arch#check cellExecCycle_refines_step Honeycomb.cellExec_refines_execDecoded (s : State defaultConfig) (d : DecodedInstr) (hhalt : s.halted = false) : cellExecRun s d "pc" = BitVec.toNat (execDecoded d s).pc cellExecRun s d "acc" = BitVec.toNat (execDecoded d s).acc cellExecRun s d "wptr" = BitVec.toNat (execDecoded d s).wptr cellExecRun s d "sptr" = BitVec.toNat (execDecoded d s).sptr (cellExecRun s d "halted" = if (execDecoded d s).halted = true then 1 else 0) (i : Reg defaultConfig), cellExecRun s d (arrName "regs" i) = BitVec.toNat ((execDecoded d s).regs i)#check cellExec_refines_execDecoded Honeycomb.cellRead_refines_envOf (s : State defaultConfig) (e : EncodedInstr) : cellReadRun s e "opcode" = envOf s e.toDecoded "opcode" cellReadRun s e "rd_idx" = envOf s e.toDecoded "rd_idx" cellReadRun s e "ra_idx" = envOf s e.toDecoded "ra_idx" cellReadRun s e "rb_idx" = envOf s e.toDecoded "rb_idx" cellReadRun s e "instr_imm" = envOf s e.toDecoded "instr_imm" cellReadRun s e "imm_word" = envOf s e.toDecoded "imm_word" cellReadRun s e "branch_off" = envOf s e.toDecoded "branch_off" cellReadRun s e "mac_term" = envOf s e.toDecoded "mac_term" cellReadRun s e "kmac_term" = envOf s e.toDecoded "kmac_term" cellReadRun s e "data_load_word" = envOf s e.toDecoded "data_load_word" cellReadRun s e "pc" = envOf s e.toDecoded "pc" cellReadRun s e "acc" = envOf s e.toDecoded "acc" cellReadRun s e "wptr" = envOf s e.toDecoded "wptr" cellReadRun s e "sptr" = envOf s e.toDecoded "sptr" cellReadRun s e "rst_n" = envOf s e.toDecoded "rst_n" cellReadRun s e "start" = envOf s e.toDecoded "start" cellReadRun s e "busy" = envOf s e.toDecoded "busy" cellReadRun s e "halted" = envOf s e.toDecoded "halted" (i : Reg defaultConfig), cellReadRun s e (arrName "regs" i) = envOf s e.toDecoded (arrName "regs" i)#check cellRead_refines_envOf Honeycomb.cellRead_sram_addresses (s : State defaultConfig) (e : EncodedInstr) : cellReadRun s e "pc_index" = memIndexOfNat defaultConfig (BitVec.toNat s.pc) cellReadRun s e "w_index" = memIndexOfLocal defaultConfig s.wptr cellReadRun s e "s_index" = memIndexOfLocal defaultConfig s.sptr cellReadRun s e "data_load_index" = memIndexOfWord defaultConfig (readReg s e.ra)#check cellRead_sram_addresses Honeycomb.cellRead_store_port (s : State defaultConfig) (e : EncodedInstr) (hop : e.op = CellOp.st) : cellReadRun s e "data_sram_we" = 1 cellReadRun s e "data_sram_waddr" = memIndexOfWord defaultConfig (readReg s e.ra) cellReadRun s e "data_sram_wdata" = BitVec.toNat (readReg s e.rb)#check cellRead_store_port Honeycomb.cellRead_weight_port (s : State defaultConfig) (e : EncodedInstr) (hop : e.op = CellOp.stw) : cellReadRun s e "weight_sram_we" = 1 cellReadRun s e "weight_sram_waddr" = memIndexOfWord defaultConfig (readReg s e.ra) cellReadRun s e "weight_sram_wdata" = BitVec.toNat (elemOfWord defaultConfig (readReg s e.rb))#check cellRead_weight_port Honeycomb.cellMacExecC_mac (s : State defaultConfig) (rd ra rb : Reg defaultConfig) (imm : Int) (addr p : Nat) (hhalt : s.halted = false) (hp : p = BitVec.toNat (wordOfInt defaultConfig (product (elemOfWord defaultConfig (readReg s ra)) (elemOfWord defaultConfig (readReg s rb))))) : cellMacExecRun s { op := CellOp.mac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } p "pc" = BitVec.toNat (execDecoded { op := CellOp.mac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } s).pc cellMacExecRun s { op := CellOp.mac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } p "acc" = BitVec.toNat (execDecoded { op := CellOp.mac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } s).acc cellMacExecRun s { op := CellOp.mac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } p "wptr" = BitVec.toNat (execDecoded { op := CellOp.mac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } s).wptr cellMacExecRun s { op := CellOp.mac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } p "sptr" = BitVec.toNat (execDecoded { op := CellOp.mac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } s).sptr (cellMacExecRun s { op := CellOp.mac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } p "halted" = if (execDecoded { op := CellOp.mac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } s).halted = true then 1 else 0) (i : Reg defaultConfig), cellMacExecRun s { op := CellOp.mac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } p (arrName "regs" i) = BitVec.toNat ((execDecoded { op := CellOp.mac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } s).regs i)#check cellMacExecC_mac Honeycomb.cellMacExecC_kmac (s : State defaultConfig) (rd ra rb : Reg defaultConfig) (imm : Int) (addr p : Nat) (hhalt : s.halted = false) (hp : p = BitVec.toNat (wordOfInt defaultConfig (product (readWeight s (memIndexOfLocal defaultConfig s.wptr)) (readStream s (memIndexOfLocal defaultConfig s.sptr))))) : cellMacExecRun s { op := CellOp.kmac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } p "pc" = BitVec.toNat (execDecoded { op := CellOp.kmac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } s).pc cellMacExecRun s { op := CellOp.kmac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } p "acc" = BitVec.toNat (execDecoded { op := CellOp.kmac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } s).acc cellMacExecRun s { op := CellOp.kmac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } p "wptr" = BitVec.toNat (execDecoded { op := CellOp.kmac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } s).wptr cellMacExecRun s { op := CellOp.kmac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } p "sptr" = BitVec.toNat (execDecoded { op := CellOp.kmac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } s).sptr (cellMacExecRun s { op := CellOp.kmac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } p "halted" = if (execDecoded { op := CellOp.kmac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } s).halted = true then 1 else 0) (i : Reg defaultConfig), cellMacExecRun s { op := CellOp.kmac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } p (arrName "regs" i) = BitVec.toNat ((execDecoded { op := CellOp.kmac, rd := rd, ra := ra, rb := rb, imm := imm, addr := addr } s).regs i)#check cellMacExecC_kmac Honeycomb.cellFpipeArms_eq_macArms : Honeycomb.fpipeExecCaseArms✝ = Honeycomb.macExecCaseArms✝#check cellFpipeArms_eq_macArms Honeycomb.macExec_selects_shared (env : Env) (x : Nat) (dflt : Option (List SV.Stmt)) (hx0 : ¬x = 0) (hx1 : ¬x = 1) : caseSel cellW env x cellMacExecCaseArms dflt = caseSel cellW env x cellBaseExecCaseArms dflt#check macExec_selects_shared end Honeycomb

Preparing the AST-level theorem surfaced two real divergences the golden suites had never exercised — stw halted five of the six cell variants instead of writing weight memory, and the model's funct decode read the full immediate where the hardware cases on its low four bits — both fixed before the theorem could close (review R3).

The trusted text renderer below the DSL value is intentionally small. What remains outside these theorems: the honeycomb_sram adapter's read-port contract (rdata = mem[raddr] — a few-line module, golden-tested, with its address inputs proved above), the sequential wrapper around the exec tree (reset/start/phase control, covered by the run-level theorems against the cycle model), and a parser-level guarantee about external SystemVerilog tooling, which remains a documented non-goal.