Skip to content

Commit fdee0cb

Browse files
authored
Various features and testing for Logic and LogicValue (#121)
1 parent d48a9af commit fdee0cb

13 files changed

+562
-37
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
- Fixed a bug related to zero-width construction of `LogicValue`s (https://github.com/intel/rohd/issues/90).
99
- Fixed a bug where generated constants in SystemVerilog had no width, which can cause issues in some cases (e.g. swizzles) (https://github.com/intel/rohd/issues/89)
1010
- Added capability to convert binary strings to ints with underscore separators using `bin` (https://github.com/intel/rohd/issues/56).
11+
- Added `getRange` and `reversed` on `Logic` and `slice` on `LogicValue` to improve consistency.
12+
- Using `slice` in reverse-index order now reverses the order.
13+
- Added the ability to extend signals (e.g. `zeroExtend` and `signExtend`) on both `Logic` and `LogicValue` (https://github.com/intel/rohd/issues/101).
14+
- Improved flexibility of `IfBlock`.
15+
- Added `withSet` on `LogicValue` and `Logic` to make it easier to assign subsets of signals and values (https://github.com/intel/rohd/issues/101).
16+
- Fixed a bug where 0-bit signals would sometimes improperly generate 0-bit constants in generated SystemVerilog (https://github.com/intel/rohd/issues/122).
1117

1218
## 0.2.0
1319
- Updated implementation to avoid `Iterable.forEach` to make debug easier.

README.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,12 @@ x.value.toInt()
175175
176176
// a BigInt
177177
x.value.toBigInt()
178+
179+
// constructing a LogicValue a handful of different ways
180+
LogicValue.ofString('0101xz01'); // 0b0101xz01
181+
LogicValue.of([LogicValue.one, LogicValue.zero]); // 0b10
182+
[LogicValue.z, LogicValue.x].swizzle(); // 0bzx
183+
LogicValue.ofInt(15, 4); // 0xf
178184
```
179185

180186
You can create `LogicValue`s using a variety of constructors including `ofInt`, `ofBigInt`, `filled` (like '0, '1, 'x, etc. in SystemVerilog), and `of` (which takes any `Iterable<LogicValue>`).
@@ -205,7 +211,8 @@ var x = Const(5, width:16);
205211
There is a convenience function for converting binary to an integer:
206212
```dart
207213
// this is equvialent to and shorter than int.parse('010101', radix:2)
208-
bin('010101')
214+
// you can put underscores to help with readability, they are ignored
215+
bin('01_0101')
209216
```
210217

211218
### Assignment
@@ -238,7 +245,7 @@ a_gte_b <= (a >= b) // greater than or equal NOTE: careful with order of oper
238245
```
239246

240247
### Shift Operations
241-
Dart has [implemented the triple shift](https://github.com/dart-lang/language/blob/master/accepted/future-releases/triple-shift-operator/feature-specification.md) operator (>>>) in the opposite way as is [implemented in SystemVerilog](https://www.nandland.com/verilog/examples/example-shift-operator-verilog.html). That is to say in Dart, >>> means *logical* shift right (fill with 0's), and >> means *arithmetic* shift right (maintaining sign). ROHD keeps consistency with Dart's implementation to avoid introducing confusion within Dart code you write (whether ROHD or plain Dart).
248+
Dart has [implemented the triple shift](https://github.com/dart-lang/language/blob/master/accepted/2.14/triple-shift-operator/feature-specification.md) operator (>>>) in the opposite way as is [implemented in SystemVerilog](https://www.nandland.com/verilog/examples/example-shift-operator-verilog.html). That is to say in Dart, >>> means *logical* shift right (fill with 0's), and >> means *arithmetic* shift right (maintaining sign). ROHD keeps consistency with Dart's implementation to avoid introducing confusion within Dart code you write (whether ROHD or plain Dart).
242249

243250
```dart
244251
a << b // logical shift left
@@ -247,7 +254,7 @@ a >>> b // logical shift right
247254
```
248255

249256
### Bus ranges and swizzling
250-
Multi-bit busses can be accessed by single bits and ranges or composed from multiple other signals.
257+
Multi-bit busses can be accessed by single bits and ranges or composed from multiple other signals. Slicing, swizzling, etc. are also accessible on `LogicValue`s.
251258
```dart
252259
var a = Logic(width:8),
253260
b = Logic(width:3),
@@ -271,7 +278,11 @@ e <= [d, c, b].swizzle();
271278
e <= [b, c, d].rswizzle();
272279
```
273280

274-
ROHD does not (currently) support assignment to a subset of a bus. That is, you *cannot* do something like `e[3] <= d`. If you need to build a bus from a collection of other signals, use swizzling.
281+
ROHD does not support assignment to a subset of a bus. That is, you *cannot* do something like `e[3] <= d`. Instead, you can use the `withSet` function to get a copy with that subset of the bus assigned to something else. This applies for both `Logic` and `LogicValue`. For example:
282+
```dart
283+
// reassign the variable `e` to a new `Logic` where bit 3 is set to `d`
284+
e = e.withSet(3, d);
285+
```
275286

276287
### Modules
277288
[`Module`](https://intel.github.io/rohd/rohd/Module-class.html)s are similar to modules in SystemVerilog. They have inputs and outputs and logic that connects them. There are a handful of rules that *must* be followed when implementing a module.
@@ -373,7 +384,7 @@ ROHD supports a variety of [`Conditional`](https://intel.github.io/rohd/rohd/Con
373384

374385
Conditional statements are executed imperatively and in order, just like the contents of `always` blocks in SystemVerilog. `_Always` blocks in ROHD map 1-to-1 with SystemVerilog `always` statements when converted.
375386

376-
Assignments within an `_Always` should be executed conditionally, so use the `<` operator which creates a [`ConditionalAssign`](https://intel.github.io/rohd/rohd/ConditionalAssign-class.html) object instead of `<=`.
387+
Assignments within an `_Always` should be executed conditionally, so use the `<` operator which creates a [`ConditionalAssign`](https://intel.github.io/rohd/rohd/ConditionalAssign-class.html) object instead of `<=`. The right hand side a `ConditionalAssign` can be anything that can be `put` onto a `Logic`, which includes `int`s. If you're looking to fill the width of something, use `Const` with the `fill = true`.
377388

378389
#### `If`
379390
Below is an example of an [`If`](https://intel.github.io/rohd/rohd/If-class.html) statement in ROHD:
@@ -390,7 +401,7 @@ Combinational([
390401
q < 13,
391402
], orElse: [
392403
y < 0,
393-
z < 1,
404+
z < Const(1, width: 4, fill: true),
394405
])])
395406
]);
396407
```

lib/src/logic.dart

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ class Logic {
174174
Module? get parentModule => _parentModule;
175175
Module? _parentModule;
176176

177-
/// Sets the value of [parentModule] to [newParent].
177+
/// Sets the value of [parentModule] to [newParentModule].
178178
///
179179
/// This should *only* be called by [Module.build()]. It is used to optimize search.
180180
@protected
@@ -426,7 +426,92 @@ class Logic {
426426
}
427427

428428
/// Accesses a subset of this signal from [startIndex] to [endIndex], both inclusive.
429+
///
430+
/// If [endIndex] is less than [startIndex], the returned value will be reversed relative
431+
/// to the original signal.
429432
Logic slice(int endIndex, int startIndex) {
430433
return BusSubset(this, startIndex, endIndex).subset;
431434
}
435+
436+
/// Returns a version of this [Logic] with the bit order reversed.
437+
Logic get reversed => slice(0, width - 1);
438+
439+
/// Returns a subset [Logic]. It is inclusive of [startIndex], exclusive of [endIndex].
440+
///
441+
/// [startIndex] must be less than [endIndex]. If [startIndex] and [endIndex] are equal, then a
442+
/// zero-width signal is returned.
443+
Logic getRange(int startIndex, int endIndex) {
444+
if (endIndex < startIndex) {
445+
throw Exception(
446+
'End ($endIndex) cannot be less than start ($startIndex).');
447+
}
448+
if (endIndex > width) {
449+
throw Exception('End ($endIndex) must be less than width ($width).');
450+
}
451+
if (startIndex < 0) {
452+
throw Exception(
453+
'Start ($startIndex) must be greater than or equal to 0.');
454+
}
455+
if (endIndex == startIndex) {
456+
return Const(0, width: 0);
457+
}
458+
return slice(endIndex - 1, startIndex);
459+
}
460+
461+
/// Returns a new [Logic] with width [newWidth] where new bits added are zeros
462+
/// as the most significant bits.
463+
///
464+
/// The [newWidth] must be greater than or equal to the current width or an exception
465+
/// will be thrown.
466+
Logic zeroExtend(int newWidth) {
467+
if (newWidth < width) {
468+
throw Exception(
469+
'New width $newWidth must be greater than or equal to width $width.');
470+
}
471+
return [
472+
Const(0, width: newWidth - width),
473+
this,
474+
].swizzle();
475+
}
476+
477+
/// Returns a new [Logic] with width [newWidth] where new bits added are sign bits
478+
/// as the most significant bits. The sign is determined using two's complement, so
479+
/// it takes the most significant bit of the original signal and extends with that.
480+
///
481+
/// The [newWidth] must be greater than or equal to the current width or an exception
482+
/// will be thrown.
483+
Logic signExtend(int newWidth) {
484+
if (newWidth < width) {
485+
throw Exception(
486+
'New width $newWidth must be greater than or equal to width $width.');
487+
}
488+
return [
489+
Mux(
490+
this[width - 1],
491+
Const(1, width: newWidth - width, fill: true),
492+
Const(0, width: newWidth - width),
493+
).y,
494+
this,
495+
].swizzle();
496+
}
497+
498+
/// Returns a copy of this [Logic] with the bits starting from [startIndex]
499+
/// up until [startIndex] + [update]`.width` set to [update] instead
500+
/// of their original value.
501+
///
502+
/// The return signal will be the same [width]. An exception will be thrown if
503+
/// the position of the [update] would cause an overrun past the [width].
504+
Logic withSet(int startIndex, Logic update) {
505+
if (startIndex + update.width > width) {
506+
throw Exception(
507+
'Width of updatedValue $update at startIndex $startIndex would'
508+
'overrun the width of the original ($width).');
509+
}
510+
511+
return [
512+
getRange(startIndex + update.width, width),
513+
update,
514+
getRange(0, startIndex),
515+
].swizzle();
516+
}
432517
}

lib/src/modules/bus.dart

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ class BusSubset extends Module with InlineSystemVerilog {
7575
throw Exception('BusSubset has exactly one input, but saw $inputs.');
7676
}
7777
var a = inputs[_original]!;
78+
79+
// SystemVerilog doesn't allow reverse-order select to reverse a bus, so do it manually
80+
if (startIndex > endIndex) {
81+
return '{' +
82+
List.generate(startIndex - endIndex + 1, (i) => '$a[${endIndex + i}]')
83+
.join(',') +
84+
'}';
85+
}
86+
7887
var sliceString =
7988
startIndex == endIndex ? '[$startIndex]' : '[$endIndex:$startIndex]';
8089
return '$a$sliceString';
@@ -122,9 +131,8 @@ class Swizzle extends Module with InlineSystemVerilog {
122131

123132
/// Executes the functional behavior of this gate.
124133
void _execute(int startIdx, Logic swizzleInput, LogicValueChanged? args) {
125-
var updatedVal = out.value.toList();
126-
updatedVal.setAll(startIdx, swizzleInput.value.toList());
127-
out.put(LogicValue.of(updatedVal));
134+
var updatedVal = out.value.withSet(startIdx, swizzleInput.value);
135+
out.put(updatedVal);
128136
}
129137

130138
@override
@@ -133,7 +141,10 @@ class Swizzle extends Module with InlineSystemVerilog {
133141
throw Exception('This swizzle has ${_swizzleInputs.length} inputs,'
134142
' but saw $inputs with ${inputs.length} values.');
135143
}
136-
var inputStr = _swizzleInputs.reversed.map((e) => inputs[e.name]).join(',');
144+
var inputStr = _swizzleInputs.reversed
145+
.where((e) => e.width > 0)
146+
.map((e) => inputs[e.name])
147+
.join(',');
137148
return '{$inputStr}';
138149
}
139150
}

lib/src/modules/conditional.dart

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -650,22 +650,20 @@ class CaseZ extends Case {
650650
/// A conditional block to execute only if [condition] is satisified.
651651
///
652652
/// Intended for use with [IfBlock].
653-
class Iff {
653+
class ElseIf {
654654
/// A condition to match against to determine if [then] should be executed.
655655
final Logic condition;
656656

657657
/// The [Conditional]s to execute if [condition] is satisfied.
658658
final List<Conditional> then;
659659

660-
Iff(this.condition, this.then);
660+
ElseIf(this.condition, this.then);
661661
}
662662

663663
/// A conditional block to execute only if [condition] is satisified.
664664
///
665665
/// Intended for use with [IfBlock].
666-
class ElseIf extends Iff {
667-
ElseIf(Logic condition, List<Conditional> then) : super(condition, then);
668-
}
666+
typedef Iff = ElseIf;
669667

670668
/// A conditional block to execute only if [condition] is satisified.
671669
///
@@ -681,8 +679,9 @@ class Else extends Iff {
681679
class IfBlock extends Conditional {
682680
/// A set of conditional items to check against for execution, in order.
683681
///
684-
/// The first item *must* be an [Iff], and if an [Else] is included it must
685-
/// be the last item. Any other items should be [ElseIf].
682+
/// The first item should be an [Iff], and if an [Else] is included it must
683+
/// be the last item. Any other items should be [ElseIf]. It is okay to make the
684+
/// first item an [ElseIf], it will act just like an [Iff].
686685
final List<Iff> iffs;
687686

688687
IfBlock(this.iffs);
@@ -753,12 +752,9 @@ class IfBlock extends Conditional {
753752
}
754753
var header = iff == iffs.first
755754
? 'if'
756-
: iff is ElseIf
757-
? 'else if'
758-
: iff is Else
759-
? 'else'
760-
: throw Exception(
761-
'Unsupported Iff type: ${iff.runtimeType}.');
755+
: iff is Else
756+
? 'else'
757+
: 'else if';
762758

763759
var conditionName = inputsNameMap[driverInput(iff.condition).name];
764760
var ifContents = iff.then

lib/src/values/logic_value.dart

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -282,20 +282,23 @@ abstract class LogicValue {
282282
/// Returns a new [LogicValue] with the order of all bits in the reverse order of this [LogicValue]
283283
LogicValue get reversed;
284284

285-
/// Returns a subset [LogicValue]. It is inclusive of [start], exclusive of [end].
285+
/// Returns a subset [LogicValue]. It is inclusive of [startIndex], exclusive of [endIndex].
286286
///
287-
/// If [start] and [end] are equal, then a zero-width signal is returned.
288-
LogicValue getRange(int start, int end) {
289-
if (end < start) {
290-
throw Exception('End ($end) cannot be greater than start ($start).');
287+
/// [startIndex] must be less than [endIndex]. If [startIndex] and [endIndex] are equal, then a
288+
/// zero-width value is returned.
289+
LogicValue getRange(int startIndex, int endIndex) {
290+
if (endIndex < startIndex) {
291+
throw Exception(
292+
'End ($endIndex) cannot be less than start ($startIndex).');
291293
}
292-
if (end > width) {
293-
throw Exception('End ($end) must be less than width ($width).');
294+
if (endIndex > width) {
295+
throw Exception('End ($endIndex) must be less than width ($width).');
294296
}
295-
if (start < 0) {
296-
throw Exception('Start ($start) must be greater than or equal to 0.');
297+
if (startIndex < 0) {
298+
throw Exception(
299+
'Start ($startIndex) must be greater than or equal to 0.');
297300
}
298-
return _getRange(start, end);
301+
return _getRange(startIndex, endIndex);
299302
}
300303

301304
/// Returns a subset [LogicValue]. It is inclusive of [start], exclusive of [end].
@@ -304,6 +307,18 @@ abstract class LogicValue {
304307
/// If [start] and [end] are equal, then a zero-width signal is returned.
305308
LogicValue _getRange(int start, int end);
306309

310+
/// Accesses a subset of this [LogicValue] from [startIndex] to [endIndex], both inclusive.
311+
///
312+
/// If [endIndex] is less than [startIndex], the returned value will be reversed relative
313+
/// to the original value.
314+
LogicValue slice(int endIndex, int startIndex) {
315+
if (startIndex <= endIndex) {
316+
return getRange(startIndex, endIndex + 1);
317+
} else {
318+
return getRange(endIndex, startIndex + 1).reversed;
319+
}
320+
}
321+
307322
/// Converts a pair of `_value` and `_invalid` into a [LogicValue].
308323
LogicValue _bitsToLogicValue(bool bitValue, bool bitInvalid) => bitInvalid
309324
? (bitValue ? LogicValue.z : LogicValue.x)
@@ -595,6 +610,64 @@ abstract class LogicValue {
595610
}
596611
return previousValue == LogicValue.one && newValue == LogicValue.zero;
597612
}
613+
614+
/// Returns a new [LogicValue] with width [newWidth] where the most significant
615+
/// bits for indices beyond the original [width] are set to [fill].
616+
///
617+
/// The [newWidth] must be greater than or equal to the current width or an exception
618+
/// will be thrown. [fill] must be a single bit ([width]=1).
619+
LogicValue extend(int newWidth, LogicValue fill) {
620+
if (newWidth < width) {
621+
throw Exception(
622+
'New width $newWidth must be greater than or equal to width $width.');
623+
}
624+
if (fill.width != 1) {
625+
throw Exception('The fill must be 1 bit, but got $fill.');
626+
}
627+
return [
628+
LogicValue.filled(newWidth - width, fill),
629+
this,
630+
].swizzle();
631+
}
632+
633+
/// Returns a new [LogicValue] with width [newWidth] where new bits added are zeros
634+
/// as the most significant bits.
635+
///
636+
/// The [newWidth] must be greater than or equal to the current width or an exception
637+
/// will be thrown.
638+
LogicValue zeroExtend(int newWidth) {
639+
return extend(newWidth, LogicValue.zero);
640+
}
641+
642+
/// Returns a new [LogicValue] with width [newWidth] where new bits added are sign bits
643+
/// as the most significant bits. The sign is determined using two's complement, so
644+
/// it takes the most significant bit of the original value and extends with that.
645+
///
646+
/// The [newWidth] must be greater than or equal to the current width or an exception
647+
/// will be thrown.
648+
LogicValue signExtend(int newWidth) {
649+
return extend(newWidth, this[width - 1]);
650+
}
651+
652+
/// Returns a copy of this [LogicValue] with the bits starting from [startIndex]
653+
/// up until [startIndex] + [update]`.width` set to [update] instead
654+
/// of their original value.
655+
///
656+
/// The return value will be the same [width]. An exception will be thrown if
657+
/// the position of the [update] would cause an overrun past the [width].
658+
LogicValue withSet(int startIndex, LogicValue update) {
659+
if (startIndex + update.width > width) {
660+
throw Exception(
661+
'Width of updatedValue $update at startIndex $startIndex would'
662+
'overrun the width of the original ($width).');
663+
}
664+
665+
return [
666+
getRange(startIndex + update.width, width),
667+
update,
668+
getRange(0, startIndex),
669+
].swizzle();
670+
}
598671
}
599672

600673
/// Enum for direction of shift

0 commit comments

Comments
 (0)