From 5cef449def05c9846c407cc227aa15f0e162da6b Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Thu, 5 Jan 2023 12:49:46 -0800 Subject: [PATCH 1/3] Optimize wave dumping performance with less file io --- benchmark/benchmark.dart | 2 + benchmark/wave_dump_benchmark.dart | 65 ++++++++++++++++++++++++++++++ lib/src/wave_dumper.dart | 13 ++++-- test/benchmark_test.dart | 5 +++ test/config_test.dart | 3 +- 5 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 benchmark/wave_dump_benchmark.dart diff --git a/benchmark/benchmark.dart b/benchmark/benchmark.dart index 5066fa561..d1877d11e 100644 --- a/benchmark/benchmark.dart +++ b/benchmark/benchmark.dart @@ -11,9 +11,11 @@ import 'byte_enable_benchmark.dart'; import 'logic_value_of_benchmark.dart'; import 'pipeline_benchmark.dart'; +import 'wave_dump_benchmark.dart'; void main() async { await PipelineBenchmark().report(); LogicValueOfBenchmark().report(); ByteEnableBenchmark().report(); + await WaveDumpBenchmark().report(); } diff --git a/benchmark/wave_dump_benchmark.dart b/benchmark/wave_dump_benchmark.dart new file mode 100644 index 000000000..593b118e2 --- /dev/null +++ b/benchmark/wave_dump_benchmark.dart @@ -0,0 +1,65 @@ +/// Copyright (C) 2023 Intel Corporation +/// SPDX-License-Identifier: BSD-3-Clause +/// +/// wave_dump_benchmark.dart +/// Benchmarking for wave dumping +/// +/// 2023 January 5 +/// Author: Max Korbel +/// + +import 'dart:io'; + +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:rohd/rohd.dart'; + +class _ModuleToDump extends Module { + _ModuleToDump(Logic d) { + d = addInput('d', d); + + final q = addOutput('q'); + + final clk = SimpleClockGenerator(10).clk; + + for (var i = 0; i < 100; i++) { + addOutput('i$i') <= FlipFlop(clk, ~output('i$i')).q; + } + + q <= FlipFlop(clk, d).q; + } +} + +class WaveDumpBenchmark extends AsyncBenchmarkBase { + late _ModuleToDump _mod; + + static const _vcdTemporaryPath = 'tmp_test/wave_dump_benchmark.vcd'; + + WaveDumpBenchmark() : super('WaveDump'); + + @override + Future setup() async { + Simulator.setMaxSimTime(1000); + + _mod = _ModuleToDump(Logic()); + await _mod.build(); + } + + @override + Future teardown() async { + File(_vcdTemporaryPath).deleteSync(); + await Simulator.reset(); + } + + @override + Future run() async { + WaveDumper(_mod, outputPath: _vcdTemporaryPath); + + await Simulator.run(); + + await Simulator.reset(); + } +} + +Future main() async { + await WaveDumpBenchmark().report(); +} diff --git a/lib/src/wave_dumper.dart b/lib/src/wave_dumper.dart index 216e40b69..dd5793805 100644 --- a/lib/src/wave_dumper.dart +++ b/lib/src/wave_dumper.dart @@ -1,4 +1,4 @@ -/// Copyright (C) 2021 Intel Corporation +/// Copyright (C) 2021-2023 Intel Corporation /// SPDX-License-Identifier: BSD-3-Clause /// /// wave_dumper.dart @@ -18,6 +18,8 @@ import 'package:rohd/src/utilities/uniquifier.dart'; /// /// Outputs to vcd format at [outputPath]. [module] must be built prior to /// attaching the [WaveDumper]. +/// +/// The waves will only dump to the file once the simulation has completed. class WaveDumper { /// The [Module] being dumped. final Module module; @@ -31,6 +33,9 @@ class WaveDumper { /// A sink to write contents into [_outputFile]. late final IOSink _outFileSink; + /// A buffer for contents before writing to the file sink. + final StringBuffer _fileBuffer = StringBuffer(); + /// A counter for tracking signal names in the VCD file. int _signalMarkerIdx = 0; @@ -82,13 +87,15 @@ class WaveDumper { }); } - /// Writes [contents] to the output file. + /// Buffers [contents] to be written to the output file. void _writeToFile(String contents) { - _outFileSink.write(contents); + _fileBuffer.write(contents); } /// Terminates the waveform dumping, including closing the file. Future _terminate() async { + _outFileSink.write(_fileBuffer.toString()); + _fileBuffer.clear(); await _outFileSink.flush(); await _outFileSink.close(); } diff --git a/test/benchmark_test.dart b/test/benchmark_test.dart index d463769ef..3933f8869 100644 --- a/test/benchmark_test.dart +++ b/test/benchmark_test.dart @@ -13,6 +13,7 @@ import 'package:test/test.dart'; import '../benchmark/byte_enable_benchmark.dart'; import '../benchmark/logic_value_of_benchmark.dart'; import '../benchmark/pipeline_benchmark.dart'; +import '../benchmark/wave_dump_benchmark.dart'; void main() { test('pipeline benchmark', () async { @@ -26,4 +27,8 @@ void main() { test('byte enable benchmark', () { ByteEnableBenchmark().measure(); }); + + test('waveform benchmark', () async { + await WaveDumpBenchmark().measure(); + }); } diff --git a/test/config_test.dart b/test/config_test.dart index fc63e4ccb..bfa5ccaee 100644 --- a/test/config_test.dart +++ b/test/config_test.dart @@ -60,10 +60,11 @@ void main() async { createTemporaryDump(mod, dumpName); + await Simulator.run(); + final vcdContents = await File(temporaryDumpPath(dumpName)).readAsString(); expect(vcdContents, contains(version)); - await Simulator.run(); deleteTemporaryDump(dumpName); }); } From 0b6e2b4cd51766c138a4b003f561801e4233444c Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Thu, 5 Jan 2023 13:53:17 -0800 Subject: [PATCH 2/3] fix benchmark and adjust periodic dump for perf --- benchmark/wave_dump_benchmark.dart | 28 ++++++++++++++++---------- lib/src/wave_dumper.dart | 32 +++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/benchmark/wave_dump_benchmark.dart b/benchmark/wave_dump_benchmark.dart index 593b118e2..2d646f283 100644 --- a/benchmark/wave_dump_benchmark.dart +++ b/benchmark/wave_dump_benchmark.dart @@ -14,14 +14,14 @@ import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:rohd/rohd.dart'; class _ModuleToDump extends Module { - _ModuleToDump(Logic d) { + static const _numExtraOutputs = 50; + + _ModuleToDump(Logic d, Logic clk) { d = addInput('d', d); final q = addOutput('q'); - final clk = SimpleClockGenerator(10).clk; - - for (var i = 0; i < 100; i++) { + for (var i = 0; i < _numExtraOutputs; i++) { addOutput('i$i') <= FlipFlop(clk, ~output('i$i')).q; } @@ -31,6 +31,9 @@ class _ModuleToDump extends Module { class WaveDumpBenchmark extends AsyncBenchmarkBase { late _ModuleToDump _mod; + late Logic _clk; + + static const _maxSimTime = 1000; static const _vcdTemporaryPath = 'tmp_test/wave_dump_benchmark.vcd'; @@ -38,25 +41,30 @@ class WaveDumpBenchmark extends AsyncBenchmarkBase { @override Future setup() async { - Simulator.setMaxSimTime(1000); - - _mod = _ModuleToDump(Logic()); - await _mod.build(); + Simulator.setMaxSimTime(_maxSimTime); } @override Future teardown() async { - File(_vcdTemporaryPath).deleteSync(); - await Simulator.reset(); + if (File(_vcdTemporaryPath).existsSync()) { + File(_vcdTemporaryPath).deleteSync(); + } } @override Future run() async { + _clk = SimpleClockGenerator(10).clk; + _mod = _ModuleToDump(Logic(), _clk); + await _mod.build(); + WaveDumper(_mod, outputPath: _vcdTemporaryPath); await Simulator.run(); + assert(Simulator.time == _maxSimTime, 'sim should run through end'); + await Simulator.reset(); + Simulator.setMaxSimTime(_maxSimTime); } } diff --git a/lib/src/wave_dumper.dart b/lib/src/wave_dumper.dart index dd5793805..05e3f7304 100644 --- a/lib/src/wave_dumper.dart +++ b/lib/src/wave_dumper.dart @@ -19,7 +19,8 @@ import 'package:rohd/src/utilities/uniquifier.dart'; /// Outputs to vcd format at [outputPath]. [module] must be built prior to /// attaching the [WaveDumper]. /// -/// The waves will only dump to the file once the simulation has completed. +/// The waves will only dump to the file periodically and then once the +/// simulation has completed. class WaveDumper { /// The [Module] being dumped. final Module module; @@ -87,15 +88,28 @@ class WaveDumper { }); } + /// Number of characters in the buffer after which it will + /// write contents to the output file. + static const _fileBufferLimit = 100000; + /// Buffers [contents] to be written to the output file. - void _writeToFile(String contents) { + void _writeToBuffer(String contents) { _fileBuffer.write(contents); + + if (_fileBuffer.length > _fileBufferLimit) { + _writeToFile(); + } } - /// Terminates the waveform dumping, including closing the file. - Future _terminate() async { + /// Writes all pending items in the [_fileBuffer] to the file. + void _writeToFile() { _outFileSink.write(_fileBuffer.toString()); _fileBuffer.clear(); + } + + /// Terminates the waveform dumping, including closing the file. + Future _terminate() async { + _writeToFile(); await _outFileSink.flush(); await _outFileSink.close(); } @@ -142,7 +156,7 @@ class WaveDumper { \$end \$timescale $timescale \$end '''; - _writeToFile(header); + _writeToBuffer(header); } /// Writes the scope of the VCD, including signal and hierarchy declarations, @@ -151,9 +165,9 @@ class WaveDumper { var scopeString = _computeScopeString(module); scopeString += '\$enddefinitions \$end\n'; scopeString += '\$dumpvars\n'; - _writeToFile(scopeString); + _writeToBuffer(scopeString); _signalToMarkerMap.keys.forEach(_writeSignalValueUpdate); - _writeToFile('\$end\n'); + _writeToBuffer('\$end\n'); } /// Generates the top of the scope string (signal and hierarchy definitions). @@ -191,7 +205,7 @@ class WaveDumper { /// Writes the current timestamp to the VCD. void _captureTimestamp(int timestamp) { final timestampString = '#$timestamp\n'; - _writeToFile(timestampString); + _writeToBuffer(timestampString); _changedLogicsThisTimestamp ..forEach(_writeSignalValueUpdate) @@ -209,7 +223,7 @@ class WaveDumper { : signal.value.toString(includeWidth: false); final marker = _signalToMarkerMap[signal]; final updateString = '$updateValue$marker\n'; - _writeToFile(updateString); + _writeToBuffer(updateString); } } From c5a759c030fcee5878429a72d9f6c403e5416ea3 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Thu, 5 Jan 2023 15:20:20 -0800 Subject: [PATCH 3/3] add a test for max sim time --- test/wave_dumper_test.dart | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/wave_dumper_test.dart b/test/wave_dumper_test.dart index 50f7d84b1..c836e60d3 100644 --- a/test/wave_dumper_test.dart +++ b/test/wave_dumper_test.dart @@ -196,4 +196,27 @@ void main() { deleteTemporaryDump(dumpName); }); + + test('dump after max sim time works', () async { + final a = SimpleClockGenerator(10).clk; + final mod = SimpleModule(a); + await mod.build(); + + const dumpName = 'maxSimTime'; + + createTemporaryDump(mod, dumpName); + + Simulator.setMaxSimTime(100); + + await Simulator.run(); + + final vcdContents = File(temporaryDumpPath(dumpName)).readAsStringSync(); + + expect( + VcdParser.confirmValue(vcdContents, 'a', 99, LogicValue.one), + equals(true), + ); + + deleteTemporaryDump(dumpName); + }); }