# This is a manual translation, from perl to python, of :
# http://cpansearch.perl.org/src/GSULLIVAN/Verilog-VCD-0.03/lib/Verilog/VCD.pm 

import re

global timescale
global endtime

def croak(*args):
  """Function similar to Perl's Carp::croak, to simplify porting this code"""
  a = "".join(args);
  raise Exception(a)

def list_sigs(file) :
    """Parse input VCD file into data structure, 
    then return just a list of the signal names."""

    vcd = parse_vcd(file, only_sigs=1)

    sigs = []
    for k,v in vcd:
        nets = v['nets']
        sigs.extend( n['hier']+n['name'] for n in net )
    
    return sigs


def parse_vcd(file, only_sigs=0, use_stdout=0, siglist=[], opt_timescale=''):
    """Parse input VCD file into data structure.
    Also, print t-v pairs to STDOUT, if requested."""

    usigs = {}
    for i in siglist:
        usigs[i] = 1

    if len(usigs):
        all_sigs = 0
    else :
        all_sigs = 1

    data = {}
    mult = 0
    num_sigs = 0
    hier = []
    time = 0

    re_time    = re.compile(r"^#(\d+)")
    re_1b_val  = re.compile(r"^([01zx])(.+)")
    re_Nb_val  = re.compile(r"^[br](\S+)\s+(.+)")

    fh = open(file, 'r')
    while True:
        line = fh.readline()
        if line == '' : # EOF
          break

        # chomp
        # s/ ^ \s+ //x
        line = line.strip()

        if "$enddefinitions" in line:
            num_sigs = len(data)
            if (num_sigs == 0) :
                if (all_sigs) :
                    croak("Error: No signals were found in the VCD file file.",
                          'Check the VCD file for proper var syntax.')
                
                else :
                    croak("Error: No matching signals were found in the VCD file file.",
                          ' Use list_sigs to view all signals in the VCD file.')
                
            
            if ((num_sigs>1) and use_stdout) :
                croak("Error: There are too many signals (num_sigs) for output ",
                      'to STDOUT.  Use list_sigs to select a single signal.')
            
            if only_sigs:
                break


        elif "$timescale" in line:
            statement = line
            if not "$end" in line:
                while fh :
                    line = fh.readline()
                    statement += line
                    if "$end" in line:
                      break
            
            mult = calc_mult(statement, opt_timescale)
        

        elif "$scope" in line:
            # assumes all on one line
            #   $scope module dff end
            hier.append( line.split()[2] ) # just keep scope name
        
        elif "$upscope" in line:
            hier.pop()
        
        elif "$var" in line:
            # assumes all on one line:
            #   $var reg 1 *@ data $end
            #   $var wire 4 ) addr [3:0] $end
            ls = line.split()
            type = ls[1]
            size = ls[2]
            code = ls[3]
            name = "".join(ls[4:-1])
            path = '.'.join(hier)
            full_name = path + name
            if (full_name in usigs) or all_sigs :
              if code not in data :
                data[code] = {}
              if 'nets' not in data[code]:
                data[code]['nets'] = []
              var_struct = {
                  'type' : type,
                  'name' : name,
                  'size' : size,
                  'hier' : path,
               } 
              if var_struct not in data[code]['nets']:
                data[code]['nets'].append( var_struct )
        

        elif line.startswith('#'):
            re_time_match   = re_time.match(line)
            time = mult * int(re_time_match.group(1))
            endtime = time
        

        elif line.startswith(('0', '1', 'x', 'z', 'b', 'r')):
            re_1b_val_match = re_1b_val.match(line)
            re_Nb_val_match = re_Nb_val.match(line)
            if re_Nb_val_match :
              value = re_Nb_val_match.group(1)
              code  = re_Nb_val_match.group(2)
            elif re_1b_val_match :
              value = re_1b_val_match.group(1)
              code  = re_1b_val_match.group(2)
            if (code in data) :
                if (use_stdout) :
                    print time, value
                else :
                    if 'tv' not in data[code]:
                      data[code]['tv'] = []
                    data[code]['tv'].append( (time, value) )
                
            
        
    
    fh.close()

    return data


def calc_mult (statement, opt_timescale=''):
    """ 
    Calculate a new multiplier for time values.
    Input statement is complete timescale, for example:
      timescale 10ns end
    Input new_units is one of s|ms|us|ns|ps|fs.
    Return numeric multiplier.
    Also sets the package timescale variable.
    """ 


    fields = statement.split()
    fields.pop()   # delete end from array
    fields.pop(0)  # delete timescale from array
    tscale = ''.join(fields)

    new_units = ''
    if (opt_timescale != ''):
        new_units = opt_timescale.lower()
        new_units = re.sub(r"\s", '', new_units)
        timescale = "1"+new_units
    
    else :
        timescale = tscale
        return 1
    

    mult = 0
    units = 0
    ts_match = re.compile(r"(\d+)([a-z]+)")
    if ts_match.match(tscale):
        mult  = ts_match.group(1)
        units = ts_match.group(2).lower()
    
    else :
        croak("Error: Unsupported timescale found in VCD file: tscale.  ",
              'Refer to the Verilog LRM.')
    

    mults = {
        'fs' : 1e-15,
        'ps' : 1e-12,
        'ns' : 1e-09,
        'us' : 1e-06,
        'ms' : 1e-03,
         's' : 1e-00,
    }
    mults_keys = keys(mults)
    mults_keys.sort(key=lambda x : mults[x])
    usage = '|'.join(mults_keys)

    scale = 0
    if units in mults :
        scale = mults[units]
    
    else :
        croak("Error: Unsupported timescale units found in VCD file: "+units+".  ",
              "Supported values are: "+usage)
    

    new_scale = 0
    if new_units in mults :
        new_scale = mults[new_units]
    
    else :
        croak("Error: Illegal user-supplied timescale: "+new_units+".  ",
              "Legal values are: "+usage)
    

    return ((mult * scale) / new_scale)


def get_timescale() :
    return timescale


def get_endtime() :
    return endtime



# =head1 NAME
# 
# Verilog_VCD - Parse a Verilog VCD text file
# 
# =head1 VERSION
# 
# This document refers to Verilog::VCD version 0.03.
# 
# =head1 SYNOPSIS
# 
#     from Verilog_VCD import parse_vcd
#     vcd = parse_vcd('/path/to/some.vcd')
# 
# =head1 DESCRIPTION
# 
# Verilog is a Hardware Description Language (HDL) used to model digital logic.
# While simulating logic circuits, the values of signals can be written out to
# a Value Change Dump (VCD) file.  This module can be used to parse a VCD file
# so that further analysis can be performed on the simulation data.  The entire
# VCD file can be stored in a python data structure and manipulated using
# standard hash and array operations.
# 
# =head2 Input File Syntax
# 
# The syntax of the VCD text file is described in the documentation of
# the IEEE standard for Verilog.  Only the four-state VCD format is supported.
# The extended VCD format (with strength information) is not supported.
# Since the input file is assumed to be legal VCD syntax, only minimal
# validation is performed.
# 
# =head1 SUBROUTINES
# 
# 
# =head2 parse_vcd(file, $opt_ref)
# 
# Parse a VCD file and return a reference to a data structure which
# includes hierarchical signal definitions and time-value data for all
# the specified signals.  A file name is required.  By default, all
# signals in the VCD file are included, and times are in units
# specified by the C<$timescale> VCD keyword.
# 
#     vcd = parse_vcd('/path/to/some.vcd')
# 
# It returns a reference to a nested data structure.  The top of the
# structure is a Hash-of-Hashes.  The keys to the top hash are the VCD
# identifier codes for each signal.  The following is an example
# representation of a very simple VCD file.  It shows one signal named
# C<chip.cpu.alu.clk>, whose VCD code is C<+>.  The time-value pairs
# are stored as an Array-of-Tuples, referenced by the C<tv> key.  The
# time is always the first number in the pair, and the times are stored in
# increasing order in the array.
# 
#     {
#       '+' : {
#                'tv' : [
#                          (
#                            0,
#                            '1'
#                          ),
#                          (
#                            12,
#                            '0'
#                          ),
#                        ],
#                'nets' : [
#                            {
#                              'hier' : 'chip.cpu.alu.',
#                              'name' : 'clk',
#                              'type' : 'reg',
#                              'size' : '1'
#                            }
#                          ]
#              }
#     }
# 
# Since each code could have multiple hierarchical signal names, the names are
# stored as an Array-of-Hashes, referenced by the C<nets> key.  The example above
# only shows one signal name for the code.
# 
# 
# =head3 OPTIONS
# 
# Options to C<parse_vcd> should be passed as a hash reference.
# 
# =over 4
# 
# =item timescale
# 
# It is possible to scale all times in the VCD file to a desired timescale.
# To specify a certain timescale, such as nanoseconds:
# 
#     vcd = parse_vcd(file, opt_timescale='ns'})
# 
# Valid timescales are:
# 
#     s ms us ns ps fs
# 
# =item siglist
# 
# If only a subset of the signals included in the VCD file are needed,
# they can be specified by a signal list passed as an array reference.
# The signals should be full hierarchical paths separated by the dot
# character.  For example:
# 
#     signals = [
#         'top.chip.clk',
#         'top.chip.cpu.alu.status',
#         'top.chip.cpu.alu.sum[15:0]',
#     ]
#     vcd = parse_vcd(file, siglist=signals)
# 
# Limiting the number of signals can substantially reduce memory usage of the
# returned data structure because only the time-value data for the selected
# signals is loaded into the data structure.
# 
# =item use_stdout
# 
# It is possible to print time-value pairs directly to STDOUT for a
# single signal using the C<use_stdout> option.  If the VCD file has
# more than one signal, the C<siglist> option must also be used, and there
# must only be one signal specified.  For example:
# 
#     vcd = parse_vcd(file, 
#                     use_stdout=1,
#                     siglist=['top.clk']
#                 )
# 
# The time-value pairs are output as space-separated tokens, one per line.
# For example:
# 
#     0 x
#     15 0
#     277 1
#     500 0
# 
# Times are listed in the first column.
# Times units can be controlled by the C<timescale> option.
# 
# =item only_sigs
# 
# Parse a VCD file and return a reference to a data structure which
# includes only the hierarchical signal definitions.  Parsing stops once
# all signals have been found.  Therefore, no time-value data are
# included in the returned data structure.  This is useful for
# analyzing signals and hierarchies.
# 
#     vcd = parse_vcd(file, only_sigs=1)
# 
# =back
# 
# 
# =head2 list_sigs(file)
# 
# Parse a VCD file and return a list of all signals in the VCD file.
# Parsing stops once all signals have been found.  This is
# helpful for deciding how to limit what signals are parsed.
# 
# Here is an example:
# 
#     signals = list_sigs('input.vcd')
# 
# The signals are full hierarchical paths separated by the dot character
# 
#     top.chip.cpu.alu.status
#     top.chip.cpu.alu.sum[15:0]
# 
# =head2 get_timescale( )
# 
# This returns a string corresponding to the timescale as specified
# by the C<$timescale> VCD keyword.  It returns the timescale for
# the last VCD file parsed.  If called before a file is parsed, it
# returns an undefined value.  If the C<parse_vcd> C<timescale> option
# was used to specify a timescale, the specified value will be returned
# instead of what is in the VCD file.
# 
#     vcd = parse_vcd(file); # Parse a file first
#     ts  = get_timescale();  # Then query the timescale
# 
# =head2 get_endtime( )
# 
# This returns the last time found in the VCD file, scaled
# appropriately.  It returns the last time for the last VCD file parsed.
# If called before a file is parsed, it returns an undefined value.
# 
#     vcd = parse_vcd(file); # Parse a file first
#     et  = get_endtime();    # Then query the endtime
# 
# =head1 EXPORT
# 
# Nothing is exported by default.  Functions may be exported individually, or
# all functions may be exported at once, using the special tag C<:all>.
# 
# =head1 DIAGNOSTICS
# 
# Error conditions cause the program to raise an Exception.
# 
# =head1 LIMITATIONS
# 
# Only the following VCD keywords are parsed:
# 
#     $end                $scope
#     $enddefinitions     $upscope
#     $timescale          $var
# 
# The extended VCD format (with strength information) is not supported.
# 
# The default mode of C<parse_vcd> is to load the entire VCD file into the
# data structure.  This could be a problem for huge VCD files.  The best solution
# to any memory problem is to plan ahead and keep VCD files as small as possible.
# When simulating, dump fewer signals and scopes, and use shorter dumping
# time ranges.  Another technique is to parse only a small list of signals
# using the C<siglist> option; this method only loads the desired signals into
# the data structure.  Finally, the C<use_stdout> option will parse the input VCD
# file line-by-line, instead of loading it into the data structure, and directly
# prints time-value data to STDOUT.  The drawback is that this only applies to
# one signal.
# 
# =head1 BUGS
# 
# There are no known bugs in this module.
# 
# =head1 SEE ALSO
# 
# Refer to the following Verilog documentation:
# 
#     IEEE Standard for Verilog (c) Hardware Description Language
#     IEEE Std 1364-2005
#     Section 18.2, "Format of four-state VCD file"
# 
# =head1 AUTHOR
# 
# Originally written in Perl by Gene Sullivan (gsullivan@cpan.org)
# Translated into Python by Sameer Gauria (sgauria+python@gmail.com)
# 
# =head1 COPYRIGHT AND LICENSE
# 
# Copyright (c) 2012 Gene Sullivan, Sameer Gauria.  All rights reserved.
# 
# This module is free software; you can redistribute it and/or modify
# it under the same terms as Perl itself.  See L<perlartistic|perlartistic>.
# 
# =cut

