librarylibs/Fixed/src/Fixed.xtl
Fixed: fixed-width two's complement numbers at a stated width, the arithmetic a w-bit circuit does Import it with an alias of your choice: "fx:" u_se< "Fixed". Put libs/Fixed/src on XETAL_PATH ("just path"); the reference is libs/Fixed/docs. Names with l: are exported; the others are private to this file.
X_eTaL's Int is 64 bits wide; hardware is 8, 16 or 32. Until X_eTaL
has sized integer types (ask F3 in docs/xetal-asks.md), the width is
written in the program: 16 ᶠˣw̲rap x + y is what a 16-bit adder
gives, and the evaluator computes exactly that, so it is the
reference the circuit must match bit for bit. Every function takes
the width first (one number) and works on whole arrays of any
shape. A function of three arguments is used curried: bind the
width once, ᵘa̲dd ← ᶠˣa̲dd 16, then x ᵘa̲dd y.
The ranges
ˡl̲owest : Num a => a -> a
l_owest w: the lowest signed value of w bits, -2^(w-1) (-128 at 8 bits).
ᶠˣ⁼u̲se< "Fixed"
ᶠˣl̲owest 8 16 -128 -32768
ˡl̲owest ← { w → n̲eg 2 ^ w − 1 }
ˡh̲ighest : Num a => a -> a
h_ighest w: the highest signed value of w bits, 2^(w-1) - 1 (127 at 8 bits).
ᶠˣ⁼u̲se< "Fixed"
ᶠˣh̲ighest 8 16 127 32767
ˡh̲ighest ← { w → (2 ^ w − 1) − 1 }
Wrapping
ˡu̲wrap : Int -> Int -> Int
w u_wrap x: x reduced to the unsigned range of w bits, 0 to 2^w - 1: the bit pattern a w-bit register holds, read unsigned.
ᶠˣ⁼u̲se< "Fixed"
8 ᶠˣu̲wrap 100 200 -1 256 100 200 255 0
ˡu̲wrap ← { w x → x m̲od 2 ^ w }
ˡw̲rap : Int -> Int -> Int
w w_rap x: x reduced to the signed range of w bits: the same bits, read as two's complement (a pattern at or above 2^(w-1) is negative).
ᶠˣ⁼u̲se< "Fixed"
8 ᶠˣw̲rap 100 200 -1 -129 100 -56 -1 127
ˡw̲rap ← { w x → u ← w ˡu̲wrap x u − (2 ^ w) × u ≥ 2 ^ w − 1 }
ˡo̲verflow? : Truthy a => Int -> Int -> a
w o_verflow? x: whether x does not fit in w signed bits, so a w-bit unit holding it has wrapped.
ᶠˣ⁼u̲se< "Fixed"
8 ᶠˣo̲verflow? 127 128 0 1
ˡo̲verflow? ← { w x → x ≠ w ˡw̲rap x }
ˡs̲at : Num a => a -> a -> a
w s_at x: x clamped to the signed range (saturating arithmetic: what a DSP does instead of wrapping).
ᶠˣ⁼u̲se< "Fixed"
8 ᶠˣs̲at 300 -300 5 127 -128 5
ˡs̲at ← { w x → (ˡl̲owest w) m̲ax x m̲in ˡh̲ighest w }
Arithmetic, wrapped: what a w-bit adder, subtractor, multiplier gives
ˡa̲dd : Int -> Int -> Int -> Int
w a_dd x y: the sum wrapped to w bits, item by item, with scalar extension: what a w-bit adder gives. Bind the width once: u:a_dd := fx:a_dd 16, then x u:a_dd y.
ᶠˣ⁼u̲se< "Fixed"
ᵘa̲dd ← ᶠˣa̲dd 8
100 ᵘa̲dd 100 -56
ˡa̲dd ← { w x y → w ˡw̲rap x + y }
ˡs̲ub : Int -> Int -> Int -> Int
w s_ub x y: the difference wrapped to w bits (a w-bit subtractor).
ᶠˣ⁼u̲se< "Fixed"
ᵘs̲ub ← ᶠˣs̲ub 8
-100 ᵘs̲ub 100 56
ˡs̲ub ← { w x y → w ˡw̲rap x − y }
ˡm̲ul : Int -> Int -> Int -> Int
w m_ul x y: the product wrapped to w bits (a w-bit multiplier keeps the low w bits).
ᶠˣ⁼u̲se< "Fixed"
ᵘm̲ul ← ᶠˣm̲ul 8
16 ᵘm̲ul 16 0
ˡm̲ul ← { w x y → w ˡw̲rap x × y }
ˡn̲eg : Int -> Int -> Int
w n_eg x: the negation wrapped (the lowest value is its own negation).
ᶠˣ⁼u̲se< "Fixed"
8 ᶠˣn̲eg 5 -128 -5 -128
ˡn̲eg ← { w x → w ˡw̲rap n̲eg x }
Bits
ˡb̲its : Int -> Int -> Int
w b_its x: the two's complement bits of each number, most significant first: a vector for one number, a row per number for a vector (as Bits' b_its, which reads numbers unsigned).
ᶠˣ⁼u̲se< "Fixed"
8 ᶠˣb̲its 5 -1 0 0 0 0 0 1 0 1 1 1 1 1 1 1 1 1
ˡb̲its ← { w x → o̲\ (w r̲eshape 2) e̲ncode w ˡu̲wrap x }
ˡv̲alue : Int -> Int
v_alue bits: the signed number each row of bits (or the vector) stands for, the inverse of b_its; the width is the row's length.
ᶠˣ⁼u̲se< "Fixed"
ᶠˣv̲alue 1 1 1 1 1 0 1 1 -5
ˡv̲alue ← { bits → (t̲ally o̲\ bits) ˡw̲rap 2 d̲ecode o̲\ bits }