diff --git a/doc/architecture.md b/doc/architecture.md index d1558ccd6..cc1e775ae 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -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 @@ -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`. diff --git a/doc/user_guide/_docs/A02-logical_signals.md b/doc/user_guide/_docs/A02-logical_signals.md index 77d1fc5a9..10f59d348 100644 --- a/doc/user_guide/_docs/A02-logical_signals.md +++ b/doc/user_guide/_docs/A02-logical_signals.md @@ -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 diff --git a/lib/src/values/logic_value.dart b/lib/src/values/logic_value.dart index d0d5bbc3a..cb7d2061e 100644 --- a/lib/src/values/logic_value.dart +++ b/lib/src/values/logic_value.dart @@ -604,6 +604,264 @@ abstract class LogicValue implements Comparable { } } + /// 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: `'`. + /// + /// [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<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: + /// + /// ``. + /// + /// `` is the binary digit length of the [LogicValue] to be + /// constructed. + /// + /// `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) + /// + /// `` 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((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.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 diff --git a/test/logic_value_test.dart b/test/logic_value_test.dart index a51638420..ba3fc796b 100644 --- a/test/logic_value_test.dart +++ b/test/logic_value_test.dart @@ -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'qZZ23")); + expect(lv.toRadixString(radix: 8), equals("10'oZZ3")); + expect(lv.toRadixString(radix: 16), equals("10'hZb")); + 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)); + } + } + } + } + } + }); + }); }