#!/usr/bin/perl
# Read CUPL source and emit equivalent Verilog.
#
# Edit History:
# 10/23/2021
# Fixed a missing "&" in the operator check for whether negation
# must include parenthesis around the negated subexpression.
# Fixed a problem with input statements more than a single line long.
# Introduced a regression, where bogus text is injected in the output,
# related to the interaction of the searches for "*/" and ";".
# 10/22/2021
# Fixed a missing assignment for "T" style flops which do not have
# an asynchronous component.
# 10/15/2021
# Fixed a missing "|" in the operator check for whether negation
# must include parenthesis around the negated subexpression.
#
#$edebug = 1;
open(INPUT, $ARGV[0]) || die "$ARGV[0]: $!";
#
# First, a pass that looks for assignments.
# Note what's written, what's read, and which
# sequential modifiers got used.
$l = "";
%isin = %isout = %islatch = ();
while () {
s/\r*//g;
y/A-Z/a-z/; # Kludge monocase for now
die "$_" if /[A-Z]/;
$l .= $_;
#
# Maybe it is/has a comment.
$ec = index($l, "*/");
if ($ec) {
# Locate the beginning of the comment.
$sc = index($l, "/*");
# For the first pass, elide the comment.
substr($l, $sc, $ec-$sc+2, "");
}
# BUGBUG: Doesn't work correctly if the comment contains things
# that look like statements, but the "*/" is on a later line.
next unless $l =~ /;/;
# For now, just ignore compiler directives.
$l =~ s/^\n*[\$].*//;
# Look for a CUPL statement.
while ($l =~ s/^([^;]*);//) {
$s = $1;
$s =~ s/\n/ /g;
$s =~ s/^\s*//g;
next unless $s;
#
# We have a whole statement.
#
# Parse pin designations, which otherwise look like
# an assignment. Just ignoring them for this pass.
next if $s =~ s/pin\s+(\d*)\s*=\s*(\S*)\s*$//i;
#
# Maybe it's an assignment!
if ($s =~ s/(\S+)\s+=\s*(.*)$//i) {
($lh, $rh) = ($1, $2);
# Check $lh for inversion.
$rh = "!($rh)" if $lh =~ s/^!//;
# We use the expression rewrite code to
# note which nets are read.
$indent = "assign $lh = "; $indent =~ s/./ /g;
#warn "$lh: $rh" if $lh eq 'skip';
#warn "$lh: $rh" if $lh eq 'rx7';
$rh = &expression($indent);
#warn "$lh: $rh" if $lh eq 'rx7';
#die "$lh: $rh" if $lh eq 'skip';
# Check $lh for a qualifier.
if ($lh =~ /^(.*)[.](.*)$/) {
($out, $in) = ($1, $2);
$isout{$out} = 1;
$ldone{$out} = 0;
# Try not to remember constant zeroes for .ar and .ap
next if ($in =~ /^a[pr]$/) && ($rh eq "1'b0");
$islatch{$out} .= "$in ";
} else {
$isout{$lh} = 1;
}
# Remember all assignments in case ".oe" occurs later.
warn "assign{$lh} = $rh\n" if $lh =~ /^rx7/;
$assign{$lh} = $rh;
}
}
}
#
# Now begin translation in earnest.
seek(INPUT, 0, 0) || die "$ARGV[0]: $!";
$l = "";
@iface = ();
@nodes = ();
while () {
s/\r*//g;
y/A-Z/a-z/; # Kludge monocase for now
die "$_" if /[A-Z]/;
$l .= $_;
#
# Maybe it is/has a comment.
$ec = index($l, "*/");
if ($ec != -1) {
# Locate the beginning of the comment.
$sc = index($l, "/*");
# Rewrite the comment and output it
$c = substr($l, $sc, $ec-$sc+2, "");
$c =~ s?\n?\n//?g;
$c =~ s?/[*]?//?;
$c =~ s?[*]/??;
print "$c\n";
}
# BUGBUG: Doesn't work correctly if the comment contains things
# that look like statements, but the "*/" is on a later line.
next unless $l =~ /;/;
#warn "$l" if $l =~ /rx_div/;
# For now, just ignore compiler directives.
$l =~ s/^\n+[\$].*//;
# Look for a CUPL statement.
while ($l =~ s/^([^;]*);//) {
$s = $1;
$s =~ s/\n/ /g;
$s =~ s/^\s*//g;
next unless $s;
#warn "s:$s\n";
#
# We have a whole statement.
#
# Remember the module name.
if ($s =~ s/Name\s+(\S+)\s*$//i) {
$module = $1;
open(PINMAP, ">$module.pins") || die "$madule.pins: $!";
next;
}
#
# Ignore various other fluff.
# It might be better to convert these to comments?
next if $s =~ s/PartNo\s+(\S+)\s*$//i;
next if $s =~ s/Date\s+(\S+)\s*$//i;
next if $s =~ s/Revision\s+(\S+)\s*$//i;
next if $s =~ s/Designer\s+(.+)$//i;
next if $s =~ s/Company\s+(.+)$//i;
next if $s =~ s/Assembly\s+(\S+)\s*$//i;
next if $s =~ s/Location\s+(\S+)\s*$//i;
next if $s =~ s/Device\s+(\S+)\s*$//i;
next if $s =~ s/property\s+(.+)$//i;
#
# Parse pin designations, for the interface list.
if ($s =~ s/pin\s+(\d*)\s*=\s*(\S*)\s*$//i) {
# BUGBUG: For now, we toss the pin number, but we
# really should output it to a constraints file.
($pad, $net) = ($1, $2);
push(@iface, $net);
# M8340.pins:
# set_location_assignment PIN_$pad -to $net
print PINMAP "set_location_assignment PIN_$pad -to $net\n" || die "PINMAP";
next;
}
#
# Parse node designations. Just remember them until after
# the module declaration is emitted.
if ($s =~ s/node\s+(\S*)\s*$//i) {
$node = $1;
push(@nodes, $1);
next;
}
#
# Maybe it's an assignment! The first assignment triggers
# output of the module declaration, as well as the saved up nodes.
#warn "as:$s\n";
if ($s =~ s/(\S+)\s+=\s*(.*)$//i) {
($lh, $rh) = ($1, $2);
if (@iface) {
print "module $module (", join(", ", @iface), ");\n";
foreach $pin (@iface) {
$isreg = "";
if (defined $islatch{$pin}) {
$isreg = " reg" unless $islatch{$pin} eq "oe ";
}
if (defined $isin{$pin}) {
if (defined $isout{$pin}) {
print "inout$isreg $pin;\n";
} else {
print "input $pin;\n";
}
} else {
print "output$isreg $pin;\n";
}
}
print "\n";
foreach $out (sort keys %islatch) {
print "reg ${out}_m;\n" if (defined $assign{"$out.ap"}) || (defined $assign{"$out.ar"});
}
print "\n";
# Clear @iface to prevent repitition.
@iface = ();
}
# Finally, output the wire declarations.
foreach $node (@nodes) {
if (defined $islatch{$node}) {
print "reg $node;\n";
} else {
print "wire $node;\n";
}
}
@nodes = ();
#warn "Got $lh = $rh";
# Check $lh for inversion.
$rh = "!($rh)" if $lh =~ s/^!//;
# Check $lh for a qualifier.
if ($lh =~ /^(.*)[.](.*)$/) {
($out, $in) = ($1, $2);
next if $ldone{$out};
# To map a qualified assignment, it is necessary to know
# which qualifiers were actually used. These were all
# remembered in the previous pass.
if (defined $assign{"$out.oe"}) {
# Output enables are relatively easy. We assume here
# that the output is otherwise unqualified. (That is, the
# qualifiers are exactly "oe ".)
die "OE qualifier on FF output $out" unless $islatch{$out} eq "oe ";
$t1 = "~($assign{$lh})"; $t1 =~ s/\s*//g;
$t2 = $assign{$out}; $t2 =~ s/\s*//g;
# warn "$out: !($assign{$lh}) ne $assign{$out}" unless $t1 eq $t2;
# warn "$out: .oe not caused by OC" unless $t1 eq $t2;
$assign{$out} = "1'b0" if $t1 eq $t2;
$indent = "assign $out = "; $indent =~ s/./ /g;
$rh = &expression($indent);
$rh =~ s/\b$out\b/($assign{$out})/g;
print "assign $out = $rh? $assign{$out}: 1'bz;\n";
} elsif ((defined $assign{"$out.ar"}) || (defined $assign{"$out.ap"})) {
# It's a FF with async preset, clear, or both.
# Model it as master-slave latches:
# if out.ar then out_m = 0 else
# if out.ap then out_m = 1 else
# if !out.ck then out_m = out.d;
# if out.ar then out = 0 else
# if out.ap then out = 1 else
# if out.ck then out = out_m;
next if $ldone{$out};
$events = $assign{"$out.ck"};
$events .= " " . $assign{"$out.ar"} if defined $assign{"$out.ar"};
$events .= " " . $assign{"$out.ap"} if defined $assign{"$out.ap"};
$events .= " " . $assign{"$out.d"} if defined $assign{"$out.d"};
$events .= " " . $assign{"$out.j"} if defined $assign{"$out.j"};
$events .= " " . $assign{"$out.k"} if defined $assign{"$out.k"};
$events .= " $out" if defined $assign{"$out.j"};
$events .= " $out" if defined $assign{"$out.t"};
#warn "($events)\n";
$events =~ s/\d+'b[01]+\s//g;
$events =~ s/[()&|^~]//g;
$events =~ s/\s+$//g;
$events =~ s/\s+/, /g;
#warn "229 ($events)\n";
print "always @($events)\n";
if (defined $assign{"$out.ar"}) {
print " if (", $assign{"$out.ar"}, ") begin\n";
print " ${out}_m <= 1'b0;\n";
print " end else\n";
}
if (defined $assign{"$out.ap"}) {
print " if (", $assign{"$out.ap"}, ") begin\n";
print " ${out}_m <= 1'b1;\n";
print " end else\n";
}
print " if (~(", $assign{"$out.ck"}, ")) begin\n";
# j-k, t, or d?
if (defined $assign{"$out.d"}) {
$dexp = $assign{"$out.d"};
die "$out.j" if defined $assign{"$out.j"};
die "$out.k" if defined $assign{"$out.k"};
die "$out.t" if defined $assign{"$out.k"};
} elsif (defined $assign{"$out.t"}) {
$dexp = $assign{"$out.t"} . " == 1'b1? ~$out: $out";
# Optimize a common case for readability.
$dexp = "~$out" if ($assign{"$out.t"} eq "1'b1");
die "$out.j" if defined $assign{"$out.j"};
die "$out.k" if defined $assign{"$out.k"};
} else {
die "no $out.j" unless defined $assign{"$out.j"};
die "no $out.k" unless defined $assign{"$out.k"};
$dexp = $assign{"$out.j"} . "? (" . $assign{"$out.k"} . "? ~$out: 1'b1) : (" . $assign{"$out.k"} . "? 1'b0: $out)";
}
print " ${out}_m <= ", $dexp, ";\n";
print " end\n";
$events = $assign{"$out.ck"};
$events .= " " . $assign{"$out.ar"} if defined $assign{"$out.ar"};
$events .= " " . $assign{"$out.ap"} if defined $assign{"$out.ap"};
$events .= " ${out}_m";
#warn "($events)\n";
$events =~ s/\d+'b[01]+\s//g;
$events =~ s/[()&|^~]//g;
$events =~ s/\s+$//g;
$events =~ s/\s+/, /g;
#warn "270 $out ($events)\n";
print "always @($events)\n";
if (defined $assign{"$out.ar"}) {
print " if (", $assign{"$out.ar"}, ") begin\n";
print " ${out} <= 1'b0;\n";
print " end else\n";
}
if (defined $assign{"$out.ap"}) {
print " if (", $assign{"$out.ap"}, ") begin\n";
print " ${out} <= 1'b1;\n";
print " end else\n";
}
print " if (", $assign{"$out.ck"}, ") begin\n";
print " ${out} <= ${out}_m;\n";
print " end\n";
$ldone{$out} = 1;
} else {
# It's a FF without async component.
# always @(posedge out.ck) out = out.d;
next if $ldone{$out};
$events = "posedge " . $assign{"$out.ck"};
$events =~ s/posedge/negedge/ if $events =~ s/[~]//;
#warn "292 $out ($events)\n";
print "always @($events)\n";
print " if (", $assign{"$out.ck"}, ") begin\n";
if (defined $assign{"$out.d"}) {
$dexp = $assign{"$out.d"};
} elsif (defined $assign{"$out.t"}) {
$t = $assign{"$out.t"};
$dexp = "$t? ~$out: $out";
$dexp = "~$out" if $t eq "1'b1";
} elsif (defined $assign{"$out.j"}) {
$j = $assign{"$out.j"};
$k = $assign{"$out.k"};
# d = j? k? ~q: 1: k? 0: q;
$dexp = "$j? $k? ~$out: 1'b1: $k? 1'b0: $out";
}
print " $out <= ", $dexp, ";\n";
print " end\n";
$ldone{$out} = 1;
}
$ldone{$out} = 1;
} else {
# Simple assignment. Rewrite $rh
# and emit an assignment.
next if defined $islatch{$lh};
$indent = "assign $lh = "; $indent =~ s/./ /g;
$rh = &expression($indent);
print "assign $lh = $rh;\n";
}
next;
}
}
}
print "endmodule\n";
#
# Expression parser/translator follows.
# We also do simple constant folding here.
#
# BUGBUG: What is the Verilog XOR operator?
$edebug = 0;
sub term {
local($indent) = @_;
local($term);
# The term is a number, identifier or a parenthesised expression.
if ($rh =~ s/^\s*[(]//) {
$term = &expression("$indent ");
warn "term: expression returned: ($term)\n" if $edebug;
# If there are no operators, skip the parentheses.
# BUGBUG: Fix this with an official operator list!
if ($term =~ /[|&~*^+]/) {
$term = "($term)";
}
$rh =~ s/^[)]//;
} else {
$rh =~ s/^\s*([^!\$)\s;]+)\s*//;
$term = $1;
$term = length($1) . "'b$1" if $term =~ /^'b'(.*)$/;
$isin{$term} = 1;
#warn "input: $term\n";
}
return $term;
}
sub nterm {
local($indent) = @_;
# The nterm is a term or a negated nterm.
if ($rh =~ s/^\s*[!]\s*//) {
local($nterm) = "~" . &nterm("$indent ");
$nterm =~ s/^~~//;
$nterm =~ s/^~1'b0\b/1'b1/;
$nterm =~ s/^~1'b1\b/1'b0/;
return $nterm;
} else {
return &term($indent);
}
}
sub pterm {
local($indent) = @_;
local($lo) = &nterm($indent);
local($ro);
# A pterm is an nterm possibly followed by "&" and more nterms.
while ($rh =~ s/^\s*[&]\s*//) {
# Since we don't newline, must adjust indentation.
$tindent = $lo; $tindent =~ s/./ /g;
$ro = &nterm("$indent $tindent");
warn "pterm: lo: $lo, ro: $ro\n" if $edebug;
if ($lo eq "1'b0") {
# $ro does not affect the result.
} elsif ($lo eq "1'b1") {
# $ro is the result.
$lo = $ro;
} elsif ($ro eq "1'b0") {
# $ro is the result.
$lo = $ro;
} elsif ($ro eq "1'b1") {
# $ro does not affect the result.
} elsif ($ro eq $lo) {
# $ro does not affect the result.
} else {
# Both $lo and $ro affect the result.
$lo .= " & $ro";
}
}
return $lo;
}
sub orterm {
local($indent) = @_;
local($lo) = &pterm($indent);
local($ro);
# A orterm is an pterm possibly followed by "#" and more pterms.
while ($rh =~ s/^\s*[#]\s*//) {
$ro = &pterm("$indent ");
warn "orterm: lo: $lo, ro: $ro\n" if $edebug;
if ($lo eq "1'b0") {
# $ro is the result.
$lo = $ro;
} elsif ($lo eq "1'b1") {
# $ro does not affect the result.
} elsif ($ro eq "1'b0") {
# $ro does not affect the result.
} elsif ($ro eq "1'b1") {
# $ro is the result.
$lo = $ro;
} elsif ($ro eq $lo) {
# $ro does not affect the result.
} else {
# Both $lo and $ro affect the result.
$lo .= "\n$indent | $ro";
}
}
return $lo;
}
sub expression {
local($indent) = @_;
local($lo) = &orterm($indent);
local($ro);
# An expression is an orterm possibly followed by "$" and more orterms.
while ($rh =~ s/^\s*\$\s*//) {
$ro = &orterm("$indent ");
warn "expression: lo: $lo, ro: $ro\n" if $edebug;
if ($lo eq "1'b0") {
# $ro is the result.
$lo = $ro;
} elsif ($lo eq "1'b1") {
# !$ro is the result.
$lo = "~$ro";
} elsif ($ro eq "1'b0") {
# $ro does not affect the result.
} elsif ($ro eq "1'b1") {
# !$lo is the result.
$lo = "~$lo";
} else {
# Both $lo and $ro affect the result.
$lo .= "\n$indent ^ $ro";
}
}
return $lo;
}