From 292bf2d72269cb098f16a77a87e2d847f5ec7294 Mon Sep 17 00:00:00 2001 From: Madhanika Jayasimha Date: Fri, 5 Apr 2024 14:17:29 -0700 Subject: [PATCH 01/10] Avoid Module creation for Constant Gate scenarios --- lib/src/signals/logic.dart | 52 ++++++++++++++++-- test/gate_test.dart | 105 +++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 3 deletions(-) diff --git a/lib/src/signals/logic.dart b/lib/src/signals/logic.dart index 8c468f254..8de96bafc 100644 --- a/lib/src/signals/logic.dart +++ b/lib/src/signals/logic.dart @@ -319,13 +319,59 @@ class Logic { void operator <=(Logic other) => gets(other); /// Logical bitwise NOT. - Logic operator ~() => NotGate(this).out; + Logic operator ~() { + if (this is Const) { + if (value.isValid) { + if (value == LogicValue.of(0, width: width)) { + return Const(LogicValue.filled(width, LogicValue.one)); + } else if (value == LogicValue.filled(width, LogicValue.one)) { + return Const(LogicValue.of(0, width: width)); + } + } else { + return Const(LogicValue.x, width: width); + } + } + return NotGate(this).out; + } /// Logical bitwise AND. - Logic operator &(Logic other) => And2Gate(this, other).out; + Logic operator &(Logic other) { + if (this is Const || other is Const) { + if (value.isValid && other.value.isValid) { + if (value == LogicValue.of(0, width: width) || + other.value == LogicValue.of(0, width: width)) { + return Const(LogicValue.of(0, width: width)); + } else if (value == LogicValue.filled(width, LogicValue.one)) { + return other; + } else if (other.value == LogicValue.filled(width, LogicValue.one)) { + return this; + } + } else { + return Const(LogicValue.x, width: width); + } + } + return And2Gate(this, other).out; + } /// Logical bitwise OR. - Logic operator |(Logic other) => Or2Gate(this, other).out; + Logic operator |(Logic other) { + if (this is Const || other is Const) { + if (value.isValid && other.value.isValid) { + if (value == LogicValue.filled(width, LogicValue.one) || + other.value == LogicValue.filled(width, LogicValue.one)) { + return Const(LogicValue.filled(width, LogicValue.one)); + } else if (value == LogicValue.of(LogicValue.zero, width: width)) { + return other; + } else if (other.value == + LogicValue.of(LogicValue.zero, width: width)) { + return this; + } + } else { + return Const(LogicValue.x, width: width); + } + } + return Or2Gate(this, other).out; + } /// Logical bitwise XOR. Logic operator ^(Logic other) => Xor2Gate(this, other).out; diff --git a/test/gate_test.dart b/test/gate_test.dart index 117783848..057c96a5d 100644 --- a/test/gate_test.dart +++ b/test/gate_test.dart @@ -7,6 +7,8 @@ // 2021 May 7 // Author: Max Korbel +import 'dart:math'; + import 'package:rohd/rohd.dart'; import 'package:rohd/src/utilities/simcompare.dart'; import 'package:test/test.dart'; @@ -353,6 +355,109 @@ void main() { }); }); + group('Const input Gate test', () { + test('NotGate single bit Constant input', () async { + final a = Logic(); + final b = Const(LogicValue.zero, width: 1); + final c = Const(LogicValue.x, width: 1); + final d = Const(LogicValue.one, width: 1); + a.put(LogicValue.one); + + final result = ~b; + expect(result, isA()); + expect(result.value, equals(LogicValue.one)); + + final result2 = ~c; + expect(result2, isA()); + expect(result2.value, equals(LogicValue.x)); + + final result3 = ~d; + expect(result3, isA()); + expect(result3.value, equals(LogicValue.zero)); + }); + + test('NotGate Multi bit Constant input', () async { + final b = Const(bin('1111'), width: 4); + final c = Const(bin('0100'), width: 4); + final result = ~b; + + expect(result, isA()); + expect(result.value, equals(LogicValue.of(LogicValue.zero, width: 4))); + + final result2 = ~c; + expect(result2, isA()); + expect(result2.value, equals(LogicValue.of(11, width: 4))); + }); + + test('And2Gate Single bit Constant input', () async { + final a = Logic(); + final b = Const(LogicValue.zero, width: 1); + final c = Const(LogicValue.x, width: 1); + final d = Const(LogicValue.one, width: 1); + a.put(LogicValue.one); + + final result = a & b; + expect(result, isA()); + expect(result.value, equals(LogicValue.zero)); + + final result2 = a & c; + expect(result2, isA()); + expect(result2.value, equals(LogicValue.x)); + + final result3 = a & d; + expect(result3, isA()); + expect(result3.value, equals(a.value)); + }); + + test('And2Gate Multi bit Constant input', () async { + final a = Const(LogicValue.of(LogicValue.zero, width: 4)); + final b = Const(bin('1111'), width: 4); + final c = Const(bin('0100'), width: 4); + final result = a & c; + + expect(result, isA()); + expect(result.value, equals(LogicValue.of(LogicValue.zero, width: 4))); + + final result2 = b & c; + expect(result2, isA()); + expect(result2.value, equals(c.value)); + }); + + test('OR2Gate Single bit Constant input', () async { + final a = Logic(); + final b = Const(LogicValue.zero, width: 1); + final c = Const(LogicValue.x, width: 1); + final d = Const(LogicValue.one, width: 1); + a.put(LogicValue.one); + + final result = a | b; + expect(result, isA()); + expect(result.value, equals(a.value)); + + final result2 = a | c; + expect(result2, isA()); + expect(result2.value, equals(LogicValue.x)); + + final result3 = a | d; + expect(result3, isA()); + expect(result3.value, equals(LogicValue.one)); + }); + + test('OR2Gate Multi bit Constant input', () async { + final a = Const(LogicValue.of(LogicValue.zero, width: 4)); + final b = Const(bin('1111'), width: 4); + final c = Const(bin('0100'), width: 4); + final result = a | c; + + expect(result, isA()); + expect(result.value, equals(c.value)); + + final result2 = b | c; + expect(result2, isA()); + expect(result2.value, equals(LogicValue.filled(4, LogicValue.one))); + }); + }); + group('simcompare', () { test('NotGate single bit', () async { final gtm = GateTestModule(Logic(), Logic()); From 81734f2c8c26e1db7b42605af3b1e4578fef3ac3 Mon Sep 17 00:00:00 2001 From: Madhanika Jayasimha Date: Fri, 5 Apr 2024 14:51:40 -0700 Subject: [PATCH 02/10] Removed extra import --- test/gate_test.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/gate_test.dart b/test/gate_test.dart index 057c96a5d..3665164ec 100644 --- a/test/gate_test.dart +++ b/test/gate_test.dart @@ -7,8 +7,6 @@ // 2021 May 7 // Author: Max Korbel -import 'dart:math'; - import 'package:rohd/rohd.dart'; import 'package:rohd/src/utilities/simcompare.dart'; import 'package:test/test.dart'; From efa4b95b70167520182a07412830b9b4b44915cc Mon Sep 17 00:00:00 2001 From: Madhanika Jayasimha Date: Mon, 8 Apr 2024 16:37:03 -0700 Subject: [PATCH 03/10] Added changes based on comments --- lib/src/signals/logic.dart | 62 +++++++++-------------- test/gate_test.dart | 100 ++++++++++++++++++++----------------- 2 files changed, 76 insertions(+), 86 deletions(-) diff --git a/lib/src/signals/logic.dart b/lib/src/signals/logic.dart index 8de96bafc..60e19a912 100644 --- a/lib/src/signals/logic.dart +++ b/lib/src/signals/logic.dart @@ -319,35 +319,21 @@ class Logic { void operator <=(Logic other) => gets(other); /// Logical bitwise NOT. - Logic operator ~() { - if (this is Const) { - if (value.isValid) { - if (value == LogicValue.of(0, width: width)) { - return Const(LogicValue.filled(width, LogicValue.one)); - } else if (value == LogicValue.filled(width, LogicValue.one)) { - return Const(LogicValue.of(0, width: width)); - } - } else { - return Const(LogicValue.x, width: width); - } - } - return NotGate(this).out; - } + Logic operator ~() => this is Const ? Const(~value) : NotGate(this).out; /// Logical bitwise AND. Logic operator &(Logic other) { - if (this is Const || other is Const) { - if (value.isValid && other.value.isValid) { - if (value == LogicValue.of(0, width: width) || - other.value == LogicValue.of(0, width: width)) { - return Const(LogicValue.of(0, width: width)); - } else if (value == LogicValue.filled(width, LogicValue.one)) { - return other; - } else if (other.value == LogicValue.filled(width, LogicValue.one)) { - return this; - } - } else { - return Const(LogicValue.x, width: width); + if (this is Const && + other is Const && + value.isValid && + other.value.isValid) { + if (value == LogicValue.of(0, width: width) || + other.value == LogicValue.of(0, width: width)) { + return Const(LogicValue.of(0, width: width)); + } else if (value == LogicValue.filled(width, LogicValue.one)) { + return other; + } else if (other.value == LogicValue.filled(width, LogicValue.one)) { + return this; } } return And2Gate(this, other).out; @@ -355,19 +341,17 @@ class Logic { /// Logical bitwise OR. Logic operator |(Logic other) { - if (this is Const || other is Const) { - if (value.isValid && other.value.isValid) { - if (value == LogicValue.filled(width, LogicValue.one) || - other.value == LogicValue.filled(width, LogicValue.one)) { - return Const(LogicValue.filled(width, LogicValue.one)); - } else if (value == LogicValue.of(LogicValue.zero, width: width)) { - return other; - } else if (other.value == - LogicValue.of(LogicValue.zero, width: width)) { - return this; - } - } else { - return Const(LogicValue.x, width: width); + if (this is Const && + other is Const && + value.isValid && + other.value.isValid) { + if (value == LogicValue.filled(width, LogicValue.one) || + other.value == LogicValue.filled(width, LogicValue.one)) { + return Const(LogicValue.filled(width, LogicValue.one)); + } else if (value == LogicValue.of(LogicValue.zero, width: width)) { + return other; + } else if (other.value == LogicValue.of(LogicValue.zero, width: width)) { + return this; } } return Or2Gate(this, other).out; diff --git a/test/gate_test.dart b/test/gate_test.dart index 3665164ec..b58b7cd27 100644 --- a/test/gate_test.dart +++ b/test/gate_test.dart @@ -361,30 +361,25 @@ void main() { final d = Const(LogicValue.one, width: 1); a.put(LogicValue.one); - final result = ~b; - expect(result, isA()); - expect(result.value, equals(LogicValue.one)); + expect(~b, isA()); + expect((~b).value, equals(LogicValue.one)); - final result2 = ~c; - expect(result2, isA()); - expect(result2.value, equals(LogicValue.x)); + expect(~c, isA()); + expect((~c).value, equals(LogicValue.x)); - final result3 = ~d; - expect(result3, isA()); - expect(result3.value, equals(LogicValue.zero)); + expect(~d, isA()); + expect((~d).value, equals(LogicValue.zero)); }); test('NotGate Multi bit Constant input', () async { final b = Const(bin('1111'), width: 4); final c = Const(bin('0100'), width: 4); - final result = ~b; - expect(result, isA()); - expect(result.value, equals(LogicValue.of(LogicValue.zero, width: 4))); + expect(~b, isA()); + expect((~b).value, equals(LogicValue.of(LogicValue.zero, width: 4))); - final result2 = ~c; - expect(result2, isA()); - expect(result2.value, equals(LogicValue.of(11, width: 4))); + expect(~c, isA()); + expect((~c).value, equals(LogicValue.of(11, width: 4))); }); test('And2Gate Single bit Constant input', () async { @@ -394,31 +389,42 @@ void main() { final d = Const(LogicValue.one, width: 1); a.put(LogicValue.one); - final result = a & b; - expect(result, isA()); - expect(result.value, equals(LogicValue.zero)); + expect(a & b, isA()); + expect((a & b).value, equals(LogicValue.zero)); - final result2 = a & c; - expect(result2, isA()); - expect(result2.value, equals(LogicValue.x)); + expect(a & c, isA()); + expect((a & c).value, equals(LogicValue.x)); - final result3 = a & d; - expect(result3, isA()); - expect(result3.value, equals(a.value)); + expect(b & c, isA()); + expect((b & c).value, equals(LogicValue.zero)); + + expect(a & d, isA()); + expect((a & d).value, equals(a.value)); + + expect(b & d, isA()); + expect((b & d).value, equals(LogicValue.zero)); }); test('And2Gate Multi bit Constant input', () async { final a = Const(LogicValue.of(LogicValue.zero, width: 4)); - final b = Const(bin('1111'), width: 4); - final c = Const(bin('0100'), width: 4); - final result = a & c; + final b = Const('1111', width: 4); + final c = Const('0100', width: 4); + final d = Const(1, width: 32, fill: true); + final e = Const('10110', width: 32); + final z = Const('11111', width: 5); + final y = Const(LogicValue.of('11x01', width: 5)); - expect(result, isA()); - expect(result.value, equals(LogicValue.of(LogicValue.zero, width: 4))); + expect(a & c, isA()); + expect((a & c).value, equals(LogicValue.of(LogicValue.zero, width: 4))); - final result2 = b & c; - expect(result2, isA()); - expect(result2.value, equals(c.value)); + expect(b & c, isA()); + expect((b & c).value, equals(c.value)); + + expect(d & e, isA()); + expect((d & e).value, equals(LogicValue.of('10110', width: 32))); + + expect(y & z, isA()); + expect((y & z).value, equals(LogicValue.of('11x01', width: 5))); }); test('OR2Gate Single bit Constant input', () async { @@ -426,33 +432,33 @@ void main() { final b = Const(LogicValue.zero, width: 1); final c = Const(LogicValue.x, width: 1); final d = Const(LogicValue.one, width: 1); + final z = Const('11111', width: 5); + final y = Const(LogicValue.of('11x01', width: 5)); a.put(LogicValue.one); - final result = a | b; - expect(result, isA()); - expect(result.value, equals(a.value)); + expect(a | b, isA()); + expect((a | b).value, equals(a.value)); + + expect(a | c, isA()); + expect((a | c).value, equals(LogicValue.one)); - final result2 = a | c; - expect(result2, isA()); - expect(result2.value, equals(LogicValue.x)); + expect(a | d, isA()); + expect((a | d).value, equals(LogicValue.one)); - final result3 = a | d; - expect(result3, isA()); - expect(result3.value, equals(LogicValue.one)); + expect(y | z, isA()); + expect((y | z).value, equals(LogicValue.of('11111', width: 5))); }); test('OR2Gate Multi bit Constant input', () async { final a = Const(LogicValue.of(LogicValue.zero, width: 4)); final b = Const(bin('1111'), width: 4); final c = Const(bin('0100'), width: 4); - final result = a | c; - expect(result, isA()); - expect(result.value, equals(c.value)); + expect(a | c, isA()); + expect((a | c).value, equals(c.value)); - final result2 = b | c; - expect(result2, isA()); - expect(result2.value, equals(LogicValue.filled(4, LogicValue.one))); + expect(b | c, isA()); + expect((b | c).value, equals(LogicValue.filled(4, LogicValue.one))); }); }); From 8e7789b0927f8d40c5dcf64468f3e4b2667eabe1 Mon Sep 17 00:00:00 2001 From: Madhanika Jayasimha Date: Tue, 9 Jul 2024 12:13:51 -0700 Subject: [PATCH 04/10] Added fixes based on feedback --- lib/src/signals/logic.dart | 58 ++++++++++++++-------- test/gate_test.dart | 98 ++++++++++++++++++-------------------- 2 files changed, 83 insertions(+), 73 deletions(-) diff --git a/lib/src/signals/logic.dart b/lib/src/signals/logic.dart index 272a37ffb..9420de2f3 100644 --- a/lib/src/signals/logic.dart +++ b/lib/src/signals/logic.dart @@ -387,17 +387,25 @@ class Logic { /// Logical bitwise AND. Logic operator &(Logic other) { - if (this is Const && - other is Const && - value.isValid && - other.value.isValid) { - if (value == LogicValue.of(0, width: width) || - other.value == LogicValue.of(0, width: width)) { - return Const(LogicValue.of(0, width: width)); - } else if (value == LogicValue.filled(width, LogicValue.one)) { - return other; - } else if (other.value == LogicValue.filled(width, LogicValue.one)) { - return this; + if (this is Const && other is Const) { + return Const((this as Const).value & other.value); + } + Const? constOperand; + Logic? otherOperand; + + if (this is Const && value.isValid) { + constOperand = Const(this); + } else if (other is Const && other.value.isValid) { + constOperand = other; + otherOperand = this; + } + + if (constOperand != null) { + if (constOperand.value == LogicValue.filled(width, LogicValue.zero)) { + return Const(LogicValue.filled(width, LogicValue.zero)); + } else if (constOperand.value == + LogicValue.filled(width, LogicValue.one)) { + return otherOperand!; } } return And2Gate(this, other).out; @@ -405,17 +413,25 @@ class Logic { /// Logical bitwise OR. Logic operator |(Logic other) { - if (this is Const && - other is Const && - value.isValid && - other.value.isValid) { - if (value == LogicValue.filled(width, LogicValue.one) || - other.value == LogicValue.filled(width, LogicValue.one)) { + if (this is Const && other is Const) { + return Const((this as Const).value | other.value); + } + Const? constOperand; + Logic? otherOperand; + + if (this is Const && value.isValid) { + constOperand = Const(this); + } else if (other is Const && other.value.isValid) { + constOperand = other; + otherOperand = this; + } + + if (constOperand != null) { + if (constOperand.value == LogicValue.filled(width, LogicValue.one)) { return Const(LogicValue.filled(width, LogicValue.one)); - } else if (value == LogicValue.of(LogicValue.zero, width: width)) { - return other; - } else if (other.value == LogicValue.of(LogicValue.zero, width: width)) { - return this; + } else if (constOperand.value == + LogicValue.filled(width, LogicValue.zero)) { + return otherOperand!; } } return Or2Gate(this, other).out; diff --git a/test/gate_test.dart b/test/gate_test.dart index b58b7cd27..d405f0ff8 100644 --- a/test/gate_test.dart +++ b/test/gate_test.dart @@ -355,76 +355,70 @@ void main() { group('Const input Gate test', () { test('NotGate single bit Constant input', () async { - final a = Logic(); - final b = Const(LogicValue.zero, width: 1); - final c = Const(LogicValue.x, width: 1); - final d = Const(LogicValue.one, width: 1); - a.put(LogicValue.one); + final normalLogic = Logic(); + final const0 = Const(LogicValue.zero, width: 1); + final constX = Const(LogicValue.x, width: 1); + final const1 = Const(LogicValue.one, width: 1); + normalLogic.put(LogicValue.one); - expect(~b, isA()); - expect((~b).value, equals(LogicValue.one)); + expect(~const0, isA()); + expect((~const0).value, equals(LogicValue.one)); - expect(~c, isA()); - expect((~c).value, equals(LogicValue.x)); + expect(~constX, isA()); + expect((~constX).value, equals(LogicValue.x)); - expect(~d, isA()); - expect((~d).value, equals(LogicValue.zero)); + expect(~const1, isA()); + expect((~const1).value, equals(LogicValue.zero)); }); test('NotGate Multi bit Constant input', () async { - final b = Const(bin('1111'), width: 4); - final c = Const(bin('0100'), width: 4); + final const1 = Const(bin('1111'), width: 4); + final const4 = Const(bin('0100'), width: 4); - expect(~b, isA()); - expect((~b).value, equals(LogicValue.of(LogicValue.zero, width: 4))); + expect(~const1, isA()); + expect((~const1).value, equals(LogicValue.of(LogicValue.zero, width: 4))); - expect(~c, isA()); - expect((~c).value, equals(LogicValue.of(11, width: 4))); + expect(~const4, isA()); + expect((~const4).value, equals(LogicValue.of(11, width: 4))); }); test('And2Gate Single bit Constant input', () async { - final a = Logic(); - final b = Const(LogicValue.zero, width: 1); - final c = Const(LogicValue.x, width: 1); - final d = Const(LogicValue.one, width: 1); - a.put(LogicValue.one); - - expect(a & b, isA()); - expect((a & b).value, equals(LogicValue.zero)); - - expect(a & c, isA()); - expect((a & c).value, equals(LogicValue.x)); - - expect(b & c, isA()); - expect((b & c).value, equals(LogicValue.zero)); - - expect(a & d, isA()); - expect((a & d).value, equals(a.value)); - - expect(b & d, isA()); - expect((b & d).value, equals(LogicValue.zero)); + final normalLogic = Logic(); + final const0 = Const(LogicValue.zero); + final logicX = Logic(); + final const1 = Const(LogicValue.one); + normalLogic.put(LogicValue.one); + logicX.put(LogicValue.x); + + expect(normalLogic & const0, isA()); + expect((normalLogic & const0).value, equals(LogicValue.zero)); + expect(logicX & const0, isA()); + expect((logicX & const0).value, equals(LogicValue.zero)); + expect(normalLogic & const1, isA()); + expect((normalLogic & const1).value, equals(normalLogic.value)); + expect(normalLogic & logicX, isA()); + expect((normalLogic & logicX).value, equals(LogicValue.x)); + expect(const0 & const1, isA()); }); test('And2Gate Multi bit Constant input', () async { - final a = Const(LogicValue.of(LogicValue.zero, width: 4)); - final b = Const('1111', width: 4); - final c = Const('0100', width: 4); - final d = Const(1, width: 32, fill: true); - final e = Const('10110', width: 32); - final z = Const('11111', width: 5); - final y = Const(LogicValue.of('11x01', width: 5)); + final const1 = Const(bin('1111'), width: 4); + final const4 = Const(bin('0100'), width: 4); + final logic0 = Logic(width: 4)..put(LogicValue.zero); + final logic1 = Logic(width: 4)..put(LogicValue.one); + final logicX = Logic(width: 4)..put(LogicValue.x); - expect(a & c, isA()); - expect((a & c).value, equals(LogicValue.of(LogicValue.zero, width: 4))); + expect(const1 & const4, isA()); + expect((const1 & const4).value, equals(LogicValue.of('0100', width: 4))); - expect(b & c, isA()); - expect((b & c).value, equals(c.value)); + expect(logic0 & const1, isA()); + expect((logic0 & const1).value, equals(LogicValue.of('0000', width: 4))); - expect(d & e, isA()); - expect((d & e).value, equals(LogicValue.of('10110', width: 32))); + expect(logic1 & const1, isA()); + expect((logic1 & const1).value, equals(LogicValue.of('0001', width: 4))); - expect(y & z, isA()); - expect((y & z).value, equals(LogicValue.of('11x01', width: 5))); + expect(logicX & const1, isA()); + expect((logicX & const1).value, equals(LogicValue.of('xxxx', width: 4))); }); test('OR2Gate Single bit Constant input', () async { From 7ba4e115b569570adf601601f52afeed8bb2b318 Mon Sep 17 00:00:00 2001 From: Madhanika Jayasimha Date: Fri, 24 Jan 2025 11:28:24 -0800 Subject: [PATCH 05/10] Avoid module creation for Const gate scenarios Added some more changes based on feedback (#481) --- lib/src/signals/logic.dart | 16 +++++----- test/gate_test.dart | 64 ++++++++++++++++++++++++-------------- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/lib/src/signals/logic.dart b/lib/src/signals/logic.dart index e489095d6..157b174d3 100644 --- a/lib/src/signals/logic.dart +++ b/lib/src/signals/logic.dart @@ -431,18 +431,18 @@ class Logic { Logic? otherOperand; if (this is Const && value.isValid) { - constOperand = Const(this); + constOperand = this as Const; } else if (other is Const && other.value.isValid) { constOperand = other; otherOperand = this; } - if (constOperand != null) { + if (constOperand != null && otherOperand != null) { if (constOperand.value == LogicValue.filled(width, LogicValue.zero)) { - return Const(LogicValue.filled(width, LogicValue.zero)); + return constOperand; } else if (constOperand.value == LogicValue.filled(width, LogicValue.one)) { - return otherOperand!; + return otherOperand; } } return And2Gate(this, other).out; @@ -457,18 +457,18 @@ class Logic { Logic? otherOperand; if (this is Const && value.isValid) { - constOperand = Const(this); + constOperand = this as Const; } else if (other is Const && other.value.isValid) { constOperand = other; otherOperand = this; } - if (constOperand != null) { + if (constOperand != null && otherOperand != null) { if (constOperand.value == LogicValue.filled(width, LogicValue.one)) { - return Const(LogicValue.filled(width, LogicValue.one)); + return constOperand; } else if (constOperand.value == LogicValue.filled(width, LogicValue.zero)) { - return otherOperand!; + return otherOperand; } } return Or2Gate(this, other).out; diff --git a/test/gate_test.dart b/test/gate_test.dart index 570946e76..fe892f726 100644 --- a/test/gate_test.dart +++ b/test/gate_test.dart @@ -368,6 +368,9 @@ void main() { expect(~const1, isA()); expect((~const1).value, equals(LogicValue.zero)); + + expect(~normalLogic, isA()); + expect((~normalLogic).value, equals(LogicValue.zero)); }); test('NotGate Multi bit Constant input', () async { @@ -375,7 +378,7 @@ void main() { final const4 = Const(bin('0100'), width: 4); expect(~const1, isA()); - expect((~const1).value, equals(LogicValue.of(LogicValue.zero, width: 4))); + expect((~const1).value, equals(LogicValue.of(0, width: 4))); expect(~const4, isA()); expect((~const4).value, equals(LogicValue.of(11, width: 4))); @@ -391,13 +394,18 @@ void main() { expect(normalLogic & const0, isA()); expect((normalLogic & const0).value, equals(LogicValue.zero)); + expect(logicX & const0, isA()); expect((logicX & const0).value, equals(LogicValue.zero)); + expect(normalLogic & const1, isA()); expect((normalLogic & const1).value, equals(normalLogic.value)); + expect(normalLogic & logicX, isA()); expect((normalLogic & logicX).value, equals(LogicValue.x)); + expect(const0 & const1, isA()); + expect((const0 & const1).value, equals(const0.value)); }); test('And2Gate Multi bit Constant input', () async { @@ -421,37 +429,47 @@ void main() { }); test('OR2Gate Single bit Constant input', () async { - final a = Logic(); - final b = Const(LogicValue.zero, width: 1); - final c = Const(LogicValue.x, width: 1); - final d = Const(LogicValue.one, width: 1); - final z = Const('11111', width: 5); - final y = Const(LogicValue.of('11x01', width: 5)); - a.put(LogicValue.one); + final normalLogic = Logic(); + final const0 = Const(LogicValue.zero); + final logicX = Logic(); + final const1 = Const(LogicValue.one); + normalLogic.put(LogicValue.one); + logicX.put(LogicValue.x); + + expect(normalLogic | const0, isA()); + expect((normalLogic | const0).value, equals(normalLogic.value)); - expect(a | b, isA()); - expect((a | b).value, equals(a.value)); + expect(logicX | const0, isA()); + expect((logicX | const0).value, equals(logicX.value)); - expect(a | c, isA()); - expect((a | c).value, equals(LogicValue.one)); + expect(normalLogic | const1, isA()); + expect((normalLogic | const1).value, equals(LogicValue.one)); - expect(a | d, isA()); - expect((a | d).value, equals(LogicValue.one)); + expect(normalLogic | logicX, isA()); + expect((normalLogic | logicX).value, equals(LogicValue.one)); - expect(y | z, isA()); - expect((y | z).value, equals(LogicValue.of('11111', width: 5))); + expect(const0 | const1, isA()); + expect((const0 | const1).value, equals(LogicValue.one)); }); test('OR2Gate Multi bit Constant input', () async { - final a = Const(LogicValue.of(LogicValue.zero, width: 4)); - final b = Const(bin('1111'), width: 4); - final c = Const(bin('0100'), width: 4); + final const1 = Const(bin('1101'), width: 4); + final const4 = Const(bin('0100'), width: 4); + final logic0 = Logic(width: 4)..put(LogicValue.zero); + final logic1 = Logic(width: 4)..put(LogicValue.one); + final logicX = Logic(width: 4)..put(LogicValue.x); + + expect(const1 | const4, isA()); + expect((const1 | const4).value, equals(LogicValue.of('1101', width: 4))); + + expect(logic0 | const1, isA()); + expect((logic0 | const1).value, equals(const1.value)); - expect(a | c, isA()); - expect((a | c).value, equals(c.value)); + expect(logic1 | const1, isA()); + expect((logic1 | const1).value, equals(const1.value)); - expect(b | c, isA()); - expect((b | c).value, equals(LogicValue.filled(4, LogicValue.one))); + expect(logicX | const1, isA()); + expect((logicX | const1).value, equals(LogicValue.of('11x1', width: 4))); }); }); From 6f3b23c9b1515c4bf8d04cd12ed451f759e528b1 Mon Sep 17 00:00:00 2001 From: "Korbel, Max" Date: Wed, 15 Jul 2026 13:30:34 -0700 Subject: [PATCH 06/10] further address issues started to be addressed in pr #481 --- CHANGELOG.md | 2 + lib/src/modules/gates.dart | 17 +++- lib/src/signals/const.dart | 128 ++++++++++++++++++++++++- lib/src/signals/logic.dart | 66 ++++++------- test/gate_test.dart | 186 +++++++++++++++++++------------------ 5 files changed, 270 insertions(+), 129 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb1970696..8f24c4b48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Next release +- Improved simulation performance and generated outputs by avoiding module creation for four-state-safe operations involving `Const`s, zero shifts, and muxes with constant controls (). +- Enforced that `Const` values cannot be changed through `put` or `inject` (). - Improved generated SystemVerilog to collapse contiguous partial array and range assignments into packed slice assignments when safe (). - Added configurable explicit or implicit object and data types for generated SystemVerilog ports, defaulting to `input wire logic`, `output var logic`, and `inout wire logic` (). - Improved `Logic.getRange` and `slice` on filled `Const`s to return direct constants instead of constructing `BusSubset` modules (). diff --git a/lib/src/modules/gates.dart b/lib/src/modules/gates.dart index 2780cde9d..9bdc858eb 100644 --- a/lib/src/modules/gates.dart +++ b/lib/src/modules/gates.dart @@ -815,7 +815,22 @@ class LShift extends _ShiftGate { /// ```SystemVerilog /// control ? d1 : d0 /// ``` -Logic mux(Logic control, Logic d1, Logic d0) => Mux(control, d1, d0).out; +/// +/// If [control] is a valid [Const], returns the selected input directly. +Logic mux(Logic control, Logic d1, Logic d0) { + if (control.width != 1) { + throw PortWidthMismatchException(control, 1); + } + if (d0.width != d1.width) { + throw PortWidthMismatchException.equalWidth(d0, d1); + } + + if (control is Const && control.value.isValid) { + return control.value == LogicValue.one ? d1 : d0; + } + + return Mux(control, d1, d0).out; +} /// A mux (multiplexer) module. /// diff --git a/lib/src/signals/const.dart b/lib/src/signals/const.dart index 3885b3d4f..f02a3d191 100644 --- a/lib/src/signals/const.dart +++ b/lib/src/signals/const.dart @@ -10,7 +10,12 @@ part of 'signals.dart'; /// Represents a [Logic] that never changes value. +/// +/// Attempts to assign, [put], or [inject] a new value throw an +/// [UnassignableException]. class Const extends Logic { + static const _unassignableMessage = '`Const` signals are unassignable.'; + /// Constructs a [Const] with the specified value. /// /// [val] should be processable by [LogicValue.of]. @@ -27,9 +32,130 @@ class Const extends Logic { ) { _wire.put(val, fill: fill, signalName: name); - makeUnassignable(reason: '`Const` signals are unassignable.'); + makeUnassignable(reason: _unassignableMessage); } @override Const clone({String? name}) => Const(value, width: width); + + @override + void put(dynamic val, {bool fill = false}) => + throw UnassignableException(this, reason: _unassignableMessage); + + @override + void inject(dynamic val, {bool fill = false}) => + throw UnassignableException(this, reason: _unassignableMessage); + + void _checkMatchingWidth(Logic other) { + if (width != other.width) { + throw PortWidthMismatchException.equalWidth(this, other); + } + } + + @override + Logic operator ~() => Const(~value); + + @override + Logic operator &(Logic other) { + _checkMatchingWidth(other); + + if (other is Const) { + return Const(value & other.value); + } else if (value.isValid && value.isZero) { + return this; + } + + return And2Gate(this, other).out; + } + + @override + Logic operator |(Logic other) { + _checkMatchingWidth(other); + + if (other is Const) { + return Const(value | other.value); + } else if (value.isValid && + value == LogicValue.filled(width, LogicValue.one)) { + return this; + } + + return Or2Gate(this, other).out; + } + + @override + Logic operator ^(Logic other) { + _checkMatchingWidth(other); + + if (other is Const) { + return Const(value ^ other.value); + } + + return Xor2Gate(this, other).out; + } + + @override + Logic operator >>(dynamic other) { + if (Logic._isZeroShiftAmount(other)) { + return this; + } else if (other is Logic && other is! Const) { + return ARShift(this, other).out; + } + + return Const(value >> (other is Const ? other.value : other)); + } + + @override + Logic operator <<(dynamic other) { + if (Logic._isZeroShiftAmount(other)) { + return this; + } else if (other is Logic && other is! Const) { + return LShift(this, other).out; + } + + return Const(value << (other is Const ? other.value : other)); + } + + @override + Logic operator >>>(dynamic other) { + if (Logic._isZeroShiftAmount(other)) { + return this; + } else if (other is Logic && other is! Const) { + return RShift(this, other).out; + } + + return Const(value >>> (other is Const ? other.value : other)); + } + + @override + Logic and() => Const(value.and()); + + @override + Logic or() => Const(value.or()); + + @override + Logic xor() => Const(value.xor()); + + @override + Logic eq(dynamic other) { + if (other is Logic) { + _checkMatchingWidth(other); + return other is Const + ? Const(value.eq(other.value)) + : Equals(this, other).out; + } + + return Const(value.eq(LogicValue.of(other, width: width))); + } + + @override + Logic neq(dynamic other) { + if (other is Logic) { + _checkMatchingWidth(other); + return other is Const + ? Const(value.neq(other.value)) + : NotEquals(this, other).out; + } + + return Const(value.neq(LogicValue.of(other, width: width))); + } } diff --git a/lib/src/signals/logic.dart b/lib/src/signals/logic.dart index aec526947..aa142d35c 100644 --- a/lib/src/signals/logic.dart +++ b/lib/src/signals/logic.dart @@ -442,57 +442,23 @@ class Logic { void operator <=(Logic other) => gets(other); /// Logical bitwise NOT. - Logic operator ~() => this is Const ? Const(~value) : NotGate(this).out; + Logic operator ~() => NotGate(this).out; /// Logical bitwise AND. Logic operator &(Logic other) { - if (this is Const && other is Const) { - return Const((this as Const).value & other.value); - } - Const? constOperand; - Logic? otherOperand; - - if (this is Const && value.isValid) { - constOperand = this as Const; - } else if (other is Const && other.value.isValid) { - constOperand = other; - otherOperand = this; + if (other is Const) { + return other & this; } - if (constOperand != null && otherOperand != null) { - if (constOperand.value == LogicValue.filled(width, LogicValue.zero)) { - return constOperand; - } else if (constOperand.value == - LogicValue.filled(width, LogicValue.one)) { - return otherOperand; - } - } return And2Gate(this, other).out; } /// Logical bitwise OR. Logic operator |(Logic other) { - if (this is Const && other is Const) { - return Const((this as Const).value | other.value); - } - Const? constOperand; - Logic? otherOperand; - - if (this is Const && value.isValid) { - constOperand = this as Const; - } else if (other is Const && other.value.isValid) { - constOperand = other; - otherOperand = this; + if (other is Const) { + return other | this; } - if (constOperand != null && otherOperand != null) { - if (constOperand.value == LogicValue.filled(width, LogicValue.one)) { - return constOperand; - } else if (constOperand.value == - LogicValue.filled(width, LogicValue.zero)) { - return otherOperand; - } - } return Or2Gate(this, other).out; } @@ -524,6 +490,10 @@ class Logic { /// /// If [isNet] and [other] is constant, then the result will also be a net. Logic operator >>(dynamic other) { + if (_isZeroShiftAmount(other)) { + return this; + } + if (isNet) { // many SV simulators don't support shifting of nets, so default this final shamt = _constShiftAmount(other); @@ -544,6 +514,10 @@ class Logic { /// /// If [isNet] and [other] is constant, then the result will also be a net. Logic operator <<(dynamic other) { + if (_isZeroShiftAmount(other)) { + return this; + } + if (isNet) { // many SV simulators don't support shifting of nets, so default this final shamt = _constShiftAmount(other); @@ -564,6 +538,10 @@ class Logic { /// /// If [isNet] and [other] is constant, then the result will also be a net. Logic operator >>>(dynamic other) { + if (_isZeroShiftAmount(other)) { + return this; + } + if (isNet) { // many SV simulators don't support shifting of nets, so default this final shamt = _constShiftAmount(other); @@ -589,6 +567,16 @@ class Logic { } } + static bool _isZeroShiftAmount(dynamic other) { + if (other is Const) { + return other.value.isValid && other.value.isZero; + } else if (other is Logic) { + return false; + } else { + return LogicValue.ofInferWidth(other).isZero; + } + } + /// Unary AND. Logic and() => AndUnary(this).out; diff --git a/test/gate_test.dart b/test/gate_test.dart index fe892f726..5acb24759 100644 --- a/test/gate_test.dart +++ b/test/gate_test.dart @@ -352,124 +352,134 @@ void main() { }); }); - group('Const input Gate test', () { - test('NotGate single bit Constant input', () async { - final normalLogic = Logic(); - final const0 = Const(LogicValue.zero, width: 1); - final constX = Const(LogicValue.x, width: 1); - final const1 = Const(LogicValue.one, width: 1); - normalLogic.put(LogicValue.one); + group('constant gate optimizations', () { + test('Const cannot be mutated', () { + final constant = Const(0); - expect(~const0, isA()); - expect((~const0).value, equals(LogicValue.one)); - - expect(~constX, isA()); - expect((~constX).value, equals(LogicValue.x)); - - expect(~const1, isA()); - expect((~const1).value, equals(LogicValue.zero)); - - expect(~normalLogic, isA()); - expect((~normalLogic).value, equals(LogicValue.zero)); + expect(() => constant.put(1), throwsA(isA())); + expect(() => constant.inject(1), throwsA(isA())); + expect(constant.value, LogicValue.zero); }); - test('NotGate Multi bit Constant input', () async { - final const1 = Const(bin('1111'), width: 4); - final const4 = Const(bin('0100'), width: 4); + test('bitwise NOT folds Const inputs', () { + final result = ~Const(LogicValue.ofString('01xz')); - expect(~const1, isA()); - expect((~const1).value, equals(LogicValue.of(0, width: 4))); + expect(result, isA()); + expect(result.value, LogicValue.ofString('10xx')); - expect(~const4, isA()); - expect((~const4).value, equals(LogicValue.of(11, width: 4))); + final normalResult = ~(Logic()..put(1)); + expect(normalResult, isNot(isA())); + expect(normalResult.value, LogicValue.zero); }); - test('And2Gate Single bit Constant input', () async { - final normalLogic = Logic(); - final const0 = Const(LogicValue.zero); - final logicX = Logic(); - final const1 = Const(LogicValue.one); - normalLogic.put(LogicValue.one); - logicX.put(LogicValue.x); + test('bitwise AND folds only four-state-safe cases', () { + final signal = Logic(width: 4)..put(LogicValue.ofString('10z1')); + final zeros = Const(0, width: 4); + final ones = Const(0xf, width: 4); - expect(normalLogic & const0, isA()); - expect((normalLogic & const0).value, equals(LogicValue.zero)); + expect(signal & zeros, same(zeros)); + expect(zeros & signal, same(zeros)); - expect(logicX & const0, isA()); - expect((logicX & const0).value, equals(LogicValue.zero)); + final identityResult = signal & ones; + expect(identityResult, isNot(same(signal))); + expect(identityResult.value, LogicValue.ofString('10x1')); - expect(normalLogic & const1, isA()); - expect((normalLogic & const1).value, equals(normalLogic.value)); + final folded = Const(LogicValue.ofString('11x1')) & + Const(LogicValue.ofString('1011')); + expect(folded, isA()); + expect(folded.value, LogicValue.ofString('10x1')); - expect(normalLogic & logicX, isA()); - expect((normalLogic & logicX).value, equals(LogicValue.x)); - - expect(const0 & const1, isA()); - expect((const0 & const1).value, equals(const0.value)); + expect(() => Const(0, width: 2) & Logic(), + throwsA(isA())); }); - test('And2Gate Multi bit Constant input', () async { - final const1 = Const(bin('1111'), width: 4); - final const4 = Const(bin('0100'), width: 4); - final logic0 = Logic(width: 4)..put(LogicValue.zero); - final logic1 = Logic(width: 4)..put(LogicValue.one); - final logicX = Logic(width: 4)..put(LogicValue.x); + test('bitwise OR folds only four-state-safe cases', () { + final signal = Logic(width: 4)..put(LogicValue.ofString('01z0')); + final zeros = Const(0, width: 4); + final ones = Const(0xf, width: 4); - expect(const1 & const4, isA()); - expect((const1 & const4).value, equals(LogicValue.of('0100', width: 4))); + expect(signal | ones, same(ones)); + expect(ones | signal, same(ones)); - expect(logic0 & const1, isA()); - expect((logic0 & const1).value, equals(LogicValue.of('0000', width: 4))); + final identityResult = signal | zeros; + expect(identityResult, isNot(same(signal))); + expect(identityResult.value, LogicValue.ofString('01x0')); - expect(logic1 & const1, isA()); - expect((logic1 & const1).value, equals(LogicValue.of('0001', width: 4))); + final folded = Const(LogicValue.ofString('10x0')) | + Const(LogicValue.ofString('0101')); + expect(folded, isA()); + expect(folded.value, LogicValue.ofString('11x1')); - expect(logicX & const1, isA()); - expect((logicX & const1).value, equals(LogicValue.of('xxxx', width: 4))); + expect(() => Const(0, width: 2) | Logic(), + throwsA(isA())); }); - test('OR2Gate Single bit Constant input', () async { - final normalLogic = Logic(); - final const0 = Const(LogicValue.zero); - final logicX = Logic(); - final const1 = Const(LogicValue.one); - normalLogic.put(LogicValue.one); - logicX.put(LogicValue.x); + test('bitwise XOR folds only when both inputs are Const', () { + final folded = Const(LogicValue.ofString('10x0')) ^ + Const(LogicValue.ofString('0101')); + expect(folded, isA()); + expect(folded.value, LogicValue.ofString('11x1')); - expect(normalLogic | const0, isA()); - expect((normalLogic | const0).value, equals(normalLogic.value)); + final signal = Logic(width: 4)..put(LogicValue.ofString('01z0')); + final identityResult = Const(0, width: 4) ^ signal; + expect(identityResult, isNot(same(signal))); + expect(identityResult.value, LogicValue.ofString('01x0')); + }); - expect(logicX | const0, isA()); - expect((logicX | const0).value, equals(logicX.value)); + test('reductions and comparisons fold Const inputs', () { + final value = Const(0xa, width: 4); - expect(normalLogic | const1, isA()); - expect((normalLogic | const1).value, equals(LogicValue.one)); + expect(value.and(), isA()); + expect(value.and().value, LogicValue.zero); + expect(value.or().value, LogicValue.one); + expect(value.xor().value, LogicValue.zero); - expect(normalLogic | logicX, isA()); - expect((normalLogic | logicX).value, equals(LogicValue.one)); + final equal = value.eq(Const(0xa, width: 4)); + final notEqual = value.neq(0xb); + expect(equal, isA()); + expect(equal.value, LogicValue.one); + expect(notEqual, isA()); + expect(notEqual.value, LogicValue.one); - expect(const0 | const1, isA()); - expect((const0 | const1).value, equals(LogicValue.one)); + final unknown = Const(LogicValue.z).eq(Const(LogicValue.z)); + expect(unknown, isA()); + expect(unknown.value, LogicValue.x); }); - test('OR2Gate Multi bit Constant input', () async { - final const1 = Const(bin('1101'), width: 4); - final const4 = Const(bin('0100'), width: 4); - final logic0 = Logic(width: 4)..put(LogicValue.zero); - final logic1 = Logic(width: 4)..put(LogicValue.one); - final logicX = Logic(width: 4)..put(LogicValue.x); + test('shifts by constant zero return the original signal', () { + final signal = Logic(width: 4)..put(LogicValue.ofString('10z1')); - expect(const1 | const4, isA()); - expect((const1 | const4).value, equals(LogicValue.of('1101', width: 4))); + expect(signal << 0, same(signal)); + expect(signal >> Const(0), same(signal)); + expect(signal >>> BigInt.zero, same(signal)); + + final constant = Const(bin('1010'), width: 4); + expect(constant << 0, same(constant)); + + final left = constant << 1; + final right = constant >>> 1; + final arithmeticRight = constant >> 1; + expect(left, isA()); + expect(left.value, LogicValue.ofString('0100')); + expect(right.value, LogicValue.ofString('0101')); + expect(arithmeticRight.value, LogicValue.ofString('1101')); + }); + + test('mux bypasses only valid constant controls', () { + final d0 = Logic(width: 4); + final d1 = Logic(width: 4); - expect(logic0 | const1, isA()); - expect((logic0 | const1).value, equals(const1.value)); + expect(mux(Const(0), d1, d0), same(d0)); + expect(mux(Const(1), d1, d0), same(d1)); - expect(logic1 | const1, isA()); - expect((logic1 | const1).value, equals(const1.value)); + final invalidResult = mux(Const(LogicValue.z), d1, d0); + expect(invalidResult, isNot(anyOf(same(d0), same(d1)))); + expect(invalidResult.value, LogicValue.filled(4, LogicValue.x)); - expect(logicX | const1, isA()); - expect((logicX | const1).value, equals(LogicValue.of('11x1', width: 4))); + expect(() => mux(Const(0, width: 2), d1, d0), + throwsA(isA())); + expect(() => mux(Const(0), Logic(width: 2), d0), + throwsA(isA())); }); }); From 96e90d8b09501b7f76e594e7f9b079ea0b657a49 Mon Sep 17 00:00:00 2001 From: "Korbel, Max" Date: Wed, 15 Jul 2026 13:45:53 -0700 Subject: [PATCH 07/10] more complete addressing of #486 --- CHANGELOG.md | 2 +- lib/src/signals/const.dart | 13 +++++++++--- lib/src/signals/logic.dart | 1 + lib/src/signals/wire.dart | 41 ++++++++++++++++++++++++++++++++++++++ test/gate_test.dart | 35 ++++++++++++++++++++++++++++++-- 5 files changed, 86 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f24c4b48..6d38d263e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## Next release - Improved simulation performance and generated outputs by avoiding module creation for four-state-safe operations involving `Const`s, zero shifts, and muxes with constant controls (). -- Enforced that `Const` values cannot be changed through `put` or `inject` (). +- Enforced that `Const` values cannot be changed through `put` or `inject`, including through another `Logic` driven by a `Const` (). - Improved generated SystemVerilog to collapse contiguous partial array and range assignments into packed slice assignments when safe (). - Added configurable explicit or implicit object and data types for generated SystemVerilog ports, defaulting to `input wire logic`, `output var logic`, and `inout wire logic` (). - Improved `Logic.getRange` and `slice` on filled `Const`s to return direct constants instead of constructing `BusSubset` modules (). diff --git a/lib/src/signals/const.dart b/lib/src/signals/const.dart index f02a3d191..4ddbe05f6 100644 --- a/lib/src/signals/const.dart +++ b/lib/src/signals/const.dart @@ -12,9 +12,13 @@ part of 'signals.dart'; /// Represents a [Logic] that never changes value. /// /// Attempts to assign, [put], or [inject] a new value throw an -/// [UnassignableException]. +/// [UnassignableException], including through another [Logic] driven by the +/// [Const]. class Const extends Logic { - static const _unassignableMessage = '`Const` signals are unassignable.'; + /// The explanation included when an attempt is made to modify a [Const]. + static const _unassignableMessage = + 'A `Const` value cannot be modified, including through another `Logic` ' + 'driven by it.'; /// Constructs a [Const] with the specified value. /// @@ -30,7 +34,9 @@ class Const extends Logic { // we don't care about maintaining this node unless necessary naming: Naming.unnamed, ) { - _wire.put(val, fill: fill, signalName: name); + _wire + ..put(val, fill: fill, signalName: name) + ..makeImmutable(this, reason: _unassignableMessage); makeUnassignable(reason: _unassignableMessage); } @@ -46,6 +52,7 @@ class Const extends Logic { void inject(dynamic val, {bool fill = false}) => throw UnassignableException(this, reason: _unassignableMessage); + /// Verifies that [other] has the same width as this [Const]. void _checkMatchingWidth(Logic other) { if (width != other.width) { throw PortWidthMismatchException.equalWidth(this, other); diff --git a/lib/src/signals/logic.dart b/lib/src/signals/logic.dart index aa142d35c..aca0e5b7c 100644 --- a/lib/src/signals/logic.dart +++ b/lib/src/signals/logic.dart @@ -567,6 +567,7 @@ class Logic { } } + /// Indicates whether [other] represents a known constant shift of zero. static bool _isZeroShiftAmount(dynamic other) { if (other is Const) { return other.value.isValid && other.value.isZero; diff --git a/lib/src/signals/wire.dart b/lib/src/signals/wire.dart index 883e3eaf0..812b09866 100644 --- a/lib/src/signals/wire.dart +++ b/lib/src/signals/wire.dart @@ -26,6 +26,39 @@ class _Wire { /// The current active value of this signal. LogicValue _currentValue; + /// The [Logic] whose immutability makes this wire immutable. + Logic? _immutableOwner; + + /// Additional context explaining why this wire is immutable. + String? _immutableReason; + + /// Makes this wire immutable on behalf of [owner]. + /// + /// If this wire is already immutable, the original owner and reason are + /// preserved. + void makeImmutable(Logic owner, {String? reason}) { + _immutableOwner ??= owner; + _immutableReason ??= reason; + } + + /// Throws an [UnassignableException] if this wire is immutable, identifying + /// [signalName] as the signal through which the update was attempted. + void _assertMutable({required String signalName}) { + final immutableOwner = _immutableOwner; + if (immutableOwner != null) { + final attemptedUpdateReason = + 'Signal "$signalName" cannot be updated because it is driven by ' + 'immutable signal "$immutableOwner".'; + throw UnassignableException( + immutableOwner, + reason: [ + attemptedUpdateReason, + if (_immutableReason != null) _immutableReason, + ].join(' '), + ); + } + } + /// The last value of this signal before the [Simulator] tick. /// /// This is useful for detecting when to trigger an edge. @@ -143,6 +176,11 @@ class _Wire { /// Tells this [_Wire] to adopt all the behavior of [other] so that /// it can replace [other]. Returns the [_Wire] that has adopted everything. _Wire _adopt(_Wire other) { + if (_immutableOwner == null && other._immutableOwner != null) { + _immutableOwner = other._immutableOwner; + _immutableReason = other._immutableReason; + } + _glitchController.emitter.adopt(other._glitchController.emitter); other._migrateChangedTriggers(this); @@ -218,6 +256,7 @@ class _Wire { /// /// This function calls [put()] in [Simulator.injectAction()]. void inject(dynamic val, {required String signalName, bool fill = false}) { + _assertMutable(signalName: signalName); Simulator.injectAction(() => put(val, signalName: signalName, fill: fill)); } @@ -233,6 +272,8 @@ class _Wire { /// This function is used for propagating glitches through connected signals. /// Use this function for custom definitions of [Module] behavior. void put(dynamic val, {required String signalName, bool fill = false}) { + _assertMutable(signalName: signalName); + var newValue = LogicValue.of(val, fill: fill, width: width); if (newValue.width != width) { diff --git a/test/gate_test.dart b/test/gate_test.dart index 5acb24759..2c6835ad1 100644 --- a/test/gate_test.dart +++ b/test/gate_test.dart @@ -112,6 +112,20 @@ class IndexGateTestModule extends Module { } } +/// A module with a signal driven by a [Const]. +class ConstAliasModule extends Module { + /// The constant source protected from mutation. + late final Const constant = Const(0); + + /// A named signal driven by [constant]. + late final Logic alias = constant.named('alias'); + + /// Creates a module whose output is driven by [alias]. + ConstAliasModule() : super(name: 'constaliasmodule') { + addOutput('out') <= alias; + } +} + void main() { tearDown(() async { await Simulator.reset(); @@ -353,12 +367,29 @@ void main() { }); group('constant gate optimizations', () { - test('Const cannot be mutated', () { - final constant = Const(0); + test('Const cannot be mutated', () async { + final module = ConstAliasModule(); + final constant = module.constant; + final alias = module.alias; expect(() => constant.put(1), throwsA(isA())); expect(() => constant.inject(1), throwsA(isA())); + expect( + () => alias.put(1), + throwsA(isA().having( + (exception) => exception.message, + 'message', + allOf( + contains('"${alias.name}" cannot be updated'), + contains('is driven by immutable signal'), + contains('A `Const` value cannot be modified'), + )))); + expect(() => alias.inject(1), throwsA(isA())); expect(constant.value, LogicValue.zero); + expect(alias.value, LogicValue.zero); + + await module.build(); + expect(module.generateSynth(), contains("1'h0")); }); test('bitwise NOT folds Const inputs', () { From 93cdaf87cae3d643feab7a447131bd52b4a63021 Mon Sep 17 00:00:00 2001 From: "Korbel, Max" Date: Wed, 15 Jul 2026 16:08:29 -0700 Subject: [PATCH 08/10] fix tests that changed from optimization --- test/gate_test.dart | 23 +++++++++++++++++------ test/sv_gen_test.dart | 5 ++++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/test/gate_test.dart b/test/gate_test.dart index 2c6835ad1..5c3627b3c 100644 --- a/test/gate_test.dart +++ b/test/gate_test.dart @@ -68,7 +68,8 @@ class Absolute extends Module { class ShiftTestModule extends Module { dynamic constant; // int or BigInt - ShiftTestModule(Logic a, Logic b, {this.constant = 3}) + ShiftTestModule(Logic a, Logic b, + {this.constant = 3, bool useExplicitConstShiftModules = false}) : super(name: 'shifttestmodule') { a = addInput('a', a, width: a.width); b = addInput('b', b, width: b.width); @@ -84,9 +85,15 @@ class ShiftTestModule extends Module { aRshiftB <= a >>> b; aLshiftB <= a << b; aArshiftB <= a >> b; - aRshiftConst <= a >>> constant; - aLshiftConst <= a << constant; - aArshiftConst <= a >> constant; + if (useExplicitConstShiftModules) { + aRshiftConst <= RShift(a, constant).out; + aLshiftConst <= LShift(a, constant).out; + aArshiftConst <= ARShift(a, constant).out; + } else { + aRshiftConst <= a >>> constant; + aLshiftConst <= a << constant; + aArshiftConst <= a >> constant; + } } } @@ -696,8 +703,12 @@ void main() { }); test('shift by const zero', () async { - final gtm = - ShiftTestModule(Logic(width: 3), Logic(width: 8), constant: 0); + final gtm = ShiftTestModule( + Logic(width: 3), + Logic(width: 8), + constant: 0, + useExplicitConstShiftModules: true, + ); await gtm.build(); final sv = gtm.generateSynth(); diff --git a/test/sv_gen_test.dart b/test/sv_gen_test.dart index 13fe8032f..a9875aa6d 100644 --- a/test/sv_gen_test.dart +++ b/test/sv_gen_test.dart @@ -496,7 +496,7 @@ class ModWithPartialArrayAssignment extends Module { class ModWithConstInlineUnaryOp extends Module { ModWithConstInlineUnaryOp() { - addOutput('b', width: 8) <= ~Const(0, width: 8); + addOutput('b', width: 8) <= NotGate(Const(0, width: 8)).out; } } @@ -682,6 +682,9 @@ void main() { test('const unary inline op', () async { final mod = ModWithConstInlineUnaryOp(); await mod.build(); + final sv = mod.generateSynth(); + + expect(sv, contains("~8'h0"), reason: sv); final vectors = [ Vector({}, {'b': 0xff}), From 27f2de4e7ea1e93ef214f7e3b2eb1038ec4b8955 Mon Sep 17 00:00:00 2001 From: "Korbel, Max" Date: Thu, 16 Jul 2026 16:23:57 -0700 Subject: [PATCH 09/10] improve test coverage, improve coverage reporting to include branch --- test/gate_test.dart | 43 +++++++++++++++++++++++++++++++++++++++ tool/generate_coverage.sh | 27 ++++++++++++++++++++---- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/test/gate_test.dart b/test/gate_test.dart index 5c3627b3c..cd50c3df5 100644 --- a/test/gate_test.dart +++ b/test/gate_test.dart @@ -474,16 +474,36 @@ void main() { final equal = value.eq(Const(0xa, width: 4)); final notEqual = value.neq(0xb); + final notEqualConst = value.neq(Const(0xb, width: 4)); expect(equal, isA()); expect(equal.value, LogicValue.one); expect(notEqual, isA()); expect(notEqual.value, LogicValue.one); + expect(notEqualConst, isA()); + expect(notEqualConst.value, LogicValue.one); final unknown = Const(LogicValue.z).eq(Const(LogicValue.z)); expect(unknown, isA()); expect(unknown.value, LogicValue.x); }); + test('comparisons with mutable Logic retain comparison modules', () { + final value = Const(0xa, width: 4); + final mutable = Logic(width: 4)..put(0xa); + + final equal = value.eq(mutable); + final notEqual = value.neq(mutable); + + expect(equal.parentModule, isA()); + expect(notEqual.parentModule, isA()); + expect(equal.value, LogicValue.one); + expect(notEqual.value, LogicValue.zero); + + mutable.put(0xb); + expect(equal.value, LogicValue.zero); + expect(notEqual.value, LogicValue.one); + }); + test('shifts by constant zero return the original signal', () { final signal = Logic(width: 4)..put(LogicValue.ofString('10z1')); @@ -493,6 +513,8 @@ void main() { final constant = Const(bin('1010'), width: 4); expect(constant << 0, same(constant)); + expect(constant >> BigInt.zero, same(constant)); + expect(constant >>> Const(0), same(constant)); final left = constant << 1; final right = constant >>> 1; @@ -503,6 +525,27 @@ void main() { expect(arithmeticRight.value, LogicValue.ofString('1101')); }); + test('Const shifts by mutable Logic retain shift modules', () { + final constant = Const(bin('1010'), width: 4); + final amount = Logic(width: 2)..put(1); + + final left = constant << amount; + final right = constant >>> amount; + final arithmeticRight = constant >> amount; + + expect(left.parentModule, isA()); + expect(right.parentModule, isA()); + expect(arithmeticRight.parentModule, isA()); + expect(left.value, LogicValue.ofString('0100')); + expect(right.value, LogicValue.ofString('0101')); + expect(arithmeticRight.value, LogicValue.ofString('1101')); + + amount.put(2); + expect(left.value, LogicValue.ofString('1000')); + expect(right.value, LogicValue.ofString('0010')); + expect(arithmeticRight.value, LogicValue.ofString('1110')); + }); + test('mux bypasses only valid constant controls', () { final d0 = Logic(width: 4); final d1 = Logic(width: 4); diff --git a/tool/generate_coverage.sh b/tool/generate_coverage.sh index 7717c71ae..ddaebabb4 100755 --- a/tool/generate_coverage.sh +++ b/tool/generate_coverage.sh @@ -34,8 +34,8 @@ done # Remove old coverage data rm -rf coverage -# Run tests with coverage -dart test --coverage=coverage || true +# Run tests with line and branch coverage +dart test --coverage=coverage --branch-coverage || true # Check if coverage was generated if [ ! -d "coverage" ]; then @@ -51,6 +51,25 @@ dart run coverage:format_coverage \ --packages=.dart_tool/package_config.json \ --report-on=lib +# package:coverage emits branch details as BRDA records without BRF/BRH totals, +# which genhtml 2.x does not summarize. Calculate the branch rate directly. +BRANCH_SUMMARY=$(awk -F'[:,]' ' + /^BRDA:/ { + found++ + if ($5 ~ /^[1-9][0-9]*$/) { + hit++ + } + } + END { + if (found == 0) { + print "Error: no branch coverage data found" > "/dev/stderr" + exit 1 + } + printf "%.1f%% (%d of %d branches)", 100 * hit / found, hit, found + } +' coverage/lcov.info) +echo "Branch coverage: $BRANCH_SUMMARY" + # Install lcov if needed if ! command -v lcov &> /dev/null; then if [ -n "${CI:-}" ] || [ -n "${GITHUB_ACTIONS:-}" ]; then @@ -59,8 +78,8 @@ if ! command -v lcov &> /dev/null; then fi fi -# Generate HTML report -genhtml -o coverage/html coverage/lcov.info --branch-coverage +# Generate HTML line coverage report +genhtml -o coverage/html coverage/lcov.info printf '\n%s\n\n' "Open coverage/html/index.html to review code coverage results." # Extract coverage percentage From 8535bebc94e7fe0db077b101a2277241c495e7d5 Mon Sep 17 00:00:00 2001 From: "Korbel, Max" Date: Fri, 17 Jul 2026 13:43:45 -0700 Subject: [PATCH 10/10] fix copyright --- test/gate_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gate_test.dart b/test/gate_test.dart index cd50c3df5..b2c47c21e 100644 --- a/test/gate_test.dart +++ b/test/gate_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2024 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // gate_test.dart