Skip to content

Commit

Permalink
RadixString code (#529)
Browse files Browse the repository at this point in the history
  • Loading branch information
desmonddak authored Nov 20, 2024
1 parent 8782f43 commit e40ca33
Show file tree
Hide file tree
Showing 4 changed files with 342 additions and 7 deletions.
14 changes: 7 additions & 7 deletions doc/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ A `LogicValue` represents a multi-bit (including 0-bit and 1-bit) 4-value (`1`,

The `Module` is the fundamental building block of hardware designs in ROHD. They have clearly defined inputs and outputs, and all logic contained within the module should connect either/both from inputs and to outputs. The ROHD framework will determine at `build()` time which logic sits within which `Module`. Any functional operation, whether a simple gate or a large module, is implemented as a `Module`.

Every `Module` defines its own functionality. This could be through composition of other `Module`s, or through custom functional definition. For a custom functionality to be convertable to an output (e.g. SystemVerilog), it has to explicitly define how to convert it (via `SystemVerilog` or `InlineSystemVerilog`). Any time the input of a custom functionality `Module` toggles, the outputs should correspondingly change, if necessary.
Every `Module` defines its own functionality. This could be through composition of other `Module`s, or through custom functional definition. For a custom functionality to be convertible to an output (e.g. SystemVerilog), it has to explicitly define how to convert it (via `SystemVerilog` or `InlineSystemVerilog`). Any time the input of a custom functionality `Module` toggles, the outputs should correspondingly change, if necessary.

### Simulator

Expand All @@ -30,26 +30,26 @@ A separate type of object responsible for taking a `Module` and converting it to

All the code for the ROHD framework library is in `lib/src/`, with `lib/rohd.dart` exporting the main stuff for usage.

### collections
### Collections

Software collections that are useful for high-performance internal implementation details in ROHD.

### exceptions
### Exceptions

Exceptions that the ROHD framework may throw.

### modules
### Modules

Contains a collection of `Module` implementations that can be used as primitive building blocks for ROHD designs.

### synthesizers
### Synthesizers

Contains logic for synthesizing `Module`s into some output. It is structured to maximize reusability across different output types (including those not yet supported).

### utilities
### Utilities

Various generic objects and classes that may be useful in different areas of ROHD.

### values
### Values

Definitions for things related to `LogicValue`.
1 change: 1 addition & 0 deletions doc/user_guide/_docs/A02-logical_signals.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ x.value.toInt()
x.value.toBigInt()
// constructing a LogicValue a handful of different ways
LogicValue.ofRadixString("31'h5761 F87A"); // 0x5761F87A
LogicValue.ofString('0101xz01'); // 0b0101xz01
LogicValue.of([LogicValue.one, LogicValue.zero]); // 0b10
[LogicValue.z, LogicValue.x].swizzle(); // 0bzx
Expand Down
258 changes: 258 additions & 0 deletions lib/src/values/logic_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,264 @@ abstract class LogicValue implements Comparable<LogicValue> {
}
}

/// Reverse a string (helper function)
static String _reverse(String inString) =>
String.fromCharCodes(inString.runes.toList().reversed);

/// Return the radix encoding of the current [LogicValue] as a sequence
/// of radix characters prefixed by the length and encoding format.
/// Output format is: `<len>'<format><encoded-value>`.
///
/// [ofRadixString] can parse a [String] produced by [toRadixString] and
/// construct a [LogicValue].
///
/// Here is the number 1492 printed as a radix string:
/// - Binary: `15'b101 1101 0100`
/// - Quaternary: `15'q11 3110`
/// - Octal: `15'o2724`
/// - Decimal: `10'd1492`
/// - Hex: `15'h05d4`
///
/// Spaces are output according to [chunkSize] starting from the LSB(right).
/// - [chunkSize] = default: `61'h2 9ebc 5f06 5bf7`
/// - [chunkSize] = 10: `61'h29e bc5f065bf7`
///
/// Leading 0s are omitted in the output string:
/// - `25'h1`
///
/// When a [LogicValue] has 'x' or 'z' bits, then the radix characters those
/// bits overlap will be expanded into binary form with '<' '>' bracketing
/// them as follows:
/// - `35'h7 ZZZZ Z<zzz0><100z>Z`
/// Such a [LogicValue] cannot be converted to a Decimal (10) radix string
/// and will throw an exception.
///
/// If the leading bits are 'z', then the output radix character is 'Z' no
/// matter what the length. When leading, 'Z' indicates one or more 'z'
/// bits to fill the first radix character.
/// - `9'bz zzzz zzz = 9'hZZZ`
///
String toRadixString({int radix = 2, int chunkSize = 4}) {
final radixStr = switch (radix) {
2 => "'b",
4 => "'q",
8 => "'o",
10 => "'d",
16 => "'h",
_ => throw Exception('Unsupported radix: $radix')
};
final String reversedStr;
if (isValid) {
final radixString =
toBigInt().toUnsigned(width).toRadixString(radix).toUpperCase();
reversedStr = _reverse(radixString);
} else {
if (radix == 10) {
throw Exception('Cannot support decimal strings with invalid bits');
}
final span = (math.log(radix) / math.log(2)).ceil();
final extendedStr =
LogicValue.of(this, width: span * (width / span).ceil());
final buf = StringBuffer();
for (var i = (extendedStr.width ~/ span) - 1; i >= 0; i--) {
final binaryChunk = extendedStr.slice((i + 1) * span - 1, i * span);
var chunkString = binaryChunk.toString(includeWidth: false);
if (i == extendedStr.width ~/ span - 1) {
final chunkWidth = chunkString.length;
chunkString = chunkString.substring(
chunkWidth - (width - i * span), chunkWidth);
}
final s = [
if (chunkString == 'z' * chunkString.length)
(span == 1 ? 'z' : 'Z')
else if (chunkString == 'x' * chunkString.length)
(span == 1 ? 'x' : 'X')
else if (chunkString.contains('z') | chunkString.contains('x'))
'>${_reverse(chunkString)}<'
else
binaryChunk.toBigInt().toUnsigned(span).toRadixString(radix)
].first;
buf.write(_reverse(s));
}
reversedStr = _reverse(buf.toString());
}
final spaceString = _reverse(reversedStr
.replaceAllMapped(
RegExp('((>(.){$chunkSize}<)|([a-zA-Z0-9])){$chunkSize}'),
(match) => '${match.group(0)} ')
.replaceAll(' <', '<'));

final fullString = spaceString[0] == ' '
? spaceString.substring(1, spaceString.length)
: spaceString;
return '$width$radixStr$fullString';
}

/// Create a [LogicValue] from a length/radix-encoded string of the
/// following format:
///
/// `<length><format><value-string>`.
///
/// `<length>` is the binary digit length of the [LogicValue] to be
/// constructed.
///
/// `<format>s` supported are `'b,'q,'o,'d,'h` supporting radixes as follows:
/// - 'b: binary (radix 2)
/// - 'q: quaternary (radix 4)
/// - 'o: octal (radix 8)
/// - 'd: decimal (radix 10)
/// - 'h: hexadecimal (radix 16)
///
/// `<value-string>` contains space-separated digits corresponding to the
/// radix format. Space-separation is for ease of reading and is often
/// in chunks of 4 digits.
///
/// Strings created by [toRadixString] are parsed by [ofRadixString].
///
/// If the LogicValue width is not encoded as round number of radix
/// characters, the leading character must be small enough to be encoded
/// in the remaining width:
/// - 9'h1AA
/// - 10'h2AA
/// - 11'h4AA
/// - 12'hAAA
static LogicValue ofRadixString(String valueString) {
if (RegExp(r'^\d+').firstMatch(valueString) != null) {
final formatStr = RegExp(r"^(\d+)'([bqodh])([0-9aAbBcCdDeEfFzZxX<> ]*)")
.firstMatch(valueString);
if (formatStr != null) {
final specifiedLength = int.parse(formatStr.group(1)!);
final compressedStr = formatStr.group(3)!.replaceAll(' ', '');
// Extract radix
final radixString = formatStr.group(2)!;
final radix = switch (radixString) {
'b' => 2,
'q' => 4,
'o' => 8,
'd' => 10,
'h' => 16,
_ => throw Exception('Unsupported radix: $radixString'),
};
final span = (math.log(radix) / math.log(2)).ceil();

final reversedStr = _reverse(compressedStr);
// Find any binary expansions, then extend to the span
final binaries = RegExp('>[^<>]*<').allMatches(reversedStr).indexed;

// At this point, binaryLength has the binary bit count for binaries
// Remove and store expansions of binary fields '<[x0z1]*>.
final fullBinaries = RegExp('>[^<>]*<');
final bitExpandLocs = fullBinaries.allMatches(reversedStr).indexed;

final numExpanded = bitExpandLocs.length;
final numChars = reversedStr.length - numExpanded * (span + 1);
final binaryLength = (binaries.isEmpty
? 0
: binaries
.map<int>((j) => j.$2.group(0)!.length - 2)
.reduce((a, b) => a + b)) +
(numChars - numExpanded) * span;

// is the binary length shorter than it appears
final int shorter;
if ((binaries.isNotEmpty) && compressedStr[0] == '<') {
final binGroup = _reverse(binaries.last.$2.group(0)!);
final binaryChunk = binGroup.substring(1, binGroup.length - 1);
var cnt = 0;
while (cnt < binaryChunk.length - 1 && binaryChunk[cnt++] == '0') {}
shorter = cnt - 1;
} else {
final leadChar = compressedStr[0];
if (RegExp('[xXzZ]').hasMatch(leadChar)) {
shorter = span - 1;
} else {
if (radix == 10) {
shorter = binaryLength -
BigInt.parse(compressedStr, radix: 10)
.toRadixString(2)
.length;
} else {
shorter = span -
BigInt.parse(leadChar, radix: radix).toRadixString(2).length;
}
}
}
if (binaryLength - shorter > specifiedLength) {
throw Exception('ofRadixString: cannot represent '
'$compressedStr in $specifiedLength');
}
final noBinariesStr = reversedStr.replaceAll(fullBinaries, '0');
final xLocations = RegExp('x|X')
.allMatches(noBinariesStr)
.indexed
.map((m) => List.generate(span, (s) => m.$2.start * span + s))
.expand((xe) => xe);
final zLocations = RegExp('z|Z')
.allMatches(noBinariesStr)
.indexed
.map((m) => List.generate(span, (s) => m.$2.start * span + s))
.expand((ze) => ze);

final intValue = BigInt.parse(
_reverse(noBinariesStr.replaceAll(RegExp('[xXzZ]'), '0')),
radix: radix)
.toUnsigned(specifiedLength);
final logicValList = List<LogicValue>.from(
LogicValue.ofString(intValue.toRadixString(2))
.zeroExtend(specifiedLength)
.toList());
// Put all the X and Z's back into the list
for (final x in xLocations) {
if (x < specifiedLength) {
logicValList[x] = LogicValue.x;
}
}
for (final z in zLocations) {
if (z < specifiedLength) {
logicValList[z] = LogicValue.z;
}
}

// Now add back the bitfield expansions stored earlier
var lastPos = 0;
var lastCpos = 0;
for (final i in bitExpandLocs) {
var len = i.$2.group(0)!.length;
if (i.$1 == bitExpandLocs.last.$1) {
final revBitChars = i.$2.group(0)!;
while (len > 1 && revBitChars[len - 2] == '0') {
len--;
}
}
final bitChars = i.$2.group(0)!.substring(1, len - 1);
var pos = 0;
if (i.$1 > 0) {
final nonExpChars = i.$2.start - lastCpos - span - 2;
pos = lastPos + span + span * nonExpChars;
} else {
final nonExpChars = i.$2.start - lastCpos;
pos = lastPos + span * nonExpChars;
}

for (var bitPos = 0; bitPos < len - 2; bitPos++) {
logicValList[pos + bitPos] = switch (bitChars[bitPos]) {
'0' => LogicValue.zero,
'1' => LogicValue.one,
'x' => LogicValue.x,
_ => LogicValue.z
};
}
lastCpos = i.$2.start;
lastPos = pos;
}
return logicValList.rswizzle();
} else {
throw Exception('Invalid LogicValue string $valueString');
}
}
return LogicValue.zero;
}

/// Compares this to `other`.
///
/// Returns a negative number if `this` is less than `other`, zero if they are
Expand Down
76 changes: 76 additions & 0 deletions test/logic_value_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2048,4 +2048,80 @@ void main() {
].swizzle());
}
});

group('RadixString', () {
test('radixString roundTrip', () {
final lv = LogicValue.ofBigInt(BigInt.from(737481838713847), 61);
for (final i in [2, 4, 8, 10, 16]) {
expect(
LogicValue.ofRadixString(lv.toRadixString(radix: i)), equals(lv));
}
});
test('radixString binary expansion', () {
final lv = LogicValue.ofRadixString("12'b10z111011z00");
expect(lv.toRadixString(radix: 16), equals("12'h<10z1>d<1z00>"));
for (final i in [2, 4, 8, 16]) {
expect(
LogicValue.ofRadixString(lv.toRadixString(radix: i)), equals(lv));
}
});

test('radixString leading zero', () {
final lv = LogicValue.ofRadixString("10'b00 0010 0111");
expect(lv.toRadixString(), equals("10'b10 0111"));
expect(lv.toRadixString(radix: 4), equals("10'q213"));
expect(lv.toRadixString(radix: 8), equals("10'o47"));
expect(lv.toRadixString(radix: 10), equals("10'd39"));
expect(lv.toRadixString(radix: 16), equals("10'h27"));
for (final i in [2, 4, 8, 10, 16]) {
expect(
LogicValue.ofRadixString(lv.toRadixString(radix: i)), equals(lv));
}
});

test('radixString leading Z', () {
final lv = LogicValue.ofRadixString("10'bzz zzz1 1011");
expect(lv.toRadixString(), equals("10'bzz zzz1 1011"));
expect(lv.toRadixString(radix: 4), equals("10'qZZ<z1>23"));
expect(lv.toRadixString(radix: 8), equals("10'oZZ<z11>3"));
expect(lv.toRadixString(radix: 16), equals("10'hZ<zzz1>b"));
for (final i in [2, 4, 8, 16]) {
expect(
LogicValue.ofRadixString(lv.toRadixString(radix: i)), equals(lv));
}
});
test('radixString small leading radix character', () {
final lv = LogicValue.ofRadixString("10'b10 1010 0111");
expect(lv.toRadixString(radix: 4), equals("10'q2 2213"));
expect(lv.toRadixString(radix: 8), equals("10'o1247"));
expect(lv.toRadixString(radix: 10), equals("10'd679"));
expect(lv.toRadixString(radix: 16), equals("10'h2A7"));
for (final i in [2, 4, 8, 10, 16]) {
expect(
LogicValue.ofRadixString(lv.toRadixString(radix: i)), equals(lv));
}
});
test('radixString: slide set bits along entire word', () {
final random = Random(5);

for (var width = 15; width < 23; width++) {
final inL = Logic(width: width);
for (var setWidth = 1; setWidth < 12; setWidth++) {
for (var iterations = 0; iterations < 10; iterations++) {
final ii = random.nextInt((1 << (setWidth + 1)) - 1);

for (var pos = 0; pos < inL.width - setWidth; pos++) {
final l = Logic(width: width);
l <= inL.withSet(pos, Const(ii, width: setWidth));
final lv = l.value;
for (final i in [2, 4, 8, 16]) {
expect(LogicValue.ofRadixString(lv.toRadixString(radix: i)),
equals(lv));
}
}
}
}
}
});
});
}

0 comments on commit e40ca33

Please sign in to comment.