// // Kiwi Scientific Acceleration // Custom fixed and floating point and operator overloading preliminary tests. // DJ Greaves - Unversity of Cambridge, Computer Laboratory. // Gustafson and Yonemoto Posit Unum Coded in CSharp. // This variant: cac_posit.cs - alpha0 - ready for first debugging. // // The idea is we either change this file or else the values set up in cac_precsion to change the basic number system. // // I quickly coded this famework that just copies precision values around the heap and the precision info // should then be deleted by KiwiC HLS since it is all compile-time constant and so should have no runtime overhead. // Types we can consider are: // cac_pureint straightforward integer (or float etc, as natively supported). // cac_signmag sign+magnitude integer. // cac_fixed fixed field-width precision integer (using Kiwi.HwWidth() attribute where necessary). // cac_rational fixed point rational. // cac_fp floating point with smaller than usual exponent and mantissa precisions. // cac_posit posit unum system. using System; using KiwiSystem; // Q. Can I pass constant expressions into my attributes such as Kiwi.HwWidth() ? public class cac_precision { // The cac_precision information should be erased by KiwiC HLS since it is all compile-time constant. public enum cac_style { style0, style1 }; // We can use an enumeration to switch between alternative implementations within the indivdual overloaded method bodies, or else we can use different .dll files to link against, or we can just use straightfoward operator overloading polymorphism. cac_style style; int posit_width_; int posit_es_; static cac_precision singleton_default = null; // Smallest possible posit of a given es width has 1 sign bit, 2 regime bits and es exponent bits, with no mantissa, but the exponent // can be entirely missing too, so, the most trivial posit has 3 bits. public const int g_pwidth = 10; public const int g_pes = 2; // Exponent size // Regime encoding: e.g. 01 gives m=-1, 10 gives m=0, 0001 gives m=-3 and 11110 gives m=3. // 10-bit, es=2 examples // one = 0_10_00_00000 = 0x100 (s=0, m=0, e=0, m=0) // two = 0_10_01_00000 = 0x120 (s=0, m=0, e=1, m=0) // three = 0_10_01_10000 = 0x130 (s=0, m=0, e=1, m=16) // fifteen = 0_10_11_11100 = 0x17c (s=0, m=0, e=3, m=14) // sixteen = 0_110_00_0000 = 0x180 (s=0, m=1, e=0, m=0) // The maximum mantissa size is pwidth-1-2-es, which could feasibly be negative, in which case it is always a zero width mantissa. public const int g_max_mantissa_size = (g_pwidth-1-2-g_pes > 0) ? g_pwidth-1-2-g_pes: 0; public static int two_to_the(int v) { int r = 1; for (int i=0; i<v;i++) r *= 2; return r; } public static cac_precision defaultVal { get { if (singleton_default==null) singleton_default = new cac_precision(-1); return singleton_default; } } public int max_mantissa_size { get { return g_max_mantissa_size; } } public int expbase { get { return (int)(two_to_the(this.pes)); } } public int useed { get { return (int)(two_to_the(two_to_the(this.pes))); } } public int pes { get { return g_pes; } } public int pwidth { get { return g_pwidth; } } public cac_precision(int p0) // constructor { // this.style = style; //this.pwidth = p0; } public cac_posit inf // Return const infinity { get { cac_posit rr = 0; rr.vale = (1<<pwidth-1); rr.prec = this; return rr; } } public cac_posit zero // Return const zero. { get { cac_posit rr = 0; rr.vale = 0; rr.prec = this; return rr; } } } public struct cac_posit { public cac_precision prec; [Kiwi.HwWidth(cac_precision.g_pwidth)] public int vale; // We will pack everything into an int of custom precision. public cac_posit(cac_precision p0, int ivale) // Struct Constructor { if (ivale == 0) { prec = p0; vale = 0;} else { bool sign_y = ivale < 0; uint mant_y = (sign_y)? (uint)(0-ivale): (uint)(ivale); int exp_y = -p0.max_mantissa_size; this = repack(p0, sign_y, exp_y, mant_y); } } public cac_posit(int ivale) // Alternative Struct Constructor { prec = cac_precision.defaultVal; if (ivale == 0) { vale = 0;} else { bool sign_y = ivale < 0; uint mant_y = (sign_y)? (uint)(0-ivale): (uint)(ivale); int exp_y = -prec.max_mantissa_size; this = repack(prec, sign_y, exp_y, mant_y); } } public int rawbits // All raw bits of the posit { get { return vale; } } public bool isZero { get { return (vale == 0); } } int packedbits { get { return (vale & ((1<< prec.pwidth-1)-1)); } } public bool isNeg // Check whether -ve { get { if (prec == null) prec = cac_precision.defaultVal; return ((vale >> prec.pwidth-1)!=0) && (packedbits != 0); } } public bool isInf // Check whether infinity { get { // Console.WriteLine("this = {0}", this); Console.WriteLine("prec = {0}", prec); System.Diagnostics.Trace.Assert(prec!=null); return (vale == (1<<prec.pwidth-1)); } } cac_posit clone // Return fresh instance. A NOP infact, owing to C# structs being valuetypes. { get { return this; } } public cac_posit neg // Return negated value { get { if (isZero) return this; if (prec == null) prec = cac_precision.defaultVal; cac_posit rr = clone; // Straightforward two's complement is used for negation of posits. rr.vale = (0 - rr.vale) & ((1<<prec.pwidth)-1); return rr; } } public cac_posit zero // Return constant zero { get { cac_posit rr = 0; rr.vale = 0; rr.prec = this.prec; return rr; } } public int asInt // Convert posit to nearest int. { get { if (this.isZero)return 0; bool sign_l; int reg_l, exp_l; uint mant_l; this.unpack(out sign_l, out reg_l, out exp_l, out mant_l); Console.WriteLine("asInt posit unpacked to reg={0} exp={1} mant={2}", reg_l, exp_l, mant_l); while(reg_l > 0) { exp_l += prec.expbase; reg_l --; } while(reg_l < 0) { exp_l -= prec.expbase; reg_l ++; } int vale = (int) (mant_l * cac_precision.two_to_the(exp_l)) >> prec.max_mantissa_size; Console.WriteLine("asInt applied regime to exponent, giving {0}", exp_l); if (sign_l) vale = -vale; return vale; } } // User-defined conversion from int to cac_posit. Uses a C# implicit function. // We also need one for floats and doubles. public static implicit operator cac_posit(int ivale) { return new cac_posit(ivale); } public override bool Equals(object o) { if (o == null) return false; var second = (cac_posit)o; // as cac_posit; return vale == second.vale; } public override int GetHashCode() { return vale; } public static bool operator==(cac_posit lhs, cac_posit rhs) // Equality test operator { return lhs.vale == rhs.vale; } public static bool operator!=(cac_posit lhs, cac_posit rhs) // Inequality test operator { return !(lhs == rhs); } void unpack(out bool signbit, out int reg, out int exp, out uint mant) { uint vv = (uint) vale; signbit = ((vv >> prec.pwidth-1) & 1) != 0; if (signbit) vv = (uint)((1u<<prec.pwidth-1)-vale); // 2's complement negate. uint toprbit = (vv >> prec.pwidth-2) & 1u; uint pp = 1u << (prec.pwidth-4); uint mm = 1; while(prec.pwidth-2-mm >= 0 && ((vv >> (int)(prec.pwidth-2-mm)) & 1u) == toprbit) { mm += 1; pp >>= 1; } int kk = (int) ((toprbit==0u)? 0u-mm: mm-1); reg = kk; Console.WriteLine("unpack mm={0} pp=0x{1:x} reg={2}", mm, pp, reg); exp = 0; for (int q = 0; q<prec.pes; q++) { exp <<= 1; exp |= ((vv & pp)!=0u) ? 1: 0; Console.WriteLine(" exp {0} pp=0x{1:x}", exp, pp); pp >>= 1; // It is ok for pp to become zero during exponent extraction. } mant = 1; // The hidden bit // The mantissa with be a number with its msb set of total bit width max_mantissa_size+1. // There may sometimes be no bits left to represent the mantissa but we still left shift the hidden bit the same amount. for (int i=0;i<prec.max_mantissa_size; i++) { mant <<= 1; mant |= ((vv & pp)!=0u) ? 1u: 0u; pp >>= 1; // It is ok for pp to become zero during mantissa extraction. } } // Our pack and unpack are not symmetric, since the pack also performs normalising. // We normalise the mantissa and compute the regime inside the repack operation. // delete re from repack please static cac_posit repack(cac_precision prec, bool sign_y, int exp_y, uint mant_y) { // We ignore mantissa input bits to the left of the maximal hidden bit position - for larger inputs, the caller should pre-normalise that region. Console.WriteLine(" pack exp_in={0} mant_in={1}", exp_y, mant_y); int normaliser = prec.max_mantissa_size+1; while((mant_y & (1u << normaliser))==0u) // Find leading 1 in mantissa. Mantissa should never be zero since zeros follow a different control flow. { if (normaliser == 0) break; normaliser -= 1; } // For repack we find the regime and exponent from the division and remainder by useed. // Consider es=2, then useed is 2^(2^es)=16 and the exponent has values 0 to 3. exp_y += prec.max_mantissa_size + normaliser; Console.WriteLine(" pack normalised mormaliser={0} exp={1}", normaliser, exp_y); int reg_k = 0; // Determine regime, leaving remainder in exp_y. while(exp_y >= prec.expbase) { exp_y -= prec.expbase; reg_k += 1; } while(exp_y < 0) { exp_y += prec.expbase; reg_k -= 1; } uint vale = 0u; int pos = prec.pwidth-2; // Encode regime unary style with leading 0 if -ve. uint pp = (reg_k<0) ? 0u:1u; uint kk = (reg_k<0) ? (uint)(0-reg_k): (uint)(1+reg_k); // Number of digits before flip. for (int i=0; i< kk; i++) { vale |= (pp << pos); pos -= 1; } // Insert regime run vale |= ((1u-pp) << pos); pos -= 1; // Insert complement regime bit. // Now insert exponent bits, if room. for (int i=prec.pes-1; i>= 0; i--) { if (pos >= 0) { vale |= ((((exp_y & (1u << i))!=0) ? 1u: 0u) << pos); pos -= 1; } } // Now insert some or all of the mantissa bits, depending on room available, but leaving out the hidden bit. for (int i=normaliser-1; i>= 0; i--) { uint dd = ((mant_y & (1u << i))!=0) ? 1u: 0u; Console.WriteLine(" pack mantissa normaliser={0} pos={1} dd={2}", normaliser, pos, dd); vale |= (dd << pos); if (pos==0) break; pos -= 1; } cac_posit rr; rr.prec = prec; rr.vale = (!sign_y) ? (int)vale : (int)((1u<<(prec.pwidth-1))-vale); // 2's complement negate. Console.WriteLine(" posit pack: regime={0} exp={1} mantissa={2}", reg_k, exp_y, mant_y); Console.WriteLine(" posit pack: vale=0x{0:x} sign={1} final=0x{2:x}", vale, sign_y, rr.vale); return rr; } public static cac_posit operator+(cac_posit lhs, cac_posit rhs) // Addition operator { if (lhs.prec == null && rhs.prec == null) lhs.prec = cac_precision.defaultVal; if (rhs.prec == null) rhs.prec = lhs.prec; if (lhs.prec == null) lhs.prec = rhs.prec; cac_precision prec = (rhs.prec != null)? rhs.prec: lhs.prec; if (lhs.isZero) return rhs; if (rhs.isZero) return lhs; //if (lhs.isNaN && rhs.isNaN) return nan(prec); //if (lhs.isInf && rhs.isInf) return nan(prec); if (lhs.isInf || rhs.isInf) return prec.inf; if (lhs.neg == rhs) return prec.zero; bool sign_l; int reg_l, exp_l; uint mant_l; bool sign_r; int reg_r, exp_r; uint mant_r; bool doadd = (lhs.neg == rhs.neg); lhs.unpack(out sign_l, out reg_l, out exp_l, out mant_l); rhs.unpack(out sign_r, out reg_r, out exp_r, out mant_r); uint mant_y = (doadd)? mant_l + mant_r: mant_l - mant_r; int exp_y = exp_l; bool sign_y = lhs.isNeg; return repack(prec, sign_y, exp_y, mant_y); } public static cac_posit operator-(cac_posit lhs, cac_posit rhs) // Subtraction operator { return lhs + rhs.neg; } public static bool operator<(cac_posit lhs, cac_posit rhs) // Comparison operator 'less than' { if (!lhs.isNeg && rhs.isNeg) return true; if (lhs.isNeg && !rhs.isNeg) return false; bool ans = rhs.vale < lhs.vale; if (lhs.isNeg) ans = !ans; return ans; } public static bool operator>(cac_posit lhs, cac_posit rhs) // Comparison operator 'greater than' { return (rhs < lhs); } public static cac_posit operator>>(cac_posit lhs, int rhs) // Right-shift operator - arithmetic version. { cac_posit rr = 0; rr.prec = lhs.prec; rr.vale = lhs.vale >> rhs; if (lhs.isNeg) rr = rr.neg; return rr; } // TODO - add further opertor overloads as needed... }; // eof