Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RadixString code #529

Merged
merged 2 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
236 changes: 236 additions & 0 deletions lib/src/values/logic_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,242 @@ 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>.
/// Here is the number 1492 printed as a radix string:
/// - Binary: 15'b101 1101 0100
desmonddak marked this conversation as resolved.
Show resolved Hide resolved
/// - Quaternary: 15'q11 3110
/// - Octal: 15'o2724
/// - Decimal: 10'd1492
/// - Hex: 15'h05d4
///
/// Spaces are output according to [chunkSize]
/// - [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
///
/// 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')
desmonddak marked this conversation as resolved.
Show resolved Hide resolved
};
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 = 0; i < extendedStr.width ~/ span; i++) {
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 len +radix-encoded string.
///
/// If the LogicValue width is not encoded as an even 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
desmonddak marked this conversation as resolved.
Show resolved Hide resolved
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(' ', '');
// print('compressedStr=$compressedStr');
// 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');
// print('nobinaryStr=$noBinariesStr');
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));
}
}
}
}
}
});
});
}
Loading