#!/usr/bin/perl

# Copyright (C) 1999, 2000, 2001, 2002 Marco Roveri.
# Originally written by Marco Roveri <roveri@fbk.eu>

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.

# Accepts a circuit description in ISCAS'89 format and outputs an
# equivalent SMV model. Notice that the current version does not
# support OUTPUT iscas89 constructs. They are parsed, but they are not
# translated into SMV since not clear which is the corresponding
# construct in SMV language.

use Getopt::Std;

my $version = "1b -- Jan 1, 2001";

my %smv_reserved = (
                    'MODULE' => 1,
                    'DEFINE' => 1,
                    'INIT'   => 1,
                    'INVAR'  => 1,
                    'TRANS'  => 1,
                    'VAR'    => 1,
                    'IVAR'   => 1,
                    'next'   => 1,
                    'init'   => 1,
                    'process' => 1,
                    'TRUE'    => 1,
                    'FALSE'   => 1
                    );

my %state_renamed;

sub help {
    print STDERR "ISCAS'89 to SMV file converter by roveri\@fbk.eu\n";
    print STDERR "Version $version\n";
    print STDERR "Usage: iscas2cnf [-h] [-d] [-o file] [-f file]\n";
    print STDERR "\t -h      This help message\n";
    print STDERR "\t -d      Run debug mode\n";
    print STDERR "\t -o file Specify a file to save the SMV model.\n";
    print STDERR "\t -f file Specify a file to read in ISCAS89 model.\n";
    print STDERR "If neither -f nor -o are used, then STDIN, STDOUT respectively are used\n";
    print STDERR "States with the same name of a SMV token are prefixed with \"_smv_\"\n";
    print STDERR "Output iscas89 constructs are parsed but not yet handled.\n";
    exit;
}

sub die_and_remove {
    my ($message) = @_;
    if ( defined($opt_o) ) {
        unlink "$opt_o";
    }
    die "$message\n";  
}

# Each occurence of "." in a ISCAS89 name is replaced with "_dot_"
sub escape_dot {
    my ($state) = @_;
    while($state =~ /^([\w_]+)\.([\w_.]+)/) {
        $state = "$1_dot_$2";
    }
    return($state);
}

# Escapes a ISCAS89 name which corresponds to a SMV reserved word with
# the prefix '_smv_'. If the name contains a . it is replaced with "_dot_"
sub escape_state {
    my ($state) = @_;
    my $oldstate = $state;

    if (defined($smv_reserved{$state})) {
        $state = "_smv_$state";
    }
    $state = escape_dot($state);
    $state_renamed{$oldstate} = $state if (not ($state eq $oldstate));
    return($state);
}

sub print_mapping {
    my (%map) = @_;
    my $i = 0;
    foreach $k (keys %map) {
        print STDOUT "\n\n-- State Name Mapping for conflicting state names\n" if ($i++ eq 0);
        print STDOUT "-- $k -> $map{$k} \n";
    }
    print STDOUT "\n";
}

sub print_list {
    local ($g_type, @nodes) = @_;

    for ($i = 0; $i<=$#nodes; $i++) {
        print STDOUT "$g_type $nodes[$i] " if !($nodes[$i] eq "");
    }
}

sub print_list_x {
    local ($f, $g_type, @nodes) = @_;

    for ($i = $f; $i<=$#nodes; $i++) {
        print STDOUT "$g_type ", escape_state($nodes[$i]), " ",  if !($nodes[$i] eq "") ;
    }
}

sub print_rs {
    local ($g_type, @nodes) = @_;

    if ($g_type eq "not") {
        die "NOT gate with more than one input\n" unless ($#nodes <= 0);
        print STDOUT "!", escape_state($nodes[0]);
        return;
    }
    if ($g_type eq "dff") {
        die "DFF gate with more than one input\n" unless ($#nodes <= 0);
        print STDOUT escape_state($nodes[0]);
        return;
    }
    if ($g_type eq "and") {
        die "AND gate with less than one input\n" unless ($#nodes >= 1);
        print STDOUT escape_state($nodes[0]);
        print_list_x(1, "&", @nodes);
        return;
    }
    if ($g_type eq "nand") {
        # NAND(a,b,c...) <-> ! AND(a,b,c...)
        die "NAND gate with less than one input\n" unless ($#nodes >= 1);
        print STDOUT "!(" , escape_state($nodes[0]), " ";
        print_list_x(1, "&", @nodes);
        print STDOUT ")";
        return;
    }
    if ($g_type eq "nor") {
        # NOR(a,b,c...) <-> ! OR(a,b,c...)
        die "NOR gate with less than one input\n" unless ($#nodes >= 1);
        print STDOUT "!(" , escape_state($nodes[0]), " ";
        print_list_x(1, "|", @nodes);
        print STDOUT ")";
        return;
    }
    if ($g_type eq "or") {
        die "OR gate with less than one input\n" unless ($#nodes >= 1);
        print STDOUT escape_state($nodes[0]);
        print_list_x(1, "|", @nodes);
        return;
    }
    if ($g_type eq "xor") {
        die "XOR gate with less than one input\n" unless ($#nodes >= 1);
        print STDOUT escape_state($nodes[0]);
        print_list_x(1,"xor", @nodes);
        return;
    }
    if ($g_type eq "xnor") {
        die "XNOR gate with less than one input\n" unless ($#nodes >= 1);
        print STDOUT escape_state($nodes[0]);
        print_list_x(1, "xnor", @nodes);
        return;
    }
    print STDOUT " $g_type TO BE IMPLEMENTED \n";
    return;
}

sub main {
    print STDOUT "-- Generated with a translator from ISCAS89 to SMV by\n";
    print STDOUT "-- Marco Roveri roveri\@fbk.eu\n";
    print STDOUT "MODULE main\n";
    while(<STDIN>) {
        if ($opt_d) { print STDERR "Read $_"; }

        print STDOUT "-- $_"; 
        # Ignores trailing comments
        if (/^#/ || /^ #/) {
            next;
        }
        # Ignores trailing comments
        if (/^[\s\t]*$/) {
            print STDOUT "\n";
            next;
        }

        # Define input variables
        if (/INPUT\s*\(\s*([\w\._]+)\s*\)/) {
            if ($opt_d) { print STDERR "INPUT = $1\n"; }
            print STDOUT "IVAR\n\t", escape_state($1), ": boolean;\n";
            next;
        }
        elsif (/input\s*\(\s*([\w\._]+)\s*\)/) {
            if ($opt_d) { print STDERR "INPUT = $1\n"; }
            print STDOUT "IVAR\n\t", escape_state($1), ": boolean;\n";
            next;
        }
        elsif (/OUTPUT\s*\(\s*([\w\._]+)\s*\)/) {
            if ($opt_d) { print STDERR "OUTPUT = $1\n"; }
            $out_node{$1} = 0;
            next;
        }
        elsif (/output\s*\(\s*([\w\._]+)\s*\)/) {
            if ($opt_d) { print STDERR "OUTPUT = $1\n"; }
            $out_node{$1} = 0;
            next;
        }

        if (/([\w\._]+)\s*=\s*(\w+)\s*\(\s*(.+)\s*\)/) {
            
            $out_node = $1;
            $g_type = $2;
            $in_node_str = $3;

            # lowercase the operator
            $g_type =~ tr/A-Z/a-z/;

            @inp_def = split(/\s*,\s*/, $in_node_str);

            @tmp_def = split(/\s+/, $inp_def[$#inp_def]); # Remove trailing spaces
            $inp_def[$#inp_def] = $tmp_def[0];


            if ($g_type eq "dff") {
                # A FLip Flop has been read
                print STDOUT "VAR\n\t", escape_state($1), " : boolean;\n";
                print STDOUT "ASSIGN\n\tnext(", escape_state($1), ") := ";
                print_rs($g_type, @inp_def);
                print STDOUT ";\n";
            } else {
                # A combinatorial input has been read
                print STDOUT "DEFINE\n\t", escape_state($1), " := ";
                print_rs($g_type, @inp_def);
                print STDOUT ";\n";
            }
            next;
        } else {
            die "ERROR: \"$_\" undefined construct\n";
        }
    }
}

###############################################################
&getopts('o:f:hd') || &help;

&help if ($opt_h);

if ( defined($opt_o) ) {
    open(STDOUT,"> $opt_o") or
    die "ERROR: opening file \"$opt_o\" for writing.";
}

if ( defined($opt_f) ) {
    open(STDIN,"< $opt_f") or
    die_and_remove("ERROR: opening file \"$opt_f\" for reading.");
}

print STDERR "Warning!!! Outputs not yet handled\n";

&main;

print_mapping(%state_renamed);
