/*******************************************************************************
* This file is part of logisim-evolution.
*
* logisim-evolution is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* logisim-evolution is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with logisim-evolution. If not, see <http://www.gnu.org/licenses/>.
*
* Original code by Carl Burch (http://www.cburch.com), 2011.
* Subsequent modifications by :
* + Haute École Spécialisée Bernoise
* http://www.bfh.ch
* + Haute École du paysage, d'ingénierie et d'architecture de Genève
* http://hepia.hesge.ch/
* + Haute École d'Ingénierie et de Gestion du Canton de Vaud
* http://www.heig-vd.ch/
* The project is currently maintained by :
* + REDS Institute - HEIG-VD
* Yverdon-les-Bains, Switzerland
* http://reds.heig-vd.ch
*******************************************************************************/
package com.cburch.logisim.data;
import java.awt.Color;
import java.util.Arrays;
import com.cburch.logisim.util.Cache;
public class Value {
private static Value create(int width, int error, int unknown, int value) {
if (width == 0) {
return Value.NIL;
} else if (width == 1) {
if ((error & 1) != 0)
return Value.ERROR;
else if ((unknown & 1) != 0)
return Value.UNKNOWN;
else if ((value & 1) != 0)
return Value.TRUE;
else
return Value.FALSE;
} else {
int mask = (width == 32 ? -1 : ~(-1 << width));
error = error & mask;
unknown = unknown & mask & ~error;
value = value & mask & ~unknown & ~error;
int hashCode = 31 * (31 * (31 * width + error) + unknown) + value;
Object cached = cache.get(hashCode);
if (cached != null) {
Value val = (Value) cached;
if (val.value == value && val.width == width
&& val.error == error && val.unknown == unknown)
return val;
}
Value ret = new Value(width, error, unknown, value);
cache.put(hashCode, ret);
return ret;
}
}
public static Value create(Value[] values) {
if (values.length == 0)
return NIL;
if (values.length == 1)
return values[0];
if (values.length > MAX_WIDTH)
throw new RuntimeException("Cannot have more than " + MAX_WIDTH
+ " bits in a value");
int width = values.length;
int value = 0;
int unknown = 0;
int error = 0;
for (int i = 0; i < values.length; i++) {
int mask = 1 << i;
if (values[i] == TRUE)
value |= mask;
else if (values[i] == FALSE) /* do nothing */
;
else if (values[i] == UNKNOWN)
unknown |= mask;
else if (values[i] == ERROR)
error |= mask;
else {
throw new RuntimeException("unrecognized value " + values[i]);
}
}
return Value.create(width, error, unknown, value);
}
public static Value createError(BitWidth bits) {
return Value.create(bits.getWidth(), -1, 0, 0);
}
public static Value createKnown(BitWidth bits, int value) {
return Value.create(bits.getWidth(), 0, 0, value);
}
public static Value createUnknown(BitWidth bits) {
return Value.create(bits.getWidth(), 0, -1, 0);
}
/**
* Code taken from Cornell's version of Logisim:
* http://www.cs.cornell.edu/courses/cs3410/2015sp/
*/
public static Value fromLogString(BitWidth width, String t)
throws Exception {
int radix = radixOfLogString(width, t);
int offset;
if (radix == 16 || radix == 8)
offset = 2;
else if (radix == 10 && t.startsWith("-"))
offset = 1;
else
offset = 0;
int n = t.length();
if (n <= offset)
throw new Exception("expected digits");
int w = width.getWidth();
long value = 0, unknown = 0;
for (int i = offset; i < n; i++) {
char c = t.charAt(i);
int d;
if (c == 'x' && radix != 10)
d = -1;
else if ('0' <= c && c <= '9')
d = c - '0';
else if ('a' <= c && c <= 'f')
d = 0xa + (c - 'a');
else if ('A' <= c && c <= 'F')
d = 0xA + (c - 'A');
else
throw new Exception("unexpected character '"
+ t.substring(i, i + 1) + "' in \"" + t + "\"");
if (d >= radix)
throw new Exception("unexpected character '"
+ t.substring(i, i + 1) + "' in \"" + t + "\"");
value *= radix;
unknown *= radix;
if ((value >> (radix == 10 ? 33 : w)) != 0 || (unknown >> 36) != 0)
throw new Exception("too many bits in \"" + t + "\"");
if (radix != 10) {
if (d == -1)
unknown |= (radix - 1);
else
value |= d;
} else {
if (d == -1)
unknown += (radix - 1);
else
value += d;
}
}
if (radix == 10 && t.charAt(0) == '-')
value = -value;
if (w == 32) {
if (((value & 0x7FFFFFFF) >> (w - 1)) != 0)
throw new Exception("too many bits in \"" + t + "\"");
} else {
if ((value >> w) != 0)
throw new Exception("too many bits in \"" + t + "\"");
}
unknown &= ((1L << w) - 1);
int v = (int) (value & 0x00000000ffffffff);
int u = (int) (unknown & 0x00000000ffffffff);
return create(w, 0, u, v);
}
/**
* Code taken from Cornell's version of Logisim:
* http://www.cs.cornell.edu/courses/cs3410/2015sp/
*/
public static int radixOfLogString(BitWidth width, String t) {
if (t.startsWith("0x"))
return 16;
if (t.startsWith("0o"))
return 8;
if (t.length() == width.getWidth())
return 2;
return 10;
}
public static Value repeat(Value base, int bits) {
if (base.getWidth() != 1) {
throw new IllegalArgumentException(
"first parameter must be one bit");
}
if (bits == 1) {
return base;
} else {
Value[] ret = new Value[bits];
Arrays.fill(ret, base);
return create(ret);
}
}
public static final Value FALSE = new Value(1, 0, 0, 0);
public static final Value TRUE = new Value(1, 0, 0, 1);
public static final Value UNKNOWN = new Value(1, 0, 1, 0);
public static final Value ERROR = new Value(1, 1, 0, 0);
public static final Value NIL = new Value(0, 0, 0, 0);
public static final int MAX_WIDTH = 32;
public static final Color NIL_COLOR = Color.GRAY;
public static final Color FALSE_COLOR = new Color(0, 100, 0);
public static final Color TRUE_COLOR = new Color(0, 210, 0);
public static final Color UNKNOWN_COLOR = new Color(40, 40, 255);
public static final Color ERROR_COLOR = new Color(192, 0, 0);
public static final Color WIDTH_ERROR_COLOR = new Color(255, 123, 0);
public static final Color MULTI_COLOR = Color.BLACK;
private static final Cache cache = new Cache();
private final int width;
private final int error;
private final int unknown;
private final int value;
private Value(int width, int error, int unknown, int value) {
// To ensure that the one-bit values are unique, this should be called
// only
// for the one-bit values and by the private create method
this.width = width;
this.error = error;
this.unknown = unknown;
this.value = value;
}
public Value and(Value other) {
if (other == null)
return this;
if (this.width == 1 && other.width == 1) {
if (this == FALSE || other == FALSE)
return FALSE;
if (this == TRUE && other == TRUE)
return TRUE;
return ERROR;
} else {
int false0 = ~this.value & ~this.error & ~this.unknown;
int false1 = ~other.value & ~other.error & ~other.unknown;
int falses = false0 | false1;
return Value.create(Math.max(this.width, other.width), (this.error
| other.error | this.unknown | other.unknown)
& ~falses, 0, this.value & other.value);
}
}
public Value combine(Value other) {
if (other == null)
return this;
if (this == NIL)
return other;
if (other == NIL)
return this;
if (this.width == 1 && other.width == 1) {
if (this == other)
return this;
if (this == UNKNOWN)
return other;
if (other == UNKNOWN)
return this;
return ERROR;
} else {
int disagree = (this.value ^ other.value)
& ~(this.unknown | other.unknown);
return Value.create(Math.max(this.width, other.width), this.error
| other.error | disagree, this.unknown & other.unknown,
(this.value & ~this.unknown)
| (other.value & ~other.unknown));
}
}
/**
* Code taken from Cornell's version of Logisim:
* http://www.cs.cornell.edu/courses/cs3410/2015sp/
*/
public boolean compatible(Value other) {
// where this has a value, other must have same value
// where this has unknown, other can have unknown or any value
// where this has error, other must have error
return (this.width == other.width && this.error == other.error
&& this.value == (other.value & ~this.unknown) && this.unknown == (other.unknown | this.unknown));
}
@Override
public boolean equals(Object other_obj) {
if (!(other_obj instanceof Value))
return false;
Value other = (Value) other_obj;
boolean ret = this.width == other.width && this.error == other.error
&& this.unknown == other.unknown && this.value == other.value;
return ret;
}
public Value extendWidth(int newWidth, Value others) {
if (width == newWidth)
return this;
int maskInverse = (width == 32 ? 0 : (-1 << width));
if (others == Value.ERROR) {
return Value.create(newWidth, error | maskInverse, unknown, value);
} else if (others == Value.FALSE) {
return Value.create(newWidth, error, unknown, value);
} else if (others == Value.TRUE) {
return Value.create(newWidth, error, unknown, value | maskInverse);
} else {
return Value.create(newWidth, error, unknown | maskInverse, value);
}
}
public Value get(int which) {
if (which < 0 || which >= width)
return ERROR;
int mask = 1 << which;
if ((error & mask) != 0)
return ERROR;
else if ((unknown & mask) != 0)
return UNKNOWN;
else if ((value & mask) != 0)
return TRUE;
else
return FALSE;
}
public Value[] getAll() {
Value[] ret = new Value[width];
for (int i = 0; i < ret.length; i++) {
ret[i] = get(i);
}
return ret;
}
public BitWidth getBitWidth() {
return BitWidth.create(width);
}
public Color getColor() {
if (error != 0) {
return ERROR_COLOR;
} else if (width == 0) {
return NIL_COLOR;
} else if (width == 1) {
if (this == UNKNOWN)
return UNKNOWN_COLOR;
else if (this == TRUE)
return TRUE_COLOR;
else
return FALSE_COLOR;
} else {
return MULTI_COLOR;
}
}
public int getWidth() {
return width;
}
@Override
public int hashCode() {
int ret = width;
ret = 31 * ret + error;
ret = 31 * ret + unknown;
ret = 31 * ret + value;
return ret;
}
public boolean isErrorValue() {
return error != 0;
}
public boolean isFullyDefined() {
return width > 0 && error == 0 && unknown == 0;
}
public boolean isUnknown() {
if (width == 32) {
return error == 0 && unknown == -1;
} else {
return error == 0 && unknown == ((1 << width) - 1);
}
}
public Value not() {
if (width <= 1) {
if (this == TRUE)
return FALSE;
if (this == FALSE)
return TRUE;
return ERROR;
} else {
return Value.create(this.width, this.error | this.unknown, 0,
~this.value);
}
}
public Value or(Value other) {
if (other == null)
return this;
if (this.width == 1 && other.width == 1) {
if (this == TRUE || other == TRUE)
return TRUE;
if (this == FALSE && other == FALSE)
return FALSE;
return ERROR;
} else {
int true0 = this.value & ~this.error & ~this.unknown;
int true1 = other.value & ~other.error & ~other.unknown;
int trues = true0 | true1;
return Value.create(Math.max(this.width, other.width), (this.error
| other.error | this.unknown | other.unknown)
& ~trues, 0, this.value | other.value);
}
}
public Value set(int which, Value val) {
if (val.width != 1) {
throw new RuntimeException("Cannot set multiple values");
} else if (which < 0 || which >= width) {
throw new RuntimeException("Attempt to set outside value's width");
} else if (width == 1) {
return val;
} else {
int mask = ~(1 << which);
return Value.create(this.width, (this.error & mask)
| (val.error << which), (this.unknown & mask)
| (val.unknown << which), (this.value & mask)
| (val.value << which));
}
}
public String toBinaryString() {
switch (width) {
case 0:
return "-";
case 1:
if (error != 0)
return "E";
else if (unknown != 0)
return "x";
else if (value != 0)
return "1";
else
return "0";
default:
StringBuilder ret = new StringBuilder();
for (int i = width - 1; i >= 0; i--) {
ret.append(get(i).toString());
}
return ret.toString();
}
}
public String toDecimalString(boolean signed) {
if (width == 0)
return "-";
if (isErrorValue())
return Strings.get("valueError");
if (!isFullyDefined())
return Strings.get("valueUnknown");
int value = toIntValue();
if (signed) {
if (width < 32 && (value >> (width - 1)) != 0) {
value |= (-1) << width;
}
return "" + value;
} else {
return "" + ((long) value & 0xFFFFFFFFL);
}
}
public String toDisplayString() {
switch (width) {
case 0:
return "-";
case 1:
if (error != 0)
return Strings.get("valueErrorSymbol");
else if (unknown != 0)
return Strings.get("valueUnknownSymbol");
else if (value != 0)
return "1";
else
return "0";
default:
StringBuilder ret = new StringBuilder();
for (int i = width - 1; i >= 0; i--) {
ret.append(get(i).toString());
if (i % 4 == 0 && i != 0)
ret.append(" ");
}
return ret.toString();
}
}
public String toDisplayString(int radix) {
switch (radix) {
case 2:
return toDisplayString();
case 8:
return toOctalString();
case 16:
return toHexString();
default:
if (width == 0)
return "-";
if (isErrorValue())
return Strings.get("valueError");
if (!isFullyDefined())
return Strings.get("valueUnknown");
return Integer.toString(toIntValue(), radix);
}
}
public String toHexString() {
if (width <= 1) {
return toString();
} else {
Value[] vals = getAll();
char[] c = new char[(vals.length + 3) / 4];
for (int i = 0; i < c.length; i++) {
int k = c.length - 1 - i;
int frst = 4 * k;
int last = Math.min(vals.length, 4 * (k + 1));
int v = 0;
c[i] = '?';
for (int j = last - 1; j >= frst; j--) {
if (vals[j] == Value.ERROR) {
c[i] = 'E';
break;
}
if (vals[j] == Value.UNKNOWN) {
c[i] = 'x';
break;
}
v = 2 * v;
if (vals[j] == Value.TRUE)
v++;
}
if (c[i] == '?')
c[i] = Character.forDigit(v, 16);
}
return new String(c);
}
}
public int toIntValue() {
if (error != 0)
return -1;
if (unknown != 0)
return -1;
return value;
}
public String toOctalString() {
if (width <= 1) {
return toString();
} else {
Value[] vals = getAll();
char[] c = new char[(vals.length + 2) / 3];
for (int i = 0; i < c.length; i++) {
int k = c.length - 1 - i;
int frst = 3 * k;
int last = Math.min(vals.length, 3 * (k + 1));
int v = 0;
c[i] = '?';
for (int j = last - 1; j >= frst; j--) {
if (vals[j] == Value.ERROR) {
c[i] = 'E';
break;
}
if (vals[j] == Value.UNKNOWN) {
c[i] = 'x';
break;
}
v = 2 * v;
if (vals[j] == Value.TRUE)
v++;
}
if (c[i] == '?')
c[i] = Character.forDigit(v, 8);
}
return new String(c);
}
}
@Override
public String toString() {
switch (width) {
case 0:
return "-";
case 1:
if (error != 0)
return "E";
else if (unknown != 0)
return "x";
else if (value != 0)
return "1";
else
return "0";
default:
StringBuilder ret = new StringBuilder();
for (int i = width - 1; i >= 0; i--) {
ret.append(get(i).toString());
if (i % 4 == 0 && i != 0)
ret.append(" ");
}
return ret.toString();
}
}
public Value xor(Value other) {
if (other == null)
return this;
if (this.width <= 1 && other.width <= 1) {
if (this == ERROR || other == ERROR)
return ERROR;
if (this == UNKNOWN || other == UNKNOWN)
return ERROR;
if (this == NIL || other == NIL)
return ERROR;
if ((this == TRUE) == (other == TRUE))
return FALSE;
return TRUE;
} else {
return Value.create(Math.max(this.width, other.width), this.error
| other.error | this.unknown | other.unknown, 0, this.value
^ other.value);
}
}
}