util: Simplify/consolidate the python conversion module.

The python conversion module was really repetitive and fragmented,
where some types of conversions use common code, and some use hand
written case statements which did something very similar. Also, some
types like Voltage could only handle V and mV but no other scaling
prefix.

This change restructures the module to centralize a lot of the unit
handling code into toFloat, and makes the various other functions use
it.

Change-Id: Ic8529203cc226c9b551b8535a444e3f2f25ad1eb
Reviewed-on: https://gem5-review.googlesource.com/5621
Reviewed-by: Jason Lowe-Power <jason@lowepower.com>
Reviewed-by: Andreas Sandberg <andreas.sandberg@arm.com>
Maintainer: Andreas Sandberg <andreas.sandberg@arm.com>
diff --git a/src/python/m5/util/convert.py b/src/python/m5/util/convert.py
index 351ee1e..cffa1bb 100644
--- a/src/python/m5/util/convert.py
+++ b/src/python/m5/util/convert.py
@@ -29,19 +29,19 @@
 #          Gabe Black
 
 # metric prefixes
-exa  = 1.0e18
-peta = 1.0e15
-tera = 1.0e12
-giga = 1.0e9
-mega = 1.0e6
-kilo = 1.0e3
-
-milli = 1.0e-3
-micro = 1.0e-6
-nano  = 1.0e-9
-pico  = 1.0e-12
-femto = 1.0e-15
 atto  = 1.0e-18
+femto = 1.0e-15
+pico  = 1.0e-12
+nano  = 1.0e-9
+micro = 1.0e-6
+milli = 1.0e-3
+
+kilo = 1.0e3
+mega = 1.0e6
+giga = 1.0e9
+tera = 1.0e12
+peta = 1.0e15
+exa  = 1.0e18
 
 # power of 2 prefixes
 kibi = 1024
@@ -51,122 +51,115 @@
 pebi = tebi * 1024
 exbi = pebi * 1024
 
-# memory size configuration stuff
-def toFloat(value):
+metric_prefixes = {
+    'Ei': exbi,
+    'E': exa,
+    'Pi': pebi,
+    'P': peta,
+    'Ti': tebi,
+    'T': tera,
+    'Gi': gibi,
+    'G': giga,
+    'M': mega,
+    'ki': kibi,
+    'k': kilo,
+    'Mi': mebi,
+    'm': milli,
+    'u': micro,
+    'n': nano,
+    'p': pico,
+    'f': femto,
+    'a': atto,
+}
+
+binary_prefixes = {
+    'Ei': exbi,
+    'E' : exbi,
+    'Pi': pebi,
+    'P' : pebi,
+    'Ti': tebi,
+    'T' : tebi,
+    'Gi': gibi,
+    'G' : gibi,
+    'Mi': mebi,
+    'M' : mebi,
+    'ki': kibi,
+    'k' : kibi,
+}
+
+def assertStr(value):
     if not isinstance(value, str):
         raise TypeError, "wrong type '%s' should be str" % type(value)
 
-    if value.endswith('Ei'):
-        return float(value[:-2]) * exbi
-    elif value.endswith('Pi'):
-        return float(value[:-2]) * pebi
-    elif value.endswith('Ti'):
-        return float(value[:-2]) * tebi
-    elif value.endswith('Gi'):
-        return float(value[:-2]) * gibi
-    elif value.endswith('Mi'):
-        return float(value[:-2]) * mebi
-    elif value.endswith('ki'):
-        return float(value[:-2]) * kibi
-    elif value.endswith('E'):
-        return float(value[:-1]) * exa
-    elif value.endswith('P'):
-        return float(value[:-1]) * peta
-    elif value.endswith('T'):
-        return float(value[:-1]) * tera
-    elif value.endswith('G'):
-        return float(value[:-1]) * giga
-    elif value.endswith('M'):
-        return float(value[:-1]) * mega
-    elif value.endswith('k'):
-        return float(value[:-1]) * kilo
-    elif value.endswith('m'):
-        return float(value[:-1]) * milli
-    elif value.endswith('u'):
-        return float(value[:-1]) * micro
-    elif value.endswith('n'):
-        return float(value[:-1]) * nano
-    elif value.endswith('p'):
-        return float(value[:-1]) * pico
-    elif value.endswith('f'):
-        return float(value[:-1]) * femto
-    else:
-        return float(value)
 
-def toInteger(value):
-    value = toFloat(value)
+# memory size configuration stuff
+def toFloat(value, target_type='float', units=None, prefixes=[]):
+    assertStr(value)
+
+    if units and not value.endswith(units):
+        units = None
+    if not units:
+        try:
+            return float(value)
+        except ValueError:
+            raise ValueError, "cannot convert '%s' to %s" % \
+                    (value, target_type)
+
+    value = value[:-len(units)]
+
+    prefix = next((p for p in prefixes.keys() if value.endswith(p)), None)
+    if not prefix:
+        return float(value)
+    value = value[:-len(prefix)]
+
+    return float(value) * prefixes[prefix]
+
+def toMetricFloat(value, target_type='float', units=None):
+    return toFloat(value, target_type, units, metric_prefixes)
+
+def toBinaryFloat(value, target_type='float', units=None):
+    return toFloat(value, target_type, units, binary_prefixes)
+
+def toInteger(value, target_type='integer', units=None, prefixes=[]):
+    value = toFloat(value, target_type, units, prefixes)
     result = long(value)
     if value != result:
-        raise ValueError, "cannot convert '%s' to integer" % value
+        raise ValueError, "cannot convert '%s' to integer %s" % \
+                (value, target_type)
 
     return result
 
-_bool_dict = {
-    'true' : True,   't' : True,  'yes' : True, 'y' : True,  '1' : True,
-    'false' : False, 'f' : False, 'no' : False, 'n' : False, '0' : False
-    }
+def toMetricInteger(value, target_type='integer', units=None):
+    return toInteger(value, target_type, units, metric_prefixes)
+
+def toBinaryInteger(value, target_type='integer', units=None):
+    return toInteger(value, target_type, units, binary_prefixes)
 
 def toBool(value):
-    if not isinstance(value, str):
-        raise TypeError, "wrong type '%s' should be str" % type(value)
+    assertStr(value)
 
     value = value.lower()
-    result = _bool_dict.get(value, None)
-    if result == None:
-        raise ValueError, "cannot convert '%s' to bool" % value
+    if value in ('true', 't', 'yes', 'y', '1'):
+        return True
+    if value in ('false', 'f', 'no', 'n', '0'):
+        return False
     return result
 
 def toFrequency(value):
-    if not isinstance(value, str):
-        raise TypeError, "wrong type '%s' should be str" % type(value)
-
-    if value.endswith('THz'):
-        return float(value[:-3]) * tera
-    elif value.endswith('GHz'):
-        return float(value[:-3]) * giga
-    elif value.endswith('MHz'):
-        return float(value[:-3]) * mega
-    elif value.endswith('kHz'):
-        return float(value[:-3]) * kilo
-    elif value.endswith('Hz'):
-        return float(value[:-2])
-
-    raise ValueError, "cannot convert '%s' to frequency" % value
+    return toMetricFloat(value, 'frequency', 'Hz')
 
 def toLatency(value):
-    if not isinstance(value, str):
-        raise TypeError, "wrong type '%s' should be str" % type(value)
-
-    if value.endswith('ps'):
-        return float(value[:-2]) * pico
-    elif value.endswith('ns'):
-        return float(value[:-2]) * nano
-    elif value.endswith('us'):
-        return float(value[:-2]) * micro
-    elif value.endswith('ms'):
-        return float(value[:-2]) * milli
-    elif value.endswith('s'):
-        return float(value[:-1])
-
-    raise ValueError, "cannot convert '%s' to latency" % value
+    return toMetricFloat(value, 'latency', 's')
 
 def anyToLatency(value):
     """result is a clock period"""
-
-    if not isinstance(value, str):
-        raise TypeError, "wrong type '%s' should be str" % type(value)
-
     try:
-        val = toFrequency(value)
-        if val != 0:
-            val = 1 / val
-        return val
-    except ValueError:
+        return 1 / toFrequency(value)
+    except ValueError, ZeroDivisionError:
         pass
 
     try:
-        val = toLatency(value)
-        return val
+        return toLatency(value)
     except ValueError:
         pass
 
@@ -174,82 +167,26 @@
 
 def anyToFrequency(value):
     """result is a clock period"""
-
-    if not isinstance(value, str):
-        raise TypeError, "wrong type '%s' should be str" % type(value)
-
     try:
-        val = toFrequency(value)
-        return val
+        return toFrequency(value)
     except ValueError:
         pass
 
     try:
-        val = toLatency(value)
-        if val != 0:
-            val = 1 / val
-        return val
-    except ValueError:
+        return 1 / toLatency(value)
+    except ValueError, ZeroDivisionError:
         pass
 
     raise ValueError, "cannot convert '%s' to clock period" % value
 
 def toNetworkBandwidth(value):
-    if not isinstance(value, str):
-        raise TypeError, "wrong type '%s' should be str" % type(value)
-
-    if value.endswith('Tbps'):
-        return float(value[:-4]) * tera
-    elif value.endswith('Gbps'):
-        return float(value[:-4]) * giga
-    elif value.endswith('Mbps'):
-        return float(value[:-4]) * mega
-    elif value.endswith('kbps'):
-        return float(value[:-4]) * kilo
-    elif value.endswith('bps'):
-        return float(value[:-3])
-    else:
-        return float(value)
-
-    raise ValueError, "cannot convert '%s' to network bandwidth" % value
+    return toMetricFloat(value, 'network bandwidth', 'bps')
 
 def toMemoryBandwidth(value):
-    if not isinstance(value, str):
-        raise TypeError, "wrong type '%s' should be str" % type(value)
-
-    if value.endswith('PB/s'):
-        return float(value[:-4]) * pebi
-    elif value.endswith('TB/s'):
-        return float(value[:-4]) * tebi
-    elif value.endswith('GB/s'):
-        return float(value[:-4]) * gibi
-    elif value.endswith('MB/s'):
-        return float(value[:-4]) * mebi
-    elif value.endswith('kB/s'):
-        return float(value[:-4]) * kibi
-    elif value.endswith('B/s'):
-        return float(value[:-3])
-
-    raise ValueError, "cannot convert '%s' to memory bandwidth" % value
+    return toBinaryFloat(value, 'memory bandwidth', 'B/s')
 
 def toMemorySize(value):
-    if not isinstance(value, str):
-        raise TypeError, "wrong type '%s' should be str" % type(value)
-
-    if value.endswith('PB'):
-        return long(value[:-2]) * pebi
-    elif value.endswith('TB'):
-        return long(value[:-2]) * tebi
-    elif value.endswith('GB'):
-        return long(value[:-2]) * gibi
-    elif value.endswith('MB'):
-        return long(value[:-2]) * mebi
-    elif value.endswith('kB'):
-        return long(value[:-2]) * kibi
-    elif value.endswith('B'):
-        return long(value[:-1])
-
-    raise ValueError, "cannot convert '%s' to memory size" % value
+    return toBinaryInteger(value, 'memory size', 'B')
 
 def toIpAddress(value):
     if not isinstance(value, str):
@@ -301,21 +238,7 @@
     return (ip, int(port))
 
 def toVoltage(value):
-    if not isinstance(value, str):
-        raise TypeError, "wrong type '%s' should be str" % type(value)
-
-    if value.endswith('mV'):
-        return float(value[:-2]) * milli
-    elif value.endswith('V'):
-        return float(value[:-1])
-
-    raise ValueError, "cannot convert '%s' to voltage" % value
+    return toMetricFloat(value, 'voltage', 'V')
 
 def toCurrent(value):
-    if not isinstance(value, str):
-        raise TypeError, "wrong type '%s' should be str" % type(value)
-
-    if value.endswith('A'):
-        return toFloat(value[:-1])
-
-    raise ValueError, "cannot convert '%s' to current" % value
+    return toMetricFloat(value, 'current', 'A')