crackNum-3.18: GUI/tclGUI/crackNum.tcl
#!/usr/bin/env wish
# CrackNum GUI — Tcl/Tk front-end for the crackNum command-line tool.
# Works on Linux and macOS. Requires wish (Tk 8.5+) and crackNum on PATH.
# ---------------------------------------------------------------------------
# Tool discovery
# ---------------------------------------------------------------------------
proc locate {name} {
set path [split [expr {
[info exists ::env(PATH)] ? $::env(PATH) : "/usr/bin:/bin"
}] :]
foreach dir $path {
set candidate [file join $dir $name]
if {[file executable $candidate]} { return $candidate }
}
return ""
}
set CRACKNUM [locate crackNum]
set Z3 [locate z3]
# ---------------------------------------------------------------------------
# Format table
# ---------------------------------------------------------------------------
# Each entry: {id label flag_kind flag_arg}
# flag_kind = fixed | customFloat | word | customWord | int | customInt
# flag_arg = the flag suffix for "fixed", or bit-count for "word"/"int"
set FORMAT_SECTIONS {
{"Float" {
{ffp4 "FP4 (E2M1)" fixed fp4}
{fe4m3 "FP8 (E4M3)" fixed e4m3}
{fe5m2 "FP8 (E5M2)" fixed e5m2}
{fhp "Half" fixed hp}
{fbp "Brain" fixed bp}
{fsp "Single" fixed sp}
{fdp "Double" fixed dp}
{fcs "Custom" customFloat {}}
}}
{"Word (Unsigned)" {
{w8 "8-bit" word 8}
{w16 "16-bit" word 16}
{w32 "32-bit" word 32}
{w64 "64-bit" word 64}
{wcs "Custom" customWord {}}
}}
{"Integer (Signed)" {
{i8 "8-bit" int 8}
{i16 "16-bit" int 16}
{i32 "32-bit" int 32}
{i64 "64-bit" int 64}
{ics "Custom" customInt {}}
}}
}
set ROUNDING_MODES {RNE RNA RTP RTN RTZ}
array set ROUNDING_LABELS {
RNE "RNE (Nearest, ties to even)"
RNA "RNA (Nearest, ties to away)"
RTP "RTP (Toward +∞)"
RTN "RTN (Toward -∞)"
RTZ "RTZ (Toward 0)"
}
# ---------------------------------------------------------------------------
# State
# ---------------------------------------------------------------------------
set state(selection) "" ;# selected format id
set state(value) ""
set state(rounding) "RNE"
set state(lanes) 1
set state(bitWidth) 64
set state(expWidth) 11
set state(fontSize) 11
set WELCOME {Enter a value above, then pick a format on the left to crack it.
You can:
- ENCODE: from a mathematical value to its internal representation
- DECODE: from an internal representation to its mathematical value
Encoding:
- Enter a decimal value (2.5, -4.1e5) or hex float (0x2.4p3).
- You can pass NaN, Inf, -0, -Inf for special values.
- For floats, pick a rounding mode.
- Input must NOT start with 0x, 0b, or N'h (else we decode instead).
Decoding:
- Use hex (0x), binary (0b), or Verilog (N'h) notation.
- You may use _, - or space as separators for readability.
- Verilog input longer than the format is decoded as SIMD lanes.}
# ---------------------------------------------------------------------------
# Build the precision flag from the selected format
# ---------------------------------------------------------------------------
proc precisionFlag {} {
global state FORMAT_SECTIONS
set sel $state(selection)
if {$sel eq ""} { return "" }
foreach section $FORMAT_SECTIONS {
foreach fmt [lindex $section 1] {
lassign $fmt id label kind arg
if {$id ne $sel} continue
switch $kind {
fixed { return "-f$arg" }
word { return "-w$arg" }
int { return "-i$arg" }
customWord { return "-w$state(bitWidth)" }
customInt { return "-i$state(bitWidth)" }
customFloat {
# Only check that the widths describe a well-formed layout; crackNum
# itself owns the remaining limits (and reports solver restrictions
# readably).
set bw $state(bitWidth)
set ew $state(expWidth)
set sig [expr {$bw - $ew - 1}]
if {$ew < 1 || $sig < 0} {
return [list invalid \
"Invalid custom FP format:
Total width: $bw
Sign : 1
Exponent : [format %4d $ew]
Significand: [format %4d $sig] (Total = Sign + Exponent + Significand)
Exponent must be at least 1 bit, and the total width must leave room for it and the sign."]
}
return "-f${ew}+[expr {$bw - $ew}]"
}
}
}
}
return ""
}
# ---------------------------------------------------------------------------
# Run crackNum and return output text
# ---------------------------------------------------------------------------
proc runCrackNum {} {
global state CRACKNUM Z3
if {$CRACKNUM eq ""} {
return "crackNum: Cannot locate the 'crackNum' binary on your PATH.\n\nMake sure it is installed and reachable (e.g. `which crackNum` works in your terminal)."
}
if {$Z3 eq ""} {
return "crackNum: Cannot locate the 'z3' binary on your PATH.\n\nMake sure it is installed and reachable (e.g. `which z3` works in your terminal)."
}
set flagResult [precisionFlag]
if {$flagResult eq ""} { return "" }
if {[lindex $flagResult 0] eq "invalid"} {
return [lindex $flagResult 1]
}
set flag $flagResult
set rm "-r$state(rounding)"
set val [expr {$state(value) eq "" ? "0" : $state(value)}]
# Pass SBV_Z3 so crackNum finds z3 even when PATH is minimal.
set savedZ3 [expr {[info exists ::env(SBV_Z3)] ? $::env(SBV_Z3) : ""}]
set ::env(SBV_Z3) $Z3
set cmd [list $CRACKNUM $flag $rm]
# Only pass -l when it's actually multi-lane: giving -l1 explicitly would
# suppress crackNum's lane inference for Verilog (N'h) input.
if {[string is integer -strict $state(lanes)] && $state(lanes) > 1} {
lappend cmd -l$state(lanes)
}
lappend cmd -- $val
# 2>@1 folds stderr into the captured result, so errors show up in the pane.
set rc [catch {exec {*}$cmd 2>@1} output]
if {$savedZ3 eq ""} { unset -nocomplain ::env(SBV_Z3) } \
else { set ::env(SBV_Z3) $savedZ3 }
if {$rc && ![string match "*ENCODED*" $output] && ![string match "*DECODED*" $output]} {
append output "\n\n** Call to crackNum failed! Make sure the value makes sense for the chosen format."
append output "\n**"
append output "\n** Run: $cmd"
append output "\n**"
append output "\n** Value : $val"
}
return $output
}
# ---------------------------------------------------------------------------
# Show output in the text widget
# ---------------------------------------------------------------------------
proc showOutput {text} {
.output configure -state normal
.output delete 1.0 end
.output insert end $text
.output configure -state disabled
}
proc crack {} {
global state FORMAT_SECTIONS
set sel $state(selection)
if {$sel eq ""} return
set out [runCrackNum]
# Determine label for header
set label ""
foreach section $FORMAT_SECTIONS {
foreach fmt [lindex $section 1] {
if {[lindex $fmt 0] eq $sel} { set label [lindex $fmt 1] }
}
}
if {[string match "*ENCODED*" $out]} { set kind "Encoding in format" } \
elseif {[string match "*DECODED*" $out]} { set kind "Decoded using format" } \
else { set kind "Format" }
showOutput "\[$kind: $label\]\n\n$out"
}
# ---------------------------------------------------------------------------
# Font size helpers
# ---------------------------------------------------------------------------
proc applyFontSize {} {
global state
.output configure -font [list Courier $state(fontSize)]
}
proc zoomIn {} { incr ::state(fontSize); applyFontSize }
proc zoomOut {} {
if {$::state(fontSize) > 6} { incr ::state(fontSize) -1; applyFontSize }
}
# ---------------------------------------------------------------------------
# Build UI
# ---------------------------------------------------------------------------
wm title . "CrackNum"
wm minsize . 1200 700
wm geometry . 1200x700
image create photo appIcon -data {
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
rkJggg==
}
wm iconphoto . appIcon
# ---- Menu bar ------------------------------------------------------------
menu .mb -tearoff 0
. configure -menu .mb
menu .mb.file -tearoff 0
.mb add cascade -label "File" -menu .mb.file -underline 0
.mb.file add command -label "Close" -accelerator "Ctrl+W" -command { destroy . }
bind . <Control-w> { destroy . }
bind . <Control-q> { destroy . }
menu .mb.edit -tearoff 0
.mb add cascade -label "Edit" -menu .mb.edit -underline 0
.mb.edit add command -label "Cut" -accelerator "Ctrl+X" -command { event generate [focus] <<Cut>> }
.mb.edit add command -label "Copy" -accelerator "Ctrl+C" -command { event generate [focus] <<Copy>> }
.mb.edit add command -label "Paste" -accelerator "Ctrl+V" -command { event generate [focus] <<Paste>> }
.mb.edit add separator
.mb.edit add command -label "Select All" -accelerator "Ctrl+A" -command { event generate [focus] <<SelectAll>> }
# ---- Top bar: zoom, help, value entry ------------------------------------
frame .top
pack .top -fill x -padx 8 -pady 6
foreach {fr txt fnt cmd} {
.top.zf1 "A" {TkDefaultFont 8} zoomOut
.top.zf2 "A" {TkDefaultFont 14} zoomIn
} {
frame $fr -width 28 -height 28
pack propagate $fr 0
button $fr.b -text $txt -font $fnt -command $cmd
pack $fr.b -fill both -expand yes
pack $fr -side left -padx 2
}
button .top.help -text "?" -command { showOutput $::WELCOME }
pack .top.help -side left -padx 2
entry .top.val -textvariable state(value) -font {Courier 11} -width 28
pack .top.val -side right -padx {0 4}
bind .top.val <Return> crack
label .top.lbl -text "Value:"
pack .top.lbl -side right -padx {4 2}
# ---- Main pane: sidebar + output ----------------------------------------
frame .main
pack .main -fill both -expand yes -padx 8 -pady {0 8}
# Sidebar
frame .main.side -width 240
pack .main.side -side left -fill y -padx {0 6}
pack propagate .main.side 0
# Format list
ttk::style configure Treeview -rowheight 22
ttk::style configure Treeview.Item -padding {4 0}
ttk::treeview .main.side.lb -selectmode browse -show tree -height 18
pack .main.side.lb -fill both -expand yes
.main.side.lb tag configure hdr -font {TkDefaultFont 9 bold}
.main.side.lb tag configure item -font {TkDefaultFont 9}
# Populate treeview; build item-id <-> format-id mappings
array set ITEM_FMT {} ;# treeview item id -> format id
array set FMT_ITEM {} ;# format id -> treeview item id
foreach section $FORMAT_SECTIONS {
set title [lindex $section 0]
set sid [.main.side.lb insert {} end -text $title -open yes -tags hdr]
foreach fmt [lindex $section 1] {
set fid [lindex $fmt 0]
set iid [.main.side.lb insert $sid end -text [lindex $fmt 1] -tags item]
set ITEM_FMT($iid) $fid
set FMT_ITEM($fid) $iid
}
}
bind .main.side.lb <<TreeviewSelect>> {
set sel [.main.side.lb selection]
if {$sel ne "" && [info exists ITEM_FMT($sel)]} {
set state(selection) $ITEM_FMT($sel)
crack
} else {
.main.side.lb selection remove $sel
}
}
# Rounding
frame .main.side.rm
pack .main.side.rm -fill x -pady {6 0}
label .main.side.rm.lbl -text "Rounding mode:" -anchor w
pack .main.side.rm.lbl -fill x
ttk::combobox .main.side.rm.cb -state readonly -width 28
pack .main.side.rm.cb -fill x
foreach rm {RNE RNA RTP RTN RTZ} { lappend rm_labels $::ROUNDING_LABELS($rm) }
.main.side.rm.cb configure -values $rm_labels
# Show the full label in the combo but store the code in state(rounding)
proc rmLabel2Code {label} {
foreach rm {RNE RNA RTP RTN RTZ} {
if {$::ROUNDING_LABELS($rm) eq $label} { return $rm }
}
return RNE
}
proc rmCode2Label {code} { return $::ROUNDING_LABELS($code) }
.main.side.rm.cb set [rmCode2Label $state(rounding)]
bind .main.side.rm.cb <<ComboboxSelected>> {
set state(rounding) [rmLabel2Code [.main.side.rm.cb get]]
crack
}
# Lanes (decoding only; crackNum rejects -l when encoding)
frame .main.side.ln
pack .main.side.ln -fill x -pady {6 0}
label .main.side.ln.l -text "Lanes:"
pack .main.side.ln.l -side left
entry .main.side.ln.e -textvariable state(lanes) -width 6 -justify right \
-font {Courier 11}
pack .main.side.ln.e -side right
bind .main.side.ln.e <Return> crack
label .main.side.ln2 \
-text "(lanes apply to decoding only)" \
-font {TkDefaultFont 8} -foreground gray -wraplength 200 -justify left
pack .main.side.ln2 -fill x
# Custom parameters
labelframe .main.side.custom -text "Custom parameters" -padx 4 -pady 4
pack .main.side.custom -fill x -pady {8 0}
frame .main.side.custom.bw
pack .main.side.custom.bw -fill x -pady 2
label .main.side.custom.bw.l -text "Total width:"
pack .main.side.custom.bw.l -side left
entry .main.side.custom.bw.e -textvariable state(bitWidth) -width 6 -justify right \
-font {Courier 11}
pack .main.side.custom.bw.e -side right
bind .main.side.custom.bw.e <Return> crack
frame .main.side.custom.ew
pack .main.side.custom.ew -fill x -pady 2
label .main.side.custom.ew.l -text "Exponent width:"
pack .main.side.custom.ew.l -side left
entry .main.side.custom.ew.e -textvariable state(expWidth) -width 6 -justify right \
-font {Courier 11}
pack .main.side.custom.ew.e -side right
bind .main.side.custom.ew.e <Return> crack
label .main.side.custom.note \
-text "(exponent width applies to custom floats)" \
-font {TkDefaultFont 8} -foreground gray -wraplength 200 -justify left
pack .main.side.custom.note -fill x -pady {4 0}
# Output pane
frame .main.out
pack .main.out -side left -fill both -expand yes
text .output -state disabled -wrap none \
-font [list Courier $state(fontSize)] \
-padx 8 -pady 8 \
-xscrollcommand {.main.out.sx set} \
-yscrollcommand {.main.out.sy set}
scrollbar .main.out.sy -orient vertical -command {.output yview}
scrollbar .main.out.sx -orient horizontal -command {.output xview}
grid .output .main.out.sy -in .main.out -sticky nsew
grid .main.out.sx -in .main.out -sticky ew
grid columnconfigure .main.out 0 -weight 1
grid rowconfigure .main.out 0 -weight 1
# ---------------------------------------------------------------------------
# Parse crackNum-style command-line args (forwarded by `crackNum --gui ...`)
# ---------------------------------------------------------------------------
proc parseArgs {argv} {
global state FORMAT_SECTIONS FMT_ITEM
set values {}
set i 0
while {$i < [llength $argv]} {
set a [lindex $argv $i]
if {$a eq "--"} {
lappend values {*}[lrange $argv [expr {$i+1}] end]
break
}
if {[string match "-f*" $a]} {
set v [string tolower [string range $a 2 end]]
switch $v {
sp { set state(selection) fsp }
dp { set state(selection) fdp }
hp { set state(selection) fhp }
bp { set state(selection) fbp }
e4m3 { set state(selection) fe4m3 }
e5m2 { set state(selection) fe5m2 }
fp4 { set state(selection) ffp4 }
default {
if {[regexp {^(\d+)\+(\d+)$} $v _ e s]} {
set state(selection) fcs
set state(expWidth) $e
set state(bitWidth) [expr {$e + $s}]
}
}
}
} elseif {[string match "-w*" $a]} {
set v [string range $a 2 end]
if {$v in {8 16 32 64}} { set state(selection) w$v } \
elseif {[string is integer -strict $v] && $v > 0} {
set state(selection) wcs
set state(bitWidth) $v
}
} elseif {[string match "-i*" $a]} {
set v [string range $a 2 end]
if {$v in {8 16 32 64}} { set state(selection) i$v } \
elseif {[string is integer -strict $v] && $v > 0} {
set state(selection) ics
set state(bitWidth) $v
}
} elseif {[string match "-r*" $a]} {
set rm [string toupper [string range $a 2 end]]
if {$rm in {RNE RNA RTP RTN RTZ}} { set state(rounding) $rm }
} elseif {[string match "-l*" $a]} {
set v [string range $a 2 end]
if {[string is integer -strict $v] && $v > 0} { set state(lanes) $v }
} elseif {![string match "-*" $a]} {
lappend values $a
}
incr i
}
if {[llength $values]} { set state(value) [join $values " "] }
# Sync treeview selection highlight
if {$state(selection) ne "" && [info exists FMT_ITEM($state(selection))]} {
set iid $FMT_ITEM($state(selection))
.main.side.lb selection set $iid
.main.side.lb see $iid
}
# Sync rounding combo label. Must happen even when no format was given:
# `crackNum --gui -rRTZ` sets state(rounding) but leaves no selection.
.main.side.rm.cb set [rmCode2Label $state(rounding)]
}
# ---------------------------------------------------------------------------
# Start
# ---------------------------------------------------------------------------
showOutput $WELCOME
parseArgs $argv
if {$state(selection) ne ""} { crack }
focus .top.val
.top.val icursor end