From dc38642475bf58612eca83f9d4cca7be4134f969 Mon Sep 17 00:00:00 2001 From: masfiyan Date: Sun, 18 Feb 2024 04:51:16 +0500 Subject: [PATCH 1/5] interactive mode, user adjust the window --- image.svg | 1 + sootty/SoottyConfig.py | 13 +++++ sootty/__main__.py | 6 ++- sootty/visualizer.py | 110 +++++++++++++++++++++++------------------ 4 files changed, 80 insertions(+), 50 deletions(-) create mode 100644 image.svg create mode 100644 sootty/SoottyConfig.py diff --git a/image.svg b/image.svg new file mode 100644 index 0000000..c7d3835 --- /dev/null +++ b/image.svg @@ -0,0 +1 @@ +01234567891011121314151617181920clockio_comp...01io_comp...0io_comp...05io_comp...0io_mat1...0io_mat1...01io_mat1...0io_mat1...0io_mat2...01io_mat2...02io_mat2...05io_mat2...06io_validresetclockdelayi01io_comp...01io_comp...0io_comp...05io_comp...0io_mat1...0io_mat1...01io_mat1...0io_mat1...0io_mat2...01io_mat2...02io_mat2...05io_mat2...06io_validio_vali...j0101matReg1...01matReg1...0matReg1...05matReg1...0reg_0reg_1reset diff --git a/sootty/SoottyConfig.py b/sootty/SoottyConfig.py new file mode 100644 index 0000000..c558b68 --- /dev/null +++ b/sootty/SoottyConfig.py @@ -0,0 +1,13 @@ +class SoottyConfig: + def __init__(self, user_start=None, user_end=None): + self.user_start = user_start + self.user_end = user_end + + def set_user_start(self, user_start): + self.user_start = user_start + + def set_user_end(self, user_end): + self.user_end = user_end + + def get_time_window(self): + return self.user_start, self.user_end diff --git a/sootty/__main__.py b/sootty/__main__.py index aa6ffae..aef0f05 100644 --- a/sootty/__main__.py +++ b/sootty/__main__.py @@ -5,6 +5,7 @@ from .save import save_query, reload_query from .storage import WireTrace from .visualizer import Visualizer +from .SoottyConfig import SoottyConfig def parse_args(): @@ -152,8 +153,11 @@ def main(): if breakpoints is not None: breakpoints = wiretrace.evaluate(breakpoints) + # Create SoottyConfig instance and set user-defined time window + config = SoottyConfig(user_start=0, user_end=20) + # Convert wiretrace to graphical vector image. - image = Visualizer().to_svg( + image = Visualizer(config=config).to_svg( wiretrace, start=start, length=length, diff --git a/sootty/visualizer.py b/sootty/visualizer.py index 30ded29..7ded819 100644 --- a/sootty/visualizer.py +++ b/sootty/visualizer.py @@ -66,9 +66,10 @@ class Debug(Default): class Visualizer: """Converter for wiretrace objects to a svg vector image format.""" - def __init__(self, style=Style.Default): + def __init__(self, style=Style.Default, config=None): """Optionally pass in a style class to control how the visualizer looks.""" self.style = style + self.config = config or SoottyConfig() def to_svg( self, @@ -105,15 +106,22 @@ def to_svg( def _wiretrace_to_svg( self, wiretrace, start, length, wires=None, breakpoints=None, vector_radix=10 ): + user_start, user_end = self.config.get_time_window() width = ( 2 * self.style.LEFT_MARGIN + self.style.TEXT_WIDTH + self.style.FULL_WIDTH ) + # Check if 'wires' is not None and not an integer + if wires is not None and not isinstance(wires, int): + num_wires = len(wires) + else: + num_wires = wiretrace.num_wires() + height = ( 2 * self.style.TOP_MARGIN - self.style.WIRE_MARGIN + (self.style.WIRE_HEIGHT + self.style.WIRE_MARGIN) - * (1 + (len(wires) if wires else wiretrace.num_wires())) + * (1 + (num_wires if wires else wiretrace.num_wires())) ) svg = ( @@ -121,11 +129,12 @@ def _wiretrace_to_svg( f'' ) + # Pass user_start and user_end to relevant functions svg += self._timestamps_to_svg( left=self.style.LEFT_MARGIN + self.style.TEXT_WIDTH, top=self.style.TOP_MARGIN, start=start, - length=length, + length=length ) if not breakpoints: @@ -136,7 +145,7 @@ def _wiretrace_to_svg( top=self.style.TOP_MARGIN, start=start, length=length, - height=height - 2 * self.style.TOP_MARGIN + self.style.WIRE_MARGIN, + height=height - 2 * self.style.TOP_MARGIN + self.style.WIRE_MARGIN ) # Add the root wiregroup to the image. @@ -172,46 +181,51 @@ def _wiretrace_to_svg( return svg def _timestamps_to_svg(self, left, top, start, length): - svg = "" - for index in range(start, start + length, ((length - 1) // 32) + 1): - svg += self._shape_to_svg( - { - "name": "text", - "x": left - + (index - start + 1 / 2) * (self.style.FULL_WIDTH / length), - "y": top + (self.style.WIRE_HEIGHT + self.style.WIRE_MARGIN) / 2, - "class": "small", - "fill": self.style.TEXT_COLOR, - "text-anchor": "middle", - "font-family": "monospace", - "content": index, - } - ) - return svg + user_start, user_end = self.config.get_time_window() - def _breakpoints_to_svg(self, breakpoints, left, top, start, length, height): - """Convert a list of breakpoint times to highlights on the svg.""" svg = "" - for i, index in enumerate(breakpoints): - if index >= start and index < start + length: + for index in range(start, start + length): + if (user_start is None or index >= user_start) and (user_end is None or index <= user_end): svg += self._shape_to_svg( { - "name": "rect", + "name": "text", "x": left - + (index - start) * (self.style.FULL_WIDTH / length) - + self.style.TRANS_START, - "y": top, - "width": (self.style.FULL_WIDTH / length), - "height": height, - "fill": self.style.BREAKPOINT_COLOR_LIST[i % len(self.style.BREAKPOINT_COLOR_LIST)], - "fill-opacity": 0.4, + + (index - start + 1 / 2) * (self.style.FULL_WIDTH / length), + "y": top + (self.style.WIRE_HEIGHT + self.style.WIRE_MARGIN) / 2, + "class": "small", + "fill": self.style.TEXT_COLOR, + "text-anchor": "middle", + "font-family": "monospace", + "content": index, } ) return svg + + def _breakpoints_to_svg(self, breakpoints, left, top, start, length, height): + user_start, user_end = self.config.get_time_window() + """Convert a list of breakpoint times to highlights on the svg.""" + svg = "" + for i, index in enumerate(breakpoints): + if (user_start is None or index >= user_start) and (user_end is None or index <= user_end): + if index >= start and index < start + length: + svg += self._shape_to_svg( + { + "name": "rect", + "x": left + (index - start) * (self.style.FULL_WIDTH / length) + self.style.TRANS_START, + "y": top, + "width": (self.style.FULL_WIDTH / length), + "height": height, + "fill": self.style.BREAKPOINT_COLOR_LIST[i % len(self.style.BREAKPOINT_COLOR_LIST)], + "fill-opacity": 0.4, + } + ) + return svg + def _wiregroup_to_svg( self, wiregroup, left, top, start, length, wires=None, vector_radix=10 ): + user_start, user_end = self.config.get_time_window() svg = "" index = 0 for wire in wiregroup.wires: @@ -232,8 +246,8 @@ def _wiregroup_to_svg( for group in wiregroup.groups: result = self._wiregroup_to_svg( group, - left=left, - top=top + (index * (self.style.WIRE_HEIGHT + self.style.WIRE_MARGIN)), + left=self.style.LEFT_MARGIN, + top=self.style.TOP_MARGIN + self.style.WIRE_HEIGHT + self.style.WIRE_MARGIN, start=start, length=length, wires=wires, @@ -244,6 +258,7 @@ def _wiregroup_to_svg( return svg, index def _wire_to_svg(self, wire, left, top, start, length, vector_radix=10): + user_start, user_end = self.config.get_time_window() svg = self._shape_to_svg( { "name": "text", @@ -253,25 +268,22 @@ def _wire_to_svg(self, wire, left, top, start, length, vector_radix=10): "fill": self.style.TEXT_COLOR, "font-family": "monospace", "content": html.escape( - wire.name - if len(wire.name) <= self.style.LABEL_WIDTH - else wire.name[: max(self.style.LABEL_WIDTH - 3, 0)] + "..." + wire.name if len(wire.name) <= self.style.LABEL_WIDTH else wire.name[: max(self.style.LABEL_WIDTH - 3, 0)] + "...", ), } ) for index in range(start, start + length): - svg += self._value_to_svg( - prev=wire[index - 1] if index > 0 else wire[index], - value=wire[index], - width=wire.width(), - left=left - + ((index - start) * (self.style.FULL_WIDTH / length)) - + self.style.TEXT_WIDTH, - top=top, - length=length, - initial=(index == start), - vector_radix=vector_radix, - ) + if (user_start is None or index >= user_start) and (user_end is None or index <= user_end): + svg += self._value_to_svg( + prev=wire[index - 1] if index > 0 else wire[index], + value=wire[index], + width=wire.width(), + left=left + ((index - start) * (self.style.FULL_WIDTH / length)) + self.style.TEXT_WIDTH, + top=top, + length=length, + initial=(index == start), + vector_radix=vector_radix, + ) return svg class ValueType(Enum): From c931f5f62e622890fa5e375600d7fcbe67e80d06 Mon Sep 17 00:00:00 2001 From: masfiyan Date: Sun, 18 Feb 2024 04:57:01 +0500 Subject: [PATCH 2/5] Delete image --- image.svg | 1 - 1 file changed, 1 deletion(-) delete mode 100644 image.svg diff --git a/image.svg b/image.svg deleted file mode 100644 index c7d3835..0000000 --- a/image.svg +++ /dev/null @@ -1 +0,0 @@ -01234567891011121314151617181920clockio_comp...01io_comp...0io_comp...05io_comp...0io_mat1...0io_mat1...01io_mat1...0io_mat1...0io_mat2...01io_mat2...02io_mat2...05io_mat2...06io_validresetclockdelayi01io_comp...01io_comp...0io_comp...05io_comp...0io_mat1...0io_mat1...01io_mat1...0io_mat1...0io_mat2...01io_mat2...02io_mat2...05io_mat2...06io_validio_vali...j0101matReg1...01matReg1...0matReg1...05matReg1...0reg_0reg_1reset From d276daa43eb55c5540e2b3c274b15729a21e57c6 Mon Sep 17 00:00:00 2001 From: masfiyan Date: Sun, 18 Feb 2024 19:45:48 +0500 Subject: [PATCH 3/5] Implement displaying user-requested visible wires --- sootty/SoottyConfig.py | 9 ++- sootty/__main__.py | 2 +- sootty/visualizer.py | 153 +++++++++++++++++++++++++++-------------- 3 files changed, 110 insertions(+), 54 deletions(-) diff --git a/sootty/SoottyConfig.py b/sootty/SoottyConfig.py index c558b68..9bec47b 100644 --- a/sootty/SoottyConfig.py +++ b/sootty/SoottyConfig.py @@ -1,7 +1,8 @@ class SoottyConfig: - def __init__(self, user_start=None, user_end=None): + def __init__(self, user_start=None, user_end=None, visible_wires=None): self.user_start = user_start self.user_end = user_end + self.visible_wires = visible_wires if visible_wires is not None else [] def set_user_start(self, user_start): self.user_start = user_start @@ -9,5 +10,11 @@ def set_user_start(self, user_start): def set_user_end(self, user_end): self.user_end = user_end + def set_visible_wires(self, visible_wires): + self.visible_wires = visible_wires + def get_time_window(self): return self.user_start, self.user_end + + def get_visible_wires(self): + return self.visible_wires diff --git a/sootty/__main__.py b/sootty/__main__.py index aef0f05..15fe79c 100644 --- a/sootty/__main__.py +++ b/sootty/__main__.py @@ -154,7 +154,7 @@ def main(): breakpoints = wiretrace.evaluate(breakpoints) # Create SoottyConfig instance and set user-defined time window - config = SoottyConfig(user_start=0, user_end=20) + config = SoottyConfig(user_start=10, user_end=20, visible_wires=['D1','Data']) # Convert wiretrace to graphical vector image. image = Visualizer(config=config).to_svg( diff --git a/sootty/visualizer.py b/sootty/visualizer.py index 7ded819..cda8b75 100644 --- a/sootty/visualizer.py +++ b/sootty/visualizer.py @@ -107,6 +107,8 @@ def _wiretrace_to_svg( self, wiretrace, start, length, wires=None, breakpoints=None, vector_radix=10 ): user_start, user_end = self.config.get_time_window() + visible_wires = self.config.get_visible_wires() + width = ( 2 * self.style.LEFT_MARGIN + self.style.TEXT_WIDTH + self.style.FULL_WIDTH ) @@ -160,25 +162,28 @@ def _wiretrace_to_svg( ) svg += result[0] index = result[1] - + # Add each composite wire to the image. if wires is not None: for wire in wires: - svg += self._wire_to_svg( - wiretrace.compute_wire(wire), - left=self.style.LEFT_MARGIN, - top=self.style.TOP_MARGIN - + self.style.WIRE_HEIGHT - + self.style.WIRE_MARGIN - + (index * (self.style.WIRE_HEIGHT + self.style.WIRE_MARGIN)), - start=start, - length=length, - ) - index += 1 + # Check if the wire is in the visible_wires list + if wire in visible_wires: + svg += self._wire_to_svg( + wiretrace.compute_wire(wire), + left=self.style.LEFT_MARGIN, + top=self.style.TOP_MARGIN + + self.style.WIRE_HEIGHT + + self.style.WIRE_MARGIN + + (index * (self.style.WIRE_HEIGHT + self.style.WIRE_MARGIN)), + start=start, + length=length, + ) + index += 1 wires.clear() # TODO: fix temporary solution for catching exceptions - + svg += "" return svg + def _timestamps_to_svg(self, left, top, start, length): user_start, user_end = self.config.get_time_window() @@ -226,22 +231,26 @@ def _wiregroup_to_svg( self, wiregroup, left, top, start, length, wires=None, vector_radix=10 ): user_start, user_end = self.config.get_time_window() + visible_wires = self.config.get_visible_wires() + svg = "" index = 0 for wire in wiregroup.wires: - if wires == None or wire.name in wires: - if wires: # ensure only one copy of the wire is included - wires.remove(wire.name) - svg += self._wire_to_svg( - wire, - left=left, - top=top - + (index * (self.style.WIRE_HEIGHT + self.style.WIRE_MARGIN)), - start=start, - length=length, - vector_radix=vector_radix, - ) - index += 1 + if wires is None or wire.name in wires: + # Check if the wire is in the visible_wires list + if wires is None or wire.name in visible_wires: + if wires: # ensure only one copy of the wire is included + wires.remove(wire.name) + svg += self._wire_to_svg( + wire, + left=left, + top=top + + (index * (self.style.WIRE_HEIGHT + self.style.WIRE_MARGIN)), + start=start, + length=length, + vector_radix=vector_radix, + ) + index += 1 # recursively call function on nested wiregroups for group in wiregroup.groups: result = self._wiregroup_to_svg( @@ -259,32 +268,72 @@ def _wiregroup_to_svg( def _wire_to_svg(self, wire, left, top, start, length, vector_radix=10): user_start, user_end = self.config.get_time_window() - svg = self._shape_to_svg( - { - "name": "text", - "x": left, - "y": top + 15, - "class": "small", - "fill": self.style.TEXT_COLOR, - "font-family": "monospace", - "content": html.escape( - wire.name if len(wire.name) <= self.style.LABEL_WIDTH else wire.name[: max(self.style.LABEL_WIDTH - 3, 0)] + "...", - ), - } - ) - for index in range(start, start + length): - if (user_start is None or index >= user_start) and (user_end is None or index <= user_end): - svg += self._value_to_svg( - prev=wire[index - 1] if index > 0 else wire[index], - value=wire[index], - width=wire.width(), - left=left + ((index - start) * (self.style.FULL_WIDTH / length)) + self.style.TEXT_WIDTH, - top=top, - length=length, - initial=(index == start), - vector_radix=vector_radix, - ) - return svg + visible_wires = self.config.get_visible_wires() + + # Initialize svg + svg = "" + + # Check if visible_wires is None or empty + if visible_wires is None or not visible_wires: + # Show all wires + svg = self._shape_to_svg( + { + "name": "text", + "x": left, + "y": top + 15, + "class": "small", + "fill": self.style.TEXT_COLOR, + "font-family": "monospace", + "content": html.escape( + wire.name if len(wire.name) <= self.style.LABEL_WIDTH else wire.name[: max(self.style.LABEL_WIDTH - 3, 0)] + "...", + ), + } + ) + for index in range(start, start + length): + if (user_start is None or index >= user_start) and (user_end is None or index <= user_end): + # Show waveform for all wires + svg += self._value_to_svg( + prev=wire[index - 1] if index > 0 else wire[index], + value=wire[index], + width=wire.width(), + left=left + ((index - start) * (self.style.FULL_WIDTH / length)) + self.style.TEXT_WIDTH, + top=top, + length=length, + initial=(index == start), + vector_radix=vector_radix, + ) + return svg + elif wire.name in visible_wires: + # Show only specified wires and their waveforms + svg = self._shape_to_svg( + { + "name": "text", + "x": left, + "y": top + 15, + "class": "small", + "fill": self.style.TEXT_COLOR, + "font-family": "monospace", + "content": html.escape( + wire.name if len(wire.name) <= self.style.LABEL_WIDTH else wire.name[: max(self.style.LABEL_WIDTH - 3, 0)] + "...", + ), + } + ) + for index in range(start, start + length): + if (user_start is None or index >= user_start) and (user_end is None or index <= user_end): + # Show waveform only for specified wires + svg += self._value_to_svg( + prev=wire[index - 1] if index > 0 else wire[index], + value=wire[index], + width=wire.width(), + left=left + ((index - start) * (self.style.FULL_WIDTH / length)) + self.style.TEXT_WIDTH, + top=top, + length=length, + initial=(index == start), + vector_radix=vector_radix, + ) + return svg + else: + return "" # Return an empty string for invisible wires class ValueType(Enum): LOW = 0 From 784275904c9ffc1e1d55ea9c75a17f3129617f17 Mon Sep 17 00:00:00 2001 From: masfiyan Date: Mon, 19 Feb 2024 14:58:46 +0500 Subject: [PATCH 4/5] query lang support for user parameters --- README.md | 1 + sootty/__main__.py | 12 ++++++++++-- sootty/parser.py | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8147729..50111ce 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Optional arguments include: - `-S | --save SAVENAME` Saves current query for future reuse. - `-R | --reload SAVENAME` Loads a saved query. Requires query name as string. - `--btable` Print the wire value table at breakpoints to `stdout` (`-b` is required). +- `-p` User parameter for starting time window, ending time window and visible wires to generate waveform *Note: For more detailed information on the query language, check out [syntax.md](syntax.md) diff --git a/sootty/__main__.py b/sootty/__main__.py index 15fe79c..de8fcab 100644 --- a/sootty/__main__.py +++ b/sootty/__main__.py @@ -6,6 +6,7 @@ from .storage import WireTrace from .visualizer import Visualizer from .SoottyConfig import SoottyConfig +from .parser import parameter_query def parse_args(): @@ -97,6 +98,9 @@ def parse_args(): help="Loads a saved query. Requires query name as string.", ) + parser.add_argument('-p', '--parameters', help='Specify parameter query') + + args = parser.parse_args() if args.save is not None and args.reload is not None: raise SoottyError( @@ -119,11 +123,12 @@ def parse_args(): args.end, args.output, args.radix, + args.parameters ) def main(): - filename, wires, breakpoints, btable, length, start, end, output, radix = parse_args() + filename, wires, breakpoints, btable, length, start, end, output, radix, parameters = parse_args() if filename is None: raise SoottyError("Input file is required. See --help for more info.") @@ -154,7 +159,10 @@ def main(): breakpoints = wiretrace.evaluate(breakpoints) # Create SoottyConfig instance and set user-defined time window - config = SoottyConfig(user_start=10, user_end=20, visible_wires=['D1','Data']) + config = SoottyConfig(user_start=None, user_end=None, visible_wires=None) + + if parameters: + config = parameter_query(parameters, config) # Convert wiretrace to graphical vector image. image = Visualizer(config=config).to_svg( diff --git a/sootty/parser.py b/sootty/parser.py index e85aa03..af1edfb 100644 --- a/sootty/parser.py +++ b/sootty/parser.py @@ -82,3 +82,19 @@ def parse_list(self, expressions: str): parser = ExpressionParser() # initialize global parser object + +def parameter_query(parameter_query, config): + key_values = parameter_query.replace('\n', '').split(',') + + for key_value in key_values: + if key_value[0][0] == 's': #start= 0 1 2 3 4 5 + config.set_user_start(int(key_value[6:len(key_value)])) + elif key_value[0][0] == 'e': #end= 0 1 2 3 + config.set_user_end(int(key_value[4:len(key_value)])) + elif key_value[0][0] == 'v': #visible_wires 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + val = key_value[14:len(key_value)] + config.set_visible_wires(val.split(',')) + + return config + + From 6e0ca02244cb2147fff9eca7869b6b218c9d5958 Mon Sep 17 00:00:00 2001 From: masfiyan Date: Mon, 19 Feb 2024 15:52:28 +0500 Subject: [PATCH 5/5] Fix the bug in query lang for user parameter -p --- sootty/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sootty/parser.py b/sootty/parser.py index af1edfb..d8de7c7 100644 --- a/sootty/parser.py +++ b/sootty/parser.py @@ -84,7 +84,7 @@ def parse_list(self, expressions: str): parser = ExpressionParser() # initialize global parser object def parameter_query(parameter_query, config): - key_values = parameter_query.replace('\n', '').split(',') + key_values = parameter_query.replace('\n', '').split(':') for key_value in key_values: if key_value[0][0] == 's': #start= 0 1 2 3 4 5