diff --git a/lib/src/models/memory_model.dart b/lib/src/models/memory_model.dart index 3947db7c6..5eb68a1a0 100644 --- a/lib/src/models/memory_model.dart +++ b/lib/src/models/memory_model.dart @@ -68,7 +68,15 @@ class MemoryModel extends Memory { for (final wrPort in wrPorts) { if (!wrPort.en.previousValue!.isValid && !storage.isEmpty) { // storage doesnt have access to `en`, so check ourselves - storage.invalidWrite(); + if (wrPort.addr.previousValue!.isValid) { + // we only need to clear that address + storage.setData( + wrPort.addr.previousValue!, LogicValue.ofInt(0, dataWidth)); + storage.onInvalidWrite(); + } else { + storage.invalidWrite(); + } + return; } diff --git a/lib/src/models/sparse_memory_storage.dart b/lib/src/models/sparse_memory_storage.dart index 4755fe75e..16039f2a3 100644 --- a/lib/src/models/sparse_memory_storage.dart +++ b/lib/src/models/sparse_memory_storage.dart @@ -10,6 +10,7 @@ import 'package:collection/collection.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/src/exceptions.dart'; import 'package:rohd_hcl/src/utils.dart'; +import 'package:rohd_vf/rohd_vf.dart'; /// A storage for memory models. abstract class MemoryStorage { @@ -28,8 +29,13 @@ abstract class MemoryStorage { /// Default behavior for [onInvalidWrite]. static void _defaultOnInvalidWrite() { - // ignore: avoid_print - print('WARNING: Memory was cleared by invalid write!'); + if (Test.instance != null) { + Test.instance?.logger + .warning('WARNING: Memory was cleared by invalid write!'); + } else { + // ignore: avoid_print + print('WARNING: Memory was cleared by invalid write!'); + } } /// A function called if a read is made to an address that has no data. diff --git a/test/memory_test.dart b/test/memory_test.dart index 460b29e50..909d77ddd 100644 --- a/test/memory_test.dart +++ b/test/memory_test.dart @@ -46,6 +46,7 @@ void main() { alignAddress: (addr) => addr, onInvalidRead: (addr, dataWidth) => LogicValue.filled(dataWidth, LogicValue.zero), + onInvalidWrite: () {}, ), ) }; @@ -54,290 +55,494 @@ void main() { final memGenName = memGen.key; final memGenFunc = memGen.value; - test('$memGenName simple', () async { - const numWr = 3; - const numRd = 3; - - final clk = SimpleClockGenerator(10).clk; - final reset = Logic(); - - final wrPorts = [ - for (var i = 0; i < numWr; i++) - DataPortInterface(dataWidth, addrWidth)..en.put(0) - ]; - final rdPorts = [ - for (var i = 0; i < numRd; i++) - DataPortInterface(dataWidth, addrWidth)..en.put(0) - ]; - - final mem = memGenFunc(clk, reset, wrPorts, rdPorts); - - await mem.build(); - - unawaited(Simulator.run()); - - // a little reset flow - await clk.nextNegedge; - reset.inject(1); - await clk.nextNegedge; - await clk.nextNegedge; - reset.inject(0); - await clk.nextNegedge; - await clk.nextNegedge; - - // write to addr 0x4 on port 0 - wrPorts[0].en.put(1); - wrPorts[0].addr.put(3); - wrPorts[0].data.put(0xdeadbeef); - - await clk.nextNegedge; - wrPorts[0].en.put(0); - await clk.nextNegedge; - - // read it back out on a different port - rdPorts[2].en.put(1); - rdPorts[2].addr.put(3); - await clk.waitCycles(mem.readLatency); - await clk.nextPosedge; - expect(rdPorts[2].data.value.toInt(), 0xdeadbeef); - - await clk.nextNegedge; - rdPorts[2].en.put(0); - await clk.nextNegedge; - - await Simulator.endSimulation(); - }); + group(memGenName, () { + test('simple', () async { + const numWr = 3; + const numRd = 3; - test('$memGenName wr masked', () async { - const numWr = 1; - const numRd = 1; - - final clk = SimpleClockGenerator(10).clk; - final reset = Logic(); - - final wrPorts = [ - for (var i = 0; i < numWr; i++) - MaskedDataPortInterface(dataWidth, addrWidth)..en.put(0) - ]; - final rdPorts = [ - for (var i = 0; i < numRd; i++) - DataPortInterface(dataWidth, addrWidth)..en.put(0) - ]; - - final mem = memGenFunc(clk, reset, wrPorts, rdPorts); - - await mem.build(); - - unawaited(Simulator.run()); - - // a little reset flow - await clk.nextNegedge; - reset.inject(1); - await clk.nextNegedge; - await clk.nextNegedge; - reset.inject(0); - await clk.nextNegedge; - await clk.nextNegedge; - - // write to addr 0x4 on port 0 - wrPorts[0].en.put(1); - wrPorts[0].mask.put(bin('1010')); - wrPorts[0].addr.put(4); - wrPorts[0].data.put(0xffffffff); - - await clk.nextNegedge; - wrPorts[0].en.put(0); - await clk.nextNegedge; - - // read it back out - rdPorts[0].en.put(1); - rdPorts[0].addr.put(4); - await clk.waitCycles(mem.readLatency); - await clk.nextPosedge; - expect(rdPorts[0].data.value.toInt(), 0xff00ff00); - - await clk.nextNegedge; - rdPorts[0].en.put(0); - await clk.nextNegedge; - - await Simulator.endSimulation(); - }); + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); - test('$memGenName driven by flops back to back', () async { - const numWr = 1; - const numRd = 1; + final wrPorts = [ + for (var i = 0; i < numWr; i++) + DataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + final rdPorts = [ + for (var i = 0; i < numRd; i++) + DataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; - final clk = SimpleClockGenerator(10).clk; - final reset = Logic(); + final mem = memGenFunc(clk, reset, wrPorts, rdPorts); - var wrPorts = [ - for (var i = 0; i < numWr; i++) - MaskedDataPortInterface(dataWidth, addrWidth)..en.put(0) - ]; - var rdPorts = [ - for (var i = 0; i < numRd; i++) - DataPortInterface(dataWidth, addrWidth)..en.put(0) - ]; + await mem.build(); - final mem = memGenFunc(clk, reset, wrPorts, rdPorts); + unawaited(Simulator.run()); - await mem.build(); + // a little reset flow + await clk.nextNegedge; + reset.inject(1); + await clk.nextNegedge; + await clk.nextNegedge; + reset.inject(0); + await clk.nextNegedge; + await clk.nextNegedge; - wrPorts = wrPorts.map((oldWrPort) { - final newWrPort = MaskedDataPortInterface(dataWidth, addrWidth) - ..en.put(0); - oldWrPort.ports.forEach((key, value) { - value <= flop(clk, reset: reset, newWrPort.port(key)); + // write to addr 0x3 on port 0 + wrPorts[0].en.put(1); + wrPorts[0].addr.put(3); + wrPorts[0].data.put(0xdeadbeef); + + await clk.nextNegedge; + wrPorts[0].en.put(0); + await clk.nextNegedge; + + // read it back out on a different port + rdPorts[2].en.put(1); + rdPorts[2].addr.put(3); + await clk.waitCycles(mem.readLatency); + await clk.nextPosedge; + expect(rdPorts[2].data.value.toInt(), 0xdeadbeef); + + await clk.nextNegedge; + rdPorts[2].en.put(0); + await clk.nextNegedge; + + await Simulator.endSimulation(); + }); + + group('invalid cases', () { + final badData = memGenName == 'rf' + ? LogicValue.x.replicate(dataWidth) + : LogicValue.ofInt(0, dataWidth); + + test('invalid addr with wren=1 causes memory clear', () async { + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + + final wrPorts = [ + DataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + final rdPorts = [ + DataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + + final mem = memGenFunc(clk, reset, wrPorts, rdPorts); + + await mem.build(); + + unawaited(Simulator.run()); + + // a little reset flow + await clk.nextNegedge; + reset.inject(1); + await clk.nextNegedge; + await clk.nextNegedge; + reset.inject(0); + await clk.nextNegedge; + await clk.nextNegedge; + + // write to addr 0x3 on port 0 + wrPorts[0].en.inject(1); + wrPorts[0].addr.inject(3); + wrPorts[0].data.inject(0xdeadbeef); + + await clk.nextNegedge; + wrPorts[0].en.inject(0); + await clk.nextNegedge; + + // write to addr X, but wren=1 + wrPorts[0].en.inject(1); + wrPorts[0].addr.inject(LogicValue.x); + wrPorts[0].data.inject(0xfeedbaad); + + await clk.nextNegedge; + wrPorts[0].en.inject(0); + await clk.nextNegedge; + + // read it back out + rdPorts[0].en.inject(1); + rdPorts[0].addr.inject(3); + await clk.waitCycles(mem.readLatency); + await clk.nextPosedge; + expect(rdPorts[0].data.value, badData); + + // read another address to see everything bad + rdPorts[0].en.inject(1); + rdPorts[0].addr.inject(2); + await clk.waitCycles(mem.readLatency); + await clk.nextPosedge; + expect(rdPorts[0].data.value, badData); + + await clk.nextNegedge; + rdPorts[0].en.inject(0); + await clk.nextNegedge; + + await Simulator.endSimulation(); }); - return newWrPort; - }).toList(); - rdPorts = rdPorts.map((oldRdPort) { - final newRdPort = DataPortInterface(dataWidth, addrWidth)..en.put(0); - oldRdPort.getPorts([DataPortGroup.control]).forEach((key, value) { - value <= flop(clk, reset: reset, newRdPort.port(key)); + test('invalid wren causes memory at addr clear', () async { + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + + final wrPorts = [ + DataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + final rdPorts = [ + DataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + + final mem = memGenFunc(clk, reset, wrPorts, rdPorts); + + await mem.build(); + + unawaited(Simulator.run()); + + // a little reset flow + await clk.nextNegedge; + reset.inject(1); + await clk.nextNegedge; + await clk.nextNegedge; + reset.inject(0); + await clk.nextNegedge; + await clk.nextNegedge; + + // write to addr 0x3 on port 0 + wrPorts[0].en.inject(1); + wrPorts[0].addr.inject(3); + wrPorts[0].data.inject(0xdeadbeef); + + await clk.nextNegedge; + wrPorts[0].en.inject(0); + await clk.nextNegedge; + + // write to addr X, but wren=1 + wrPorts[0].en.inject(LogicValue.x); + wrPorts[0].addr.inject(2); + wrPorts[0].data.inject(0xfeedbaad); + + await clk.nextNegedge; + wrPorts[0].en.inject(0); + await clk.nextNegedge; + + // read it back out + rdPorts[0].en.inject(1); + rdPorts[0].addr.inject(3); + await clk.waitCycles(mem.readLatency); + await clk.nextPosedge; + expect(rdPorts[0].data.value.toInt(), 0xdeadbeef); + + // read the bad write back out + rdPorts[0].en.inject(1); + rdPorts[0].addr.inject(2); + await clk.waitCycles(mem.readLatency); + await clk.nextPosedge; + expect(rdPorts[0].data.value, badData); + + await clk.nextNegedge; + rdPorts[0].en.inject(0); + await clk.nextNegedge; + + await Simulator.endSimulation(); }); - newRdPort.data <= oldRdPort.data; - return newRdPort; - }).toList(); - - unawaited(Simulator.run()); - - // a little reset flow - await clk.nextPosedge; - reset.inject(1); - await clk.nextPosedge; - await clk.nextPosedge; - reset.inject(0); - await clk.nextPosedge; - await clk.nextPosedge; - - // write to addr 0x4 on port 0 - wrPorts[0].en.inject(1); - wrPorts[0].mask.inject(bin('1010')); - wrPorts[0].addr.inject(4); - wrPorts[0].data.inject(0xffffffff); - - await clk.nextPosedge; - - // write to addr 0x5 on port 0 - wrPorts[0].en.inject(1); - wrPorts[0].mask.inject(bin('0101')); - wrPorts[0].addr.inject(5); - wrPorts[0].data.inject(0x55555555); - - rdPorts[0].en.inject(1); - rdPorts[0].addr.inject(4); - unawaited(clk.waitCycles(mem.readLatency + 1).then((value) async { + + test('invalid wren and addr causes memory clear', () async { + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + + final wrPorts = [ + DataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + final rdPorts = [ + DataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + + final mem = memGenFunc(clk, reset, wrPorts, rdPorts); + + await mem.build(); + + unawaited(Simulator.run()); + + // a little reset flow + await clk.nextNegedge; + reset.inject(1); + await clk.nextNegedge; + await clk.nextNegedge; + reset.inject(0); + await clk.nextNegedge; + await clk.nextNegedge; + + // write to addr 0x3 on port 0 + wrPorts[0].en.inject(1); + wrPorts[0].addr.inject(3); + wrPorts[0].data.inject(0xdeadbeef); + + await clk.nextNegedge; + wrPorts[0].en.inject(0); + await clk.nextNegedge; + + // write to addr X and wren=x + wrPorts[0].en.inject(LogicValue.x); + wrPorts[0].addr.inject(LogicValue.x); + wrPorts[0].data.inject(0xfeedbaad); + + await clk.nextNegedge; + wrPorts[0].en.inject(0); + await clk.nextNegedge; + + // read it back out + rdPorts[0].en.inject(1); + rdPorts[0].addr.inject(3); + await clk.waitCycles(mem.readLatency); + await clk.nextPosedge; + expect(rdPorts[0].data.value, badData); + + // read another address to see everything bad + rdPorts[0].en.inject(1); + rdPorts[0].addr.inject(2); + await clk.waitCycles(mem.readLatency); + await clk.nextPosedge; + expect(rdPorts[0].data.value, badData); + + await clk.nextNegedge; + rdPorts[0].en.inject(0); + await clk.nextNegedge; + + await Simulator.endSimulation(); + }); + }); + + test('wr masked', () async { + const numWr = 1; + const numRd = 1; + + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + + final wrPorts = [ + for (var i = 0; i < numWr; i++) + MaskedDataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + final rdPorts = [ + for (var i = 0; i < numRd; i++) + DataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + + final mem = memGenFunc(clk, reset, wrPorts, rdPorts); + + await mem.build(); + + unawaited(Simulator.run()); + + // a little reset flow await clk.nextNegedge; - expect(rdPorts[0].data.value.toInt(), 0xff00ff00); - })); + reset.inject(1); + await clk.nextNegedge; + await clk.nextNegedge; + reset.inject(0); + await clk.nextNegedge; + await clk.nextNegedge; + + // write to addr 0x4 on port 0 + wrPorts[0].en.put(1); + wrPorts[0].mask.put(bin('1010')); + wrPorts[0].addr.put(4); + wrPorts[0].data.put(0xffffffff); - await clk.nextPosedge; + await clk.nextNegedge; + wrPorts[0].en.put(0); + await clk.nextNegedge; - wrPorts[0].en.inject(0); + // read it back out + rdPorts[0].en.put(1); + rdPorts[0].addr.put(4); + await clk.waitCycles(mem.readLatency); + await clk.nextPosedge; + expect(rdPorts[0].data.value.toInt(), 0xff00ff00); - rdPorts[0].en.inject(1); - rdPorts[0].addr.inject(5); - unawaited(clk.waitCycles(mem.readLatency + 1).then((value) async { await clk.nextNegedge; - expect(rdPorts[0].data.value.toInt(), 0x00550055); - })); + rdPorts[0].en.put(0); + await clk.nextNegedge; - await clk.nextPosedge; + await Simulator.endSimulation(); + }); + + test('driven by flops back to back', () async { + const numWr = 1; + const numRd = 1; + + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + + var wrPorts = [ + for (var i = 0; i < numWr; i++) + MaskedDataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + var rdPorts = [ + for (var i = 0; i < numRd; i++) + DataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + + final mem = memGenFunc(clk, reset, wrPorts, rdPorts); + + await mem.build(); + + wrPorts = wrPorts.map((oldWrPort) { + final newWrPort = MaskedDataPortInterface(dataWidth, addrWidth) + ..en.put(0); + oldWrPort.ports.forEach((key, value) { + value <= flop(clk, reset: reset, newWrPort.port(key)); + }); + return newWrPort; + }).toList(); + + rdPorts = rdPorts.map((oldRdPort) { + final newRdPort = DataPortInterface(dataWidth, addrWidth) + ..en.put(0); + oldRdPort.getPorts([DataPortGroup.control]).forEach((key, value) { + value <= flop(clk, reset: reset, newRdPort.port(key)); + }); + newRdPort.data <= oldRdPort.data; + return newRdPort; + }).toList(); + + unawaited(Simulator.run()); + + // a little reset flow + await clk.nextPosedge; + reset.inject(1); + await clk.nextPosedge; + await clk.nextPosedge; + reset.inject(0); + await clk.nextPosedge; + await clk.nextPosedge; - rdPorts[0].en.inject(0); + // write to addr 0x4 on port 0 + wrPorts[0].en.inject(1); + wrPorts[0].mask.inject(bin('1010')); + wrPorts[0].addr.inject(4); + wrPorts[0].data.inject(0xffffffff); - await clk.waitCycles(10); + await clk.nextPosedge; - await Simulator.endSimulation(); - }); + // write to addr 0x5 on port 0 + wrPorts[0].en.inject(1); + wrPorts[0].mask.inject(bin('0101')); + wrPorts[0].addr.inject(5); + wrPorts[0].data.inject(0x55555555); + + rdPorts[0].en.inject(1); + rdPorts[0].addr.inject(4); + unawaited(clk.waitCycles(mem.readLatency + 1).then((value) async { + await clk.nextNegedge; + expect(rdPorts[0].data.value.toInt(), 0xff00ff00); + })); + + await clk.nextPosedge; + + wrPorts[0].en.inject(0); + + rdPorts[0].en.inject(1); + rdPorts[0].addr.inject(5); + unawaited(clk.waitCycles(mem.readLatency + 1).then((value) async { + await clk.nextNegedge; + expect(rdPorts[0].data.value.toInt(), 0x00550055); + })); + + await clk.nextPosedge; - test('$memGenName random and bursty streaming writes and reads', - () async { - const numWr = 3; - const numRd = numWr; - - final clk = SimpleClockGenerator(10).clk; - final reset = Logic(); - - final wrPorts = [ - for (var i = 0; i < numWr; i++) - MaskedDataPortInterface(dataWidth, addrWidth)..en.put(0) - ]; - final rdPorts = [ - for (var i = 0; i < numRd; i++) - DataPortInterface(dataWidth, addrWidth)..en.put(0) - ]; - - final mem = memGenFunc(clk, reset, wrPorts, rdPorts); - - await mem.build(); - - unawaited(Simulator.run()); - - // a little reset flow - await clk.nextPosedge; - reset.inject(1); - await clk.nextPosedge; - await clk.nextPosedge; - reset.inject(0); - await clk.nextPosedge; - await clk.nextPosedge; - - final rand = Random(123); - - for (var i = 0; i < 100; i++) { - for (var p = 0; p < numWr; p++) { - final rdPort = (p + 1) % numRd; - final rdDelay = rdPort + 1; - - if (i % numWr == p) { - wrPorts[p].en.inject(0); - - unawaited(clk.waitCycles(rdDelay).then((value) { - rdPorts[rdPort].en.inject(0); - })); - } else { - final addr = (i * numWr + p) % numEntries; - final data = rand.nextLogicValue(width: dataWidth); - final mask = rand.nextLogicValue(width: 4); - - wrPorts[p].en.inject(1); - wrPorts[p].addr.inject(addr); - wrPorts[p].data.inject(data); - wrPorts[p].mask.inject(mask); - - unawaited(clk.waitCycles(rdDelay).then((value) async { - rdPorts[rdPort].en.inject(1); - rdPorts[rdPort].addr.inject(addr); - - await clk.waitCycles(mem.readLatency); - - await clk.nextNegedge; - - final rdData = rdPorts[rdPort].data.value; - for (var m = 0; m < mask.width; m++) { - if (mask[m].toBool()) { - final actual = rdData.getRange(m * 8, (m + 1) * 8); - final expected = data.getRange(m * 8, (m + 1) * 8); - expect( - actual, - expected, - reason: '@${Simulator.time} byte $m on rd port $rdPort: ' - 'was $actual, expected $expected', - ); + rdPorts[0].en.inject(0); + + await clk.waitCycles(10); + + await Simulator.endSimulation(); + }); + + test('random and bursty streaming writes and reads', () async { + const numWr = 3; + const numRd = numWr; + + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + + final wrPorts = [ + for (var i = 0; i < numWr; i++) + MaskedDataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + final rdPorts = [ + for (var i = 0; i < numRd; i++) + DataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + + final mem = memGenFunc(clk, reset, wrPorts, rdPorts); + + await mem.build(); + + unawaited(Simulator.run()); + + // a little reset flow + await clk.nextPosedge; + reset.inject(1); + await clk.nextPosedge; + await clk.nextPosedge; + reset.inject(0); + await clk.nextPosedge; + await clk.nextPosedge; + + final rand = Random(123); + + for (var i = 0; i < 100; i++) { + for (var p = 0; p < numWr; p++) { + final rdPort = (p + 1) % numRd; + final rdDelay = rdPort + 1; + + if (i % numWr == p) { + wrPorts[p].en.inject(0); + + unawaited(clk.waitCycles(rdDelay).then((value) { + rdPorts[rdPort].en.inject(0); + })); + } else { + final addr = (i * numWr + p) % numEntries; + final data = rand.nextLogicValue(width: dataWidth); + final mask = rand.nextLogicValue(width: 4); + + wrPorts[p].en.inject(1); + wrPorts[p].addr.inject(addr); + wrPorts[p].data.inject(data); + wrPorts[p].mask.inject(mask); + + unawaited(clk.waitCycles(rdDelay).then((value) async { + rdPorts[rdPort].en.inject(1); + rdPorts[rdPort].addr.inject(addr); + + await clk.waitCycles(mem.readLatency); + + await clk.nextNegedge; + + final rdData = rdPorts[rdPort].data.value; + for (var m = 0; m < mask.width; m++) { + if (mask[m].toBool()) { + final actual = rdData.getRange(m * 8, (m + 1) * 8); + final expected = data.getRange(m * 8, (m + 1) * 8); + expect( + actual, + expected, + reason: + '@${Simulator.time} byte $m on rd port $rdPort: ' + 'was $actual, expected $expected', + ); + } } - } - })); + })); + } } + await clk.nextPosedge; } - await clk.nextPosedge; - } - await clk.waitCycles(mem.readLatency + numWr + 1); + await clk.waitCycles(mem.readLatency + numWr + 1); - await Simulator.endSimulation(); + await Simulator.endSimulation(); + }); }); } });