Added option for reduction of long clock sequences in wavedrom converter

Signed-off-by: Wojciech Gryncewicz <wgryncewicz@antmicro.com>
diff --git a/scripts/python-skywater-pdk/skywater_pdk/cells/generate/vcd2wavedrom.py b/scripts/python-skywater-pdk/skywater_pdk/cells/generate/vcd2wavedrom.py
index ed9b43b..7e4adf1 100755
--- a/scripts/python-skywater-pdk/skywater_pdk/cells/generate/vcd2wavedrom.py
+++ b/scripts/python-skywater-pdk/skywater_pdk/cells/generate/vcd2wavedrom.py
@@ -18,6 +18,7 @@
 import argparse
 import pathlib
 import wavedrom
+import re
 from contextlib import contextmanager
 
 
@@ -44,7 +45,7 @@
 
 
 def readVCD (file):
-    ''' Parses VCD file. 
+    ''' Parses VCD file.
 
     Args:
         file - path to a VCD file [pathlib.Path]
@@ -67,12 +68,12 @@
             # tag, other than end
             if not line.startswith('$end'):
                 currtag = line.partition(' ')[0].lstrip('$').rstrip()
-                vcd[currtag] = vcd.setdefault(currtag, '') + line.partition(' ')[2].rpartition('$')[0]               
+                vcd[currtag] = vcd.setdefault(currtag, '') + line.partition(' ')[2].rpartition('$')[0]
             # line ends with end tag
                 if not vcd[currtag].endswith('\n'):
-                     vcd[currtag] += '\n'                          
+                     vcd[currtag] += '\n'
             if line.split()[-1]=='$end':
-                currtag = 'body'    
+                currtag = 'body'
                 vcd[currtag] = ''
 
     if 'var' not in vcd:
@@ -83,13 +84,39 @@
 
     return vcd
 
-def parsetowavedrom (file, savetofile = False):
+
+def reduce_clock_sequences (wave) :
+    ''' Remove clock seqnces longer than 2 cycles
+        not accompanied by other signals changes
+
+    Parameters:
+        wave - dictionary 'signal'->['list of states'] [dict]
+    '''
+    for v in wave:
+        sig   = wave[v] # analized signal
+        other = [wave[i] for i in wave if i!=v] # list of other signals
+        other = [''.join(s) for s in zip(*other)]  # list of concatenated states
+        other = [len(s.replace('.','')) for s in other] # list of state changes count
+        sig   = [s if o==0 else ' ' for s,o in zip(sig,other)] # keep only when no changes in other
+        sig   = "".join(sig)
+        cuts  = []
+        for m in re.finditer("(10){2,}",sig):
+            cuts.append( (m.start()+1, m.end()-1) ) # area to be reduced, leave 1..0
+        cuts.reverse()
+        for cut in cuts:
+            for v,w in wave.items():              # reduce cuts from all signals
+                wave[v] = w[ :cut[0]] + w[cut[1]: ]
+
+    return wave
+
+
+def parsetowavedrom (file, savetofile = False, reduce_clock = False):
     ''' Reads and simplifies VCD waveform
         Generates wavedrom notation.
 
     Args:
         file - path to a VCD file [pathlib.Path]
-    
+
     '''
     varsubst = {} # var substitution
     reg    = []   # list of signals
@@ -117,21 +144,21 @@
 
     # set initial states
     event.append(0)
-    #default 
+    #default
     for v in reg+wire:
         wave[v] = ['x']
     #defined
-    for line in vcd['dumpvars'].split('\n'): 
+    for line in vcd['dumpvars'].split('\n'):
         if len(line)>=2:
             wave[ varsubst[line[1]] ] = [line[0]]
 
-    # parse wave body        
+    # parse wave body
     for line in vcd['body'].split('\n'):
         #timestamp line
         if line.startswith('#'):
             line = line.strip().lstrip('#')
             if not line.isnumeric():
-                raise SyntaxError("Invalid VCD timestamp")      
+                raise SyntaxError("Invalid VCD timestamp")
             event.append(int(line))
             for v in wave.keys():
                 wave[v].append('.')
@@ -139,11 +166,12 @@
         else :
             if len(line)>=2:
                 wave [ varsubst[line[1]] ][-1] = line[0]
-            
-    # TODO: add "double interval support"
+
+    if reduce_clock:
+        wave = reduce_clock_sequences(wave)
 
     signals  = []
-    for v in wave.keys():     
+    for v in wave.keys():
         fill    = ' ' * (max( [len(s) for s in wave.keys()] ) - len(v))
         wavestr = ''.join(wave[v])
         signals.append( signal_template.format( name = v, wave = wavestr, fill = fill ) )
@@ -151,15 +179,18 @@
 
     wavedrom = wavedrom_template.format ( signals = signals )
 
-    outfile = file.with_suffix(".wdr.json") if savetofile else None       
+    outfile = file.with_suffix(".wdr.json") if savetofile else None
     with file_or_stdout(outfile) as f:
         f.write(wavedrom)
-    
+
     return wavedrom
 
 def quoted_strings_wavedrom (wdr) :
     ''' Convert wavedrom script to more restrictive
         version of JSON with quoted keywords
+
+    Parameters:
+        wdr - wavedrom script [str]
     '''
     wdr = wdr.replace(' signal:',' "signal":')
     wdr = wdr.replace(' name:',' "name":')
@@ -191,6 +222,11 @@
             help="generate .svg image",
             action="store_true")
     parser.add_argument(
+            "-r",
+            "--reduceclk",
+            help="reduce clock sequences",
+            action="store_true")
+    parser.add_argument(
             "infile",
             help="VCD waveform file",
             type=pathlib.Path,
@@ -209,10 +245,10 @@
     errors = 0
     for f in infile:
         try:
-            wdr = parsetowavedrom(f, args.wavedrom)
+            wdr = parsetowavedrom(f, args.wavedrom, args.reduceclk)
             if args.savesvg:
                 svg = wavedrom.render( quoted_strings_wavedrom(wdr) )
-                outfile = f.with_suffix(".svg")  
+                outfile = f.with_suffix(".svg")
                 svg.saveas(outfile)
         except KeyboardInterrupt:
             sys.exit(1)