Skip to content

Commit e40ca33

Browse files
authored
RadixString code (#529)
1 parent 8782f43 commit e40ca33

File tree

4 files changed

+342
-7
lines changed

4 files changed

+342
-7
lines changed

doc/architecture.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ A `LogicValue` represents a multi-bit (including 0-bit and 1-bit) 4-value (`1`,
1616

1717
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`.
1818

19-
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.
19+
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.
2020

2121
### Simulator
2222

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

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

33-
### collections
33+
### Collections
3434

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

37-
### exceptions
37+
### Exceptions
3838

3939
Exceptions that the ROHD framework may throw.
4040

41-
### modules
41+
### Modules
4242

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

45-
### synthesizers
45+
### Synthesizers
4646

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

49-
### utilities
49+
### Utilities
5050

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

53-
### values
53+
### Values
5454

5555
Definitions for things related to `LogicValue`.

doc/user_guide/_docs/A02-logical_signals.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ x.value.toInt()
3737
x.value.toBigInt()
3838
3939
// constructing a LogicValue a handful of different ways
40+
LogicValue.ofRadixString("31'h5761 F87A"); // 0x5761F87A
4041
LogicValue.ofString('0101xz01'); // 0b0101xz01
4142
LogicValue.of([LogicValue.one, LogicValue.zero]); // 0b10
4243
[LogicValue.z, LogicValue.x].swizzle(); // 0bzx

lib/src/values/logic_value.dart

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,264 @@ abstract class LogicValue implements Comparable<LogicValue> {
604604
}
605605
}
606606

607+
/// Reverse a string (helper function)
608+
static String _reverse(String inString) =>
609+
String.fromCharCodes(inString.runes.toList().reversed);
610+
611+
/// Return the radix encoding of the current [LogicValue] as a sequence
612+
/// of radix characters prefixed by the length and encoding format.
613+
/// Output format is: `<len>'<format><encoded-value>`.
614+
///
615+
/// [ofRadixString] can parse a [String] produced by [toRadixString] and
616+
/// construct a [LogicValue].
617+
///
618+
/// Here is the number 1492 printed as a radix string:
619+
/// - Binary: `15'b101 1101 0100`
620+
/// - Quaternary: `15'q11 3110`
621+
/// - Octal: `15'o2724`
622+
/// - Decimal: `10'd1492`
623+
/// - Hex: `15'h05d4`
624+
///
625+
/// Spaces are output according to [chunkSize] starting from the LSB(right).
626+
/// - [chunkSize] = default: `61'h2 9ebc 5f06 5bf7`
627+
/// - [chunkSize] = 10: `61'h29e bc5f065bf7`
628+
///
629+
/// Leading 0s are omitted in the output string:
630+
/// - `25'h1`
631+
///
632+
/// When a [LogicValue] has 'x' or 'z' bits, then the radix characters those
633+
/// bits overlap will be expanded into binary form with '<' '>' bracketing
634+
/// them as follows:
635+
/// - `35'h7 ZZZZ Z<zzz0><100z>Z`
636+
/// Such a [LogicValue] cannot be converted to a Decimal (10) radix string
637+
/// and will throw an exception.
638+
///
639+
/// If the leading bits are 'z', then the output radix character is 'Z' no
640+
/// matter what the length. When leading, 'Z' indicates one or more 'z'
641+
/// bits to fill the first radix character.
642+
/// - `9'bz zzzz zzz = 9'hZZZ`
643+
///
644+
String toRadixString({int radix = 2, int chunkSize = 4}) {
645+
final radixStr = switch (radix) {
646+
2 => "'b",
647+
4 => "'q",
648+
8 => "'o",
649+
10 => "'d",
650+
16 => "'h",
651+
_ => throw Exception('Unsupported radix: $radix')
652+
};
653+
final String reversedStr;
654+
if (isValid) {
655+
final radixString =
656+
toBigInt().toUnsigned(width).toRadixString(radix).toUpperCase();
657+
reversedStr = _reverse(radixString);
658+
} else {
659+
if (radix == 10) {
660+
throw Exception('Cannot support decimal strings with invalid bits');
661+
}
662+
final span = (math.log(radix) / math.log(2)).ceil();
663+
final extendedStr =
664+
LogicValue.of(this, width: span * (width / span).ceil());
665+
final buf = StringBuffer();
666+
for (var i = (extendedStr.width ~/ span) - 1; i >= 0; i--) {
667+
final binaryChunk = extendedStr.slice((i + 1) * span - 1, i * span);
668+
var chunkString = binaryChunk.toString(includeWidth: false);
669+
if (i == extendedStr.width ~/ span - 1) {
670+
final chunkWidth = chunkString.length;
671+
chunkString = chunkString.substring(
672+
chunkWidth - (width - i * span), chunkWidth);
673+
}
674+
final s = [
675+
if (chunkString == 'z' * chunkString.length)
676+
(span == 1 ? 'z' : 'Z')
677+
else if (chunkString == 'x' * chunkString.length)
678+
(span == 1 ? 'x' : 'X')
679+
else if (chunkString.contains('z') | chunkString.contains('x'))
680+
'>${_reverse(chunkString)}<'
681+
else
682+
binaryChunk.toBigInt().toUnsigned(span).toRadixString(radix)
683+
].first;
684+
buf.write(_reverse(s));
685+
}
686+
reversedStr = _reverse(buf.toString());
687+
}
688+
final spaceString = _reverse(reversedStr
689+
.replaceAllMapped(
690+
RegExp('((>(.){$chunkSize}<)|([a-zA-Z0-9])){$chunkSize}'),
691+
(match) => '${match.group(0)} ')
692+
.replaceAll(' <', '<'));
693+
694+
final fullString = spaceString[0] == ' '
695+
? spaceString.substring(1, spaceString.length)
696+
: spaceString;
697+
return '$width$radixStr$fullString';
698+
}
699+
700+
/// Create a [LogicValue] from a length/radix-encoded string of the
701+
/// following format:
702+
///
703+
/// `<length><format><value-string>`.
704+
///
705+
/// `<length>` is the binary digit length of the [LogicValue] to be
706+
/// constructed.
707+
///
708+
/// `<format>s` supported are `'b,'q,'o,'d,'h` supporting radixes as follows:
709+
/// - 'b: binary (radix 2)
710+
/// - 'q: quaternary (radix 4)
711+
/// - 'o: octal (radix 8)
712+
/// - 'd: decimal (radix 10)
713+
/// - 'h: hexadecimal (radix 16)
714+
///
715+
/// `<value-string>` contains space-separated digits corresponding to the
716+
/// radix format. Space-separation is for ease of reading and is often
717+
/// in chunks of 4 digits.
718+
///
719+
/// Strings created by [toRadixString] are parsed by [ofRadixString].
720+
///
721+
/// If the LogicValue width is not encoded as round number of radix
722+
/// characters, the leading character must be small enough to be encoded
723+
/// in the remaining width:
724+
/// - 9'h1AA
725+
/// - 10'h2AA
726+
/// - 11'h4AA
727+
/// - 12'hAAA
728+
static LogicValue ofRadixString(String valueString) {
729+
if (RegExp(r'^\d+').firstMatch(valueString) != null) {
730+
final formatStr = RegExp(r"^(\d+)'([bqodh])([0-9aAbBcCdDeEfFzZxX<> ]*)")
731+
.firstMatch(valueString);
732+
if (formatStr != null) {
733+
final specifiedLength = int.parse(formatStr.group(1)!);
734+
final compressedStr = formatStr.group(3)!.replaceAll(' ', '');
735+
// Extract radix
736+
final radixString = formatStr.group(2)!;
737+
final radix = switch (radixString) {
738+
'b' => 2,
739+
'q' => 4,
740+
'o' => 8,
741+
'd' => 10,
742+
'h' => 16,
743+
_ => throw Exception('Unsupported radix: $radixString'),
744+
};
745+
final span = (math.log(radix) / math.log(2)).ceil();
746+
747+
final reversedStr = _reverse(compressedStr);
748+
// Find any binary expansions, then extend to the span
749+
final binaries = RegExp('>[^<>]*<').allMatches(reversedStr).indexed;
750+
751+
// At this point, binaryLength has the binary bit count for binaries
752+
// Remove and store expansions of binary fields '<[x0z1]*>.
753+
final fullBinaries = RegExp('>[^<>]*<');
754+
final bitExpandLocs = fullBinaries.allMatches(reversedStr).indexed;
755+
756+
final numExpanded = bitExpandLocs.length;
757+
final numChars = reversedStr.length - numExpanded * (span + 1);
758+
final binaryLength = (binaries.isEmpty
759+
? 0
760+
: binaries
761+
.map<int>((j) => j.$2.group(0)!.length - 2)
762+
.reduce((a, b) => a + b)) +
763+
(numChars - numExpanded) * span;
764+
765+
// is the binary length shorter than it appears
766+
final int shorter;
767+
if ((binaries.isNotEmpty) && compressedStr[0] == '<') {
768+
final binGroup = _reverse(binaries.last.$2.group(0)!);
769+
final binaryChunk = binGroup.substring(1, binGroup.length - 1);
770+
var cnt = 0;
771+
while (cnt < binaryChunk.length - 1 && binaryChunk[cnt++] == '0') {}
772+
shorter = cnt - 1;
773+
} else {
774+
final leadChar = compressedStr[0];
775+
if (RegExp('[xXzZ]').hasMatch(leadChar)) {
776+
shorter = span - 1;
777+
} else {
778+
if (radix == 10) {
779+
shorter = binaryLength -
780+
BigInt.parse(compressedStr, radix: 10)
781+
.toRadixString(2)
782+
.length;
783+
} else {
784+
shorter = span -
785+
BigInt.parse(leadChar, radix: radix).toRadixString(2).length;
786+
}
787+
}
788+
}
789+
if (binaryLength - shorter > specifiedLength) {
790+
throw Exception('ofRadixString: cannot represent '
791+
'$compressedStr in $specifiedLength');
792+
}
793+
final noBinariesStr = reversedStr.replaceAll(fullBinaries, '0');
794+
final xLocations = RegExp('x|X')
795+
.allMatches(noBinariesStr)
796+
.indexed
797+
.map((m) => List.generate(span, (s) => m.$2.start * span + s))
798+
.expand((xe) => xe);
799+
final zLocations = RegExp('z|Z')
800+
.allMatches(noBinariesStr)
801+
.indexed
802+
.map((m) => List.generate(span, (s) => m.$2.start * span + s))
803+
.expand((ze) => ze);
804+
805+
final intValue = BigInt.parse(
806+
_reverse(noBinariesStr.replaceAll(RegExp('[xXzZ]'), '0')),
807+
radix: radix)
808+
.toUnsigned(specifiedLength);
809+
final logicValList = List<LogicValue>.from(
810+
LogicValue.ofString(intValue.toRadixString(2))
811+
.zeroExtend(specifiedLength)
812+
.toList());
813+
// Put all the X and Z's back into the list
814+
for (final x in xLocations) {
815+
if (x < specifiedLength) {
816+
logicValList[x] = LogicValue.x;
817+
}
818+
}
819+
for (final z in zLocations) {
820+
if (z < specifiedLength) {
821+
logicValList[z] = LogicValue.z;
822+
}
823+
}
824+
825+
// Now add back the bitfield expansions stored earlier
826+
var lastPos = 0;
827+
var lastCpos = 0;
828+
for (final i in bitExpandLocs) {
829+
var len = i.$2.group(0)!.length;
830+
if (i.$1 == bitExpandLocs.last.$1) {
831+
final revBitChars = i.$2.group(0)!;
832+
while (len > 1 && revBitChars[len - 2] == '0') {
833+
len--;
834+
}
835+
}
836+
final bitChars = i.$2.group(0)!.substring(1, len - 1);
837+
var pos = 0;
838+
if (i.$1 > 0) {
839+
final nonExpChars = i.$2.start - lastCpos - span - 2;
840+
pos = lastPos + span + span * nonExpChars;
841+
} else {
842+
final nonExpChars = i.$2.start - lastCpos;
843+
pos = lastPos + span * nonExpChars;
844+
}
845+
846+
for (var bitPos = 0; bitPos < len - 2; bitPos++) {
847+
logicValList[pos + bitPos] = switch (bitChars[bitPos]) {
848+
'0' => LogicValue.zero,
849+
'1' => LogicValue.one,
850+
'x' => LogicValue.x,
851+
_ => LogicValue.z
852+
};
853+
}
854+
lastCpos = i.$2.start;
855+
lastPos = pos;
856+
}
857+
return logicValList.rswizzle();
858+
} else {
859+
throw Exception('Invalid LogicValue string $valueString');
860+
}
861+
}
862+
return LogicValue.zero;
863+
}
864+
607865
/// Compares this to `other`.
608866
///
609867
/// Returns a negative number if `this` is less than `other`, zero if they are

test/logic_value_test.dart

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,4 +2048,80 @@ void main() {
20482048
].swizzle());
20492049
}
20502050
});
2051+
2052+
group('RadixString', () {
2053+
test('radixString roundTrip', () {
2054+
final lv = LogicValue.ofBigInt(BigInt.from(737481838713847), 61);
2055+
for (final i in [2, 4, 8, 10, 16]) {
2056+
expect(
2057+
LogicValue.ofRadixString(lv.toRadixString(radix: i)), equals(lv));
2058+
}
2059+
});
2060+
test('radixString binary expansion', () {
2061+
final lv = LogicValue.ofRadixString("12'b10z111011z00");
2062+
expect(lv.toRadixString(radix: 16), equals("12'h<10z1>d<1z00>"));
2063+
for (final i in [2, 4, 8, 16]) {
2064+
expect(
2065+
LogicValue.ofRadixString(lv.toRadixString(radix: i)), equals(lv));
2066+
}
2067+
});
2068+
2069+
test('radixString leading zero', () {
2070+
final lv = LogicValue.ofRadixString("10'b00 0010 0111");
2071+
expect(lv.toRadixString(), equals("10'b10 0111"));
2072+
expect(lv.toRadixString(radix: 4), equals("10'q213"));
2073+
expect(lv.toRadixString(radix: 8), equals("10'o47"));
2074+
expect(lv.toRadixString(radix: 10), equals("10'd39"));
2075+
expect(lv.toRadixString(radix: 16), equals("10'h27"));
2076+
for (final i in [2, 4, 8, 10, 16]) {
2077+
expect(
2078+
LogicValue.ofRadixString(lv.toRadixString(radix: i)), equals(lv));
2079+
}
2080+
});
2081+
2082+
test('radixString leading Z', () {
2083+
final lv = LogicValue.ofRadixString("10'bzz zzz1 1011");
2084+
expect(lv.toRadixString(), equals("10'bzz zzz1 1011"));
2085+
expect(lv.toRadixString(radix: 4), equals("10'qZZ<z1>23"));
2086+
expect(lv.toRadixString(radix: 8), equals("10'oZZ<z11>3"));
2087+
expect(lv.toRadixString(radix: 16), equals("10'hZ<zzz1>b"));
2088+
for (final i in [2, 4, 8, 16]) {
2089+
expect(
2090+
LogicValue.ofRadixString(lv.toRadixString(radix: i)), equals(lv));
2091+
}
2092+
});
2093+
test('radixString small leading radix character', () {
2094+
final lv = LogicValue.ofRadixString("10'b10 1010 0111");
2095+
expect(lv.toRadixString(radix: 4), equals("10'q2 2213"));
2096+
expect(lv.toRadixString(radix: 8), equals("10'o1247"));
2097+
expect(lv.toRadixString(radix: 10), equals("10'd679"));
2098+
expect(lv.toRadixString(radix: 16), equals("10'h2A7"));
2099+
for (final i in [2, 4, 8, 10, 16]) {
2100+
expect(
2101+
LogicValue.ofRadixString(lv.toRadixString(radix: i)), equals(lv));
2102+
}
2103+
});
2104+
test('radixString: slide set bits along entire word', () {
2105+
final random = Random(5);
2106+
2107+
for (var width = 15; width < 23; width++) {
2108+
final inL = Logic(width: width);
2109+
for (var setWidth = 1; setWidth < 12; setWidth++) {
2110+
for (var iterations = 0; iterations < 10; iterations++) {
2111+
final ii = random.nextInt((1 << (setWidth + 1)) - 1);
2112+
2113+
for (var pos = 0; pos < inL.width - setWidth; pos++) {
2114+
final l = Logic(width: width);
2115+
l <= inL.withSet(pos, Const(ii, width: setWidth));
2116+
final lv = l.value;
2117+
for (final i in [2, 4, 8, 16]) {
2118+
expect(LogicValue.ofRadixString(lv.toRadixString(radix: i)),
2119+
equals(lv));
2120+
}
2121+
}
2122+
}
2123+
}
2124+
}
2125+
});
2126+
});
20512127
}

0 commit comments

Comments
 (0)